|
1 YUI.add('tabview', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 * The TabView module |
|
5 * |
|
6 * @module tabview |
|
7 */ |
|
8 |
|
9 var DOT = '.', |
|
10 |
|
11 /** |
|
12 * Provides a tabbed widget interface |
|
13 * @param config {Object} Object literal specifying tabview configuration properties. |
|
14 * |
|
15 * @class TabView |
|
16 * @constructor |
|
17 * @extends Widget |
|
18 * @uses WidgetParent |
|
19 */ |
|
20 TabView = Y.Base.create('tabView', Y.Widget, [Y.WidgetParent], { |
|
21 |
|
22 _afterChildAdded: function() { |
|
23 this.get('contentBox').focusManager.refresh(); |
|
24 }, |
|
25 |
|
26 _defListNodeValueFn: function() { |
|
27 var node = Y.Node.create(this.LIST_TEMPLATE); |
|
28 |
|
29 node.addClass(Y.TabviewBase._classNames.tabviewList); |
|
30 |
|
31 return node; |
|
32 }, |
|
33 |
|
34 _defPanelNodeValueFn: function() { |
|
35 var node = Y.Node.create(this.PANEL_TEMPLATE); |
|
36 |
|
37 node.addClass(Y.TabviewBase._classNames.tabviewPanel); |
|
38 |
|
39 return node; |
|
40 }, |
|
41 |
|
42 _afterChildRemoved: function(e) { // update the selected tab when removed |
|
43 var i = e.index, |
|
44 selection = this.get('selection'); |
|
45 |
|
46 if (!selection) { // select previous item if selection removed |
|
47 selection = this.item(i - 1) || this.item(0); |
|
48 if (selection) { |
|
49 selection.set('selected', 1); |
|
50 } |
|
51 } |
|
52 |
|
53 this.get('contentBox').focusManager.refresh(); |
|
54 }, |
|
55 |
|
56 _initAria: function(contentBox) { |
|
57 var tablist = contentBox.one(Y.TabviewBase._queries.tabviewList); |
|
58 |
|
59 if (tablist) { |
|
60 tablist.setAttrs({ |
|
61 //'aria-labelledby': |
|
62 role: 'tablist' |
|
63 }); |
|
64 } |
|
65 }, |
|
66 |
|
67 bindUI: function() { |
|
68 // Use the Node Focus Manager to add keyboard support: |
|
69 // Pressing the left and right arrow keys will move focus |
|
70 // among each of the tabs. |
|
71 |
|
72 this.get('contentBox').plug(Y.Plugin.NodeFocusManager, { |
|
73 descendants: DOT + Y.TabviewBase._classNames.tabLabel, |
|
74 keys: { next: 'down:39', // Right arrow |
|
75 previous: 'down:37' }, // Left arrow |
|
76 circular: true |
|
77 }); |
|
78 |
|
79 this.after('render', this._setDefSelection); |
|
80 this.after('addChild', this._afterChildAdded); |
|
81 this.after('removeChild', this._afterChildRemoved); |
|
82 }, |
|
83 |
|
84 renderUI: function() { |
|
85 var contentBox = this.get('contentBox'); |
|
86 this._renderListBox(contentBox); |
|
87 this._renderPanelBox(contentBox); |
|
88 this._childrenContainer = this.get('listNode'); |
|
89 this._renderTabs(contentBox); |
|
90 this._initAria(contentBox); |
|
91 }, |
|
92 |
|
93 _setDefSelection: function() { |
|
94 // If no tab is selected, select the first tab. |
|
95 var selection = this.get('selection') || this.item(0); |
|
96 |
|
97 this.some(function(tab) { |
|
98 if (tab.get('selected')) { |
|
99 selection = tab; |
|
100 return true; |
|
101 } |
|
102 }); |
|
103 if (selection) { |
|
104 // TODO: why both needed? (via widgetParent/Child)? |
|
105 this.set('selection', selection); |
|
106 selection.set('selected', 1); |
|
107 } |
|
108 }, |
|
109 |
|
110 _renderListBox: function(contentBox) { |
|
111 var node = this.get('listNode'); |
|
112 if (!node.inDoc()) { |
|
113 contentBox.append(node); |
|
114 } |
|
115 }, |
|
116 |
|
117 _renderPanelBox: function(contentBox) { |
|
118 var node = this.get('panelNode'); |
|
119 if (!node.inDoc()) { |
|
120 contentBox.append(node); |
|
121 } |
|
122 }, |
|
123 |
|
124 _renderTabs: function(contentBox) { |
|
125 var _classNames = Y.TabviewBase._classNames, |
|
126 _queries = Y.TabviewBase._queries, |
|
127 tabs = contentBox.all(_queries.tab), |
|
128 panelNode = this.get('panelNode'), |
|
129 panels = (panelNode) ? this.get('panelNode').get('children') : null, |
|
130 tabview = this; |
|
131 |
|
132 if (tabs) { // add classNames and fill in Tab fields from markup when possible |
|
133 tabs.addClass(_classNames.tab); |
|
134 contentBox.all(_queries.tabLabel).addClass(_classNames.tabLabel); |
|
135 contentBox.all(_queries.tabPanel).addClass(_classNames.tabPanel); |
|
136 |
|
137 tabs.each(function(node, i) { |
|
138 var panelNode = (panels) ? panels.item(i) : null; |
|
139 tabview.add({ |
|
140 boundingBox: node, |
|
141 contentBox: node.one(DOT + _classNames.tabLabel), |
|
142 panelNode: panelNode |
|
143 }); |
|
144 }); |
|
145 } |
|
146 } |
|
147 }, { |
|
148 ATTRS: { |
|
149 defaultChildType: { |
|
150 value: 'Tab' |
|
151 }, |
|
152 |
|
153 listNode: { |
|
154 setter: function(node) { |
|
155 node = Y.one(node); |
|
156 if (node) { |
|
157 node.addClass(Y.TabviewBase._classNames.tabviewList); |
|
158 } |
|
159 return node; |
|
160 }, |
|
161 |
|
162 valueFn: '_defListNodeValueFn' |
|
163 }, |
|
164 |
|
165 panelNode: { |
|
166 setter: function(node) { |
|
167 node = Y.one(node); |
|
168 if (node) { |
|
169 node.addClass(Y.TabviewBase._classNames.tabviewPanel); |
|
170 } |
|
171 return node; |
|
172 }, |
|
173 |
|
174 valueFn: '_defPanelNodeValueFn' |
|
175 }, |
|
176 |
|
177 tabIndex: { |
|
178 value: null |
|
179 //validator: '_validTabIndex' |
|
180 } |
|
181 }, |
|
182 |
|
183 HTML_PARSER: { |
|
184 listNode: function(srcNode) { |
|
185 return srcNode.one(Y.TabviewBase._queries.tabviewList); |
|
186 }, |
|
187 panelNode: function(srcNode) { |
|
188 return srcNode.one(Y.TabviewBase._queries.tabviewPanel); |
|
189 } |
|
190 }, |
|
191 |
|
192 // Static for legacy support. |
|
193 LIST_TEMPLATE: '<ul></ul>', |
|
194 PANEL_TEMPLATE: '<div></div>' |
|
195 }); |
|
196 |
|
197 // Map to static values by default. |
|
198 TabView.prototype.LIST_TEMPLATE = TabView.LIST_TEMPLATE; |
|
199 TabView.prototype.PANEL_TEMPLATE = TabView.PANEL_TEMPLATE; |
|
200 |
|
201 Y.TabView = TabView; |
|
202 /** |
|
203 * Provides Tab instances for use with TabView |
|
204 * @param config {Object} Object literal specifying tabview configuration properties. |
|
205 * |
|
206 * @class Tab |
|
207 * @constructor |
|
208 * @extends Widget |
|
209 * @uses WidgetChild |
|
210 */ |
|
211 Y.Tab = Y.Base.create('tab', Y.Widget, [Y.WidgetChild], { |
|
212 BOUNDING_TEMPLATE: '<li></li>', |
|
213 CONTENT_TEMPLATE: '<a></a>', |
|
214 PANEL_TEMPLATE: '<div></div>', |
|
215 |
|
216 _uiSetSelectedPanel: function(selected) { |
|
217 this.get('panelNode').toggleClass(Y.TabviewBase._classNames.selectedPanel, selected); |
|
218 }, |
|
219 |
|
220 _afterTabSelectedChange: function(event) { |
|
221 this._uiSetSelectedPanel(event.newVal); |
|
222 }, |
|
223 |
|
224 _afterParentChange: function(e) { |
|
225 if (!e.newVal) { |
|
226 this._remove(); |
|
227 } else { |
|
228 this._add(); |
|
229 } |
|
230 }, |
|
231 |
|
232 _initAria: function() { |
|
233 var anchor = this.get('contentBox'), |
|
234 id = anchor.get('id'), |
|
235 panel = this.get('panelNode'); |
|
236 |
|
237 if (!id) { |
|
238 id = Y.guid(); |
|
239 anchor.set('id', id); |
|
240 } |
|
241 // Apply the ARIA roles, states and properties to each tab |
|
242 anchor.set('role', 'tab'); |
|
243 anchor.get('parentNode').set('role', 'presentation'); |
|
244 |
|
245 // Apply the ARIA roles, states and properties to each panel |
|
246 panel.setAttrs({ |
|
247 role: 'tabpanel', |
|
248 'aria-labelledby': id |
|
249 }); |
|
250 }, |
|
251 |
|
252 syncUI: function() { |
|
253 var _classNames = Y.TabviewBase._classNames; |
|
254 |
|
255 this.get('boundingBox').addClass(_classNames.tab); |
|
256 this.get('contentBox').addClass(_classNames.tabLabel); |
|
257 this.set('label', this.get('label')); |
|
258 this.set('content', this.get('content')); |
|
259 this._uiSetSelectedPanel(this.get('selected')); |
|
260 }, |
|
261 |
|
262 bindUI: function() { |
|
263 this.after('selectedChange', this._afterTabSelectedChange); |
|
264 this.after('parentChange', this._afterParentChange); |
|
265 }, |
|
266 |
|
267 renderUI: function() { |
|
268 this._renderPanel(); |
|
269 this._initAria(); |
|
270 }, |
|
271 |
|
272 _renderPanel: function() { |
|
273 this.get('parent').get('panelNode') |
|
274 .appendChild(this.get('panelNode')); |
|
275 }, |
|
276 |
|
277 _add: function() { |
|
278 var parent = this.get('parent').get('contentBox'), |
|
279 list = parent.get('listNode'), |
|
280 panel = parent.get('panelNode'); |
|
281 |
|
282 if (list) { |
|
283 list.appendChild(this.get('boundingBox')); |
|
284 } |
|
285 |
|
286 if (panel) { |
|
287 panel.appendChild(this.get('panelNode')); |
|
288 } |
|
289 }, |
|
290 |
|
291 _remove: function() { |
|
292 this.get('boundingBox').remove(); |
|
293 this.get('panelNode').remove(); |
|
294 }, |
|
295 |
|
296 _onActivate: function(e) { |
|
297 if (e.target === this) { |
|
298 // Prevent the browser from navigating to the URL specified by the |
|
299 // anchor's href attribute. |
|
300 e.domEvent.preventDefault(); |
|
301 e.target.set('selected', 1); |
|
302 } |
|
303 }, |
|
304 |
|
305 initializer: function() { |
|
306 this.publish(this.get('triggerEvent'), { |
|
307 defaultFn: this._onActivate |
|
308 }); |
|
309 }, |
|
310 |
|
311 _defLabelGetter: function() { |
|
312 return this.get('contentBox').getHTML(); |
|
313 }, |
|
314 |
|
315 _defLabelSetter: function(label) { |
|
316 var labelNode = this.get('contentBox'); |
|
317 if (labelNode.getHTML() !== label) { // Avoid rewriting existing label. |
|
318 labelNode.setHTML(label); |
|
319 } |
|
320 return label; |
|
321 }, |
|
322 |
|
323 _defContentSetter: function(content) { |
|
324 var panel = this.get('panelNode'); |
|
325 if (panel.getHTML() !== content) { // Avoid rewriting existing content. |
|
326 panel.setHTML(content); |
|
327 } |
|
328 return content; |
|
329 }, |
|
330 |
|
331 _defContentGetter: function() { |
|
332 return this.get('panelNode').getHTML(); |
|
333 }, |
|
334 |
|
335 // find panel by ID mapping from label href |
|
336 _defPanelNodeValueFn: function() { |
|
337 var _classNames = Y.TabviewBase._classNames, |
|
338 href = this.get('contentBox').get('href') || '', |
|
339 parent = this.get('parent'), |
|
340 hashIndex = href.indexOf('#'), |
|
341 panel; |
|
342 |
|
343 href = href.substr(hashIndex); |
|
344 |
|
345 if (href.charAt(0) === '#') { // in-page nav, find by ID |
|
346 panel = Y.one(href); |
|
347 if (panel) { |
|
348 panel.addClass(_classNames.tabPanel); |
|
349 } |
|
350 } |
|
351 |
|
352 // use the one found by id, or else try matching indices |
|
353 if (!panel && parent) { |
|
354 panel = parent.get('panelNode') |
|
355 .get('children').item(this.get('index')); |
|
356 } |
|
357 |
|
358 if (!panel) { // create if none found |
|
359 panel = Y.Node.create(this.PANEL_TEMPLATE); |
|
360 panel.addClass(_classNames.tabPanel); |
|
361 } |
|
362 return panel; |
|
363 } |
|
364 }, { |
|
365 ATTRS: { |
|
366 /** |
|
367 * @attribute triggerEvent |
|
368 * @default "click" |
|
369 * @type String |
|
370 */ |
|
371 triggerEvent: { |
|
372 value: 'click' |
|
373 }, |
|
374 |
|
375 /** |
|
376 * @attribute label |
|
377 * @type HTML |
|
378 */ |
|
379 label: { |
|
380 setter: '_defLabelSetter', |
|
381 getter: '_defLabelGetter' |
|
382 }, |
|
383 |
|
384 /** |
|
385 * @attribute content |
|
386 * @type HTML |
|
387 */ |
|
388 content: { |
|
389 setter: '_defContentSetter', |
|
390 getter: '_defContentGetter' |
|
391 }, |
|
392 |
|
393 /** |
|
394 * @attribute panelNode |
|
395 * @type Y.Node |
|
396 */ |
|
397 panelNode: { |
|
398 setter: function(node) { |
|
399 node = Y.one(node); |
|
400 if (node) { |
|
401 node.addClass(Y.TabviewBase._classNames.tabPanel); |
|
402 } |
|
403 return node; |
|
404 }, |
|
405 valueFn: '_defPanelNodeValueFn' |
|
406 }, |
|
407 |
|
408 tabIndex: { |
|
409 value: null, |
|
410 validator: '_validTabIndex' |
|
411 } |
|
412 |
|
413 }, |
|
414 |
|
415 HTML_PARSER: { |
|
416 selected: function() { |
|
417 var ret = (this.get('boundingBox').hasClass(Y.TabviewBase._classNames.selectedTab)) ? |
|
418 1 : 0; |
|
419 return ret; |
|
420 } |
|
421 } |
|
422 |
|
423 }); |
|
424 |
|
425 |
|
426 }, '@VERSION@', { |
|
427 "requires": [ |
|
428 "widget", |
|
429 "widget-parent", |
|
430 "widget-child", |
|
431 "tabview-base", |
|
432 "node-pluginhost", |
|
433 "node-focusmanager" |
|
434 ], |
|
435 "skinnable": true |
|
436 }); |