2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('tabview', function(Y) {
16 var _queries = Y.TabviewBase._queries,
17 _classNames = Y.TabviewBase._classNames,
19 getClassName = Y.ClassNameManager.getClassName,
22 * Provides a tabbed widget interface
23 * @param config {Object} Object literal specifying tabview configuration properties.
30 TabView = Y.Base.create('tabView', Y.Widget, [Y.WidgetParent], {
31 _afterChildAdded: function(e) {
32 this.get('contentBox').focusManager.refresh();
35 _defListNodeValueFn: function() {
36 return Y.Node.create(TabView.LIST_TEMPLATE);
39 _defPanelNodeValueFn: function() {
40 return Y.Node.create(TabView.PANEL_TEMPLATE);
43 _afterChildRemoved: function(e) { // update the selected tab when removed
45 selection = this.get('selection');
47 if (!selection) { // select previous item if selection removed
48 selection = this.item(i - 1) || this.item(0);
50 selection.set('selected', 1);
54 this.get('contentBox').focusManager.refresh();
57 _initAria: function() {
58 var contentBox = this.get('contentBox'),
59 tablist = contentBox.one(_queries.tabviewList);
70 // Use the Node Focus Manager to add keyboard support:
71 // Pressing the left and right arrow keys will move focus
72 // among each of the tabs.
74 this.get('contentBox').plug(Y.Plugin.NodeFocusManager, {
75 descendants: DOT + _classNames.tabLabel,
76 keys: { next: 'down:39', // Right arrow
77 previous: 'down:37' }, // Left arrow
81 this.after('render', this._setDefSelection);
82 this.after('addChild', this._afterChildAdded);
83 this.after('removeChild', this._afterChildRemoved);
86 renderUI: function() {
87 var contentBox = this.get('contentBox');
88 this._renderListBox(contentBox);
89 this._renderPanelBox(contentBox);
90 this._childrenContainer = this.get('listNode');
91 this._renderTabs(contentBox);
94 _setDefSelection: function(contentBox) {
95 // If no tab is selected, select the first tab.
96 var selection = this.get('selection') || this.item(0);
98 this.some(function(tab) {
99 if (tab.get('selected')) {
105 // TODO: why both needed? (via widgetParent/Child)?
106 this.set('selection', selection);
107 selection.set('selected', 1);
111 _renderListBox: function(contentBox) {
112 var node = this.get('listNode');
114 contentBox.append(node);
118 _renderPanelBox: function(contentBox) {
119 var node = this.get('panelNode');
121 contentBox.append(node);
125 _renderTabs: function(contentBox) {
126 var tabs = contentBox.all(_queries.tab),
127 panelNode = this.get('panelNode'),
128 panels = (panelNode) ? this.get('panelNode').get('children') : null,
131 if (tabs) { // add classNames and fill in Tab fields from markup when possible
132 tabs.addClass(_classNames.tab);
133 contentBox.all(_queries.tabLabel).addClass(_classNames.tabLabel);
134 contentBox.all(_queries.tabPanel).addClass(_classNames.tabPanel);
136 tabs.each(function(node, i) {
137 var panelNode = (panels) ? panels.item(i) : null;
140 contentBox: node.one(DOT + _classNames.tabLabel),
141 label: node.one(DOT + _classNames.tabLabel).get('text'),
149 LIST_TEMPLATE: '<ul class="' + _classNames.tabviewList + '"></ul>',
150 PANEL_TEMPLATE: '<div class="' + _classNames.tabviewPanel + '"></div>',
158 setter: function(node) {
161 node.addClass(_classNames.tabviewList);
166 valueFn: '_defListNodeValueFn'
170 setter: function(node) {
173 node.addClass(_classNames.tabviewPanel);
178 valueFn: '_defPanelNodeValueFn'
183 //validator: '_validTabIndex'
188 listNode: _queries.tabviewList,
189 panelNode: _queries.tabviewPanel
195 _queries = Y.TabviewBase._queries,
196 _classNames = Y.TabviewBase._classNames,
197 getClassName = Y.ClassNameManager.getClassName;
200 * Provides Tab instances for use with TabView
201 * @param config {Object} Object literal specifying tabview configuration properties.
208 Y.Tab = Y.Base.create('tab', Y.Widget, [Y.WidgetChild], {
209 BOUNDING_TEMPLATE: '<li class="' + _classNames.tab + '"></li>',
210 CONTENT_TEMPLATE: '<a class="' + _classNames.tabLabel + '"></a>',
211 PANEL_TEMPLATE: '<div class="' + _classNames.tabPanel + '"></div>',
213 _uiSetSelectedPanel: function(selected) {
214 this.get('panelNode').toggleClass(_classNames.selectedPanel, selected);
217 _afterTabSelectedChange: function(event) {
218 this._uiSetSelectedPanel(event.newVal);
221 _afterParentChange: function(e) {
229 _initAria: function() {
230 var anchor = this.get('contentBox'),
231 id = anchor.get('id'),
232 panel = this.get('panelNode');
236 anchor.set('id', id);
238 // Apply the ARIA roles, states and properties to each tab
239 anchor.set('role', 'tab');
240 anchor.get('parentNode').set('role', 'presentation');
243 // Apply the ARIA roles, states and properties to each panel
246 'aria-labelledby': id
251 this.set('label', this.get('label'));
252 this.set('content', this.get('content'));
253 this._uiSetSelectedPanel(this.get('selected'));
257 this.after('selectedChange', this._afterTabSelectedChange);
258 this.after('parentChange', this._afterParentChange);
261 renderUI: function() {
266 _renderPanel: function() {
267 this.get('parent').get('panelNode')
268 .appendChild(this.get('panelNode'));
272 var parent = this.get('parent').get('contentBox'),
273 list = parent.get('listNode'),
274 panel = parent.get('panelNode');
277 list.appendChild(this.get('boundingBox'));
281 panel.appendChild(this.get('panelNode'));
285 _remove: function() {
286 this.get('boundingBox').remove();
287 this.get('panelNode').remove();
290 _onActivate: function(e) {
291 if (e.target === this) {
292 // Prevent the browser from navigating to the URL specified by the
293 // anchor's href attribute.
294 e.domEvent.preventDefault();
295 e.target.set('selected', 1);
299 initializer: function() {
300 this.publish(this.get('triggerEvent'), {
301 defaultFn: this._onActivate
305 _defLabelSetter: function(label) {
306 this.get('contentBox').setContent(label);
310 _defContentSetter: function(content) {
311 this.get('panelNode').setContent(content);
315 // find panel by ID mapping from label href
316 _defPanelNodeValueFn: function() {
317 var href = this.get('contentBox').get('href') || '',
318 parent = this.get('parent'),
319 hashIndex = href.indexOf('#'),
322 href = href.substr(hashIndex);
324 if (href.charAt(0) === '#') { // in-page nav, find by ID
327 panel.addClass(_classNames.tabPanel);
331 // use the one found by id, or else try matching indices
332 if (!panel && parent) {
333 panel = parent.get('panelNode')
334 .get('children').item(this.get('index'));
337 if (!panel) { // create if none found
338 panel = Y.Node.create(this.PANEL_TEMPLATE);
345 * @attribute triggerEvent
358 setter: '_defLabelSetter',
359 validator: Lang.isString
367 setter: '_defContentSetter',
368 validator: Lang.isString
372 * @attribute panelNode
376 setter: function(node) {
379 node.addClass(_classNames.tabPanel);
383 valueFn: '_defPanelNodeValueFn'
388 validator: '_validTabIndex'
394 selected: function(contentBox) {
395 var ret = (this.get('boundingBox').hasClass(_classNames.selectedTab)) ?
404 }, '3.3.0' ,{requires:['substitute', 'node-pluginhost', 'node-focusmanager', 'tabview-base', 'widget', 'widget-parent', 'widget-child']});