// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview TabPane widget implementation. * * @author eae@google.com (Emil A Eklund) */ goog.provide('goog.ui.TabPane'); goog.provide('goog.ui.TabPane.Events'); goog.provide('goog.ui.TabPane.TabLocation'); goog.provide('goog.ui.TabPane.TabPage'); goog.provide('goog.ui.TabPaneEvent'); goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.dom.classlist'); goog.require('goog.events'); goog.require('goog.events.Event'); goog.require('goog.events.EventTarget'); goog.require('goog.events.EventType'); goog.require('goog.events.KeyCodes'); goog.require('goog.html.SafeStyleSheet'); goog.require('goog.style'); /** * TabPane widget. All children already inside the tab pane container element * will be be converted to tabs. Each tab is represented by a goog.ui.TabPane. * TabPage object. Further pages can be constructed either from an existing * container or created from scratch. * * @param {Element} el Container element to create the tab pane out of. * @param {goog.ui.TabPane.TabLocation=} opt_tabLocation Location of the tabs * in relation to the content container. Default is top. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. * @param {boolean=} opt_useMouseDown Whether to use MOUSEDOWN instead of CLICK * for tab changes. * @extends {goog.events.EventTarget} * @constructor * @see ../demos/tabpane.html * @deprecated Use goog.ui.TabBar instead. */ goog.ui.TabPane = function( el, opt_tabLocation, opt_domHelper, opt_useMouseDown) { goog.events.EventTarget.call(this); /** * DomHelper used to interact with the document, allowing components to be * created in a different window. This property is considered protected; * subclasses of Component may refer to it directly. * @type {goog.dom.DomHelper} * @protected * @suppress {underscore|visibility} */ this.dom_ = opt_domHelper || goog.dom.getDomHelper(); /** * Tab pane element. * @type {Element} * @private */ this.el_ = el; /** * Collection of tab panes. * @type {Array} * @private */ this.pages_ = []; /** * Location of the tabs with respect to the content box. * @type {goog.ui.TabPane.TabLocation} * @private */ this.tabLocation_ = opt_tabLocation ? opt_tabLocation : goog.ui.TabPane.TabLocation.TOP; /** * Whether to use MOUSEDOWN instead of CLICK for tab change events. This * fixes some focus problems on Safari/Chrome. * @type {boolean} * @private */ this.useMouseDown_ = !!opt_useMouseDown; this.create_(); }; goog.inherits(goog.ui.TabPane, goog.events.EventTarget); goog.tagUnsealableClass(goog.ui.TabPane); /** * Element containing the tab buttons. * @type {Element} * @private */ goog.ui.TabPane.prototype.elButtonBar_; /** * Element containing the tab pages. * @type {Element} * @private */ goog.ui.TabPane.prototype.elContent_; /** * Selected page. * @type {goog.ui.TabPane.TabPage?} * @private */ goog.ui.TabPane.prototype.selected_; /** * Constants for event names * * @const */ goog.ui.TabPane.Events = { CHANGE: 'change' }; /** * Enum for representing the location of the tabs in relation to the content. * * @enum {number} */ goog.ui.TabPane.TabLocation = { TOP: 0, BOTTOM: 1, LEFT: 2, RIGHT: 3 }; /** * Creates HTML nodes for tab pane. * * @private */ goog.ui.TabPane.prototype.create_ = function() { this.el_.className = goog.getCssName('goog-tabpane'); var nodes = this.getChildNodes_(); // Create tab strip this.elButtonBar_ = this.dom_.createDom( goog.dom.TagName.UL, {'className': goog.getCssName('goog-tabpane-tabs'), 'tabIndex': '0'}); // Create content area this.elContent_ = this.dom_.createDom( goog.dom.TagName.DIV, goog.getCssName('goog-tabpane-cont')); this.el_.appendChild(this.elContent_); var element = goog.asserts.assertElement(this.el_); switch (this.tabLocation_) { case goog.ui.TabPane.TabLocation.TOP: element.insertBefore(this.elButtonBar_, this.elContent_); element.insertBefore(this.createClear_(), this.elContent_); goog.dom.classlist.add(element, goog.getCssName('goog-tabpane-top')); break; case goog.ui.TabPane.TabLocation.BOTTOM: element.appendChild(this.elButtonBar_); element.appendChild(this.createClear_()); goog.dom.classlist.add(element, goog.getCssName('goog-tabpane-bottom')); break; case goog.ui.TabPane.TabLocation.LEFT: element.insertBefore(this.elButtonBar_, this.elContent_); goog.dom.classlist.add(element, goog.getCssName('goog-tabpane-left')); break; case goog.ui.TabPane.TabLocation.RIGHT: element.insertBefore(this.elButtonBar_, this.elContent_); goog.dom.classlist.add(element, goog.getCssName('goog-tabpane-right')); break; default: throw Error('Invalid tab location'); } // Listen for click and keydown events on header this.elButtonBar_.tabIndex = 0; goog.events.listen( this.elButtonBar_, this.useMouseDown_ ? goog.events.EventType.MOUSEDOWN : goog.events.EventType.CLICK, this.onHeaderClick_, false, this); goog.events.listen( this.elButtonBar_, goog.events.EventType.KEYDOWN, this.onHeaderKeyDown_, false, this); this.createPages_(nodes); }; /** * Creates the HTML node for the clearing div, and associated style in * the . * * @return {!Element} Reference to a DOM div node. * @private */ goog.ui.TabPane.prototype.createClear_ = function() { var clearFloatStyle = goog.html.SafeStyleSheet.createRule( '.' + goog.getCssName('goog-tabpane-clear'), {'clear': 'both', 'height': '0', 'overflow': 'hidden'}); goog.style.installSafeStyleSheet(clearFloatStyle); return this.dom_.createDom( goog.dom.TagName.DIV, goog.getCssName('goog-tabpane-clear')); }; /** @override */ goog.ui.TabPane.prototype.disposeInternal = function() { goog.ui.TabPane.superClass_.disposeInternal.call(this); goog.events.unlisten( this.elButtonBar_, this.useMouseDown_ ? goog.events.EventType.MOUSEDOWN : goog.events.EventType.CLICK, this.onHeaderClick_, false, this); goog.events.unlisten( this.elButtonBar_, goog.events.EventType.KEYDOWN, this.onHeaderKeyDown_, false, this); delete this.el_; this.elButtonBar_ = null; this.elContent_ = null; }; /** * @return {!Array} The element child nodes of tab pane container. * @private */ goog.ui.TabPane.prototype.getChildNodes_ = function() { var nodes = []; var child = goog.dom.getFirstElementChild(this.el_); while (child) { nodes.push(child); child = goog.dom.getNextElementSibling(child); } return nodes; }; /** * Creates pages out of a collection of elements. * * @param {Array} nodes Array of elements to create pages out of. * @private */ goog.ui.TabPane.prototype.createPages_ = function(nodes) { for (var node, i = 0; node = nodes[i]; i++) { this.addPage(new goog.ui.TabPane.TabPage(node)); } }; /** * Adds a page to the tab pane. * * @param {goog.ui.TabPane.TabPage} page Tab page to add. * @param {number=} opt_index Zero based index to insert tab at. Inserted at the * end if not specified. */ goog.ui.TabPane.prototype.addPage = function(page, opt_index) { // If page is already in another tab pane it's removed from that one before it // can be added to this one. if (page.parent_ && page.parent_ != this && page.parent_ instanceof goog.ui.TabPane) { page.parent_.removePage(page); } // Insert page at specified position var index = this.pages_.length; if (goog.isDef(opt_index) && opt_index != index) { index = opt_index; this.pages_.splice(index, 0, page); this.elButtonBar_.insertBefore( page.elTitle_, this.elButtonBar_.childNodes[index]); } // Append page to end else { this.pages_.push(page); this.elButtonBar_.appendChild(page.elTitle_); } page.setParent_(this, index); // Select first page and fire change event if (!this.selected_) { this.selected_ = page; this.dispatchEvent( new goog.ui.TabPaneEvent( goog.ui.TabPane.Events.CHANGE, this, this.selected_)); } // Move page content to the tab pane and update visibility. this.elContent_.appendChild(page.elContent_); page.setVisible_(page == this.selected_); // Update index for following pages for (var pg, i = index + 1; pg = this.pages_[i]; i++) { pg.index_ = i; } }; /** * Removes the specified page from the tab pane. * * @param {goog.ui.TabPane.TabPage|number} page Reference to tab page or zero * based index. */ goog.ui.TabPane.prototype.removePage = function(page) { if (goog.isNumber(page)) { page = this.pages_[page]; } this.pages_.splice(page.index_, 1); page.setParent_(null); goog.dom.removeNode(page.elTitle_); goog.dom.removeNode(page.elContent_); for (var pg, i = 0; pg = this.pages_[i]; i++) { pg.setParent_(this, i); } }; /** * Gets the tab page by zero based index. * * @param {number} index Index of page to return. * @return {goog.ui.TabPane.TabPage?} page The tab page. */ goog.ui.TabPane.prototype.getPage = function(index) { return this.pages_[index]; }; /** * Sets the selected tab page by object reference. * * @param {goog.ui.TabPane.TabPage} page Tab page to select. */ goog.ui.TabPane.prototype.setSelectedPage = function(page) { if (page.isEnabled() && (!this.selected_ || page != this.selected_)) { this.selected_.setVisible_(false); page.setVisible_(true); this.selected_ = page; // Fire changed event this.dispatchEvent( new goog.ui.TabPaneEvent( goog.ui.TabPane.Events.CHANGE, this, this.selected_)); } }; /** * Sets the selected tab page by zero based index. * * @param {number} index Index of page to select. */ goog.ui.TabPane.prototype.setSelectedIndex = function(index) { if (index >= 0 && index < this.pages_.length) { this.setSelectedPage(this.pages_[index]); } }; /** * @return {number} The index for the selected tab page or -1 if no page is * selected. */ goog.ui.TabPane.prototype.getSelectedIndex = function() { return this.selected_ ? /** @type {number} */ (this.selected_.index_) : -1; }; /** * @return {goog.ui.TabPane.TabPage?} The selected tab page. */ goog.ui.TabPane.prototype.getSelectedPage = function() { return this.selected_ || null; }; /** * @return {Element} The element that contains the tab pages. */ goog.ui.TabPane.prototype.getContentElement = function() { return this.elContent_ || null; }; /** * @return {Element} The main element for the tabpane. */ goog.ui.TabPane.prototype.getElement = function() { return this.el_ || null; }; /** * Click event handler for header element, handles clicks on tabs. * * @param {goog.events.BrowserEvent} event Click event. * @private */ goog.ui.TabPane.prototype.onHeaderClick_ = function(event) { var el = event.target; // Determine index if a tab (li element) was clicked. while (el != this.elButtonBar_) { if (el.tagName == goog.dom.TagName.LI) { var i; // {} prevents compiler warning for (i = 0; el = el.previousSibling; i++) { } this.setSelectedIndex(i); break; } el = el.parentNode; } event.preventDefault(); }; /** * KeyDown event handler for header element. Arrow keys moves between pages. * Home and end selects the first/last page. * * @param {goog.events.BrowserEvent} event KeyDown event. * @private */ goog.ui.TabPane.prototype.onHeaderKeyDown_ = function(event) { if (event.altKey || event.metaKey || event.ctrlKey) { return; } switch (event.keyCode) { case goog.events.KeyCodes.LEFT: var index = this.selected_.getIndex() - 1; this.setSelectedIndex(index < 0 ? this.pages_.length - 1 : index); break; case goog.events.KeyCodes.RIGHT: var index = this.selected_.getIndex() + 1; this.setSelectedIndex(index >= this.pages_.length ? 0 : index); break; case goog.events.KeyCodes.HOME: this.setSelectedIndex(0); break; case goog.events.KeyCodes.END: this.setSelectedIndex(this.pages_.length - 1); break; } }; /** * Object representing an individual tab pane. * * @param {Element=} opt_el Container element to create the pane out of. * @param {(Element|string)=} opt_title Pane title or element to use as the * title. If not specified the first element in the container is used as * the title. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper * The first parameter can be omitted. * @constructor */ goog.ui.TabPane.TabPage = function(opt_el, opt_title, opt_domHelper) { var title = null, el; if (goog.isString(opt_el) && !goog.isDef(opt_title)) { title = opt_el; } else if (opt_title) { title = opt_title; el = opt_el; } else if (opt_el) { var child = goog.dom.getFirstElementChild(opt_el); if (child) { title = goog.dom.getTextContent(child); child.parentNode.removeChild(child); } el = opt_el; } /** * DomHelper used to interact with the document, allowing components to be * created in a different window. This property is considered protected; * subclasses of Component may refer to it directly. * @type {goog.dom.DomHelper} * @protected * @suppress {underscore|visibility} */ this.dom_ = opt_domHelper || goog.dom.getDomHelper(); /** * Content element * @type {Element} * @private */ this.elContent_ = el || this.dom_.createDom(goog.dom.TagName.DIV); /** * Title element * @type {Element} * @private */ this.elTitle_ = this.dom_.createDom(goog.dom.TagName.LI, null, title); /** * Parent TabPane reference. * @type {goog.ui.TabPane?} * @private */ this.parent_ = null; /** * Index for page in tab pane. * @type {?number} * @private */ this.index_ = null; /** * Flags if this page is enabled and can be selected. * @type {boolean} * @private */ this.enabled_ = true; }; /** * @return {string} The title for tab page. */ goog.ui.TabPane.TabPage.prototype.getTitle = function() { return goog.dom.getTextContent(this.elTitle_); }; /** * Sets title for tab page. * * @param {string} title Title for tab page. */ goog.ui.TabPane.TabPage.prototype.setTitle = function(title) { goog.dom.setTextContent(this.elTitle_, title); }; /** * @return {Element} The title element. */ goog.ui.TabPane.TabPage.prototype.getTitleElement = function() { return this.elTitle_; }; /** * @return {Element} The content element. */ goog.ui.TabPane.TabPage.prototype.getContentElement = function() { return this.elContent_; }; /** * @return {?number} The index of page in tab pane. */ goog.ui.TabPane.TabPage.prototype.getIndex = function() { return this.index_; }; /** * @return {goog.ui.TabPane?} The parent tab pane for page. */ goog.ui.TabPane.TabPage.prototype.getParent = function() { return this.parent_; }; /** * Selects page in the associated tab pane. */ goog.ui.TabPane.TabPage.prototype.select = function() { if (this.parent_) { this.parent_.setSelectedPage(this); } }; /** * Sets the enabled state. * * @param {boolean} enabled Enabled state. */ goog.ui.TabPane.TabPage.prototype.setEnabled = function(enabled) { this.enabled_ = enabled; this.elTitle_.className = enabled ? goog.getCssName('goog-tabpane-tab') : goog.getCssName('goog-tabpane-tab-disabled'); }; /** * Returns if the page is enabled. * @return {boolean} Whether the page is enabled or not. */ goog.ui.TabPane.TabPage.prototype.isEnabled = function() { return this.enabled_; }; /** * Sets visible state for page content and updates style of tab. * * @param {boolean} visible Visible state. * @private */ goog.ui.TabPane.TabPage.prototype.setVisible_ = function(visible) { if (this.isEnabled()) { this.elContent_.style.display = visible ? '' : 'none'; this.elTitle_.className = visible ? goog.getCssName('goog-tabpane-tab-selected') : goog.getCssName('goog-tabpane-tab'); } }; /** * Sets parent tab pane for tab page. * * @param {goog.ui.TabPane?} tabPane Tab strip object. * @param {number=} opt_index Index of page in pane. * @private */ goog.ui.TabPane.TabPage.prototype.setParent_ = function(tabPane, opt_index) { this.parent_ = tabPane; this.index_ = goog.isDef(opt_index) ? opt_index : null; }; /** * Object representing a tab pane page changed event. * * @param {string} type Event type. * @param {goog.ui.TabPane} target Tab widget initiating event. * @param {goog.ui.TabPane.TabPage} page Selected page in tab pane. * @extends {goog.events.Event} * @constructor * @final */ goog.ui.TabPaneEvent = function(type, target, page) { goog.events.Event.call(this, type, target); /** * The selected page. * @type {goog.ui.TabPane.TabPage} */ this.page = page; }; goog.inherits(goog.ui.TabPaneEvent, goog.events.Event);