Path: blob/trunk/third_party/closure/goog/ui/control.js
4506 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Base class for UI controls such as buttons, menus, menu items,8* toolbar buttons, etc. The implementation is based on a generalized version9* of {@link goog.ui.MenuItem}.10* TODO(attila): If the renderer framework works well, pull it into Component.11*12* @see ../demos/control.html13* @see http://code.google.com/p/closure-library/wiki/IntroToControls14*/1516goog.provide('goog.ui.Control');1718goog.require('goog.Disposable');19goog.require('goog.array');20goog.require('goog.dispose');21goog.require('goog.dom');22goog.require('goog.events.BrowserEvent');23goog.require('goog.events.Event');24goog.require('goog.events.EventHandler');25goog.require('goog.events.EventType');26goog.require('goog.events.KeyCodes');27goog.require('goog.events.KeyHandler');28goog.require('goog.functions');29goog.require('goog.string');30goog.require('goog.ui.Component');31goog.require('goog.ui.ComponentUtil');32/** @suppress {extraRequire} */33goog.require('goog.ui.ControlContent');34goog.require('goog.ui.ControlRenderer');35goog.require('goog.ui.registry');36goog.require('goog.userAgent');37goog.requireType('goog.a11y.aria.Role');38goog.requireType('goog.events.KeyEvent');39404142/**43* Base class for UI controls. Extends {@link goog.ui.Component} by adding44* the following:45* <ul>46* <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling,47* <li>a pluggable <em>renderer</em> framework, to simplify the creation of48* simple controls without the need to subclass this class,49* <li>the notion of component <em>content</em>, like a text caption or DOM50* structure displayed in the component (e.g. a button label),51* <li>getter and setter for component content, as well as a getter and52* setter specifically for caption text (for convenience),53* <li>support for hiding/showing the component,54<li>fine-grained control over supported states and state transition55events, and56* <li>default mouse and keyboard event handling.57* </ul>58* This class has sufficient built-in functionality for most simple UI controls.59* All controls dispatch SHOW, HIDE, ENTER, LEAVE, and ACTION events on show,60* hide, mouseover, mouseout, and user action, respectively. Additional states61* are also supported. See closure/demos/control.html62* for example usage.63* @param {goog.ui.ControlContent=} opt_content Text caption or DOM structure64* to display as the content of the control (if any).65* @param {goog.ui.ControlRenderer=} opt_renderer Renderer used to render or66* decorate the component; defaults to {@link goog.ui.ControlRenderer}.67* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for68* document interaction.69* @constructor70* @extends {goog.ui.Component}71*/72goog.ui.Control = function(opt_content, opt_renderer, opt_domHelper) {73'use strict';74goog.ui.Component.call(this, opt_domHelper);75this.renderer_ =76opt_renderer || goog.ui.registry.getDefaultRenderer(this.constructor);77this.setContentInternal(opt_content !== undefined ? opt_content : null);7879/** @private {?string} The control's aria-label. */80this.ariaLabel_ = null;8182/** @private {goog.ui.Control.IeMouseEventSequenceSimulator_} */83this.ieMouseEventSequenceSimulator_;84};85goog.inherits(goog.ui.Control, goog.ui.Component);868788// Renderer registry.89// TODO(attila): Refactor existing usages inside Google in a follow-up CL.909192/**93* Maps a CSS class name to a function that returns a new instance of94* {@link goog.ui.Control} or a subclass thereof, suitable to decorate95* an element that has the specified CSS class. UI components that extend96* {@link goog.ui.Control} and want {@link goog.ui.Container}s to be able97* to discover and decorate elements using them should register a factory98* function via this API.99* @param {string} className CSS class name.100* @param {Function} decoratorFunction Function that takes no arguments and101* returns a new instance of a control to decorate an element with the102* given class.103* @deprecated Use {@link goog.ui.registry.setDecoratorByClassName} instead.104*/105goog.ui.Control.registerDecorator = goog.ui.registry.setDecoratorByClassName;106107108/**109* Takes an element and returns a new instance of {@link goog.ui.Control}110* or a subclass, suitable to decorate it (based on the element's CSS class).111* @param {Element} element Element to decorate.112* @return {goog.ui.Control?} New control instance to decorate the element113* (null if none).114* @deprecated Use {@link goog.ui.registry.getDecorator} instead.115*/116goog.ui.Control.getDecorator =117/** @type {function(Element): goog.ui.Control} */ (118goog.ui.registry.getDecorator);119120121/**122* Renderer associated with the component.123* @type {goog.ui.ControlRenderer|undefined}124* @private125*/126goog.ui.Control.prototype.renderer_;127128129/**130* Text caption or DOM structure displayed in the component.131* @type {?goog.ui.ControlContent}132* @private133*/134goog.ui.Control.prototype.content_ = null;135136137/**138* Current component state; a bit mask of {@link goog.ui.Component.State}s.139* @type {number}140* @private141*/142goog.ui.Control.prototype.state_ = 0x00;143144145/**146* A bit mask of {@link goog.ui.Component.State}s this component supports.147* @type {number}148* @private149*/150goog.ui.Control.prototype.supportedStates_ = goog.ui.Component.State.DISABLED |151goog.ui.Component.State.HOVER | goog.ui.Component.State.ACTIVE |152goog.ui.Component.State.FOCUSED;153154155/**156* A bit mask of {@link goog.ui.Component.State}s for which this component157* provides default event handling. For example, a component that handles158* the HOVER state automatically will highlight itself on mouseover, whereas159* a component that doesn't handle HOVER automatically will only dispatch160* ENTER and LEAVE events but not call {@link setHighlighted} on itself.161* By default, components provide default event handling for all states.162* Controls hosted in containers (e.g. menu items in a menu, or buttons in a163* toolbar) will typically want to have their container manage their highlight164* state. Selectable controls managed by a selection model will also typically165* want their selection state to be managed by the model.166* @type {number}167* @private168*/169goog.ui.Control.prototype.autoStates_ = goog.ui.Component.State.ALL;170171172/**173* A bit mask of {@link goog.ui.Component.State}s for which this component174* dispatches state transition events. Because events are expensive, the175* default behavior is to not dispatch any state transition events at all.176* Use the {@link #setDispatchTransitionEvents} API to request transition177* events as needed. Subclasses may enable transition events by default.178* Controls hosted in containers or managed by a selection model will typically179* want to dispatch transition events.180* @type {number}181* @private182*/183goog.ui.Control.prototype.statesWithTransitionEvents_ = 0x00;184185186/**187* Component visibility.188* @type {boolean}189* @private190*/191goog.ui.Control.prototype.visible_ = true;192193194/**195* Keyboard event handler.196* @type {goog.events.KeyHandler}197* @private198*/199goog.ui.Control.prototype.keyHandler_;200201202/**203* Additional class name(s) to apply to the control's root element, if any.204* @type {Array<string>?}205* @private206*/207goog.ui.Control.prototype.extraClassNames_ = null;208209210/**211* Whether the control should listen for and handle mouse events; defaults to212* true.213* @type {boolean}214* @private215*/216goog.ui.Control.prototype.handleMouseEvents_ = true;217218219/**220* Whether the control allows text selection within its DOM. Defaults to false.221* @type {boolean}222* @private223*/224goog.ui.Control.prototype.allowTextSelection_ = false;225226227/**228* The control's preferred ARIA role.229* @type {?goog.a11y.aria.Role}230* @private231*/232goog.ui.Control.prototype.preferredAriaRole_ = null;233234235// Event handler and renderer management.236237238/**239* Returns true if the control is configured to handle its own mouse events,240* false otherwise. Controls not hosted in {@link goog.ui.Container}s have241* to handle their own mouse events, but controls hosted in containers may242* allow their parent to handle mouse events on their behalf. Considered243* protected; should only be used within this package and by subclasses.244* @return {boolean} Whether the control handles its own mouse events.245*/246goog.ui.Control.prototype.isHandleMouseEvents = function() {247'use strict';248return this.handleMouseEvents_;249};250251252/**253* Enables or disables mouse event handling for the control. Containers may254* use this method to disable mouse event handling in their child controls.255* Considered protected; should only be used within this package and by256* subclasses.257* @param {boolean} enable Whether to enable or disable mouse event handling.258*/259goog.ui.Control.prototype.setHandleMouseEvents = function(enable) {260'use strict';261if (this.isInDocument() && enable != this.handleMouseEvents_) {262// Already in the document; need to update event handler.263this.enableMouseEventHandling_(enable);264}265this.handleMouseEvents_ = enable;266};267268269/**270* Returns the DOM element on which the control is listening for keyboard271* events (null if none).272* @return {Element} Element on which the control is listening for key273* events.274*/275goog.ui.Control.prototype.getKeyEventTarget = function() {276'use strict';277// Delegate to renderer.278return this.renderer_.getKeyEventTarget(this);279};280281282/**283* Returns the keyboard event handler for this component, lazily created the284* first time this method is called. Considered protected; should only be285* used within this package and by subclasses.286* @return {!goog.events.KeyHandler} Keyboard event handler for this component.287* @protected288*/289goog.ui.Control.prototype.getKeyHandler = function() {290'use strict';291return this.keyHandler_ || (this.keyHandler_ = new goog.events.KeyHandler());292};293294295/**296* Returns the renderer used by this component to render itself or to decorate297* an existing element.298* @return {goog.ui.ControlRenderer|undefined} Renderer used by the component299* (undefined if none).300*/301goog.ui.Control.prototype.getRenderer = function() {302'use strict';303return this.renderer_;304};305306307/**308* Registers the given renderer with the component. Changing renderers after309* the component has entered the document is an error.310* @param {goog.ui.ControlRenderer} renderer Renderer used by the component.311* @throws {Error} If the control is already in the document.312*/313goog.ui.Control.prototype.setRenderer = function(renderer) {314'use strict';315if (this.isInDocument()) {316// Too late.317throw new Error(goog.ui.Component.Error.ALREADY_RENDERED);318}319320if (this.getElement()) {321// The component has already been rendered, but isn't yet in the document.322// Replace the renderer and delete the current DOM, so it can be re-rendered323// using the new renderer the next time someone calls render().324this.setElementInternal(null);325}326327this.renderer_ = renderer;328};329330331// Support for additional styling.332333334/**335* Returns any additional class name(s) to be applied to the component's336* root element, or null if no extra class names are needed.337* @return {Array<string>?} Additional class names to be applied to338* the component's root element (null if none).339*/340goog.ui.Control.prototype.getExtraClassNames = function() {341'use strict';342return this.extraClassNames_;343};344345346/**347* Adds the given class name to the list of classes to be applied to the348* component's root element.349* @param {string} className Additional class name to be applied to the350* component's root element.351*/352goog.ui.Control.prototype.addClassName = function(className) {353'use strict';354if (className) {355if (this.extraClassNames_) {356if (!goog.array.contains(this.extraClassNames_, className)) {357this.extraClassNames_.push(className);358}359} else {360this.extraClassNames_ = [className];361}362this.renderer_.enableExtraClassName(this, className, true);363}364};365366367/**368* Removes the given class name from the list of classes to be applied to369* the component's root element.370* @param {string} className Class name to be removed from the component's root371* element.372*/373goog.ui.Control.prototype.removeClassName = function(className) {374'use strict';375if (className && this.extraClassNames_ &&376goog.array.remove(this.extraClassNames_, className)) {377if (this.extraClassNames_.length == 0) {378this.extraClassNames_ = null;379}380this.renderer_.enableExtraClassName(this, className, false);381}382};383384385/**386* Adds or removes the given class name to/from the list of classes to be387* applied to the component's root element.388* @param {string} className CSS class name to add or remove.389* @param {boolean} enable Whether to add or remove the class name.390*/391goog.ui.Control.prototype.enableClassName = function(className, enable) {392'use strict';393if (enable) {394this.addClassName(className);395} else {396this.removeClassName(className);397}398};399400401// Standard goog.ui.Component implementation.402403404/**405* Creates the control's DOM. Overrides {@link goog.ui.Component#createDom} by406* delegating DOM manipulation to the control's renderer.407* @override408*/409goog.ui.Control.prototype.createDom = function() {410'use strict';411var element = this.renderer_.createDom(this);412this.setElementInternal(element);413414// Initialize ARIA role.415this.renderer_.setAriaRole(element, this.getPreferredAriaRole());416417// Initialize text selection.418if (!this.isAllowTextSelection()) {419// The renderer is assumed to create selectable elements. Since making420// elements unselectable is expensive, only do it if needed (bug 1037090).421this.renderer_.setAllowTextSelection(element, false);422}423424// Initialize visibility.425if (!this.isVisible()) {426// The renderer is assumed to create visible elements. Since hiding427// elements can be expensive, only do it if needed (bug 1037105).428this.renderer_.setVisible(element, false);429}430};431432433/**434* Returns the control's preferred ARIA role. This can be used by a control to435* override the role that would be assigned by the renderer. This is useful in436* cases where a different ARIA role is appropriate for a control because of the437* context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a438* {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.439* @return {?goog.a11y.aria.Role} This control's preferred ARIA role or null if440* no preferred ARIA role is set.441*/442goog.ui.Control.prototype.getPreferredAriaRole = function() {443'use strict';444return this.preferredAriaRole_;445};446447448/**449* Sets the control's preferred ARIA role. This can be used to override the role450* that would be assigned by the renderer. This is useful in cases where a451* different ARIA role is appropriate for a control because of the452* context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a453* {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.454* @param {goog.a11y.aria.Role} role This control's preferred ARIA role.455*/456goog.ui.Control.prototype.setPreferredAriaRole = function(role) {457'use strict';458this.preferredAriaRole_ = role;459};460461462/**463* Gets the control's aria label.464* @return {?string} This control's aria label.465*/466goog.ui.Control.prototype.getAriaLabel = function() {467'use strict';468return this.ariaLabel_;469};470471472/**473* Sets the control's aria label. This can be used to assign aria label to the474* element after it is rendered.475* @param {string} label The string to set as the aria label for this control.476* No escaping is done on this value.477*/478goog.ui.Control.prototype.setAriaLabel = function(label) {479'use strict';480this.ariaLabel_ = label;481var element = this.getElement();482if (element) {483this.renderer_.setAriaLabel(element, label);484}485};486487488/**489* Returns the DOM element into which child components are to be rendered,490* or null if the control itself hasn't been rendered yet. Overrides491* {@link goog.ui.Component#getContentElement} by delegating to the renderer.492* @return {Element} Element to contain child elements (null if none).493* @override494*/495goog.ui.Control.prototype.getContentElement = function() {496'use strict';497// Delegate to renderer.498return this.renderer_.getContentElement(this.getElement());499};500501502/**503* Returns true if the given element can be decorated by this component.504* Overrides {@link goog.ui.Component#canDecorate}.505* @param {Element} element Element to decorate.506* @return {boolean} Whether the element can be decorated by this component.507* @override508*/509goog.ui.Control.prototype.canDecorate = function(element) {510'use strict';511// Controls support pluggable renderers; delegate to the renderer.512return this.renderer_.canDecorate(element);513};514515516/**517* Decorates the given element with this component. Overrides {@link518* goog.ui.Component#decorateInternal} by delegating DOM manipulation519* to the control's renderer.520* @param {Element} element Element to decorate.521* @protected522* @override523*/524goog.ui.Control.prototype.decorateInternal = function(element) {525'use strict';526element = this.renderer_.decorate(this, element);527this.setElementInternal(element);528529// Initialize ARIA role.530this.renderer_.setAriaRole(element, this.getPreferredAriaRole());531532// Initialize text selection.533if (!this.isAllowTextSelection()) {534// Decorated elements are assumed to be selectable. Since making elements535// unselectable is expensive, only do it if needed (bug 1037090).536this.renderer_.setAllowTextSelection(element, false);537}538539// Initialize visibility based on the decorated element's styling.540this.visible_ = element.style.display != 'none';541};542543544/**545* Configures the component after its DOM has been rendered, and sets up event546* handling. Overrides {@link goog.ui.Component#enterDocument}.547* @override548*/549goog.ui.Control.prototype.enterDocument = function() {550'use strict';551goog.ui.Control.superClass_.enterDocument.call(this);552553// Call the renderer's setAriaStates method to set element's aria attributes.554this.renderer_.setAriaStates(this, this.getElementStrict());555556// Call the renderer's initializeDom method to configure properties of the557// control's DOM that can only be done once it's in the document.558this.renderer_.initializeDom(this);559560// Initialize event handling if at least one state other than DISABLED is561// supported.562if (this.supportedStates_ & ~goog.ui.Component.State.DISABLED) {563// Initialize mouse event handling if the control is configured to handle564// its own mouse events. (Controls hosted in containers don't need to565// handle their own mouse events.)566if (this.isHandleMouseEvents()) {567this.enableMouseEventHandling_(true);568}569570// Initialize keyboard event handling if the control is focusable and has571// a key event target. (Controls hosted in containers typically aren't572// focusable, allowing their container to handle keyboard events for them.)573if (this.isSupportedState(goog.ui.Component.State.FOCUSED)) {574var keyTarget = this.getKeyEventTarget();575if (keyTarget) {576var keyHandler = this.getKeyHandler();577keyHandler.attach(keyTarget);578this.getHandler()579.listen(580keyHandler, goog.events.KeyHandler.EventType.KEY,581this.handleKeyEvent)582.listen(keyTarget, goog.events.EventType.FOCUS, this.handleFocus)583.listen(keyTarget, goog.events.EventType.BLUR, this.handleBlur);584}585}586}587};588589590/**591* Enables or disables mouse event handling on the control.592* @param {boolean} enable Whether to enable mouse event handling.593* @private594*/595goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) {596'use strict';597var MouseEventType = goog.ui.ComponentUtil.getMouseEventType(this);598599var handler = this.getHandler();600var element = this.getElement();601if (enable) {602handler.listen(element, MouseEventType.MOUSEDOWN, this.handleMouseDown)603.listen(604element, [MouseEventType.MOUSEUP, MouseEventType.MOUSECANCEL],605this.handleMouseUp)606.listen(element, goog.events.EventType.MOUSEOVER, this.handleMouseOver)607.listen(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);608if (this.pointerEventsEnabled()) {609// Prevent pointer events from capturing the target element so they behave610// more like mouse events.611handler.listen(612element, goog.events.EventType.GOTPOINTERCAPTURE,613this.preventPointerCapture_);614}615if (this.handleContextMenu != goog.functions.UNDEFINED) {616handler.listen(617element, goog.events.EventType.CONTEXTMENU, this.handleContextMenu);618}619if (goog.userAgent.IE && !this.ieMouseEventSequenceSimulator_) {620this.ieMouseEventSequenceSimulator_ =621new goog.ui.Control.IeMouseEventSequenceSimulator_(this);622this.registerDisposable(this.ieMouseEventSequenceSimulator_);623}624} else {625handler.unlisten(element, MouseEventType.MOUSEDOWN, this.handleMouseDown)626.unlisten(627element, [MouseEventType.MOUSEUP, MouseEventType.MOUSECANCEL],628this.handleMouseUp)629.unlisten(630element, goog.events.EventType.MOUSEOVER, this.handleMouseOver)631.unlisten(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);632if (this.pointerEventsEnabled()) {633handler.unlisten(634element, goog.events.EventType.GOTPOINTERCAPTURE,635this.preventPointerCapture_);636}637if (this.handleContextMenu != goog.functions.UNDEFINED) {638handler.unlisten(639element, goog.events.EventType.CONTEXTMENU, this.handleContextMenu);640}641if (goog.userAgent.IE) {642goog.dispose(this.ieMouseEventSequenceSimulator_);643this.ieMouseEventSequenceSimulator_ = null;644}645}646};647648649/**650* Cleans up the component before its DOM is removed from the document, and651* removes event handlers. Overrides {@link goog.ui.Component#exitDocument}652* by making sure that components that are removed from the document aren't653* focusable (i.e. have no tab index).654* @override655*/656goog.ui.Control.prototype.exitDocument = function() {657'use strict';658goog.ui.Control.superClass_.exitDocument.call(this);659if (this.keyHandler_) {660this.keyHandler_.detach();661}662if (this.isVisible() && this.isEnabled()) {663this.renderer_.setFocusable(this, false);664}665};666667668/** @override */669goog.ui.Control.prototype.disposeInternal = function() {670'use strict';671goog.ui.Control.superClass_.disposeInternal.call(this);672if (this.keyHandler_) {673this.keyHandler_.dispose();674delete this.keyHandler_;675}676delete this.renderer_;677this.content_ = null;678this.extraClassNames_ = null;679this.ieMouseEventSequenceSimulator_ = null;680};681682683// Component content management.684685686/**687* Returns the text caption or DOM structure displayed in the component.688* @return {goog.ui.ControlContent} Text caption or DOM structure689* comprising the component's contents.690*/691goog.ui.Control.prototype.getContent = function() {692'use strict';693return this.content_;694};695696697/**698* Sets the component's content to the given text caption, element, or array of699* nodes. (If the argument is an array of nodes, it must be an actual array,700* not an array-like object.)701* @param {goog.ui.ControlContent} content Text caption or DOM702* structure to set as the component's contents.703*/704goog.ui.Control.prototype.setContent = function(content) {705'use strict';706// Controls support pluggable renderers; delegate to the renderer.707this.renderer_.setContent(this.getElement(), content);708709// setContentInternal needs to be after the renderer, since the implementation710// may depend on the content being in the DOM.711this.setContentInternal(content);712};713714715/**716* Sets the component's content to the given text caption, element, or array717* of nodes. Unlike {@link #setContent}, doesn't modify the component's DOM.718* Called by renderers during element decoration.719*720* This should only be used by subclasses and its associated renderers.721*722* @param {goog.ui.ControlContent} content Text caption or DOM structure723* to set as the component's contents.724*/725goog.ui.Control.prototype.setContentInternal = function(content) {726'use strict';727this.content_ = content;728};729730731/**732* @return {string} Text caption of the control or empty string if none.733*/734goog.ui.Control.prototype.getCaption = function() {735'use strict';736var content = this.getContent();737if (!content) {738return '';739}740var caption = (typeof content === 'string') ?741content :742Array.isArray(content) ?743content.map(goog.dom.getRawTextContent).join('') :744goog.dom.getTextContent(/** @type {!Node} */ (content));745return goog.string.collapseBreakingSpaces(caption);746};747748749/**750* Sets the text caption of the component.751* @param {string} caption Text caption of the component.752*/753goog.ui.Control.prototype.setCaption = function(caption) {754'use strict';755this.setContent(caption);756};757758759// Component state management.760761762/** @override */763goog.ui.Control.prototype.setRightToLeft = function(rightToLeft) {764'use strict';765// The superclass implementation ensures the control isn't in the document.766goog.ui.Control.superClass_.setRightToLeft.call(this, rightToLeft);767768var element = this.getElement();769if (element) {770this.renderer_.setRightToLeft(element, rightToLeft);771}772};773774775/**776* Returns true if the control allows text selection within its DOM, false777* otherwise. Controls that disallow text selection have the appropriate778* unselectable styling applied to their elements. Note that controls hosted779* in containers will report that they allow text selection even if their780* container disallows text selection.781* @return {boolean} Whether the control allows text selection.782*/783goog.ui.Control.prototype.isAllowTextSelection = function() {784'use strict';785return this.allowTextSelection_;786};787788789/**790* Allows or disallows text selection within the control's DOM.791* @param {boolean} allow Whether the control should allow text selection.792*/793goog.ui.Control.prototype.setAllowTextSelection = function(allow) {794'use strict';795this.allowTextSelection_ = allow;796797var element = this.getElement();798if (element) {799this.renderer_.setAllowTextSelection(element, allow);800}801};802803804/**805* Returns true if the component's visibility is set to visible, false if806* it is set to hidden. A component that is set to hidden is guaranteed807* to be hidden from the user, but the reverse isn't necessarily true.808* A component may be set to visible but can otherwise be obscured by another809* element, rendered off-screen, or hidden using direct CSS manipulation.810* @return {boolean} Whether the component is visible.811*/812goog.ui.Control.prototype.isVisible = function() {813'use strict';814return this.visible_;815};816817818/**819* Shows or hides the component. Does nothing if the component already has820* the requested visibility. Otherwise, dispatches a SHOW or HIDE event as821* appropriate, giving listeners a chance to prevent the visibility change.822* When showing a component that is both enabled and focusable, ensures that823* its key target has a tab index. When hiding a component that is enabled824* and focusable, blurs its key target and removes its tab index.825* @param {boolean} visible Whether to show or hide the component.826* @param {boolean=} opt_force If true, doesn't check whether the component827* already has the requested visibility, and doesn't dispatch any events.828* @return {boolean} Whether the visibility was changed.829*/830goog.ui.Control.prototype.setVisible = function(visible, opt_force) {831'use strict';832if (opt_force ||833(this.visible_ != visible &&834this.dispatchEvent(835visible ? goog.ui.Component.EventType.SHOW :836goog.ui.Component.EventType.HIDE))) {837var element = this.getElement();838if (element) {839this.renderer_.setVisible(element, visible);840}841if (this.isEnabled()) {842this.renderer_.setFocusable(this, visible);843}844this.visible_ = visible;845return true;846}847return false;848};849850851/**852* Returns true if the component is enabled, false otherwise.853* @return {boolean} Whether the component is enabled.854*/855goog.ui.Control.prototype.isEnabled = function() {856'use strict';857return !this.hasState(goog.ui.Component.State.DISABLED);858};859860861/**862* Returns true if the control has a parent that is itself disabled, false863* otherwise.864* @return {boolean} Whether the component is hosted in a disabled container.865* @private866*/867goog.ui.Control.prototype.isParentDisabled_ = function() {868'use strict';869var parent = this.getParent();870return !!parent && typeof parent.isEnabled == 'function' &&871!parent.isEnabled();872};873874875/**876* Enables or disables the component. Does nothing if this state transition877* is disallowed. If the component is both visible and focusable, updates its878* focused state and tab index as needed. If the component is being disabled,879* ensures that it is also deactivated and un-highlighted first. Note that the880* component's enabled/disabled state is "locked" as long as it is hosted in a881* {@link goog.ui.Container} that is itself disabled; this is to prevent clients882* from accidentally re-enabling a control that is in a disabled container.883* @param {boolean} enable Whether to enable or disable the component.884* @see #isTransitionAllowed885*/886goog.ui.Control.prototype.setEnabled = function(enable) {887'use strict';888if (!this.isParentDisabled_() &&889this.isTransitionAllowed(goog.ui.Component.State.DISABLED, !enable)) {890if (!enable) {891this.setActive(false);892this.setHighlighted(false);893}894if (this.isVisible()) {895this.renderer_.setFocusable(this, enable);896}897this.setState(goog.ui.Component.State.DISABLED, !enable, true);898}899};900901902/**903* Returns true if the component is currently highlighted, false otherwise.904* @return {boolean} Whether the component is highlighted.905*/906goog.ui.Control.prototype.isHighlighted = function() {907'use strict';908return this.hasState(goog.ui.Component.State.HOVER);909};910911912/**913* Highlights or unhighlights the component. Does nothing if this state914* transition is disallowed.915* @param {boolean} highlight Whether to highlight or unhighlight the component.916* @see #isTransitionAllowed917*/918goog.ui.Control.prototype.setHighlighted = function(highlight) {919'use strict';920if (this.isTransitionAllowed(goog.ui.Component.State.HOVER, highlight)) {921this.setState(goog.ui.Component.State.HOVER, highlight);922}923};924925926/**927* Returns true if the component is active (pressed), false otherwise.928* @return {boolean} Whether the component is active.929*/930goog.ui.Control.prototype.isActive = function() {931'use strict';932return this.hasState(goog.ui.Component.State.ACTIVE);933};934935936/**937* Activates or deactivates the component. Does nothing if this state938* transition is disallowed.939* @param {boolean} active Whether to activate or deactivate the component.940* @see #isTransitionAllowed941*/942goog.ui.Control.prototype.setActive = function(active) {943'use strict';944if (this.isTransitionAllowed(goog.ui.Component.State.ACTIVE, active)) {945this.setState(goog.ui.Component.State.ACTIVE, active);946}947};948949950/**951* Returns true if the component is selected, false otherwise.952* @return {boolean} Whether the component is selected.953*/954goog.ui.Control.prototype.isSelected = function() {955'use strict';956return this.hasState(goog.ui.Component.State.SELECTED);957};958959960/**961* Selects or unselects the component. Does nothing if this state transition962* is disallowed.963* @param {boolean} select Whether to select or unselect the component.964* @see #isTransitionAllowed965*/966goog.ui.Control.prototype.setSelected = function(select) {967'use strict';968if (this.isTransitionAllowed(goog.ui.Component.State.SELECTED, select)) {969this.setState(goog.ui.Component.State.SELECTED, select);970}971};972973974/**975* Returns true if the component is checked, false otherwise.976* @return {boolean} Whether the component is checked.977*/978goog.ui.Control.prototype.isChecked = function() {979'use strict';980return this.hasState(goog.ui.Component.State.CHECKED);981};982983984/**985* Checks or unchecks the component. Does nothing if this state transition986* is disallowed.987* @param {boolean} check Whether to check or uncheck the component.988* @see #isTransitionAllowed989*/990goog.ui.Control.prototype.setChecked = function(check) {991'use strict';992if (this.isTransitionAllowed(goog.ui.Component.State.CHECKED, check)) {993this.setState(goog.ui.Component.State.CHECKED, check);994}995};996997998/**999* Returns true if the component is styled to indicate that it has keyboard1000* focus, false otherwise. Note that `isFocused()` returning true1001* doesn't guarantee that the component's key event target has keyboard focus,1002* only that it is styled as such.1003* @return {boolean} Whether the component is styled to indicate as having1004* keyboard focus.1005*/1006goog.ui.Control.prototype.isFocused = function() {1007'use strict';1008return this.hasState(goog.ui.Component.State.FOCUSED);1009};101010111012/**1013* Applies or removes styling indicating that the component has keyboard focus.1014* Note that unlike the other "set" methods, this method is called as a result1015* of the component's element having received or lost keyboard focus, not the1016* other way around, so calling `setFocused(true)` doesn't guarantee that1017* the component's key event target has keyboard focus, only that it is styled1018* as such.1019* @param {boolean} focused Whether to apply or remove styling to indicate that1020* the component's element has keyboard focus.1021*/1022goog.ui.Control.prototype.setFocused = function(focused) {1023'use strict';1024if (this.isTransitionAllowed(goog.ui.Component.State.FOCUSED, focused)) {1025this.setState(goog.ui.Component.State.FOCUSED, focused);1026}1027};102810291030/**1031* Returns true if the component is open (expanded), false otherwise.1032* @return {boolean} Whether the component is open.1033*/1034goog.ui.Control.prototype.isOpen = function() {1035'use strict';1036return this.hasState(goog.ui.Component.State.OPENED);1037};103810391040/**1041* Opens (expands) or closes (collapses) the component. Does nothing if this1042* state transition is disallowed.1043* @param {boolean} open Whether to open or close the component.1044* @see #isTransitionAllowed1045*/1046goog.ui.Control.prototype.setOpen = function(open) {1047'use strict';1048if (this.isTransitionAllowed(goog.ui.Component.State.OPENED, open)) {1049this.setState(goog.ui.Component.State.OPENED, open);1050}1051};105210531054/**1055* Returns the component's state as a bit mask of {@link1056* goog.ui.Component.State}s.1057* @return {number} Bit mask representing component state.1058*/1059goog.ui.Control.prototype.getState = function() {1060'use strict';1061return this.state_;1062};106310641065/**1066* Returns true if the component is in the specified state, false otherwise.1067* @param {goog.ui.Component.State} state State to check.1068* @return {boolean} Whether the component is in the given state.1069*/1070goog.ui.Control.prototype.hasState = function(state) {1071'use strict';1072return !!(this.state_ & state);1073};107410751076/**1077* Sets or clears the given state on the component, and updates its styling1078* accordingly. Does nothing if the component is already in the correct state1079* or if it doesn't support the specified state. Doesn't dispatch any state1080* transition events; use advisedly.1081* @param {goog.ui.Component.State} state State to set or clear.1082* @param {boolean} enable Whether to set or clear the state (if supported).1083* @param {boolean=} opt_calledFrom Prevents looping with setEnabled.1084*/1085goog.ui.Control.prototype.setState = function(state, enable, opt_calledFrom) {1086'use strict';1087if (!opt_calledFrom && state == goog.ui.Component.State.DISABLED) {1088this.setEnabled(!enable);1089return;1090}1091if (this.isSupportedState(state) && enable != this.hasState(state)) {1092// Delegate actual styling to the renderer, since it is DOM-specific.1093this.renderer_.setState(this, state, enable);1094this.state_ = enable ? this.state_ | state : this.state_ & ~state;1095}1096};109710981099/**1100* Sets the component's state to the state represented by a bit mask of1101* {@link goog.ui.Component.State}s. Unlike {@link #setState}, doesn't1102* update the component's styling, and doesn't reject unsupported states.1103* Called by renderers during element decoration. Considered protected;1104* should only be used within this package and by subclasses.1105*1106* This should only be used by subclasses and its associated renderers.1107*1108* @param {number} state Bit mask representing component state.1109*/1110goog.ui.Control.prototype.setStateInternal = function(state) {1111'use strict';1112this.state_ = state;1113};111411151116/**1117* Returns true if the component supports the specified state, false otherwise.1118* @param {goog.ui.Component.State} state State to check.1119* @return {boolean} Whether the component supports the given state.1120*/1121goog.ui.Control.prototype.isSupportedState = function(state) {1122'use strict';1123return !!(this.supportedStates_ & state);1124};112511261127/**1128* Enables or disables support for the given state. Disabling support1129* for a state while the component is in that state is an error.1130* @param {goog.ui.Component.State} state State to support or de-support.1131* @param {boolean} support Whether the component should support the state.1132* @throws {Error} If disabling support for a state the control is currently in.1133*/1134goog.ui.Control.prototype.setSupportedState = function(state, support) {1135'use strict';1136if (this.isInDocument() && this.hasState(state) && !support) {1137// Since we hook up event handlers in enterDocument(), this is an error.1138throw new Error(goog.ui.Component.Error.ALREADY_RENDERED);1139}11401141if (!support && this.hasState(state)) {1142// We are removing support for a state that the component is currently in.1143this.setState(state, false);1144}11451146this.supportedStates_ =1147support ? this.supportedStates_ | state : this.supportedStates_ & ~state;1148};114911501151/**1152* Returns true if the component provides default event handling for the state,1153* false otherwise.1154* @param {goog.ui.Component.State} state State to check.1155* @return {boolean} Whether the component provides default event handling for1156* the state.1157*/1158goog.ui.Control.prototype.isAutoState = function(state) {1159'use strict';1160return !!(this.autoStates_ & state) && this.isSupportedState(state);1161};116211631164/**1165* Enables or disables automatic event handling for the given state(s).1166* @param {number} states Bit mask of {@link goog.ui.Component.State}s for which1167* default event handling is to be enabled or disabled.1168* @param {boolean} enable Whether the component should provide default event1169* handling for the state(s).1170*/1171goog.ui.Control.prototype.setAutoStates = function(states, enable) {1172'use strict';1173this.autoStates_ =1174enable ? this.autoStates_ | states : this.autoStates_ & ~states;1175};117611771178/**1179* Returns true if the component is set to dispatch transition events for the1180* given state, false otherwise.1181* @param {goog.ui.Component.State} state State to check.1182* @return {boolean} Whether the component dispatches transition events for1183* the state.1184*/1185goog.ui.Control.prototype.isDispatchTransitionEvents = function(state) {1186'use strict';1187return !!(this.statesWithTransitionEvents_ & state) &&1188this.isSupportedState(state);1189};119011911192/**1193* Enables or disables transition events for the given state(s). Controls1194* handle state transitions internally by default, and only dispatch state1195* transition events if explicitly requested to do so by calling this method.1196* @param {number} states Bit mask of {@link goog.ui.Component.State}s for1197* which transition events should be enabled or disabled.1198* @param {boolean} enable Whether transition events should be enabled.1199*/1200goog.ui.Control.prototype.setDispatchTransitionEvents = function(1201states, enable) {1202'use strict';1203this.statesWithTransitionEvents_ = enable ?1204this.statesWithTransitionEvents_ | states :1205this.statesWithTransitionEvents_ & ~states;1206};120712081209/**1210* Returns true if the transition into or out of the given state is allowed to1211* proceed, false otherwise. A state transition is allowed under the following1212* conditions:1213* <ul>1214* <li>the component supports the state,1215* <li>the component isn't already in the target state,1216* <li>either the component is configured not to dispatch events for this1217* state transition, or a transition event was dispatched and wasn't1218* canceled by any event listener, and1219* <li>the component hasn't been disposed of1220* </ul>1221* Considered protected; should only be used within this package and by1222* subclasses.1223* @param {goog.ui.Component.State} state State to/from which the control is1224* transitioning.1225* @param {boolean} enable Whether the control is entering or leaving the state.1226* @return {boolean} Whether the state transition is allowed to proceed.1227* @protected1228*/1229goog.ui.Control.prototype.isTransitionAllowed = function(state, enable) {1230'use strict';1231return this.isSupportedState(state) && this.hasState(state) != enable &&1232(!(this.statesWithTransitionEvents_ & state) ||1233this.dispatchEvent(1234goog.ui.Component.getStateTransitionEvent(state, enable))) &&1235!this.isDisposed();1236};123712381239// Default event handlers, to be overridden in subclasses.124012411242/**1243* Handles mouseover events. Dispatches an ENTER event; if the event isn't1244* canceled, the component is enabled, and it supports auto-highlighting,1245* highlights the component. Considered protected; should only be used1246* within this package and by subclasses.1247* @param {goog.events.BrowserEvent} e Mouse event to handle.1248*/1249goog.ui.Control.prototype.handleMouseOver = function(e) {1250'use strict';1251// Ignore mouse moves between descendants.1252if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&1253this.dispatchEvent(goog.ui.Component.EventType.ENTER) &&1254this.isEnabled() && this.isAutoState(goog.ui.Component.State.HOVER)) {1255this.setHighlighted(true);1256}1257};125812591260/**1261* Handles mouseout events. Dispatches a LEAVE event; if the event isn't1262* canceled, and the component supports auto-highlighting, deactivates and1263* un-highlights the component. Considered protected; should only be used1264* within this package and by subclasses.1265* @param {goog.events.BrowserEvent} e Mouse event to handle.1266*/1267goog.ui.Control.prototype.handleMouseOut = function(e) {1268'use strict';1269if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&1270this.dispatchEvent(goog.ui.Component.EventType.LEAVE)) {1271if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {1272// Deactivate on mouseout; otherwise we lose track of the mouse button.1273this.setActive(false);1274}1275if (this.isAutoState(goog.ui.Component.State.HOVER)) {1276this.setHighlighted(false);1277}1278}1279};128012811282/**1283* @param {!goog.events.BrowserEvent} e Event to handle.1284* @private1285*/1286goog.ui.Control.prototype.preventPointerCapture_ = function(e) {1287'use strict';1288var elem = /** @type {!Element} */ (e.target);1289if (!!elem.releasePointerCapture) {1290elem.releasePointerCapture(e.pointerId);1291}1292};129312941295/**1296* Handles contextmenu events.1297* @param {goog.events.BrowserEvent} e Event to handle.1298*/1299goog.ui.Control.prototype.handleContextMenu = goog.functions.UNDEFINED;130013011302/**1303* Checks if a mouse event (mouseover or mouseout) occurred below an element.1304* @param {goog.events.BrowserEvent} e Mouse event (should be mouseover or1305* mouseout).1306* @param {Element} elem The ancestor element.1307* @return {boolean} Whether the event has a relatedTarget (the element the1308* mouse is coming from) and it's a descendant of elem.1309* @private1310*/1311goog.ui.Control.isMouseEventWithinElement_ = function(e, elem) {1312'use strict';1313// If relatedTarget is null, it means there was no previous element (e.g.1314// the mouse moved out of the window). Assume this means that the mouse1315// event was not within the element.1316return !!e.relatedTarget && goog.dom.contains(elem, e.relatedTarget);1317};131813191320/**1321* Handles mousedown events. If the component is enabled, highlights and1322* activates it. If the component isn't configured for keyboard access,1323* prevents it from receiving keyboard focus. Considered protected; should1324* only be used within this package and by subclasses.1325* @param {goog.events.Event} e Mouse event to handle.1326* @suppress {strictMissingProperties} Added to tighten compiler checks1327*/1328goog.ui.Control.prototype.handleMouseDown = function(e) {1329'use strict';1330if (this.isEnabled()) {1331// Highlight enabled control on mousedown, regardless of the mouse button.1332if (this.isAutoState(goog.ui.Component.State.HOVER)) {1333this.setHighlighted(true);1334}13351336// For the left button only, activate the control, and focus its key event1337// target (if supported).1338if (e.isMouseActionButton()) {1339if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {1340this.setActive(true);1341}1342if (this.renderer_ && this.renderer_.isFocusable(this)) {1343this.getKeyEventTarget().focus();1344}1345}1346}13471348// Cancel the default action unless the control allows text selection.1349if (!this.isAllowTextSelection() && e.isMouseActionButton()) {1350e.preventDefault();1351}1352};135313541355/**1356* Handles mouseup events. If the component is enabled, highlights it. If1357* the component has previously been activated, performs its associated action1358* by calling {@link performActionInternal}, then deactivates it. Considered1359* protected; should only be used within this package and by subclasses.1360* @param {goog.events.Event} e Mouse event to handle.1361*/1362goog.ui.Control.prototype.handleMouseUp = function(e) {1363'use strict';1364if (this.isEnabled()) {1365if (this.isAutoState(goog.ui.Component.State.HOVER)) {1366this.setHighlighted(true);1367}1368if (this.isActive() && this.performActionInternal(e) &&1369this.isAutoState(goog.ui.Component.State.ACTIVE)) {1370this.setActive(false);1371}1372}1373};137413751376/**1377* Handles dblclick events. Should only be registered if the user agent is1378* IE. If the component is enabled, performs its associated action by calling1379* {@link performActionInternal}. This is used to allow more performant1380* buttons in IE. In IE, no mousedown event is fired when that mousedown will1381* trigger a dblclick event. Because of this, a user clicking quickly will1382* only cause ACTION events to fire on every other click. This is a workaround1383* to generate ACTION events for every click. Unfortunately, this workaround1384* won't ever trigger the ACTIVE state. This is roughly the same behaviour as1385* if this were a 'button' element with a listener on mouseup. Considered1386* protected; should only be used within this package and by subclasses.1387* @param {goog.events.Event} e Mouse event to handle.1388*/1389goog.ui.Control.prototype.handleDblClick = function(e) {1390'use strict';1391if (this.isEnabled()) {1392this.performActionInternal(e);1393}1394};139513961397/**1398* Performs the appropriate action when the control is activated by the user.1399* The default implementation first updates the checked and selected state of1400* controls that support them, then dispatches an ACTION event. Considered1401* protected; should only be used within this package and by subclasses.1402* @param {goog.events.Event} e Event that triggered the action.1403* @return {boolean} Whether the action is allowed to proceed.1404* @protected1405*/1406goog.ui.Control.prototype.performActionInternal = function(e) {1407'use strict';1408if (this.isAutoState(goog.ui.Component.State.CHECKED)) {1409this.setChecked(!this.isChecked());1410}1411if (this.isAutoState(goog.ui.Component.State.SELECTED)) {1412this.setSelected(true);1413}1414if (this.isAutoState(goog.ui.Component.State.OPENED)) {1415this.setOpen(!this.isOpen());1416}14171418var actionEvent =1419new goog.events.Event(goog.ui.Component.EventType.ACTION, this);1420if (e) {1421/** @suppress {strictMissingProperties} Added to tighten compiler checks */1422actionEvent.altKey = e.altKey;1423/** @suppress {strictMissingProperties} Added to tighten compiler checks */1424actionEvent.ctrlKey = e.ctrlKey;1425/** @suppress {strictMissingProperties} Added to tighten compiler checks */1426actionEvent.metaKey = e.metaKey;1427/** @suppress {strictMissingProperties} Added to tighten compiler checks */1428actionEvent.shiftKey = e.shiftKey;1429/** @suppress {strictMissingProperties} Added to tighten compiler checks */1430actionEvent.platformModifierKey = e.platformModifierKey;1431}1432return this.dispatchEvent(actionEvent);1433};143414351436/**1437* Handles focus events on the component's key event target element. If the1438* component is focusable, updates its state and styling to indicate that it1439* now has keyboard focus. Considered protected; should only be used within1440* this package and by subclasses. <b>Warning:</b> IE dispatches focus and1441* blur events asynchronously!1442* @param {goog.events.Event} e Focus event to handle.1443*/1444goog.ui.Control.prototype.handleFocus = function(e) {1445'use strict';1446if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {1447this.setFocused(true);1448}1449};145014511452/**1453* Handles blur events on the component's key event target element. Always1454* deactivates the component. In addition, if the component is focusable,1455* updates its state and styling to indicate that it no longer has keyboard1456* focus. Considered protected; should only be used within this package and1457* by subclasses. <b>Warning:</b> IE dispatches focus and blur events1458* asynchronously!1459* @param {goog.events.Event} e Blur event to handle.1460*/1461goog.ui.Control.prototype.handleBlur = function(e) {1462'use strict';1463if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {1464this.setActive(false);1465}1466if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {1467this.setFocused(false);1468}1469};147014711472/**1473* Attempts to handle a keyboard event, if the component is enabled and visible,1474* by calling {@link handleKeyEventInternal}. Considered protected; should only1475* be used within this package and by subclasses.1476* @param {goog.events.KeyEvent} e Key event to handle.1477* @return {boolean} Whether the key event was handled.1478*/1479goog.ui.Control.prototype.handleKeyEvent = function(e) {1480'use strict';1481if (this.isVisible() && this.isEnabled() && this.handleKeyEventInternal(e)) {1482e.preventDefault();1483e.stopPropagation();1484return true;1485}1486return false;1487};148814891490/**1491* Attempts to handle a keyboard event; returns true if the event was handled,1492* false otherwise. Considered protected; should only be used within this1493* package and by subclasses.1494* @param {goog.events.KeyEvent} e Key event to handle.1495* @return {boolean} Whether the key event was handled.1496* @protected1497*/1498goog.ui.Control.prototype.handleKeyEventInternal = function(e) {1499'use strict';1500return e.keyCode == goog.events.KeyCodes.ENTER &&1501this.performActionInternal(e);1502};150315041505// Register the default renderer for goog.ui.Controls.1506goog.ui.registry.setDefaultRenderer(goog.ui.Control, goog.ui.ControlRenderer);150715081509// Register a decorator factory function for goog.ui.Controls.1510goog.ui.registry.setDecoratorByClassName(1511goog.ui.ControlRenderer.CSS_CLASS, function() {1512'use strict';1513return new goog.ui.Control(null);1514});1515151615171518/**1519* A singleton that helps goog.ui.Control instances play well with screen1520* readers. It necessitated by shortcomings in IE, and need not be1521* instantiated in any other browser.1522*1523* In most cases, a click on a goog.ui.Control results in a sequence of events:1524* MOUSEDOWN, MOUSEUP and CLICK. UI controls rely on this sequence since most1525* behavior is trigged by MOUSEDOWN and MOUSEUP. But when IE is used with some1526* traditional screen readers (JAWS, NVDA and perhaps others), IE only sends1527* the CLICK event, resulting in the control being unresponsive. This class1528* monitors the sequence of these events, and if it detects a CLICK event not1529* not preceded by a MOUSEUP event, directly calls the control's event handlers1530* for MOUSEDOWN, then MOUSEUP. While the resulting sequence is different from1531* the norm (the CLICK comes first instead of last), testing thus far shows1532* the resulting behavior to be correct.1533*1534* See http://goo.gl/qvQR4C for more details.1535*1536* @param {!goog.ui.Control} control1537* @constructor1538* @extends {goog.Disposable}1539* @private1540*/1541goog.ui.Control.IeMouseEventSequenceSimulator_ = function(control) {1542'use strict';1543goog.ui.Control.IeMouseEventSequenceSimulator_.base(this, 'constructor');15441545/** @private {goog.ui.Control}*/1546this.control_ = control;15471548/** @private {boolean} */1549this.clickExpected_ = false;15501551/** @private @const {!goog.events.EventHandler<1552* !goog.ui.Control.IeMouseEventSequenceSimulator_>}1553*/1554this.handler_ = new goog.events.EventHandler(this);1555this.registerDisposable(this.handler_);15561557var element = this.control_.getElementStrict();1558var MouseEventType = goog.ui.ComponentUtil.getMouseEventType(control);15591560this.handler_.listen(element, MouseEventType.MOUSEDOWN, this.handleMouseDown_)1561.listen(element, MouseEventType.MOUSEUP, this.handleMouseUp_)1562.listen(element, goog.events.EventType.CLICK, this.handleClick_);1563};1564goog.inherits(goog.ui.Control.IeMouseEventSequenceSimulator_, goog.Disposable);156515661567/**1568* Whether this browser supports synthetic MouseEvents.1569*1570* See https://msdn.microsoft.com/library/dn905219(v=vs.85).aspx for details.1571*1572* @private {boolean}1573* @const1574*/1575goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_ =1576!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9);157715781579/** @private */1580goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseDown_ =1581function() {1582'use strict';1583this.clickExpected_ = false;1584};158515861587/** @private */1588goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseUp_ =1589function() {1590'use strict';1591this.clickExpected_ = true;1592};159315941595/**1596* @param {!MouseEvent} e1597* @param {goog.events.EventType} typeArg1598* @return {!MouseEvent}1599* @private1600*/1601goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_ = function(1602e, typeArg) {1603'use strict';1604'use strict';16051606if (!goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_) {1607// IE < 9 does not support synthetic mouse events. Therefore, reuse the1608// existing MouseEvent by overwriting the read only button and type1609// properties. As IE < 9 does not support ES5 strict mode this will not1610// generate an exception even when the script specifies "use strict".1611e.button = goog.events.BrowserEvent.MouseButton.LEFT;1612e.type = typeArg;1613return e;1614}16151616var event = /** @type {!MouseEvent} */ (document.createEvent('MouseEvents'));1617event.initMouseEvent(1618typeArg, e.bubbles, e.cancelable,1619e.view || null, // IE9 errors if view is undefined1620e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey,1621e.shiftKey, e.metaKey, goog.events.BrowserEvent.MouseButton.LEFT,1622e.relatedTarget || null); // IE9 errors if relatedTarget is undefined1623return event;1624};162516261627/**1628* @param {!goog.events.Event} e1629* @private1630*/1631goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleClick_ =1632function(e) {1633'use strict';1634if (this.clickExpected_) {1635// This is the end of a normal click sequence: mouse-down, mouse-up, click.1636// Assume appropriate actions have already been performed.1637this.clickExpected_ = false;1638return;1639}16401641// For click events not part of a normal sequence, similate the mouse-down and1642// mouse-up events by creating synthetic events for each and directly invoke1643// the corresponding event listeners in order.16441645var browserEvent = /** @type {goog.events.BrowserEvent} */ (e);16461647var event = /** @type {!MouseEvent} */ (browserEvent.getBrowserEvent());1648var origEventButton = event.button;1649var origEventType = event.type;16501651var down = goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_(1652event, goog.events.EventType.MOUSEDOWN);1653this.control_.handleMouseDown(1654new goog.events.BrowserEvent(down, browserEvent.currentTarget));16551656var up = goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_(1657event, goog.events.EventType.MOUSEUP);1658this.control_.handleMouseUp(1659new goog.events.BrowserEvent(up, browserEvent.currentTarget));16601661if (goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_) {1662// This browser supports synthetic events. Avoid resetting the read only1663// properties (type, button) as they were not overwritten and writing them1664// results in an exception when running in ES5 strict mode.1665return;1666}16671668// Restore original values for click handlers that have not yet been invoked.1669event.button = origEventButton;1670event.type = origEventType;1671};167216731674/** @override */1675goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.disposeInternal =1676function() {1677'use strict';1678this.control_ = null;1679goog.ui.Control.IeMouseEventSequenceSimulator_.base(this, 'disposeInternal');1680};168116821683