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