123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- // 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 Zippy widget implementation.
- *
- * @author eae@google.com (Emil A Eklund)
- * @see ../demos/zippy.html
- */
- goog.provide('goog.ui.Zippy');
- goog.provide('goog.ui.Zippy.Events');
- goog.provide('goog.ui.ZippyEvent');
- goog.require('goog.a11y.aria');
- goog.require('goog.a11y.aria.Role');
- goog.require('goog.a11y.aria.State');
- goog.require('goog.dom');
- goog.require('goog.dom.classlist');
- goog.require('goog.events.Event');
- goog.require('goog.events.EventHandler');
- goog.require('goog.events.EventTarget');
- goog.require('goog.events.EventType');
- goog.require('goog.events.KeyCodes');
- goog.require('goog.events.KeyHandler');
- goog.require('goog.style');
- /**
- * Zippy widget. Expandable/collapsible container, clicking the header toggles
- * the visibility of the content.
- *
- * @extends {goog.events.EventTarget}
- * @param {Element|string|null} header Header element, either element
- * reference, string id or null if no header exists.
- * @param {Element|string|function():Element=} opt_content Content element
- * (if any), either element reference or string id. If skipped, the caller
- * should handle the TOGGLE event in its own way. If a function is passed,
- * then if will be called to create the content element the first time the
- * zippy is expanded.
- * @param {boolean=} opt_expanded Initial expanded/visibility state. If
- * undefined, attempts to infer the state from the DOM. Setting visibility
- * using one of the standard Soy templates guarantees correct inference.
- * @param {Element|string=} opt_expandedHeader Element to use as the header when
- * the zippy is expanded.
- * @param {goog.dom.DomHelper=} opt_domHelper An optional DOM helper.
- * @constructor
- */
- goog.ui.Zippy = function(
- header, opt_content, opt_expanded, opt_expandedHeader, opt_domHelper) {
- goog.ui.Zippy.base(this, 'constructor');
- /**
- * DomHelper used to interact with the document, allowing components to be
- * created in a different window.
- * @type {!goog.dom.DomHelper}
- * @private
- */
- this.dom_ = opt_domHelper || goog.dom.getDomHelper();
- /**
- * Header element or null if no header exists.
- * @type {Element}
- * @private
- */
- this.elHeader_ = this.dom_.getElement(header) || null;
- /**
- * When present, the header to use when the zippy is expanded.
- * @type {Element}
- * @private
- */
- this.elExpandedHeader_ = this.dom_.getElement(opt_expandedHeader || null);
- /**
- * Function that will create the content element, or false if there is no such
- * function.
- * @type {?function():Element}
- * @private
- */
- this.lazyCreateFunc_ = goog.isFunction(opt_content) ? opt_content : null;
- /**
- * Content element.
- * @type {Element}
- * @private
- */
- this.elContent_ = this.lazyCreateFunc_ || !opt_content ?
- null :
- this.dom_.getElement(/** @type {!Element} */ (opt_content));
- /**
- * Expanded state.
- * @type {boolean}
- * @private
- */
- this.expanded_ = opt_expanded == true;
- if (!goog.isDef(opt_expanded) && !this.lazyCreateFunc_) {
- // For the dual caption case, we can get expanded_ from the visibility of
- // the expandedHeader. For the single-caption case, we use the
- // presence/absence of the relevant class. Using one of the standard Soy
- // templates guarantees that this will work.
- if (this.elExpandedHeader_) {
- this.expanded_ = goog.style.isElementShown(this.elExpandedHeader_);
- } else if (this.elHeader_) {
- this.expanded_ = goog.dom.classlist.contains(
- this.elHeader_, goog.getCssName('goog-zippy-expanded'));
- }
- }
- /**
- * A keyboard events handler. If there are two headers it is shared for both.
- * @type {goog.events.EventHandler<!goog.ui.Zippy>}
- * @private
- */
- this.keyboardEventHandler_ = new goog.events.EventHandler(this);
- /**
- * The keyhandler used for listening on most key events. This takes care of
- * abstracting away some of the browser differences.
- * @private {!goog.events.KeyHandler}
- */
- this.keyHandler_ = new goog.events.KeyHandler();
- /**
- * A mouse events handler. If there are two headers it is shared for both.
- * @type {goog.events.EventHandler<!goog.ui.Zippy>}
- * @private
- */
- this.mouseEventHandler_ = new goog.events.EventHandler(this);
- var self = this;
- function addHeaderEvents(el) {
- if (el) {
- el.tabIndex = 0;
- goog.a11y.aria.setRole(el, self.getAriaRole());
- goog.dom.classlist.add(el, goog.getCssName('goog-zippy-header'));
- self.enableMouseEventsHandling_(el);
- self.enableKeyboardEventsHandling_(el);
- }
- }
- addHeaderEvents(this.elHeader_);
- addHeaderEvents(this.elExpandedHeader_);
- // initialize based on expanded state
- this.setExpanded(this.expanded_);
- };
- goog.inherits(goog.ui.Zippy, goog.events.EventTarget);
- goog.tagUnsealableClass(goog.ui.Zippy);
- /**
- * Constants for event names
- *
- * @const
- */
- goog.ui.Zippy.Events = {
- // Zippy will dispatch an ACTION event for user interaction. Mimics
- // {@code goog.ui.Controls#performActionInternal} by first changing
- // the toggle state and then dispatching an ACTION event.
- ACTION: 'action',
- // Zippy state is toggled from collapsed to expanded or vice versa.
- TOGGLE: 'toggle'
- };
- /**
- * Whether to listen for and handle mouse events; defaults to true.
- * @type {boolean}
- * @private
- */
- goog.ui.Zippy.prototype.handleMouseEvents_ = true;
- /**
- * Whether to listen for and handle key events; defaults to true.
- * @type {boolean}
- * @private
- */
- goog.ui.Zippy.prototype.handleKeyEvents_ = true;
- /** @override */
- goog.ui.Zippy.prototype.disposeInternal = function() {
- goog.ui.Zippy.base(this, 'disposeInternal');
- goog.dispose(this.keyboardEventHandler_);
- goog.dispose(this.keyHandler_);
- goog.dispose(this.mouseEventHandler_);
- };
- /**
- * @return {goog.a11y.aria.Role} The ARIA role to be applied to Zippy element.
- */
- goog.ui.Zippy.prototype.getAriaRole = function() {
- return goog.a11y.aria.Role.TAB;
- };
- /**
- * @return {HTMLElement} The content element.
- */
- goog.ui.Zippy.prototype.getContentElement = function() {
- return /** @type {!HTMLElement} */ (this.elContent_);
- };
- /**
- * @return {Element} The visible header element.
- */
- goog.ui.Zippy.prototype.getVisibleHeaderElement = function() {
- var expandedHeader = this.elExpandedHeader_;
- return expandedHeader && goog.style.isElementShown(expandedHeader) ?
- expandedHeader :
- this.elHeader_;
- };
- /**
- * Expands content pane.
- */
- goog.ui.Zippy.prototype.expand = function() {
- this.setExpanded(true);
- };
- /**
- * Collapses content pane.
- */
- goog.ui.Zippy.prototype.collapse = function() {
- this.setExpanded(false);
- };
- /**
- * Toggles expanded state.
- */
- goog.ui.Zippy.prototype.toggle = function() {
- this.setExpanded(!this.expanded_);
- };
- /**
- * Sets expanded state.
- *
- * @param {boolean} expanded Expanded/visibility state.
- */
- goog.ui.Zippy.prototype.setExpanded = function(expanded) {
- if (this.elContent_) {
- // Hide the element, if one is provided.
- goog.style.setElementShown(this.elContent_, expanded);
- } else if (expanded && this.lazyCreateFunc_) {
- // Assume that when the element is not hidden upon creation.
- this.elContent_ = this.lazyCreateFunc_();
- }
- if (this.elContent_) {
- goog.dom.classlist.add(
- this.elContent_, goog.getCssName('goog-zippy-content'));
- }
- if (this.elExpandedHeader_) {
- // Hide the show header and show the hide one.
- goog.style.setElementShown(this.elHeader_, !expanded);
- goog.style.setElementShown(this.elExpandedHeader_, expanded);
- } else {
- // Update header image, if any.
- this.updateHeaderClassName(expanded);
- }
- this.setExpandedInternal(expanded);
- // Fire toggle event
- this.dispatchEvent(
- new goog.ui.ZippyEvent(
- goog.ui.Zippy.Events.TOGGLE, this, this.expanded_));
- };
- /**
- * Sets expanded internal state.
- *
- * @param {boolean} expanded Expanded/visibility state.
- * @protected
- */
- goog.ui.Zippy.prototype.setExpandedInternal = function(expanded) {
- this.expanded_ = expanded;
- };
- /**
- * @return {boolean} Whether the zippy is expanded.
- */
- goog.ui.Zippy.prototype.isExpanded = function() {
- return this.expanded_;
- };
- /**
- * Updates the header element's className and ARIA (accessibility) EXPANDED
- * state.
- *
- * @param {boolean} expanded Expanded/visibility state.
- * @protected
- */
- goog.ui.Zippy.prototype.updateHeaderClassName = function(expanded) {
- if (this.elHeader_) {
- goog.dom.classlist.enable(
- this.elHeader_, goog.getCssName('goog-zippy-expanded'), expanded);
- goog.dom.classlist.enable(
- this.elHeader_, goog.getCssName('goog-zippy-collapsed'), !expanded);
- goog.a11y.aria.setState(
- this.elHeader_, goog.a11y.aria.State.EXPANDED, expanded);
- }
- };
- /**
- * @return {boolean} Whether the Zippy handles its own key events.
- */
- goog.ui.Zippy.prototype.isHandleKeyEvents = function() {
- return this.handleKeyEvents_;
- };
- /**
- * @return {boolean} Whether the Zippy handles its own mouse events.
- */
- goog.ui.Zippy.prototype.isHandleMouseEvents = function() {
- return this.handleMouseEvents_;
- };
- /**
- * Sets whether the Zippy handles it's own keyboard events.
- * @param {boolean} enable Whether the Zippy handles keyboard events.
- */
- goog.ui.Zippy.prototype.setHandleKeyboardEvents = function(enable) {
- if (this.handleKeyEvents_ != enable) {
- this.handleKeyEvents_ = enable;
- if (enable) {
- this.enableKeyboardEventsHandling_(this.elHeader_);
- this.enableKeyboardEventsHandling_(this.elExpandedHeader_);
- } else {
- this.keyboardEventHandler_.removeAll();
- this.keyHandler_.detach();
- }
- }
- };
- /**
- * Sets whether the Zippy handles it's own mouse events.
- * @param {boolean} enable Whether the Zippy handles mouse events.
- */
- goog.ui.Zippy.prototype.setHandleMouseEvents = function(enable) {
- if (this.handleMouseEvents_ != enable) {
- this.handleMouseEvents_ = enable;
- if (enable) {
- this.enableMouseEventsHandling_(this.elHeader_);
- this.enableMouseEventsHandling_(this.elExpandedHeader_);
- } else {
- this.mouseEventHandler_.removeAll();
- }
- }
- };
- /**
- * Enables keyboard events handling for the passed header element.
- * @param {Element} header The header element.
- * @private
- */
- goog.ui.Zippy.prototype.enableKeyboardEventsHandling_ = function(header) {
- if (header) {
- this.keyHandler_.attach(header);
- this.keyboardEventHandler_.listen(
- this.keyHandler_, goog.events.KeyHandler.EventType.KEY,
- this.onHeaderKeyDown_);
- }
- };
- /**
- * Enables mouse events handling for the passed header element.
- * @param {Element} header The header element.
- * @private
- */
- goog.ui.Zippy.prototype.enableMouseEventsHandling_ = function(header) {
- if (header) {
- this.mouseEventHandler_.listen(
- header, goog.events.EventType.CLICK, this.onHeaderClick_);
- }
- };
- /**
- * KeyDown event handler for header element. Enter and space toggles expanded
- * state.
- *
- * @param {goog.events.BrowserEvent} event KeyDown event.
- * @private
- */
- goog.ui.Zippy.prototype.onHeaderKeyDown_ = function(event) {
- if (event.keyCode == goog.events.KeyCodes.ENTER ||
- event.keyCode == goog.events.KeyCodes.SPACE) {
- this.toggle();
- this.dispatchActionEvent_();
- // Prevent enter key from submitting form.
- event.preventDefault();
- event.stopPropagation();
- }
- };
- /**
- * Click event handler for header element.
- *
- * @param {goog.events.BrowserEvent} event Click event.
- * @private
- */
- goog.ui.Zippy.prototype.onHeaderClick_ = function(event) {
- this.toggle();
- this.dispatchActionEvent_();
- };
- /**
- * Dispatch an ACTION event whenever there is user interaction with the header.
- * Please note that after the zippy state change is completed a TOGGLE event
- * will be dispatched. However, the TOGGLE event is dispatch on every toggle,
- * including programmatic call to {@code #toggle}.
- * @private
- */
- goog.ui.Zippy.prototype.dispatchActionEvent_ = function() {
- this.dispatchEvent(new goog.events.Event(goog.ui.Zippy.Events.ACTION, this));
- };
- /**
- * Object representing a zippy toggle event.
- *
- * @param {string} type Event type.
- * @param {goog.ui.Zippy} target Zippy widget initiating event.
- * @param {boolean} expanded Expanded state.
- * @extends {goog.events.Event}
- * @constructor
- * @final
- */
- goog.ui.ZippyEvent = function(type, target, expanded) {
- goog.ui.ZippyEvent.base(this, 'constructor', type, target);
- /**
- * The expanded state.
- * @type {boolean}
- */
- this.expanded = expanded;
- };
- goog.inherits(goog.ui.ZippyEvent, goog.events.Event);
|