Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/ui/control.js
4506 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview Base class for UI controls such as buttons, menus, menu items,
9
* toolbar buttons, etc. The implementation is based on a generalized version
10
* of {@link goog.ui.MenuItem}.
11
* TODO(attila): If the renderer framework works well, pull it into Component.
12
*
13
* @see ../demos/control.html
14
* @see http://code.google.com/p/closure-library/wiki/IntroToControls
15
*/
16
17
goog.provide('goog.ui.Control');
18
19
goog.require('goog.Disposable');
20
goog.require('goog.array');
21
goog.require('goog.dispose');
22
goog.require('goog.dom');
23
goog.require('goog.events.BrowserEvent');
24
goog.require('goog.events.Event');
25
goog.require('goog.events.EventHandler');
26
goog.require('goog.events.EventType');
27
goog.require('goog.events.KeyCodes');
28
goog.require('goog.events.KeyHandler');
29
goog.require('goog.functions');
30
goog.require('goog.string');
31
goog.require('goog.ui.Component');
32
goog.require('goog.ui.ComponentUtil');
33
/** @suppress {extraRequire} */
34
goog.require('goog.ui.ControlContent');
35
goog.require('goog.ui.ControlRenderer');
36
goog.require('goog.ui.registry');
37
goog.require('goog.userAgent');
38
goog.requireType('goog.a11y.aria.Role');
39
goog.requireType('goog.events.KeyEvent');
40
41
42
43
/**
44
* Base class for UI controls. Extends {@link goog.ui.Component} by adding
45
* the following:
46
* <ul>
47
* <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling,
48
* <li>a pluggable <em>renderer</em> framework, to simplify the creation of
49
* simple controls without the need to subclass this class,
50
* <li>the notion of component <em>content</em>, like a text caption or DOM
51
* structure displayed in the component (e.g. a button label),
52
* <li>getter and setter for component content, as well as a getter and
53
* setter specifically for caption text (for convenience),
54
* <li>support for hiding/showing the component,
55
<li>fine-grained control over supported states and state transition
56
events, and
57
* <li>default mouse and keyboard event handling.
58
* </ul>
59
* This class has sufficient built-in functionality for most simple UI controls.
60
* All controls dispatch SHOW, HIDE, ENTER, LEAVE, and ACTION events on show,
61
* hide, mouseover, mouseout, and user action, respectively. Additional states
62
* are also supported. See closure/demos/control.html
63
* for example usage.
64
* @param {goog.ui.ControlContent=} opt_content Text caption or DOM structure
65
* to display as the content of the control (if any).
66
* @param {goog.ui.ControlRenderer=} opt_renderer Renderer used to render or
67
* decorate the component; defaults to {@link goog.ui.ControlRenderer}.
68
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
69
* document interaction.
70
* @constructor
71
* @extends {goog.ui.Component}
72
*/
73
goog.ui.Control = function(opt_content, opt_renderer, opt_domHelper) {
74
'use strict';
75
goog.ui.Component.call(this, opt_domHelper);
76
this.renderer_ =
77
opt_renderer || goog.ui.registry.getDefaultRenderer(this.constructor);
78
this.setContentInternal(opt_content !== undefined ? opt_content : null);
79
80
/** @private {?string} The control's aria-label. */
81
this.ariaLabel_ = null;
82
83
/** @private {goog.ui.Control.IeMouseEventSequenceSimulator_} */
84
this.ieMouseEventSequenceSimulator_;
85
};
86
goog.inherits(goog.ui.Control, goog.ui.Component);
87
88
89
// Renderer registry.
90
// TODO(attila): Refactor existing usages inside Google in a follow-up CL.
91
92
93
/**
94
* Maps a CSS class name to a function that returns a new instance of
95
* {@link goog.ui.Control} or a subclass thereof, suitable to decorate
96
* an element that has the specified CSS class. UI components that extend
97
* {@link goog.ui.Control} and want {@link goog.ui.Container}s to be able
98
* to discover and decorate elements using them should register a factory
99
* function via this API.
100
* @param {string} className CSS class name.
101
* @param {Function} decoratorFunction Function that takes no arguments and
102
* returns a new instance of a control to decorate an element with the
103
* given class.
104
* @deprecated Use {@link goog.ui.registry.setDecoratorByClassName} instead.
105
*/
106
goog.ui.Control.registerDecorator = goog.ui.registry.setDecoratorByClassName;
107
108
109
/**
110
* Takes an element and returns a new instance of {@link goog.ui.Control}
111
* or a subclass, suitable to decorate it (based on the element's CSS class).
112
* @param {Element} element Element to decorate.
113
* @return {goog.ui.Control?} New control instance to decorate the element
114
* (null if none).
115
* @deprecated Use {@link goog.ui.registry.getDecorator} instead.
116
*/
117
goog.ui.Control.getDecorator =
118
/** @type {function(Element): goog.ui.Control} */ (
119
goog.ui.registry.getDecorator);
120
121
122
/**
123
* Renderer associated with the component.
124
* @type {goog.ui.ControlRenderer|undefined}
125
* @private
126
*/
127
goog.ui.Control.prototype.renderer_;
128
129
130
/**
131
* Text caption or DOM structure displayed in the component.
132
* @type {?goog.ui.ControlContent}
133
* @private
134
*/
135
goog.ui.Control.prototype.content_ = null;
136
137
138
/**
139
* Current component state; a bit mask of {@link goog.ui.Component.State}s.
140
* @type {number}
141
* @private
142
*/
143
goog.ui.Control.prototype.state_ = 0x00;
144
145
146
/**
147
* A bit mask of {@link goog.ui.Component.State}s this component supports.
148
* @type {number}
149
* @private
150
*/
151
goog.ui.Control.prototype.supportedStates_ = goog.ui.Component.State.DISABLED |
152
goog.ui.Component.State.HOVER | goog.ui.Component.State.ACTIVE |
153
goog.ui.Component.State.FOCUSED;
154
155
156
/**
157
* A bit mask of {@link goog.ui.Component.State}s for which this component
158
* provides default event handling. For example, a component that handles
159
* the HOVER state automatically will highlight itself on mouseover, whereas
160
* a component that doesn't handle HOVER automatically will only dispatch
161
* ENTER and LEAVE events but not call {@link setHighlighted} on itself.
162
* By default, components provide default event handling for all states.
163
* Controls hosted in containers (e.g. menu items in a menu, or buttons in a
164
* toolbar) will typically want to have their container manage their highlight
165
* state. Selectable controls managed by a selection model will also typically
166
* want their selection state to be managed by the model.
167
* @type {number}
168
* @private
169
*/
170
goog.ui.Control.prototype.autoStates_ = goog.ui.Component.State.ALL;
171
172
173
/**
174
* A bit mask of {@link goog.ui.Component.State}s for which this component
175
* dispatches state transition events. Because events are expensive, the
176
* default behavior is to not dispatch any state transition events at all.
177
* Use the {@link #setDispatchTransitionEvents} API to request transition
178
* events as needed. Subclasses may enable transition events by default.
179
* Controls hosted in containers or managed by a selection model will typically
180
* want to dispatch transition events.
181
* @type {number}
182
* @private
183
*/
184
goog.ui.Control.prototype.statesWithTransitionEvents_ = 0x00;
185
186
187
/**
188
* Component visibility.
189
* @type {boolean}
190
* @private
191
*/
192
goog.ui.Control.prototype.visible_ = true;
193
194
195
/**
196
* Keyboard event handler.
197
* @type {goog.events.KeyHandler}
198
* @private
199
*/
200
goog.ui.Control.prototype.keyHandler_;
201
202
203
/**
204
* Additional class name(s) to apply to the control's root element, if any.
205
* @type {Array<string>?}
206
* @private
207
*/
208
goog.ui.Control.prototype.extraClassNames_ = null;
209
210
211
/**
212
* Whether the control should listen for and handle mouse events; defaults to
213
* true.
214
* @type {boolean}
215
* @private
216
*/
217
goog.ui.Control.prototype.handleMouseEvents_ = true;
218
219
220
/**
221
* Whether the control allows text selection within its DOM. Defaults to false.
222
* @type {boolean}
223
* @private
224
*/
225
goog.ui.Control.prototype.allowTextSelection_ = false;
226
227
228
/**
229
* The control's preferred ARIA role.
230
* @type {?goog.a11y.aria.Role}
231
* @private
232
*/
233
goog.ui.Control.prototype.preferredAriaRole_ = null;
234
235
236
// Event handler and renderer management.
237
238
239
/**
240
* Returns true if the control is configured to handle its own mouse events,
241
* false otherwise. Controls not hosted in {@link goog.ui.Container}s have
242
* to handle their own mouse events, but controls hosted in containers may
243
* allow their parent to handle mouse events on their behalf. Considered
244
* protected; should only be used within this package and by subclasses.
245
* @return {boolean} Whether the control handles its own mouse events.
246
*/
247
goog.ui.Control.prototype.isHandleMouseEvents = function() {
248
'use strict';
249
return this.handleMouseEvents_;
250
};
251
252
253
/**
254
* Enables or disables mouse event handling for the control. Containers may
255
* use this method to disable mouse event handling in their child controls.
256
* Considered protected; should only be used within this package and by
257
* subclasses.
258
* @param {boolean} enable Whether to enable or disable mouse event handling.
259
*/
260
goog.ui.Control.prototype.setHandleMouseEvents = function(enable) {
261
'use strict';
262
if (this.isInDocument() && enable != this.handleMouseEvents_) {
263
// Already in the document; need to update event handler.
264
this.enableMouseEventHandling_(enable);
265
}
266
this.handleMouseEvents_ = enable;
267
};
268
269
270
/**
271
* Returns the DOM element on which the control is listening for keyboard
272
* events (null if none).
273
* @return {Element} Element on which the control is listening for key
274
* events.
275
*/
276
goog.ui.Control.prototype.getKeyEventTarget = function() {
277
'use strict';
278
// Delegate to renderer.
279
return this.renderer_.getKeyEventTarget(this);
280
};
281
282
283
/**
284
* Returns the keyboard event handler for this component, lazily created the
285
* first time this method is called. Considered protected; should only be
286
* used within this package and by subclasses.
287
* @return {!goog.events.KeyHandler} Keyboard event handler for this component.
288
* @protected
289
*/
290
goog.ui.Control.prototype.getKeyHandler = function() {
291
'use strict';
292
return this.keyHandler_ || (this.keyHandler_ = new goog.events.KeyHandler());
293
};
294
295
296
/**
297
* Returns the renderer used by this component to render itself or to decorate
298
* an existing element.
299
* @return {goog.ui.ControlRenderer|undefined} Renderer used by the component
300
* (undefined if none).
301
*/
302
goog.ui.Control.prototype.getRenderer = function() {
303
'use strict';
304
return this.renderer_;
305
};
306
307
308
/**
309
* Registers the given renderer with the component. Changing renderers after
310
* the component has entered the document is an error.
311
* @param {goog.ui.ControlRenderer} renderer Renderer used by the component.
312
* @throws {Error} If the control is already in the document.
313
*/
314
goog.ui.Control.prototype.setRenderer = function(renderer) {
315
'use strict';
316
if (this.isInDocument()) {
317
// Too late.
318
throw new Error(goog.ui.Component.Error.ALREADY_RENDERED);
319
}
320
321
if (this.getElement()) {
322
// The component has already been rendered, but isn't yet in the document.
323
// Replace the renderer and delete the current DOM, so it can be re-rendered
324
// using the new renderer the next time someone calls render().
325
this.setElementInternal(null);
326
}
327
328
this.renderer_ = renderer;
329
};
330
331
332
// Support for additional styling.
333
334
335
/**
336
* Returns any additional class name(s) to be applied to the component's
337
* root element, or null if no extra class names are needed.
338
* @return {Array<string>?} Additional class names to be applied to
339
* the component's root element (null if none).
340
*/
341
goog.ui.Control.prototype.getExtraClassNames = function() {
342
'use strict';
343
return this.extraClassNames_;
344
};
345
346
347
/**
348
* Adds the given class name to the list of classes to be applied to the
349
* component's root element.
350
* @param {string} className Additional class name to be applied to the
351
* component's root element.
352
*/
353
goog.ui.Control.prototype.addClassName = function(className) {
354
'use strict';
355
if (className) {
356
if (this.extraClassNames_) {
357
if (!goog.array.contains(this.extraClassNames_, className)) {
358
this.extraClassNames_.push(className);
359
}
360
} else {
361
this.extraClassNames_ = [className];
362
}
363
this.renderer_.enableExtraClassName(this, className, true);
364
}
365
};
366
367
368
/**
369
* Removes the given class name from the list of classes to be applied to
370
* the component's root element.
371
* @param {string} className Class name to be removed from the component's root
372
* element.
373
*/
374
goog.ui.Control.prototype.removeClassName = function(className) {
375
'use strict';
376
if (className && this.extraClassNames_ &&
377
goog.array.remove(this.extraClassNames_, className)) {
378
if (this.extraClassNames_.length == 0) {
379
this.extraClassNames_ = null;
380
}
381
this.renderer_.enableExtraClassName(this, className, false);
382
}
383
};
384
385
386
/**
387
* Adds or removes the given class name to/from the list of classes to be
388
* applied to the component's root element.
389
* @param {string} className CSS class name to add or remove.
390
* @param {boolean} enable Whether to add or remove the class name.
391
*/
392
goog.ui.Control.prototype.enableClassName = function(className, enable) {
393
'use strict';
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
'use strict';
412
var element = this.renderer_.createDom(this);
413
this.setElementInternal(element);
414
415
// Initialize ARIA role.
416
this.renderer_.setAriaRole(element, this.getPreferredAriaRole());
417
418
// Initialize text selection.
419
if (!this.isAllowTextSelection()) {
420
// The renderer is assumed to create selectable elements. Since making
421
// elements unselectable is expensive, only do it if needed (bug 1037090).
422
this.renderer_.setAllowTextSelection(element, false);
423
}
424
425
// Initialize visibility.
426
if (!this.isVisible()) {
427
// The renderer is assumed to create visible elements. Since hiding
428
// elements can be expensive, only do it if needed (bug 1037105).
429
this.renderer_.setVisible(element, false);
430
}
431
};
432
433
434
/**
435
* Returns the control's preferred ARIA role. This can be used by a control to
436
* override the role that would be assigned by the renderer. This is useful in
437
* cases where a different ARIA role is appropriate for a control because of the
438
* context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a
439
* {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.
440
* @return {?goog.a11y.aria.Role} This control's preferred ARIA role or null if
441
* no preferred ARIA role is set.
442
*/
443
goog.ui.Control.prototype.getPreferredAriaRole = function() {
444
'use strict';
445
return this.preferredAriaRole_;
446
};
447
448
449
/**
450
* Sets the control's preferred ARIA role. This can be used to override the role
451
* that would be assigned by the renderer. This is useful in cases where a
452
* different ARIA role is appropriate for a control because of the
453
* context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a
454
* {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM.
455
* @param {goog.a11y.aria.Role} role This control's preferred ARIA role.
456
*/
457
goog.ui.Control.prototype.setPreferredAriaRole = function(role) {
458
'use strict';
459
this.preferredAriaRole_ = role;
460
};
461
462
463
/**
464
* Gets the control's aria label.
465
* @return {?string} This control's aria label.
466
*/
467
goog.ui.Control.prototype.getAriaLabel = function() {
468
'use strict';
469
return this.ariaLabel_;
470
};
471
472
473
/**
474
* Sets the control's aria label. This can be used to assign aria label to the
475
* element after it is rendered.
476
* @param {string} label The string to set as the aria label for this control.
477
* No escaping is done on this value.
478
*/
479
goog.ui.Control.prototype.setAriaLabel = function(label) {
480
'use strict';
481
this.ariaLabel_ = label;
482
var element = this.getElement();
483
if (element) {
484
this.renderer_.setAriaLabel(element, label);
485
}
486
};
487
488
489
/**
490
* Returns the DOM element into which child components are to be rendered,
491
* or null if the control itself hasn't been rendered yet. Overrides
492
* {@link goog.ui.Component#getContentElement} by delegating to the renderer.
493
* @return {Element} Element to contain child elements (null if none).
494
* @override
495
*/
496
goog.ui.Control.prototype.getContentElement = function() {
497
'use strict';
498
// Delegate to renderer.
499
return this.renderer_.getContentElement(this.getElement());
500
};
501
502
503
/**
504
* Returns true if the given element can be decorated by this component.
505
* Overrides {@link goog.ui.Component#canDecorate}.
506
* @param {Element} element Element to decorate.
507
* @return {boolean} Whether the element can be decorated by this component.
508
* @override
509
*/
510
goog.ui.Control.prototype.canDecorate = function(element) {
511
'use strict';
512
// Controls support pluggable renderers; delegate to the renderer.
513
return this.renderer_.canDecorate(element);
514
};
515
516
517
/**
518
* Decorates the given element with this component. Overrides {@link
519
* goog.ui.Component#decorateInternal} by delegating DOM manipulation
520
* to the control's renderer.
521
* @param {Element} element Element to decorate.
522
* @protected
523
* @override
524
*/
525
goog.ui.Control.prototype.decorateInternal = function(element) {
526
'use strict';
527
element = this.renderer_.decorate(this, element);
528
this.setElementInternal(element);
529
530
// Initialize ARIA role.
531
this.renderer_.setAriaRole(element, this.getPreferredAriaRole());
532
533
// Initialize text selection.
534
if (!this.isAllowTextSelection()) {
535
// Decorated elements are assumed to be selectable. Since making elements
536
// unselectable is expensive, only do it if needed (bug 1037090).
537
this.renderer_.setAllowTextSelection(element, false);
538
}
539
540
// Initialize visibility based on the decorated element's styling.
541
this.visible_ = element.style.display != 'none';
542
};
543
544
545
/**
546
* Configures the component after its DOM has been rendered, and sets up event
547
* handling. Overrides {@link goog.ui.Component#enterDocument}.
548
* @override
549
*/
550
goog.ui.Control.prototype.enterDocument = function() {
551
'use strict';
552
goog.ui.Control.superClass_.enterDocument.call(this);
553
554
// Call the renderer's setAriaStates method to set element's aria attributes.
555
this.renderer_.setAriaStates(this, this.getElementStrict());
556
557
// Call the renderer's initializeDom method to configure properties of the
558
// control's DOM that can only be done once it's in the document.
559
this.renderer_.initializeDom(this);
560
561
// Initialize event handling if at least one state other than DISABLED is
562
// supported.
563
if (this.supportedStates_ & ~goog.ui.Component.State.DISABLED) {
564
// Initialize mouse event handling if the control is configured to handle
565
// its own mouse events. (Controls hosted in containers don't need to
566
// handle their own mouse events.)
567
if (this.isHandleMouseEvents()) {
568
this.enableMouseEventHandling_(true);
569
}
570
571
// Initialize keyboard event handling if the control is focusable and has
572
// a key event target. (Controls hosted in containers typically aren't
573
// focusable, allowing their container to handle keyboard events for them.)
574
if (this.isSupportedState(goog.ui.Component.State.FOCUSED)) {
575
var keyTarget = this.getKeyEventTarget();
576
if (keyTarget) {
577
var keyHandler = this.getKeyHandler();
578
keyHandler.attach(keyTarget);
579
this.getHandler()
580
.listen(
581
keyHandler, goog.events.KeyHandler.EventType.KEY,
582
this.handleKeyEvent)
583
.listen(keyTarget, goog.events.EventType.FOCUS, this.handleFocus)
584
.listen(keyTarget, goog.events.EventType.BLUR, this.handleBlur);
585
}
586
}
587
}
588
};
589
590
591
/**
592
* Enables or disables mouse event handling on the control.
593
* @param {boolean} enable Whether to enable mouse event handling.
594
* @private
595
*/
596
goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) {
597
'use strict';
598
var MouseEventType = goog.ui.ComponentUtil.getMouseEventType(this);
599
600
var handler = this.getHandler();
601
var element = this.getElement();
602
if (enable) {
603
handler.listen(element, MouseEventType.MOUSEDOWN, this.handleMouseDown)
604
.listen(
605
element, [MouseEventType.MOUSEUP, MouseEventType.MOUSECANCEL],
606
this.handleMouseUp)
607
.listen(element, goog.events.EventType.MOUSEOVER, this.handleMouseOver)
608
.listen(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);
609
if (this.pointerEventsEnabled()) {
610
// Prevent pointer events from capturing the target element so they behave
611
// more like mouse events.
612
handler.listen(
613
element, goog.events.EventType.GOTPOINTERCAPTURE,
614
this.preventPointerCapture_);
615
}
616
if (this.handleContextMenu != goog.functions.UNDEFINED) {
617
handler.listen(
618
element, goog.events.EventType.CONTEXTMENU, this.handleContextMenu);
619
}
620
if (goog.userAgent.IE && !this.ieMouseEventSequenceSimulator_) {
621
this.ieMouseEventSequenceSimulator_ =
622
new goog.ui.Control.IeMouseEventSequenceSimulator_(this);
623
this.registerDisposable(this.ieMouseEventSequenceSimulator_);
624
}
625
} else {
626
handler.unlisten(element, MouseEventType.MOUSEDOWN, this.handleMouseDown)
627
.unlisten(
628
element, [MouseEventType.MOUSEUP, MouseEventType.MOUSECANCEL],
629
this.handleMouseUp)
630
.unlisten(
631
element, goog.events.EventType.MOUSEOVER, this.handleMouseOver)
632
.unlisten(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut);
633
if (this.pointerEventsEnabled()) {
634
handler.unlisten(
635
element, goog.events.EventType.GOTPOINTERCAPTURE,
636
this.preventPointerCapture_);
637
}
638
if (this.handleContextMenu != goog.functions.UNDEFINED) {
639
handler.unlisten(
640
element, goog.events.EventType.CONTEXTMENU, this.handleContextMenu);
641
}
642
if (goog.userAgent.IE) {
643
goog.dispose(this.ieMouseEventSequenceSimulator_);
644
this.ieMouseEventSequenceSimulator_ = null;
645
}
646
}
647
};
648
649
650
/**
651
* Cleans up the component before its DOM is removed from the document, and
652
* removes event handlers. Overrides {@link goog.ui.Component#exitDocument}
653
* by making sure that components that are removed from the document aren't
654
* focusable (i.e. have no tab index).
655
* @override
656
*/
657
goog.ui.Control.prototype.exitDocument = function() {
658
'use strict';
659
goog.ui.Control.superClass_.exitDocument.call(this);
660
if (this.keyHandler_) {
661
this.keyHandler_.detach();
662
}
663
if (this.isVisible() && this.isEnabled()) {
664
this.renderer_.setFocusable(this, false);
665
}
666
};
667
668
669
/** @override */
670
goog.ui.Control.prototype.disposeInternal = function() {
671
'use strict';
672
goog.ui.Control.superClass_.disposeInternal.call(this);
673
if (this.keyHandler_) {
674
this.keyHandler_.dispose();
675
delete this.keyHandler_;
676
}
677
delete this.renderer_;
678
this.content_ = null;
679
this.extraClassNames_ = null;
680
this.ieMouseEventSequenceSimulator_ = null;
681
};
682
683
684
// Component content management.
685
686
687
/**
688
* Returns the text caption or DOM structure displayed in the component.
689
* @return {goog.ui.ControlContent} Text caption or DOM structure
690
* comprising the component's contents.
691
*/
692
goog.ui.Control.prototype.getContent = function() {
693
'use strict';
694
return this.content_;
695
};
696
697
698
/**
699
* Sets the component's content to the given text caption, element, or array of
700
* nodes. (If the argument is an array of nodes, it must be an actual array,
701
* not an array-like object.)
702
* @param {goog.ui.ControlContent} content Text caption or DOM
703
* structure to set as the component's contents.
704
*/
705
goog.ui.Control.prototype.setContent = function(content) {
706
'use strict';
707
// Controls support pluggable renderers; delegate to the renderer.
708
this.renderer_.setContent(this.getElement(), content);
709
710
// setContentInternal needs to be after the renderer, since the implementation
711
// may depend on the content being in the DOM.
712
this.setContentInternal(content);
713
};
714
715
716
/**
717
* Sets the component's content to the given text caption, element, or array
718
* of nodes. Unlike {@link #setContent}, doesn't modify the component's DOM.
719
* Called by renderers during element decoration.
720
*
721
* This should only be used by subclasses and its associated renderers.
722
*
723
* @param {goog.ui.ControlContent} content Text caption or DOM structure
724
* to set as the component's contents.
725
*/
726
goog.ui.Control.prototype.setContentInternal = function(content) {
727
'use strict';
728
this.content_ = content;
729
};
730
731
732
/**
733
* @return {string} Text caption of the control or empty string if none.
734
*/
735
goog.ui.Control.prototype.getCaption = function() {
736
'use strict';
737
var content = this.getContent();
738
if (!content) {
739
return '';
740
}
741
var caption = (typeof content === 'string') ?
742
content :
743
Array.isArray(content) ?
744
content.map(goog.dom.getRawTextContent).join('') :
745
goog.dom.getTextContent(/** @type {!Node} */ (content));
746
return goog.string.collapseBreakingSpaces(caption);
747
};
748
749
750
/**
751
* Sets the text caption of the component.
752
* @param {string} caption Text caption of the component.
753
*/
754
goog.ui.Control.prototype.setCaption = function(caption) {
755
'use strict';
756
this.setContent(caption);
757
};
758
759
760
// Component state management.
761
762
763
/** @override */
764
goog.ui.Control.prototype.setRightToLeft = function(rightToLeft) {
765
'use strict';
766
// The superclass implementation ensures the control isn't in the document.
767
goog.ui.Control.superClass_.setRightToLeft.call(this, rightToLeft);
768
769
var element = this.getElement();
770
if (element) {
771
this.renderer_.setRightToLeft(element, rightToLeft);
772
}
773
};
774
775
776
/**
777
* Returns true if the control allows text selection within its DOM, false
778
* otherwise. Controls that disallow text selection have the appropriate
779
* unselectable styling applied to their elements. Note that controls hosted
780
* in containers will report that they allow text selection even if their
781
* container disallows text selection.
782
* @return {boolean} Whether the control allows text selection.
783
*/
784
goog.ui.Control.prototype.isAllowTextSelection = function() {
785
'use strict';
786
return this.allowTextSelection_;
787
};
788
789
790
/**
791
* Allows or disallows text selection within the control's DOM.
792
* @param {boolean} allow Whether the control should allow text selection.
793
*/
794
goog.ui.Control.prototype.setAllowTextSelection = function(allow) {
795
'use strict';
796
this.allowTextSelection_ = allow;
797
798
var element = this.getElement();
799
if (element) {
800
this.renderer_.setAllowTextSelection(element, allow);
801
}
802
};
803
804
805
/**
806
* Returns true if the component's visibility is set to visible, false if
807
* it is set to hidden. A component that is set to hidden is guaranteed
808
* to be hidden from the user, but the reverse isn't necessarily true.
809
* A component may be set to visible but can otherwise be obscured by another
810
* element, rendered off-screen, or hidden using direct CSS manipulation.
811
* @return {boolean} Whether the component is visible.
812
*/
813
goog.ui.Control.prototype.isVisible = function() {
814
'use strict';
815
return this.visible_;
816
};
817
818
819
/**
820
* Shows or hides the component. Does nothing if the component already has
821
* the requested visibility. Otherwise, dispatches a SHOW or HIDE event as
822
* appropriate, giving listeners a chance to prevent the visibility change.
823
* When showing a component that is both enabled and focusable, ensures that
824
* its key target has a tab index. When hiding a component that is enabled
825
* and focusable, blurs its key target and removes its tab index.
826
* @param {boolean} visible Whether to show or hide the component.
827
* @param {boolean=} opt_force If true, doesn't check whether the component
828
* already has the requested visibility, and doesn't dispatch any events.
829
* @return {boolean} Whether the visibility was changed.
830
*/
831
goog.ui.Control.prototype.setVisible = function(visible, opt_force) {
832
'use strict';
833
if (opt_force ||
834
(this.visible_ != visible &&
835
this.dispatchEvent(
836
visible ? goog.ui.Component.EventType.SHOW :
837
goog.ui.Component.EventType.HIDE))) {
838
var element = this.getElement();
839
if (element) {
840
this.renderer_.setVisible(element, visible);
841
}
842
if (this.isEnabled()) {
843
this.renderer_.setFocusable(this, visible);
844
}
845
this.visible_ = visible;
846
return true;
847
}
848
return false;
849
};
850
851
852
/**
853
* Returns true if the component is enabled, false otherwise.
854
* @return {boolean} Whether the component is enabled.
855
*/
856
goog.ui.Control.prototype.isEnabled = function() {
857
'use strict';
858
return !this.hasState(goog.ui.Component.State.DISABLED);
859
};
860
861
862
/**
863
* Returns true if the control has a parent that is itself disabled, false
864
* otherwise.
865
* @return {boolean} Whether the component is hosted in a disabled container.
866
* @private
867
*/
868
goog.ui.Control.prototype.isParentDisabled_ = function() {
869
'use strict';
870
var parent = this.getParent();
871
return !!parent && typeof parent.isEnabled == 'function' &&
872
!parent.isEnabled();
873
};
874
875
876
/**
877
* Enables or disables the component. Does nothing if this state transition
878
* is disallowed. If the component is both visible and focusable, updates its
879
* focused state and tab index as needed. If the component is being disabled,
880
* ensures that it is also deactivated and un-highlighted first. Note that the
881
* component's enabled/disabled state is "locked" as long as it is hosted in a
882
* {@link goog.ui.Container} that is itself disabled; this is to prevent clients
883
* from accidentally re-enabling a control that is in a disabled container.
884
* @param {boolean} enable Whether to enable or disable the component.
885
* @see #isTransitionAllowed
886
*/
887
goog.ui.Control.prototype.setEnabled = function(enable) {
888
'use strict';
889
if (!this.isParentDisabled_() &&
890
this.isTransitionAllowed(goog.ui.Component.State.DISABLED, !enable)) {
891
if (!enable) {
892
this.setActive(false);
893
this.setHighlighted(false);
894
}
895
if (this.isVisible()) {
896
this.renderer_.setFocusable(this, enable);
897
}
898
this.setState(goog.ui.Component.State.DISABLED, !enable, true);
899
}
900
};
901
902
903
/**
904
* Returns true if the component is currently highlighted, false otherwise.
905
* @return {boolean} Whether the component is highlighted.
906
*/
907
goog.ui.Control.prototype.isHighlighted = function() {
908
'use strict';
909
return this.hasState(goog.ui.Component.State.HOVER);
910
};
911
912
913
/**
914
* Highlights or unhighlights the component. Does nothing if this state
915
* transition is disallowed.
916
* @param {boolean} highlight Whether to highlight or unhighlight the component.
917
* @see #isTransitionAllowed
918
*/
919
goog.ui.Control.prototype.setHighlighted = function(highlight) {
920
'use strict';
921
if (this.isTransitionAllowed(goog.ui.Component.State.HOVER, highlight)) {
922
this.setState(goog.ui.Component.State.HOVER, highlight);
923
}
924
};
925
926
927
/**
928
* Returns true if the component is active (pressed), false otherwise.
929
* @return {boolean} Whether the component is active.
930
*/
931
goog.ui.Control.prototype.isActive = function() {
932
'use strict';
933
return this.hasState(goog.ui.Component.State.ACTIVE);
934
};
935
936
937
/**
938
* Activates or deactivates the component. Does nothing if this state
939
* transition is disallowed.
940
* @param {boolean} active Whether to activate or deactivate the component.
941
* @see #isTransitionAllowed
942
*/
943
goog.ui.Control.prototype.setActive = function(active) {
944
'use strict';
945
if (this.isTransitionAllowed(goog.ui.Component.State.ACTIVE, active)) {
946
this.setState(goog.ui.Component.State.ACTIVE, active);
947
}
948
};
949
950
951
/**
952
* Returns true if the component is selected, false otherwise.
953
* @return {boolean} Whether the component is selected.
954
*/
955
goog.ui.Control.prototype.isSelected = function() {
956
'use strict';
957
return this.hasState(goog.ui.Component.State.SELECTED);
958
};
959
960
961
/**
962
* Selects or unselects the component. Does nothing if this state transition
963
* is disallowed.
964
* @param {boolean} select Whether to select or unselect the component.
965
* @see #isTransitionAllowed
966
*/
967
goog.ui.Control.prototype.setSelected = function(select) {
968
'use strict';
969
if (this.isTransitionAllowed(goog.ui.Component.State.SELECTED, select)) {
970
this.setState(goog.ui.Component.State.SELECTED, select);
971
}
972
};
973
974
975
/**
976
* Returns true if the component is checked, false otherwise.
977
* @return {boolean} Whether the component is checked.
978
*/
979
goog.ui.Control.prototype.isChecked = function() {
980
'use strict';
981
return this.hasState(goog.ui.Component.State.CHECKED);
982
};
983
984
985
/**
986
* Checks or unchecks the component. Does nothing if this state transition
987
* is disallowed.
988
* @param {boolean} check Whether to check or uncheck the component.
989
* @see #isTransitionAllowed
990
*/
991
goog.ui.Control.prototype.setChecked = function(check) {
992
'use strict';
993
if (this.isTransitionAllowed(goog.ui.Component.State.CHECKED, check)) {
994
this.setState(goog.ui.Component.State.CHECKED, check);
995
}
996
};
997
998
999
/**
1000
* Returns true if the component is styled to indicate that it has keyboard
1001
* focus, false otherwise. Note that `isFocused()` returning true
1002
* doesn't guarantee that the component's key event target has keyboard focus,
1003
* only that it is styled as such.
1004
* @return {boolean} Whether the component is styled to indicate as having
1005
* keyboard focus.
1006
*/
1007
goog.ui.Control.prototype.isFocused = function() {
1008
'use strict';
1009
return this.hasState(goog.ui.Component.State.FOCUSED);
1010
};
1011
1012
1013
/**
1014
* Applies or removes styling indicating that the component has keyboard focus.
1015
* Note that unlike the other "set" methods, this method is called as a result
1016
* of the component's element having received or lost keyboard focus, not the
1017
* other way around, so calling `setFocused(true)` doesn't guarantee that
1018
* the component's key event target has keyboard focus, only that it is styled
1019
* as such.
1020
* @param {boolean} focused Whether to apply or remove styling to indicate that
1021
* the component's element has keyboard focus.
1022
*/
1023
goog.ui.Control.prototype.setFocused = function(focused) {
1024
'use strict';
1025
if (this.isTransitionAllowed(goog.ui.Component.State.FOCUSED, focused)) {
1026
this.setState(goog.ui.Component.State.FOCUSED, focused);
1027
}
1028
};
1029
1030
1031
/**
1032
* Returns true if the component is open (expanded), false otherwise.
1033
* @return {boolean} Whether the component is open.
1034
*/
1035
goog.ui.Control.prototype.isOpen = function() {
1036
'use strict';
1037
return this.hasState(goog.ui.Component.State.OPENED);
1038
};
1039
1040
1041
/**
1042
* Opens (expands) or closes (collapses) the component. Does nothing if this
1043
* state transition is disallowed.
1044
* @param {boolean} open Whether to open or close the component.
1045
* @see #isTransitionAllowed
1046
*/
1047
goog.ui.Control.prototype.setOpen = function(open) {
1048
'use strict';
1049
if (this.isTransitionAllowed(goog.ui.Component.State.OPENED, open)) {
1050
this.setState(goog.ui.Component.State.OPENED, open);
1051
}
1052
};
1053
1054
1055
/**
1056
* Returns the component's state as a bit mask of {@link
1057
* goog.ui.Component.State}s.
1058
* @return {number} Bit mask representing component state.
1059
*/
1060
goog.ui.Control.prototype.getState = function() {
1061
'use strict';
1062
return this.state_;
1063
};
1064
1065
1066
/**
1067
* Returns true if the component is in the specified state, false otherwise.
1068
* @param {goog.ui.Component.State} state State to check.
1069
* @return {boolean} Whether the component is in the given state.
1070
*/
1071
goog.ui.Control.prototype.hasState = function(state) {
1072
'use strict';
1073
return !!(this.state_ & state);
1074
};
1075
1076
1077
/**
1078
* Sets or clears the given state on the component, and updates its styling
1079
* accordingly. Does nothing if the component is already in the correct state
1080
* or if it doesn't support the specified state. Doesn't dispatch any state
1081
* transition events; use advisedly.
1082
* @param {goog.ui.Component.State} state State to set or clear.
1083
* @param {boolean} enable Whether to set or clear the state (if supported).
1084
* @param {boolean=} opt_calledFrom Prevents looping with setEnabled.
1085
*/
1086
goog.ui.Control.prototype.setState = function(state, enable, opt_calledFrom) {
1087
'use strict';
1088
if (!opt_calledFrom && state == goog.ui.Component.State.DISABLED) {
1089
this.setEnabled(!enable);
1090
return;
1091
}
1092
if (this.isSupportedState(state) && enable != this.hasState(state)) {
1093
// Delegate actual styling to the renderer, since it is DOM-specific.
1094
this.renderer_.setState(this, state, enable);
1095
this.state_ = enable ? this.state_ | state : this.state_ & ~state;
1096
}
1097
};
1098
1099
1100
/**
1101
* Sets the component's state to the state represented by a bit mask of
1102
* {@link goog.ui.Component.State}s. Unlike {@link #setState}, doesn't
1103
* update the component's styling, and doesn't reject unsupported states.
1104
* Called by renderers during element decoration. Considered protected;
1105
* should only be used within this package and by subclasses.
1106
*
1107
* This should only be used by subclasses and its associated renderers.
1108
*
1109
* @param {number} state Bit mask representing component state.
1110
*/
1111
goog.ui.Control.prototype.setStateInternal = function(state) {
1112
'use strict';
1113
this.state_ = state;
1114
};
1115
1116
1117
/**
1118
* Returns true if the component supports the specified state, false otherwise.
1119
* @param {goog.ui.Component.State} state State to check.
1120
* @return {boolean} Whether the component supports the given state.
1121
*/
1122
goog.ui.Control.prototype.isSupportedState = function(state) {
1123
'use strict';
1124
return !!(this.supportedStates_ & state);
1125
};
1126
1127
1128
/**
1129
* Enables or disables support for the given state. Disabling support
1130
* for a state while the component is in that state is an error.
1131
* @param {goog.ui.Component.State} state State to support or de-support.
1132
* @param {boolean} support Whether the component should support the state.
1133
* @throws {Error} If disabling support for a state the control is currently in.
1134
*/
1135
goog.ui.Control.prototype.setSupportedState = function(state, support) {
1136
'use strict';
1137
if (this.isInDocument() && this.hasState(state) && !support) {
1138
// Since we hook up event handlers in enterDocument(), this is an error.
1139
throw new Error(goog.ui.Component.Error.ALREADY_RENDERED);
1140
}
1141
1142
if (!support && this.hasState(state)) {
1143
// We are removing support for a state that the component is currently in.
1144
this.setState(state, false);
1145
}
1146
1147
this.supportedStates_ =
1148
support ? this.supportedStates_ | state : this.supportedStates_ & ~state;
1149
};
1150
1151
1152
/**
1153
* Returns true if the component provides default event handling for the state,
1154
* false otherwise.
1155
* @param {goog.ui.Component.State} state State to check.
1156
* @return {boolean} Whether the component provides default event handling for
1157
* the state.
1158
*/
1159
goog.ui.Control.prototype.isAutoState = function(state) {
1160
'use strict';
1161
return !!(this.autoStates_ & state) && this.isSupportedState(state);
1162
};
1163
1164
1165
/**
1166
* Enables or disables automatic event handling for the given state(s).
1167
* @param {number} states Bit mask of {@link goog.ui.Component.State}s for which
1168
* default event handling is to be enabled or disabled.
1169
* @param {boolean} enable Whether the component should provide default event
1170
* handling for the state(s).
1171
*/
1172
goog.ui.Control.prototype.setAutoStates = function(states, enable) {
1173
'use strict';
1174
this.autoStates_ =
1175
enable ? this.autoStates_ | states : this.autoStates_ & ~states;
1176
};
1177
1178
1179
/**
1180
* Returns true if the component is set to dispatch transition events for the
1181
* given state, false otherwise.
1182
* @param {goog.ui.Component.State} state State to check.
1183
* @return {boolean} Whether the component dispatches transition events for
1184
* the state.
1185
*/
1186
goog.ui.Control.prototype.isDispatchTransitionEvents = function(state) {
1187
'use strict';
1188
return !!(this.statesWithTransitionEvents_ & state) &&
1189
this.isSupportedState(state);
1190
};
1191
1192
1193
/**
1194
* Enables or disables transition events for the given state(s). Controls
1195
* handle state transitions internally by default, and only dispatch state
1196
* transition events if explicitly requested to do so by calling this method.
1197
* @param {number} states Bit mask of {@link goog.ui.Component.State}s for
1198
* which transition events should be enabled or disabled.
1199
* @param {boolean} enable Whether transition events should be enabled.
1200
*/
1201
goog.ui.Control.prototype.setDispatchTransitionEvents = function(
1202
states, enable) {
1203
'use strict';
1204
this.statesWithTransitionEvents_ = enable ?
1205
this.statesWithTransitionEvents_ | states :
1206
this.statesWithTransitionEvents_ & ~states;
1207
};
1208
1209
1210
/**
1211
* Returns true if the transition into or out of the given state is allowed to
1212
* proceed, false otherwise. A state transition is allowed under the following
1213
* conditions:
1214
* <ul>
1215
* <li>the component supports the state,
1216
* <li>the component isn't already in the target state,
1217
* <li>either the component is configured not to dispatch events for this
1218
* state transition, or a transition event was dispatched and wasn't
1219
* canceled by any event listener, and
1220
* <li>the component hasn't been disposed of
1221
* </ul>
1222
* Considered protected; should only be used within this package and by
1223
* subclasses.
1224
* @param {goog.ui.Component.State} state State to/from which the control is
1225
* transitioning.
1226
* @param {boolean} enable Whether the control is entering or leaving the state.
1227
* @return {boolean} Whether the state transition is allowed to proceed.
1228
* @protected
1229
*/
1230
goog.ui.Control.prototype.isTransitionAllowed = function(state, enable) {
1231
'use strict';
1232
return this.isSupportedState(state) && this.hasState(state) != enable &&
1233
(!(this.statesWithTransitionEvents_ & state) ||
1234
this.dispatchEvent(
1235
goog.ui.Component.getStateTransitionEvent(state, enable))) &&
1236
!this.isDisposed();
1237
};
1238
1239
1240
// Default event handlers, to be overridden in subclasses.
1241
1242
1243
/**
1244
* Handles mouseover events. Dispatches an ENTER event; if the event isn't
1245
* canceled, the component is enabled, and it supports auto-highlighting,
1246
* highlights the component. Considered protected; should only be used
1247
* within this package and by subclasses.
1248
* @param {goog.events.BrowserEvent} e Mouse event to handle.
1249
*/
1250
goog.ui.Control.prototype.handleMouseOver = function(e) {
1251
'use strict';
1252
// Ignore mouse moves between descendants.
1253
if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&
1254
this.dispatchEvent(goog.ui.Component.EventType.ENTER) &&
1255
this.isEnabled() && this.isAutoState(goog.ui.Component.State.HOVER)) {
1256
this.setHighlighted(true);
1257
}
1258
};
1259
1260
1261
/**
1262
* Handles mouseout events. Dispatches a LEAVE event; if the event isn't
1263
* canceled, and the component supports auto-highlighting, deactivates and
1264
* un-highlights the component. Considered protected; should only be used
1265
* within this package and by subclasses.
1266
* @param {goog.events.BrowserEvent} e Mouse event to handle.
1267
*/
1268
goog.ui.Control.prototype.handleMouseOut = function(e) {
1269
'use strict';
1270
if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) &&
1271
this.dispatchEvent(goog.ui.Component.EventType.LEAVE)) {
1272
if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1273
// Deactivate on mouseout; otherwise we lose track of the mouse button.
1274
this.setActive(false);
1275
}
1276
if (this.isAutoState(goog.ui.Component.State.HOVER)) {
1277
this.setHighlighted(false);
1278
}
1279
}
1280
};
1281
1282
1283
/**
1284
* @param {!goog.events.BrowserEvent} e Event to handle.
1285
* @private
1286
*/
1287
goog.ui.Control.prototype.preventPointerCapture_ = function(e) {
1288
'use strict';
1289
var elem = /** @type {!Element} */ (e.target);
1290
if (!!elem.releasePointerCapture) {
1291
elem.releasePointerCapture(e.pointerId);
1292
}
1293
};
1294
1295
1296
/**
1297
* Handles contextmenu events.
1298
* @param {goog.events.BrowserEvent} e Event to handle.
1299
*/
1300
goog.ui.Control.prototype.handleContextMenu = goog.functions.UNDEFINED;
1301
1302
1303
/**
1304
* Checks if a mouse event (mouseover or mouseout) occurred below an element.
1305
* @param {goog.events.BrowserEvent} e Mouse event (should be mouseover or
1306
* mouseout).
1307
* @param {Element} elem The ancestor element.
1308
* @return {boolean} Whether the event has a relatedTarget (the element the
1309
* mouse is coming from) and it's a descendant of elem.
1310
* @private
1311
*/
1312
goog.ui.Control.isMouseEventWithinElement_ = function(e, elem) {
1313
'use strict';
1314
// If relatedTarget is null, it means there was no previous element (e.g.
1315
// the mouse moved out of the window). Assume this means that the mouse
1316
// event was not within the element.
1317
return !!e.relatedTarget && goog.dom.contains(elem, e.relatedTarget);
1318
};
1319
1320
1321
/**
1322
* Handles mousedown events. If the component is enabled, highlights and
1323
* activates it. If the component isn't configured for keyboard access,
1324
* prevents it from receiving keyboard focus. Considered protected; should
1325
* only be used within this package and by subclasses.
1326
* @param {goog.events.Event} e Mouse event to handle.
1327
* @suppress {strictMissingProperties} Added to tighten compiler checks
1328
*/
1329
goog.ui.Control.prototype.handleMouseDown = function(e) {
1330
'use strict';
1331
if (this.isEnabled()) {
1332
// Highlight enabled control on mousedown, regardless of the mouse button.
1333
if (this.isAutoState(goog.ui.Component.State.HOVER)) {
1334
this.setHighlighted(true);
1335
}
1336
1337
// For the left button only, activate the control, and focus its key event
1338
// target (if supported).
1339
if (e.isMouseActionButton()) {
1340
if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1341
this.setActive(true);
1342
}
1343
if (this.renderer_ && this.renderer_.isFocusable(this)) {
1344
this.getKeyEventTarget().focus();
1345
}
1346
}
1347
}
1348
1349
// Cancel the default action unless the control allows text selection.
1350
if (!this.isAllowTextSelection() && e.isMouseActionButton()) {
1351
e.preventDefault();
1352
}
1353
};
1354
1355
1356
/**
1357
* Handles mouseup events. If the component is enabled, highlights it. If
1358
* the component has previously been activated, performs its associated action
1359
* by calling {@link performActionInternal}, then deactivates it. Considered
1360
* protected; should only be used within this package and by subclasses.
1361
* @param {goog.events.Event} e Mouse event to handle.
1362
*/
1363
goog.ui.Control.prototype.handleMouseUp = function(e) {
1364
'use strict';
1365
if (this.isEnabled()) {
1366
if (this.isAutoState(goog.ui.Component.State.HOVER)) {
1367
this.setHighlighted(true);
1368
}
1369
if (this.isActive() && this.performActionInternal(e) &&
1370
this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1371
this.setActive(false);
1372
}
1373
}
1374
};
1375
1376
1377
/**
1378
* Handles dblclick events. Should only be registered if the user agent is
1379
* IE. If the component is enabled, performs its associated action by calling
1380
* {@link performActionInternal}. This is used to allow more performant
1381
* buttons in IE. In IE, no mousedown event is fired when that mousedown will
1382
* trigger a dblclick event. Because of this, a user clicking quickly will
1383
* only cause ACTION events to fire on every other click. This is a workaround
1384
* to generate ACTION events for every click. Unfortunately, this workaround
1385
* won't ever trigger the ACTIVE state. This is roughly the same behaviour as
1386
* if this were a 'button' element with a listener on mouseup. Considered
1387
* protected; should only be used within this package and by subclasses.
1388
* @param {goog.events.Event} e Mouse event to handle.
1389
*/
1390
goog.ui.Control.prototype.handleDblClick = function(e) {
1391
'use strict';
1392
if (this.isEnabled()) {
1393
this.performActionInternal(e);
1394
}
1395
};
1396
1397
1398
/**
1399
* Performs the appropriate action when the control is activated by the user.
1400
* The default implementation first updates the checked and selected state of
1401
* controls that support them, then dispatches an ACTION event. Considered
1402
* protected; should only be used within this package and by subclasses.
1403
* @param {goog.events.Event} e Event that triggered the action.
1404
* @return {boolean} Whether the action is allowed to proceed.
1405
* @protected
1406
*/
1407
goog.ui.Control.prototype.performActionInternal = function(e) {
1408
'use strict';
1409
if (this.isAutoState(goog.ui.Component.State.CHECKED)) {
1410
this.setChecked(!this.isChecked());
1411
}
1412
if (this.isAutoState(goog.ui.Component.State.SELECTED)) {
1413
this.setSelected(true);
1414
}
1415
if (this.isAutoState(goog.ui.Component.State.OPENED)) {
1416
this.setOpen(!this.isOpen());
1417
}
1418
1419
var actionEvent =
1420
new goog.events.Event(goog.ui.Component.EventType.ACTION, this);
1421
if (e) {
1422
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
1423
actionEvent.altKey = e.altKey;
1424
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
1425
actionEvent.ctrlKey = e.ctrlKey;
1426
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
1427
actionEvent.metaKey = e.metaKey;
1428
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
1429
actionEvent.shiftKey = e.shiftKey;
1430
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
1431
actionEvent.platformModifierKey = e.platformModifierKey;
1432
}
1433
return this.dispatchEvent(actionEvent);
1434
};
1435
1436
1437
/**
1438
* Handles focus events on the component's key event target element. If the
1439
* component is focusable, updates its state and styling to indicate that it
1440
* now has keyboard focus. Considered protected; should only be used within
1441
* this package and by subclasses. <b>Warning:</b> IE dispatches focus and
1442
* blur events asynchronously!
1443
* @param {goog.events.Event} e Focus event to handle.
1444
*/
1445
goog.ui.Control.prototype.handleFocus = function(e) {
1446
'use strict';
1447
if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {
1448
this.setFocused(true);
1449
}
1450
};
1451
1452
1453
/**
1454
* Handles blur events on the component's key event target element. Always
1455
* deactivates the component. In addition, if the component is focusable,
1456
* updates its state and styling to indicate that it no longer has keyboard
1457
* focus. Considered protected; should only be used within this package and
1458
* by subclasses. <b>Warning:</b> IE dispatches focus and blur events
1459
* asynchronously!
1460
* @param {goog.events.Event} e Blur event to handle.
1461
*/
1462
goog.ui.Control.prototype.handleBlur = function(e) {
1463
'use strict';
1464
if (this.isAutoState(goog.ui.Component.State.ACTIVE)) {
1465
this.setActive(false);
1466
}
1467
if (this.isAutoState(goog.ui.Component.State.FOCUSED)) {
1468
this.setFocused(false);
1469
}
1470
};
1471
1472
1473
/**
1474
* Attempts to handle a keyboard event, if the component is enabled and visible,
1475
* by calling {@link handleKeyEventInternal}. Considered protected; should only
1476
* be used within this package and by subclasses.
1477
* @param {goog.events.KeyEvent} e Key event to handle.
1478
* @return {boolean} Whether the key event was handled.
1479
*/
1480
goog.ui.Control.prototype.handleKeyEvent = function(e) {
1481
'use strict';
1482
if (this.isVisible() && this.isEnabled() && this.handleKeyEventInternal(e)) {
1483
e.preventDefault();
1484
e.stopPropagation();
1485
return true;
1486
}
1487
return false;
1488
};
1489
1490
1491
/**
1492
* Attempts to handle a keyboard event; returns true if the event was handled,
1493
* false otherwise. Considered protected; should only be used within this
1494
* package and by subclasses.
1495
* @param {goog.events.KeyEvent} e Key event to handle.
1496
* @return {boolean} Whether the key event was handled.
1497
* @protected
1498
*/
1499
goog.ui.Control.prototype.handleKeyEventInternal = function(e) {
1500
'use strict';
1501
return e.keyCode == goog.events.KeyCodes.ENTER &&
1502
this.performActionInternal(e);
1503
};
1504
1505
1506
// Register the default renderer for goog.ui.Controls.
1507
goog.ui.registry.setDefaultRenderer(goog.ui.Control, goog.ui.ControlRenderer);
1508
1509
1510
// Register a decorator factory function for goog.ui.Controls.
1511
goog.ui.registry.setDecoratorByClassName(
1512
goog.ui.ControlRenderer.CSS_CLASS, function() {
1513
'use strict';
1514
return new goog.ui.Control(null);
1515
});
1516
1517
1518
1519
/**
1520
* A singleton that helps goog.ui.Control instances play well with screen
1521
* readers. It necessitated by shortcomings in IE, and need not be
1522
* instantiated in any other browser.
1523
*
1524
* In most cases, a click on a goog.ui.Control results in a sequence of events:
1525
* MOUSEDOWN, MOUSEUP and CLICK. UI controls rely on this sequence since most
1526
* behavior is trigged by MOUSEDOWN and MOUSEUP. But when IE is used with some
1527
* traditional screen readers (JAWS, NVDA and perhaps others), IE only sends
1528
* the CLICK event, resulting in the control being unresponsive. This class
1529
* monitors the sequence of these events, and if it detects a CLICK event not
1530
* not preceded by a MOUSEUP event, directly calls the control's event handlers
1531
* for MOUSEDOWN, then MOUSEUP. While the resulting sequence is different from
1532
* the norm (the CLICK comes first instead of last), testing thus far shows
1533
* the resulting behavior to be correct.
1534
*
1535
* See http://goo.gl/qvQR4C for more details.
1536
*
1537
* @param {!goog.ui.Control} control
1538
* @constructor
1539
* @extends {goog.Disposable}
1540
* @private
1541
*/
1542
goog.ui.Control.IeMouseEventSequenceSimulator_ = function(control) {
1543
'use strict';
1544
goog.ui.Control.IeMouseEventSequenceSimulator_.base(this, 'constructor');
1545
1546
/** @private {goog.ui.Control}*/
1547
this.control_ = control;
1548
1549
/** @private {boolean} */
1550
this.clickExpected_ = false;
1551
1552
/** @private @const {!goog.events.EventHandler<
1553
* !goog.ui.Control.IeMouseEventSequenceSimulator_>}
1554
*/
1555
this.handler_ = new goog.events.EventHandler(this);
1556
this.registerDisposable(this.handler_);
1557
1558
var element = this.control_.getElementStrict();
1559
var MouseEventType = goog.ui.ComponentUtil.getMouseEventType(control);
1560
1561
this.handler_.listen(element, MouseEventType.MOUSEDOWN, this.handleMouseDown_)
1562
.listen(element, MouseEventType.MOUSEUP, this.handleMouseUp_)
1563
.listen(element, goog.events.EventType.CLICK, this.handleClick_);
1564
};
1565
goog.inherits(goog.ui.Control.IeMouseEventSequenceSimulator_, goog.Disposable);
1566
1567
1568
/**
1569
* Whether this browser supports synthetic MouseEvents.
1570
*
1571
* See https://msdn.microsoft.com/library/dn905219(v=vs.85).aspx for details.
1572
*
1573
* @private {boolean}
1574
* @const
1575
*/
1576
goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_ =
1577
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9);
1578
1579
1580
/** @private */
1581
goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseDown_ =
1582
function() {
1583
'use strict';
1584
this.clickExpected_ = false;
1585
};
1586
1587
1588
/** @private */
1589
goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseUp_ =
1590
function() {
1591
'use strict';
1592
this.clickExpected_ = true;
1593
};
1594
1595
1596
/**
1597
* @param {!MouseEvent} e
1598
* @param {goog.events.EventType} typeArg
1599
* @return {!MouseEvent}
1600
* @private
1601
*/
1602
goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_ = function(
1603
e, typeArg) {
1604
'use strict';
1605
'use strict';
1606
1607
if (!goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_) {
1608
// IE < 9 does not support synthetic mouse events. Therefore, reuse the
1609
// existing MouseEvent by overwriting the read only button and type
1610
// properties. As IE < 9 does not support ES5 strict mode this will not
1611
// generate an exception even when the script specifies "use strict".
1612
e.button = goog.events.BrowserEvent.MouseButton.LEFT;
1613
e.type = typeArg;
1614
return e;
1615
}
1616
1617
var event = /** @type {!MouseEvent} */ (document.createEvent('MouseEvents'));
1618
event.initMouseEvent(
1619
typeArg, e.bubbles, e.cancelable,
1620
e.view || null, // IE9 errors if view is undefined
1621
e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey,
1622
e.shiftKey, e.metaKey, goog.events.BrowserEvent.MouseButton.LEFT,
1623
e.relatedTarget || null); // IE9 errors if relatedTarget is undefined
1624
return event;
1625
};
1626
1627
1628
/**
1629
* @param {!goog.events.Event} e
1630
* @private
1631
*/
1632
goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleClick_ =
1633
function(e) {
1634
'use strict';
1635
if (this.clickExpected_) {
1636
// This is the end of a normal click sequence: mouse-down, mouse-up, click.
1637
// Assume appropriate actions have already been performed.
1638
this.clickExpected_ = false;
1639
return;
1640
}
1641
1642
// For click events not part of a normal sequence, similate the mouse-down and
1643
// mouse-up events by creating synthetic events for each and directly invoke
1644
// the corresponding event listeners in order.
1645
1646
var browserEvent = /** @type {goog.events.BrowserEvent} */ (e);
1647
1648
var event = /** @type {!MouseEvent} */ (browserEvent.getBrowserEvent());
1649
var origEventButton = event.button;
1650
var origEventType = event.type;
1651
1652
var down = goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_(
1653
event, goog.events.EventType.MOUSEDOWN);
1654
this.control_.handleMouseDown(
1655
new goog.events.BrowserEvent(down, browserEvent.currentTarget));
1656
1657
var up = goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_(
1658
event, goog.events.EventType.MOUSEUP);
1659
this.control_.handleMouseUp(
1660
new goog.events.BrowserEvent(up, browserEvent.currentTarget));
1661
1662
if (goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_) {
1663
// This browser supports synthetic events. Avoid resetting the read only
1664
// properties (type, button) as they were not overwritten and writing them
1665
// results in an exception when running in ES5 strict mode.
1666
return;
1667
}
1668
1669
// Restore original values for click handlers that have not yet been invoked.
1670
event.button = origEventButton;
1671
event.type = origEventType;
1672
};
1673
1674
1675
/** @override */
1676
goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.disposeInternal =
1677
function() {
1678
'use strict';
1679
this.control_ = null;
1680
goog.ui.Control.IeMouseEventSequenceSimulator_.base(this, 'disposeInternal');
1681
};
1682
1683