2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('tabview', function (Y, NAME) {
19 * Provides a tabbed widget interface
20 * @param config {Object} Object literal specifying tabview configuration properties.
27 TabView = Y.Base.create('tabView', Y.Widget, [Y.WidgetParent], {
29 _afterChildAdded: function() {
30 this.get('contentBox').focusManager.refresh();
33 _defListNodeValueFn: function() {
34 var node = Y.Node.create(this.LIST_TEMPLATE);
36 node.addClass(Y.TabviewBase._classNames.tabviewList);
41 _defPanelNodeValueFn: function() {
42 var node = Y.Node.create(this.PANEL_TEMPLATE);
44 node.addClass(Y.TabviewBase._classNames.tabviewPanel);
49 _afterChildRemoved: function(e) { // update the selected tab when removed
51 selection = this.get('selection');
53 if (!selection) { // select previous item if selection removed
54 selection = this.item(i - 1) || this.item(0);
56 selection.set('selected', 1);
60 this.get('contentBox').focusManager.refresh();
63 _initAria: function(contentBox) {
64 var tablist = contentBox.one(Y.TabviewBase._queries.tabviewList);
75 // Use the Node Focus Manager to add keyboard support:
76 // Pressing the left and right arrow keys will move focus
77 // among each of the tabs.
79 this.get('contentBox').plug(Y.Plugin.NodeFocusManager, {
80 descendants: DOT + Y.TabviewBase._classNames.tabLabel,
81 keys: { next: 'down:39', // Right arrow
82 previous: 'down:37' }, // Left arrow
86 this.after('render', this._setDefSelection);
87 this.after('addChild', this._afterChildAdded);
88 this.after('removeChild', this._afterChildRemoved);
91 renderUI: function() {
92 var contentBox = this.get('contentBox');
93 this._renderListBox(contentBox);
94 this._renderPanelBox(contentBox);
95 this._childrenContainer = this.get('listNode');
96 this._renderTabs(contentBox);
97 this._initAria(contentBox);
100 _setDefSelection: function() {
101 // If no tab is selected, select the first tab.
102 var selection = this.get('selection') || this.item(0);
104 this.some(function(tab) {
105 if (tab.get('selected')) {
111 // TODO: why both needed? (via widgetParent/Child)?
112 this.set('selection', selection);
113 selection.set('selected', 1);
117 _renderListBox: function(contentBox) {
118 var node = this.get('listNode');
120 contentBox.append(node);
124 _renderPanelBox: function(contentBox) {
125 var node = this.get('panelNode');
127 contentBox.append(node);
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,
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);
144 tabs.each(function(node, i) {
145 var panelNode = (panels) ? panels.item(i) : null;
148 contentBox: node.one(DOT + _classNames.tabLabel),
161 setter: function(node) {
164 node.addClass(Y.TabviewBase._classNames.tabviewList);
169 valueFn: '_defListNodeValueFn'
173 setter: function(node) {
176 node.addClass(Y.TabviewBase._classNames.tabviewPanel);
181 valueFn: '_defPanelNodeValueFn'
186 //validator: '_validTabIndex'
191 listNode: function(srcNode) {
192 return srcNode.one(Y.TabviewBase._queries.tabviewList);
194 panelNode: function(srcNode) {
195 return srcNode.one(Y.TabviewBase._queries.tabviewPanel);
199 // Static for legacy support.
200 LIST_TEMPLATE: '<ul></ul>',
201 PANEL_TEMPLATE: '<div></div>'
204 // Map to static values by default.
205 TabView.prototype.LIST_TEMPLATE = TabView.LIST_TEMPLATE;
206 TabView.prototype.PANEL_TEMPLATE = TabView.PANEL_TEMPLATE;
210 * Provides Tab instances for use with TabView
211 * @param config {Object} Object literal specifying tabview configuration properties.
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>',
223 _uiSetSelectedPanel: function(selected) {
224 this.get('panelNode').toggleClass(Y.TabviewBase._classNames.selectedPanel, selected);
227 _afterTabSelectedChange: function(event) {
228 this._uiSetSelectedPanel(event.newVal);
231 _afterParentChange: function(e) {
239 _initAria: function() {
240 var anchor = this.get('contentBox'),
241 id = anchor.get('id'),
242 panel = this.get('panelNode');
246 anchor.set('id', id);
248 // Apply the ARIA roles, states and properties to each tab
249 anchor.set('role', 'tab');
250 anchor.get('parentNode').set('role', 'presentation');
252 // Apply the ARIA roles, states and properties to each panel
255 'aria-labelledby': id
260 var _classNames = Y.TabviewBase._classNames;
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'));
270 this.after('selectedChange', this._afterTabSelectedChange);
271 this.after('parentChange', this._afterParentChange);
274 renderUI: function() {
279 _renderPanel: function() {
280 this.get('parent').get('panelNode')
281 .appendChild(this.get('panelNode'));
285 var parent = this.get('parent').get('contentBox'),
286 list = parent.get('listNode'),
287 panel = parent.get('panelNode');
290 list.appendChild(this.get('boundingBox'));
294 panel.appendChild(this.get('panelNode'));
298 _remove: function() {
299 this.get('boundingBox').remove();
300 this.get('panelNode').remove();
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);
312 initializer: function() {
313 this.publish(this.get('triggerEvent'), {
314 defaultFn: this._onActivate
318 _defLabelGetter: function() {
319 return this.get('contentBox').getHTML();
322 _defLabelSetter: function(label) {
323 var labelNode = this.get('contentBox');
324 if (labelNode.getHTML() !== label) { // Avoid rewriting existing label.
325 labelNode.setHTML(label);
330 _defContentSetter: function(content) {
331 var panel = this.get('panelNode');
332 if (panel.getHTML() !== content) { // Avoid rewriting existing content.
333 panel.setHTML(content);
338 _defContentGetter: function() {
339 return this.get('panelNode').getHTML();
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('#'),
350 href = href.substr(hashIndex);
352 if (href.charAt(0) === '#') { // in-page nav, find by ID
355 panel.addClass(_classNames.tabPanel);
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'));
365 if (!panel) { // create if none found
366 panel = Y.Node.create(this.PANEL_TEMPLATE);
367 panel.addClass(_classNames.tabPanel);
374 * @attribute triggerEvent
387 setter: '_defLabelSetter',
388 getter: '_defLabelGetter'
396 setter: '_defContentSetter',
397 getter: '_defContentGetter'
401 * @attribute panelNode
405 setter: function(node) {
408 node.addClass(Y.TabviewBase._classNames.tabPanel);
412 valueFn: '_defPanelNodeValueFn'
417 validator: '_validTabIndex'
423 selected: function() {
424 var ret = (this.get('boundingBox').hasClass(Y.TabviewBase._classNames.selectedTab)) ?