Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / third_party / google_input_tools / third_party / closure_library / closure / goog / ui / control.js
1 // Copyright 2007 The Closure Library Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS-IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 /**
16  * @fileoverview Base class for UI controls such as buttons, menus, menu items,
17  * toolbar buttons, etc.  The implementation is based on a generalized version
18  * of {@link goog.ui.MenuItem}.
19  * TODO(attila):  If the renderer framework works well, pull it into Component.
20  *
21  * @author attila@google.com (Attila Bodis)
22  * @see ../demos/control.html
23  * @see http://code.google.com/p/closure-library/wiki/IntroToControls
24  */
25
26 goog.provide('goog.ui.Control');
27
28 goog.require('goog.array');
29 goog.require('goog.dom');
30 goog.require('goog.events.Event');
31 goog.require('goog.events.EventType');
32 goog.require('goog.events.KeyCodes');
33 goog.require('goog.events.KeyHandler');
34 goog.require('goog.string');
35 goog.require('goog.ui.Component');
36 /** @suppress {extraRequire} */
37 goog.require('goog.ui.ControlContent');
38 goog.require('goog.ui.ControlRenderer');
39 goog.require('goog.ui.decorate');
40 goog.require('goog.ui.registry');
41 goog.require('goog.userAgent');
42
43
44
45 /**
46  * Base class for UI controls.  Extends {@link goog.ui.Component} by adding
47  * the following:
48  *  <ul>
49  *    <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling,
50  *    <li>a pluggable <em>renderer</em> framework, to simplify the creation of
51  *        simple controls without the need to subclass this class,
52  *    <li>the notion of component <em>content</em>, like a text caption or DOM
53  *        structure displayed in the component (e.g. a button label),
54  *    <li>getter and setter for component content, as well as a getter and
55  *        setter specifically for caption text (for convenience),
56  *    <li>support for hiding/showing the component,
57       <li>fine-grained control over supported states and state transition
58           events, and
59  *    <li>default mouse and keyboard event handling.
60  *  </ul>
61  * This class has sufficient built-in functionality for most simple UI controls.
62  * All controls dispatch SHOW, HIDE, ENTER, LEAVE, and ACTION events on show,
63  * hide, mouseover, mouseout, and user action, respectively.  Additional states
64  * are also supported.  See closure/demos/control.html
65  * for example usage.
66  * @param {goog.ui.ControlContent=} opt_content Text caption or DOM structure
67  *     to display as the content of the control (if any).
68  * @param {goog.ui.ControlRenderer=} opt_renderer Renderer used to render or
69  *     decorate the component; defaults to {@link goog.ui.ControlRenderer}.
70  * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
71  *     document interaction.
72  * @constructor
73  * @extends {goog.ui.Component}
74  */
75 goog.ui.Control = function(opt_content, opt_renderer, opt_domHelper) {
76   goog.ui.Component.call(this, opt_domHelper);
77   this.renderer_ = opt_renderer ||
78       goog.ui.registry.getDefaultRenderer(this.constructor);
79   this.setContentInternal(goog.isDef(opt_content) ? opt_content : null);
80 };
81 goog.inherits(goog.ui.Control, goog.ui.Component);
82 goog.tagUnsealableClass(goog.ui.Control);
83
84
85 // Renderer registry.
86 // TODO(attila): Refactor existing usages inside Google in a follow-up CL.
87
88
89 /**
90  * Maps a CSS class name to a function that returns a new instance of
91  * {@link goog.ui.Control} or a subclass thereof, suitable to decorate
92  * an element that has the specified CSS class.  UI components that extend
93  * {@link goog.ui.Control} and want {@link goog.ui.Container}s to be able
94  * to discover and decorate elements using them should register a factory
95  * function via this API.
96  * @param {string} className CSS class name.
97  * @param {Function} decoratorFunction Function that takes no arguments and
98  *     returns a new instance of a control to decorate an element with the
99  *     given class.
100  * @deprecated Use {@link goog.ui.registry.setDecoratorByClassName} instead.
101  */
102 goog.ui.Control.registerDecorator = goog.ui.registry.setDecoratorByClassName;
103
104
105 /**
106  * Takes an element and returns a new instance of {@link goog.ui.Control}
107  * or a subclass, suitable to decorate it (based on the element's CSS class).
108  * @param {Element} element Element to decorate.
109  * @return {goog.ui.Control?} New control instance to decorate the element
110  *     (null if none).
111  * @deprecated Use {@link goog.ui.registry.getDecorator} instead.
112  */
113 goog.ui.Control.getDecorator =
114     /** @type {function(Element): goog.ui.Control} */ (
115         goog.ui.registry.getDecorator);
116
117
118 /**
119  * Takes an element, and decorates it with a {@link goog.ui.Control} instance
120  * if a suitable decorator is found.
121  * @param {Element} element Element to decorate.
122  * @return {goog.ui.Control?} New control instance that decorates the element
123  *     (null if none).
124  * @deprecated Use {@link goog.ui.decorate} instead.
125  */
126 goog.ui.Control.decorate = /** @type {function(Element): goog.ui.Control} */ (
127     goog.ui.decorate);
128
129
130 /**
131  * Renderer associated with the component.
132  * @type {goog.ui.ControlRenderer|undefined}
133  * @private
134  */
135 goog.ui.Control.prototype.renderer_;
136
137
138 /**
139  * Text caption or DOM structure displayed in the component.
140  * @type {goog.ui.ControlContent}
141  * @private
142  */
143 goog.ui.Control.prototype.content_ = null;
144
145
146 /**
147  * Current component state; a bit mask of {@link goog.ui.Component.State}s.
148  * @type {number}
149  * @private
150  */
151 goog.ui.Control.prototype.state_ = 0x00;
152
153
154 /**
155  * A bit mask of {@link goog.ui.Component.State}s this component supports.
156  * @type {number}
157  * @private
158  */
159 goog.ui.Control.prototype.supportedStates_ =
160     goog.ui.Component.State.DISABLED |
161     goog.ui.Component.State.HOVER |
162     goog.ui.Component.State.ACTIVE |
163     goog.ui.Component.State.FOCUSED;
164
165
166 /**
167  * A bit mask of {@link goog.ui.Component.State}s for which this component
168  * provides default event handling.  For example, a component that handles
169  * the HOVER state automatically will highlight itself on mouseover, whereas
170  * a component that doesn't handle HOVER automatically will only dispatch
171  * ENTER and LEAVE events but not call {@link setHighlighted} on itself.
172  * By default, components provide default event handling for all states.
173  * Controls hosted in containers (e.g. menu items in a menu, or buttons in a
174  * toolbar) will typically want to have their container manage their highlight
175  * state.  Selectable controls managed by a selection model will also typically
176  * want their selection state to be managed by the model.
177  * @type {number}
178  * @private
179  */
180 goog.ui.Control.prototype.autoStates_ = goog.ui.Component.State.ALL;
181
182
183 /**
184  * A bit mask of {@link goog.ui.Component.State}s for which this component
185  * dispatches state transition events.  Because events are expensive, the
186  * default behavior is to not dispatch any state transition events at all.
187  * Use the {@link #setDispatchTransitionEvents} API to request transition
188  * events  as needed.  Subclasses may enable transition events by default.
189  * Controls hosted in containers or managed by a selection model will typically
190  * want to dispatch transition events.
191  * @type {number}
192  * @private
193  */
194 goog.ui.Control.prototype.statesWithTransitionEvents_ = 0x00;
195
196
197 /**
198  * Component visibility.
199  * @type {boolean}
200  * @private
201  */
202 goog.ui.Control.prototype.visible_ = true;
203
204
205 /**
206  * Keyboard event handler.
207  * @type {goog.events.KeyHandler}
208  * @private
209  */
210 goog.ui.Control.prototype.keyHandler_;
211
212
213 /**
214  * Additional class name(s) to apply to the control's root element, if any.
215  * @type {Array.<string>?}
216  * @private
217  */
218 goog.ui.Control.prototype.extraClassNames_ = null;
219
220
221 /**
222  * Whether the control should listen for and handle mouse events; defaults to
223  * true.
224  * @type {boolean}
225  * @private
226  */
227 goog.ui.Control.prototype.handleMouseEvents_ = true;
228
229
230 /**
231  * Whether the control allows text selection within its DOM.  Defaults to false.
232  * @type {boolean}
233  * @private
234  */
235 goog.ui.Control.prototype.allowTextSelection_ = false;
236
237
238 /**
239  * The control's preferred ARIA role.
240  * @type {?goog.a11y.aria.Role}
241  * @private
242  */
243 goog.ui.Control.prototype.preferredAriaRole_ = null;
244
245
246 // Event handler and renderer management.
247
248
249 /**
250  * Returns true if the control is configured to handle its own mouse events,
251  * false otherwise.  Controls not hosted in {@link goog.ui.Container}s have
252  * to handle their own mouse events, but controls hosted in containers may
253  * allow their parent to handle mouse events on their behalf.  Considered
254  * protected; should only be used within this package and by subclasses.
255  * @return {boolean} Whether the control handles its own mouse events.
256  */
257 goog.ui.Control.prototype.isHandleMouseEvents = function() {
258   return this.handleMouseEvents_;
259 };
260
261
262 /**
263  * Enables or disables mouse event handling for the control.  Containers may
264  * use this method to disable mouse event handling in their child controls.
265  * Considered protected; should only be used within this package and by
266  * subclasses.
267  * @param {boolean} enable Whether to enable or disable mouse event handling.
268  */
269 goog.ui.Control.prototype.setHandleMouseEvents = function(enable) {
270   if (this.isInDocument() && enable != this.handleMouseEvents_) {
271     // Already in the document; need to update event handler.
272     this.enableMouseEventHandling_(enable);
273   }
274   this.handleMouseEvents_ = enable;
275 };
276
277
278 /**
279  * Returns the DOM element on which the control is listening for keyboard
280  * events (null if none).
281  * @return {Element} Element on which the control is listening for key
282  *     events.
283  */
284 goog.ui.Control.prototype.getKeyEventTarget = function() {
285   // Delegate to renderer.
286   return this.renderer_.getKeyEventTarget(this);
287 };
288
289
290 /**
291  * Returns the keyboard event handler for this component, lazily created the
292  * first time this method is called.  Considered protected; should only be
293  * used within this package and by subclasses.
294  * @return {!goog.events.KeyHandler} Keyboard event handler for this component.
295  * @protected
296  */
297 goog.ui.Control.prototype.getKeyHandler = function() {
298   return this.keyHandler_ || (this.keyHandler_ = new goog.events.KeyHandler());
299 };
300
301
302 /**
303  * Returns the renderer used by this component to render itself or to decorate
304  * an existing element.
305  * @return {goog.ui.ControlRenderer|undefined} Renderer used by the component
306  *     (undefined if none).
307  */
308 goog.ui.Control.prototype.getRenderer = function() {
309   return this.renderer_;
310 };
311
312
313 /**
314  * Registers the given renderer with the component.  Changing renderers after
315  * the component has entered the document is an error.
316  * @param {goog.ui.ControlRenderer} renderer Renderer used by the component.
317  * @throws {Error} If the control is already in the document.
318  */
319 goog.ui.Control.prototype.setRenderer = function(renderer) {
320   if (this.isInDocument()) {
321     // Too late.
322     throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
323   }
324
325   if (this.getElement()) {
326     // The component has already been rendered, but isn't yet in the document.
327     // Replace the renderer and delete the current DOM, so it can be re-rendered
328     // using the new renderer the next time someone calls render().
329     this.setElementInternal(null);
330   }
331
332   this.renderer_ = renderer;
333 };
334
335
336 // Support for additional styling.
337
338
339 /**
340  * Returns any additional class name(s) to be applied to the component's
341  * root element, or null if no extra class names are needed.
342  * @return {Array.<string>?} Additional class names to be applied to
343  *     the component's root element (null if none).
344  */
345 goog.ui.Control.prototype.getExtraClassNames = function() {
346   return this.extraClassNames_;
347 };
348
349
350 /**
351  * Adds the given class name to the list of classes to be applied to the
352  * component's root element.
353  * @param {string} className Additional class name to be applied to the
354  *     component's root element.
355  */
356 goog.ui.Control.prototype.addClassName = function(className) {
357   if (className) {
358     if (this.extraClassNames_) {
359       if (!goog.array.contains(this.extraClassNames_, className)) {
360         this.extraClassNames_.push(className);
361       }
362     } else {
363       this.extraClassNames_ = [className];
364     }
365     this.renderer_.enableExtraClassName(this, className, true);
366   }
367 };
368
369
370 /**
371  * Removes the given class name from the list of classes to be applied to
372  * the component's root element.
373  * @param {string} className Class name to be removed from the component's root
374  *     element.
375  */
376 goog.ui.Control.prototype.removeClassName = function(className) {
377   if (className && this.extraClassNames_ &&
378       goog.array.remove(this.extraClassNames_, className)) {
379     if (this.extraClassNames_.length == 0) {
380       this.extraClassNames_ = null;
381     }
382     this.renderer_.enableExtraClassName(this, className, false);
383   }
384 };
385
386
387 /**
388  * Adds or removes the given class name to/from the list of classes to be
389  * applied to the component's root element.
390  * @param {string} className CSS class name to add or remove.
391  * @param {boolean} enable Whether to add or remove the class name.
392  */
393 goog.ui.Control.prototype.enableClassName = function(className, enable) {
394   if (enable) {
395     this.addClassName(className);
396   } else {
397     this.removeClassName(className);
398   }
399 };
400
401
402 // Standard goog.ui.Component implementation.
403
404
405 /**
406  * Creates the control's DOM.  Overrides {@link goog.ui.Component#createDom} by
407  * delegating DOM manipulation to the control's renderer.
408  * @override
409  */
410 goog.ui.Control.prototype.createDom = function() {
411   var element = this.renderer_.createDom(this);
412   this.setElementInternal(element);
413
414   // Initialize ARIA role.
415   this.renderer_.setAriaRole(element, this.getPreferredAriaRole());
416
417   // Initialize text selection.
418   if (!this.isAllowTextSelection()) {
419     // The renderer is assumed to create selectable elements.  Since making
420     // elements unselectable is expensive, only do it if needed (bug 1037090).
421     this.renderer_.setAllowTextSelection(element, false);
422   }
423
424   // Initialize visibility.
425   if (!this.isVisible()) {
426     // The renderer is assumed to create visible elements. Since hiding
427     // elements can be expensive, only do it if needed (bug 1037105).
428     this.renderer_.setVisible(element, false);
429   }
430 };
431
432
433 /**
434  * Returns the control's preferred ARIA role. This can be used by a control to
435  * override the role that would be assigned by the renderer.  This is useful in
436  * cases where a different ARIA role is appropriate for a control because of the
437  * context in which it's used.  E.g., a {@link goog.ui.MenuButton} added to a
438  * {@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 if
440  *     no preferred ARIA role is set.
441  */
442 goog.ui.Control.prototype.getPreferredAriaRole = function() {
443   return this.preferredAriaRole_;
444 };
445
446
447 /**
448  * Sets the control's preferred ARIA role. This can be used to override the role
449  * that would be assigned by the renderer.  This is useful in cases where a
450  * different ARIA role is appropriate for a control because of the
451  * context in which it's used.  E.g., a {@link goog.ui.MenuButton} added to a
452  * {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.
453  * @param {goog.a11y.aria.Role} role This control's preferred ARIA role.
454  */
455 goog.ui.Control.prototype.setPreferredAriaRole = function(role) {
456   this.preferredAriaRole_ = role;
457 };
458
459
460 /**
461  * Returns the DOM element into which child components are to be rendered,
462  * or null if the control itself hasn't been rendered yet.  Overrides
463  * {@link goog.ui.Component#getContentElement} by delegating to the renderer.
464  * @return {Element} Element to contain child elements (null if none).
465  * @override
466  */
467 goog.ui.Control.prototype.getContentElement = function() {
468   // Delegate to renderer.
469   return this.renderer_.getContentElement(this.getElement());
470 };
471
472
473 /**
474  * Returns true if the given element can be decorated by this component.
475  * Overrides {@link goog.ui.Component#canDecorate}.
476  * @param {Element} element Element to decorate.
477  * @return {boolean} Whether the element can be decorated by this component.
478  * @override
479  */
480 goog.ui.Control.prototype.canDecorate = function(element) {
481   // Controls support pluggable renderers; delegate to the renderer.
482   return this.renderer_.canDecorate(element);
483 };
484
485
486 /**
487  * Decorates the given element with this component. Overrides {@link
488  * goog.ui.Component#decorateInternal} by delegating DOM manipulation
489  * to the control's renderer.
490  * @param {Element} element Element to decorate.
491  * @protected
492  * @override
493  */
494 goog.ui.Control.prototype.decorateInternal = function(element) {
495   element = this.renderer_.decorate(this, element);
496   this.setElementInternal(element);
497
498   // Initialize ARIA role.
499   this.renderer_.setAriaRole(element, this.getPreferredAriaRole());
500
501   // Initialize text selection.
502   if (!this.isAllowTextSelection()) {
503     // Decorated elements are assumed to be selectable.  Since making elements
504     // unselectable is expensive, only do it if needed (bug 1037090).
505     this.renderer_.setAllowTextSelection(element, false);
506   }
507
508   // Initialize visibility based on the decorated element's styling.
509   this.visible_ = element.style.display != 'none';
510 };
511
512
513 /**
514  * Configures the component after its DOM has been rendered, and sets up event
515  * handling.  Overrides {@link goog.ui.Component#enterDocument}.
516  * @override
517  */
518 goog.ui.Control.prototype.enterDocument = function() {
519   goog.ui.Control.superClass_.enterDocument.call(this);
520
521   // Call the renderer's initializeDom method to configure properties of the
522   // control's DOM that can only be done once it's in the document.
523   this.renderer_.initializeDom(this);
524
525   // Initialize event handling if at least one state other than DISABLED is
526   // supported.
527   if (this.supportedStates_ & ~goog.ui.Component.State.DISABLED) {
528     // Initialize mouse event handling if the control is configured to handle
529     // its own mouse events.  (Controls hosted in containers don't need to
530     // handle their own mouse events.)
531     if (this.isHandleMouseEvents()) {
532       this.enableMouseEventHandling_(true);
533     }
534
535     // Initialize keyboard event handling if the control is focusable and has
536     // a key event target.  (Controls hosted in containers typically aren't
537     // focusable, allowing their container to handle keyboard events for them.)
538     if (this.isSupportedState(goog.ui.Component.State.FOCUSED)) {
539       var keyTarget = this.getKeyEventTarget();
540       if (keyTarget) {
541         var keyHandler = this.getKeyHandler();
542         keyHandler.attach(keyTarget);
543         this.getHandler().
544             listen(keyHandler, goog.events.KeyHandler.EventType.KEY,
545                 this.handleKeyEvent).
546             listen(keyTarget, goog.events.EventType.FOCUS,
547                 this.handleFocus).
548             listen(keyTarget, goog.events.EventType.BLUR,
549                 this.handleBlur);
550       }
551     }
552   }
553 };
554
555
556 /**
557  * Enables or disables mouse event handling on the control.
558  * @param {boolean} enable Whether to enable mouse event handling.
559  * @private
560  */
561 goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) {
562   var handler = this.getHandler();
563   var element = this.getElement();
564   if (enable) {
565     handler.
566         listen(element, goog.events.EventType.MOUSEOVER, this.handleMouseOver).
567         listen(element, goog.events.EventType.MOUSEDOWN, this.handleMouseDown).
568         listen(element, goog.events.EventType.MOUSEUP, this.handleMouseUp).
569         listen(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);
570     if (this.handleContextMenu != goog.nullFunction) {
571       handler.listen(element, goog.events.EventType.CONTEXTMENU,
572           this.handleContextMenu);
573     }
574     if (goog.userAgent.IE) {
575       handler.listen(element, goog.events.EventType.DBLCLICK,
576           this.handleDblClick);
577     }
578   } else {
579     handler.
580         unlisten(element, goog.events.EventType.MOUSEOVER,
581             this.handleMouseOver).
582         unlisten(element, goog.events.EventType.MOUSEDOWN,
583             this.handleMouseDown).
584         unlisten(element, goog.events.EventType.MOUSEUP, this.handleMouseUp).
585         unlisten(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);
586     if (this.handleContextMenu != goog.nullFunction) {
587       handler.unlisten(element, goog.events.EventType.CONTEXTMENU,
588           this.handleContextMenu);
589     }
590     if (goog.userAgent.IE) {
591       handler.unlisten(element, goog.events.EventType.DBLCLICK,
592           this.handleDblClick);
593     }
594   }
595 };
596
597
598 /**
599  * Cleans up the component before its DOM is removed from the document, and
600  * removes event handlers.  Overrides {@link goog.ui.Component#exitDocument}
601  * by making sure that components that are removed from the document aren't
602  * focusable (i.e. have no tab index).
603  * @override
604  */
605 goog.ui.Control.prototype.exitDocument = function() {
606   goog.ui.Control.superClass_.exitDocument.call(this);
607   if (this.keyHandler_) {
608     this.keyHandler_.detach();
609   }
610   if (this.isVisible() && this.isEnabled()) {
611     this.renderer_.setFocusable(this, false);
612   }
613 };
614
615
616 /** @override */
617 goog.ui.Control.prototype.disposeInternal = function() {
618   goog.ui.Control.superClass_.disposeInternal.call(this);
619   if (this.keyHandler_) {
620     this.keyHandler_.dispose();
621     delete this.keyHandler_;
622   }
623   delete this.renderer_;
624   this.content_ = null;
625   this.extraClassNames_ = null;
626 };
627
628
629 // Component content management.
630
631
632 /**
633  * Returns the text caption or DOM structure displayed in the component.
634  * @return {goog.ui.ControlContent} Text caption or DOM structure
635  *     comprising the component's contents.
636  */
637 goog.ui.Control.prototype.getContent = function() {
638   return this.content_;
639 };
640
641
642 /**
643  * Sets the component's content to the given text caption, element, or array of
644  * nodes.  (If the argument is an array of nodes, it must be an actual array,
645  * not an array-like object.)
646  * @param {goog.ui.ControlContent} content Text caption or DOM
647  *     structure to set as the component's contents.
648  */
649 goog.ui.Control.prototype.setContent = function(content) {
650   // Controls support pluggable renderers; delegate to the renderer.
651   this.renderer_.setContent(this.getElement(), content);
652
653   // setContentInternal needs to be after the renderer, since the implementation
654   // may depend on the content being in the DOM.
655   this.setContentInternal(content);
656 };
657
658
659 /**
660  * Sets the component's content to the given text caption, element, or array
661  * of nodes.  Unlike {@link #setContent}, doesn't modify the component's DOM.
662  * Called by renderers during element decoration.
663  *
664  * This should only be used by subclasses and its associated renderers.
665  *
666  * @param {goog.ui.ControlContent} content Text caption or DOM structure
667  *     to set as the component's contents.
668  */
669 goog.ui.Control.prototype.setContentInternal = function(content) {
670   this.content_ = content;
671 };
672
673
674 /**
675  * @return {string} Text caption of the control or empty string if none.
676  */
677 goog.ui.Control.prototype.getCaption = function() {
678   var content = this.getContent();
679   if (!content) {
680     return '';
681   }
682   var caption =
683       goog.isString(content) ? content :
684       goog.isArray(content) ? goog.array.map(content,
685           goog.dom.getRawTextContent).join('') :
686       goog.dom.getTextContent(/** @type {!Node} */ (content));
687   return goog.string.collapseBreakingSpaces(caption);
688 };
689
690
691 /**
692  * Sets the text caption of the component.
693  * @param {string} caption Text caption of the component.
694  */
695 goog.ui.Control.prototype.setCaption = function(caption) {
696   this.setContent(caption);
697 };
698
699
700 // Component state management.
701
702
703 /** @override */
704 goog.ui.Control.prototype.setRightToLeft = function(rightToLeft) {
705   // The superclass implementation ensures the control isn't in the document.
706   goog.ui.Control.superClass_.setRightToLeft.call(this, rightToLeft);
707
708   var element = this.getElement();
709   if (element) {
710     this.renderer_.setRightToLeft(element, rightToLeft);
711   }
712 };
713
714
715 /**
716  * Returns true if the control allows text selection within its DOM, false
717  * otherwise.  Controls that disallow text selection have the appropriate
718  * unselectable styling applied to their elements.  Note that controls hosted
719  * in containers will report that they allow text selection even if their
720  * container disallows text selection.
721  * @return {boolean} Whether the control allows text selection.
722  */
723 goog.ui.Control.prototype.isAllowTextSelection = function() {
724   return this.allowTextSelection_;
725 };
726
727
728 /**
729  * Allows or disallows text selection within the control's DOM.
730  * @param {boolean} allow Whether the control should allow text selection.
731  */
732 goog.ui.Control.prototype.setAllowTextSelection = function(allow) {
733   this.allowTextSelection_ = allow;
734
735   var element = this.getElement();
736   if (element) {
737     this.renderer_.setAllowTextSelection(element, allow);
738   }
739 };
740
741
742 /**
743  * Returns true if the component's visibility is set to visible, false if
744  * it is set to hidden.  A component that is set to hidden is guaranteed
745  * to be hidden from the user, but the reverse isn't necessarily true.
746  * A component may be set to visible but can otherwise be obscured by another
747  * element, rendered off-screen, or hidden using direct CSS manipulation.
748  * @return {boolean} Whether the component is visible.
749  */
750 goog.ui.Control.prototype.isVisible = function() {
751   return this.visible_;
752 };
753
754
755 /**
756  * Shows or hides the component.  Does nothing if the component already has
757  * the requested visibility.  Otherwise, dispatches a SHOW or HIDE event as
758  * appropriate, giving listeners a chance to prevent the visibility change.
759  * When showing a component that is both enabled and focusable, ensures that
760  * its key target has a tab index.  When hiding a component that is enabled
761  * and focusable, blurs its key target and removes its tab index.
762  * @param {boolean} visible Whether to show or hide the component.
763  * @param {boolean=} opt_force If true, doesn't check whether the component
764  *     already has the requested visibility, and doesn't dispatch any events.
765  * @return {boolean} Whether the visibility was changed.
766  */
767 goog.ui.Control.prototype.setVisible = function(visible, opt_force) {
768   if (opt_force || (this.visible_ != visible && this.dispatchEvent(visible ?
769       goog.ui.Component.EventType.SHOW : goog.ui.Component.EventType.HIDE))) {
770     var element = this.getElement();
771     if (element) {
772       this.renderer_.setVisible(element, visible);
773     }
774     if (this.isEnabled()) {
775       this.renderer_.setFocusable(this, visible);
776     }
777     this.visible_ = visible;
778     return true;
779   }
780   return false;
781 };
782
783
784 /**
785  * Returns true if the component is enabled, false otherwise.
786  * @return {boolean} Whether the component is enabled.
787  */
788 goog.ui.Control.prototype.isEnabled = function() {
789   return !this.hasState(goog.ui.Component.State.DISABLED);
790 };
791
792
793 /**
794  * Returns true if the control has a parent that is itself disabled, false
795  * otherwise.
796  * @return {boolean} Whether the component is hosted in a disabled container.
797  * @private
798  */
799 goog.ui.Control.prototype.isParentDisabled_ = function() {
800   var parent = this.getParent();
801   return !!parent && typeof parent.isEnabled == 'function' &&
802       !parent.isEnabled();
803 };
804
805
806 /**
807  * Enables or disables the component.  Does nothing if this state transition
808  * is disallowed.  If the component is both visible and focusable, updates its
809  * focused state and tab index as needed.  If the component is being disabled,
810  * ensures that it is also deactivated and un-highlighted first.  Note that the
811  * component's enabled/disabled state is "locked" as long as it is hosted in a
812  * {@link goog.ui.Container} that is itself disabled; this is to prevent clients
813  * from accidentally re-enabling a control that is in a disabled container.
814  * @param {boolean} enable Whether to enable or disable the component.
815  * @see #isTransitionAllowed
816  */
817 goog.ui.Control.prototype.setEnabled = function(enable) {
818   if (!this.isParentDisabled_() &&
819       this.isTransitionAllowed(goog.ui.Component.State.DISABLED, !enable)) {
820     if (!enable) {
821       this.setActive(false);
822       this.setHighlighted(false);
823     }
824     if (this.isVisible()) {
825       this.renderer_.setFocusable(this, enable);
826     }
827     this.setState(goog.ui.Component.State.DISABLED, !enable);
828   }
829 };
830
831
832 /**
833  * Returns true if the component is currently highlighted, false otherwise.
834  * @return {boolean} Whether the component is highlighted.
835  */
836 goog.ui.Control.prototype.isHighlighted = function() {
837   return this.hasState(goog.ui.Component.State.HOVER);
838 };
839
840
841 /**
842  * Highlights or unhighlights the component.  Does nothing if this state
843  * transition is disallowed.
844  * @param {boolean} highlight Whether to highlight or unhighlight the component.
845  * @see #isTransitionAllowed
846  */
847 goog.ui.Control.prototype.setHighlighted = function(highlight) {
848   if (this.isTransitionAllowed(goog.ui.Component.State.HOVER, highlight)) {
849     this.setState(goog.ui.Component.State.HOVER, highlight);
850   }
851 };
852
853
854 /**
855  * Returns true if the component is active (pressed), false otherwise.
856  * @return {boolean} Whether the component is active.
857  */
858 goog.ui.Control.prototype.isActive = function() {
859   return this.hasState(goog.ui.Component.State.ACTIVE);
860 };
861
862
863 /**
864  * Activates or deactivates the component.  Does nothing if this state
865  * transition is disallowed.
866  * @param {boolean} active Whether to activate or deactivate the component.
867  * @see #isTransitionAllowed
868  */
869 goog.ui.Control.prototype.setActive = function(active) {
870   if (this.isTransitionAllowed(goog.ui.Component.State.ACTIVE, active)) {
871     this.setState(goog.ui.Component.State.ACTIVE, active);
872   }
873 };
874
875
876 /**
877  * Returns true if the component is selected, false otherwise.
878  * @return {boolean} Whether the component is selected.
879  */
880 goog.ui.Control.prototype.isSelected = function() {
881   return this.hasState(goog.ui.Component.State.SELECTED);
882 };
883
884
885 /**
886  * Selects or unselects the component.  Does nothing if this state transition
887  * is disallowed.
888  * @param {boolean} select Whether to select or unselect the component.
889  * @see #isTransitionAllowed
890  */
891 goog.ui.Control.prototype.setSelected = function(select) {
892   if (this.isTransitionAllowed(goog.ui.Component.State.SELECTED, select)) {
893     this.setState(goog.ui.Component.State.SELECTED, select);
894   }
895 };
896
897
898 /**
899  * Returns true if the component is checked, false otherwise.
900  * @return {boolean} Whether the component is checked.
901  */
902 goog.ui.Control.prototype.isChecked = function() {
903   return this.hasState(goog.ui.Component.State.CHECKED);
904 };
905
906
907 /**
908  * Checks or unchecks the component.  Does nothing if this state transition
909  * is disallowed.
910  * @param {boolean} check Whether to check or uncheck the component.
911  * @see #isTransitionAllowed
912  */
913 goog.ui.Control.prototype.setChecked = function(check) {
914   if (this.isTransitionAllowed(goog.ui.Component.State.CHECKED, check)) {
915     this.setState(goog.ui.Component.State.CHECKED, check);
916   }
917 };
918
919
920 /**
921  * Returns true if the component is styled to indicate that it has keyboard
922  * focus, false otherwise.  Note that {@code isFocused()} returning true
923  * doesn't guarantee that the component's key event target has keyborad focus,
924  * only that it is styled as such.
925  * @return {boolean} Whether the component is styled to indicate as having
926  *     keyboard focus.
927  */
928 goog.ui.Control.prototype.isFocused = function() {
929   return this.hasState(goog.ui.Component.State.FOCUSED);
930 };
931
932
933 /**
934  * Applies or removes styling indicating that the component has keyboard focus.
935  * Note that unlike the other "set" methods, this method is called as a result
936  * of the component's element having received or lost keyboard focus, not the
937  * other way around, so calling {@code setFocused(true)} doesn't guarantee that
938  * the component's key event target has keyboard focus, only that it is styled
939  * as such.
940  * @param {boolean} focused Whether to apply or remove styling to indicate that
941  *     the component's element has keyboard focus.
942  */
943 goog.ui.Control.prototype.setFocused = function(focused) {
944   if (this.isTransitionAllowed(goog.ui.Component.State.FOCUSED, focused)) {
945     this.setState(goog.ui.Component.State.FOCUSED, focused);
946   }
947 };
948
949
950 /**
951  * Returns true if the component is open (expanded), false otherwise.
952  * @return {boolean} Whether the component is open.
953  */
954 goog.ui.Control.prototype.isOpen = function() {
955   return this.hasState(goog.ui.Component.State.OPENED);
956 };
957
958
959 /**
960  * Opens (expands) or closes (collapses) the component.  Does nothing if this
961  * state transition is disallowed.
962  * @param {boolean} open Whether to open or close the component.
963  * @see #isTransitionAllowed
964  */
965 goog.ui.Control.prototype.setOpen = function(open) {
966   if (this.isTransitionAllowed(goog.ui.Component.State.OPENED, open)) {
967     this.setState(goog.ui.Component.State.OPENED, open);
968   }
969 };
970
971
972 /**
973  * Returns the component's state as a bit mask of {@link
974  * goog.ui.Component.State}s.
975  * @return {number} Bit mask representing component state.
976  */
977 goog.ui.Control.prototype.getState = function() {
978   return this.state_;
979 };
980
981
982 /**
983  * Returns true if the component is in the specified state, false otherwise.
984  * @param {goog.ui.Component.State} state State to check.
985  * @return {boolean} Whether the component is in the given state.
986  */
987 goog.ui.Control.prototype.hasState = function(state) {
988   return !!(this.state_ & state);
989 };
990
991
992 /**
993  * Sets or clears the given state on the component, and updates its styling
994  * accordingly.  Does nothing if the component is already in the correct state
995  * or if it doesn't support the specified state.  Doesn't dispatch any state
996  * transition events; use advisedly.
997  * @param {goog.ui.Component.State} state State to set or clear.
998  * @param {boolean} enable Whether to set or clear the state (if supported).
999  */
1000 goog.ui.Control.prototype.setState = function(state, enable) {
1001   if (this.isSupportedState(state) && enable != this.hasState(state)) {
1002     // Delegate actual styling to the renderer, since it is DOM-specific.
1003     this.renderer_.setState(this, state, enable);
1004     this.state_ = enable ? this.state_ | state : this.state_ & ~state;
1005   }
1006 };
1007
1008
1009 /**
1010  * Sets the component's state to the state represented by a bit mask of
1011  * {@link goog.ui.Component.State}s.  Unlike {@link #setState}, doesn't
1012  * update the component's styling, and doesn't reject unsupported states.
1013  * Called by renderers during element decoration.  Considered protected;
1014  * should only be used within this package and by subclasses.
1015  *
1016  * This should only be used by subclasses and its associated renderers.
1017  *
1018  * @param {number} state Bit mask representing component state.
1019  */
1020 goog.ui.Control.prototype.setStateInternal = function(state) {
1021   this.state_ = state;
1022 };
1023
1024
1025 /**
1026  * Returns true if the component supports the specified state, false otherwise.
1027  * @param {goog.ui.Component.State} state State to check.
1028  * @return {boolean} Whether the component supports the given state.
1029  */
1030 goog.ui.Control.prototype.isSupportedState = function(state) {
1031   return !!(this.supportedStates_ & state);
1032 };
1033
1034
1035 /**
1036  * Enables or disables support for the given state. Disabling support
1037  * for a state while the component is in that state is an error.
1038  * @param {goog.ui.Component.State} state State to support or de-support.
1039  * @param {boolean} support Whether the component should support the state.
1040  * @throws {Error} If disabling support for a state the control is currently in.
1041  */
1042 goog.ui.Control.prototype.setSupportedState = function(state, support) {
1043   if (this.isInDocument() && this.hasState(state) && !support) {
1044     // Since we hook up event handlers in enterDocument(), this is an error.
1045     throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
1046   }
1047
1048   if (!support && this.hasState(state)) {
1049     // We are removing support for a state that the component is currently in.
1050     this.setState(state, false);
1051   }
1052
1053   this.supportedStates_ = support ?
1054       this.supportedStates_ | state : this.supportedStates_ & ~state;
1055 };
1056
1057
1058 /**
1059  * Returns true if the component provides default event handling for the state,
1060  * false otherwise.
1061  * @param {goog.ui.Component.State} state State to check.
1062  * @return {boolean} Whether the component provides default event handling for
1063  *     the state.
1064  */
1065 goog.ui.Control.prototype.isAutoState = function(state) {
1066   return !!(this.autoStates_ & state) && this.isSupportedState(state);
1067 };
1068
1069
1070 /**
1071  * Enables or disables automatic event handling for the given state(s).
1072  * @param {number} states Bit mask of {@link goog.ui.Component.State}s for which
1073  *     default event handling is to be enabled or disabled.
1074  * @param {boolean} enable Whether the component should provide default event
1075  *     handling for the state(s).
1076  */
1077 goog.ui.Control.prototype.setAutoStates = function(states, enable) {
1078   this.autoStates_ = enable ?
1079       this.autoStates_ | states : this.autoStates_ & ~states;
1080 };
1081
1082
1083 /**
1084  * Returns true if the component is set to dispatch transition events for the
1085  * given state, false otherwise.
1086  * @param {goog.ui.Component.State} state State to check.
1087  * @return {boolean} Whether the component dispatches transition events for
1088  *     the state.
1089  */
1090 goog.ui.Control.prototype.isDispatchTransitionEvents = function(state) {
1091   return !!(this.statesWithTransitionEvents_ & state) &&
1092       this.isSupportedState(state);
1093 };
1094
1095
1096 /**
1097  * Enables or disables transition events for the given state(s).  Controls
1098  * handle state transitions internally by default, and only dispatch state
1099  * transition events if explicitly requested to do so by calling this method.
1100  * @param {number} states Bit mask of {@link goog.ui.Component.State}s for
1101  *     which transition events should be enabled or disabled.
1102  * @param {boolean} enable Whether transition events should be enabled.
1103  */
1104 goog.ui.Control.prototype.setDispatchTransitionEvents = function(states,
1105     enable) {
1106   this.statesWithTransitionEvents_ = enable ?
1107       this.statesWithTransitionEvents_ | states :
1108       this.statesWithTransitionEvents_ & ~states;
1109 };
1110
1111
1112 /**
1113  * Returns true if the transition into or out of the given state is allowed to
1114  * proceed, false otherwise.  A state transition is allowed under the following
1115  * conditions:
1116  * <ul>
1117  *   <li>the component supports the state,
1118  *   <li>the component isn't already in the target state,
1119  *   <li>either the component is configured not to dispatch events for this
1120  *       state transition, or a transition event was dispatched and wasn't
1121  *       canceled by any event listener, and
1122  *   <li>the component hasn't been disposed of
1123  * </ul>
1124  * Considered protected; should only be used within this package and by
1125  * subclasses.
1126  * @param {goog.ui.Component.State} state State to/from which the control is
1127  *     transitioning.
1128  * @param {boolean} enable Whether the control is entering or leaving the state.
1129  * @return {boolean} Whether the state transition is allowed to proceed.
1130  * @protected
1131  */
1132 goog.ui.Control.prototype.isTransitionAllowed = function(state, enable) {
1133   return this.isSupportedState(state) &&
1134       this.hasState(state) != enable &&
1135       (!(this.statesWithTransitionEvents_ & state) || this.dispatchEvent(
1136           goog.ui.Component.getStateTransitionEvent(state, enable))) &&
1137       !this.isDisposed();
1138 };
1139
1140
1141 // Default event handlers, to be overridden in subclasses.
1142
1143
1144 /**
1145  * Handles mouseover events.  Dispatches an ENTER event; if the event isn't
1146  * canceled, the component is enabled, and it supports auto-highlighting,
1147  * highlights the component.  Considered protected; should only be used
1148  * within this package and by subclasses.
1149  * @param {goog.events.BrowserEvent} e Mouse event to handle.
1150  */
1151 goog.ui.Control.prototype.handleMouseOver = function(e) {
1152   // Ignore mouse moves between descendants.
1153   if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&
1154       this.dispatchEvent(goog.ui.Component.EventType.ENTER) &&
1155       this.isEnabled() &&
1156       this.isAutoState(goog.ui.Component.State.HOVER)) {
1157     this.setHighlighted(true);
1158   }
1159 };
1160
1161
1162 /**
1163  * Handles mouseout events.  Dispatches a LEAVE event; if the event isn't
1164  * canceled, and the component supports auto-highlighting, deactivates and
1165  * un-highlights the component.  Considered protected; should only be used
1166  * within this package and by subclasses.
1167  * @param {goog.events.BrowserEvent} e Mouse event to handle.
1168  */
1169 goog.ui.Control.prototype.handleMouseOut = function(e) {
1170   if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&
1171       this.dispatchEvent(goog.ui.Component.EventType.LEAVE)) {
1172     if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1173       // Deactivate on mouseout; otherwise we lose track of the mouse button.
1174       this.setActive(false);
1175     }
1176     if (this.isAutoState(goog.ui.Component.State.HOVER)) {
1177       this.setHighlighted(false);
1178     }
1179   }
1180 };
1181
1182
1183 /**
1184  * Handles contextmenu events.
1185  * @param {goog.events.BrowserEvent} e Event to handle.
1186  */
1187 goog.ui.Control.prototype.handleContextMenu = goog.nullFunction;
1188
1189
1190 /**
1191  * Checks if a mouse event (mouseover or mouseout) occured below an element.
1192  * @param {goog.events.BrowserEvent} e Mouse event (should be mouseover or
1193  *     mouseout).
1194  * @param {Element} elem The ancestor element.
1195  * @return {boolean} Whether the event has a relatedTarget (the element the
1196  *     mouse is coming from) and it's a descendent of elem.
1197  * @private
1198  */
1199 goog.ui.Control.isMouseEventWithinElement_ = function(e, elem) {
1200   // If relatedTarget is null, it means there was no previous element (e.g.
1201   // the mouse moved out of the window).  Assume this means that the mouse
1202   // event was not within the element.
1203   return !!e.relatedTarget && goog.dom.contains(elem, e.relatedTarget);
1204 };
1205
1206
1207 /**
1208  * Handles mousedown events.  If the component is enabled, highlights and
1209  * activates it.  If the component isn't configured for keyboard access,
1210  * prevents it from receiving keyboard focus.  Considered protected; should
1211  * only be used within this package and by subclasses.
1212  * @param {goog.events.Event} e Mouse event to handle.
1213  */
1214 goog.ui.Control.prototype.handleMouseDown = function(e) {
1215   if (this.isEnabled()) {
1216     // Highlight enabled control on mousedown, regardless of the mouse button.
1217     if (this.isAutoState(goog.ui.Component.State.HOVER)) {
1218       this.setHighlighted(true);
1219     }
1220
1221     // For the left button only, activate the control, and focus its key event
1222     // target (if supported).
1223     if (e.isMouseActionButton()) {
1224       if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1225         this.setActive(true);
1226       }
1227       if (this.renderer_.isFocusable(this)) {
1228         this.getKeyEventTarget().focus();
1229       }
1230     }
1231   }
1232
1233   // Cancel the default action unless the control allows text selection.
1234   if (!this.isAllowTextSelection() && e.isMouseActionButton()) {
1235     e.preventDefault();
1236   }
1237 };
1238
1239
1240 /**
1241  * Handles mouseup events.  If the component is enabled, highlights it.  If
1242  * the component has previously been activated, performs its associated action
1243  * by calling {@link performActionInternal}, then deactivates it.  Considered
1244  * protected; should only be used within this package and by subclasses.
1245  * @param {goog.events.Event} e Mouse event to handle.
1246  */
1247 goog.ui.Control.prototype.handleMouseUp = function(e) {
1248   if (this.isEnabled()) {
1249     if (this.isAutoState(goog.ui.Component.State.HOVER)) {
1250       this.setHighlighted(true);
1251     }
1252     if (this.isActive() &&
1253         this.performActionInternal(e) &&
1254         this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1255       this.setActive(false);
1256     }
1257   }
1258 };
1259
1260
1261 /**
1262  * Handles dblclick events.  Should only be registered if the user agent is
1263  * IE.  If the component is enabled, performs its associated action by calling
1264  * {@link performActionInternal}.  This is used to allow more performant
1265  * buttons in IE.  In IE, no mousedown event is fired when that mousedown will
1266  * trigger a dblclick event.  Because of this, a user clicking quickly will
1267  * only cause ACTION events to fire on every other click.  This is a workaround
1268  * to generate ACTION events for every click.  Unfortunately, this workaround
1269  * won't ever trigger the ACTIVE state.  This is roughly the same behaviour as
1270  * if this were a 'button' element with a listener on mouseup.  Considered
1271  * protected; should only be used within this package and by subclasses.
1272  * @param {goog.events.Event} e Mouse event to handle.
1273  */
1274 goog.ui.Control.prototype.handleDblClick = function(e) {
1275   if (this.isEnabled()) {
1276     this.performActionInternal(e);
1277   }
1278 };
1279
1280
1281 /**
1282  * Performs the appropriate action when the control is activated by the user.
1283  * The default implementation first updates the checked and selected state of
1284  * controls that support them, then dispatches an ACTION event.  Considered
1285  * protected; should only be used within this package and by subclasses.
1286  * @param {goog.events.Event} e Event that triggered the action.
1287  * @return {boolean} Whether the action is allowed to proceed.
1288  * @protected
1289  */
1290 goog.ui.Control.prototype.performActionInternal = function(e) {
1291   if (this.isAutoState(goog.ui.Component.State.CHECKED)) {
1292     this.setChecked(!this.isChecked());
1293   }
1294   if (this.isAutoState(goog.ui.Component.State.SELECTED)) {
1295     this.setSelected(true);
1296   }
1297   if (this.isAutoState(goog.ui.Component.State.OPENED)) {
1298     this.setOpen(!this.isOpen());
1299   }
1300
1301   var actionEvent = new goog.events.Event(goog.ui.Component.EventType.ACTION,
1302       this);
1303   if (e) {
1304     actionEvent.altKey = e.altKey;
1305     actionEvent.ctrlKey = e.ctrlKey;
1306     actionEvent.metaKey = e.metaKey;
1307     actionEvent.shiftKey = e.shiftKey;
1308     actionEvent.platformModifierKey = e.platformModifierKey;
1309   }
1310   return this.dispatchEvent(actionEvent);
1311 };
1312
1313
1314 /**
1315  * Handles focus events on the component's key event target element.  If the
1316  * component is focusable, updates its state and styling to indicate that it
1317  * now has keyboard focus.  Considered protected; should only be used within
1318  * this package and by subclasses.  <b>Warning:</b> IE dispatches focus and
1319  * blur events asynchronously!
1320  * @param {goog.events.Event} e Focus event to handle.
1321  */
1322 goog.ui.Control.prototype.handleFocus = function(e) {
1323   if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {
1324     this.setFocused(true);
1325   }
1326 };
1327
1328
1329 /**
1330  * Handles blur events on the component's key event target element.  Always
1331  * deactivates the component.  In addition, if the component is focusable,
1332  * updates its state and styling to indicate that it no longer has keyboard
1333  * focus.  Considered protected; should only be used within this package and
1334  * by subclasses.  <b>Warning:</b> IE dispatches focus and blur events
1335  * asynchronously!
1336  * @param {goog.events.Event} e Blur event to handle.
1337  */
1338 goog.ui.Control.prototype.handleBlur = function(e) {
1339   if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1340     this.setActive(false);
1341   }
1342   if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {
1343     this.setFocused(false);
1344   }
1345 };
1346
1347
1348 /**
1349  * Attempts to handle a keyboard event, if the component is enabled and visible,
1350  * by calling {@link handleKeyEventInternal}.  Considered protected; should only
1351  * be used within this package and by subclasses.
1352  * @param {goog.events.KeyEvent} e Key event to handle.
1353  * @return {boolean} Whether the key event was handled.
1354  */
1355 goog.ui.Control.prototype.handleKeyEvent = function(e) {
1356   if (this.isVisible() && this.isEnabled() &&
1357       this.handleKeyEventInternal(e)) {
1358     e.preventDefault();
1359     e.stopPropagation();
1360     return true;
1361   }
1362   return false;
1363 };
1364
1365
1366 /**
1367  * Attempts to handle a keyboard event; returns true if the event was handled,
1368  * false otherwise.  Considered protected; should only be used within this
1369  * package and by subclasses.
1370  * @param {goog.events.KeyEvent} e Key event to handle.
1371  * @return {boolean} Whether the key event was handled.
1372  * @protected
1373  */
1374 goog.ui.Control.prototype.handleKeyEventInternal = function(e) {
1375   return e.keyCode == goog.events.KeyCodes.ENTER &&
1376       this.performActionInternal(e);
1377 };
1378
1379
1380 // Register the default renderer for goog.ui.Controls.
1381 goog.ui.registry.setDefaultRenderer(goog.ui.Control, goog.ui.ControlRenderer);
1382
1383
1384 // Register a decorator factory function for goog.ui.Controls.
1385 goog.ui.registry.setDecoratorByClassName(goog.ui.ControlRenderer.CSS_CLASS,
1386     function() {
1387       return new goog.ui.Control(null);
1388     });