Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/dom/dom.js
2868 views
1
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Utilities for manipulating the browser's Document Object Model
17
* Inspiration taken *heavily* from mochikit (http://mochikit.com/).
18
*
19
* You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
20
* to a different document object. This is useful if you are working with
21
* frames or multiple windows.
22
*
23
* @author [email protected] (Erik Arvidsson)
24
*/
25
26
27
// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem
28
// is that getTextContent should mimic the DOM3 textContent. We should add a
29
// getInnerText (or getText) which tries to return the visible text, innerText.
30
31
32
goog.provide('goog.dom');
33
goog.provide('goog.dom.Appendable');
34
goog.provide('goog.dom.DomHelper');
35
36
goog.require('goog.array');
37
goog.require('goog.asserts');
38
goog.require('goog.dom.BrowserFeature');
39
goog.require('goog.dom.NodeType');
40
goog.require('goog.dom.TagName');
41
goog.require('goog.dom.safe');
42
goog.require('goog.html.SafeHtml');
43
goog.require('goog.html.uncheckedconversions');
44
goog.require('goog.math.Coordinate');
45
goog.require('goog.math.Size');
46
goog.require('goog.object');
47
goog.require('goog.string');
48
goog.require('goog.string.Unicode');
49
goog.require('goog.userAgent');
50
51
52
/**
53
* @define {boolean} Whether we know at compile time that the browser is in
54
* quirks mode.
55
*/
56
goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
57
58
59
/**
60
* @define {boolean} Whether we know at compile time that the browser is in
61
* standards compliance mode.
62
*/
63
goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
64
65
66
/**
67
* Whether we know the compatibility mode at compile time.
68
* @type {boolean}
69
* @private
70
*/
71
goog.dom.COMPAT_MODE_KNOWN_ =
72
goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
73
74
75
/**
76
* Gets the DomHelper object for the document where the element resides.
77
* @param {(Node|Window)=} opt_element If present, gets the DomHelper for this
78
* element.
79
* @return {!goog.dom.DomHelper} The DomHelper.
80
*/
81
goog.dom.getDomHelper = function(opt_element) {
82
return opt_element ?
83
new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
84
(goog.dom.defaultDomHelper_ ||
85
(goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
86
};
87
88
89
/**
90
* Cached default DOM helper.
91
* @type {!goog.dom.DomHelper|undefined}
92
* @private
93
*/
94
goog.dom.defaultDomHelper_;
95
96
97
/**
98
* Gets the document object being used by the dom library.
99
* @return {!Document} Document object.
100
*/
101
goog.dom.getDocument = function() {
102
return document;
103
};
104
105
106
/**
107
* Gets an element from the current document by element id.
108
*
109
* If an Element is passed in, it is returned.
110
*
111
* @param {string|Element} element Element ID or a DOM node.
112
* @return {Element} The element with the given ID, or the node passed in.
113
*/
114
goog.dom.getElement = function(element) {
115
return goog.dom.getElementHelper_(document, element);
116
};
117
118
119
/**
120
* Gets an element by id from the given document (if present).
121
* If an element is given, it is returned.
122
* @param {!Document} doc
123
* @param {string|Element} element Element ID or a DOM node.
124
* @return {Element} The resulting element.
125
* @private
126
*/
127
goog.dom.getElementHelper_ = function(doc, element) {
128
return goog.isString(element) ? doc.getElementById(element) : element;
129
};
130
131
132
/**
133
* Gets an element by id, asserting that the element is found.
134
*
135
* This is used when an element is expected to exist, and should fail with
136
* an assertion error if it does not (if assertions are enabled).
137
*
138
* @param {string} id Element ID.
139
* @return {!Element} The element with the given ID, if it exists.
140
*/
141
goog.dom.getRequiredElement = function(id) {
142
return goog.dom.getRequiredElementHelper_(document, id);
143
};
144
145
146
/**
147
* Helper function for getRequiredElementHelper functions, both static and
148
* on DomHelper. Asserts the element with the given id exists.
149
* @param {!Document} doc
150
* @param {string} id
151
* @return {!Element} The element with the given ID, if it exists.
152
* @private
153
*/
154
goog.dom.getRequiredElementHelper_ = function(doc, id) {
155
// To prevent users passing in Elements as is permitted in getElement().
156
goog.asserts.assertString(id);
157
var element = goog.dom.getElementHelper_(doc, id);
158
element =
159
goog.asserts.assertElement(element, 'No element found with id: ' + id);
160
return element;
161
};
162
163
164
/**
165
* Alias for getElement.
166
* @param {string|Element} element Element ID or a DOM node.
167
* @return {Element} The element with the given ID, or the node passed in.
168
* @deprecated Use {@link goog.dom.getElement} instead.
169
*/
170
goog.dom.$ = goog.dom.getElement;
171
172
173
/**
174
* Gets elements by tag name.
175
* @param {!goog.dom.TagName<T>} tagName
176
* @param {(!Document|!Element)=} opt_parent Parent element or document where to
177
* look for elements. Defaults to document.
178
* @return {!NodeList<R>} List of elements. The members of the list are
179
* {!Element} if tagName is not a member of goog.dom.TagName or more
180
* specific types if it is (e.g. {!HTMLAnchorElement} for
181
* goog.dom.TagName.A).
182
* @template T
183
* @template R := cond(isUnknown(T), 'Element', T) =:
184
*/
185
goog.dom.getElementsByTagName = function(tagName, opt_parent) {
186
var parent = opt_parent || document;
187
return parent.getElementsByTagName(String(tagName));
188
};
189
190
191
/**
192
* Looks up elements by both tag and class name, using browser native functions
193
* ({@code querySelectorAll}, {@code getElementsByTagName} or
194
* {@code getElementsByClassName}) where possible. This function
195
* is a useful, if limited, way of collecting a list of DOM elements
196
* with certain characteristics. {@code goog.dom.query} offers a
197
* more powerful and general solution which allows matching on CSS3
198
* selector expressions, but at increased cost in code size. If all you
199
* need is particular tags belonging to a single class, this function
200
* is fast and sleek.
201
*
202
* Note that tag names are case sensitive in the SVG namespace, and this
203
* function converts opt_tag to uppercase for comparisons. For queries in the
204
* SVG namespace you should use querySelector or querySelectorAll instead.
205
* https://bugzilla.mozilla.org/show_bug.cgi?id=963870
206
* https://bugs.webkit.org/show_bug.cgi?id=83438
207
*
208
* @see {goog.dom.query}
209
*
210
* @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name.
211
* @param {?string=} opt_class Optional class name.
212
* @param {(Document|Element)=} opt_el Optional element to look in.
213
* @return {!IArrayLike<R>} Array-like list of elements (only a length property
214
* and numerical indices are guaranteed to exist). The members of the array
215
* are {!Element} if opt_tag is not a member of goog.dom.TagName or more
216
* specific types if it is (e.g. {!HTMLAnchorElement} for
217
* goog.dom.TagName.A).
218
* @template T
219
* @template R := cond(isUnknown(T), 'Element', T) =:
220
*/
221
goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
222
return goog.dom.getElementsByTagNameAndClass_(
223
document, opt_tag, opt_class, opt_el);
224
};
225
226
227
/**
228
* Returns a static, array-like list of the elements with the provided
229
* className.
230
* @see {goog.dom.query}
231
* @param {string} className the name of the class to look for.
232
* @param {(Document|Element)=} opt_el Optional element to look in.
233
* @return {!IArrayLike<!Element>} The items found with the class name provided.
234
*/
235
goog.dom.getElementsByClass = function(className, opt_el) {
236
var parent = opt_el || document;
237
if (goog.dom.canUseQuerySelector_(parent)) {
238
return parent.querySelectorAll('.' + className);
239
}
240
return goog.dom.getElementsByTagNameAndClass_(
241
document, '*', className, opt_el);
242
};
243
244
245
/**
246
* Returns the first element with the provided className.
247
* @see {goog.dom.query}
248
* @param {string} className the name of the class to look for.
249
* @param {Element|Document=} opt_el Optional element to look in.
250
* @return {Element} The first item with the class name provided.
251
*/
252
goog.dom.getElementByClass = function(className, opt_el) {
253
var parent = opt_el || document;
254
var retVal = null;
255
if (parent.getElementsByClassName) {
256
retVal = parent.getElementsByClassName(className)[0];
257
} else if (goog.dom.canUseQuerySelector_(parent)) {
258
retVal = parent.querySelector('.' + className);
259
} else {
260
retVal = goog.dom.getElementsByTagNameAndClass_(
261
document, '*', className, opt_el)[0];
262
}
263
return retVal || null;
264
};
265
266
267
/**
268
* Ensures an element with the given className exists, and then returns the
269
* first element with the provided className.
270
* @see {goog.dom.query}
271
* @param {string} className the name of the class to look for.
272
* @param {!Element|!Document=} opt_root Optional element or document to look
273
* in.
274
* @return {!Element} The first item with the class name provided.
275
* @throws {goog.asserts.AssertionError} Thrown if no element is found.
276
*/
277
goog.dom.getRequiredElementByClass = function(className, opt_root) {
278
var retValue = goog.dom.getElementByClass(className, opt_root);
279
return goog.asserts.assert(
280
retValue, 'No element found with className: ' + className);
281
};
282
283
284
/**
285
* Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
286
* fast W3C Selectors API.
287
* @param {!(Element|Document)} parent The parent document object.
288
* @return {boolean} whether or not we can use parent.querySelector* APIs.
289
* @private
290
*/
291
goog.dom.canUseQuerySelector_ = function(parent) {
292
return !!(parent.querySelectorAll && parent.querySelector);
293
};
294
295
296
/**
297
* Helper for {@code getElementsByTagNameAndClass}.
298
* @param {!Document} doc The document to get the elements in.
299
* @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name.
300
* @param {?string=} opt_class Optional class name.
301
* @param {(Document|Element)=} opt_el Optional element to look in.
302
* @return {!IArrayLike<R>} Array-like list of elements (only a length property
303
* and numerical indices are guaranteed to exist). The members of the array
304
* are {!Element} if opt_tag is not a member of goog.dom.TagName or more
305
* specific types if it is (e.g. {!HTMLAnchorElement} for
306
* goog.dom.TagName.A).
307
* @template T
308
* @template R := cond(isUnknown(T), 'Element', T) =:
309
* @private
310
*/
311
goog.dom.getElementsByTagNameAndClass_ = function(
312
doc, opt_tag, opt_class, opt_el) {
313
var parent = opt_el || doc;
314
var tagName =
315
(opt_tag && opt_tag != '*') ? String(opt_tag).toUpperCase() : '';
316
317
if (goog.dom.canUseQuerySelector_(parent) && (tagName || opt_class)) {
318
var query = tagName + (opt_class ? '.' + opt_class : '');
319
return parent.querySelectorAll(query);
320
}
321
322
// Use the native getElementsByClassName if available, under the assumption
323
// that even when the tag name is specified, there will be fewer elements to
324
// filter through when going by class than by tag name
325
if (opt_class && parent.getElementsByClassName) {
326
var els = parent.getElementsByClassName(opt_class);
327
328
if (tagName) {
329
var arrayLike = {};
330
var len = 0;
331
332
// Filter for specific tags if requested.
333
for (var i = 0, el; el = els[i]; i++) {
334
if (tagName == el.nodeName) {
335
arrayLike[len++] = el;
336
}
337
}
338
arrayLike.length = len;
339
340
return /** @type {!IArrayLike<!Element>} */ (arrayLike);
341
} else {
342
return els;
343
}
344
}
345
346
var els = parent.getElementsByTagName(tagName || '*');
347
348
if (opt_class) {
349
var arrayLike = {};
350
var len = 0;
351
for (var i = 0, el; el = els[i]; i++) {
352
var className = el.className;
353
// Check if className has a split function since SVG className does not.
354
if (typeof className.split == 'function' &&
355
goog.array.contains(className.split(/\s+/), opt_class)) {
356
arrayLike[len++] = el;
357
}
358
}
359
arrayLike.length = len;
360
return /** @type {!IArrayLike<!Element>} */ (arrayLike);
361
} else {
362
return els;
363
}
364
};
365
366
367
/**
368
* Alias for {@code getElementsByTagNameAndClass}.
369
* @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name.
370
* @param {?string=} opt_class Optional class name.
371
* @param {Element=} opt_el Optional element to look in.
372
* @return {!IArrayLike<R>} Array-like list of elements (only a length property
373
* and numerical indices are guaranteed to exist). The members of the array
374
* are {!Element} if opt_tag is not a member of goog.dom.TagName or more
375
* specific types if it is (e.g. {!HTMLAnchorElement} for
376
* goog.dom.TagName.A).
377
* @template T
378
* @template R := cond(isUnknown(T), 'Element', T) =:
379
* @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
380
*/
381
goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
382
383
384
/**
385
* Sets multiple properties, and sometimes attributes, on an element. Note that
386
* properties are simply object properties on the element instance, while
387
* attributes are visible in the DOM. Many properties map to attributes with the
388
* same names, some with different names, and there are also unmappable cases.
389
*
390
* This method sets properties by default (which means that custom attributes
391
* are not supported). These are the exeptions (some of which is legacy):
392
* - "style": Even though this is an attribute name, it is translated to a
393
* property, "style.cssText". Note that this property sanitizes and formats
394
* its value, unlike the attribute.
395
* - "class": This is an attribute name, it is translated to the "className"
396
* property.
397
* - "for": This is an attribute name, it is translated to the "htmlFor"
398
* property.
399
* - Entries in {@see goog.dom.DIRECT_ATTRIBUTE_MAP_} are set as attributes,
400
* this is probably due to browser quirks.
401
* - "aria-*", "data-*": Always set as attributes, they have no property
402
* counterparts.
403
*
404
* @param {Element} element DOM node to set properties on.
405
* @param {Object} properties Hash of property:value pairs.
406
* Property values can be strings or goog.string.TypedString values (such as
407
* goog.html.SafeUrl).
408
*/
409
goog.dom.setProperties = function(element, properties) {
410
goog.object.forEach(properties, function(val, key) {
411
if (val && val.implementsGoogStringTypedString) {
412
val = val.getTypedStringValue();
413
}
414
if (key == 'style') {
415
element.style.cssText = val;
416
} else if (key == 'class') {
417
element.className = val;
418
} else if (key == 'for') {
419
element.htmlFor = val;
420
} else if (goog.dom.DIRECT_ATTRIBUTE_MAP_.hasOwnProperty(key)) {
421
element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
422
} else if (
423
goog.string.startsWith(key, 'aria-') ||
424
goog.string.startsWith(key, 'data-')) {
425
element.setAttribute(key, val);
426
} else {
427
element[key] = val;
428
}
429
});
430
};
431
432
433
/**
434
* Map of attributes that should be set using
435
* element.setAttribute(key, val) instead of element[key] = val. Used
436
* by goog.dom.setProperties.
437
*
438
* @private {!Object<string, string>}
439
* @const
440
*/
441
goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
442
'cellpadding': 'cellPadding',
443
'cellspacing': 'cellSpacing',
444
'colspan': 'colSpan',
445
'frameborder': 'frameBorder',
446
'height': 'height',
447
'maxlength': 'maxLength',
448
'nonce': 'nonce',
449
'role': 'role',
450
'rowspan': 'rowSpan',
451
'type': 'type',
452
'usemap': 'useMap',
453
'valign': 'vAlign',
454
'width': 'width'
455
};
456
457
458
/**
459
* Gets the dimensions of the viewport.
460
*
461
* Gecko Standards mode:
462
* docEl.clientWidth Width of viewport excluding scrollbar.
463
* win.innerWidth Width of viewport including scrollbar.
464
* body.clientWidth Width of body element.
465
*
466
* docEl.clientHeight Height of viewport excluding scrollbar.
467
* win.innerHeight Height of viewport including scrollbar.
468
* body.clientHeight Height of document.
469
*
470
* Gecko Backwards compatible mode:
471
* docEl.clientWidth Width of viewport excluding scrollbar.
472
* win.innerWidth Width of viewport including scrollbar.
473
* body.clientWidth Width of viewport excluding scrollbar.
474
*
475
* docEl.clientHeight Height of document.
476
* win.innerHeight Height of viewport including scrollbar.
477
* body.clientHeight Height of viewport excluding scrollbar.
478
*
479
* IE6/7 Standards mode:
480
* docEl.clientWidth Width of viewport excluding scrollbar.
481
* win.innerWidth Undefined.
482
* body.clientWidth Width of body element.
483
*
484
* docEl.clientHeight Height of viewport excluding scrollbar.
485
* win.innerHeight Undefined.
486
* body.clientHeight Height of document element.
487
*
488
* IE5 + IE6/7 Backwards compatible mode:
489
* docEl.clientWidth 0.
490
* win.innerWidth Undefined.
491
* body.clientWidth Width of viewport excluding scrollbar.
492
*
493
* docEl.clientHeight 0.
494
* win.innerHeight Undefined.
495
* body.clientHeight Height of viewport excluding scrollbar.
496
*
497
* Opera 9 Standards and backwards compatible mode:
498
* docEl.clientWidth Width of viewport excluding scrollbar.
499
* win.innerWidth Width of viewport including scrollbar.
500
* body.clientWidth Width of viewport excluding scrollbar.
501
*
502
* docEl.clientHeight Height of document.
503
* win.innerHeight Height of viewport including scrollbar.
504
* body.clientHeight Height of viewport excluding scrollbar.
505
*
506
* WebKit:
507
* Safari 2
508
* docEl.clientHeight Same as scrollHeight.
509
* docEl.clientWidth Same as innerWidth.
510
* win.innerWidth Width of viewport excluding scrollbar.
511
* win.innerHeight Height of the viewport including scrollbar.
512
* frame.innerHeight Height of the viewport exluding scrollbar.
513
*
514
* Safari 3 (tested in 522)
515
*
516
* docEl.clientWidth Width of viewport excluding scrollbar.
517
* docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
518
* body.clientHeight Height of viewport excluding scrollbar in quirks mode.
519
*
520
* @param {Window=} opt_window Optional window element to test.
521
* @return {!goog.math.Size} Object with values 'width' and 'height'.
522
*/
523
goog.dom.getViewportSize = function(opt_window) {
524
// TODO(arv): This should not take an argument
525
return goog.dom.getViewportSize_(opt_window || window);
526
};
527
528
529
/**
530
* Helper for {@code getViewportSize}.
531
* @param {Window} win The window to get the view port size for.
532
* @return {!goog.math.Size} Object with values 'width' and 'height'.
533
* @private
534
*/
535
goog.dom.getViewportSize_ = function(win) {
536
var doc = win.document;
537
var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
538
return new goog.math.Size(el.clientWidth, el.clientHeight);
539
};
540
541
542
/**
543
* Calculates the height of the document.
544
*
545
* @return {number} The height of the current document.
546
*/
547
goog.dom.getDocumentHeight = function() {
548
return goog.dom.getDocumentHeight_(window);
549
};
550
551
/**
552
* Calculates the height of the document of the given window.
553
*
554
* @param {!Window} win The window whose document height to retrieve.
555
* @return {number} The height of the document of the given window.
556
*/
557
goog.dom.getDocumentHeightForWindow = function(win) {
558
return goog.dom.getDocumentHeight_(win);
559
};
560
561
/**
562
* Calculates the height of the document of the given window.
563
*
564
* Function code copied from the opensocial gadget api:
565
* gadgets.window.adjustHeight(opt_height)
566
*
567
* @private
568
* @param {!Window} win The window whose document height to retrieve.
569
* @return {number} The height of the document of the given window.
570
*/
571
goog.dom.getDocumentHeight_ = function(win) {
572
// NOTE(eae): This method will return the window size rather than the document
573
// size in webkit quirks mode.
574
var doc = win.document;
575
var height = 0;
576
577
if (doc) {
578
// Calculating inner content height is hard and different between
579
// browsers rendering in Strict vs. Quirks mode. We use a combination of
580
// three properties within document.body and document.documentElement:
581
// - scrollHeight
582
// - offsetHeight
583
// - clientHeight
584
// These values differ significantly between browsers and rendering modes.
585
// But there are patterns. It just takes a lot of time and persistence
586
// to figure out.
587
588
var body = doc.body;
589
var docEl = /** @type {!HTMLElement} */ (doc.documentElement);
590
if (!(docEl && body)) {
591
return 0;
592
}
593
594
// Get the height of the viewport
595
var vh = goog.dom.getViewportSize_(win).height;
596
if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
597
// In Strict mode:
598
// The inner content height is contained in either:
599
// document.documentElement.scrollHeight
600
// document.documentElement.offsetHeight
601
// Based on studying the values output by different browsers,
602
// use the value that's NOT equal to the viewport height found above.
603
height =
604
docEl.scrollHeight != vh ? docEl.scrollHeight : docEl.offsetHeight;
605
} else {
606
// In Quirks mode:
607
// documentElement.clientHeight is equal to documentElement.offsetHeight
608
// except in IE. In most browsers, document.documentElement can be used
609
// to calculate the inner content height.
610
// However, in other browsers (e.g. IE), document.body must be used
611
// instead. How do we know which one to use?
612
// If document.documentElement.clientHeight does NOT equal
613
// document.documentElement.offsetHeight, then use document.body.
614
var sh = docEl.scrollHeight;
615
var oh = docEl.offsetHeight;
616
if (docEl.clientHeight != oh) {
617
sh = body.scrollHeight;
618
oh = body.offsetHeight;
619
}
620
621
// Detect whether the inner content height is bigger or smaller
622
// than the bounding box (viewport). If bigger, take the larger
623
// value. If smaller, take the smaller value.
624
if (sh > vh) {
625
// Content is larger
626
height = sh > oh ? sh : oh;
627
} else {
628
// Content is smaller
629
height = sh < oh ? sh : oh;
630
}
631
}
632
}
633
634
return height;
635
};
636
637
638
/**
639
* Gets the page scroll distance as a coordinate object.
640
*
641
* @param {Window=} opt_window Optional window element to test.
642
* @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
643
* @deprecated Use {@link goog.dom.getDocumentScroll} instead.
644
*/
645
goog.dom.getPageScroll = function(opt_window) {
646
var win = opt_window || goog.global || window;
647
return goog.dom.getDomHelper(win.document).getDocumentScroll();
648
};
649
650
651
/**
652
* Gets the document scroll distance as a coordinate object.
653
*
654
* @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
655
*/
656
goog.dom.getDocumentScroll = function() {
657
return goog.dom.getDocumentScroll_(document);
658
};
659
660
661
/**
662
* Helper for {@code getDocumentScroll}.
663
*
664
* @param {!Document} doc The document to get the scroll for.
665
* @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
666
* @private
667
*/
668
goog.dom.getDocumentScroll_ = function(doc) {
669
var el = goog.dom.getDocumentScrollElement_(doc);
670
var win = goog.dom.getWindow_(doc);
671
if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') &&
672
win.pageYOffset != el.scrollTop) {
673
// The keyboard on IE10 touch devices shifts the page using the pageYOffset
674
// without modifying scrollTop. For this case, we want the body scroll
675
// offsets.
676
return new goog.math.Coordinate(el.scrollLeft, el.scrollTop);
677
}
678
return new goog.math.Coordinate(
679
win.pageXOffset || el.scrollLeft, win.pageYOffset || el.scrollTop);
680
};
681
682
683
/**
684
* Gets the document scroll element.
685
* @return {!Element} Scrolling element.
686
*/
687
goog.dom.getDocumentScrollElement = function() {
688
return goog.dom.getDocumentScrollElement_(document);
689
};
690
691
692
/**
693
* Helper for {@code getDocumentScrollElement}.
694
* @param {!Document} doc The document to get the scroll element for.
695
* @return {!Element} Scrolling element.
696
* @private
697
*/
698
goog.dom.getDocumentScrollElement_ = function(doc) {
699
// Old WebKit needs body.scrollLeft in both quirks mode and strict mode. We
700
// also default to the documentElement if the document does not have a body
701
// (e.g. a SVG document).
702
// Uses http://dev.w3.org/csswg/cssom-view/#dom-document-scrollingelement to
703
// avoid trying to guess about browser behavior from the UA string.
704
if (doc.scrollingElement) {
705
return doc.scrollingElement;
706
}
707
if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) {
708
return doc.documentElement;
709
}
710
return doc.body || doc.documentElement;
711
};
712
713
714
/**
715
* Gets the window object associated with the given document.
716
*
717
* @param {Document=} opt_doc Document object to get window for.
718
* @return {!Window} The window associated with the given document.
719
*/
720
goog.dom.getWindow = function(opt_doc) {
721
// TODO(arv): This should not take an argument.
722
return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
723
};
724
725
726
/**
727
* Helper for {@code getWindow}.
728
*
729
* @param {!Document} doc Document object to get window for.
730
* @return {!Window} The window associated with the given document.
731
* @private
732
*/
733
goog.dom.getWindow_ = function(doc) {
734
return /** @type {!Window} */ (doc.parentWindow || doc.defaultView);
735
};
736
737
738
/**
739
* Returns a dom node with a set of attributes. This function accepts varargs
740
* for subsequent nodes to be added. Subsequent nodes will be added to the
741
* first node as childNodes.
742
*
743
* So:
744
* <code>createDom(goog.dom.TagName.DIV, null, createDom(goog.dom.TagName.P), createDom(goog.dom.TagName.P));</code>
745
* would return a div with two child paragraphs
746
*
747
* For passing properties, please see {@link goog.dom.setProperties} for more
748
* information.
749
*
750
* @param {string|!goog.dom.TagName<T>} tagName Tag to create.
751
* @param {(Object|Array<string>|string)=} opt_properties If object, then a map
752
* of name-value pairs for properties. If a string, then this is the
753
* className of the new element. If an array, the elements will be joined
754
* together as the className of the new element.
755
* @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
756
* strings for text nodes. If one of the var_args is an array or NodeList,
757
* its elements will be added as childNodes instead.
758
* @return {R} Reference to a DOM node. The return type is {!Element} if tagName
759
* is a string or a more specific type if it is a member of
760
* goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
761
* @template T
762
* @template R := cond(isUnknown(T), 'Element', T) =:
763
*/
764
goog.dom.createDom = function(tagName, opt_properties, var_args) {
765
return goog.dom.createDom_(document, arguments);
766
};
767
768
769
/**
770
* Helper for {@code createDom}.
771
* @param {!Document} doc The document to create the DOM in.
772
* @param {!Arguments} args Argument object passed from the callers. See
773
* {@code goog.dom.createDom} for details.
774
* @return {!Element} Reference to a DOM node.
775
* @private
776
*/
777
goog.dom.createDom_ = function(doc, args) {
778
var tagName = String(args[0]);
779
var attributes = args[1];
780
781
// Internet Explorer is dumb:
782
// name: https://msdn.microsoft.com/en-us/library/ms534184(v=vs.85).aspx
783
// type: https://msdn.microsoft.com/en-us/library/ms534700(v=vs.85).aspx
784
// Also does not allow setting of 'type' attribute on 'input' or 'button'.
785
if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
786
(attributes.name || attributes.type)) {
787
var tagNameArr = ['<', tagName];
788
if (attributes.name) {
789
tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), '"');
790
}
791
if (attributes.type) {
792
tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), '"');
793
794
// Clone attributes map to remove 'type' without mutating the input.
795
var clone = {};
796
goog.object.extend(clone, attributes);
797
798
// JSCompiler can't see how goog.object.extend added this property,
799
// because it was essentially added by reflection.
800
// So it needs to be quoted.
801
delete clone['type'];
802
803
attributes = clone;
804
}
805
tagNameArr.push('>');
806
tagName = tagNameArr.join('');
807
}
808
809
var element = doc.createElement(tagName);
810
811
if (attributes) {
812
if (goog.isString(attributes)) {
813
element.className = attributes;
814
} else if (goog.isArray(attributes)) {
815
element.className = attributes.join(' ');
816
} else {
817
goog.dom.setProperties(element, attributes);
818
}
819
}
820
821
if (args.length > 2) {
822
goog.dom.append_(doc, element, args, 2);
823
}
824
825
return element;
826
};
827
828
829
/**
830
* Appends a node with text or other nodes.
831
* @param {!Document} doc The document to create new nodes in.
832
* @param {!Node} parent The node to append nodes to.
833
* @param {!Arguments} args The values to add. See {@code goog.dom.append}.
834
* @param {number} startIndex The index of the array to start from.
835
* @private
836
*/
837
goog.dom.append_ = function(doc, parent, args, startIndex) {
838
function childHandler(child) {
839
// TODO(user): More coercion, ala MochiKit?
840
if (child) {
841
parent.appendChild(
842
goog.isString(child) ? doc.createTextNode(child) : child);
843
}
844
}
845
846
for (var i = startIndex; i < args.length; i++) {
847
var arg = args[i];
848
// TODO(attila): Fix isArrayLike to return false for a text node.
849
if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
850
// If the argument is a node list, not a real array, use a clone,
851
// because forEach can't be used to mutate a NodeList.
852
goog.array.forEach(
853
goog.dom.isNodeList(arg) ? goog.array.toArray(arg) : arg,
854
childHandler);
855
} else {
856
childHandler(arg);
857
}
858
}
859
};
860
861
862
/**
863
* Alias for {@code createDom}.
864
* @param {string|!goog.dom.TagName<T>} tagName Tag to create.
865
* @param {(string|Object)=} opt_properties If object, then a map of name-value
866
* pairs for properties. If a string, then this is the className of the new
867
* element.
868
* @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
869
* strings for text nodes. If one of the var_args is an array, its
870
* children will be added as childNodes instead.
871
* @return {R} Reference to a DOM node. The return type is {!Element} if tagName
872
* is a string or a more specific type if it is a member of
873
* goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
874
* @template T
875
* @template R := cond(isUnknown(T), 'Element', T) =:
876
* @deprecated Use {@link goog.dom.createDom} instead.
877
*/
878
goog.dom.$dom = goog.dom.createDom;
879
880
881
/**
882
* Creates a new element.
883
* @param {string|!goog.dom.TagName<T>} name Tag to create.
884
* @return {R} The new element. The return type is {!Element} if name is
885
* a string or a more specific type if it is a member of goog.dom.TagName
886
* (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
887
* @template T
888
* @template R := cond(isUnknown(T), 'Element', T) =:
889
*/
890
goog.dom.createElement = function(name) {
891
return goog.dom.createElement_(document, name);
892
};
893
894
895
/**
896
* Creates a new element.
897
* @param {!Document} doc The document to create the element in.
898
* @param {string|!goog.dom.TagName<T>} name Tag to create.
899
* @return {R} The new element. The return type is {!Element} if name is
900
* a string or a more specific type if it is a member of goog.dom.TagName
901
* (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
902
* @template T
903
* @template R := cond(isUnknown(T), 'Element', T) =:
904
* @private
905
*/
906
goog.dom.createElement_ = function(doc, name) {
907
return doc.createElement(String(name));
908
};
909
910
911
/**
912
* Creates a new text node.
913
* @param {number|string} content Content.
914
* @return {!Text} The new text node.
915
*/
916
goog.dom.createTextNode = function(content) {
917
return document.createTextNode(String(content));
918
};
919
920
921
/**
922
* Create a table.
923
* @param {number} rows The number of rows in the table. Must be >= 1.
924
* @param {number} columns The number of columns in the table. Must be >= 1.
925
* @param {boolean=} opt_fillWithNbsp If true, fills table entries with
926
* {@code goog.string.Unicode.NBSP} characters.
927
* @return {!Element} The created table.
928
*/
929
goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
930
// TODO(mlourenco): Return HTMLTableElement, also in prototype function.
931
// Callers need to be updated to e.g. not assign numbers to table.cellSpacing.
932
return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp);
933
};
934
935
936
/**
937
* Create a table.
938
* @param {!Document} doc Document object to use to create the table.
939
* @param {number} rows The number of rows in the table. Must be >= 1.
940
* @param {number} columns The number of columns in the table. Must be >= 1.
941
* @param {boolean} fillWithNbsp If true, fills table entries with
942
* {@code goog.string.Unicode.NBSP} characters.
943
* @return {!HTMLTableElement} The created table.
944
* @private
945
*/
946
goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
947
var table = goog.dom.createElement_(doc, goog.dom.TagName.TABLE);
948
var tbody =
949
table.appendChild(goog.dom.createElement_(doc, goog.dom.TagName.TBODY));
950
for (var i = 0; i < rows; i++) {
951
var tr = goog.dom.createElement_(doc, goog.dom.TagName.TR);
952
for (var j = 0; j < columns; j++) {
953
var td = goog.dom.createElement_(doc, goog.dom.TagName.TD);
954
// IE <= 9 will create a text node if we set text content to the empty
955
// string, so we avoid doing it unless necessary. This ensures that the
956
// same DOM tree is returned on all browsers.
957
if (fillWithNbsp) {
958
goog.dom.setTextContent(td, goog.string.Unicode.NBSP);
959
}
960
tr.appendChild(td);
961
}
962
tbody.appendChild(tr);
963
}
964
return table;
965
};
966
967
968
969
/**
970
* Creates a new Node from constant strings of HTML markup.
971
* @param {...!goog.string.Const} var_args The HTML strings to concatenate then
972
* convert into a node.
973
* @return {!Node}
974
*/
975
goog.dom.constHtmlToNode = function(var_args) {
976
var stringArray = goog.array.map(arguments, goog.string.Const.unwrap);
977
var safeHtml =
978
goog.html.uncheckedconversions
979
.safeHtmlFromStringKnownToSatisfyTypeContract(
980
goog.string.Const.from(
981
'Constant HTML string, that gets turned into a ' +
982
'Node later, so it will be automatically balanced.'),
983
stringArray.join(''));
984
return goog.dom.safeHtmlToNode(safeHtml);
985
};
986
987
988
/**
989
* Converts HTML markup into a node. This is a safe version of
990
* {@code goog.dom.htmlToDocumentFragment} which is now deleted.
991
* @param {!goog.html.SafeHtml} html The HTML markup to convert.
992
* @return {!Node} The resulting node.
993
*/
994
goog.dom.safeHtmlToNode = function(html) {
995
return goog.dom.safeHtmlToNode_(document, html);
996
};
997
998
999
/**
1000
* Helper for {@code safeHtmlToNode}.
1001
* @param {!Document} doc The document.
1002
* @param {!goog.html.SafeHtml} html The HTML markup to convert.
1003
* @return {!Node} The resulting node.
1004
* @private
1005
*/
1006
goog.dom.safeHtmlToNode_ = function(doc, html) {
1007
var tempDiv = goog.dom.createElement_(doc, goog.dom.TagName.DIV);
1008
if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
1009
goog.dom.safe.setInnerHtml(
1010
tempDiv, goog.html.SafeHtml.concat(goog.html.SafeHtml.BR, html));
1011
tempDiv.removeChild(tempDiv.firstChild);
1012
} else {
1013
goog.dom.safe.setInnerHtml(tempDiv, html);
1014
}
1015
return goog.dom.childrenToNode_(doc, tempDiv);
1016
};
1017
1018
1019
/**
1020
* Helper for {@code safeHtmlToNode_}.
1021
* @param {!Document} doc The document.
1022
* @param {!Node} tempDiv The input node.
1023
* @return {!Node} The resulting node.
1024
* @private
1025
*/
1026
goog.dom.childrenToNode_ = function(doc, tempDiv) {
1027
if (tempDiv.childNodes.length == 1) {
1028
return tempDiv.removeChild(tempDiv.firstChild);
1029
} else {
1030
var fragment = doc.createDocumentFragment();
1031
while (tempDiv.firstChild) {
1032
fragment.appendChild(tempDiv.firstChild);
1033
}
1034
return fragment;
1035
}
1036
};
1037
1038
1039
/**
1040
* Returns true if the browser is in "CSS1-compatible" (standards-compliant)
1041
* mode, false otherwise.
1042
* @return {boolean} True if in CSS1-compatible mode.
1043
*/
1044
goog.dom.isCss1CompatMode = function() {
1045
return goog.dom.isCss1CompatMode_(document);
1046
};
1047
1048
1049
/**
1050
* Returns true if the browser is in "CSS1-compatible" (standards-compliant)
1051
* mode, false otherwise.
1052
* @param {!Document} doc The document to check.
1053
* @return {boolean} True if in CSS1-compatible mode.
1054
* @private
1055
*/
1056
goog.dom.isCss1CompatMode_ = function(doc) {
1057
if (goog.dom.COMPAT_MODE_KNOWN_) {
1058
return goog.dom.ASSUME_STANDARDS_MODE;
1059
}
1060
1061
return doc.compatMode == 'CSS1Compat';
1062
};
1063
1064
1065
/**
1066
* Determines if the given node can contain children, intended to be used for
1067
* HTML generation.
1068
*
1069
* IE natively supports node.canHaveChildren but has inconsistent behavior.
1070
* Prior to IE8 the base tag allows children and in IE9 all nodes return true
1071
* for canHaveChildren.
1072
*
1073
* In practice all non-IE browsers allow you to add children to any node, but
1074
* the behavior is inconsistent:
1075
*
1076
* <pre>
1077
* var a = goog.dom.createElement(goog.dom.TagName.BR);
1078
* a.appendChild(document.createTextNode('foo'));
1079
* a.appendChild(document.createTextNode('bar'));
1080
* console.log(a.childNodes.length); // 2
1081
* console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar"
1082
* </pre>
1083
*
1084
* For more information, see:
1085
* http://dev.w3.org/html5/markup/syntax.html#syntax-elements
1086
*
1087
* TODO(user): Rename shouldAllowChildren() ?
1088
*
1089
* @param {Node} node The node to check.
1090
* @return {boolean} Whether the node can contain children.
1091
*/
1092
goog.dom.canHaveChildren = function(node) {
1093
if (node.nodeType != goog.dom.NodeType.ELEMENT) {
1094
return false;
1095
}
1096
switch (/** @type {!Element} */ (node).tagName) {
1097
case String(goog.dom.TagName.APPLET):
1098
case String(goog.dom.TagName.AREA):
1099
case String(goog.dom.TagName.BASE):
1100
case String(goog.dom.TagName.BR):
1101
case String(goog.dom.TagName.COL):
1102
case String(goog.dom.TagName.COMMAND):
1103
case String(goog.dom.TagName.EMBED):
1104
case String(goog.dom.TagName.FRAME):
1105
case String(goog.dom.TagName.HR):
1106
case String(goog.dom.TagName.IMG):
1107
case String(goog.dom.TagName.INPUT):
1108
case String(goog.dom.TagName.IFRAME):
1109
case String(goog.dom.TagName.ISINDEX):
1110
case String(goog.dom.TagName.KEYGEN):
1111
case String(goog.dom.TagName.LINK):
1112
case String(goog.dom.TagName.NOFRAMES):
1113
case String(goog.dom.TagName.NOSCRIPT):
1114
case String(goog.dom.TagName.META):
1115
case String(goog.dom.TagName.OBJECT):
1116
case String(goog.dom.TagName.PARAM):
1117
case String(goog.dom.TagName.SCRIPT):
1118
case String(goog.dom.TagName.SOURCE):
1119
case String(goog.dom.TagName.STYLE):
1120
case String(goog.dom.TagName.TRACK):
1121
case String(goog.dom.TagName.WBR):
1122
return false;
1123
}
1124
return true;
1125
};
1126
1127
1128
/**
1129
* Appends a child to a node.
1130
* @param {Node} parent Parent.
1131
* @param {Node} child Child.
1132
*/
1133
goog.dom.appendChild = function(parent, child) {
1134
parent.appendChild(child);
1135
};
1136
1137
1138
/**
1139
* Appends a node with text or other nodes.
1140
* @param {!Node} parent The node to append nodes to.
1141
* @param {...goog.dom.Appendable} var_args The things to append to the node.
1142
* If this is a Node it is appended as is.
1143
* If this is a string then a text node is appended.
1144
* If this is an array like object then fields 0 to length - 1 are appended.
1145
*/
1146
goog.dom.append = function(parent, var_args) {
1147
goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
1148
};
1149
1150
1151
/**
1152
* Removes all the child nodes on a DOM node.
1153
* @param {Node} node Node to remove children from.
1154
*/
1155
goog.dom.removeChildren = function(node) {
1156
// Note: Iterations over live collections can be slow, this is the fastest
1157
// we could find. The double parenthesis are used to prevent JsCompiler and
1158
// strict warnings.
1159
var child;
1160
while ((child = node.firstChild)) {
1161
node.removeChild(child);
1162
}
1163
};
1164
1165
1166
/**
1167
* Inserts a new node before an existing reference node (i.e. as the previous
1168
* sibling). If the reference node has no parent, then does nothing.
1169
* @param {Node} newNode Node to insert.
1170
* @param {Node} refNode Reference node to insert before.
1171
*/
1172
goog.dom.insertSiblingBefore = function(newNode, refNode) {
1173
if (refNode.parentNode) {
1174
refNode.parentNode.insertBefore(newNode, refNode);
1175
}
1176
};
1177
1178
1179
/**
1180
* Inserts a new node after an existing reference node (i.e. as the next
1181
* sibling). If the reference node has no parent, then does nothing.
1182
* @param {Node} newNode Node to insert.
1183
* @param {Node} refNode Reference node to insert after.
1184
*/
1185
goog.dom.insertSiblingAfter = function(newNode, refNode) {
1186
if (refNode.parentNode) {
1187
refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
1188
}
1189
};
1190
1191
1192
/**
1193
* Insert a child at a given index. If index is larger than the number of child
1194
* nodes that the parent currently has, the node is inserted as the last child
1195
* node.
1196
* @param {Element} parent The element into which to insert the child.
1197
* @param {Node} child The element to insert.
1198
* @param {number} index The index at which to insert the new child node. Must
1199
* not be negative.
1200
*/
1201
goog.dom.insertChildAt = function(parent, child, index) {
1202
// Note that if the second argument is null, insertBefore
1203
// will append the child at the end of the list of children.
1204
parent.insertBefore(child, parent.childNodes[index] || null);
1205
};
1206
1207
1208
/**
1209
* Removes a node from its parent.
1210
* @param {Node} node The node to remove.
1211
* @return {Node} The node removed if removed; else, null.
1212
*/
1213
goog.dom.removeNode = function(node) {
1214
return node && node.parentNode ? node.parentNode.removeChild(node) : null;
1215
};
1216
1217
1218
/**
1219
* Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
1220
* parent.
1221
* @param {Node} newNode Node to insert.
1222
* @param {Node} oldNode Node to replace.
1223
*/
1224
goog.dom.replaceNode = function(newNode, oldNode) {
1225
var parent = oldNode.parentNode;
1226
if (parent) {
1227
parent.replaceChild(newNode, oldNode);
1228
}
1229
};
1230
1231
1232
/**
1233
* Flattens an element. That is, removes it and replace it with its children.
1234
* Does nothing if the element is not in the document.
1235
* @param {Element} element The element to flatten.
1236
* @return {Element|undefined} The original element, detached from the document
1237
* tree, sans children; or undefined, if the element was not in the document
1238
* to begin with.
1239
*/
1240
goog.dom.flattenElement = function(element) {
1241
var child, parent = element.parentNode;
1242
if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
1243
// Use IE DOM method (supported by Opera too) if available
1244
if (element.removeNode) {
1245
return /** @type {Element} */ (element.removeNode(false));
1246
} else {
1247
// Move all children of the original node up one level.
1248
while ((child = element.firstChild)) {
1249
parent.insertBefore(child, element);
1250
}
1251
1252
// Detach the original element.
1253
return /** @type {Element} */ (goog.dom.removeNode(element));
1254
}
1255
}
1256
};
1257
1258
1259
/**
1260
* Returns an array containing just the element children of the given element.
1261
* @param {Element} element The element whose element children we want.
1262
* @return {!(Array<!Element>|NodeList<!Element>)} An array or array-like list
1263
* of just the element children of the given element.
1264
*/
1265
goog.dom.getChildren = function(element) {
1266
// We check if the children attribute is supported for child elements
1267
// since IE8 misuses the attribute by also including comments.
1268
if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE &&
1269
element.children != undefined) {
1270
return element.children;
1271
}
1272
// Fall back to manually filtering the element's child nodes.
1273
return goog.array.filter(element.childNodes, function(node) {
1274
return node.nodeType == goog.dom.NodeType.ELEMENT;
1275
});
1276
};
1277
1278
1279
/**
1280
* Returns the first child node that is an element.
1281
* @param {Node} node The node to get the first child element of.
1282
* @return {Element} The first child node of {@code node} that is an element.
1283
*/
1284
goog.dom.getFirstElementChild = function(node) {
1285
if (goog.isDef(node.firstElementChild)) {
1286
return /** @type {!Element} */ (node).firstElementChild;
1287
}
1288
return goog.dom.getNextElementNode_(node.firstChild, true);
1289
};
1290
1291
1292
/**
1293
* Returns the last child node that is an element.
1294
* @param {Node} node The node to get the last child element of.
1295
* @return {Element} The last child node of {@code node} that is an element.
1296
*/
1297
goog.dom.getLastElementChild = function(node) {
1298
if (goog.isDef(node.lastElementChild)) {
1299
return /** @type {!Element} */ (node).lastElementChild;
1300
}
1301
return goog.dom.getNextElementNode_(node.lastChild, false);
1302
};
1303
1304
1305
/**
1306
* Returns the first next sibling that is an element.
1307
* @param {Node} node The node to get the next sibling element of.
1308
* @return {Element} The next sibling of {@code node} that is an element.
1309
*/
1310
goog.dom.getNextElementSibling = function(node) {
1311
if (goog.isDef(node.nextElementSibling)) {
1312
return /** @type {!Element} */ (node).nextElementSibling;
1313
}
1314
return goog.dom.getNextElementNode_(node.nextSibling, true);
1315
};
1316
1317
1318
/**
1319
* Returns the first previous sibling that is an element.
1320
* @param {Node} node The node to get the previous sibling element of.
1321
* @return {Element} The first previous sibling of {@code node} that is
1322
* an element.
1323
*/
1324
goog.dom.getPreviousElementSibling = function(node) {
1325
if (goog.isDef(node.previousElementSibling)) {
1326
return /** @type {!Element} */ (node).previousElementSibling;
1327
}
1328
return goog.dom.getNextElementNode_(node.previousSibling, false);
1329
};
1330
1331
1332
/**
1333
* Returns the first node that is an element in the specified direction,
1334
* starting with {@code node}.
1335
* @param {Node} node The node to get the next element from.
1336
* @param {boolean} forward Whether to look forwards or backwards.
1337
* @return {Element} The first element.
1338
* @private
1339
*/
1340
goog.dom.getNextElementNode_ = function(node, forward) {
1341
while (node && node.nodeType != goog.dom.NodeType.ELEMENT) {
1342
node = forward ? node.nextSibling : node.previousSibling;
1343
}
1344
1345
return /** @type {Element} */ (node);
1346
};
1347
1348
1349
/**
1350
* Returns the next node in source order from the given node.
1351
* @param {Node} node The node.
1352
* @return {Node} The next node in the DOM tree, or null if this was the last
1353
* node.
1354
*/
1355
goog.dom.getNextNode = function(node) {
1356
if (!node) {
1357
return null;
1358
}
1359
1360
if (node.firstChild) {
1361
return node.firstChild;
1362
}
1363
1364
while (node && !node.nextSibling) {
1365
node = node.parentNode;
1366
}
1367
1368
return node ? node.nextSibling : null;
1369
};
1370
1371
1372
/**
1373
* Returns the previous node in source order from the given node.
1374
* @param {Node} node The node.
1375
* @return {Node} The previous node in the DOM tree, or null if this was the
1376
* first node.
1377
*/
1378
goog.dom.getPreviousNode = function(node) {
1379
if (!node) {
1380
return null;
1381
}
1382
1383
if (!node.previousSibling) {
1384
return node.parentNode;
1385
}
1386
1387
node = node.previousSibling;
1388
while (node && node.lastChild) {
1389
node = node.lastChild;
1390
}
1391
1392
return node;
1393
};
1394
1395
1396
/**
1397
* Whether the object looks like a DOM node.
1398
* @param {?} obj The object being tested for node likeness.
1399
* @return {boolean} Whether the object looks like a DOM node.
1400
*/
1401
goog.dom.isNodeLike = function(obj) {
1402
return goog.isObject(obj) && obj.nodeType > 0;
1403
};
1404
1405
1406
/**
1407
* Whether the object looks like an Element.
1408
* @param {?} obj The object being tested for Element likeness.
1409
* @return {boolean} Whether the object looks like an Element.
1410
*/
1411
goog.dom.isElement = function(obj) {
1412
return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
1413
};
1414
1415
1416
/**
1417
* Returns true if the specified value is a Window object. This includes the
1418
* global window for HTML pages, and iframe windows.
1419
* @param {?} obj Variable to test.
1420
* @return {boolean} Whether the variable is a window.
1421
*/
1422
goog.dom.isWindow = function(obj) {
1423
return goog.isObject(obj) && obj['window'] == obj;
1424
};
1425
1426
1427
/**
1428
* Returns an element's parent, if it's an Element.
1429
* @param {Element} element The DOM element.
1430
* @return {Element} The parent, or null if not an Element.
1431
*/
1432
goog.dom.getParentElement = function(element) {
1433
var parent;
1434
if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) {
1435
var isIe9 = goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9') &&
1436
!goog.userAgent.isVersionOrHigher('10');
1437
// SVG elements in IE9 can't use the parentElement property.
1438
// goog.global['SVGElement'] is not defined in IE9 quirks mode.
1439
if (!(isIe9 && goog.global['SVGElement'] &&
1440
element instanceof goog.global['SVGElement'])) {
1441
parent = element.parentElement;
1442
if (parent) {
1443
return parent;
1444
}
1445
}
1446
}
1447
parent = element.parentNode;
1448
return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null;
1449
};
1450
1451
1452
/**
1453
* Whether a node contains another node.
1454
* @param {?Node|undefined} parent The node that should contain the other node.
1455
* @param {?Node|undefined} descendant The node to test presence of.
1456
* @return {boolean} Whether the parent node contains the descendent node.
1457
*/
1458
goog.dom.contains = function(parent, descendant) {
1459
if (!parent || !descendant) {
1460
return false;
1461
}
1462
// We use browser specific methods for this if available since it is faster
1463
// that way.
1464
1465
// IE DOM
1466
if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
1467
return parent == descendant || parent.contains(descendant);
1468
}
1469
1470
// W3C DOM Level 3
1471
if (typeof parent.compareDocumentPosition != 'undefined') {
1472
return parent == descendant ||
1473
Boolean(parent.compareDocumentPosition(descendant) & 16);
1474
}
1475
1476
// W3C DOM Level 1
1477
while (descendant && parent != descendant) {
1478
descendant = descendant.parentNode;
1479
}
1480
return descendant == parent;
1481
};
1482
1483
1484
/**
1485
* Compares the document order of two nodes, returning 0 if they are the same
1486
* node, a negative number if node1 is before node2, and a positive number if
1487
* node2 is before node1. Note that we compare the order the tags appear in the
1488
* document so in the tree <b><i>text</i></b> the B node is considered to be
1489
* before the I node.
1490
*
1491
* @param {Node} node1 The first node to compare.
1492
* @param {Node} node2 The second node to compare.
1493
* @return {number} 0 if the nodes are the same node, a negative number if node1
1494
* is before node2, and a positive number if node2 is before node1.
1495
*/
1496
goog.dom.compareNodeOrder = function(node1, node2) {
1497
// Fall out quickly for equality.
1498
if (node1 == node2) {
1499
return 0;
1500
}
1501
1502
// Use compareDocumentPosition where available
1503
if (node1.compareDocumentPosition) {
1504
// 4 is the bitmask for FOLLOWS.
1505
return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
1506
}
1507
1508
// Special case for document nodes on IE 7 and 8.
1509
if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
1510
if (node1.nodeType == goog.dom.NodeType.DOCUMENT) {
1511
return -1;
1512
}
1513
if (node2.nodeType == goog.dom.NodeType.DOCUMENT) {
1514
return 1;
1515
}
1516
}
1517
1518
// Process in IE using sourceIndex - we check to see if the first node has
1519
// a source index or if its parent has one.
1520
if ('sourceIndex' in node1 ||
1521
(node1.parentNode && 'sourceIndex' in node1.parentNode)) {
1522
var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
1523
var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
1524
1525
if (isElement1 && isElement2) {
1526
return node1.sourceIndex - node2.sourceIndex;
1527
} else {
1528
var parent1 = node1.parentNode;
1529
var parent2 = node2.parentNode;
1530
1531
if (parent1 == parent2) {
1532
return goog.dom.compareSiblingOrder_(node1, node2);
1533
}
1534
1535
if (!isElement1 && goog.dom.contains(parent1, node2)) {
1536
return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
1537
}
1538
1539
1540
if (!isElement2 && goog.dom.contains(parent2, node1)) {
1541
return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
1542
}
1543
1544
return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
1545
(isElement2 ? node2.sourceIndex : parent2.sourceIndex);
1546
}
1547
}
1548
1549
// For Safari, we compare ranges.
1550
var doc = goog.dom.getOwnerDocument(node1);
1551
1552
var range1, range2;
1553
range1 = doc.createRange();
1554
range1.selectNode(node1);
1555
range1.collapse(true);
1556
1557
range2 = doc.createRange();
1558
range2.selectNode(node2);
1559
range2.collapse(true);
1560
1561
return range1.compareBoundaryPoints(
1562
goog.global['Range'].START_TO_END, range2);
1563
};
1564
1565
1566
/**
1567
* Utility function to compare the position of two nodes, when
1568
* {@code textNode}'s parent is an ancestor of {@code node}. If this entry
1569
* condition is not met, this function will attempt to reference a null object.
1570
* @param {!Node} textNode The textNode to compare.
1571
* @param {Node} node The node to compare.
1572
* @return {number} -1 if node is before textNode, +1 otherwise.
1573
* @private
1574
*/
1575
goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
1576
var parent = textNode.parentNode;
1577
if (parent == node) {
1578
// If textNode is a child of node, then node comes first.
1579
return -1;
1580
}
1581
var sibling = node;
1582
while (sibling.parentNode != parent) {
1583
sibling = sibling.parentNode;
1584
}
1585
return goog.dom.compareSiblingOrder_(sibling, textNode);
1586
};
1587
1588
1589
/**
1590
* Utility function to compare the position of two nodes known to be non-equal
1591
* siblings.
1592
* @param {Node} node1 The first node to compare.
1593
* @param {!Node} node2 The second node to compare.
1594
* @return {number} -1 if node1 is before node2, +1 otherwise.
1595
* @private
1596
*/
1597
goog.dom.compareSiblingOrder_ = function(node1, node2) {
1598
var s = node2;
1599
while ((s = s.previousSibling)) {
1600
if (s == node1) {
1601
// We just found node1 before node2.
1602
return -1;
1603
}
1604
}
1605
1606
// Since we didn't find it, node1 must be after node2.
1607
return 1;
1608
};
1609
1610
1611
/**
1612
* Find the deepest common ancestor of the given nodes.
1613
* @param {...Node} var_args The nodes to find a common ancestor of.
1614
* @return {Node} The common ancestor of the nodes, or null if there is none.
1615
* null will only be returned if two or more of the nodes are from different
1616
* documents.
1617
*/
1618
goog.dom.findCommonAncestor = function(var_args) {
1619
var i, count = arguments.length;
1620
if (!count) {
1621
return null;
1622
} else if (count == 1) {
1623
return arguments[0];
1624
}
1625
1626
var paths = [];
1627
var minLength = Infinity;
1628
for (i = 0; i < count; i++) {
1629
// Compute the list of ancestors.
1630
var ancestors = [];
1631
var node = arguments[i];
1632
while (node) {
1633
ancestors.unshift(node);
1634
node = node.parentNode;
1635
}
1636
1637
// Save the list for comparison.
1638
paths.push(ancestors);
1639
minLength = Math.min(minLength, ancestors.length);
1640
}
1641
var output = null;
1642
for (i = 0; i < minLength; i++) {
1643
var first = paths[0][i];
1644
for (var j = 1; j < count; j++) {
1645
if (first != paths[j][i]) {
1646
return output;
1647
}
1648
}
1649
output = first;
1650
}
1651
return output;
1652
};
1653
1654
1655
/**
1656
* Returns the owner document for a node.
1657
* @param {Node|Window} node The node to get the document for.
1658
* @return {!Document} The document owning the node.
1659
*/
1660
goog.dom.getOwnerDocument = function(node) {
1661
// TODO(nnaze): Update param signature to be non-nullable.
1662
goog.asserts.assert(node, 'Node cannot be null or undefined.');
1663
return /** @type {!Document} */ (
1664
node.nodeType == goog.dom.NodeType.DOCUMENT ? node : node.ownerDocument ||
1665
node.document);
1666
};
1667
1668
1669
/**
1670
* Cross-browser function for getting the document element of a frame or iframe.
1671
* @param {Element} frame Frame element.
1672
* @return {!Document} The frame content document.
1673
*/
1674
goog.dom.getFrameContentDocument = function(frame) {
1675
return frame.contentDocument ||
1676
/** @type {!HTMLFrameElement} */ (frame).contentWindow.document;
1677
};
1678
1679
1680
/**
1681
* Cross-browser function for getting the window of a frame or iframe.
1682
* @param {Element} frame Frame element.
1683
* @return {Window} The window associated with the given frame, or null if none
1684
* exists.
1685
*/
1686
goog.dom.getFrameContentWindow = function(frame) {
1687
try {
1688
return frame.contentWindow ||
1689
(frame.contentDocument ? goog.dom.getWindow(frame.contentDocument) :
1690
null);
1691
} catch (e) {
1692
// NOTE(user): In IE8, checking the contentWindow or contentDocument
1693
// properties will throw a "Unspecified Error" exception if the iframe is
1694
// not inserted in the DOM. If we get this we can be sure that no window
1695
// exists, so return null.
1696
}
1697
return null;
1698
};
1699
1700
1701
/**
1702
* Sets the text content of a node, with cross-browser support.
1703
* @param {Node} node The node to change the text content of.
1704
* @param {string|number} text The value that should replace the node's content.
1705
*/
1706
goog.dom.setTextContent = function(node, text) {
1707
goog.asserts.assert(
1708
node != null,
1709
'goog.dom.setTextContent expects a non-null value for node');
1710
1711
if ('textContent' in node) {
1712
node.textContent = text;
1713
} else if (node.nodeType == goog.dom.NodeType.TEXT) {
1714
node.data = text;
1715
} else if (
1716
node.firstChild && node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
1717
// If the first child is a text node we just change its data and remove the
1718
// rest of the children.
1719
while (node.lastChild != node.firstChild) {
1720
node.removeChild(node.lastChild);
1721
}
1722
node.firstChild.data = text;
1723
} else {
1724
goog.dom.removeChildren(node);
1725
var doc = goog.dom.getOwnerDocument(node);
1726
node.appendChild(doc.createTextNode(String(text)));
1727
}
1728
};
1729
1730
1731
/**
1732
* Gets the outerHTML of a node, which islike innerHTML, except that it
1733
* actually contains the HTML of the node itself.
1734
* @param {Element} element The element to get the HTML of.
1735
* @return {string} The outerHTML of the given element.
1736
*/
1737
goog.dom.getOuterHtml = function(element) {
1738
goog.asserts.assert(
1739
element !== null,
1740
'goog.dom.getOuterHtml expects a non-null value for element');
1741
// IE, Opera and WebKit all have outerHTML.
1742
if ('outerHTML' in element) {
1743
return element.outerHTML;
1744
} else {
1745
var doc = goog.dom.getOwnerDocument(element);
1746
var div = goog.dom.createElement_(doc, goog.dom.TagName.DIV);
1747
div.appendChild(element.cloneNode(true));
1748
return div.innerHTML;
1749
}
1750
};
1751
1752
1753
/**
1754
* Finds the first descendant node that matches the filter function, using
1755
* a depth first search. This function offers the most general purpose way
1756
* of finding a matching element. You may also wish to consider
1757
* {@code goog.dom.query} which can express many matching criteria using
1758
* CSS selector expressions. These expressions often result in a more
1759
* compact representation of the desired result.
1760
* @see goog.dom.query
1761
*
1762
* @param {Node} root The root of the tree to search.
1763
* @param {function(Node) : boolean} p The filter function.
1764
* @return {Node|undefined} The found node or undefined if none is found.
1765
*/
1766
goog.dom.findNode = function(root, p) {
1767
var rv = [];
1768
var found = goog.dom.findNodes_(root, p, rv, true);
1769
return found ? rv[0] : undefined;
1770
};
1771
1772
1773
/**
1774
* Finds all the descendant nodes that match the filter function, using a
1775
* a depth first search. This function offers the most general-purpose way
1776
* of finding a set of matching elements. You may also wish to consider
1777
* {@code goog.dom.query} which can express many matching criteria using
1778
* CSS selector expressions. These expressions often result in a more
1779
* compact representation of the desired result.
1780
1781
* @param {Node} root The root of the tree to search.
1782
* @param {function(Node) : boolean} p The filter function.
1783
* @return {!Array<!Node>} The found nodes or an empty array if none are found.
1784
*/
1785
goog.dom.findNodes = function(root, p) {
1786
var rv = [];
1787
goog.dom.findNodes_(root, p, rv, false);
1788
return rv;
1789
};
1790
1791
1792
/**
1793
* Finds the first or all the descendant nodes that match the filter function,
1794
* using a depth first search.
1795
* @param {Node} root The root of the tree to search.
1796
* @param {function(Node) : boolean} p The filter function.
1797
* @param {!Array<!Node>} rv The found nodes are added to this array.
1798
* @param {boolean} findOne If true we exit after the first found node.
1799
* @return {boolean} Whether the search is complete or not. True in case findOne
1800
* is true and the node is found. False otherwise.
1801
* @private
1802
*/
1803
goog.dom.findNodes_ = function(root, p, rv, findOne) {
1804
if (root != null) {
1805
var child = root.firstChild;
1806
while (child) {
1807
if (p(child)) {
1808
rv.push(child);
1809
if (findOne) {
1810
return true;
1811
}
1812
}
1813
if (goog.dom.findNodes_(child, p, rv, findOne)) {
1814
return true;
1815
}
1816
child = child.nextSibling;
1817
}
1818
}
1819
return false;
1820
};
1821
1822
1823
/**
1824
* Map of tags whose content to ignore when calculating text length.
1825
* @private {!Object<string, number>}
1826
* @const
1827
*/
1828
goog.dom.TAGS_TO_IGNORE_ = {
1829
'SCRIPT': 1,
1830
'STYLE': 1,
1831
'HEAD': 1,
1832
'IFRAME': 1,
1833
'OBJECT': 1
1834
};
1835
1836
1837
/**
1838
* Map of tags which have predefined values with regard to whitespace.
1839
* @private {!Object<string, string>}
1840
* @const
1841
*/
1842
goog.dom.PREDEFINED_TAG_VALUES_ = {
1843
'IMG': ' ',
1844
'BR': '\n'
1845
};
1846
1847
1848
/**
1849
* Returns true if the element has a tab index that allows it to receive
1850
* keyboard focus (tabIndex >= 0), false otherwise. Note that some elements
1851
* natively support keyboard focus, even if they have no tab index.
1852
* @param {!Element} element Element to check.
1853
* @return {boolean} Whether the element has a tab index that allows keyboard
1854
* focus.
1855
*/
1856
goog.dom.isFocusableTabIndex = function(element) {
1857
return goog.dom.hasSpecifiedTabIndex_(element) &&
1858
goog.dom.isTabIndexFocusable_(element);
1859
};
1860
1861
1862
/**
1863
* Enables or disables keyboard focus support on the element via its tab index.
1864
* Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
1865
* (or elements that natively support keyboard focus, like form elements) can
1866
* receive keyboard focus. See http://go/tabindex for more info.
1867
* @param {Element} element Element whose tab index is to be changed.
1868
* @param {boolean} enable Whether to set or remove a tab index on the element
1869
* that supports keyboard focus.
1870
*/
1871
goog.dom.setFocusableTabIndex = function(element, enable) {
1872
if (enable) {
1873
element.tabIndex = 0;
1874
} else {
1875
// Set tabIndex to -1 first, then remove it. This is a workaround for
1876
// Safari (confirmed in version 4 on Windows). When removing the attribute
1877
// without setting it to -1 first, the element remains keyboard focusable
1878
// despite not having a tabIndex attribute anymore.
1879
element.tabIndex = -1;
1880
element.removeAttribute('tabIndex'); // Must be camelCase!
1881
}
1882
};
1883
1884
1885
/**
1886
* Returns true if the element can be focused, i.e. it has a tab index that
1887
* allows it to receive keyboard focus (tabIndex >= 0), or it is an element
1888
* that natively supports keyboard focus.
1889
* @param {!Element} element Element to check.
1890
* @return {boolean} Whether the element allows keyboard focus.
1891
*/
1892
goog.dom.isFocusable = function(element) {
1893
var focusable;
1894
// Some elements can have unspecified tab index and still receive focus.
1895
if (goog.dom.nativelySupportsFocus_(element)) {
1896
// Make sure the element is not disabled ...
1897
focusable = !element.disabled &&
1898
// ... and if a tab index is specified, it allows focus.
1899
(!goog.dom.hasSpecifiedTabIndex_(element) ||
1900
goog.dom.isTabIndexFocusable_(element));
1901
} else {
1902
focusable = goog.dom.isFocusableTabIndex(element);
1903
}
1904
1905
// IE requires elements to be visible in order to focus them.
1906
return focusable && goog.userAgent.IE ?
1907
goog.dom.hasNonZeroBoundingRect_(/** @type {!HTMLElement} */ (element)) :
1908
focusable;
1909
};
1910
1911
1912
/**
1913
* Returns true if the element has a specified tab index.
1914
* @param {!Element} element Element to check.
1915
* @return {boolean} Whether the element has a specified tab index.
1916
* @private
1917
*/
1918
goog.dom.hasSpecifiedTabIndex_ = function(element) {
1919
// IE8 and below don't support hasAttribute(), instead check whether the
1920
// 'tabindex' attributeNode is specified. Otherwise check hasAttribute().
1921
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')) {
1922
var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
1923
return goog.isDefAndNotNull(attrNode) && attrNode.specified;
1924
} else {
1925
return element.hasAttribute('tabindex');
1926
}
1927
};
1928
1929
1930
/**
1931
* Returns true if the element's tab index allows the element to be focused.
1932
* @param {!Element} element Element to check.
1933
* @return {boolean} Whether the element's tab index allows focus.
1934
* @private
1935
*/
1936
goog.dom.isTabIndexFocusable_ = function(element) {
1937
var index = /** @type {!HTMLElement} */ (element).tabIndex;
1938
// NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534.
1939
return goog.isNumber(index) && index >= 0 && index < 32768;
1940
};
1941
1942
1943
/**
1944
* Returns true if the element is focusable even when tabIndex is not set.
1945
* @param {!Element} element Element to check.
1946
* @return {boolean} Whether the element natively supports focus.
1947
* @private
1948
*/
1949
goog.dom.nativelySupportsFocus_ = function(element) {
1950
return element.tagName == goog.dom.TagName.A ||
1951
element.tagName == goog.dom.TagName.INPUT ||
1952
element.tagName == goog.dom.TagName.TEXTAREA ||
1953
element.tagName == goog.dom.TagName.SELECT ||
1954
element.tagName == goog.dom.TagName.BUTTON;
1955
};
1956
1957
1958
/**
1959
* Returns true if the element has a bounding rectangle that would be visible
1960
* (i.e. its width and height are greater than zero).
1961
* @param {!HTMLElement} element Element to check.
1962
* @return {boolean} Whether the element has a non-zero bounding rectangle.
1963
* @private
1964
*/
1965
goog.dom.hasNonZeroBoundingRect_ = function(element) {
1966
var rect;
1967
if (!goog.isFunction(element['getBoundingClientRect']) ||
1968
// In IE, getBoundingClientRect throws on detached nodes.
1969
(goog.userAgent.IE && element.parentElement == null)) {
1970
rect = {'height': element.offsetHeight, 'width': element.offsetWidth};
1971
} else {
1972
rect = element.getBoundingClientRect();
1973
}
1974
return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0;
1975
};
1976
1977
1978
/**
1979
* Returns the text content of the current node, without markup and invisible
1980
* symbols. New lines are stripped and whitespace is collapsed,
1981
* such that each character would be visible.
1982
*
1983
* In browsers that support it, innerText is used. Other browsers attempt to
1984
* simulate it via node traversal. Line breaks are canonicalized in IE.
1985
*
1986
* @param {Node} node The node from which we are getting content.
1987
* @return {string} The text content.
1988
*/
1989
goog.dom.getTextContent = function(node) {
1990
var textContent;
1991
// Note(arv): IE9, Opera, and Safari 3 support innerText but they include
1992
// text nodes in script tags. So we revert to use a user agent test here.
1993
if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && node !== null &&
1994
('innerText' in node)) {
1995
textContent = goog.string.canonicalizeNewlines(node.innerText);
1996
// Unfortunately .innerText() returns text with &shy; symbols
1997
// We need to filter it out and then remove duplicate whitespaces
1998
} else {
1999
var buf = [];
2000
goog.dom.getTextContent_(node, buf, true);
2001
textContent = buf.join('');
2002
}
2003
2004
// Strip &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
2005
textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
2006
// Strip &#8203; entities. goog.format.insertWordBreaks inserts them in IE8.
2007
textContent = textContent.replace(/\u200B/g, '');
2008
2009
// Skip this replacement on old browsers with working innerText, which
2010
// automatically turns &nbsp; into ' ' and / +/ into ' ' when reading
2011
// innerText.
2012
if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) {
2013
textContent = textContent.replace(/ +/g, ' ');
2014
}
2015
if (textContent != ' ') {
2016
textContent = textContent.replace(/^\s*/, '');
2017
}
2018
2019
return textContent;
2020
};
2021
2022
2023
/**
2024
* Returns the text content of the current node, without markup.
2025
*
2026
* Unlike {@code getTextContent} this method does not collapse whitespaces
2027
* or normalize lines breaks.
2028
*
2029
* @param {Node} node The node from which we are getting content.
2030
* @return {string} The raw text content.
2031
*/
2032
goog.dom.getRawTextContent = function(node) {
2033
var buf = [];
2034
goog.dom.getTextContent_(node, buf, false);
2035
2036
return buf.join('');
2037
};
2038
2039
2040
/**
2041
* Recursive support function for text content retrieval.
2042
*
2043
* @param {Node} node The node from which we are getting content.
2044
* @param {Array<string>} buf string buffer.
2045
* @param {boolean} normalizeWhitespace Whether to normalize whitespace.
2046
* @private
2047
*/
2048
goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
2049
if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
2050
// ignore certain tags
2051
} else if (node.nodeType == goog.dom.NodeType.TEXT) {
2052
if (normalizeWhitespace) {
2053
buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
2054
} else {
2055
buf.push(node.nodeValue);
2056
}
2057
} else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
2058
buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
2059
} else {
2060
var child = node.firstChild;
2061
while (child) {
2062
goog.dom.getTextContent_(child, buf, normalizeWhitespace);
2063
child = child.nextSibling;
2064
}
2065
}
2066
};
2067
2068
2069
/**
2070
* Returns the text length of the text contained in a node, without markup. This
2071
* is equivalent to the selection length if the node was selected, or the number
2072
* of cursor movements to traverse the node. Images & BRs take one space. New
2073
* lines are ignored.
2074
*
2075
* @param {Node} node The node whose text content length is being calculated.
2076
* @return {number} The length of {@code node}'s text content.
2077
*/
2078
goog.dom.getNodeTextLength = function(node) {
2079
return goog.dom.getTextContent(node).length;
2080
};
2081
2082
2083
/**
2084
* Returns the text offset of a node relative to one of its ancestors. The text
2085
* length is the same as the length calculated by goog.dom.getNodeTextLength.
2086
*
2087
* @param {Node} node The node whose offset is being calculated.
2088
* @param {Node=} opt_offsetParent The node relative to which the offset will
2089
* be calculated. Defaults to the node's owner document's body.
2090
* @return {number} The text offset.
2091
*/
2092
goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
2093
var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
2094
var buf = [];
2095
while (node && node != root) {
2096
var cur = node;
2097
while ((cur = cur.previousSibling)) {
2098
buf.unshift(goog.dom.getTextContent(cur));
2099
}
2100
node = node.parentNode;
2101
}
2102
// Trim left to deal with FF cases when there might be line breaks and empty
2103
// nodes at the front of the text
2104
return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length;
2105
};
2106
2107
2108
/**
2109
* Returns the node at a given offset in a parent node. If an object is
2110
* provided for the optional third parameter, the node and the remainder of the
2111
* offset will stored as properties of this object.
2112
* @param {Node} parent The parent node.
2113
* @param {number} offset The offset into the parent node.
2114
* @param {Object=} opt_result Object to be used to store the return value. The
2115
* return value will be stored in the form {node: Node, remainder: number}
2116
* if this object is provided.
2117
* @return {Node} The node at the given offset.
2118
*/
2119
goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
2120
var stack = [parent], pos = 0, cur = null;
2121
while (stack.length > 0 && pos < offset) {
2122
cur = stack.pop();
2123
if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
2124
// ignore certain tags
2125
} else if (cur.nodeType == goog.dom.NodeType.TEXT) {
2126
var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' ');
2127
pos += text.length;
2128
} else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
2129
pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length;
2130
} else {
2131
for (var i = cur.childNodes.length - 1; i >= 0; i--) {
2132
stack.push(cur.childNodes[i]);
2133
}
2134
}
2135
}
2136
if (goog.isObject(opt_result)) {
2137
opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
2138
opt_result.node = cur;
2139
}
2140
2141
return cur;
2142
};
2143
2144
2145
/**
2146
* Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
2147
* the object must have a numeric length property and an item function (which
2148
* has type 'string' on IE for some reason).
2149
* @param {Object} val Object to test.
2150
* @return {boolean} Whether the object is a NodeList.
2151
*/
2152
goog.dom.isNodeList = function(val) {
2153
// TODO(attila): Now the isNodeList is part of goog.dom we can use
2154
// goog.userAgent to make this simpler.
2155
// A NodeList must have a length property of type 'number' on all platforms.
2156
if (val && typeof val.length == 'number') {
2157
// A NodeList is an object everywhere except Safari, where it's a function.
2158
if (goog.isObject(val)) {
2159
// A NodeList must have an item function (on non-IE platforms) or an item
2160
// property of type 'string' (on IE).
2161
return typeof val.item == 'function' || typeof val.item == 'string';
2162
} else if (goog.isFunction(val)) {
2163
// On Safari, a NodeList is a function with an item property that is also
2164
// a function.
2165
return typeof val.item == 'function';
2166
}
2167
}
2168
2169
// Not a NodeList.
2170
return false;
2171
};
2172
2173
2174
/**
2175
* Walks up the DOM hierarchy returning the first ancestor that has the passed
2176
* tag name and/or class name. If the passed element matches the specified
2177
* criteria, the element itself is returned.
2178
* @param {Node} element The DOM node to start with.
2179
* @param {?(goog.dom.TagName<T>|string)=} opt_tag The tag name to match (or
2180
* null/undefined to match only based on class name).
2181
* @param {?string=} opt_class The class name to match (or null/undefined to
2182
* match only based on tag name).
2183
* @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
2184
* dom.
2185
* @return {?R} The first ancestor that matches the passed criteria, or
2186
* null if no match is found. The return type is {?Element} if opt_tag is
2187
* not a member of goog.dom.TagName or a more specific type if it is (e.g.
2188
* {?HTMLAnchorElement} for goog.dom.TagName.A).
2189
* @template T
2190
* @template R := cond(isUnknown(T), 'Element', T) =:
2191
*/
2192
goog.dom.getAncestorByTagNameAndClass = function(
2193
element, opt_tag, opt_class, opt_maxSearchSteps) {
2194
if (!opt_tag && !opt_class) {
2195
return null;
2196
}
2197
var tagName = opt_tag ? String(opt_tag).toUpperCase() : null;
2198
return /** @type {Element} */ (goog.dom.getAncestor(element, function(node) {
2199
return (!tagName || node.nodeName == tagName) &&
2200
(!opt_class ||
2201
goog.isString(node.className) &&
2202
goog.array.contains(node.className.split(/\s+/), opt_class));
2203
}, true, opt_maxSearchSteps));
2204
};
2205
2206
2207
/**
2208
* Walks up the DOM hierarchy returning the first ancestor that has the passed
2209
* class name. If the passed element matches the specified criteria, the
2210
* element itself is returned.
2211
* @param {Node} element The DOM node to start with.
2212
* @param {string} className The class name to match.
2213
* @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
2214
* dom.
2215
* @return {Element} The first ancestor that matches the passed criteria, or
2216
* null if none match.
2217
*/
2218
goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) {
2219
return goog.dom.getAncestorByTagNameAndClass(
2220
element, null, className, opt_maxSearchSteps);
2221
};
2222
2223
2224
/**
2225
* Walks up the DOM hierarchy returning the first ancestor that passes the
2226
* matcher function.
2227
* @param {Node} element The DOM node to start with.
2228
* @param {function(Node) : boolean} matcher A function that returns true if the
2229
* passed node matches the desired criteria.
2230
* @param {boolean=} opt_includeNode If true, the node itself is included in
2231
* the search (the first call to the matcher will pass startElement as
2232
* the node to test).
2233
* @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
2234
* dom.
2235
* @return {Node} DOM node that matched the matcher, or null if there was
2236
* no match.
2237
*/
2238
goog.dom.getAncestor = function(
2239
element, matcher, opt_includeNode, opt_maxSearchSteps) {
2240
if (element && !opt_includeNode) {
2241
element = element.parentNode;
2242
}
2243
var steps = 0;
2244
while (element &&
2245
(opt_maxSearchSteps == null || steps <= opt_maxSearchSteps)) {
2246
goog.asserts.assert(element.name != 'parentNode');
2247
if (matcher(element)) {
2248
return element;
2249
}
2250
element = element.parentNode;
2251
steps++;
2252
}
2253
// Reached the root of the DOM without a match
2254
return null;
2255
};
2256
2257
2258
/**
2259
* Determines the active element in the given document.
2260
* @param {Document} doc The document to look in.
2261
* @return {Element} The active element.
2262
*/
2263
goog.dom.getActiveElement = function(doc) {
2264
try {
2265
return doc && doc.activeElement;
2266
} catch (e) {
2267
// NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE
2268
// throws an exception. I'm not 100% sure why, but I suspect it chokes
2269
// on document.activeElement if the activeElement has been recently
2270
// removed from the DOM by a JS operation.
2271
//
2272
// We assume that an exception here simply means
2273
// "there is no active element."
2274
}
2275
2276
return null;
2277
};
2278
2279
2280
/**
2281
* Gives the current devicePixelRatio.
2282
*
2283
* By default, this is the value of window.devicePixelRatio (which should be
2284
* preferred if present).
2285
*
2286
* If window.devicePixelRatio is not present, the ratio is calculated with
2287
* window.matchMedia, if present. Otherwise, gives 1.0.
2288
*
2289
* Some browsers (including Chrome) consider the browser zoom level in the pixel
2290
* ratio, so the value may change across multiple calls.
2291
*
2292
* @return {number} The number of actual pixels per virtual pixel.
2293
*/
2294
goog.dom.getPixelRatio = function() {
2295
var win = goog.dom.getWindow();
2296
if (goog.isDef(win.devicePixelRatio)) {
2297
return win.devicePixelRatio;
2298
} else if (win.matchMedia) {
2299
// Should be for IE10 and FF6-17 (this basically clamps to lower)
2300
// Note that the order of these statements is important
2301
return goog.dom.matchesPixelRatio_(3) || goog.dom.matchesPixelRatio_(2) ||
2302
goog.dom.matchesPixelRatio_(1.5) || goog.dom.matchesPixelRatio_(1) ||
2303
.75;
2304
}
2305
return 1;
2306
};
2307
2308
2309
/**
2310
* Calculates a mediaQuery to check if the current device supports the
2311
* given actual to virtual pixel ratio.
2312
* @param {number} pixelRatio The ratio of actual pixels to virtual pixels.
2313
* @return {number} pixelRatio if applicable, otherwise 0.
2314
* @private
2315
*/
2316
goog.dom.matchesPixelRatio_ = function(pixelRatio) {
2317
var win = goog.dom.getWindow();
2318
/**
2319
* Due to the 1:96 fixed ratio of CSS in to CSS px, 1dppx is equivalent to
2320
* 96dpi.
2321
* @const {number}
2322
*/
2323
var dpiPerDppx = 96;
2324
var query =
2325
// FF16-17
2326
'(min-resolution: ' + pixelRatio + 'dppx),' +
2327
// FF6-15
2328
'(min--moz-device-pixel-ratio: ' + pixelRatio + '),' +
2329
// IE10 (this works for the two browsers above too but I don't want to
2330
// trust the 1:96 fixed ratio magic)
2331
'(min-resolution: ' + (pixelRatio * dpiPerDppx) + 'dpi)';
2332
return win.matchMedia(query).matches ? pixelRatio : 0;
2333
};
2334
2335
2336
/**
2337
* Gets '2d' context of a canvas. Shortcut for canvas.getContext('2d') with a
2338
* type information.
2339
* @param {!HTMLCanvasElement} canvas
2340
* @return {!CanvasRenderingContext2D}
2341
*/
2342
goog.dom.getCanvasContext2D = function(canvas) {
2343
return /** @type {!CanvasRenderingContext2D} */ (canvas.getContext('2d'));
2344
};
2345
2346
2347
2348
/**
2349
* Create an instance of a DOM helper with a new document object.
2350
* @param {Document=} opt_document Document object to associate with this
2351
* DOM helper.
2352
* @constructor
2353
*/
2354
goog.dom.DomHelper = function(opt_document) {
2355
/**
2356
* Reference to the document object to use
2357
* @type {!Document}
2358
* @private
2359
*/
2360
this.document_ = opt_document || goog.global.document || document;
2361
};
2362
2363
2364
/**
2365
* Gets the dom helper object for the document where the element resides.
2366
* @param {Node=} opt_node If present, gets the DomHelper for this node.
2367
* @return {!goog.dom.DomHelper} The DomHelper.
2368
*/
2369
goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
2370
2371
2372
/**
2373
* Sets the document object.
2374
* @param {!Document} document Document object.
2375
*/
2376
goog.dom.DomHelper.prototype.setDocument = function(document) {
2377
this.document_ = document;
2378
};
2379
2380
2381
/**
2382
* Gets the document object being used by the dom library.
2383
* @return {!Document} Document object.
2384
*/
2385
goog.dom.DomHelper.prototype.getDocument = function() {
2386
return this.document_;
2387
};
2388
2389
2390
/**
2391
* Alias for {@code getElementById}. If a DOM node is passed in then we just
2392
* return that.
2393
* @param {string|Element} element Element ID or a DOM node.
2394
* @return {Element} The element with the given ID, or the node passed in.
2395
*/
2396
goog.dom.DomHelper.prototype.getElement = function(element) {
2397
return goog.dom.getElementHelper_(this.document_, element);
2398
};
2399
2400
2401
/**
2402
* Gets an element by id, asserting that the element is found.
2403
*
2404
* This is used when an element is expected to exist, and should fail with
2405
* an assertion error if it does not (if assertions are enabled).
2406
*
2407
* @param {string} id Element ID.
2408
* @return {!Element} The element with the given ID, if it exists.
2409
*/
2410
goog.dom.DomHelper.prototype.getRequiredElement = function(id) {
2411
return goog.dom.getRequiredElementHelper_(this.document_, id);
2412
};
2413
2414
2415
/**
2416
* Alias for {@code getElement}.
2417
* @param {string|Element} element Element ID or a DOM node.
2418
* @return {Element} The element with the given ID, or the node passed in.
2419
* @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
2420
*/
2421
goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
2422
2423
2424
/**
2425
* Gets elements by tag name.
2426
* @param {!goog.dom.TagName<T>} tagName
2427
* @param {(!Document|!Element)=} opt_parent Parent element or document where to
2428
* look for elements. Defaults to document of this DomHelper.
2429
* @return {!NodeList<R>} List of elements. The members of the list are
2430
* {!Element} if tagName is not a member of goog.dom.TagName or more
2431
* specific types if it is (e.g. {!HTMLAnchorElement} for
2432
* goog.dom.TagName.A).
2433
* @template T
2434
* @template R := cond(isUnknown(T), 'Element', T) =:
2435
*/
2436
goog.dom.DomHelper.prototype.getElementsByTagName =
2437
function(tagName, opt_parent) {
2438
var parent = opt_parent || this.document_;
2439
return parent.getElementsByTagName(String(tagName));
2440
};
2441
2442
2443
/**
2444
* Looks up elements by both tag and class name, using browser native functions
2445
* ({@code querySelectorAll}, {@code getElementsByTagName} or
2446
* {@code getElementsByClassName}) where possible. The returned array is a live
2447
* NodeList or a static list depending on the code path taken.
2448
*
2449
* @see goog.dom.query
2450
*
2451
* @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name or * for all
2452
* tags.
2453
* @param {?string=} opt_class Optional class name.
2454
* @param {(Document|Element)=} opt_el Optional element to look in.
2455
* @return {!IArrayLike<R>} Array-like list of elements (only a length property
2456
* and numerical indices are guaranteed to exist). The members of the array
2457
* are {!Element} if opt_tag is not a member of goog.dom.TagName or more
2458
* specific types if it is (e.g. {!HTMLAnchorElement} for
2459
* goog.dom.TagName.A).
2460
* @template T
2461
* @template R := cond(isUnknown(T), 'Element', T) =:
2462
*/
2463
goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(
2464
opt_tag, opt_class, opt_el) {
2465
return goog.dom.getElementsByTagNameAndClass_(
2466
this.document_, opt_tag, opt_class, opt_el);
2467
};
2468
2469
2470
/**
2471
* Returns an array of all the elements with the provided className.
2472
* @see {goog.dom.query}
2473
* @param {string} className the name of the class to look for.
2474
* @param {Element|Document=} opt_el Optional element to look in.
2475
* @return {!IArrayLike<!Element>} The items found with the class name provided.
2476
*/
2477
goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
2478
var doc = opt_el || this.document_;
2479
return goog.dom.getElementsByClass(className, doc);
2480
};
2481
2482
2483
/**
2484
* Returns the first element we find matching the provided class name.
2485
* @see {goog.dom.query}
2486
* @param {string} className the name of the class to look for.
2487
* @param {(Element|Document)=} opt_el Optional element to look in.
2488
* @return {Element} The first item found with the class name provided.
2489
*/
2490
goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
2491
var doc = opt_el || this.document_;
2492
return goog.dom.getElementByClass(className, doc);
2493
};
2494
2495
2496
/**
2497
* Ensures an element with the given className exists, and then returns the
2498
* first element with the provided className.
2499
* @see {goog.dom.query}
2500
* @param {string} className the name of the class to look for.
2501
* @param {(!Element|!Document)=} opt_root Optional element or document to look
2502
* in.
2503
* @return {!Element} The first item found with the class name provided.
2504
* @throws {goog.asserts.AssertionError} Thrown if no element is found.
2505
*/
2506
goog.dom.DomHelper.prototype.getRequiredElementByClass = function(
2507
className, opt_root) {
2508
var root = opt_root || this.document_;
2509
return goog.dom.getRequiredElementByClass(className, root);
2510
};
2511
2512
2513
/**
2514
* Alias for {@code getElementsByTagNameAndClass}.
2515
* @deprecated Use DomHelper getElementsByTagNameAndClass.
2516
* @see goog.dom.query
2517
*
2518
* @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name.
2519
* @param {?string=} opt_class Optional class name.
2520
* @param {Element=} opt_el Optional element to look in.
2521
* @return {!IArrayLike<R>} Array-like list of elements (only a length property
2522
* and numerical indices are guaranteed to exist). The members of the array
2523
* are {!Element} if opt_tag is a string or more specific types if it is
2524
* a member of goog.dom.TagName (e.g. {!HTMLAnchorElement} for
2525
* goog.dom.TagName.A).
2526
* @template T
2527
* @template R := cond(isUnknown(T), 'Element', T) =:
2528
*/
2529
goog.dom.DomHelper.prototype.$$ =
2530
goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
2531
2532
2533
/**
2534
* Sets a number of properties on a node.
2535
* @param {Element} element DOM node to set properties on.
2536
* @param {Object} properties Hash of property:value pairs.
2537
*/
2538
goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
2539
2540
2541
/**
2542
* Gets the dimensions of the viewport.
2543
* @param {Window=} opt_window Optional window element to test. Defaults to
2544
* the window of the Dom Helper.
2545
* @return {!goog.math.Size} Object with values 'width' and 'height'.
2546
*/
2547
goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
2548
// TODO(arv): This should not take an argument. That breaks the rule of a
2549
// a DomHelper representing a single frame/window/document.
2550
return goog.dom.getViewportSize(opt_window || this.getWindow());
2551
};
2552
2553
2554
/**
2555
* Calculates the height of the document.
2556
*
2557
* @return {number} The height of the document.
2558
*/
2559
goog.dom.DomHelper.prototype.getDocumentHeight = function() {
2560
return goog.dom.getDocumentHeight_(this.getWindow());
2561
};
2562
2563
2564
/**
2565
* Typedef for use with goog.dom.createDom and goog.dom.append.
2566
* @typedef {Object|string|Array|NodeList}
2567
*/
2568
goog.dom.Appendable;
2569
2570
2571
/**
2572
* Returns a dom node with a set of attributes. This function accepts varargs
2573
* for subsequent nodes to be added. Subsequent nodes will be added to the
2574
* first node as childNodes.
2575
*
2576
* So:
2577
* <code>createDom(goog.dom.TagName.DIV, null, createDom(goog.dom.TagName.P), createDom(goog.dom.TagName.P));</code>
2578
* would return a div with two child paragraphs
2579
*
2580
* An easy way to move all child nodes of an existing element to a new parent
2581
* element is:
2582
* <code>createDom(goog.dom.TagName.DIV, null, oldElement.childNodes);</code>
2583
* which will remove all child nodes from the old element and add them as
2584
* child nodes of the new DIV.
2585
*
2586
* @param {string|!goog.dom.TagName<T>} tagName Tag to create.
2587
* @param {Object|string=} opt_attributes If object, then a map of name-value
2588
* pairs for attributes. If a string, then this is the className of the new
2589
* element.
2590
* @param {...goog.dom.Appendable} var_args Further DOM nodes or
2591
* strings for text nodes. If one of the var_args is an array or
2592
* NodeList, its elements will be added as childNodes instead.
2593
* @return {R} Reference to a DOM node. The return type is {!Element} if tagName
2594
* is a string or a more specific type if it is a member of
2595
* goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
2596
* @template T
2597
* @template R := cond(isUnknown(T), 'Element', T) =:
2598
*/
2599
goog.dom.DomHelper.prototype.createDom = function(
2600
tagName, opt_attributes, var_args) {
2601
return goog.dom.createDom_(this.document_, arguments);
2602
};
2603
2604
2605
/**
2606
* Alias for {@code createDom}.
2607
* @param {string|!goog.dom.TagName<T>} tagName Tag to create.
2608
* @param {(Object|string)=} opt_attributes If object, then a map of name-value
2609
* pairs for attributes. If a string, then this is the className of the new
2610
* element.
2611
* @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
2612
* text nodes. If one of the var_args is an array, its children will be
2613
* added as childNodes instead.
2614
* @return {R} Reference to a DOM node. The return type is {!Element} if tagName
2615
* is a string or a more specific type if it is a member of
2616
* goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
2617
* @template T
2618
* @template R := cond(isUnknown(T), 'Element', T) =:
2619
* @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
2620
*/
2621
goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
2622
2623
2624
/**
2625
* Creates a new element.
2626
* @param {string|!goog.dom.TagName<T>} name Tag to create.
2627
* @return {R} The new element. The return type is {!Element} if name is
2628
* a string or a more specific type if it is a member of goog.dom.TagName
2629
* (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A).
2630
* @template T
2631
* @template R := cond(isUnknown(T), 'Element', T) =:
2632
*/
2633
goog.dom.DomHelper.prototype.createElement = function(name) {
2634
return goog.dom.createElement_(this.document_, name);
2635
};
2636
2637
2638
/**
2639
* Creates a new text node.
2640
* @param {number|string} content Content.
2641
* @return {!Text} The new text node.
2642
*/
2643
goog.dom.DomHelper.prototype.createTextNode = function(content) {
2644
return this.document_.createTextNode(String(content));
2645
};
2646
2647
2648
/**
2649
* Create a table.
2650
* @param {number} rows The number of rows in the table. Must be >= 1.
2651
* @param {number} columns The number of columns in the table. Must be >= 1.
2652
* @param {boolean=} opt_fillWithNbsp If true, fills table entries with
2653
* {@code goog.string.Unicode.NBSP} characters.
2654
* @return {!HTMLElement} The created table.
2655
*/
2656
goog.dom.DomHelper.prototype.createTable = function(
2657
rows, columns, opt_fillWithNbsp) {
2658
return goog.dom.createTable_(
2659
this.document_, rows, columns, !!opt_fillWithNbsp);
2660
};
2661
2662
2663
/**
2664
* Converts an HTML into a node or a document fragment. A single Node is used if
2665
* {@code html} only generates a single node. If {@code html} generates multiple
2666
* nodes then these are put inside a {@code DocumentFragment}. This is a safe
2667
* version of {@code goog.dom.DomHelper#htmlToDocumentFragment} which is now
2668
* deleted.
2669
* @param {!goog.html.SafeHtml} html The HTML markup to convert.
2670
* @return {!Node} The resulting node.
2671
*/
2672
goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) {
2673
return goog.dom.safeHtmlToNode_(this.document_, html);
2674
};
2675
2676
2677
/**
2678
* Returns true if the browser is in "CSS1-compatible" (standards-compliant)
2679
* mode, false otherwise.
2680
* @return {boolean} True if in CSS1-compatible mode.
2681
*/
2682
goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
2683
return goog.dom.isCss1CompatMode_(this.document_);
2684
};
2685
2686
2687
/**
2688
* Gets the window object associated with the document.
2689
* @return {!Window} The window associated with the given document.
2690
*/
2691
goog.dom.DomHelper.prototype.getWindow = function() {
2692
return goog.dom.getWindow_(this.document_);
2693
};
2694
2695
2696
/**
2697
* Gets the document scroll element.
2698
* @return {!Element} Scrolling element.
2699
*/
2700
goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
2701
return goog.dom.getDocumentScrollElement_(this.document_);
2702
};
2703
2704
2705
/**
2706
* Gets the document scroll distance as a coordinate object.
2707
* @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
2708
*/
2709
goog.dom.DomHelper.prototype.getDocumentScroll = function() {
2710
return goog.dom.getDocumentScroll_(this.document_);
2711
};
2712
2713
2714
/**
2715
* Determines the active element in the given document.
2716
* @param {Document=} opt_doc The document to look in.
2717
* @return {Element} The active element.
2718
*/
2719
goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) {
2720
return goog.dom.getActiveElement(opt_doc || this.document_);
2721
};
2722
2723
2724
/**
2725
* Appends a child to a node.
2726
* @param {Node} parent Parent.
2727
* @param {Node} child Child.
2728
*/
2729
goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
2730
2731
2732
/**
2733
* Appends a node with text or other nodes.
2734
* @param {!Node} parent The node to append nodes to.
2735
* @param {...goog.dom.Appendable} var_args The things to append to the node.
2736
* If this is a Node it is appended as is.
2737
* If this is a string then a text node is appended.
2738
* If this is an array like object then fields 0 to length - 1 are appended.
2739
*/
2740
goog.dom.DomHelper.prototype.append = goog.dom.append;
2741
2742
2743
/**
2744
* Determines if the given node can contain children, intended to be used for
2745
* HTML generation.
2746
*
2747
* @param {Node} node The node to check.
2748
* @return {boolean} Whether the node can contain children.
2749
*/
2750
goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;
2751
2752
2753
/**
2754
* Removes all the child nodes on a DOM node.
2755
* @param {Node} node Node to remove children from.
2756
*/
2757
goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
2758
2759
2760
/**
2761
* Inserts a new node before an existing reference node (i.e., as the previous
2762
* sibling). If the reference node has no parent, then does nothing.
2763
* @param {Node} newNode Node to insert.
2764
* @param {Node} refNode Reference node to insert before.
2765
*/
2766
goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
2767
2768
2769
/**
2770
* Inserts a new node after an existing reference node (i.e., as the next
2771
* sibling). If the reference node has no parent, then does nothing.
2772
* @param {Node} newNode Node to insert.
2773
* @param {Node} refNode Reference node to insert after.
2774
*/
2775
goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
2776
2777
2778
/**
2779
* Insert a child at a given index. If index is larger than the number of child
2780
* nodes that the parent currently has, the node is inserted as the last child
2781
* node.
2782
* @param {Element} parent The element into which to insert the child.
2783
* @param {Node} child The element to insert.
2784
* @param {number} index The index at which to insert the new child node. Must
2785
* not be negative.
2786
*/
2787
goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt;
2788
2789
2790
/**
2791
* Removes a node from its parent.
2792
* @param {Node} node The node to remove.
2793
* @return {Node} The node removed if removed; else, null.
2794
*/
2795
goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
2796
2797
2798
/**
2799
* Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
2800
* parent.
2801
* @param {Node} newNode Node to insert.
2802
* @param {Node} oldNode Node to replace.
2803
*/
2804
goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
2805
2806
2807
/**
2808
* Flattens an element. That is, removes it and replace it with its children.
2809
* @param {Element} element The element to flatten.
2810
* @return {Element|undefined} The original element, detached from the document
2811
* tree, sans children, or undefined if the element was already not in the
2812
* document.
2813
*/
2814
goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
2815
2816
2817
/**
2818
* Returns an array containing just the element children of the given element.
2819
* @param {Element} element The element whose element children we want.
2820
* @return {!(Array<!Element>|NodeList<!Element>)} An array or array-like list
2821
* of just the element children of the given element.
2822
*/
2823
goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;
2824
2825
2826
/**
2827
* Returns the first child node that is an element.
2828
* @param {Node} node The node to get the first child element of.
2829
* @return {Element} The first child node of {@code node} that is an element.
2830
*/
2831
goog.dom.DomHelper.prototype.getFirstElementChild =
2832
goog.dom.getFirstElementChild;
2833
2834
2835
/**
2836
* Returns the last child node that is an element.
2837
* @param {Node} node The node to get the last child element of.
2838
* @return {Element} The last child node of {@code node} that is an element.
2839
*/
2840
goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;
2841
2842
2843
/**
2844
* Returns the first next sibling that is an element.
2845
* @param {Node} node The node to get the next sibling element of.
2846
* @return {Element} The next sibling of {@code node} that is an element.
2847
*/
2848
goog.dom.DomHelper.prototype.getNextElementSibling =
2849
goog.dom.getNextElementSibling;
2850
2851
2852
/**
2853
* Returns the first previous sibling that is an element.
2854
* @param {Node} node The node to get the previous sibling element of.
2855
* @return {Element} The first previous sibling of {@code node} that is
2856
* an element.
2857
*/
2858
goog.dom.DomHelper.prototype.getPreviousElementSibling =
2859
goog.dom.getPreviousElementSibling;
2860
2861
2862
/**
2863
* Returns the next node in source order from the given node.
2864
* @param {Node} node The node.
2865
* @return {Node} The next node in the DOM tree, or null if this was the last
2866
* node.
2867
*/
2868
goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
2869
2870
2871
/**
2872
* Returns the previous node in source order from the given node.
2873
* @param {Node} node The node.
2874
* @return {Node} The previous node in the DOM tree, or null if this was the
2875
* first node.
2876
*/
2877
goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
2878
2879
2880
/**
2881
* Whether the object looks like a DOM node.
2882
* @param {?} obj The object being tested for node likeness.
2883
* @return {boolean} Whether the object looks like a DOM node.
2884
*/
2885
goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
2886
2887
2888
/**
2889
* Whether the object looks like an Element.
2890
* @param {?} obj The object being tested for Element likeness.
2891
* @return {boolean} Whether the object looks like an Element.
2892
*/
2893
goog.dom.DomHelper.prototype.isElement = goog.dom.isElement;
2894
2895
2896
/**
2897
* Returns true if the specified value is a Window object. This includes the
2898
* global window for HTML pages, and iframe windows.
2899
* @param {?} obj Variable to test.
2900
* @return {boolean} Whether the variable is a window.
2901
*/
2902
goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;
2903
2904
2905
/**
2906
* Returns an element's parent, if it's an Element.
2907
* @param {Element} element The DOM element.
2908
* @return {Element} The parent, or null if not an Element.
2909
*/
2910
goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement;
2911
2912
2913
/**
2914
* Whether a node contains another node.
2915
* @param {Node} parent The node that should contain the other node.
2916
* @param {Node} descendant The node to test presence of.
2917
* @return {boolean} Whether the parent node contains the descendent node.
2918
*/
2919
goog.dom.DomHelper.prototype.contains = goog.dom.contains;
2920
2921
2922
/**
2923
* Compares the document order of two nodes, returning 0 if they are the same
2924
* node, a negative number if node1 is before node2, and a positive number if
2925
* node2 is before node1. Note that we compare the order the tags appear in the
2926
* document so in the tree <b><i>text</i></b> the B node is considered to be
2927
* before the I node.
2928
*
2929
* @param {Node} node1 The first node to compare.
2930
* @param {Node} node2 The second node to compare.
2931
* @return {number} 0 if the nodes are the same node, a negative number if node1
2932
* is before node2, and a positive number if node2 is before node1.
2933
*/
2934
goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;
2935
2936
2937
/**
2938
* Find the deepest common ancestor of the given nodes.
2939
* @param {...Node} var_args The nodes to find a common ancestor of.
2940
* @return {Node} The common ancestor of the nodes, or null if there is none.
2941
* null will only be returned if two or more of the nodes are from different
2942
* documents.
2943
*/
2944
goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor;
2945
2946
2947
/**
2948
* Returns the owner document for a node.
2949
* @param {Node} node The node to get the document for.
2950
* @return {!Document} The document owning the node.
2951
*/
2952
goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
2953
2954
2955
/**
2956
* Cross browser function for getting the document element of an iframe.
2957
* @param {Element} iframe Iframe element.
2958
* @return {!Document} The frame content document.
2959
*/
2960
goog.dom.DomHelper.prototype.getFrameContentDocument =
2961
goog.dom.getFrameContentDocument;
2962
2963
2964
/**
2965
* Cross browser function for getting the window of a frame or iframe.
2966
* @param {Element} frame Frame element.
2967
* @return {Window} The window associated with the given frame.
2968
*/
2969
goog.dom.DomHelper.prototype.getFrameContentWindow =
2970
goog.dom.getFrameContentWindow;
2971
2972
2973
/**
2974
* Sets the text content of a node, with cross-browser support.
2975
* @param {Node} node The node to change the text content of.
2976
* @param {string|number} text The value that should replace the node's content.
2977
*/
2978
goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
2979
2980
2981
/**
2982
* Gets the outerHTML of a node, which islike innerHTML, except that it
2983
* actually contains the HTML of the node itself.
2984
* @param {Element} element The element to get the HTML of.
2985
* @return {string} The outerHTML of the given element.
2986
*/
2987
goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;
2988
2989
2990
/**
2991
* Finds the first descendant node that matches the filter function. This does
2992
* a depth first search.
2993
* @param {Node} root The root of the tree to search.
2994
* @param {function(Node) : boolean} p The filter function.
2995
* @return {Node|undefined} The found node or undefined if none is found.
2996
*/
2997
goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;
2998
2999
3000
/**
3001
* Finds all the descendant nodes that matches the filter function. This does a
3002
* depth first search.
3003
* @param {Node} root The root of the tree to search.
3004
* @param {function(Node) : boolean} p The filter function.
3005
* @return {Array<Node>} The found nodes or an empty array if none are found.
3006
*/
3007
goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
3008
3009
3010
/**
3011
* Returns true if the element has a tab index that allows it to receive
3012
* keyboard focus (tabIndex >= 0), false otherwise. Note that some elements
3013
* natively support keyboard focus, even if they have no tab index.
3014
* @param {!Element} element Element to check.
3015
* @return {boolean} Whether the element has a tab index that allows keyboard
3016
* focus.
3017
*/
3018
goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
3019
3020
3021
/**
3022
* Enables or disables keyboard focus support on the element via its tab index.
3023
* Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
3024
* (or elements that natively support keyboard focus, like form elements) can
3025
* receive keyboard focus. See http://go/tabindex for more info.
3026
* @param {Element} element Element whose tab index is to be changed.
3027
* @param {boolean} enable Whether to set or remove a tab index on the element
3028
* that supports keyboard focus.
3029
*/
3030
goog.dom.DomHelper.prototype.setFocusableTabIndex =
3031
goog.dom.setFocusableTabIndex;
3032
3033
3034
/**
3035
* Returns true if the element can be focused, i.e. it has a tab index that
3036
* allows it to receive keyboard focus (tabIndex >= 0), or it is an element
3037
* that natively supports keyboard focus.
3038
* @param {!Element} element Element to check.
3039
* @return {boolean} Whether the element allows keyboard focus.
3040
*/
3041
goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
3042
3043
3044
/**
3045
* Returns the text contents of the current node, without markup. New lines are
3046
* stripped and whitespace is collapsed, such that each character would be
3047
* visible.
3048
*
3049
* In browsers that support it, innerText is used. Other browsers attempt to
3050
* simulate it via node traversal. Line breaks are canonicalized in IE.
3051
*
3052
* @param {Node} node The node from which we are getting content.
3053
* @return {string} The text content.
3054
*/
3055
goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
3056
3057
3058
/**
3059
* Returns the text length of the text contained in a node, without markup. This
3060
* is equivalent to the selection length if the node was selected, or the number
3061
* of cursor movements to traverse the node. Images & BRs take one space. New
3062
* lines are ignored.
3063
*
3064
* @param {Node} node The node whose text content length is being calculated.
3065
* @return {number} The length of {@code node}'s text content.
3066
*/
3067
goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
3068
3069
3070
/**
3071
* Returns the text offset of a node relative to one of its ancestors. The text
3072
* length is the same as the length calculated by
3073
* {@code goog.dom.getNodeTextLength}.
3074
*
3075
* @param {Node} node The node whose offset is being calculated.
3076
* @param {Node=} opt_offsetParent Defaults to the node's owner document's body.
3077
* @return {number} The text offset.
3078
*/
3079
goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
3080
3081
3082
/**
3083
* Returns the node at a given offset in a parent node. If an object is
3084
* provided for the optional third parameter, the node and the remainder of the
3085
* offset will stored as properties of this object.
3086
* @param {Node} parent The parent node.
3087
* @param {number} offset The offset into the parent node.
3088
* @param {Object=} opt_result Object to be used to store the return value. The
3089
* return value will be stored in the form {node: Node, remainder: number}
3090
* if this object is provided.
3091
* @return {Node} The node at the given offset.
3092
*/
3093
goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;
3094
3095
3096
/**
3097
* Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
3098
* the object must have a numeric length property and an item function (which
3099
* has type 'string' on IE for some reason).
3100
* @param {Object} val Object to test.
3101
* @return {boolean} Whether the object is a NodeList.
3102
*/
3103
goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;
3104
3105
3106
/**
3107
* Walks up the DOM hierarchy returning the first ancestor that has the passed
3108
* tag name and/or class name. If the passed element matches the specified
3109
* criteria, the element itself is returned.
3110
* @param {Node} element The DOM node to start with.
3111
* @param {?(goog.dom.TagName<T>|string)=} opt_tag The tag name to match (or
3112
* null/undefined to match only based on class name).
3113
* @param {?string=} opt_class The class name to match (or null/undefined to
3114
* match only based on tag name).
3115
* @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
3116
* dom.
3117
* @return {?R} The first ancestor that matches the passed criteria, or
3118
* null if no match is found. The return type is {?Element} if opt_tag is
3119
* not a member of goog.dom.TagName or a more specific type if it is (e.g.
3120
* {?HTMLAnchorElement} for goog.dom.TagName.A).
3121
* @template T
3122
* @template R := cond(isUnknown(T), 'Element', T) =:
3123
*/
3124
goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass =
3125
goog.dom.getAncestorByTagNameAndClass;
3126
3127
3128
/**
3129
* Walks up the DOM hierarchy returning the first ancestor that has the passed
3130
* class name. If the passed element matches the specified criteria, the
3131
* element itself is returned.
3132
* @param {Node} element The DOM node to start with.
3133
* @param {string} class The class name to match.
3134
* @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
3135
* dom.
3136
* @return {Element} The first ancestor that matches the passed criteria, or
3137
* null if none match.
3138
*/
3139
goog.dom.DomHelper.prototype.getAncestorByClass = goog.dom.getAncestorByClass;
3140
3141
3142
/**
3143
* Walks up the DOM hierarchy returning the first ancestor that passes the
3144
* matcher function.
3145
* @param {Node} element The DOM node to start with.
3146
* @param {function(Node) : boolean} matcher A function that returns true if the
3147
* passed node matches the desired criteria.
3148
* @param {boolean=} opt_includeNode If true, the node itself is included in
3149
* the search (the first call to the matcher will pass startElement as
3150
* the node to test).
3151
* @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
3152
* dom.
3153
* @return {Node} DOM node that matched the matcher, or null if there was
3154
* no match.
3155
*/
3156
goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;
3157
3158
3159
/**
3160
* Gets '2d' context of a canvas. Shortcut for canvas.getContext('2d') with a
3161
* type information.
3162
* @param {!HTMLCanvasElement} canvas
3163
* @return {!CanvasRenderingContext2D}
3164
*/
3165
goog.dom.DomHelper.prototype.getCanvasContext2D = goog.dom.getCanvasContext2D;
3166
3167