Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/ui/containerrenderer.js
4049 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 container renderers.
9
*/
10
11
goog.provide('goog.ui.ContainerRenderer');
12
13
goog.require('goog.a11y.aria');
14
goog.require('goog.asserts');
15
goog.require('goog.dom.NodeType');
16
goog.require('goog.dom.TagName');
17
goog.require('goog.dom.classlist');
18
goog.require('goog.string');
19
goog.require('goog.style');
20
goog.require('goog.ui.registry');
21
goog.require('goog.userAgent');
22
goog.requireType('goog.ui.Container');
23
goog.requireType('goog.ui.Container.Orientation');
24
goog.requireType('goog.ui.Control');
25
26
27
28
/**
29
* Default renderer for {@link goog.ui.Container}. Can be used as-is, but
30
* subclasses of Container will probably want to use renderers specifically
31
* tailored for them by extending this class.
32
* @param {string=} opt_ariaRole Optional ARIA role used for the element.
33
* @constructor
34
*/
35
goog.ui.ContainerRenderer = function(opt_ariaRole) {
36
'use strict';
37
// By default, the ARIA role is unspecified.
38
/** @private {string|undefined} */
39
this.ariaRole_ = opt_ariaRole;
40
};
41
goog.addSingletonGetter(goog.ui.ContainerRenderer);
42
43
44
/**
45
* Constructs a new renderer and sets the CSS class that the renderer will use
46
* as the base CSS class to apply to all elements rendered by that renderer.
47
* An example to use this function using a menu is:
48
*
49
* <pre>
50
* var myCustomRenderer = goog.ui.ContainerRenderer.getCustomRenderer(
51
* goog.ui.MenuRenderer, 'my-special-menu');
52
* var newMenu = new goog.ui.Menu(opt_domHelper, myCustomRenderer);
53
* </pre>
54
*
55
* Your styles for the menu can now be:
56
* <pre>
57
* .my-special-menu { }
58
* </pre>
59
*
60
* <em>instead</em> of
61
* <pre>
62
* .CSS_MY_SPECIAL_MENU .goog-menu { }
63
* </pre>
64
*
65
* You would want to use this functionality when you want an instance of a
66
* component to have specific styles different than the other components of the
67
* same type in your application. This avoids using descendant selectors to
68
* apply the specific styles to this component.
69
*
70
* @param {Function} ctor The constructor of the renderer you want to create.
71
* @param {string} cssClassName The name of the CSS class for this renderer.
72
* @return {goog.ui.ContainerRenderer} An instance of the desired renderer with
73
* its getCssClass() method overridden to return the supplied custom CSS
74
* class name.
75
*/
76
goog.ui.ContainerRenderer.getCustomRenderer = function(ctor, cssClassName) {
77
'use strict';
78
var renderer = new ctor();
79
80
/**
81
* Returns the CSS class to be applied to the root element of components
82
* rendered using this renderer.
83
* @return {string} Renderer-specific CSS class.
84
*/
85
renderer.getCssClass = function() {
86
'use strict';
87
return cssClassName;
88
};
89
90
return renderer;
91
};
92
93
94
/**
95
* Default CSS class to be applied to the root element of containers rendered
96
* by this renderer.
97
* @type {string}
98
*/
99
goog.ui.ContainerRenderer.CSS_CLASS = goog.getCssName('goog-container');
100
101
102
/**
103
* Returns the ARIA role to be applied to the container.
104
* See http://wiki/Main/ARIA for more info.
105
* @return {undefined|string} ARIA role.
106
*/
107
goog.ui.ContainerRenderer.prototype.getAriaRole = function() {
108
'use strict';
109
return this.ariaRole_;
110
};
111
112
113
/**
114
* Enables or disables the tab index of the element. Only elements with a
115
* valid tab index can receive focus.
116
* @param {Element} element Element whose tab index is to be changed.
117
* @param {boolean} enable Whether to add or remove the element's tab index.
118
* @suppress {strictMissingProperties}
119
*/
120
goog.ui.ContainerRenderer.prototype.enableTabIndex = function(element, enable) {
121
'use strict';
122
if (element) {
123
element.tabIndex = enable ? 0 : -1;
124
}
125
};
126
127
128
/**
129
* Creates and returns the container's root element. The default
130
* simply creates a DIV and applies the renderer's own CSS class name to it.
131
* To be overridden in subclasses.
132
* @param {goog.ui.Container} container Container to render.
133
* @return {Element} Root element for the container.
134
*/
135
goog.ui.ContainerRenderer.prototype.createDom = function(container) {
136
'use strict';
137
return container.getDomHelper().createDom(
138
goog.dom.TagName.DIV, this.getClassNames(container).join(' '));
139
};
140
141
142
/**
143
* Returns the DOM element into which child components are to be rendered,
144
* or null if the container hasn't been rendered yet.
145
* @param {Element} element Root element of the container whose content element
146
* is to be returned.
147
* @return {Element} Element to contain child elements (null if none).
148
*/
149
goog.ui.ContainerRenderer.prototype.getContentElement = function(element) {
150
'use strict';
151
return element;
152
};
153
154
155
/**
156
* Default implementation of `canDecorate`; returns true if the element
157
* is a DIV, false otherwise.
158
* @param {Element} element Element to decorate.
159
* @return {boolean} Whether the renderer can decorate the element.
160
*/
161
goog.ui.ContainerRenderer.prototype.canDecorate = function(element) {
162
'use strict';
163
return element.tagName == 'DIV';
164
};
165
166
167
/**
168
* Default implementation of `decorate` for {@link goog.ui.Container}s.
169
* Decorates the element with the container, and attempts to decorate its child
170
* elements. Returns the decorated element.
171
* @param {goog.ui.Container} container Container to decorate the element.
172
* @param {Element} element Element to decorate.
173
* @return {!Element} Decorated element.
174
*/
175
goog.ui.ContainerRenderer.prototype.decorate = function(container, element) {
176
'use strict';
177
// Set the container's ID to the decorated element's DOM ID, if any.
178
if (element.id) {
179
container.setId(element.id);
180
}
181
182
// Configure the container's state based on the CSS class names it has.
183
var baseClass = this.getCssClass();
184
var hasBaseClass = false;
185
var classNames = goog.dom.classlist.get(element);
186
if (classNames) {
187
Array.prototype.forEach.call(classNames, function(className) {
188
'use strict';
189
if (className == baseClass) {
190
hasBaseClass = true;
191
} else {
192
if (className) {
193
this.setStateFromClassName(container, className, baseClass);
194
}
195
}
196
}, this);
197
}
198
199
if (!hasBaseClass) {
200
// Make sure the container's root element has the renderer's own CSS class.
201
goog.dom.classlist.add(element, baseClass);
202
}
203
204
// Decorate the element's children, if applicable. This should happen after
205
// the container's own state has been initialized, since how children are
206
// decorated may depend on the state of the container.
207
this.decorateChildren(container, this.getContentElement(element));
208
209
return element;
210
};
211
212
213
/**
214
* Sets the container's state based on the given CSS class name, encountered
215
* during decoration. CSS class names that don't represent container states
216
* are ignored. Considered protected; subclasses should override this method
217
* to support more states and CSS class names.
218
* @param {goog.ui.Container} container Container to update.
219
* @param {string} className CSS class name.
220
* @param {string} baseClass Base class name used as the root of state-specific
221
* class names (typically the renderer's own class name).
222
* @protected
223
* @suppress {missingRequire} goog.ui.Container
224
*/
225
goog.ui.ContainerRenderer.prototype.setStateFromClassName = function(
226
container, className, baseClass) {
227
'use strict';
228
if (className == goog.getCssName(baseClass, 'disabled')) {
229
container.setEnabled(false);
230
} else if (className == goog.getCssName(baseClass, 'horizontal')) {
231
container.setOrientation(goog.ui.Container.Orientation.HORIZONTAL);
232
} else if (className == goog.getCssName(baseClass, 'vertical')) {
233
container.setOrientation(goog.ui.Container.Orientation.VERTICAL);
234
}
235
};
236
237
238
/**
239
* Takes a container and an element that may contain child elements, decorates
240
* the child elements, and adds the corresponding components to the container
241
* as child components. Any non-element child nodes (e.g. empty text nodes
242
* introduced by line breaks in the HTML source) are removed from the element.
243
* @param {goog.ui.Container} container Container whose children are to be
244
* discovered.
245
* @param {Element} element Element whose children are to be decorated.
246
* @param {Element=} opt_firstChild the first child to be decorated.
247
*/
248
goog.ui.ContainerRenderer.prototype.decorateChildren = function(
249
container, element, opt_firstChild) {
250
'use strict';
251
if (element) {
252
var node = opt_firstChild || element.firstChild, next;
253
// Tag soup HTML may result in a DOM where siblings have different parents.
254
while (node && node.parentNode == element) {
255
// Get the next sibling here, since the node may be replaced or removed.
256
next = node.nextSibling;
257
if (node.nodeType == goog.dom.NodeType.ELEMENT) {
258
// Decorate element node.
259
var child = this.getDecoratorForChild(/** @type {!Element} */ (node));
260
if (child) {
261
// addChild() may need to look at the element.
262
child.setElementInternal(/** @type {!Element} */ (node));
263
// If the container is disabled, mark the child disabled too. See
264
// bug 1263729. Note that this must precede the call to addChild().
265
if (!container.isEnabled()) {
266
child.setEnabled(false);
267
}
268
container.addChild(child);
269
child.decorate(/** @type {!Element} */ (node));
270
}
271
} else if (!node.nodeValue || goog.string.trim(node.nodeValue) == '') {
272
// Remove empty text node, otherwise madness ensues (e.g. controls that
273
// use goog-inline-block will flicker and shift on hover on Gecko).
274
element.removeChild(node);
275
}
276
node = next;
277
}
278
}
279
};
280
281
282
/**
283
* Inspects the element, and creates an instance of {@link goog.ui.Control} or
284
* an appropriate subclass best suited to decorate it. Returns the control (or
285
* null if no suitable class was found). This default implementation uses the
286
* element's CSS class to find the appropriate control class to instantiate.
287
* May be overridden in subclasses.
288
* @param {Element} element Element to decorate.
289
* @return {goog.ui.Control?} A new control suitable to decorate the element
290
* (null if none).
291
*/
292
goog.ui.ContainerRenderer.prototype.getDecoratorForChild = function(element) {
293
'use strict';
294
return /** @type {goog.ui.Control} */ (
295
goog.ui.registry.getDecorator(element));
296
};
297
298
299
/**
300
* Initializes the container's DOM when the container enters the document.
301
* Called from {@link goog.ui.Container#enterDocument}.
302
* @param {goog.ui.Container} container Container whose DOM is to be initialized
303
* as it enters the document.
304
*/
305
goog.ui.ContainerRenderer.prototype.initializeDom = function(container) {
306
'use strict';
307
var elem = container.getElement();
308
goog.asserts.assert(elem, 'The container DOM element cannot be null.');
309
// Make sure the container's element isn't selectable. On Gecko, recursively
310
// marking each child element unselectable is expensive and unnecessary, so
311
// only mark the root element unselectable.
312
goog.style.setUnselectable(elem, true, goog.userAgent.GECKO);
313
314
// IE doesn't support outline:none, so we have to use the hideFocus property.
315
if (goog.userAgent.IE) {
316
elem.hideFocus = true;
317
}
318
319
// Set the ARIA role.
320
var ariaRole = this.getAriaRole();
321
if (ariaRole) {
322
goog.a11y.aria.setRole(elem, ariaRole);
323
}
324
};
325
326
327
/**
328
* Returns the element within the container's DOM that should receive keyboard
329
* focus (null if none). The default implementation returns the container's
330
* root element.
331
* @param {goog.ui.Container} container Container whose key event target is
332
* to be returned.
333
* @return {Element} Key event target (null if none).
334
*/
335
goog.ui.ContainerRenderer.prototype.getKeyEventTarget = function(container) {
336
'use strict';
337
return container.getElement();
338
};
339
340
341
/**
342
* Returns the CSS class to be applied to the root element of containers
343
* rendered using this renderer.
344
* @return {string} Renderer-specific CSS class.
345
*/
346
goog.ui.ContainerRenderer.prototype.getCssClass = function() {
347
'use strict';
348
return goog.ui.ContainerRenderer.CSS_CLASS;
349
};
350
351
352
/**
353
* Returns all CSS class names applicable to the given container, based on its
354
* state. The array of class names returned includes the renderer's own CSS
355
* class, followed by a CSS class indicating the container's orientation,
356
* followed by any state-specific CSS classes.
357
* @param {goog.ui.Container} container Container whose CSS classes are to be
358
* returned.
359
* @return {!Array<string>} Array of CSS class names applicable to the
360
* container.
361
* @suppress {missingRequire} TODO(user): fix this
362
*/
363
goog.ui.ContainerRenderer.prototype.getClassNames = function(container) {
364
'use strict';
365
var baseClass = this.getCssClass();
366
var isHorizontal =
367
container.getOrientation() == goog.ui.Container.Orientation.HORIZONTAL;
368
var classNames = [
369
baseClass, (isHorizontal ? goog.getCssName(baseClass, 'horizontal') :
370
goog.getCssName(baseClass, 'vertical'))
371
];
372
if (!container.isEnabled()) {
373
classNames.push(goog.getCssName(baseClass, 'disabled'));
374
}
375
return classNames;
376
};
377
378
379
/**
380
* Returns the default orientation of containers rendered or decorated by this
381
* renderer. The base class implementation returns `VERTICAL`.
382
* @return {goog.ui.Container.Orientation} Default orientation for containers
383
* created or decorated by this renderer.
384
* @suppress {missingRequire} goog.ui.Container
385
*/
386
goog.ui.ContainerRenderer.prototype.getDefaultOrientation = function() {
387
'use strict';
388
return goog.ui.Container.Orientation.VERTICAL;
389
};
390
391