3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('tabview', function(Y) {
15 var _queries = Y.TabviewBase._queries,
16 _classNames = Y.TabviewBase._classNames,
18 getClassName = Y.ClassNameManager.getClassName,
21 * Provides a tabbed widget interface
22 * @param config {Object} Object literal specifying tabview configuration properties.
29 TabView = Y.Base.create('tabView', Y.Widget, [Y.WidgetParent], {
30 _afterChildAdded: function(e) {
31 this.get('contentBox').focusManager.refresh();
34 _defListNodeValueFn: function() {
35 return Y.Node.create(TabView.LIST_TEMPLATE);
38 _defPanelNodeValueFn: function() {
39 return Y.Node.create(TabView.PANEL_TEMPLATE);
42 _afterChildRemoved: function(e) { // update the selected tab when removed
44 selection = this.get('selection');
46 if (!selection) { // select previous item if selection removed
47 selection = this.item(i - 1) || this.item(0);
49 selection.set('selected', 1);
53 this.get('contentBox').focusManager.refresh();
56 _initAria: function() {
57 var contentBox = this.get('contentBox'),
58 tablist = contentBox.one(_queries.tabviewList);
69 // Use the Node Focus Manager to add keyboard support:
70 // Pressing the left and right arrow keys will move focus
71 // among each of the tabs.
73 this.get('contentBox').plug(Y.Plugin.NodeFocusManager, {
74 descendants: DOT + _classNames.tabLabel,
75 keys: { next: 'down:39', // Right arrow
76 previous: 'down:37' }, // Left arrow
80 this.after('render', this._setDefSelection);
81 this.after('addChild', this._afterChildAdded);
82 this.after('removeChild', this._afterChildRemoved);
85 renderUI: function() {
86 var contentBox = this.get('contentBox');
87 this._renderListBox(contentBox);
88 this._renderPanelBox(contentBox);
89 this._childrenContainer = this.get('listNode');
90 this._renderTabs(contentBox);
93 _setDefSelection: function(contentBox) {
94 // If no tab is selected, select the first tab.
95 var selection = this.get('selection') || this.item(0);
97 this.some(function(tab) {
98 if (tab.get('selected')) {
104 // TODO: why both needed? (via widgetParent/Child)?
105 this.set('selection', selection);
106 selection.set('selected', 1);
110 _renderListBox: function(contentBox) {
111 var node = this.get('listNode');
113 contentBox.append(node);
117 _renderPanelBox: function(contentBox) {
118 var node = this.get('panelNode');
120 contentBox.append(node);
124 _renderTabs: function(contentBox) {
125 var tabs = contentBox.all(_queries.tab),
126 panelNode = this.get('panelNode'),
127 panels = (panelNode) ? this.get('panelNode').get('children') : null,
130 if (tabs) { // add classNames and fill in Tab fields from markup when possible
131 tabs.addClass(_classNames.tab);
132 contentBox.all(_queries.tabLabel).addClass(_classNames.tabLabel);
133 contentBox.all(_queries.tabPanel).addClass(_classNames.tabPanel);
135 tabs.each(function(node, i) {
136 var panelNode = (panels) ? panels.item(i) : null;
139 contentBox: node.one(DOT + _classNames.tabLabel),
140 label: node.one(DOT + _classNames.tabLabel).get('text'),
148 LIST_TEMPLATE: '<ul class="' + _classNames.tabviewList + '"></ul>',
149 PANEL_TEMPLATE: '<div class="' + _classNames.tabviewPanel + '"></div>',
157 setter: function(node) {
160 node.addClass(_classNames.tabviewList);
165 valueFn: '_defListNodeValueFn'
169 setter: function(node) {
172 node.addClass(_classNames.tabviewPanel);
177 valueFn: '_defPanelNodeValueFn'
182 //validator: '_validTabIndex'
187 listNode: _queries.tabviewList,
188 panelNode: _queries.tabviewPanel
194 _queries = Y.TabviewBase._queries,
195 _classNames = Y.TabviewBase._classNames,
196 getClassName = Y.ClassNameManager.getClassName;
199 * Provides Tab instances for use with TabView
200 * @param config {Object} Object literal specifying tabview configuration properties.
207 Y.Tab = Y.Base.create('tab', Y.Widget, [Y.WidgetChild], {
208 BOUNDING_TEMPLATE: '<li class="' + _classNames.tab + '"></li>',
209 CONTENT_TEMPLATE: '<a class="' + _classNames.tabLabel + '"></a>',
210 PANEL_TEMPLATE: '<div class="' + _classNames.tabPanel + '"></div>',
212 _uiSetSelectedPanel: function(selected) {
213 this.get('panelNode').toggleClass(_classNames.selectedPanel, selected);
216 _afterTabSelectedChange: function(event) {
217 this._uiSetSelectedPanel(event.newVal);
220 _afterParentChange: function(e) {
228 _initAria: function() {
229 var anchor = this.get('contentBox'),
230 id = anchor.get('id'),
231 panel = this.get('panelNode');
235 anchor.set('id', id);
237 // Apply the ARIA roles, states and properties to each tab
238 anchor.set('role', 'tab');
239 anchor.get('parentNode').set('role', 'presentation');
242 // Apply the ARIA roles, states and properties to each panel
245 'aria-labelledby': id
250 this.set('label', this.get('label'));
251 this.set('content', this.get('content'));
252 this._uiSetSelectedPanel(this.get('selected'));
256 this.after('selectedChange', this._afterTabSelectedChange);
257 this.after('parentChange', this._afterParentChange);
260 renderUI: function() {
265 _renderPanel: function() {
266 this.get('parent').get('panelNode')
267 .appendChild(this.get('panelNode'));
271 var parent = this.get('parent').get('contentBox'),
272 list = parent.get('listNode'),
273 panel = parent.get('panelNode');
276 list.appendChild(this.get('boundingBox'));
280 panel.appendChild(this.get('panelNode'));
284 _remove: function() {
285 this.get('boundingBox').remove();
286 this.get('panelNode').remove();
289 _onActivate: function(e) {
290 if (e.target === this) {
291 // Prevent the browser from navigating to the URL specified by the
292 // anchor's href attribute.
293 e.domEvent.preventDefault();
294 e.target.set('selected', 1);
298 initializer: function() {
299 this.publish(this.get('triggerEvent'), {
300 defaultFn: this._onActivate
304 _defLabelSetter: function(label) {
305 this.get('contentBox').setContent(label);
309 _defContentSetter: function(content) {
310 this.get('panelNode').setContent(content);
314 _defContentGetter: function(content) {
315 return this.get('panelNode').getContent();
318 // find panel by ID mapping from label href
319 _defPanelNodeValueFn: function() {
320 var href = this.get('contentBox').get('href') || '',
321 parent = this.get('parent'),
322 hashIndex = href.indexOf('#'),
325 href = href.substr(hashIndex);
327 if (href.charAt(0) === '#') { // in-page nav, find by ID
330 panel.addClass(_classNames.tabPanel);
334 // use the one found by id, or else try matching indices
335 if (!panel && parent) {
336 panel = parent.get('panelNode')
337 .get('children').item(this.get('index'));
340 if (!panel) { // create if none found
341 panel = Y.Node.create(this.PANEL_TEMPLATE);
348 * @attribute triggerEvent
361 setter: '_defLabelSetter',
362 validator: Lang.isString
370 setter: '_defContentSetter',
371 getter: '_defContentGetter'
375 * @attribute panelNode
379 setter: function(node) {
382 node.addClass(_classNames.tabPanel);
386 valueFn: '_defPanelNodeValueFn'
391 validator: '_validTabIndex'
397 selected: function(contentBox) {
398 var ret = (this.get('boundingBox').hasClass(_classNames.selectedTab)) ?
407 }, '3.5.1' ,{requires:['node-pluginhost', 'node-focusmanager', 'tabview-base', 'widget', 'widget-parent', 'widget-child']});