|
602
|
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 |
}); |