/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('tabview', function(Y) { /** * The TabView module * * @module tabview */ var _queries = Y.TabviewBase._queries, _classNames = Y.TabviewBase._classNames, DOT = '.', getClassName = Y.ClassNameManager.getClassName, /** * Provides a tabbed widget interface * @param config {Object} Object literal specifying tabview configuration properties. * * @class TabView * @constructor * @extends Widget * @uses WidgetParent */ TabView = Y.Base.create('tabView', Y.Widget, [Y.WidgetParent], { _afterChildAdded: function(e) { this.get('contentBox').focusManager.refresh(); }, _defListNodeValueFn: function() { return Y.Node.create(TabView.LIST_TEMPLATE); }, _defPanelNodeValueFn: function() { return Y.Node.create(TabView.PANEL_TEMPLATE); }, _afterChildRemoved: function(e) { // update the selected tab when removed var i = e.index, selection = this.get('selection'); if (!selection) { // select previous item if selection removed selection = this.item(i - 1) || this.item(0); if (selection) { selection.set('selected', 1); } } this.get('contentBox').focusManager.refresh(); }, _initAria: function() { var contentBox = this.get('contentBox'), tablist = contentBox.one(_queries.tabviewList); if (tablist) { tablist.setAttrs({ //'aria-labelledby': role: 'tablist' }); } }, bindUI: function() { // Use the Node Focus Manager to add keyboard support: // Pressing the left and right arrow keys will move focus // among each of the tabs. this.get('contentBox').plug(Y.Plugin.NodeFocusManager, { descendants: DOT + _classNames.tabLabel, keys: { next: 'down:39', // Right arrow previous: 'down:37' }, // Left arrow circular: true }); this.after('render', this._setDefSelection); this.after('addChild', this._afterChildAdded); this.after('removeChild', this._afterChildRemoved); }, renderUI: function() { var contentBox = this.get('contentBox'); this._renderListBox(contentBox); this._renderPanelBox(contentBox); this._childrenContainer = this.get('listNode'); this._renderTabs(contentBox); }, _setDefSelection: function(contentBox) { // If no tab is selected, select the first tab. var selection = this.get('selection') || this.item(0); this.some(function(tab) { if (tab.get('selected')) { selection = tab; return true; } }); if (selection) { // TODO: why both needed? (via widgetParent/Child)? this.set('selection', selection); selection.set('selected', 1); } }, _renderListBox: function(contentBox) { var node = this.get('listNode'); if (!node.inDoc()) { contentBox.append(node); } }, _renderPanelBox: function(contentBox) { var node = this.get('panelNode'); if (!node.inDoc()) { contentBox.append(node); } }, _renderTabs: function(contentBox) { var tabs = contentBox.all(_queries.tab), panelNode = this.get('panelNode'), panels = (panelNode) ? this.get('panelNode').get('children') : null, tabview = this; if (tabs) { // add classNames and fill in Tab fields from markup when possible tabs.addClass(_classNames.tab); contentBox.all(_queries.tabLabel).addClass(_classNames.tabLabel); contentBox.all(_queries.tabPanel).addClass(_classNames.tabPanel); tabs.each(function(node, i) { var panelNode = (panels) ? panels.item(i) : null; tabview.add({ boundingBox: node, contentBox: node.one(DOT + _classNames.tabLabel), label: node.one(DOT + _classNames.tabLabel).get('text'), panelNode: panelNode }); }); } } }, { LIST_TEMPLATE: '', PANEL_TEMPLATE: '
', ATTRS: { defaultChildType: { value: 'Tab' }, listNode: { setter: function(node) { node = Y.one(node); if (node) { node.addClass(_classNames.tabviewList); } return node; }, valueFn: '_defListNodeValueFn' }, panelNode: { setter: function(node) { node = Y.one(node); if (node) { node.addClass(_classNames.tabviewPanel); } return node; }, valueFn: '_defPanelNodeValueFn' }, tabIndex: { value: null //validator: '_validTabIndex' } }, HTML_PARSER: { listNode: _queries.tabviewList, panelNode: _queries.tabviewPanel } }); Y.TabView = TabView; var Lang = Y.Lang, _queries = Y.TabviewBase._queries, _classNames = Y.TabviewBase._classNames, getClassName = Y.ClassNameManager.getClassName; /** * Provides Tab instances for use with TabView * @param config {Object} Object literal specifying tabview configuration properties. * * @class Tab * @constructor * @extends Widget * @uses WidgetChild */ Y.Tab = Y.Base.create('tab', Y.Widget, [Y.WidgetChild], { BOUNDING_TEMPLATE: '
  • ', CONTENT_TEMPLATE: '', PANEL_TEMPLATE: '
    ', _uiSetSelectedPanel: function(selected) { this.get('panelNode').toggleClass(_classNames.selectedPanel, selected); }, _afterTabSelectedChange: function(event) { this._uiSetSelectedPanel(event.newVal); }, _afterParentChange: function(e) { if (!e.newVal) { this._remove(); } else { this._add(); } }, _initAria: function() { var anchor = this.get('contentBox'), id = anchor.get('id'), panel = this.get('panelNode'); if (!id) { id = Y.guid(); anchor.set('id', id); } // Apply the ARIA roles, states and properties to each tab anchor.set('role', 'tab'); anchor.get('parentNode').set('role', 'presentation'); // Apply the ARIA roles, states and properties to each panel panel.setAttrs({ role: 'tabpanel', 'aria-labelledby': id }); }, syncUI: function() { this.set('label', this.get('label')); this.set('content', this.get('content')); this._uiSetSelectedPanel(this.get('selected')); }, bindUI: function() { this.after('selectedChange', this._afterTabSelectedChange); this.after('parentChange', this._afterParentChange); }, renderUI: function() { this._renderPanel(); this._initAria(); }, _renderPanel: function() { this.get('parent').get('panelNode') .appendChild(this.get('panelNode')); }, _add: function() { var parent = this.get('parent').get('contentBox'), list = parent.get('listNode'), panel = parent.get('panelNode'); if (list) { list.appendChild(this.get('boundingBox')); } if (panel) { panel.appendChild(this.get('panelNode')); } }, _remove: function() { this.get('boundingBox').remove(); this.get('panelNode').remove(); }, _onActivate: function(e) { if (e.target === this) { // Prevent the browser from navigating to the URL specified by the // anchor's href attribute. e.domEvent.preventDefault(); e.target.set('selected', 1); } }, initializer: function() { this.publish(this.get('triggerEvent'), { defaultFn: this._onActivate }); }, _defLabelSetter: function(label) { this.get('contentBox').setContent(label); return label; }, _defContentSetter: function(content) { this.get('panelNode').setContent(content); return content; }, // find panel by ID mapping from label href _defPanelNodeValueFn: function() { var href = this.get('contentBox').get('href') || '', parent = this.get('parent'), hashIndex = href.indexOf('#'), panel; href = href.substr(hashIndex); if (href.charAt(0) === '#') { // in-page nav, find by ID panel = Y.one(href); if (panel) { panel.addClass(_classNames.tabPanel); } } // use the one found by id, or else try matching indices if (!panel && parent) { panel = parent.get('panelNode') .get('children').item(this.get('index')); } if (!panel) { // create if none found panel = Y.Node.create(this.PANEL_TEMPLATE); } return panel; } }, { ATTRS: { /** * @attribute triggerEvent * @default "click" * @type String */ triggerEvent: { value: 'click' }, /** * @attribute label * @type String */ label: { setter: '_defLabelSetter', validator: Lang.isString }, /** * @attribute content * @type String */ content: { setter: '_defContentSetter', validator: Lang.isString }, /** * @attribute panelNode * @type Y.Node */ panelNode: { setter: function(node) { node = Y.one(node); if (node) { node.addClass(_classNames.tabPanel); } return node; }, valueFn: '_defPanelNodeValueFn' }, tabIndex: { value: null, validator: '_validTabIndex' } }, HTML_PARSER: { selected: function(contentBox) { var ret = (this.get('boundingBox').hasClass(_classNames.selectedTab)) ? 1 : 0; return ret; } } }); }, '3.3.0' ,{requires:['substitute', 'node-pluginhost', 'node-focusmanager', 'tabview-base', 'widget', 'widget-parent', 'widget-child']});