Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/ui/menu.js
4506 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview A base menu class that supports key and mouse events. The menu
9
* can be bound to an existing HTML structure or can generate its own DOM.
10
*
11
* To decorate, the menu should be bound to an element containing children
12
* with the classname 'goog-menuitem'. HRs will be classed as separators.
13
*
14
* Decorate Example:
15
* <div id="menu" class="goog-menu" tabIndex="0">
16
* <div class="goog-menuitem">Google</div>
17
* <div class="goog-menuitem">Yahoo</div>
18
* <div class="goog-menuitem">MSN</div>
19
* <hr>
20
* <div class="goog-menuitem">New...</div>
21
* </div>
22
* <script>
23
*
24
* var menu = new goog.ui.Menu();
25
* menu.decorate(goog.dom.getElement('menu'));
26
*
27
* TESTED=FireFox 2.0, IE6, Opera 9, Chrome.
28
* TODO(user): Key handling is flaky in Opera and Chrome
29
* TODO(user): Rename all references of "item" to child since menu is
30
* essentially very generic and could, in theory, host a date or color picker.
31
*
32
* @see ../demos/menu.html
33
* @see ../demos/menus.html
34
*/
35
36
goog.provide('goog.ui.Menu');
37
goog.provide('goog.ui.Menu.EventType');
38
39
goog.require('goog.dom.TagName');
40
goog.require('goog.math.Coordinate');
41
goog.require('goog.string');
42
goog.require('goog.style');
43
goog.require('goog.ui.Component.EventType');
44
goog.require('goog.ui.Component.State');
45
goog.require('goog.ui.Container');
46
goog.require('goog.ui.Container.Orientation');
47
goog.require('goog.ui.MenuHeader');
48
goog.require('goog.ui.MenuItem');
49
goog.require('goog.ui.MenuRenderer');
50
goog.require('goog.ui.MenuSeparator');
51
goog.requireType('goog.dom.DomHelper');
52
goog.requireType('goog.events.Event');
53
54
// The dependencies MenuHeader, MenuItem, and MenuSeparator are implicit.
55
// There are no references in the code, but we need to load these
56
// classes before goog.ui.Menu.
57
58
59
60
// TODO(robbyw): Reverse constructor argument order for consistency.
61
/**
62
* A basic menu class.
63
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
64
* @param {goog.ui.MenuRenderer=} opt_renderer Renderer used to render or
65
* decorate the container; defaults to {@link goog.ui.MenuRenderer}.
66
* @constructor
67
* @extends {goog.ui.Container}
68
*/
69
goog.ui.Menu = function(opt_domHelper, opt_renderer) {
70
'use strict';
71
goog.ui.Container.call(
72
this, goog.ui.Container.Orientation.VERTICAL,
73
opt_renderer || goog.ui.MenuRenderer.getInstance(), opt_domHelper);
74
75
// Unlike Containers, Menus aren't keyboard-accessible by default. This line
76
// preserves backwards compatibility with code that depends on menus not
77
// receiving focus - e.g. `goog.ui.MenuButton`.
78
this.setFocusable(false);
79
};
80
goog.inherits(goog.ui.Menu, goog.ui.Container);
81
82
83
// TODO(robbyw): Remove this and all references to it.
84
// Please ensure that BEFORE_SHOW behavior is not disrupted as a result.
85
/**
86
* Event types dispatched by the menu.
87
* @enum {string}
88
* @deprecated Use goog.ui.Component.EventType.
89
*/
90
goog.ui.Menu.EventType = {
91
/** Dispatched before the menu becomes visible */
92
BEFORE_SHOW: goog.ui.Component.EventType.BEFORE_SHOW,
93
94
/** Dispatched when the menu is shown */
95
SHOW: goog.ui.Component.EventType.SHOW,
96
97
/** Dispatched before the menu becomes hidden */
98
BEFORE_HIDE: goog.ui.Component.EventType.HIDE,
99
100
/** Dispatched when the menu is hidden */
101
HIDE: goog.ui.Component.EventType.HIDE
102
};
103
104
105
// TODO(robbyw): Remove this and all references to it.
106
/**
107
* CSS class for menus.
108
* @type {string}
109
* @deprecated Use goog.ui.MenuRenderer.CSS_CLASS.
110
*/
111
goog.ui.Menu.CSS_CLASS = goog.ui.MenuRenderer.CSS_CLASS;
112
113
114
/**
115
* Coordinates of the mousedown event that caused this menu to be made visible.
116
* Used to prevent the consequent mouseup event due to a simple click from
117
* activating a menu item immediately. Considered protected; should only be used
118
* within this package or by subclasses.
119
* @type {goog.math.Coordinate|undefined}
120
*/
121
goog.ui.Menu.prototype.openingCoords;
122
123
124
/**
125
* Whether the menu can move the focus to its key event target when it is
126
* shown. Default = true
127
* @type {boolean}
128
* @private
129
*/
130
goog.ui.Menu.prototype.allowAutoFocus_ = true;
131
132
133
/**
134
* Whether the menu should use windows style behavior and allow disabled menu
135
* items to be highlighted (though not selectable). Defaults to false
136
* @type {boolean}
137
* @private
138
*/
139
goog.ui.Menu.prototype.allowHighlightDisabled_ = false;
140
141
142
/**
143
* Returns the CSS class applied to menu elements, also used as the prefix for
144
* derived styles, if any. Subclasses should override this method as needed.
145
* Considered protected.
146
* @return {string} The CSS class applied to menu elements.
147
* @protected
148
* @deprecated Use getRenderer().getCssClass().
149
*/
150
goog.ui.Menu.prototype.getCssClass = function() {
151
'use strict';
152
return this.getRenderer().getCssClass();
153
};
154
155
156
/**
157
* Returns whether the provided element is to be considered inside the menu for
158
* purposes such as dismissing the menu on an event. This is so submenus can
159
* make use of elements outside their own DOM.
160
* @param {Element} element The element to test for.
161
* @return {boolean} Whether the provided element is to be considered inside
162
* the menu.
163
* @suppress {strictMissingProperties} Added to tighten compiler checks
164
*/
165
goog.ui.Menu.prototype.containsElement = function(element) {
166
'use strict';
167
if (this.getRenderer().containsElement(this, element)) {
168
return true;
169
}
170
171
for (var i = 0, count = this.getChildCount(); i < count; i++) {
172
var child = this.getChildAt(i);
173
if (typeof child.containsElement == 'function' &&
174
child.containsElement(element)) {
175
return true;
176
}
177
}
178
179
return false;
180
};
181
182
183
/**
184
* Adds a new menu item at the end of the menu.
185
* @param {goog.ui.MenuHeader|goog.ui.MenuItem|goog.ui.MenuSeparator} item Menu
186
* item to add to the menu.
187
* @deprecated Use {@link #addChild} instead, with true for the second argument.
188
*/
189
goog.ui.Menu.prototype.addItem = function(item) {
190
'use strict';
191
this.addChild(item, true);
192
};
193
194
195
/**
196
* Adds a new menu item at a specific index in the menu.
197
* @param {goog.ui.MenuHeader|goog.ui.MenuItem|goog.ui.MenuSeparator} item Menu
198
* item to add to the menu.
199
* @param {number} n Index at which to insert the menu item.
200
* @deprecated Use {@link #addChildAt} instead, with true for the third
201
* argument.
202
*/
203
goog.ui.Menu.prototype.addItemAt = function(item, n) {
204
'use strict';
205
this.addChildAt(item, n, true);
206
};
207
208
209
/**
210
* Removes an item from the menu and disposes of it.
211
* @param {goog.ui.MenuHeader|goog.ui.MenuItem|goog.ui.MenuSeparator} item The
212
* menu item to remove.
213
* @deprecated Use {@link #removeChild} instead.
214
*/
215
goog.ui.Menu.prototype.removeItem = function(item) {
216
'use strict';
217
var removedChild = this.removeChild(item, true);
218
if (removedChild) {
219
removedChild.dispose();
220
}
221
};
222
223
224
/**
225
* Removes a menu item at a given index in the menu and disposes of it.
226
* @param {number} n Index of item.
227
* @deprecated Use {@link #removeChildAt} instead.
228
*/
229
goog.ui.Menu.prototype.removeItemAt = function(n) {
230
'use strict';
231
var removedChild = this.removeChildAt(n, true);
232
if (removedChild) {
233
removedChild.dispose();
234
}
235
};
236
237
238
/**
239
* Returns a reference to the menu item at a given index.
240
* @param {number} n Index of menu item.
241
* @return {goog.ui.MenuHeader|goog.ui.MenuItem|goog.ui.MenuSeparator|null}
242
* Reference to the menu item.
243
* @deprecated Use {@link #getChildAt} instead.
244
*/
245
goog.ui.Menu.prototype.getItemAt = function(n) {
246
'use strict';
247
return /** @type {goog.ui.MenuItem?} */ (this.getChildAt(n));
248
};
249
250
251
/**
252
* Returns the number of items in the menu (including separators).
253
* @return {number} The number of items in the menu.
254
* @deprecated Use {@link #getChildCount} instead.
255
*/
256
goog.ui.Menu.prototype.getItemCount = function() {
257
'use strict';
258
return this.getChildCount();
259
};
260
261
262
/**
263
* Returns an array containing the menu items contained in the menu.
264
* @return {!Array<goog.ui.MenuItem>} An array of menu items.
265
* @deprecated Use getChildAt, forEachChild, and getChildCount.
266
*/
267
goog.ui.Menu.prototype.getItems = function() {
268
'use strict';
269
// TODO(user): Remove reference to getItems and instead use getChildAt,
270
// forEachChild, and getChildCount
271
var children = [];
272
this.forEachChild(function(child) {
273
'use strict';
274
children.push(child);
275
});
276
return children;
277
};
278
279
280
/**
281
* Sets the position of the menu relative to the view port.
282
* @param {number|goog.math.Coordinate} x Left position or coordinate obj.
283
* @param {number=} opt_y Top position.
284
*/
285
goog.ui.Menu.prototype.setPosition = function(x, opt_y) {
286
'use strict';
287
// NOTE(user): It is necessary to temporarily set the display from none, so
288
// that the position gets set correctly.
289
var visible = this.isVisible();
290
if (!visible) {
291
goog.style.setElementShown(this.getElement(), true);
292
}
293
goog.style.setPageOffset(this.getElement(), x, opt_y);
294
if (!visible) {
295
goog.style.setElementShown(this.getElement(), false);
296
}
297
};
298
299
300
/**
301
* Gets the page offset of the menu, or null if the menu isn't visible
302
* @return {goog.math.Coordinate?} Object holding the x-y coordinates of the
303
* menu or null if the menu is not visible.
304
*/
305
goog.ui.Menu.prototype.getPosition = function() {
306
'use strict';
307
return this.isVisible() ? goog.style.getPageOffset(this.getElement()) : null;
308
};
309
310
311
/**
312
* Sets whether the menu can automatically move focus to its key event target
313
* when it is set to visible.
314
* @param {boolean} allow Whether the menu can automatically move focus to its
315
* key event target when it is set to visible.
316
*/
317
goog.ui.Menu.prototype.setAllowAutoFocus = function(allow) {
318
'use strict';
319
this.allowAutoFocus_ = allow;
320
if (allow) {
321
this.setFocusable(true);
322
}
323
};
324
325
326
/**
327
* @return {boolean} Whether the menu can automatically move focus to its key
328
* event target when it is set to visible.
329
*/
330
goog.ui.Menu.prototype.getAllowAutoFocus = function() {
331
'use strict';
332
return this.allowAutoFocus_;
333
};
334
335
336
/**
337
* Sets whether the menu will highlight disabled menu items or skip to the next
338
* active item.
339
* @param {boolean} allow Whether the menu will highlight disabled menu items or
340
* skip to the next active item.
341
*/
342
goog.ui.Menu.prototype.setAllowHighlightDisabled = function(allow) {
343
'use strict';
344
this.allowHighlightDisabled_ = allow;
345
};
346
347
348
/**
349
* @return {boolean} Whether the menu will highlight disabled menu items or skip
350
* to the next active item.
351
*/
352
goog.ui.Menu.prototype.getAllowHighlightDisabled = function() {
353
'use strict';
354
return this.allowHighlightDisabled_;
355
};
356
357
358
/**
359
* @override
360
* @param {boolean} show Whether to show or hide the menu.
361
* @param {boolean=} opt_force If true, doesn't check whether the menu
362
* already has the requested visibility, and doesn't dispatch any events.
363
* @param {goog.events.Event=} opt_e Mousedown event that caused this menu to
364
* be made visible (ignored if show is false).
365
*/
366
goog.ui.Menu.prototype.setVisible = function(show, opt_force, opt_e) {
367
'use strict';
368
var visibilityChanged =
369
goog.ui.Menu.superClass_.setVisible.call(this, show, opt_force);
370
if (visibilityChanged && show && this.isInDocument() &&
371
this.allowAutoFocus_) {
372
this.getKeyEventTarget().focus();
373
}
374
if (show && opt_e && typeof opt_e.clientX === 'number') {
375
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
376
this.openingCoords = new goog.math.Coordinate(opt_e.clientX, opt_e.clientY);
377
} else {
378
this.openingCoords = null;
379
}
380
return visibilityChanged;
381
};
382
383
384
/** @override */
385
goog.ui.Menu.prototype.handleEnterItem = function(e) {
386
'use strict';
387
if (this.allowAutoFocus_) {
388
this.getKeyEventTarget().focus();
389
}
390
391
return goog.ui.Menu.superClass_.handleEnterItem.call(this, e);
392
};
393
394
395
/**
396
* Highlights the next item that begins with the specified string. If no
397
* (other) item begins with the given string, the selection is unchanged.
398
* @param {string} charStr The prefix to match.
399
* @return {boolean} Whether a matching prefix was found.
400
*/
401
goog.ui.Menu.prototype.highlightNextPrefix = function(charStr) {
402
'use strict';
403
var re = new RegExp('^' + goog.string.regExpEscape(charStr), 'i');
404
return this.highlightHelper(function(index, max) {
405
'use strict';
406
// Index is >= -1 because it is set to -1 when nothing is selected.
407
var start = index < 0 ? 0 : index;
408
var wrapped = false;
409
410
// We always start looking from one after the current, because we
411
// keep the current selection only as a last resort. This makes the
412
// loop a little awkward in the case where there is no current
413
// selection, as we need to stop somewhere but can't just stop
414
// when index == start, which is why we need the 'wrapped' flag.
415
do {
416
++index;
417
if (index == max) {
418
index = 0;
419
wrapped = true;
420
}
421
var name = this.getChildAt(index).getCaption();
422
if (name && name.match(re)) {
423
return index;
424
}
425
} while (!wrapped || index != start);
426
return this.getHighlightedIndex();
427
}, this.getHighlightedIndex());
428
};
429
430
431
/** @override */
432
goog.ui.Menu.prototype.canHighlightItem = function(item) {
433
'use strict';
434
return (this.allowHighlightDisabled_ || item.isEnabled()) &&
435
item.isVisible() && item.isSupportedState(goog.ui.Component.State.HOVER);
436
};
437
438
439
/** @override */
440
goog.ui.Menu.prototype.decorateInternal = function(element) {
441
'use strict';
442
this.decorateContent(element);
443
goog.ui.Menu.superClass_.decorateInternal.call(this, element);
444
};
445
446
447
/** @override */
448
goog.ui.Menu.prototype.handleKeyEventInternal = function(e) {
449
'use strict';
450
var handled = goog.ui.Menu.base(this, 'handleKeyEventInternal', e);
451
if (!handled) {
452
// Loop through all child components, and for each menu item call its
453
// key event handler so that keyboard mnemonics can be handled.
454
this.forEachChild(function(menuItem) {
455
'use strict';
456
if (!handled && menuItem.getMnemonic &&
457
menuItem.getMnemonic() == e.keyCode) {
458
if (this.isEnabled()) {
459
this.setHighlighted(menuItem);
460
}
461
// We still delegate to handleKeyEvent, so that it can handle
462
// enabled/disabled state.
463
handled = menuItem.handleKeyEvent(e);
464
}
465
}, this);
466
}
467
return handled;
468
};
469
470
471
/** @override */
472
goog.ui.Menu.prototype.setHighlightedIndex = function(index) {
473
'use strict';
474
goog.ui.Menu.base(this, 'setHighlightedIndex', index);
475
476
// Bring the highlighted item into view. This has no effect if the menu is not
477
// scrollable.
478
var child = this.getChildAt(index);
479
if (child) {
480
goog.style.scrollIntoContainerView(child.getElement(), this.getElement());
481
}
482
};
483
484
485
/**
486
* Decorate menu items located in any descendant node which as been explicitly
487
* marked as a 'content' node.
488
* @param {Element} element Element to decorate.
489
* @protected
490
*/
491
goog.ui.Menu.prototype.decorateContent = function(element) {
492
'use strict';
493
var renderer = this.getRenderer();
494
var contentElements = this.getDomHelper().getElementsByTagNameAndClass(
495
goog.dom.TagName.DIV, goog.getCssName(renderer.getCssClass(), 'content'),
496
element);
497
498
// Some versions of IE do not like it when you access this nodeList
499
// with invalid indices. See
500
// http://code.google.com/p/closure-library/issues/detail?id=373
501
var length = contentElements.length;
502
for (var i = 0; i < length; i++) {
503
renderer.decorateChildren(this, contentElements[i]);
504
}
505
};
506
507