" as JSCompiler
* option.
*
* Take into account that the locale code format is important. You should use
* the canonical Unicode format with hyphen as a delimiter. Language must be
* lowercase, Language Script - Capitalized, Region - UPPERCASE.
* There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
*
* See more info about locale codes here:
* http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
*
* For language codes you should use values defined by ISO 693-1. See it here
* http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
* this rule: the Hebrew language. For legacy reasons the old code (iw) should
* be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
*/
goog.LOCALE = 'en'; // default to en
/**
* Creates object stubs for a namespace. When present in a file, goog.provide
* also indicates that the file defines the indicated object. Calls to
* goog.provide are resolved by the compiler if --closure_pass is set.
* @param {string} name name of the object that this file defines.
*/
goog.provide = function(name) {
goog.exportPath_(name);
};
/**
* Builds an object structure for the provided namespace path,
* ensuring that names that already exist are not overwritten. For
* example:
* "a.b.c" -> a = {};a.b={};a.b.c={};
* Used by goog.provide and goog.exportSymbol.
* @param {string} name name of the object that this file defines.
* @param {*=} opt_object the object to expose at the end of the path.
* @param {Object=} opt_objectToExportTo The object to add the path to; default
* is |goog.global|.
* @private
*/
goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
var parts = name.split('.');
var cur = opt_objectToExportTo || goog.global;
// Internet Explorer exhibits strange behavior when throwing errors from
// methods externed in this manner. See the testExportSymbolExceptions in
// base_test.html for an example.
if (!(parts[0] in cur) && cur.execScript) {
cur.execScript('var ' + parts[0]);
}
// Certain browsers cannot parse code in the form for((a in b); c;);
// This pattern is produced by the JSCompiler when it collapses the
// statement above into the conditional loop below. To prevent this from
// happening, use a for-loop and reserve the init logic as below.
// Parentheses added to eliminate strict JS warning in Firefox.
for (var part; parts.length && (part = parts.shift());) {
if (!parts.length && goog.isDef(opt_object)) {
// last part and we have an object; use it
cur[part] = opt_object;
} else if (cur[part]) {
cur = cur[part];
} else {
cur = cur[part] = {};
}
}
};
/**
* When defining a class Foo with an abstract method bar(), you can do:
*
* Foo.prototype.bar = goog.abstractMethod
*
* Now if a subclass of Foo fails to override bar(), an error
* will be thrown when bar() is invoked.
*
* Note: This does not take the name of the function to override as
* an argument because that would make it more difficult to obfuscate
* our JavaScript code.
*
* @type {!Function}
* @throws {Error} when invoked to indicate the method should be
* overridden.
*/
goog.abstractMethod = function() {
throw Error('unimplemented abstract method');
};
//==============================================================================
// Language Enhancements
//==============================================================================
/**
* This is a "fixed" version of the typeof operator. It differs from the typeof
* operator in such a way that null returns 'null' and arrays return 'array'.
* @param {*} value The value to get the type of.
* @return {string} The name of the type.
*/
goog.typeOf = function(value) {
var s = typeof value;
if (s == 'object') {
if (value) {
// Check these first, so we can avoid calling Object.prototype.toString if
// possible.
//
// IE improperly marshals tyepof across execution contexts, but a
// cross-context object will still return _FALSE for "instanceof Object".
if (value instanceof Array) {
return 'array';
} else if (value instanceof Object) {
return s;
}
// HACK: In order to use an Object prototype method on the arbitrary
// value, the compiler requires the value be cast to type Object,
// even though the ECMA spec explicitly allows it.
var className = Object.prototype.toString.call(
/** @type {Object} */ (value));
// In Firefox 3.6, attempting to access iframe window objects' length
// property throws an NS_ERROR_FAILURE, so we need to special-case it
// here.
if (className == '[object Window]') {
return 'object';
}
// We cannot always use constructor == Array or instanceof Array because
// different frames have different Array objects. In IE6, if the iframe
// where the array was created is destroyed, the array loses its
// prototype. Then dereferencing val.splice here throws an exception, so
// we can't use goog.isFunction. Calling typeof directly returns 'unknown'
// so that will work. In this case, this function will return _FALSE and
// most array functions will still work because the array is still
// array-like (supports length and []) even though it has lost its
// prototype.
// Mark Miller noticed that Object.prototype.toString
// allows access to the unforgeable [[Class]] property.
// 15.2.4.2 Object.prototype.toString ( )
// When the toString method is called, the following steps are taken:
// 1. Get the [[Class]] property of this object.
// 2. Compute a string value by concatenating the three strings
// "[object ", Result(1), and "]".
// 3. Return Result(2).
// and this behavior survives the destruction of the execution context.
if ((className == '[object Array]' ||
// In IE all non value types are wrapped as objects across window
// boundaries (not iframe though) so we have to do object detection
// for this edge case
typeof value.length == 'number' &&
typeof value.splice != _UNDEFINED+'' &&
typeof value.propertyIsEnumerable != _UNDEFINED+'' &&
!value.propertyIsEnumerable('splice')
)) {
return 'array';
}
// HACK: There is still an array case that fails.
// function ArrayImpostor() {}
// ArrayImpostor.prototype = [];
// var impostor = new ArrayImpostor;
// this can be fixed by getting rid of the fast path
// (value instanceof Array) and solely relying on
// (value && Object.prototype.toString.vall(value) === '[object Array]')
// but that would require many more function calls and is not warranted
// unless closure code is receiving objects from untrusted sources.
// IE in cross-window calls does not correctly marshal the function type
// (it appears just as an object) so we cannot use just typeof val ==
// 'function'. However, if the object has a call property, it is a
// function.
if ((className == '[object Function]' ||
typeof value.call != _UNDEFINED+'' &&
typeof value.propertyIsEnumerable != _UNDEFINED+'' &&
!value.propertyIsEnumerable('call'))) {
return 'function';
}
} else {
return _NULL+'';
}
} else if (s == 'function' && typeof value.call == _UNDEFINED+'') {
// In Safari typeof nodeList returns 'function', and on Firefox
// typeof behaves similarly for HTML{Applet,Embed,Object}Elements
// and RegExps. We would like to return object for those and we can
// detect an invalid function by making sure that the function
// object has a call method.
return 'object';
}
return s;
};
/**
* Returns true if the specified value is not |undefined|.
* WARNING: Do not use this to test if an object has a property. Use the in
* operator instead. Additionally, this function assumes that the global
* undefined variable has not been redefined.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is defined.
*/
goog.isDef = function(val) {
return val !== _UNDEFINED;
};
/**
* Returns true if the specified value is |null|
* @param {*} val Variable to test.
* @return {boolean} Whether variable is null.
*/
goog.isNull = function(val) {
return val === _NULL;
};
/**
* Returns true if the specified value is defined and not null
* @param {*} val Variable to test.
* @return {boolean} Whether variable is defined and not null.
*/
goog.isDefAndNotNull = function(val) {
// Note that undefined == null.
return val != _NULL;
};
/**
* Returns true if the specified value is an array
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an array.
*/
goog.isArray = function(val) {
return goog.typeOf(val) == 'array';
};
/**
* Returns true if the object looks like an array. To qualify as array like
* the value needs to be either a NodeList or an object with a Number length
* property.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an array.
*/
goog.isArrayLike = function(val) {
var type = goog.typeOf(val);
return type == 'array' || type == 'object' && typeof val.length == 'number';
};
/**
* Returns true if the object looks like a Date. To qualify as Date-like
* the value needs to be an object and have a getFullYear() function.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a like a Date.
*/
goog.isDateLike = function(val) {
return goog.isObject(val) && typeof val.getFullYear == 'function';
};
/**
* Returns true if the specified value is a string
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a string.
*/
goog.isString = function(val) {
return typeof val == 'string';
};
/**
* Returns true if the specified value is a boolean
* @param {*} val Variable to test.
* @return {boolean} Whether variable is boolean.
*/
goog.isBoolean = function(val) {
return typeof val == 'boolean';
};
/**
* Returns true if the specified value is a number
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a number.
*/
goog.isNumber = function(val) {
return typeof val == 'number';
};
/**
* Returns true if the specified value is a function
* @param {*} val Variable to test.
* @return {boolean} Whether variable is a function.
*/
goog.isFunction = function(val) {
return goog.typeOf(val) == 'function';
};
/**
* Returns true if the specified value is an object. This includes arrays
* and functions.
* @param {*} val Variable to test.
* @return {boolean} Whether variable is an object.
*/
goog.isObject = function(val) {
var type = goog.typeOf(val);
return type == 'object' || type == 'array' || type == 'function';
};
/**
* Gets a unique ID for an object. This mutates the object so that further
* calls with the same object as a parameter returns the same value. The unique
* ID is guaranteed to be unique across the current session amongst objects that
* are passed into {@code getUid}. There is no guarantee that the ID is unique
* or consistent across sessions. It is unsafe to generate unique ID for
* function prototypes.
*
* @param {Object} obj The object to get the unique ID for.
* @return {number} The unique ID for the object.
*/
goog.getUid = function(obj) {
// TODO(user): Make the type stricter, do not accept null.
// In Opera window.hasOwnProperty exists but always returns _FALSE so we avoid
// using it. As a consequence the unique ID generated for BaseClass.prototype
// and SubClass.prototype will be the same.
return obj[goog.UID_PROPERTY_] ||
(obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
};
/**
* Name for unique ID property. Initialized in a way to help avoid collisions
* with other closure javascript on the same page.
* @type {string}
* @private
*/
goog.UID_PROPERTY_ = 'closure_uid_' +
Math.floor(Math.random() * 2147483648).toString(36);
/**
* Counter for UID.
* @type {number}
* @private
*/
goog.uidCounter_ = 0;
/**
* Forward declaration for the clone method. This is necessary until the
* compiler can better support duck-typing constructs as used in
* goog.cloneObject.
*
* TODO(user): Remove once the JSCompiler can infer that the check for
* proto.clone is safe in goog.cloneObject.
*
* @type {Function}
*/
Object.prototype.clone;
/**
* A native implementation of goog.bind.
* @param {Function} fn A function to partially apply.
* @param {Object|undefined} selfObj Specifies the object which |this| should
* point to when the function is run. If the value is null or undefined, it
* will default to the global object.
* @param {...*} var_args Additional arguments that are partially
* applied to the function.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
* @private
* @suppress {deprecated} The compiler thinks that Function.prototype.bind
* is deprecated because some people have declared a pure-JS version.
* Only the pure-JS version is truly deprecated.
*/
goog.bindNative_ = function(fn, selfObj, var_args) {
return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
};
/**
* A pure-JS implementation of goog.bind.
* @param {Function} fn A function to partially apply.
* @param {Object|undefined} selfObj Specifies the object which |this| should
* point to when the function is run. If the value is null or undefined, it
* will default to the global object.
* @param {...*} var_args Additional arguments that are partially
* applied to the function.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
* @private
*/
goog.bindJs_ = function(fn, selfObj, var_args) {
var context = selfObj || goog.global;
if (arguments.length > 2) {
var boundArgs = Array.prototype.slice.call(arguments, 2);
return function() {
// Prepend the bound arguments to the current arguments.
var newArgs = Array.prototype.slice.call(arguments);
Array.prototype.unshift.apply(newArgs, boundArgs);
return fn.apply(context, newArgs);
};
} else {
return function() {
return fn.apply(context, arguments);
};
}
};
/**
* Partially applies this function to a particular 'this object' and zero or
* more arguments. The result is a new function with some arguments of the first
* function pre-filled and the value of |this| 'pre-specified'.
*
* Remaining arguments specified at call-time are appended to the pre-
* specified ones.
*
* Also see: {@link #partial}.
*
* Usage:
* var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
* barMethBound('arg3', 'arg4');
*
* @param {Function} fn A function to partially apply.
* @param {Object|undefined} selfObj Specifies the object which |this| should
* point to when the function is run. If the value is null or undefined, it
* will default to the global object.
* @param {...*} var_args Additional arguments that are partially
* applied to the function.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
* @suppress {deprecated} See above.
*/
goog.bind = function(fn, selfObj, var_args) {
// TODO(nicksantos): narrow the type signature.
if (Function.prototype.bind &&
// NOTE(nicksantos): Somebody pulled base.js into the default
// Chrome extension environment. This means that for Chrome extensions,
// they get the implementation of Function.prototype.bind that
// calls goog.bind instead of the native one. Even worse, we don't want
// to introduce a circular dependency between goog.bind and
// Function.prototype.bind, so we have to hack this to make sure it
// works correctly.
Function.prototype.bind.toString().indexOf('native code') != -1) {
goog.bind = goog.bindNative_;
} else {
goog.bind = goog.bindJs_;
}
return goog.bind.apply(_NULL, arguments);
};
/**
* Like bind(), except that a 'this object' is not required. Useful when the
* target function is already bound.
*
* Usage:
* var g = partial(f, arg1, arg2);
* g(arg3, arg4);
*
* @param {Function} fn A function to partially apply.
* @param {...*} var_args Additional arguments that are partially
* applied to fn.
* @return {!Function} A partially-applied form of the function bind() was
* invoked as a method of.
*/
goog.partial = function(fn, var_args) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
// Prepend the bound arguments to the current arguments.
var newArgs = Array.prototype.slice.call(arguments);
newArgs.unshift.apply(newArgs, args);
return fn.apply(this, newArgs);
};
};
/**
* @return {number} An integer value representing the number of milliseconds
* between midnight, January 1, 1970 and the current time.
*/
goog.now = Date.now || (function() {
// Unary plus operator converts its operand to a number which in the case of
// a date is done by calling getTime().
return +new Date();
});
/**
* Inherit the prototype methods from one constructor into another.
*
* Usage:
*
* function ParentClass(a, b) { }
* ParentClass.prototype.foo = function(a) { }
*
* function ChildClass(a, b, c) {
* ParentClass.call(this, a, b);
* }
*
* goog.inherits(ChildClass, ParentClass);
*
* var child = new ChildClass('a', 'b', 'see');
* child.foo(); // works
*
*
* In addition, a superclass' implementation of a method can be invoked
* as follows:
*
*
* ChildClass.prototype.foo = function(a) {
* ChildClass.superClass_.foo.call(this, a);
* // other code
* };
*
*
* @param {Function} childCtor Child class.
* @param {Function} parentCtor Parent class.
*/
goog.inherits = function(childCtor, parentCtor) {
/** @constructor */
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
/**
* Call up to the superclass.
*
* If this is called from a constructor, then this calls the superclass
* contructor with arguments 1-N.
*
* If this is called from a prototype method, then you must pass
* the name of the method as the second argument to this function. If
* you do not, you will get a runtime error. This calls the superclass'
* method with arguments 2-N.
*
* This function only works if you use goog.inherits to express
* inheritance relationships between your classes.
*
* This function is a compiler primitive. At compile-time, the
* compiler will do macro expansion to remove a lot of
* the extra overhead that this function introduces. The compiler
* will also enforce a lot of the assumptions that this function
* makes, and treat it as a compiler error if you break them.
*
* @param {!Object} me Should always be "this".
* @param {*=} opt_methodName The method name if calling a super method.
* @param {...*} var_args The rest of the arguments.
* @return {*} The return value of the superclass method.
*/
goog.base = function(me, opt_methodName, var_args) {
var caller = arguments.callee.caller;
if (caller.superClass_) {
// This is a constructor. Call the superclass constructor.
return caller.superClass_.constructor.apply(
me, Array.prototype.slice.call(arguments, 1));
}
var args = Array.prototype.slice.call(arguments, 2);
var foundCaller = _FALSE;
for (var ctor = me.constructor;
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
if (ctor.prototype[opt_methodName] === caller) {
foundCaller = _TRUE;
} else if (foundCaller) {
return ctor.prototype[opt_methodName].apply(me, args);
}
}
// If we did not find the caller in the prototype chain,
// then one of two things happened:
// 1) The caller is an instance method.
// 2) This method was not called by the right caller.
if (me[opt_methodName] === caller) {
return me.constructor.prototype[opt_methodName].apply(me, args);
} else {
throw Error(
'goog.base called from a method of one name ' +
'to a method of a different name');
}
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for string manipulation.
*/
/**
* Namespace for string utilities
*/
goog.provide('goog.string');
goog.provide('goog.string.Unicode');
/**
* Common Unicode string characters.
* @enum {string}
*/
goog.string.Unicode = {
NBSP: '\xa0'
};
/**
* Fast prefix-checker.
* @param {string} str The string to check.
* @param {string} prefix A string to look for at the start of {@code str}.
* @return {boolean} True if {@code str} begins with {@code prefix}.
*/
goog.string.startsWith = function(str, prefix) {
return str.lastIndexOf(prefix, 0) == 0;
};
/**
* Checks if a string is empty or contains only whitespaces.
* @param {string} str The string to check.
* @return {boolean} True if {@code str} is empty or whitespace only.
*/
goog.string.isEmpty = function(str) {
// testing length == 0 first is actually slower in all browsers (about the
// same in Opera).
// Since IE doesn't include non-breaking-space (0xa0) in their \s character
// class (as required by section 7.2 of the ECMAScript spec), we explicitly
// include it in the regexp to enforce consistent cross-browser behavior.
return /^[\s\xa0]*$/.test(str);
};
/**
* Takes a string and replaces newlines with a space. Multiple lines are
* replaced with a single space.
* @param {string} str The string from which to strip newlines.
* @return {string} A copy of {@code str} stripped of newlines.
*/
// FTDUEDTR-1183
goog.string.stripNewlines = function(str) {
return str.replace(/ ?(\r\n|\r|\n)+/g, ' ');
};
/**
* Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
* @param {string} str The string to in which to canonicalize newlines.
* @return {string} {@code str} A copy of {@code} with canonicalized newlines.
*/
goog.string.canonicalizeNewlines = function(str) {
return str.replace(/(\r\n|\r|\n)/g, '\n');
};
/**
* Trims white spaces to the left and right of a string.
* @param {string} str The string to trim.
* @return {string} A trimmed copy of {@code str}.
*/
goog.string.trim = function(str) {
// Since IE doesn't include non-breaking-space (0xa0) in their \s character
// class (as required by section 7.2 of the ECMAScript spec), we explicitly
// include it in the regexp to enforce consistent cross-browser behavior.
return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
};
/**
* Escape double quote '"' characters in addition to '&', '<', and '>' so that a
* string can be included in an HTML tag attribute value within double quotes.
*
* It should be noted that > doesn't need to be escaped for the HTML or XML to
* be valid, but it has been decided to escape it for consistency with other
* implementations.
*
* NOTE(user):
* HtmlEscape is often called during the generation of large blocks of HTML.
* Using statics for the regular expressions and strings is an optimization
* that can more than half the amount of time IE spends in this function for
* large apps, since strings and regexes both contribute to GC allocations.
*
* Testing for the presence of a character before escaping increases the number
* of function calls, but actually provides a speed increase for the average
* case -- since the average case often doesn't require the escaping of all 4
* characters and indexOf() is much cheaper than replace().
* The worst case does suffer slightly from the additional calls, therefore the
* opt_isLikelyToContainHtmlChars option has been included for situations
* where all 4 HTML entities are very likely to be present and need escaping.
*
* Some benchmarks (times tended to fluctuate +-0.05ms):
* FireFox IE6
* (no chars / average (mix of cases) / all 4 chars)
* no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80
* indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84
* indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85
*
* An additional advantage of checking if replace actually needs to be called
* is a reduction in the number of object allocations, so as the size of the
* application grows the difference between the various methods would increase.
*
* @param {string} str string to be escaped.
* @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
* if the character needs replacing - use this option if you expect each of
* the characters to appear often. Leave _FALSE if you expect few html
* characters to occur in your strings, such as if you are escaping HTML.
* @return {string} An escaped copy of {@code str}.
*/
goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
if (opt_isLikelyToContainHtmlChars) {
return str.replace(goog.string.amperRe_, '&')
.replace(goog.string.ltRe_, '<')
.replace(goog.string.gtRe_, '>')
.replace(goog.string.quotRe_, '"');
} else {
// quick test helps in the case when there are no chars to replace, in
// worst case this makes barely a difference to the time taken
if (!goog.string.allRe_.test(str)) return str;
// str.indexOf is faster than regex.test in this case
if (str.indexOf('&') != -1) {
str = str.replace(goog.string.amperRe_, '&');
}
if (str.indexOf('<') != -1) {
str = str.replace(goog.string.ltRe_, '<');
}
if (str.indexOf('>') != -1) {
str = str.replace(goog.string.gtRe_, '>');
}
if (str.indexOf('"') != -1) {
str = str.replace(goog.string.quotRe_, '"');
}
return str;
}
};
/**
* Regular expression that matches an ampersand, for use in escaping.
* @type {RegExp}
* @private
*/
goog.string.amperRe_ = /&/g;
/**
* Regular expression that matches a less than sign, for use in escaping.
* @type {RegExp}
* @private
*/
goog.string.ltRe_ = //g;
/**
* Regular expression that matches a double quote, for use in escaping.
* @type {RegExp}
* @private
*/
goog.string.quotRe_ = /\"/g;
/**
* Regular expression that matches any character that needs to be escaped.
* @type {RegExp}
* @private
*/
goog.string.allRe_ = /[&<>\"]/;
/**
* Checks whether a string contains a given character.
* @param {string} s The string to test.
* @param {string} ss The substring to test for.
* @return {boolean} True if {@code s} contains {@code ss}.
*/
goog.string.contains = function(s, ss) {
return s.indexOf(ss) != -1;
};
/**
* Concatenates string expressions. This is useful
* since some browsers are very inefficient when it comes to using plus to
* concat strings. Be careful when using null and undefined here since
* these will not be included in the result. If you need to represent these
* be sure to cast the argument to a String first.
* For example:
* buildString('a', 'b', 'c', 'd') -> 'abcd'
* buildString(null, undefined) -> ''
*
* @param {...*} var_args A list of strings to concatenate. If not a string,
* it will be casted to one.
* @return {string} The concatenation of {@code var_args}.
*/
goog.string.buildString = function(var_args) {
return Array.prototype.join.call(arguments, '');
};
/**
* Compares two version numbers.
*
* @param {string|number} version1 Version of first item.
* @param {string|number} version2 Version of second item.
*
* @return {number} 1 if {@code version1} is higher.
* 0 if arguments are equal.
* -1 if {@code version2} is higher.
*/
goog.string.compareVersions = function(version1, version2) {
var order = 0;
// Trim leading and trailing whitespace and split the versions into
// subversions.
var v1Subs = goog.string.trim(String(version1)).split('.');
var v2Subs = goog.string.trim(String(version2)).split('.');
var subCount = Math.max(v1Subs.length, v2Subs.length);
// Iterate over the subversions, as long as they appear to be equivalent.
for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
var v1Sub = v1Subs[subIdx] || '';
var v2Sub = v2Subs[subIdx] || '';
// Split the subversions into pairs of numbers and qualifiers (like 'b').
// Two different RegExp objects are needed because they are both using
// the 'g' flag.
var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
do {
var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
// Break if there are no more matches.
if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
break;
}
// Parse the numeric part of the subversion. A missing number is
// equivalent to 0.
var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
// Compare the subversion components. The number has the highest
// precedence. Next, if the numbers are equal, a subversion without any
// qualifier is always higher than a subversion with any qualifier. Next,
// the qualifiers are compared as strings.
order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
goog.string.compareElements_(v1Comp[2].length == 0,
v2Comp[2].length == 0) ||
goog.string.compareElements_(v1Comp[2], v2Comp[2]);
// Stop as soon as an inequality is discovered.
} while (order == 0);
}
return order;
};
/**
* Compares elements of a version number.
*
* @param {string|number|boolean} left An element from a version number.
* @param {string|number|boolean} right An element from a version number.
*
* @return {number} 1 if {@code left} is higher.
* 0 if arguments are equal.
* -1 if {@code right} is higher.
* @private
*/
goog.string.compareElements_ = function(left, right) {
if (left < right) {
return -1;
} else if (left > right) {
return 1;
}
return 0;
};
/**
* The most recent unique ID. |0 is equivalent to Math.floor in this case.
* @type {number}
* @private
*/
goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;
/**
* Generates and returns a string which is unique in the current document.
* This is useful, for example, to create unique IDs for DOM elements.
* @return {string} A unique id.
*/
goog.string.createUniqueString = function() {
return 'goog_' + goog.string.uniqueStringCounter_++;
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Rendering engine detection.
* @see User agent strings
* For information on the browser brand (such as Safari versus Chrome), see
* goog.userAgent.product.
* @see ../demos/useragent.html
*/
goog.provide('goog.userAgent');
//goog.require('goog.string');
/**
* @define {boolean} Whether we know at compile-time that the browser is IE.
*/
goog.userAgent.ASSUME_IE = _FALSE;
/**
* @define {boolean} Whether we know at compile-time that the browser is GECKO.
*/
goog.userAgent.ASSUME_GECKO = _FALSE;
/**
* @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
*/
goog.userAgent.ASSUME_WEBKIT = _FALSE;
/**
* @define {boolean} Whether we know at compile-time that the browser is a
* mobile device running WebKit e.g. iPhone or Android.
*/
goog.userAgent.ASSUME_MOBILE_WEBKIT = _FALSE;
/**
* @define {boolean} Whether we know at compile-time that the browser is OPERA.
*/
goog.userAgent.ASSUME_OPERA = _FALSE;
/**
* @define {boolean} Whether the
* {@code goog.userAgent.isVersionOrHigher}
* function will return true for any version.
*/
goog.userAgent.ASSUME_ANY_VERSION = _FALSE;
/**
* Whether we know the browser engine at compile-time.
* @type {boolean}
* @private
*/
goog.userAgent.BROWSER_KNOWN_ =
goog.userAgent.ASSUME_IE ||
goog.userAgent.ASSUME_GECKO ||
goog.userAgent.ASSUME_MOBILE_WEBKIT ||
goog.userAgent.ASSUME_WEBKIT ||
goog.userAgent.ASSUME_OPERA;
/**
* Returns the userAgent string for the current browser.
* Some user agents (I'm thinking of you, Gears WorkerPool) do not expose a
* navigator object off the global scope. In that case we return null.
*
* @return {?string} The userAgent string or null if there is none.
*/
goog.userAgent.getUserAgentString = function() {
return goog.global['navigator'] ? goog.global['navigator'].userAgent : _NULL;
};
/**
* @return {Object} The native navigator object.
*/
goog.userAgent.getNavigator = function() {
// Need a local navigator reference instead of using the global one,
// to avoid the rare case where they reference different objects.
// (goog.gears.FakeWorkerPool, for example).
return goog.global['navigator'];
};
/**
* Initializer for goog.userAgent.
*
* This is a named function so that it can be stripped via the jscompiler
* option for stripping types.
* @private
*/
goog.userAgent.init_ = function() {
/**
* Whether the user agent string denotes Opera.
* @type {boolean}
* @private
*/
goog.userAgent.detectedOpera_ = _FALSE;
/**
* Whether the user agent string denotes Internet Explorer. This includes
* other browsers using Trident as its rendering engine. For example AOL
* and Netscape 8
* @type {boolean}
* @private
*/
goog.userAgent.detectedIe_ = _FALSE;
/**
* Whether the user agent string denotes WebKit. WebKit is the rendering
* engine that Safari, Android and others use.
* @type {boolean}
* @private
*/
goog.userAgent.detectedWebkit_ = _FALSE;
/**
* Whether the user agent string denotes a mobile device.
* @type {boolean}
* @private
*/
goog.userAgent.detectedMobile_ = _FALSE;
/**
* Whether the user agent string denotes Gecko. Gecko is the rendering
* engine used by Mozilla, Mozilla Firefox, Camino and many more.
* @type {boolean}
* @private
*/
goog.userAgent.detectedGecko_ = _FALSE;
var ua;
if (!goog.userAgent.BROWSER_KNOWN_ &&
(ua = goog.userAgent.getUserAgentString())) {
var navigator = goog.userAgent.getNavigator();
goog.userAgent.detectedOpera_ = goog.string.startsWith(ua, 'Opera');
goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ &&
(goog.string.contains(ua, 'MSIE') ||
goog.string.contains(ua, 'Trident'));
goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ &&
goog.string.contains(ua, 'WebKit');
// WebKit also gives navigator.product string equal to 'Gecko'.
goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ &&
goog.string.contains(ua, 'Mobile');
goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ &&
!goog.userAgent.detectedWebkit_ && !goog.userAgent.detectedIe_ &&
navigator.product == 'Gecko';
}
};
if (!goog.userAgent.BROWSER_KNOWN_) {
goog.userAgent.init_();
}
/**
* Whether the user agent is Opera.
* @type {boolean}
*/
goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_;
/**
* Whether the user agent is Internet Explorer. This includes other browsers
* using Trident as its rendering engine. For example AOL and Netscape 8
* @type {boolean}
*/
goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_;
/**
* Whether the user agent is Gecko. Gecko is the rendering engine used by
* Mozilla, Mozilla Firefox, Camino and many more.
* @type {boolean}
*/
goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_GECKO :
goog.userAgent.detectedGecko_;
/**
* Whether the user agent is WebKit. WebKit is the rendering engine that
* Safari, Android and others use.
* @type {boolean}
*/
goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
goog.userAgent.detectedWebkit_;
/**
* Whether the user agent is running on a mobile device.
* @type {boolean}
*/
goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
goog.userAgent.detectedMobile_;
/**
* Used while transitioning code to use WEBKIT instead.
* @type {boolean}
* @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
* TODO(nicksantos): Delete this from goog.userAgent.
*/
goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
/**
* @return {string} the platform (operating system) the user agent is running
* on. Default to empty string because navigator.platform may not be defined
* (on Rhino, for example).
* @private
*/
goog.userAgent.determinePlatform_ = function() {
var navigator = goog.userAgent.getNavigator();
return navigator && navigator.platform || '';
};
/**
* The platform (operating system) the user agent is running on. Default to
* empty string because navigator.platform may not be defined (on Rhino, for
* example).
* @type {string}
*/
goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
/**
* @define {boolean} Whether the user agent is running on a Macintosh operating
* system.
*/
goog.userAgent.ASSUME_MAC = _FALSE;
/**
* @define {boolean} Whether the user agent is running on a Windows operating
* system.
*/
goog.userAgent.ASSUME_WINDOWS = _FALSE;
/**
* @define {boolean} Whether the user agent is running on a Linux operating
* system.
*/
goog.userAgent.ASSUME_LINUX = _FALSE;
/**
* @define {boolean} Whether the user agent is running on a X11 windowing
* system.
*/
goog.userAgent.ASSUME_X11 = _FALSE;
/**
* @type {boolean}
* @private
*/
goog.userAgent.PLATFORM_KNOWN_ =
goog.userAgent.ASSUME_MAC ||
goog.userAgent.ASSUME_WINDOWS ||
goog.userAgent.ASSUME_LINUX ||
goog.userAgent.ASSUME_X11;
/**
* Initialize the goog.userAgent constants that define which platform the user
* agent is running on.
* @private
*/
goog.userAgent.initPlatform_ = function() {
/**
* Whether the user agent is running on a Macintosh operating system.
* @type {boolean}
* @private
*/
goog.userAgent.detectedMac_ =goog.string.contains(goog.userAgent.PLATFORM,
'Mac');
/**
* Whether the user agent is running on a Windows operating system.
* @type {boolean}
* @private
*/
goog.userAgent.detectedWindows_ = goog.string.contains(
goog.userAgent.PLATFORM, 'Win');
/**
* Whether the user agent is running on a Linux operating system.
* @type {boolean}
* @private
*/
goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM,
'Linux');
/**
* Whether the user agent is running on a X11 windowing system.
* @type {boolean}
* @private
*/
goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() &&
goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '',
'X11');
};
if (!goog.userAgent.PLATFORM_KNOWN_) {
goog.userAgent.initPlatform_();
}
/**
* Whether the user agent is running on a Macintosh operating system.
* @type {boolean}
*/
goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_;
/**
* Whether the user agent is running on a Windows operating system.
* @type {boolean}
*/
goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_;
/**
* Whether the user agent is running on a Linux operating system.
* @type {boolean}
*/
goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_;
/**
* Whether the user agent is running on a X11 windowing system.
* @type {boolean}
*/
goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_;
/**
* @return {string} The string that describes the version number of the user
* agent.
* @private
*/
goog.userAgent.determineVersion_ = function() {
// All browsers have different ways to detect the version and they all have
// different naming schemes.
// version is a string rather than a number because it may contain 'b', 'a',
// and so on.
var version = '', re;
if (goog.userAgent.OPERA && goog.global['opera']) {
var operaVersion = goog.global['opera'].version;
version = typeof operaVersion == 'function' ? operaVersion() : operaVersion;
} else {
if (goog.userAgent.GECKO) {
re = /rv\:([^\);]+)(\)|;)/;
} else if (goog.userAgent.IE) {
re = /MSIE\s+([^\);]+)(\)|;)/;
} else if (goog.userAgent.WEBKIT) {
// WebKit/125.4
re = /WebKit\/(\S+)/;
}
if (re) {
var arr = re.exec(goog.userAgent.getUserAgentString());
version = arr ? arr[1] : '';
}
}
if (goog.userAgent.IE) {
// IE9 can be in document mode 9 but be reporting an inconsistent user agent
// version. If it is identifying as a version lower than 9 we take the
// documentMode as the version instead. IE8 has similar behavior.
// It is recommended to set the X-UA-Compatible header to ensure that IE9
// uses documentMode 9.
var docMode = goog.userAgent.getDocumentMode_();
if (docMode > parseFloat(version)) {
return String(docMode);
}
}
return version;
};
/**
* @return {number|undefined} Returns the document mode (for testing).
* @private
*/
goog.userAgent.getDocumentMode_ = function() {
// NOTE(user): goog.userAgent may be used in context where there is no DOM.
var doc = goog.global['document'];
return doc ? doc['documentMode'] : _UNDEFINED;
};
/**
* The version of the user agent. This is a string because it might contain
* 'b' (as in beta) as well as multiple dots.
* @type {string}
*/
goog.userAgent.VERSION = goog.userAgent.determineVersion_();
/**
* Compares two version numbers.
*
* @param {string} v1 Version of first item.
* @param {string} v2 Version of second item.
*
* @return {number} 1 if first argument is higher
* 0 if arguments are equal
* -1 if second argument is higher.
* @deprecated Use goog.string.compareVersions.
*/
goog.userAgent.compare = function(v1, v2) {
return goog.string.compareVersions(v1, v2);
};
/**
* Cache for {@link goog.userAgent.isVersion}. Calls to compareVersions are
* surprisingly expensive and as a browsers version number is unlikely to change
* during a session we cache the results.
* @type {Object}
* @private
*/
goog.userAgent.isVersionOrHigherCache_ = {};
/**
* Whether the user agent version is higher or the same as the given version.
* NOTE: When checking the version numbers for Firefox and Safari, be sure to
* use the engine's version, not the browser's version number. For example,
* Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
* Opera and Internet Explorer versions match the product release number.
* @see
* Webkit
* @see Gecko
*
* @param {string|number} version The version to check.
* @return {boolean} Whether the user agent version is higher or the same as
* the given version.
*/
goog.userAgent.isVersionOrHigher = function(version) {
return goog.userAgent.ASSUME_ANY_VERSION ||
goog.userAgent.isVersionOrHigherCache_[version] ||
(goog.userAgent.isVersionOrHigherCache_[version] =
goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
};
/**
* Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
* @param {string|number} version The version to check.
* @return {boolean} Whether the user agent version is higher or the same as
* the given version.
* @deprecated Use goog.userAgent.isVersionOrHigher().
*/
goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
/**
* Cache for {@link goog.userAgent.isDocumentMode}.
* Browsers document mode version number is unlikely to change during a session
* we cache the results.
* @type {Object}
* @private
*/
goog.userAgent.isDocumentModeCache_ = {};
/**
* Whether the IE effective document mode is higher or the same as the given
* document mode version.
* NOTE: Only for IE, return false for another browser.
*
* @param {number} documentMode The document mode version to check.
* @return {boolean} Whether the IE effective document mode is higher or the
* same as the given version.
*/
goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
};
/**
* Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
* @param {number} version The version to check.
* @return {boolean} Whether the IE effective document mode is higher or the
* same as the given version.
* @deprecated Use goog.userAgent.isDocumentModeOrHigher().
*/
goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
/**
* For IE version < 7, documentMode is undefined, so attempt to use the
* CSS1Compat property to see if we are in standards mode. If we are in
* standards mode, treat the browser version as the document mode. Otherwise,
* IE is emulating version 5.
* @type {number|undefined}
* @const
*/
goog.userAgent.DOCUMENT_MODE = (function() {
var doc = goog.global['document'];
if (!doc || !goog.userAgent.IE) {
return undefined;
}
var mode = goog.userAgent.getDocumentMode_();
return mode || (doc['compatMode'] == 'CSS1Compat' ?
parseInt(goog.userAgent.VERSION, 10) : 5);
})();
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for manipulating arrays.
*
*/
goog.provide('goog.array');
goog.provide('goog.array.ArrayLike');
/**
* @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
* rely on Array.prototype functions, if available.
*
* The Array.prototype functions can be defined by external libraries like
* Prototype and setting this flag to _FALSE forces closure to use its own
* goog.array implementation.
*
* If your javascript can be loaded by a third party site and you are wary about
* relying on the prototype functions, specify
* "--define goog.NATIVE_ARRAY_PROTOTYPES=_FALSE" to the JSCompiler.
*/
goog.NATIVE_ARRAY_PROTOTYPES = _TRUE;
/**
* @typedef {Array|NodeList|Arguments|{length: number}}
*/
goog.array.ArrayLike;
/**
* Returns the last element in an array without removing it.
* @param {goog.array.ArrayLike} array The array.
* @return {*} Last item in array.
*/
goog.array.peek = function(array) {
return array[array.length - 1];
};
/**
* Reference to the original {@code Array.prototype}.
* @private
*/
goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
// NOTE(user): Since most of the array functions are generic it allows you to
// pass an array-like object. Strings have a length and are considered array-
// like. However, the 'in' operator does not work on strings so we cannot just
// use the array path even if the browser supports indexing into strings. We
// therefore end up splitting the string.
/**
* Returns the index of the first element of an array with a specified
* value, or -1 if the element is not present in the array.
*
* See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
*
* @param {goog.array.ArrayLike} arr The array to be searched.
* @param {*} obj The object for which we are searching.
* @param {number=} opt_fromIndex The index at which to start the search. If
* omitted the search starts at index 0.
* @return {number} The index of the first matching array element.
*/
goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
goog.array.ARRAY_PROTOTYPE_.indexOf ?
function(arr, obj, opt_fromIndex) {
return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
} :
function(arr, obj, opt_fromIndex) {
var fromIndex = opt_fromIndex == _NULL ?
0 : (opt_fromIndex < 0 ?
Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
if (goog.isString(arr)) {
// Array.prototype.indexOf uses === so only strings should be found.
if (!goog.isString(obj) || obj.length != 1) {
return -1;
}
return arr.indexOf(obj, fromIndex);
}
for (var i = fromIndex; i < arr.length; i++) {
if (i in arr && arr[i] === obj)
return i;
}
return -1;
};
/**
* Calls a function for each element in an array.
*
* See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
*
* @param {goog.array.ArrayLike} arr Array or array like object over
* which to iterate.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array). The return
* value is ignored. The function is called only for indexes of the array
* which have assigned values; it is not called for indexes which have
* been deleted or which have never been assigned values.
*
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within f.
*/
goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
goog.array.ARRAY_PROTOTYPE_.forEach ?
function(arr, f, opt_obj) {
goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
} :
function(arr, f, opt_obj) {
var l = arr.length; // must be fixed during loop... see docs
var arr2 = goog.isString(arr) ? arr.split('') : arr;
for (var i = 0; i < l; i++) {
if (i in arr2) {
f.call(opt_obj, arr2[i], i, arr);
}
}
};
/**
* Calls a function for each element in an array, starting from the last
* element rather than the first.
*
* @param {goog.array.ArrayLike} arr The array over which to iterate.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array). The return
* value is ignored.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within f.
*/
goog.array.forEachRight = function(arr, f, opt_obj) {
var l = arr.length; // must be fixed during loop... see docs
var arr2 = goog.isString(arr) ? arr.split('') : arr;
for (var i = l - 1; i >= 0; --i) {
if (i in arr2) {
f.call(opt_obj, arr2[i], i, arr);
}
}
};
/**
* Calls a function for each element in an array, and if the function returns
* true adds the element to a new array.
*
* See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
*
* @param {goog.array.ArrayLike} arr The array over which to iterate.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array) and must
* return a Boolean. If the return value is true the element is added to the
* result array. If it is _FALSE the element is not included.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within f.
* @return {!Array} a new array in which only elements that passed the test are
* present.
*/
goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
goog.array.ARRAY_PROTOTYPE_.filter ?
function(arr, f, opt_obj) {
return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
} :
function(arr, f, opt_obj) {
var l = arr.length; // must be fixed during loop... see docs
var res = [];
var resLength = 0;
var arr2 = goog.isString(arr) ? arr.split('') : arr;
for (var i = 0; i < l; i++) {
if (i in arr2) {
var val = arr2[i]; // in case f mutates arr2
if (f.call(opt_obj, val, i, arr)) {
res[resLength++] = val;
}
}
}
return res;
};
/**
* Calls a function for each element in an array and inserts the result into a
* new array.
*
* See {@link http://tinyurl.com/developer-mozilla-org-array-map}
*
* @param {goog.array.ArrayLike} arr The array over which to iterate.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array) and should
* return something. The result will be inserted into a new array.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within f.
* @return {!Array} a new array with the results from f.
*/
goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
goog.array.ARRAY_PROTOTYPE_.map ?
function(arr, f, opt_obj) {
return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
} :
function(arr, f, opt_obj) {
var l = arr.length; // must be fixed during loop... see docs
var res = new Array(l);
var arr2 = goog.isString(arr) ? arr.split('') : arr;
for (var i = 0; i < l; i++) {
if (i in arr2) {
res[i] = f.call(opt_obj, arr2[i], i, arr);
}
}
return res;
};
/**
* Calls f for each element of an array. If any call returns true, some()
* returns true (without checking the remaining elements). If all calls
* return _FALSE, some() returns _FALSE.
*
* See {@link http://tinyurl.com/developer-mozilla-org-array-some}
*
* @param {goog.array.ArrayLike} arr The array to check.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array) and must
* return a Boolean.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within f.
* @return {boolean} true if any element passes the test.
*/
goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
goog.array.ARRAY_PROTOTYPE_.some ?
function(arr, f, opt_obj) {
return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
} :
function(arr, f, opt_obj) {
var l = arr.length; // must be fixed during loop... see docs
var arr2 = goog.isString(arr) ? arr.split('') : arr;
for (var i = 0; i < l; i++) {
if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
return _TRUE;
}
}
return _FALSE;
};
/**
* Call f for each element of an array. If all calls return true, every()
* returns true. If any call returns _FALSE, every() returns _FALSE and
* does not continue to check the remaining elements.
*
* See {@link http://tinyurl.com/developer-mozilla-org-array-every}
*
* @param {goog.array.ArrayLike} arr The array to check.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array) and must
* return a Boolean.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within f.
* @return {boolean} _FALSE if any element fails the test.
*/
goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
goog.array.ARRAY_PROTOTYPE_.every ?
function(arr, f, opt_obj) {
return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
} :
function(arr, f, opt_obj) {
var l = arr.length; // must be fixed during loop... see docs
var arr2 = goog.isString(arr) ? arr.split('') : arr;
for (var i = 0; i < l; i++) {
if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
return _FALSE;
}
}
return _TRUE;
};
/**
* Whether the array contains the given object.
* @param {goog.array.ArrayLike} arr The array to test for the presence of the
* element.
* @param {*} obj The object for which to test.
* @return {boolean} true if obj is present.
*/
goog.array.contains = function(arr, obj) {
return goog.array.indexOf(arr, obj) >= 0;
};
/**
* Whether the array is empty.
* @param {goog.array.ArrayLike} arr The array to test.
* @return {boolean} true if empty.
*/
goog.array.isEmpty = function(arr) {
return arr.length == 0;
};
/**
* Clears the array.
* @param {goog.array.ArrayLike} arr Array or array like object to clear.
*/
goog.array.clear = function(arr) {
// For non real arrays we don't have the magic length so we delete the
// indices.
if (!goog.isArray(arr)) {
for (var i = arr.length - 1; i >= 0; i--) {
delete arr[i];
}
}
arr.length = 0;
};
/**
* Inserts an object at the given index of the array.
* @param {goog.array.ArrayLike} arr The array to modify.
* @param {*} obj The object to insert.
* @param {number=} opt_i The index at which to insert the object. If omitted,
* treated as 0. A negative index is counted from the end of the array.
*/
goog.array.insertAt = function(arr, obj, opt_i) {
goog.array.splice(arr, opt_i, 0, obj);
};
/**
* Removes the first occurrence of a particular value from an array.
* @param {goog.array.ArrayLike} arr Array from which to remove value.
* @param {*} obj Object to remove.
* @return {boolean} True if an element was removed.
*/
goog.array.remove = function(arr, obj) {
var i = goog.array.indexOf(arr, obj);
var rv;
if ((rv = i >= 0)) {
goog.array.removeAt(arr, i);
}
return rv;
};
/**
* Removes from an array the element at index i
* @param {goog.array.ArrayLike} arr Array or array like object from which to
* remove value.
* @param {number} i The index to remove.
* @return {boolean} True if an element was removed.
*/
goog.array.removeAt = function(arr, i) {
// use generic form of splice
// splice returns the removed items and if successful the length of that
// will be 1
return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
};
/**
* Returns a new array that is the result of joining the arguments. If arrays
* are passed then their items are added, however, if non-arrays are passed they
* will be added to the return array as is.
*
* Note that ArrayLike objects will be added as is, rather than having their
* items added.
*
* goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
* goog.array.concat(0, [1, 2]) -> [0, 1, 2]
* goog.array.concat([1, 2], null) -> [1, 2, null]
*
* There is bug in all current versions of IE (6, 7 and 8) where arrays created
* in an iframe become corrupted soon (not immediately) after the iframe is
* destroyed. This is common if loading data via goog.net.IframeIo, for example.
* This corruption only affects the concat method which will start throwing
* Catastrophic Errors (#-2147418113).
*
* See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
*
* Internally goog.array should use this, so that all methods will continue to
* work on these broken array objects.
*
* @param {...*} var_args Items to concatenate. Arrays will have each item
* added, while primitives and objects will be added as is.
* @return {!Array} The new resultant array.
*/
goog.array.concat = function(var_args) {
return goog.array.ARRAY_PROTOTYPE_.concat.apply(
goog.array.ARRAY_PROTOTYPE_, arguments);
};
/**
* Does a shallow copy of an array.
* @param {goog.array.ArrayLike} arr Array or array-like object to clone.
* @return {!Array} Clone of the input array.
*/
goog.array.clone = function(arr) {
if (goog.isArray(arr)) {
return goog.array.concat(/** @type {!Array} */ (arr));
} else { // array like
// Concat does not work with non arrays.
var rv = [];
for (var i = 0, len = arr.length; i < len; i++) {
rv[i] = arr[i];
}
return rv;
}
};
/**
* Converts an object to an array.
* @param {goog.array.ArrayLike} object The object to convert to an array.
* @return {!Array} The object converted into an array. If object has a
* length property, every property indexed with a non-negative number
* less than length will be included in the result. If object does not
* have a length property, an empty array will be returned.
*/
goog.array.toArray = function(object) {
if (goog.isArray(object)) {
// This fixes the JS compiler warning and forces the Object to an Array type
return goog.array.concat(/** @type {!Array} */ (object));
}
// Clone what we hope to be an array-like object to an array.
// We could check isArrayLike() first, but no check we perform would be as
// reliable as simply making the call.
return goog.array.clone(/** @type {Array} */ (object));
};
/**
* Adds or removes elements from an array. This is a generic version of Array
* splice. This means that it might work on other objects similar to arrays,
* such as the arguments object.
*
* @param {goog.array.ArrayLike} arr The array to modify.
* @param {number|undefined} index The index at which to start changing the
* array. If not defined, treated as 0.
* @param {number} howMany How many elements to remove (0 means no removal. A
* value below 0 is treated as zero and so is any other non number. Numbers
* are floored).
* @param {...*} var_args Optional, additional elements to insert into the
* array.
* @return {!Array} the removed elements.
*/
goog.array.splice = function(arr, index, howMany, var_args) {
return goog.array.ARRAY_PROTOTYPE_.splice.apply(
arr, goog.array.slice(arguments, 1));
};
/**
* Returns a new array from a segment of an array. This is a generic version of
* Array slice. This means that it might work on other objects similar to
* arrays, such as the arguments object.
*
* @param {goog.array.ArrayLike} arr The array from which to copy a segment.
* @param {number} start The index of the first element to copy.
* @param {number=} opt_end The index after the last element to copy.
* @return {!Array} A new array containing the specified segment of the original
* array.
*/
goog.array.slice = function(arr, start, opt_end) {
// passing 1 arg to slice is not the same as passing 2 where the second is
// null or undefined (in that case the second argument is treated as 0).
// we could use slice on the arguments object and then use apply instead of
// testing the length
if (arguments.length <= 2) {
return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
} else {
return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
}
};
/**
* Sorts the specified array into ascending order. If no opt_compareFn is
* specified, elements are compared using
* goog.array.defaultCompare, which compares the elements using
* the built in < and > operators. This will produce the expected behavior
* for homogeneous arrays of String(s) and Number(s), unlike the native sort,
* but will give unpredictable results for heterogenous lists of strings and
* numbers with different numbers of digits.
*
* This sort is not guaranteed to be stable.
*
* Runtime: Same as Array.prototype.sort
*
* @param {Array} arr The array to be sorted.
* @param {Function=} opt_compareFn Optional comparison function by which the
* array is to be ordered. Should take 2 arguments to compare, and return a
* negative number, zero, or a positive number depending on whether the
* first argument is less than, equal to, or greater than the second.
*/
goog.array.sort = function(arr, opt_compareFn) {
// TODO(user): Update type annotation since null is not accepted.
goog.array.ARRAY_PROTOTYPE_.sort.call(
arr, opt_compareFn || goog.array.defaultCompare);
};
/**
* Compares two arrays for equality. Two arrays are considered equal if they
* have the same length and their corresponding elements are equal according to
* the comparison function.
*
* @param {goog.array.ArrayLike} arr1 The first array to compare.
* @param {goog.array.ArrayLike} arr2 The second array to compare.
* @param {Function=} opt_equalsFn Optional comparison function.
* Should take 2 arguments to compare, and return true if the arguments
* are equal. Defaults to {@link goog.array.defaultCompareEquality} which
* compares the elements using the built-in '===' operator.
* @return {boolean} Whether the two arrays are equal.
*/
goog.array.equals = function(arr1, arr2, opt_equalsFn) {
if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
arr1.length != arr2.length) {
return _FALSE;
}
var l = arr1.length;
var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
for (var i = 0; i < l; i++) {
if (!equalsFn(arr1[i], arr2[i])) {
return _FALSE;
}
}
return _TRUE;
};
/**
* @deprecated Use {@link goog.array.equals}.
* @param {goog.array.ArrayLike} arr1 See {@link goog.array.equals}.
* @param {goog.array.ArrayLike} arr2 See {@link goog.array.equals}.
* @param {Function=} opt_equalsFn See {@link goog.array.equals}.
* @return {boolean} See {@link goog.array.equals}.
*/
goog.array.compare = function(arr1, arr2, opt_equalsFn) {
return goog.array.equals(arr1, arr2, opt_equalsFn);
};
/**
* Compares its two arguments for order, using the built in < and >
* operators.
* @param {*} a The first object to be compared.
* @param {*} b The second object to be compared.
* @return {number} A negative number, zero, or a positive number as the first
* argument is less than, equal to, or greater than the second.
*/
goog.array.defaultCompare = function(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
};
/**
* Compares its two arguments for equality, using the built in === operator.
* @param {*} a The first object to compare.
* @param {*} b The second object to compare.
* @return {boolean} True if the two arguments are equal, _FALSE otherwise.
*/
goog.array.defaultCompareEquality = function(a, b) {
return a === b;
};
/**
* Returns an array consisting of the given value repeated N times.
*
* @param {*} value The value to repeat.
* @param {number} n The repeat count.
* @return {!Array.<*>} An array with the repeated value.
*/
goog.array.repeat = function(value, n) {
var array = [];
for (var i = 0; i < n; i++) {
array[i] = value;
}
return array;
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for adding, removing and setting classes.
*
*/
goog.provide('goog.dom.classes');
//goog.require('goog.array');
/**
* Gets an array of class names on an element
* @param {Node} element DOM node to get class of.
* @return {Array} Class names on {@code element}.
*/
goog.dom.classes.get = function(element) {
var className = element.className;
// Some types of elements don't have a className in IE (e.g. iframes).
// Furthermore, in Firefox, className is not a string when the element is
// an SVG element.
return className && typeof className.split == 'function' ?
className.split(/\s+/) : [];
};
/**
* Adds a class or classes to an element. Does not add multiples of class names.
* @param {Node} element DOM node to add class to.
* @param {...string} var_args Class names to add.
* @return {boolean} Whether class was added (or all classes were added).
*/
goog.dom.classes.add = function(element, var_args) {
var classes = goog.dom.classes.get(element);
var args = goog.array.slice(arguments, 1);
var b = goog.dom.classes.add_(classes, args);
element.className = classes.join(' ');
return b;
};
/**
* Helper method for {@link goog.dom.classes.add} and
* {@link goog.dom.classes.addRemove}. Adds one or more classes to the supplied
* classes array.
* @param {Array.} classes All class names for the element, will be
* updated to have the classes supplied in {@code args} added.
* @param {Array.} args Class names to add.
* @return {boolean} Whether all classes in were added.
* @private
*/
goog.dom.classes.add_ = function(classes, args) {
var rv = 0;
for (var i = 0; i < args.length; i++) {
if (!goog.array.contains(classes, args[i])) {
classes.push(args[i]);
rv++;
}
}
return rv == args.length;
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for manipulating objects/maps/hashes.
*/
goog.provide('goog.object');
/**
* Calls a function for each element in an object/map/hash.
*
* @param {Object} obj The object over which to iterate.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the object)
* and the return value is irrelevant.
* @param {Object=} opt_obj This is used as the 'this' object within f.
*/
goog.object.forEach = function(obj, f, opt_obj) {
for (var key in obj) {
f.call(opt_obj, obj[key], key, obj);
}
};
/**
* Calls a function for each element in an object/map/hash. If that call returns
* true, adds the element to a new object.
*
* @param {Object} obj The object over which to iterate.
* @param {Function} f The function to call for every element. This
* function takes 3 arguments (the element, the index and the object)
* and should return a boolean. If the return value is true the
* element is added to the result object. If it is _FALSE the
* element is not included.
* @param {Object=} opt_obj This is used as the 'this' object within f.
* @return {!Object} a new object in which only elements that passed the test
* are present.
*/
goog.object.filter = function(obj, f, opt_obj) {
var res = {};
for (var key in obj) {
if (f.call(opt_obj, obj[key], key, obj)) {
res[key] = obj[key];
}
}
return res;
};
/**
* For every element in an object/map/hash calls a function and inserts the
* result into a new object.
*
* @param {Object} obj The object over which to iterate.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the object)
* and should return something. The result will be inserted
* into a new object.
* @param {Object=} opt_obj This is used as the 'this' object within f.
* @return {!Object} a new object with the results from f.
*/
goog.object.map = function(obj, f, opt_obj) {
var res = {};
for (var key in obj) {
res[key] = f.call(opt_obj, obj[key], key, obj);
}
return res;
};
/**
* Calls a function for each element in an object/map/hash. If
* all calls return true, returns true. If any call returns _FALSE, returns
* _FALSE at this point and does not continue to check the remaining elements.
*
* @param {Object} obj The object to check.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the object) and should
* return a boolean.
* @param {Object=} opt_obj This is used as the 'this' object within f.
* @return {boolean} _FALSE if any element fails the test.
*/
goog.object.every = function(obj, f, opt_obj) {
for (var key in obj) {
if (!f.call(opt_obj, obj[key], key, obj)) {
return _FALSE;
}
}
return _TRUE;
};
/**
* Returns the number of key-value pairs in the object map.
*
* @param {Object} obj The object for which to get the number of key-value
* pairs.
* @return {number} The number of key-value pairs in the object map.
*/
goog.object.getCount = function(obj) {
// JS1.5 has __count__ but it has been deprecated so it raises a warning...
// in other words do not use. Also __count__ only includes the fields on the
// actual object and not in the prototype chain.
var rv = 0;
for (var key in obj) {
rv++;
}
return rv;
};
/**
* Whether the object/hash/map contains the given object as a value.
* An alias for goog.object.containsValue(obj, val).
*
* @param {Object} obj The object in which to look for val.
* @param {*} val The object for which to check.
* @return {boolean} true if val is present.
*/
goog.object.contains = function(obj, val) {
return goog.object.containsValue(obj, val);
};
/**
* Returns the values of the object/map/hash.
*
* @param {Object} obj The object from which to get the values.
* @return {!Array} The values in the object/map/hash.
*/
goog.object.getValues = function(obj) {
var res = [];
var i = 0;
for (var key in obj) {
res[i++] = obj[key];
}
return res;
};
/**
* Returns the keys of the object/map/hash.
*
* @param {Object} obj The object from which to get the keys.
* @return {!Array.} Array of property keys.
*/
goog.object.getKeys = function(obj) {
var res = [];
var i = 0;
for (var key in obj) {
res[i++] = key;
}
return res;
};
/**
* Whether the object/map/hash contains the given value. This is O(n).
*
* @param {Object} obj The object in which to look for val.
* @param {*} val The value for which to check.
* @return {boolean} true If the map contains the value.
*/
goog.object.containsValue = function(obj, val) {
for (var key in obj) {
if (obj[key] == val) {
return _TRUE;
}
}
return _FALSE;
};
/**
* Whether the object/map/hash is empty.
*
* @param {Object} obj The object to test.
* @return {boolean} true if obj is empty.
*/
goog.object.isEmpty = function(obj) {
for (var key in obj) {
return _FALSE;
}
return _TRUE;
};
/**
* Removes all key value pairs from the object/map/hash.
*
* @param {Object} obj The object to clear.
*/
goog.object.clear = function(obj) {
for (var i in obj) {
delete obj[i];
}
};
/**
* Removes a key-value pair based on the key.
*
* @param {Object} obj The object from which to remove the key.
* @param {*} key The key to remove.
* @return {boolean} Whether an element was removed.
*/
goog.object.remove = function(obj, key) {
var rv;
if ((rv = key in obj)) {
delete obj[key];
}
return rv;
};
/**
* Adds a key-value pair to the object. Throws an exception if the key is
* already in use. Use set if you want to change an existing pair.
*
* @param {Object} obj The object to which to add the key-value pair.
* @param {string} key The key to add.
* @param {*} val The value to add.
*/
goog.object.add = function(obj, key, val) {
if (key in obj) {
throw Error('The object already contains the key "' + key + '"');
}
goog.object.set(obj, key, val);
};
/**
* Returns the value for the given key.
*
* @param {Object} obj The object from which to get the value.
* @param {string} key The key for which to get the value.
* @param {*=} opt_val The value to return if no item is found for the given
* key (default is undefined).
* @return {*} The value for the given key.
*/
goog.object.get = function(obj, key, opt_val) {
if (key in obj) {
return obj[key];
}
return opt_val;
};
/**
* Adds a key-value pair to the object/map/hash.
*
* @param {Object} obj The object to which to add the key-value pair.
* @param {string} key The key to add.
* @param {*} value The value to add.
*/
goog.object.set = function(obj, key, value) {
obj[key] = value;
};
/**
* Does a flat clone of the object.
*
* @param {Object} obj Object to clone.
* @return {!Object} Clone of the input object.
*/
goog.object.clone = function(obj) {
// We cannot use the prototype trick because a lot of methods depend on where
// the actual key is set.
var res = {};
for (var key in obj) {
res[key] = obj[key];
}
return res;
// We could also use goog.mixin but I wanted this to be independent from that.
};
/**
* The names of the fields that are defined on Object.prototype.
* @type {Array.}
* @private
*/
goog.object.PROTOTYPE_FIELDS_ = [
'constructor',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'toLocaleString',
'toString',
'valueOf'
];
/**
* Extends an object with another object.
* This operates 'in-place'; it does not create a new Object.
*
* Example:
* var o = {};
* goog.object.extend(o, {a: 0, b: 1});
* o; // {a: 0, b: 1}
* goog.object.extend(o, {c: 2});
* o; // {a: 0, b: 1, c: 2}
*
* @param {Object} target The object to modify.
* @param {...Object} var_args The objects from which values will be copied.
*/
goog.object.extend = function(target, var_args) {
var key, source;
for (var i = 1; i < arguments.length; i++) {
source = arguments[i];
for (key in source) {
target[key] = source[key];
}
// For IE the for-in-loop does not contain any properties that are not
// enumerable on the prototype object (for example isPrototypeOf from
// Object.prototype) and it will also not include 'replace' on objects that
// extend String and change 'replace' (not that it is common for anyone to
// extend anything except Object).
for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
key = goog.object.PROTOTYPE_FIELDS_[j];
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
};
/**
* Creates a new object built from the key-value pairs provided as arguments.
* @param {...*} var_args If only one argument is provided and it is an array
* then this is used as the arguments, otherwise even arguments are used as
* the property names and odd arguments are used as the property values.
* @return {!Object} The new object.
* @throws {Error} If there are uneven number of arguments or there is only one
* non array argument.
*/
goog.object.create = function(var_args) {
var argLength = arguments.length;
if (argLength == 1 && goog.isArray(arguments[0])) {
return goog.object.create.apply(_NULL, arguments[0]);
}
if (argLength % 2) {
throw Error('Uneven number of arguments');
}
var rv = {};
for (var i = 0; i < argLength; i += 2) {
rv[arguments[i]] = arguments[i + 1];
}
return rv;
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines the goog.dom.TagName enum. This enumerates
* all html tag names specified by the W3C HTML 4.01 Specification.
* Reference http://www.w3.org/TR/html401/index/elements.html.
*/
goog.provide('goog.dom.TagName');
/**
* Enum of all html tag names specified by the W3C HTML 4.01 Specification.
* Reference http://www.w3.org/TR/html401/index/elements.html
* @enum {string}
*/
goog.dom.TagName = {
A: 'A',
ABBR: 'ABBR',
ACRONYM: 'ACRONYM',
ADDRESS: 'ADDRESS',
APPLET: 'APPLET',
AREA: 'AREA',
B: 'B',
BASE: 'BASE',
BASEFONT: 'BASEFONT',
BDO: 'BDO',
BIG: 'BIG',
BLOCKQUOTE: 'BLOCKQUOTE',
BODY: 'BODY',
BR: 'BR',
BUTTON: 'BUTTON',
CANVAS: 'CANVAS',
CAPTION: 'CAPTION',
CENTER: 'CENTER',
CITE: 'CITE',
CODE: 'CODE',
COL: 'COL',
COLGROUP: 'COLGROUP',
DD: 'DD',
DEL: 'DEL',
DFN: 'DFN',
DIR: 'DIR',
DIV: 'DIV',
DL: 'DL',
DT: 'DT',
EM: 'EM',
FIELDSET: 'FIELDSET',
FONT: 'FONT',
FORM: 'FORM',
FRAME: 'FRAME',
FRAMESET: 'FRAMESET',
H1: 'H1',
H2: 'H2',
H3: 'H3',
H4: 'H4',
H5: 'H5',
H6: 'H6',
HEAD: 'HEAD',
HR: 'HR',
HTML: 'HTML',
I: 'I',
IFRAME: 'IFRAME',
IMG: 'IMG',
INPUT: 'INPUT',
INS: 'INS',
ISINDEX: 'ISINDEX',
KBD: 'KBD',
LABEL: 'LABEL',
LEGEND: 'LEGEND',
LI: 'LI',
LINK: 'LINK',
MAP: 'MAP',
MENU: 'MENU',
META: 'META',
NOFRAMES: 'NOFRAMES',
NOSCRIPT: 'NOSCRIPT',
OBJECT: 'OBJECT',
OL: 'OL',
OPTGROUP: 'OPTGROUP',
OPTION: 'OPTION',
P: 'P',
PARAM: 'PARAM',
PRE: 'PRE',
Q: 'Q',
S: 'S',
SAMP: 'SAMP',
SCRIPT: 'SCRIPT',
SELECT: 'SELECT',
SMALL: 'SMALL',
SPAN: 'SPAN',
STRIKE: 'STRIKE',
STRONG: 'STRONG',
STYLE: 'STYLE',
SUB: 'SUB',
SUP: 'SUP',
TABLE: 'TABLE',
TBODY: 'TBODY',
TD: 'TD',
TEXTAREA: 'TEXTAREA',
TFOOT: 'TFOOT',
TH: 'TH',
THEAD: 'THEAD',
TITLE: 'TITLE',
TR: 'TR',
TT: 'TT',
U: 'U',
UL: 'UL',
VAR: 'VAR'
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional sizes.
*/
goog.provide('goog.math.Size');
/**
* Class for representing sizes consisting of a width and height. Undefined
* width and height support is deprecated and results in compiler warning.
* @param {number} width Width.
* @param {number} height Height.
* @constructor
*/
goog.math.Size = function(width, height) {
/**
* Width
* @type {number}
*/
this.width = width;
/**
* Height
* @type {number}
*/
this.height = height;
};
/**
* Compares sizes for equality.
* @param {goog.math.Size} a A Size.
* @param {goog.math.Size} b A Size.
* @return {boolean} True iff the sizes have equal widths and equal
* heights, or if both are null.
*/
goog.math.Size.equals = function(a, b) {
if (a == b) {
return _TRUE;
}
if (!a || !b) {
return _FALSE;
}
return a.width == b.width && a.height == b.height;
};
/**
* @return {!goog.math.Size} A new copy of the Size.
*/
goog.math.Size.prototype.clone = function() {
return new goog.math.Size(this.width, this.height);
};
/**
* @return {number} The area of the size (width * height).
*/
goog.math.Size.prototype.area = function() {
return this.width * this.height;
};
/**
* @return {boolean} True if the size has zero area, _FALSE if both dimensions
* are non-zero numbers.
*/
goog.math.Size.prototype.isEmpty = function() {
return !this.area();
};
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Browser capability checks for the dom package.
*
*/
goog.provide('goog.dom.BrowserFeature');
//goog.require('goog.userAgent');
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.dom.BrowserFeature = {
/**
* Whether attributes 'name' and 'type' can be added to an element after it's
* created. _FALSE in Internet Explorer prior to version 9.
*/
CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE ||
goog.userAgent.isVersion('9'),
/**
* Whether we can use element.children to access an element's Element
* children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
* nodes in the collection.)
*/
CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
goog.userAgent.IE && goog.userAgent.isVersion('9') ||
goog.userAgent.GECKO && goog.userAgent.isVersion('1.9.1'),
/**
* Opera, Safari 3, and Internet Explorer 9 all support innerText but they
* include text nodes in script and style tags.
*/
CAN_USE_INNER_TEXT: goog.userAgent.IE && !goog.userAgent.isVersion('9'),
/**
* Whether NoScope elements need a scoped element written before them in
* innerHTML.
* MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
*/
INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional positions.
*/
goog.provide('goog.math.Coordinate');
/**
* Class for representing coordinates and positions.
* @param {number=} opt_x Left, defaults to 0.
* @param {number=} opt_y Top, defaults to 0.
* @constructor
*/
goog.math.Coordinate = function(opt_x, opt_y) {
/**
* X-value
* @type {number}
*/
this.x = goog.isDef(opt_x) ? opt_x : 0;
/**
* Y-value
* @type {number}
*/
this.y = goog.isDef(opt_y) ? opt_y : 0;
};
/**
* Returns a new copy of the coordinate.
* @return {!goog.math.Coordinate} A clone of this coordinate.
*/
goog.math.Coordinate.prototype.clone = function() {
return new goog.math.Coordinate(this.x, this.y);
};
/**
* Compares coordinates for equality.
* @param {goog.math.Coordinate} a A Coordinate.
* @param {goog.math.Coordinate} b A Coordinate.
* @return {boolean} True iff the coordinates are equal, or if both are null.
*/
goog.math.Coordinate.equals = function(a, b) {
if (a == b) {
return _TRUE;
}
if (!a || !b) {
return _FALSE;
}
return a.x == b.x && a.y == b.y;
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for manipulating the browser's Document Object Model
* Inspiration taken *heavily* from mochikit (http://mochikit.com/).
*
* You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
* to a different document object. This is useful if you are working with
* frames or multiple windows.
*
*/
// TODO(user): Rename/refactor getTextContent and getRawTextContent. The problem
// is that getTextContent should mimic the DOM3 textContent. We should add a
// getInnerText (or getText) which tries to return the visible text, innerText.
goog.provide('goog.dom');
goog.provide('goog.dom.DomHelper');
goog.provide('goog.dom.NodeType');
//goog.require('goog.array');
//goog.require('goog.dom.BrowserFeature');
//goog.require('goog.dom.TagName');
//goog.require('goog.dom.classes');
//goog.require('goog.math.Coordinate');
//goog.require('goog.math.Size');
//goog.require('goog.object');
//goog.require('goog.string');
//goog.require('goog.userAgent');
/**
* @define {boolean} Whether we know at compile time that the browser is in
* quirks mode.
*/
goog.dom.ASSUME_QUIRKS_MODE = _FALSE;
/**
* @define {boolean} Whether we know at compile time that the browser is in
* standards compliance mode.
*/
goog.dom.ASSUME_STANDARDS_MODE = _FALSE;
/**
* Whether we know the compatibility mode at compile time.
* @type {boolean}
* @private
*/
goog.dom.COMPAT_MODE_KNOWN_ =
goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
/**
* Enumeration for DOM node types (for reference)
* @enum {number}
*/
goog.dom.NodeType = {
ELEMENT: 1,
ATTRIBUTE: 2,
TEXT: 3,
CDATA_SECTION: 4,
ENTITY_REFERENCE: 5,
ENTITY: 6,
PROCESSING_INSTRUCTION: 7,
COMMENT: 8,
DOCUMENT: 9,
DOCUMENT_TYPE: 10,
DOCUMENT_FRAGMENT: 11,
NOTATION: 12
};
/**
* Gets the DomHelper object for the document where the element resides.
* @param {Node|Window=} opt_element If present, gets the DomHelper for this
* element.
* @return {!goog.dom.DomHelper} The DomHelper.
*/
goog.dom.getDomHelper = function(opt_element) {
return opt_element ?
new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
(goog.dom.defaultDomHelper_ ||
(goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
};
/**
* Cached default DOM helper.
* @type {goog.dom.DomHelper}
* @private
*/
goog.dom.defaultDomHelper_;
/**
* Gets the document object being used by the dom library.
* @return {!Document} Document object.
*/
goog.dom.getDocument = function() {
return document;
};
/**
* Alias for getElementById. If a DOM node is passed in then we just return
* that.
* @param {string|Element} element Element ID or a DOM node.
* @return {Element} The element with the given ID, or the node passed in.
*/
goog.dom.getElement = function(element) {
return goog.isString(element) ?
_DOC.getElementById(element) : element;
};
/**
* Alias for getElement.
* @param {string|Element} element Element ID or a DOM node.
* @return {Element} The element with the given ID, or the node passed in.
* @deprecated Use {@link goog.dom.getElement} instead.
*/
goog.dom.$ = goog.dom.getElement;
/**
* Looks up elements by both tag and class name, using browser native functions
* ({@code querySelectorAll}, {@code getElementsByTagName} or
* {@code getElementsByClassName}) where possible. This function
* is a useful, if limited, way of collecting a list of DOM elements
* with certain characteristics. {@code goog.dom.query} offers a
* more powerful and general solution which allows matching on CSS3
* selector expressions, but at increased cost in code size. If all you
* need is particular tags belonging to a single class, this function
* is fast and sleek.
*
* @see {goog.dom.query}
*
* @param {?string=} opt_tag Element tag name.
* @param {?string=} opt_class Optional class name.
* @param {Document|Element=} opt_el Optional element to look in.
* @return { {length: number} } Array-like list of elements (only a length
* property and numerical indices are guaranteed to exist).
*/
goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
opt_el);
};
/**
* Returns an array of all the elements with the provided className.
* @see {goog.dom.query}
* @param {!string} className the name of the class to look for.
* @param {Document|Element=} opt_el Optional element to look in.
* @return { {length: number} } The items found with the class name provided.
*/
goog.dom.getElementsByClass = function(className, opt_el) {
var parent = opt_el || document;
if (goog.dom.canUseQuerySelector_(parent)) {
return parent.querySelectorAll('.' + className);
} else if (parent.getElementsByClassName) {
return parent.getElementsByClassName(className);
}
return goog.dom.getElementsByTagNameAndClass_(
document, '*', className, opt_el);
};
/**
* Returns the first element with the provided className.
* @see {goog.dom.query}
* @param {!string} className the name of the class to look for.
* @param {Element|Document=} opt_el Optional element to look in.
* @return {Element} The first item with the class name provided.
*/
goog.dom.getElementByClass = function(className, opt_el) {
var parent = opt_el || document;
var retVal = _NULL;
if (goog.dom.canUseQuerySelector_(parent)) {
retVal = parent.querySelector('.' + className);
} else {
retVal = goog.dom.getElementsByClass(className, opt_el)[0];
}
return retVal || _NULL;
};
/**
* Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
* fast W3C Selectors API. However, the version of WebKit that shipped with
* Safari 3.1 and Chrome has a bug where it will not correctly match mixed-
* case class name selectors in quirks mode.
* @param {!Element|Document} parent The parent document object.
* @return {boolean} whether or not we can use parent.querySelector* APIs.
* @private
*/
goog.dom.canUseQuerySelector_ = function(parent) {
return parent.querySelectorAll &&
parent.querySelector &&
(!goog.userAgent.WEBKIT || goog.dom.isCss1CompatMode_(document) ||
goog.userAgent.isVersion('528'));
};
/**
* Helper for {@code getElementsByTagNameAndClass}.
* @param {!Document} doc The document to get the elements in.
* @param {?string=} opt_tag Element tag name.
* @param {?string=} opt_class Optional class name.
* @param {Document|Element=} opt_el Optional element to look in.
* @return { {length: number} } Array-like list of elements (only a length
* property and numerical indices are guaranteed to exist).
* @private
*/
goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class,
opt_el) {
var parent = opt_el || doc;
var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : '';
if (goog.dom.canUseQuerySelector_(parent) &&
(tagName || opt_class)) {
var query = tagName + (opt_class ? '.' + opt_class : '');
return parent.querySelectorAll(query);
}
// Use the native getElementsByClassName if available, under the assumption
// that even when the tag name is specified, there will be fewer elements to
// filter through when going by class than by tag name
if (opt_class && parent.getElementsByClassName) {
var els = parent.getElementsByClassName(opt_class);
if (tagName) {
var arrayLike = {};
var len = 0;
// Filter for specific tags if requested.
for (var i = 0, el; el = els[i]; i++) {
if (tagName == el.nodeName) {
arrayLike[len++] = el;
}
}
arrayLike.length = len;
return arrayLike;
} else {
return els;
}
}
var els = parent.getElementsByTagName(tagName || '*');
if (opt_class) {
var arrayLike = {};
var len = 0;
for (var i = 0, el; el = els[i]; i++) {
var className = el.className;
// Check if className has a split function since SVG className does not.
if (typeof className.split == 'function' &&
goog.array.contains(className.split(/\s+/), opt_class)) {
arrayLike[len++] = el;
}
}
arrayLike.length = len;
return arrayLike;
} else {
return els;
}
};
/**
* Alias for {@code getElementsByTagNameAndClass}.
* @param {?string=} opt_tag Element tag name.
* @param {?string=} opt_class Optional class name.
* @param {Element=} opt_el Optional element to look in.
* @return { {length: number} } Array-like list of elements (only a length
* property and numerical indices are guaranteed to exist).
* @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
*/
goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
/**
* Sets multiple properties on a node.
* @param {Element} element DOM node to set properties on.
* @param {Object} properties Hash of property:value pairs.
*/
goog.dom.setProperties = function(element, properties) {
goog.object.forEach(properties, function(val, key) {
if (key == 'style') {
element.style.cssText = val;
} else if (key == 'class') {
element.className = val;
} else if (key == 'for') {
element.htmlFor = val;
} else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
} else {
element[key] = val;
}
});
};
/**
* Map of attributes that should be set using
* element.setAttribute(key, val) instead of element[key] = val. Used
* by goog.dom.setProperties.
*
* @type {Object}
* @private
*/
goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
'cellpadding': 'cellPadding',
'cellspacing': 'cellSpacing',
'colspan': 'colSpan',
'rowspan': 'rowSpan',
'valign': 'vAlign',
'height': 'height',
'width': 'width',
'usemap': 'useMap',
'frameborder': 'frameBorder',
'maxlength': 'maxLength',
'type': 'type'
};
/**
* Gets the dimensions of the viewport.
*
* Gecko Standards mode:
* docEl.clientWidth Width of viewport excluding scrollbar.
* win.innerWidth Width of viewport including scrollbar.
* body.clientWidth Width of body element.
*
* docEl.clientHeight Height of viewport excluding scrollbar.
* win.innerHeight Height of viewport including scrollbar.
* body.clientHeight Height of document.
*
* Gecko Backwards compatible mode:
* docEl.clientWidth Width of viewport excluding scrollbar.
* win.innerWidth Width of viewport including scrollbar.
* body.clientWidth Width of viewport excluding scrollbar.
*
* docEl.clientHeight Height of document.
* win.innerHeight Height of viewport including scrollbar.
* body.clientHeight Height of viewport excluding scrollbar.
*
* IE6/7 Standards mode:
* docEl.clientWidth Width of viewport excluding scrollbar.
* win.innerWidth Undefined.
* body.clientWidth Width of body element.
*
* docEl.clientHeight Height of viewport excluding scrollbar.
* win.innerHeight Undefined.
* body.clientHeight Height of document element.
*
* IE5 + IE6/7 Backwards compatible mode:
* docEl.clientWidth 0.
* win.innerWidth Undefined.
* body.clientWidth Width of viewport excluding scrollbar.
*
* docEl.clientHeight 0.
* win.innerHeight Undefined.
* body.clientHeight Height of viewport excluding scrollbar.
*
* Opera 9 Standards and backwards compatible mode:
* docEl.clientWidth Width of viewport excluding scrollbar.
* win.innerWidth Width of viewport including scrollbar.
* body.clientWidth Width of viewport excluding scrollbar.
*
* docEl.clientHeight Height of document.
* win.innerHeight Height of viewport including scrollbar.
* body.clientHeight Height of viewport excluding scrollbar.
*
* WebKit:
* Safari 2
* docEl.clientHeight Same as scrollHeight.
* docEl.clientWidth Same as innerWidth.
* win.innerWidth Width of viewport excluding scrollbar.
* win.innerHeight Height of the viewport including scrollbar.
* frame.innerHeight Height of the viewport exluding scrollbar.
*
* Safari 3 (tested in 522)
*
* docEl.clientWidth Width of viewport excluding scrollbar.
* docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
* body.clientHeight Height of viewport excluding scrollbar in quirks mode.
*
* @param {Window=} opt_window Optional window element to test.
* @return {!goog.math.Size} Object with values 'width' and 'height'.
*/
goog.dom.getViewportSize = function(opt_window) {
// TODO(user): This should not take an argument
return goog.dom.getViewportSize_(opt_window || window);
};
/**
* Helper for {@code getViewportSize}.
* @param {Window} win The window to get the view port size for.
* @return {!goog.math.Size} Object with values 'width' and 'height'.
* @private
*/
goog.dom.getViewportSize_ = function(win) {
var doc = win.document;
if (goog.userAgent.WEBKIT && !goog.userAgent.isVersion('500') &&
!goog.userAgent.MOBILE) {
// TODO(user): Sometimes we get something that isn't a valid window
// object. In this case we just revert to the current window. We need to
// figure out when this happens and find a real fix for it.
// See the comments on goog.dom.getWindow.
if (typeof win.innerHeight == _UNDEFINED+'') {
win = window;
}
var innerHeight = win.innerHeight;
var scrollHeight = win.document.documentElement.scrollHeight;
if (win == win.top) {
if (scrollHeight < innerHeight) {
innerHeight -= 15; // Scrollbars are 15px wide on Mac
}
}
return new goog.math.Size(win.innerWidth, innerHeight);
}
var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
return new goog.math.Size(el.clientWidth, el.clientHeight);
};
/**
* Gets the window object associated with the given document.
*
* @param {Document=} opt_doc Document object to get window for.
* @return {Window} The window associated with the given document.
*/
goog.dom.getWindow = function(opt_doc) {
// TODO(user): This should not take an argument.
return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
};
/**
* Helper for {@code getWindow}.
*
* @param {!Document} doc Document object to get window for.
* @return {!Window} The window associated with the given document.
* @private
*/
goog.dom.getWindow_ = function(doc) {
return doc.parentWindow || doc.defaultView;
};
/**
* Returns a dom node with a set of attributes. This function accepts varargs
* for subsequent nodes to be added. Subsequent nodes will be added to the
* first node as childNodes.
*
* So:
* createDom('div', null, createDom('p'), createDom('p'));
* would return a div with two child paragraphs
*
* @param {string} tagName Tag to create.
* @param {Object|Array.|string=} opt_attributes If object, then a map
* of name-value pairs for attributes. If a string, then this is the
* className of the new element. If an array, the elements will be joined
* together as the className of the new element.
* @param {...Object|string|Array|NodeList} var_args Further DOM nodes or
* strings for text nodes. If one of the var_args is an array or NodeList,i
* its elements will be added as childNodes instead.
* @return {!Element} Reference to a DOM node.
*/
goog.dom.createDom = function(tagName, opt_attributes, var_args) {
return goog.dom.createDom_(document, arguments);
};
/**
* Helper for {@code createDom}.
* @param {!Document} doc The document to create the DOM in.
* @param {!Arguments} args Argument object passed from the callers. See
* {@code goog.dom.createDom} for details.
* @return {!Element} Reference to a DOM node.
* @private
*/
goog.dom.createDom_ = function(doc, args) {
var tagName = args[0];
var attributes = args[1];
// Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/
// dhtml/reference/properties/name_2.asp
// Also does not allow setting of 'type' attribute on 'input' or 'button'.
if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
(attributes.name || attributes.type)) {
var tagNameArr = ['<', tagName];
if (attributes.name) {
tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name),
'"');
}
if (attributes.type) {
tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type),
'"');
// Clone attributes map to remove 'type' without mutating the input.
var clone = {};
goog.object.extend(clone, attributes);
attributes = clone;
delete attributes.type;
}
tagNameArr.push('>');
tagName = tagNameArr.join('');
}
var element = doc.createElement(tagName);
if (attributes) {
if (goog.isString(attributes)) {
element.className = attributes;
} else if (goog.isArray(attributes)) {
goog.dom.classes.add.apply(_NULL, [element].concat(attributes));
} else {
goog.dom.setProperties(element, attributes);
}
}
if (args.length > 2) {
goog.dom.append_(doc, element, args, 2);
}
return element;
};
/**
* Appends a node with text or other nodes.
* @param {!Document} doc The document to create new nodes in.
* @param {!Node} parent The node to append nodes to.
* @param {!Arguments} args The values to add. See {@code goog.dom.append}.
* @param {number} startIndex The index of the array to start from.
* @private
*/
goog.dom.append_ = function(doc, parent, args, startIndex) {
function childHandler(child) {
// TODO(user): More coercion, ala MochiKit?
if (child) {
parent.appendChild(goog.isString(child) ?
doc.createTextNode(child) : child);
}
}
for (var i = startIndex; i < args.length; i++) {
var arg = args[i];
// TODO(user): Fix isArrayLike to return _FALSE for a text node.
if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
// If the argument is a node list, not a real array, use a clone,
// because forEach can't be used to mutate a NodeList.
goog.array.forEach(goog.dom.isNodeList(arg) ?
goog.array.clone(arg) : arg,
childHandler);
} else {
childHandler(arg);
}
}
};
/**
* Alias for {@code createDom}.
* @param {string} tagName Tag to create.
* @param {string|Object=} opt_attributes If object, then a map of name-value
* pairs for attributes. If a string, then this is the className of the new
* element.
* @param {...Object|string|Array|NodeList} var_args Further DOM nodes or
* strings for text nodes. If one of the var_args is an array, its
* children will be added as childNodes instead.
* @return {!Element} Reference to a DOM node.
* @deprecated Use {@link goog.dom.createDom} instead.
*/
goog.dom.$dom = goog.dom.createDom;
/**
* Creates a new element.
* @param {string} name Tag name.
* @return {!Element} The new element.
*/
goog.dom.createElement = function(name) {
return _DOC.createElement(name);
};
/**
* Creates a new text node.
* @param {string} content Content.
* @return {!Text} The new text node.
*/
goog.dom.createTextNode = function(content) {
return _DOC.createTextNode(content);
};
/**
* Returns true if the browser is in "CSS1-compatible" (standards-compliant)
* mode, _FALSE otherwise.
* @param {Document} doc The document to check.
* @return {boolean} True if in CSS1-compatible mode.
* @private
*/
goog.dom.isCss1CompatMode_ = function(doc) {
if (goog.dom.COMPAT_MODE_KNOWN_) {
return goog.dom.ASSUME_STANDARDS_MODE;
}
return doc.compatMode == 'CSS1Compat';
};
/**
* Determines if the given node can contain children, intended to be used for
* HTML generation.
*
* IE natively supports node.canHaveChildren but has inconsistent behavior.
* Prior to IE8 the base tag allows children and in IE9 all nodes return true
* for canHaveChildren.
*
* In practice all non-IE browsers allow you to add children to any node, but
* the behavior is inconsistent:
*
*
* var a = document.createElement('br');
* a.appendChild(document.createTextNode('foo'));
* a.appendChild(document.createTextNode('bar'));
* console.log(a.childNodes.length); // 2
* console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar"
*
*
* TODO(user): Rename shouldAllowChildren() ?
*
* @param {Node} node The node to check.
* @return {boolean} Whether the node can contain children.
*/
goog.dom.canHaveChildren_OLD = function(node) {
if (node.nodeType != goog.dom.NodeType.ELEMENT) {
return _FALSE;
}
switch (node.tagName) {
case goog.dom.TagName.APPLET:
case goog.dom.TagName.AREA:
case goog.dom.TagName.BASE:
case goog.dom.TagName.BR:
case goog.dom.TagName.COL:
case goog.dom.TagName.FRAME:
case goog.dom.TagName.HR:
case goog.dom.TagName.IMG:
case goog.dom.TagName.INPUT:
case goog.dom.TagName.IFRAME:
case goog.dom.TagName.ISINDEX:
case goog.dom.TagName.LINK:
case goog.dom.TagName.NOFRAMES:
case goog.dom.TagName.NOSCRIPT:
case goog.dom.TagName.META:
case goog.dom.TagName.OBJECT:
case goog.dom.TagName.PARAM:
case goog.dom.TagName.SCRIPT:
case goog.dom.TagName.STYLE:
return _FALSE;
}
return _TRUE;
};
goog.dom.TAGS_CANT_HAVE_CHILDREN = {
APPLET: _TRUE,
AREA: _TRUE,
BASE: _TRUE,
BR: _TRUE,
COL: _TRUE,
FRAME: _TRUE,
HR: _TRUE,
IMG: _TRUE,
INPUT: _TRUE,
IFRAME: _TRUE,
ISINDEX: _TRUE,
LINK: _TRUE,
NOFRAMES: _TRUE,
NOSCRIPT: _TRUE,
META: _TRUE,
OBJECT: _TRUE,
PARAM: _TRUE,
SCRIPT: _TRUE,
STYLE: _TRUE
};
goog.dom.canHaveChildren = function(node) {
return node.nodeType == goog.dom.NodeType.ELEMENT &&
!goog.dom.TAGS_CANT_HAVE_CHILDREN[node.tagName];
};
/**
* Appends a child to a node.
* @param {Node} parent Parent.
* @param {Node} child Child.
*/
goog.dom.appendChild = function(parent, child) {
parent.appendChild(child);
};
/**
* Appends a node with text or other nodes.
* @param {!Node} parent The node to append nodes to.
* @param {...goog.dom.Appendable} var_args The things to append to the node.
* If this is a Node it is appended as is.
* If this is a string then a text node is appended.
* If this is an array like object then fields 0 to length - 1 are appended.
*/
goog.dom.append = function(parent, var_args) {
goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
};
/**
* Removes all the child nodes on a DOM node.
* @param {Node} node Node to remove children from.
*/
goog.dom.removeChildren = function(node) {
// Note: Iterations over live collections can be slow, this is the fastest
// we could find. The double parenthesis are used to prevent JsCompiler and
// strict warnings.
var child;
while ((child = node.firstChild)) {
node.removeChild(child);
}
};
/**
* Inserts a new node before an existing reference node (i.e. as the previous
* sibling). If the reference node has no parent, then does nothing.
* @param {Node} newNode Node to insert.
* @param {Node} refNode Reference node to insert before.
*/
goog.dom.insertSiblingBefore = function(newNode, refNode) {
if (refNode.parentNode) {
refNode.parentNode.insertBefore(newNode, refNode);
}
};
/**
* Inserts a new node after an existing reference node (i.e. as the next
* sibling). If the reference node has no parent, then does nothing.
* @param {Node} newNode Node to insert.
* @param {Node} refNode Reference node to insert after.
*/
goog.dom.insertSiblingAfter = function(newNode, refNode) {
if (refNode.parentNode) {
refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
}
};
/**
* Removes a node from its parent.
* @param {Node} node The node to remove.
* @return {Node} The node removed if removed; else, null.
*/
goog.dom.removeNode = function(node) {
return node && node.parentNode ? node.parentNode.removeChild(node) : _NULL;
};
/**
* Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
* parent.
* @param {Node} newNode Node to insert.
* @param {Node} oldNode Node to replace.
*/
goog.dom.replaceNode = function(newNode, oldNode) {
var parent = oldNode.parentNode;
if (parent) {
parent.replaceChild(newNode, oldNode);
}
};
/**
* Flattens an element. That is, removes it and replace it with its children.
* Does nothing if the element is not in the document.
* @param {Element} element The element to flatten.
* @return {Element|undefined} The original element, detached from the document
* tree, sans children; or undefined, if the element was not in the
* document to begin with.
*/
goog.dom.flattenElement = function(element) {
var child, parent = element.parentNode;
if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
// Use IE DOM method (supported by Opera too) if available
if (element.removeNode) {
return /** @type {Element} */ (element.removeNode(_FALSE));
} else {
// Move all children of the original node up one level.
while ((child = element.firstChild)) {
parent.insertBefore(child, element);
}
// Detach the original element.
return /** @type {Element} */ (goog.dom.removeNode(element));
}
}
};
/**
* Whether the object looks like a DOM node.
* @param {*} obj The object being tested for node likeness.
* @return {boolean} Whether the object looks like a DOM node.
*/
goog.dom.isNodeLike = function(obj) {
return goog.isObject(obj) && obj.nodeType > 0;
};
/**
* Whether a node contains another node.
* @param {Node} parent The node that should contain the other node.
* @param {Node} descendant The node to test presence of.
* @return {boolean} Whether the parent node contains the descendent node.
*/
goog.dom.contains = function(parent, descendant) {
// We use browser specific methods for this if available since it is faster
// that way.
// IE DOM
if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
return parent == descendant || parent.contains(descendant);
}
// W3C DOM Level 3
if (typeof parent.compareDocumentPosition != _UNDEFINED+'') {
return parent == descendant ||
Boolean(parent.compareDocumentPosition(descendant) & 16);
}
// W3C DOM Level 1
while (descendant && parent != descendant) {
descendant = descendant.parentNode;
}
return descendant == parent;
};
/**
* Compares the document order of two nodes, returning 0 if they are the same
* node, a negative number if node1 is before node2, and a positive number if
* node2 is before node1. Note that we compare the order the tags appear in the
* document so in the tree text the B node is considered to be
* before the I node.
*
* @param {Node} node1 The first node to compare.
* @param {Node} node2 The second node to compare.
* @return {number} 0 if the nodes are the same node, a negative number if node1
* is before node2, and a positive number if node2 is before node1.
*/
goog.dom.compareNodeOrder = function(node1, node2) {
// Fall out quickly for equality.
if (node1 == node2) {
return 0;
}
// Use compareDocumentPosition where available
if (node1.compareDocumentPosition) {
// 4 is the bitmask for FOLLOWS.
return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
}
// Process in IE using sourceIndex - we check to see if the first node has
// a source index or if its parent has one.
if ('sourceIndex' in node1 ||
(node1.parentNode && 'sourceIndex' in node1.parentNode)) {
var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
if (isElement1 && isElement2) {
return node1.sourceIndex - node2.sourceIndex;
} else {
var parent1 = node1.parentNode;
var parent2 = node2.parentNode;
if (parent1 == parent2) {
return goog.dom.compareSiblingOrder_(node1, node2);
}
if (!isElement1 && goog.dom.contains(parent1, node2)) {
return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
}
if (!isElement2 && goog.dom.contains(parent2, node1)) {
return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
}
return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
(isElement2 ? node2.sourceIndex : parent2.sourceIndex);
}
}
// For Safari, we compare ranges.
var doc = goog.dom.getOwnerDocument(node1);
var range1, range2;
range1 = doc.createRange();
range1.selectNode(node1);
range1.collapse(_TRUE);
range2 = doc.createRange();
range2.selectNode(node2);
range2.collapse(_TRUE);
return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
range2);
};
/**
* Utility function to compare the position of two nodes, when
* {@code textNode}'s parent is an ancestor of {@code node}. If this entry
* condition is not met, this function will attempt to reference a null object.
* @param {Node} textNode The textNode to compare.
* @param {Node} node The node to compare.
* @return {number} -1 if node is before textNode, +1 otherwise.
* @private
*/
goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
var parent = textNode.parentNode;
if (parent == node) {
// If textNode is a child of node, then node comes first.
return -1;
}
var sibling = node;
while (sibling.parentNode != parent) {
sibling = sibling.parentNode;
}
return goog.dom.compareSiblingOrder_(sibling, textNode);
};
/**
* Utility function to compare the position of two nodes known to be non-equal
* siblings.
* @param {Node} node1 The first node to compare.
* @param {Node} node2 The second node to compare.
* @return {number} -1 if node1 is before node2, +1 otherwise.
* @private
*/
goog.dom.compareSiblingOrder_ = function(node1, node2) {
var s = node2;
while ((s = s.previousSibling)) {
if (s == node1) {
// We just found node1 before node2.
return -1;
}
}
// Since we didn't find it, node1 must be after node2.
return 1;
};
/**
* Find the deepest common ancestor of the given nodes.
* @param {...Node} var_args The nodes to find a common ancestor of.
* @return {Node} The common ancestor of the nodes, or null if there is none.
* null will only be returned if two or more of the nodes are from different
* documents.
*/
goog.dom.findCommonAncestor = function(var_args) {
var i, count = arguments.length;
if (!count) {
return _NULL;
} else if (count == 1) {
return arguments[0];
}
var paths = [];
var minLength = Infinity;
for (i = 0; i < count; i++) {
// Compute the list of ancestors.
var ancestors = [];
var node = arguments[i];
while (node) {
ancestors.unshift(node);
node = node.parentNode;
}
// Save the list for comparison.
paths.push(ancestors);
minLength = Math.min(minLength, ancestors.length);
}
var output = _NULL;
for (i = 0; i < minLength; i++) {
var first = paths[0][i];
for (var j = 1; j < count; j++) {
if (first != paths[j][i]) {
return output;
}
}
output = first;
}
return output;
};
/**
* Returns the owner document for a node.
* @param {Node|Window} node The node to get the document for.
* @return {!Document} The document owning the node.
*/
goog.dom.getOwnerDocument = function(node) {
// TODO(user): Remove IE5 code.
// IE5 uses document instead of ownerDocument
return /** @type {!Document} */ (
node.nodeType == goog.dom.NodeType.DOCUMENT ? node :
node.ownerDocument || node.document);
};
/**
* Gets the outerHTML of a node, which islike innerHTML, except that it
* actually contains the HTML of the node itself.
* @param {Element} element The element to get the HTML of.
* @return {string} The outerHTML of the given element.
*/
goog.dom.getOuterHtml = function(element) {
// IE, Opera and WebKit all have outerHTML.
if ('outerHTML' in element) {
return element.outerHTML;
} else {
var doc = goog.dom.getOwnerDocument(element);
var div = doc.createElement('div');
div.appendChild(element.cloneNode(_TRUE));
return div.innerHTML;
}
};
/**
* Map of tags whose content to ignore when calculating text length.
* @type {Object}
* @private
*/
goog.dom.TAGS_TO_IGNORE_ = {
'SCRIPT': 1,
'STYLE': 1,
'HEAD': 1,
'IFRAME': 1,
'OBJECT': 1
};
/**
* Map of tags which have predefined values with regard to whitespace.
* @type {Object}
* @private
*/
goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
/**
* Returns the text content of the current node, without markup and invisible
* symbols. New lines are stripped and whitespace is collapsed,
* such that each character would be visible.
*
* In browsers that support it, innerText is used. Other browsers attempt to
* simulate it via node traversal. Line breaks are canonicalized in IE.
*
* @param {Node} node The node from which we are getting content.
* @return {string} The text content.
*/
goog.dom.getTextContent = function(node) {
var textContent;
// Note(user): IE9, Opera, and Safara 3 support innerText but they include
// text nodes in script tags. So we revert to use a user agent test here.
if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) {
textContent = goog.string.canonicalizeNewlines(node.innerText);
// Unfortunately .innerText() returns text with symbols
// We need to filter it out and then remove duplicate whitespaces
} else {
var buf = [];
goog.dom.getTextContent_(node, buf, _TRUE);
textContent = buf.join('');
}
// Strip entities. goog.format.insertWordBreaks inserts them in Opera.
textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
// Strip entities. goog.format.insertWordBreaks inserts them in IE8.
textContent = textContent.replace(/\u200B/g, '');
// Skip this replacement on IE, which automatically turns into ' '
// and / +/ into ' ' when reading innerText.
if (!goog.userAgent.IE) {
textContent = textContent.replace(/ +/g, ' ');
}
if (textContent != ' ') {
textContent = textContent.replace(/^\s*/, '');
}
return textContent;
};
/**
* Returns the text content of the current node, without markup.
*
* Unlike {@code getTextContent} this method does not collapse whitespaces
* or normalize lines breaks.
*
* @param {Node} node The node from which we are getting content.
* @return {string} The raw text content.
*/
goog.dom.getRawTextContent = function(node) {
var buf = [];
goog.dom.getTextContent_(node, buf, _FALSE);
return buf.join('');
};
/**
* Recursive support function for text content retrieval.
*
* @param {Node} node The node from which we are getting content.
* @param {Array} buf string buffer.
* @param {boolean} normalizeWhitespace Whether to normalize whitespace.
* @private
*/
goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
// ignore certain tags
} else if (node.nodeType == goog.dom.NodeType.TEXT) {
if (normalizeWhitespace) {
buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
} else {
buf.push(node.nodeValue);
}
} else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
} else {
var child = node.firstChild;
while (child) {
goog.dom.getTextContent_(child, buf, normalizeWhitespace);
child = child.nextSibling;
}
}
};
/**
* Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
* the object must have a numeric length property and an item function (which
* has type 'string' on IE for some reason).
* @param {Object} val Object to test.
* @return {boolean} Whether the object is a NodeList.
*/
goog.dom.isNodeList = function(val) {
// TODO(user): Now the isNodeList is part of goog.dom we can use
// goog.userAgent to make this simpler.
// A NodeList must have a length property of type 'number' on all platforms.
if (val && typeof val.length == 'number') {
// A NodeList is an object everywhere except Safari, where it's a function.
if (goog.isObject(val)) {
// A NodeList must have an item function (on non-IE platforms) or an item
// property of type 'string' (on IE).
return typeof val.item == 'function' || typeof val.item == 'string';
} else if (goog.isFunction(val)) {
// On Safari, a NodeList is a function with an item property that is also
// a function.
return typeof val.item == 'function';
}
}
// Not a NodeList.
return _FALSE;
};
/**
* Create an instance of a DOM helper with a new document object.
* @param {Document=} opt_document Document object to associate with this
* DOM helper.
* @constructor
*/
goog.dom.DomHelper = function(opt_document) {
/**
* Reference to the document object to use
* @type {!Document}
* @private
*/
this.document_ = opt_document || goog.global.document || document;
};
/**
* Gets the dom helper object for the document where the element resides.
* @param {Node=} opt_node If present, gets the DomHelper for this node.
* @return {!goog.dom.DomHelper} The DomHelper.
*/
goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
/**
* Sets the document object.
* @param {!Document} document Document object.
*/
goog.dom.DomHelper.prototype.setDocument = function(document) {
this.document_ = document;
};
/**
* Gets the document object being used by the dom library.
* @return {!Document} Document object.
*/
goog.dom.DomHelper.prototype.getDocument = function() {
return this.document_;
};
/**
* Alias for {@code getElementById}. If a DOM node is passed in then we just
* return that.
* @param {string|Element} element Element ID or a DOM node.
* @return {Element} The element with the given ID, or the node passed in.
*/
goog.dom.DomHelper.prototype.getElement = function(element) {
if (goog.isString(element)) {
return this.document_.getElementById(element);
} else {
return element;
}
};
/**
* Alias for {@code getElement}.
* @param {string|Element} element Element ID or a DOM node.
* @return {Element} The element with the given ID, or the node passed in.
* @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
*/
goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
/**
* Looks up elements by both tag and class name, using browser native functions
* ({@code querySelectorAll}, {@code getElementsByTagName} or
* {@code getElementsByClassName}) where possible. The returned array is a live
* NodeList or a static list depending on the code path taken.
*
* @see goog.dom.query
*
* @param {?string=} opt_tag Element tag name or * for all tags.
* @param {?string=} opt_class Optional class name.
* @param {Document|Element=} opt_el Optional element to look in.
* @return { {length: number} } Array-like list of elements (only a length
* property and numerical indices are guaranteed to exist).
*/
goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
opt_class,
opt_el) {
return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
opt_class, opt_el);
};
/**
* Returns an array of all the elements with the provided className.
* @see {goog.dom.query}
* @param {!string} className the name of the class to look for.
* @param {Element|Document=} opt_el Optional element to look in.
* @return { {length: number} } The items found with the class name provided.
*/
goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
var doc = opt_el || this.document_;
return goog.dom.getElementsByClass(className, doc);
};
/**
* Returns the first element we find matching the provided class name.
* @see {goog.dom.query}
* @param {!string} className the name of the class to look for.
* @param {Element|Document=} opt_el Optional element to look in.
* @return {Element} The first item found with the class name provided.
*/
goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
var doc = opt_el || this.document_;
return goog.dom.getElementByClass(className, doc);
};
/**
* Alias for {@code getElementsByTagNameAndClass}.
* @deprecated Use DomHelper getElementsByTagNameAndClass.
* @see goog.dom.query
*
* @param {?string=} opt_tag Element tag name.
* @param {?string=} opt_class Optional class name.
* @param {Element=} opt_el Optional element to look in.
* @return { {length: number} } Array-like list of elements (only a length
* property and numerical indices are guaranteed to exist).
*/
goog.dom.DomHelper.prototype.$$ =
goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
/**
* Sets a number of properties on a node.
* @param {Element} element DOM node to set properties on.
* @param {Object} properties Hash of property:value pairs.
*/
goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
/**
* Gets the dimensions of the viewport.
* @param {Window=} opt_window Optional window element to test. Defaults to
* the window of the Dom Helper.
* @return {!goog.math.Size} Object with values 'width' and 'height'.
*/
goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
// TODO(user): This should not take an argument. That breaks the rule of a
// a DomHelper representing a single frame/window/document.
return goog.dom.getViewportSize(opt_window || this.getWindow());
};
/**
* Typedef for use with goog.dom.createDom and goog.dom.append.
* @typedef {Object|string|Array|NodeList}
*/
goog.dom.Appendable;
/**
* Returns a dom node with a set of attributes. This function accepts varargs
* for subsequent nodes to be added. Subsequent nodes will be added to the
* first node as childNodes.
*
* So:
* createDom('div', null, createDom('p'), createDom('p'));
* would return a div with two child paragraphs
*
* An easy way to move all child nodes of an existing element to a new parent
* element is:
* createDom('div', null, oldElement.childNodes);
* which will remove all child nodes from the old element and add them as
* child nodes of the new DIV.
*
* @param {string} tagName Tag to create.
* @param {Object|string=} opt_attributes If object, then a map of name-value
* pairs for attributes. If a string, then this is the className of the new
* element.
* @param {...goog.dom.Appendable} var_args Further DOM nodes or
* strings for text nodes. If one of the var_args is an array or
* NodeList, its elements will be added as childNodes instead.
* @return {!Element} Reference to a DOM node.
*/
goog.dom.DomHelper.prototype.createDom = function(tagName,
opt_attributes,
var_args) {
return goog.dom.createDom_(this.document_, arguments);
};
/**
* Alias for {@code createDom}.
* @param {string} tagName Tag to create.
* @param {Object|string=} opt_attributes If object, then a map of name-value
* pairs for attributes. If a string, then this is the className of the new
* element.
* @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
* text nodes. If one of the var_args is an array, its children will be
* added as childNodes instead.
* @return {!Element} Reference to a DOM node.
* @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
*/
goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
/**
* Creates a new element.
* @param {string} name Tag name.
* @return {!Element} The new element.
*/
goog.dom.DomHelper.prototype.createElement = function(name) {
return this.document_.createElement(name);
};
/**
* Creates a new text node.
* @param {string} content Content.
* @return {!Text} The new text node.
*/
goog.dom.DomHelper.prototype.createTextNode = function(content) {
return this.document_.createTextNode(content);
};
/**
* Gets the window object associated with the document.
* @return {!Window} The window associated with the given document.
*/
goog.dom.DomHelper.prototype.getWindow = function() {
return goog.dom.getWindow_(this.document_);
};
/**
* Appends a child to a node.
* @param {Node} parent Parent.
* @param {Node} child Child.
*/
goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
/**
* Appends a node with text or other nodes.
* @param {!Node} parent The node to append nodes to.
* @param {...goog.dom.Appendable} var_args The things to append to the node.
* If this is a Node it is appended as is.
* If this is a string then a text node is appended.
* If this is an array like object then fields 0 to length - 1 are appended.
*/
goog.dom.DomHelper.prototype.append = goog.dom.append;
/**
* Removes all the child nodes on a DOM node.
* @param {Node} node Node to remove children from.
*/
goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
/**
* Inserts a new node before an existing reference node (i.e., as the previous
* sibling). If the reference node has no parent, then does nothing.
* @param {Node} newNode Node to insert.
* @param {Node} refNode Reference node to insert before.
*/
goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
/**
* Inserts a new node after an existing reference node (i.e., as the next
* sibling). If the reference node has no parent, then does nothing.
* @param {Node} newNode Node to insert.
* @param {Node} refNode Reference node to insert after.
*/
goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
/**
* Removes a node from its parent.
* @param {Node} node The node to remove.
* @return {Node} The node removed if removed; else, null.
*/
goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
/**
* Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
* parent.
* @param {Node} newNode Node to insert.
* @param {Node} oldNode Node to replace.
*/
goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
/**
* Flattens an element. That is, removes it and replace it with its children.
* @param {Element} element The element to flatten.
* @return {Element|undefined} The original element, detached from the document
* tree, sans children, or undefined if the element was already not in the
* document.
*/
goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
/**
* Whether the object looks like a DOM node.
* @param {*} obj The object being tested for node likeness.
* @return {boolean} Whether the object looks like a DOM node.
*/
goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
/**
* Whether a node contains another node.
* @param {Node} parent The node that should contain the other node.
* @param {Node} descendant The node to test presence of.
* @return {boolean} Whether the parent node contains the descendent node.
*/
goog.dom.DomHelper.prototype.contains = goog.dom.contains;
/**
* Returns the owner document for a node.
* @param {Node} node The node to get the document for.
* @return {!Document} The document owning the node.
*/
goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
/**
* Returns the text contents of the current node, without markup. New lines are
* stripped and whitespace is collapsed, such that each character would be
* visible.
*
* In browsers that support it, innerText is used. Other browsers attempt to
* simulate it via node traversal. Line breaks are canonicalized in IE.
*
* @param {Node} node The node from which we are getting content.
* @return {string} The text content.
*/
goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Python style iteration utilities.
*/
goog.provide('goog.iter');
goog.provide('goog.iter.Iterator');
goog.provide('goog.iter.StopIteration');
//goog.require('goog.array');
// TODO(user): Add more functions from Python's itertools.
// http://docs.python.org/library/itertools.html
/**
* @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
*/
goog.iter.Iterable;
// For script engines that already support iterators.
if ('StopIteration' in goog.global) {
/**
* Singleton Error object that is used to terminate iterations.
* @type {Error}
*/
goog.iter.StopIteration = goog.global['StopIteration'];
} else {
/**
* Singleton Error object that is used to terminate iterations.
* @type {Error}
* @suppress {duplicate}
*/
goog.iter.StopIteration = Error('StopIteration');
}
/**
* Class/interface for iterators. An iterator needs to implement a {@code next}
* method and it needs to throw a {@code goog.iter.StopIteration} when the
* iteration passes beyond the end. Iterators have no {@code hasNext} method.
* It is recommended to always use the helper functions to iterate over the
* iterator or in case you are only targeting JavaScript 1.7 for in loops.
* @constructor
*/
goog.iter.Iterator = function() {};
/**
* Returns the next value of the iteration. This will throw the object
* {@see goog.iter#StopIteration} when the iteration passes the end.
* @return {*} Any object or value.
*/
goog.iter.Iterator.prototype.next = function() {
throw goog.iter.StopIteration;
};
/**
* Returns the {@code Iterator} object itself. This is used to implement
* the iterator protocol in JavaScript 1.7
* @param {boolean=} opt_keys Whether to return the keys or values. Default is
* to only return the values. This is being used by the for-in loop (true)
* and the for-each-in loop (_FALSE). Even though the param gives a hint
* about what the iterator will return there is no guarantee that it will
* return the keys when true is passed.
* @return {!goog.iter.Iterator} The object itself.
*/
goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
return this;
};
/**
* Returns an iterator that knows how to iterate over the values in the object.
* @param {goog.iter.Iterable} iterable If the object is an iterator it
* will be returned as is. If the object has a {@code __iterator__} method
* that will be called to get the value iterator. If the object is an
* array-like object we create an iterator for that.
* @return {!goog.iter.Iterator} An iterator that knows how to iterate over the
* values in {@code iterable}.
*/
goog.iter.toIterator = function(iterable) {
if (iterable instanceof goog.iter.Iterator) {
return iterable;
}
if (typeof iterable.__iterator__ == 'function') {
return iterable.__iterator__(_FALSE);
}
if (goog.isArrayLike(iterable)) {
var i = 0;
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (_TRUE) {
if (i >= iterable.length) {
throw goog.iter.StopIteration;
}
// Don't include deleted elements.
if (!(i in iterable)) {
i++;
continue;
}
return iterable[i++];
}
};
return newIter;
}
// TODO(user): Should we fall back on goog.structs.getValues()?
throw Error('Not implemented');
};
/**
* Calls a function for each element in the iterator with the element of the
* iterator passed as argument.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate
* over. If the iterable is an object {@code toIterator} will be called on
* it.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, undefined, and the iterator) and the
* return value is irrelevant. The reason for passing undefined as the
* second argument is so that the same function can be used in
* {@see goog.array#forEach} as well as others.
* @param {Object=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
*/
goog.iter.forEach = function(iterable, f, opt_obj) {
if (goog.isArrayLike(iterable)) {
/** @preserveTry */
try {
goog.array.forEach((/** @type {goog.array.ArrayLike} */ iterable), f,
opt_obj);
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
} else {
iterable = goog.iter.toIterator(iterable);
/** @preserveTry */
try {
while (_TRUE) {
f.call(opt_obj, iterable.next(), _UNDEFINED, iterable);
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
}
};
/**
* Calls a function for every element in the iterator, and if the function
* returns true adds the element to a new iterator.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate over.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, undefined, and the iterator) and should
* return a boolean. If the return value is true the element will be
* included in the returned iteror. If it is _FALSE the element is not
* included.
* @param {Object=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {!goog.iter.Iterator} A new iterator in which only elements that
* passed the test are present.
*/
goog.iter.filter = function(iterable, f, opt_obj) {
iterable = goog.iter.toIterator(iterable);
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (_TRUE) {
var val = iterable.next();
if (f.call(opt_obj, val, _UNDEFINED, iterable)) {
return val;
}
}
};
return newIter;
};
/**
* Joins the values in a iterator with a delimiter.
* @param {goog.iter.Iterable} iterable The iterator to get the values from.
* @param {string} deliminator The text to put between the values.
* @return {string} The joined value string.
*/
goog.iter.join = function(iterable, deliminator) {
return goog.iter.toArray(iterable).join(deliminator);
};
/**
* For every element in the iterator call a function and return a new iterator
* with that value.
*
* @param {goog.iter.Iterable} iterable The iterator to iterate over.
* @param {Function} f The function to call for every element. This function
* takes 3 arguments (the element, undefined, and the iterator) and should
* return a new value.
* @param {Object=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {!goog.iter.Iterator} A new iterator that returns the results of
* applying the function to each element in the original iterator.
*/
goog.iter.map = function(iterable, f, opt_obj) {
iterable = goog.iter.toIterator(iterable);
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (_TRUE) {
var val = iterable.next();
return f.call(opt_obj, val, _UNDEFINED, iterable);
}
};
return newIter;
};
/**
* Goes through the values in the iterator. Calls f for each these and if any of
* them returns true, this returns true (without checking the rest). If all
* return _FALSE this will return _FALSE.
*
* @param {goog.iter.Iterable} iterable The iterator object.
* @param {Function} f The function to call for every value. This function
* takes 3 arguments (the value, undefined, and the iterator) and should
* return a boolean.
* @param {Object=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {boolean} true if any value passes the test.
*/
goog.iter.some = function(iterable, f, opt_obj) {
iterable = goog.iter.toIterator(iterable);
/** @preserveTry */
try {
while (_TRUE) {
if (f.call(opt_obj, iterable.next(), _UNDEFINED, iterable)) {
return _TRUE;
}
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
return _FALSE;
};
/**
* Goes through the values in the iterator. Calls f for each these and if any of
* them returns _FALSE this returns _FALSE (without checking the rest). If all
* return true this will return true.
*
* @param {goog.iter.Iterable} iterable The iterator object.
* @param {Function} f The function to call for every value. This function
* takes 3 arguments (the value, undefined, and the iterator) and should
* return a boolean.
* @param {Object=} opt_obj The object to be used as the value of 'this' within
* {@code f}.
* @return {boolean} true if every value passes the test.
*/
goog.iter.every = function(iterable, f, opt_obj) {
iterable = goog.iter.toIterator(iterable);
/** @preserveTry */
try {
while (_TRUE) {
if (!f.call(opt_obj, iterable.next(), _UNDEFINED, iterable)) {
return _FALSE;
}
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
}
}
return _TRUE;
};
/**
* Converts the iterator to an array
* @param {goog.iter.Iterable} iterable The iterator to convert to an array.
* @return {!Array} An array of the elements the iterator iterates over.
*/
goog.iter.toArray = function(iterable) {
// Fast path for array-like.
if (goog.isArrayLike(iterable)) {
return goog.array.toArray((/** @type {!goog.array.ArrayLike} */ iterable));
}
iterable = goog.iter.toIterator(iterable);
var array = [];
goog.iter.forEach(iterable, function(val) {
array.push(val);
});
return array;
};
/**
* Iterates over 2 iterators and returns true if they contain the same sequence
* of elements and have the same length.
* @param {goog.iter.Iterable} iterable1 The first iterable object.
* @param {goog.iter.Iterable} iterable2 The second iterable object.
* @return {boolean} true if the iterators contain the same sequence of
* elements and have the same length.
*/
goog.iter.equals = function(iterable1, iterable2) {
iterable1 = goog.iter.toIterator(iterable1);
iterable2 = goog.iter.toIterator(iterable2);
var b1, b2;
/** @preserveTry */
try {
while (_TRUE) {
b1 = b2 = _FALSE;
var val1 = iterable1.next();
b1 = _TRUE;
var val2 = iterable2.next();
b2 = _TRUE;
if (val1 != val2) {
return _FALSE;
}
}
} catch (ex) {
if (ex !== goog.iter.StopIteration) {
throw ex;
} else {
if (b1 && !b2) {
// iterable1 done but iterable2 is not done.
return _FALSE;
}
if (!b2) {
/** @preserveTry */
try {
// iterable2 not done?
val2 = iterable2.next();
// iterable2 not done but iterable1 is done
return _FALSE;
} catch (ex1) {
if (ex1 !== goog.iter.StopIteration) {
throw ex1;
}
// iterable2 done as well... They are equal
return _TRUE;
}
}
}
}
return _FALSE;
};
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Iterator subclass for DOM tree traversal.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.TagIterator');
goog.provide('goog.dom.TagWalkType');
//goog.require('goog.dom.NodeType');
//goog.require('goog.iter.Iterator');
//goog.require('goog.iter.StopIteration');
/**
* There are three types of token:
*
* - {@code START_TAG} - The beginning of a tag.
*
- {@code OTHER} - Any non-element node position.
*
- {@code END_TAG} - The end of a tag.
*
* Users of this enumeration can rely on {@code START_TAG + END_TAG = 0} and
* that {@code OTHER = 0}.
*
* @enum {number}
*/
goog.dom.TagWalkType = {
START_TAG: 1,
OTHER: 0,
END_TAG: -1
};
/**
* A DOM tree traversal iterator.
*
* Starting with the given node, the iterator walks the DOM in order, reporting
* events for the start and end of Elements, and the presence of text nodes. For
* example:
*
*
* <div>1<span>2</span>3</div>
*
*
* Will return the following nodes:
*
* [div, 1, span, 2, span, 3, div]
*
* With the following states:
*
* [START, OTHER, START, OTHER, END, OTHER, END]
*
* And the following depths
*
* [1, 1, 2, 2, 1, 1, 0]
*
* Imagining | represents iterator position, the traversal stops at
* each of the following locations:
*
*
* <div>|1|<span>|2|</span>|3|</div>|
*
*
* The iterator can also be used in reverse mode, which will return the nodes
* and states in the opposite order. The depths will be slightly different
* since, like in normal mode, the depth is computed *after* the given node.
*
* Lastly, it is possible to create an iterator that is unconstrained, meaning
* that it will continue iterating until the end of the document instead of
* until exiting the start node.
*
* @param {Node=} opt_node The start node. If unspecified or null, defaults to
* an empty iterator.
* @param {boolean=} opt_reversed Whether to traverse the tree in reverse.
* @param {boolean=} opt_unconstrained Whether the iterator is not constrained
* to the starting node and its children.
* @param {goog.dom.TagWalkType?=} opt_tagType The type of the position.
* Defaults to the start of the given node for forward iterators, and
* the end of the node for reverse iterators.
* @param {number=} opt_depth The starting tree depth.
* @constructor
* @extends {goog.iter.Iterator}
*/
goog.dom.TagIterator = function(opt_node, opt_reversed,
opt_unconstrained, opt_tagType, opt_depth) {
this.reversed = !!opt_reversed;
if (opt_node) {
this.setPosition(opt_node, opt_tagType);
}
this.depth = opt_depth != _UNDEFINED ? opt_depth : this.tagType || 0;
if (this.reversed) {
this.depth *= -1;
}
this.constrained = !opt_unconstrained;
};
goog.inherits(goog.dom.TagIterator, goog.iter.Iterator);
/**
* The node this position is located on.
* @type {Node}
*/
goog.dom.TagIterator.prototype.node = _NULL;
/**
* The type of this position.
* @type {goog.dom.TagWalkType}
*/
goog.dom.TagIterator.prototype.tagType = goog.dom.TagWalkType.OTHER;
/**
* The tree depth of this position relative to where the iterator started. The
* depth is considered to be the tree depth just past the current node, so if an
* iterator is at position
* |
*
* (i.e. the node is the div and the type is START_TAG) its depth will be 1.
* @type {number}
*/
goog.dom.TagIterator.prototype.depth;
/**
* Whether the node iterator is moving in reverse.
* @type {boolean}
*/
goog.dom.TagIterator.prototype.reversed;
/**
* Whether the iterator is constrained to the starting node and its children.
* @type {boolean}
*/
goog.dom.TagIterator.prototype.constrained;
/**
* Whether iteration has started.
* @type {boolean}
* @private
*/
goog.dom.TagIterator.prototype.started_ = _FALSE;
/**
* Set the position of the iterator. Overwrite the tree node and the position
* type which can be one of the {@link goog.dom.TagWalkType} token types.
* Only overwrites the tree depth when the parameter is specified.
* @param {Node} node The node to set the position to.
* @param {goog.dom.TagWalkType?=} opt_tagType The type of the position
* Defaults to the start of the given node.
* @param {number=} opt_depth The tree depth.
*/
goog.dom.TagIterator.prototype.setPosition = function(node,
opt_tagType, opt_depth) {
this.node = node;
if (node) {
if (goog.isNumber(opt_tagType)) {
this.tagType = opt_tagType;
} else {
// Auto-determine the proper type
this.tagType = this.node.nodeType != goog.dom.NodeType.ELEMENT ?
goog.dom.TagWalkType.OTHER :
this.reversed ? goog.dom.TagWalkType.END_TAG :
goog.dom.TagWalkType.START_TAG;
}
}
if (goog.isNumber(opt_depth)) {
this.depth = opt_depth;
}
};
/**
* Replace this iterator's values with values from another.
* @param {goog.dom.TagIterator} other The iterator to copy.
* @protected
*/
goog.dom.TagIterator.prototype.copyFrom = function(other) {
this.node = other.node;
this.tagType = other.tagType;
this.depth = other.depth;
this.reversed = other.reversed;
this.constrained = other.constrained;
};
/**
* @return {goog.dom.TagIterator} A copy of this iterator.
*/
goog.dom.TagIterator.prototype.clone = function() {
return new goog.dom.TagIterator(this.node, this.reversed,
!this.constrained, this.tagType, this.depth);
};
/**
* Skip the current tag.
*/
goog.dom.TagIterator.prototype.skipTag = function() {
var check = this.reversed ? goog.dom.TagWalkType.END_TAG :
goog.dom.TagWalkType.START_TAG;
if (this.tagType == check) {
this.tagType = /** @type {goog.dom.TagWalkType} */ (check * -1);
this.depth += this.tagType * (this.reversed ? -1 : 1);
}
};
/**
* Restart the current tag.
*/
goog.dom.TagIterator.prototype.restartTag = function() {
var check = this.reversed ? goog.dom.TagWalkType.START_TAG :
goog.dom.TagWalkType.END_TAG;
if (this.tagType == check) {
this.tagType = /** @type {goog.dom.TagWalkType} */ (check * -1);
this.depth += this.tagType * (this.reversed ? -1 : 1);
}
};
/**
* Move to the next position in the DOM tree.
* @return {Node} Returns the next node, or throws a goog.iter.StopIteration
* exception if the end of the iterator's range has been reached.
*/
goog.dom.TagIterator.prototype.next = function() {
var node;
if (this.started_) {
if (!this.node || this.constrained && this.depth == 0) {
throw goog.iter.StopIteration;
}
node = this.node;
var startType = this.reversed ? goog.dom.TagWalkType.END_TAG :
goog.dom.TagWalkType.START_TAG;
if (this.tagType == startType) {
// If we have entered the tag, test if there are any children to move to.
var child = this.reversed ? node.lastChild : node.firstChild;
if (child) {
this.setPosition(child);
} else {
// If not, move on to exiting this tag.
this.setPosition(node,
/** @type {goog.dom.TagWalkType} */ (startType * -1));
}
} else {
var sibling = this.reversed ? node.previousSibling : node.nextSibling;
// by simonz
//
인 경우에 IE에서 무한 loop가 발생한다.
// strong을 iteration하지 않고 빠져나가도록 수정을 했다.
if (sibling && node.parentNode == sibling.parentNode) {
// if (sibling) {
// Try to move to the next node.
this.setPosition(sibling);
} else {
// If no such node exists, exit our parent.
this.setPosition(node.parentNode,
/** @type {goog.dom.TagWalkType} */ (startType * -1));
}
}
this.depth += this.tagType * (this.reversed ? -1 : 1);
} else {
this.started_ = _TRUE;
}
// Check the new position for being last, and return it if it's not.
node = this.node;
if (!this.node) {
throw goog.iter.StopIteration;
}
return node;
};
/**
* @return {boolean} Whether next has ever been called on this iterator.
* @protected
*/
goog.dom.TagIterator.prototype.isStarted = function() {
return this.started_;
};
/**
* @return {boolean} Whether this iterator's position is a start tag position.
*/
goog.dom.TagIterator.prototype.isStartTag = function() {
return this.tagType == goog.dom.TagWalkType.START_TAG;
};
/**
* @return {boolean} Whether this iterator's position is an end tag position.
*/
goog.dom.TagIterator.prototype.isEndTag = function() {
return this.tagType == goog.dom.TagWalkType.END_TAG;
};
/**
* Test if two iterators are at the same position - i.e. if the node and tagType
* is the same. This will still return true if the two iterators are moving in
* opposite directions or have different constraints.
* @param {goog.dom.TagIterator} other The iterator to compare to.
* @return {boolean} Whether the two iterators are at the same position.
*/
goog.dom.TagIterator.prototype.equals = function(other) {
// Nodes must be equal, and we must either have reached the end of our tree
// or be at the same position.
return other.node == this.node && (!this.node ||
other.tagType == this.tagType);
};
/**
* Replace the current node with the list of nodes. Reset the iterator so that
* it visits the first of the nodes next.
* @param {...Object} var_args A list of nodes to replace the current node with.
* If the first argument is array-like, it will be used, otherwise all the
* arguments are assumed to be nodes.
*/
goog.dom.TagIterator.prototype.splice = function(var_args) {
// Reset the iterator so that it iterates over the first replacement node in
// the arguments on the next iteration.
var node = this.node;
this.restartTag();
this.reversed = !this.reversed;
goog.dom.TagIterator.prototype.next.call(this);
this.reversed = !this.reversed;
// Replace the node with the arguments.
var arr = goog.isArrayLike(arguments[0]) ? arguments[0] : arguments;
for (var i = arr.length - 1; i >= 0; i--) {
goog.dom.insertSiblingAfter(arr[i], node);
}
goog.dom.removeNode(node);
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Generics method for collection-like classes and objects.
*
*
* This file contains functions to work with collections. It supports using
* Map, Set, Array and Object and other classes that implement collection-like
* methods.
*/
goog.provide('goog.structs');
//goog.require('goog.array');
//goog.require('goog.object');
// We treat an object as a dictionary if it has getKeys or it is an object that
// isn't arrayLike.
/**
* Returns the number of values in the collection-like object.
* @param {Object} col The collection-like object.
* @return {number} The number of values in the collection-like object.
*/
goog.structs.getCount = function(col) {
if (typeof col.getCount == 'function') {
return col.getCount();
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return col.length;
}
return goog.object.getCount(col);
};
/**
* Returns the values of the collection-like object.
* @param {Object} col The collection-like object.
* @return {!Array} The values in the collection-like object.
*/
goog.structs.getValues = function(col) {
if (typeof col.getValues == 'function') {
return col.getValues();
}
if (goog.isString(col)) {
return col.split('');
}
if (goog.isArrayLike(col)) {
var rv = [];
var l = col.length;
for (var i = 0; i < l; i++) {
rv.push(col[i]);
}
return rv;
}
return goog.object.getValues(col);
};
/**
* Returns the keys of the collection. Some collections have no notion of
* keys/indexes and this function will return undefined in those cases.
* @param {Object} col The collection-like object.
* @return {!Array|undefined} The keys in the collection.
*/
goog.structs.getKeys = function(col) {
if (typeof col.getKeys == 'function') {
return col.getKeys();
}
// if we have getValues but no getKeys we know this is a key-less collection
if (typeof col.getValues == 'function') {
return _UNDEFINED;
}
if (goog.isArrayLike(col) || goog.isString(col)) {
var rv = [];
var l = col.length;
for (var i = 0; i < l; i++) {
rv.push(i);
}
return rv;
}
return goog.object.getKeys(col);
};
/**
* Whether the collection contains the given value. This is O(n) and uses
* equals (==) to test the existence.
* @param {Object} col The collection-like object.
* @param {*} val The value to check for.
* @return {boolean} True if the map contains the value.
*/
goog.structs.contains = function(col, val) {
if (typeof col.contains == 'function') {
return col.contains(val);
}
if (typeof col.containsValue == 'function') {
return col.containsValue(val);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.contains(/** @type {Array} */ (col), val);
}
return goog.object.containsValue(col, val);
};
/**
* Whether the collection is empty.
* @param {Object} col The collection-like object.
* @return {boolean} True if empty.
*/
goog.structs.isEmpty = function(col) {
if (typeof col.isEmpty == 'function') {
return col.isEmpty();
}
// We do not use goog.string.isEmpty because here we treat the string as
// collection and as such even whitespace matters
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.isEmpty(/** @type {Array} */ (col));
}
return goog.object.isEmpty(col);
};
/**
* Removes all the elements from the collection.
* @param {Object} col The collection-like object.
*/
goog.structs.clear = function(col) {
// NOTE(user): This should not contain strings because strings are immutable
if (typeof col.clear == 'function') {
col.clear();
} else if (goog.isArrayLike(col)) {
goog.array.clear((/** @type {goog.array.ArrayLike} */ col));
} else {
goog.object.clear(col);
}
};
/**
* Calls a function for each value in a collection. The function takes
* three arguments; the value, the key and the collection.
*
* @param {Object} col The collection-like object.
* @param {Function} f The function to call for every value. This function takes
* 3 arguments (the value, the key or undefined if the collection has no
* notion of keys, and the collection) and the return value is irrelevant.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
*/
goog.structs.forEach = function(col, f, opt_obj) {
if (typeof col.forEach == 'function') {
col.forEach(f, opt_obj);
} else if (goog.isArrayLike(col) || goog.isString(col)) {
goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
} else {
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
f.call(opt_obj, values[i], keys && keys[i], col);
}
}
};
/**
* Calls a function for every value in the collection. When a call returns true,
* adds the value to a new collection (Array is returned by default).
*
* @param {Object} col The collection-like object.
* @param {Function} f The function to call for every value. This function takes
* 3 arguments (the value, the key or undefined if the collection has no
* notion of keys, and the collection) and should return a Boolean. If the
* return value is true the value is added to the result collection. If it
* is _FALSE the value is not included.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {!Object|!Array} A new collection where the passed values are
* present. If col is a key-less collection an array is returned. If col
* has keys and values a plain old JS object is returned.
*/
goog.structs.filter = function(col, f, opt_obj) {
if (typeof col.filter == 'function') {
return col.filter(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
}
var rv;
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
if (keys) {
rv = {};
for (var i = 0; i < l; i++) {
if (f.call(opt_obj, values[i], keys[i], col)) {
rv[keys[i]] = values[i];
}
}
} else {
// We should not use goog.array.filter here since we want to make sure that
// the index is undefined as well as make sure that col is passed to the
// function.
rv = [];
for (var i = 0; i < l; i++) {
if (f.call(opt_obj, values[i], _UNDEFINED, col)) {
rv.push(values[i]);
}
}
}
return rv;
};
/**
* Calls a function for every value in the collection and adds the result into a
* new collection (defaults to creating a new Array).
*
* @param {Object} col The collection-like object.
* @param {Function} f The function to call for every value. This function
* takes 3 arguments (the value, the key or undefined if the collection has
* no notion of keys, and the collection) and should return something. The
* result will be used as the value in the new collection.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {!Object|!Array} A new collection with the new values. If col is a
* key-less collection an array is returned. If col has keys and values a
* plain old JS object is returned.
*/
goog.structs.map = function(col, f, opt_obj) {
if (typeof col.map == 'function') {
return col.map(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
}
var rv;
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
if (keys) {
rv = {};
for (var i = 0; i < l; i++) {
rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
}
} else {
// We should not use goog.array.map here since we want to make sure that
// the index is undefined as well as make sure that col is passed to the
// function.
rv = [];
for (var i = 0; i < l; i++) {
rv[i] = f.call(opt_obj, values[i], _UNDEFINED, col);
}
}
return rv;
};
/**
* Calls f for each value in a collection. If any call returns true this returns
* true (without checking the rest). If all returns _FALSE this returns _FALSE.
*
* @param {Object|Array|string} col The collection-like object.
* @param {Function} f The function to call for every value. This function takes
* 3 arguments (the value, the key or undefined if the collection has no
* notion of keys, and the collection) and should return a Boolean.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {boolean} True if any value passes the test.
*/
goog.structs.some = function(col, f, opt_obj) {
if (typeof col.some == 'function') {
return col.some(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
}
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
if (f.call(opt_obj, values[i], keys && keys[i], col)) {
return _TRUE;
}
}
return _FALSE;
};
/**
* Calls f for each value in a collection. If all calls return true this return
* true this returns true. If any returns _FALSE this returns _FALSE at this point
* and does not continue to check the remaining values.
*
* @param {Object} col The collection-like object.
* @param {Function} f The function to call for every value. This function takes
* 3 arguments (the value, the key or undefined if the collection has no
* notion of keys, and the collection) and should return a Boolean.
* @param {Object=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {boolean} True if all key-value pairs pass the test.
*/
goog.structs.every = function(col, f, opt_obj) {
if (typeof col.every == 'function') {
return col.every(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
}
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
return _FALSE;
}
}
return _TRUE;
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Datastructure: Hash Map.
*
*
* This file contains an implementation of a Map structure. It implements a lot
* of the methods used in goog.structs so those functions work on hashes. For
* convenience with common usage the methods accept any type for the key, though
* internally they will be cast to strings.
*/
goog.provide('goog.structs.Map');
//goog.require('goog.iter.Iterator');
//goog.require('goog.iter.StopIteration');
//goog.require('goog.object');
//goog.require('goog.structs');
/**
* Class for Hash Map datastructure.
* @param {*=} opt_map Map or Object to initialize the map with.
* @param {...*} var_args If 2 or more arguments are present then they
* will be used as key-value pairs.
* @constructor
*/
goog.structs.Map = function(opt_map, var_args) {
/**
* Underlying JS object used to implement the map.
* @type {!Object}
* @private
*/
this.map_ = {};
/**
* An array of keys. This is necessary for two reasons:
* 1. Iterating the keys using for (var key in this.map_) allocates an
* object for every key in IE which is really bad for IE6 GC perf.
* 2. Without a side data structure, we would need to escape all the keys
* as that would be the only way we could tell during iteration if the
* key was an internal key or a property of the object.
*
* This array can contain deleted keys so it's necessary to check the map
* as well to see if the key is still in the map (this doesn't require a
* memory allocation in IE).
* @type {!Array.}
* @private
*/
this.keys_ = [];
var argLength = arguments.length;
if (argLength > 1) {
if (argLength % 2) {
throw Error('Uneven number of arguments');
}
for (var i = 0; i < argLength; i += 2) {
this.set(arguments[i], arguments[i + 1]);
}
} else if (opt_map) {
this.addAll(/** @type {Object} */ (opt_map));
}
};
/**
* The number of key value pairs in the map.
* @private
* @type {number}
*/
goog.structs.Map.prototype.count_ = 0;
/**
* Version used to detect changes while iterating.
* @private
* @type {number}
*/
goog.structs.Map.prototype.version_ = 0;
/**
* @return {number} The number of key-value pairs in the map.
*/
goog.structs.Map.prototype.getCount = function() {
return this.count_;
};
/**
* Returns the values of the map.
* @return {!Array} The values in the map.
*/
goog.structs.Map.prototype.getValues = function() {
this.cleanupKeysArray_();
var rv = [];
for (var i = 0; i < this.keys_.length; i++) {
var key = this.keys_[i];
rv.push(this.map_[key]);
}
return rv;
};
/**
* Returns the keys of the map.
* @return {!Array.} Array of string values.
*/
goog.structs.Map.prototype.getKeys = function() {
this.cleanupKeysArray_();
return /** @type {!Array.} */ (this.keys_.concat());
};
/**
* Whether the map contains the given key.
* @param {*} key The key to check for.
* @return {boolean} Whether the map contains the key.
*/
goog.structs.Map.prototype.containsKey = function(key) {
return goog.structs.Map.hasKey_(this.map_, key);
};
/**
* Whether the map contains the given value. This is O(n).
* @param {*} val The value to check for.
* @return {boolean} Whether the map contains the value.
*/
goog.structs.Map.prototype.containsValue = function(val) {
for (var i = 0; i < this.keys_.length; i++) {
var key = this.keys_[i];
if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
return _TRUE;
}
}
return _FALSE;
};
/**
* Whether this map is equal to the argument map.
* @param {goog.structs.Map} otherMap The map against which to test equality.
* @param {function(*, *) : boolean=} opt_equalityFn Optional equality function
* to test equality of values. If not specified, this will test whether
* the values contained in each map are identical objects.
* @return {boolean} Whether the maps are equal.
*/
goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
if (this === otherMap) {
return _TRUE;
}
if (this.count_ != otherMap.getCount()) {
return _FALSE;
}
var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
this.cleanupKeysArray_();
for (var key, i = 0; key = this.keys_[i]; i++) {
if (!equalityFn(this.get(key), otherMap.get(key))) {
return _FALSE;
}
}
return _TRUE;
};
/**
* Default equality test for values.
* @param {*} a The first value.
* @param {*} b The second value.
* @return {boolean} Whether a and b reference the same object.
*/
goog.structs.Map.defaultEquals = function(a, b) {
return a === b;
};
/**
* @return {boolean} Whether the map is empty.
*/
goog.structs.Map.prototype.isEmpty = function() {
return this.count_ == 0;
};
/**
* Removes all key-value pairs from the map.
*/
goog.structs.Map.prototype.clear = function() {
this.map_ = {};
this.keys_.length = 0;
this.count_ = 0;
this.version_ = 0;
};
/**
* Removes a key-value pair based on the key. This is O(logN) amortized due to
* updating the keys array whenever the count becomes half the size of the keys
* in the keys array.
* @param {*} key The key to remove.
* @return {boolean} Whether object was removed.
*/
goog.structs.Map.prototype.remove = function(key) {
if (goog.structs.Map.hasKey_(this.map_, key)) {
delete this.map_[key];
this.count_--;
this.version_++;
// clean up the keys array if the threshhold is hit
if (this.keys_.length > 2 * this.count_) {
this.cleanupKeysArray_();
}
return _TRUE;
}
return _FALSE;
};
/**
* Cleans up the temp keys array by removing entries that are no longer in the
* map.
* @private
*/
goog.structs.Map.prototype.cleanupKeysArray_ = function() {
if (this.count_ != this.keys_.length) {
// First remove keys that are no longer in the map.
var srcIndex = 0;
var destIndex = 0;
while (srcIndex < this.keys_.length) {
var key = this.keys_[srcIndex];
if (goog.structs.Map.hasKey_(this.map_, key)) {
this.keys_[destIndex++] = key;
}
srcIndex++;
}
this.keys_.length = destIndex;
}
if (this.count_ != this.keys_.length) {
// If the count still isn't correct, that means we have duplicates. This can
// happen when the same key is added and removed multiple times. Now we have
// to allocate one extra Object to remove the duplicates. This could have
// been done in the first pass, but in the common case, we can avoid
// allocating an extra object by only doing this when necessary.
var seen = {};
var srcIndex = 0;
var destIndex = 0;
while (srcIndex < this.keys_.length) {
var key = this.keys_[srcIndex];
if (!(goog.structs.Map.hasKey_(seen, key))) {
this.keys_[destIndex++] = key;
seen[key] = 1;
}
srcIndex++;
}
this.keys_.length = destIndex;
}
};
/**
* Returns the value for the given key. If the key is not found and the default
* value is not given this will return {@code undefined}.
* @param {*} key The key to get the value for.
* @param {*=} opt_val The value to return if no item is found for the given
* key, defaults to undefined.
* @return {*} The value for the given key.
*/
goog.structs.Map.prototype.get = function(key, opt_val) {
if (goog.structs.Map.hasKey_(this.map_, key)) {
return this.map_[key];
}
return opt_val;
};
/**
* Adds a key-value pair to the map.
* @param {*} key The key.
* @param {*} value The value to add.
*/
goog.structs.Map.prototype.set = function(key, value) {
if (!(goog.structs.Map.hasKey_(this.map_, key))) {
this.count_++;
this.keys_.push(key);
// Only change the version if we add a new key.
this.version_++;
}
this.map_[key] = value;
};
/**
* Adds multiple key-value pairs from another goog.structs.Map or Object.
* @param {Object} map Object containing the data to add.
*/
goog.structs.Map.prototype.addAll = function(map) {
var keys, values;
if (map instanceof goog.structs.Map) {
keys = map.getKeys();
values = map.getValues();
} else {
keys = goog.object.getKeys(map);
values = goog.object.getValues(map);
}
// we could use goog.array.forEach here but I don't want to introduce that
// dependency just for this.
for (var i = 0; i < keys.length; i++) {
this.set(keys[i], values[i]);
}
};
/**
* Clones a map and returns a new map.
* @return {!goog.structs.Map} A new map with the same key-value pairs.
*/
goog.structs.Map.prototype.clone = function() {
return new goog.structs.Map(this);
};
/**
* @return {!Object} Object representation of the map.
*/
goog.structs.Map.prototype.toObject = function() {
this.cleanupKeysArray_();
var obj = {};
for (var i = 0; i < this.keys_.length; i++) {
var key = this.keys_[i];
obj[key] = this.map_[key];
}
return obj;
};
/**
* Returns an iterator that iterates over the values or the keys in the map.
* This throws an exception if the map was mutated since the iterator was
* created.
* @param {boolean=} opt_keys True to iterate over the keys. _FALSE to iterate
* over the values. The default value is _FALSE.
* @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
*/
goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
// Clean up keys to minimize the risk of iterating over dead keys.
this.cleanupKeysArray_();
var i = 0;
var keys = this.keys_;
var map = this.map_;
var version = this.version_;
var selfObj = this;
var newIter = new goog.iter.Iterator;
newIter.next = function() {
while (_TRUE) {
if (version != selfObj.version_) {
throw Error('The map has changed since the iterator was created');
}
if (i >= keys.length) {
throw goog.iter.StopIteration;
}
var key = keys[i++];
return opt_keys ? key : map[key];
}
};
return newIter;
};
/**
* Safe way to test for hasOwnProperty. It even allows testing for
* 'hasOwnProperty'.
* @param {Object} obj The object to test for presence of the given key.
* @param {*} key The key to check for.
* @return {boolean} Whether the object has the key.
* @private
*/
goog.structs.Map.hasKey_ = function(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Datastructure: Set.
*
*
* This class implements a set data structure. Adding and removing is O(1). It
* supports both object and primitive values. Be careful because you can add
* both 1 and new Number(1), because these are not the same. You can even add
* multiple new Number(1) because these are not equal.
*/
goog.provide('goog.structs.Set');
//goog.require('goog.structs');
//goog.require('goog.structs.Map');
/**
* A set that can contain both primitives and objects. Adding and removing
* elements is O(1). Primitives are treated as identical if they have the same
* type and convert to the same string. Objects are treated as identical only
* if they are references to the same object. WARNING: A goog.structs.Set can
* contain both 1 and (new Number(1)), because they are not the same. WARNING:
* Adding (new Number(1)) twice will yield two distinct elements, because they
* are two different objects. WARNING: Any object that is added to a
* goog.structs.Set will be modified! Because goog.getUid() is used to
* identify objects, every object in the set will be mutated.
* @param {Array|Object=} opt_values Initial values to start with.
* @constructor
*/
goog.structs.Set = function(opt_values) {
this.map_ = new goog.structs.Map;
if (opt_values) {
this.addAll(opt_values);
}
};
/**
* Obtains a unique key for an element of the set. Primitives will yield the
* same key if they have the same type and convert to the same string. Object
* references will yield the same key only if they refer to the same object.
* @param {*} val Object or primitive value to get a key for.
* @return {string} A unique key for this value/object.
* @private
*/
goog.structs.Set.getKey_ = function(val) {
var type = typeof val;
if (type == 'object' && val || type == 'function') {
return 'o' + goog.getUid(/** @type {Object} */ (val));
} else {
return type.substr(0, 1) + val;
}
};
/**
* @return {number} The number of elements in the set.
*/
goog.structs.Set.prototype.getCount = function() {
return this.map_.getCount();
};
/**
* Add a primitive or an object to the set.
* @param {*} element The primitive or object to add.
*/
goog.structs.Set.prototype.add = function(element) {
this.map_.set(goog.structs.Set.getKey_(element), element);
};
/**
* Adds all the values in the given collection to this set.
* @param {Array|Object} col A collection containing the elements to add.
*/
goog.structs.Set.prototype.addAll = function(col) {
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
this.add(values[i]);
}
};
/**
* Removes the given element from this set.
* @param {*} element The primitive or object to remove.
* @return {boolean} Whether the element was found and removed.
*/
goog.structs.Set.prototype.remove = function(element) {
return this.map_.remove(goog.structs.Set.getKey_(element));
};
/**
* Removes all elements from this set.
*/
goog.structs.Set.prototype.clear = function() {
this.map_.clear();
};
/**
* Tests whether this set is empty.
* @return {boolean} True if there are no elements in this set.
*/
goog.structs.Set.prototype.isEmpty = function() {
return this.map_.isEmpty();
};
/**
* Tests whether this set contains the given element.
* @param {*} element The primitive or object to test for.
* @return {boolean} True if this set contains the given element.
*/
goog.structs.Set.prototype.contains = function(element) {
return this.map_.containsKey(goog.structs.Set.getKey_(element));
};
/**
* Tests whether this set contains all the values in a given collection.
* Repeated elements in the collection are ignored, e.g. (new
* goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
* @param {Object} col A collection-like object.
* @return {boolean} True if the set contains all elements.
*/
goog.structs.Set.prototype.containsAll = function(col) {
return goog.structs.every(col, this.contains, this);
};
/**
* Returns an array containing all the elements in this set.
* @return {Array} An array containing all the elements in this set.
*/
goog.structs.Set.prototype.getValues = function() {
return this.map_.getValues();
};
/**
* Creates a shallow clone of this set.
* @return {goog.structs.Set} A new set containing all the same elements as
* this set.
*/
goog.structs.Set.prototype.clone = function() {
return new goog.structs.Set(this);
};
/**
* Tests whether the given collection consists of the same elements as this set,
* regardless of order, without repetition. Primitives are treated as equal if
* they have the same type and convert to the same string; objects are treated
* as equal if they are references to the same object. This operation is O(n).
* @param {Object} col A collection.
* @return {boolean} True if the given collection consists of the same elements
* as this set, regardless of order, without repetition.
*/
goog.structs.Set.prototype.equals = function(col) {
return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
};
/**
* Tests whether the given collection contains all the elements in this set.
* Primitives are treated as equal if they have the same type and convert to the
* same string; objects are treated as equal if they are references to the same
* object. This operation is O(n).
* @param {Object} col A collection.
* @return {boolean} True if this set is a subset of the given collection.
*/
goog.structs.Set.prototype.isSubsetOf = function(col) {
var colCount = goog.structs.getCount(col);
if (this.getCount() > colCount) {
return _FALSE;
}
// TODO(user) Find the minimal collection size where the conversion makes
// the contains() method faster.
if (!(col instanceof goog.structs.Set) && colCount > 5) {
// Convert to a goog.structs.Set so that goog.structs.contains runs in
// O(1) time instead of O(n) time.
col = new goog.structs.Set(col);
}
return goog.structs.every(this, function(value) {
return goog.structs.contains(col, value);
});
};
/**
* Returns an iterator that iterates over the elements in this set.
* @param {boolean=} opt_keys This argument is ignored.
* @return {goog.iter.Iterator} An iterator over the elements in this set.
*/
goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
return this.map_.__iterator__(_FALSE);
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//goog.require('goog.array');
//goog.require('goog.string');
//goog.require('goog.structs.Set');
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the disposable interface. A disposable object
* has a dispose method to to clean up references and resources.
*/
goog.provide('goog.disposable.IDisposable');
/**
* Interface for a disposable object. If a instance requires cleanup
* (references COM objects, DOM notes, or other disposable objects), it should
* implement this interface (it may subclass goog.Disposable).
* @interface
*/
goog.disposable.IDisposable = function() {};
/**
* Disposes of the object and its resources.
* @return {void} Nothing.
*/
goog.disposable.IDisposable.prototype.dispose;
/**
* @return {boolean} Whether the object has been disposed of.
*/
goog.disposable.IDisposable.prototype.isDisposed;
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implements the disposable interface. The dispose method is used
* to clean up references and resources.
*/
goog.provide('goog.Disposable');
goog.provide('goog.dispose');
//goog.require('goog.disposable.IDisposable');
/**
* Class that provides the basic implementation for disposable objects. If your
* class holds one or more references to COM objects, DOM nodes, or other
* disposable objects, it should extend this class or implement the disposable
* interface (defined in goog.disposable.IDisposable).
* @constructor
* @implements {goog.disposable.IDisposable}
*/
goog.Disposable = function() {
if (goog.Disposable.ENABLE_MONITORING) {
goog.Disposable.instances_[goog.getUid(this)] = this;
}
};
/**
* @define {boolean} Whether to enable the monitoring of the goog.Disposable
* instances. Switching on the monitoring is only recommended for debugging
* because it has a significant impact on performance and memory usage.
* If switched off, the monitoring code compiles down to 0 bytes.
* The monitoring expects that all disposable objects call the
* {@code goog.Disposable} base constructor.
*/
goog.Disposable.ENABLE_MONITORING = _FALSE;
/**
* Maps the unique ID of every undisposed {@code goog.Disposable} object to
* the object itself.
* @type {!Object.}
* @private
*/
goog.Disposable.instances_ = {};
/**
* Whether the object has been disposed of.
* @type {boolean}
* @private
*/
goog.Disposable.prototype.disposed_ = _FALSE;
/**
* @return {boolean} Whether the object has been disposed of.
*/
goog.Disposable.prototype.isDisposed = function() {
return this.disposed_;
};
/**
* @return {boolean} Whether the object has been disposed of.
* @deprecated Use {@link #isDisposed} instead.
*/
goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
/**
* Disposes of the object. If the object hasn't already been disposed of, calls
* {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
* override {@link #disposeInternal} in order to delete references to COM
* objects, DOM nodes, and other disposable objects.
*
* @return {void} Nothing.
*/
goog.Disposable.prototype.dispose = function() {
if (!this.disposed_) {
// Set disposed_ to true first, in case during the chain of disposal this
// gets disposed recursively.
this.disposed_ = _TRUE;
this.disposeInternal();
if (goog.Disposable.ENABLE_MONITORING) {
var uid = goog.getUid(this);
if (!goog.Disposable.instances_.hasOwnProperty(uid)) {
throw Error(this + ' did not call the goog.Disposable base ' +
'constructor or was disposed of after a clearUndisposedObjects ' +
'call');
}
delete goog.Disposable.instances_[uid];
}
}
};
/**
* Deletes or nulls out any references to COM objects, DOM nodes, or other
* disposable objects. Classes that extend {@code goog.Disposable} should
* override this method. For example:
*
* mypackage.MyClass = function() {
* goog.Disposable.call(this);
* // Constructor logic specific to MyClass.
* ...
* };
* goog.inherits(mypackage.MyClass, goog.Disposable);
*
* mypackage.MyClass.prototype.disposeInternal = function() {
* mypackage.MyClass.superClass_.disposeInternal.call(this);
* // Dispose logic specific to MyClass.
* ...
* };
*
* @protected
*/
goog.Disposable.prototype.disposeInternal = function() {
// No-op in the base class.
};
/**
* Calls {@code dispose} on the argument if it supports it. If obj is not an
* object with a dispose() method, this is a no-op.
* @param {*} obj The object to dispose of.
*/
goog.dispose = function(obj) {
if (obj && typeof obj.dispose == 'function') {
obj.dispose();
}
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A generic interface for saving and restoring ranges.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.SavedRange');
//goog.require('goog.Disposable');
/**
* Abstract interface for a saved range.
* @constructor
* @extends {goog.Disposable}
*/
goog.dom.SavedRange = function() {
goog.Disposable.call(this);
};
goog.inherits(goog.dom.SavedRange, goog.Disposable);
/**
* Restores the range and by default disposes of the saved copy. Take note:
* this means the by default SavedRange objects are single use objects.
* @param {boolean=} opt_stayAlive Whether this SavedRange should stay alive
* (not be disposed) after restoring the range. Defaults to _FALSE (dispose).
* @return {goog.dom.AbstractRange} The restored range.
*/
goog.dom.SavedRange.prototype.restore = function(opt_stayAlive) {
var range = this.restoreInternal();
if (!opt_stayAlive) {
this.dispose();
}
return range;
};
/**
* Internal method to restore the saved range.
* @return {goog.dom.AbstractRange} The restored range.
*/
goog.dom.SavedRange.prototype.restoreInternal = goog.abstractMethod;
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An API for saving and restoring ranges as HTML carets.
*
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.dom.SavedCaretRange');
//goog.require('goog.array');
//goog.require('goog.dom');
//goog.require('goog.dom.SavedRange');
//goog.require('goog.dom.TagName');
//goog.require('goog.string');
/**
* A struct for holding context about saved selections.
* This can be used to preserve the selection and restore while the DOM is
* manipulated, or through an asynchronous call. Use goog.dom.Range factory
* methods to obtain an {@see goog.dom.AbstractRange} instance, and use
* {@see goog.dom.AbstractRange#saveUsingCarets} to obtain a SavedCaretRange.
* For editor ranges under content-editable elements or design-mode iframes,
* prefer using {@see goog.editor.range.saveUsingNormalizedCarets}.
* @param {goog.dom.AbstractRange} range The range being saved.
* @constructor
* @extends {goog.dom.SavedRange}
*/
goog.dom.SavedCaretRange = function(range) {
goog.dom.SavedRange.call(this);
/**
* The DOM id of the caret at the start of the range.
* @type {string}
* @private
*/
this.startCaretId_ = goog.string.createUniqueString();
/**
* The DOM id of the caret at the end of the range.
* @type {string}
* @private
*/
this.endCaretId_ = goog.string.createUniqueString();
/**
* A DOM helper for storing the current document context.
* @type {goog.dom.DomHelper}
* @private
*/
this.dom_ = goog.dom.getDomHelper(range.getDocument());
range.surroundWithNodes(this.createCaret_(_TRUE), this.createCaret_(_FALSE));
this.removeIncidentalTextNode_();
};
goog.inherits(goog.dom.SavedCaretRange, goog.dom.SavedRange);
/**
* Gets carets.
* @param {boolean} start If true, returns the start caret. Otherwise, get the
* end caret.
* @return {Element} The start or end caret in the given document.
*/
goog.dom.SavedCaretRange.prototype.getCaret = function(start) {
return this.dom_.getElement(start ? this.startCaretId_ : this.endCaretId_);
};
goog.dom.SavedCaretRange.prototype.removeIncidentalTextNode_ = function() {
var startCaret = this.getCaret(_TRUE);
this.removeIfEmptyText_(startCaret.previousSibling);
this.removeIfEmptyText_(startCaret.nextSibling);
var endCaret = this.getCaret(_FALSE);
this.removeIfEmptyText_(endCaret.previousSibling);
this.removeIfEmptyText_(endCaret.nextSibling);
};
goog.dom.SavedCaretRange.prototype.removeIfEmptyText_ = function(node) {
if (node && node.nodeType === goog.dom.NodeType.TEXT && !node.nodeValue) {
goog.dom.removeNode(node);
}
}
/**
* Removes the carets from the current restoration document.
* @param {goog.dom.AbstractRange=} opt_range A range whose offsets have already
* been adjusted for caret removal; it will be adjusted if it is also
* affected by post-removal operations, such as text node normalization.
* @return {goog.dom.AbstractRange|undefined} The adjusted range, if opt_range
* was provided.
*/
goog.dom.SavedCaretRange.prototype.removeCarets = function(opt_range) {
goog.dom.removeNode(this.getCaret(_TRUE));
goog.dom.removeNode(this.getCaret(_FALSE));
return opt_range;
};
/**
* Reconstruct the selection from the given saved range. Removes carets after
* restoring the selection. If restore does not dispose this saved range, it may
* only be restored a second time if innerHTML or some other mechanism is used
* to restore the carets to the dom.
* @return {goog.dom.AbstractRange?} Restored selection.
* @override
* @protected
*/
goog.dom.SavedCaretRange.prototype.restoreInternal = function() {
var range = _NULL;
var startCaret = this.getCaret(_TRUE);
var endCaret = this.getCaret(_FALSE);
if (startCaret && endCaret) {
var startNode = startCaret.parentNode;
var startOffset = goog.array.indexOf(startNode.childNodes, startCaret);
var endNode = endCaret.parentNode;
var endOffset = goog.array.indexOf(endNode.childNodes, endCaret);
range = goog.dom.Range.createFromNodes(startNode, startOffset + 1,
endNode, endOffset);
range.select();
}
return range;
};
//goog.dom.SavedCaretRange.prototype.restoreInternal = function() {
// var range = null;
// var startCaret = this.getCaret(true);
// var endCaret = this.getCaret(false);
// if (startCaret && endCaret) {
// var startNode = startCaret.parentNode;
// var startOffset = goog.array.indexOf(startNode.childNodes, startCaret);
// var endNode = endCaret.parentNode;
// var endOffset = goog.array.indexOf(endNode.childNodes, endCaret);
// if (endNode == startNode) {
// // Compensate for the start caret being removed.
// endOffset -= 1;
// }
// range = goog.dom.Range.createFromNodes(startNode, startOffset,
// endNode, endOffset);
// range = this.removeCarets(range);
//// range.select();
// } else {
// // If only one caret was found, remove it.
// this.removeCarets();
// }
// return range;
//};
/**
* Dispose the saved range and remove the carets from the DOM.
* @override
* @protected
*/
goog.dom.SavedCaretRange.prototype.disposeInternal = function() {
this.removeCarets();
this.dom_ = _NULL;
};
/**
* Creates a caret element.
* @param {boolean} start If true, creates the start caret. Otherwise,
* creates the end caret.
* @return {Element} The new caret element.
* @private
*/
goog.dom.SavedCaretRange.prototype.createCaret_ = function(start) {
return this.dom_.createDom(goog.dom.TagName.SPAN,
{'id': start ? this.startCaretId_ : this.endCaretId_});
};
/**
* A regex that will match all saved range carets in a string.
* @type {RegExp}
*/
goog.dom.SavedCaretRange.CARET_REGEX = /<\/span>/ig;
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Interface definitions for working with ranges
* in HTML documents.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.AbstractRange');
goog.provide('goog.dom.RangeIterator');
goog.provide('goog.dom.RangeType');
//goog.require('goog.dom');
//goog.require('goog.dom.NodeType');
//goog.require('goog.dom.SavedCaretRange');
//goog.require('goog.dom.TagIterator');
//goog.require('goog.userAgent');
/**
* Types of ranges.
* @enum {string}
*/
goog.dom.RangeType = {
TEXT: 'text',
CONTROL: 'control',
MULTI: 'mutli'
};
/**
* Creates a new selection with no properties. Do not use this constructor -
* use one of the goog.dom.Range.from* methods instead.
* @constructor
*/
goog.dom.AbstractRange = function() {
};
/**
* Gets the browser native selection object from the given window.
* @param {Window} win The window to get the selection object from.
* @return {Object} The browser native selection object, or null if it could
* not be retrieved.
*/
goog.dom.AbstractRange.getBrowserSelectionForWindow = function(win) {
if (win.getSelection) {
// W3C
return win.getSelection();
} else {
// IE
var doc = win.document;
var sel = doc.selection;
if (sel) {
// IE has a bug where it sometimes returns a selection from the wrong
// document. Catching these cases now helps us avoid problems later.
try {
var range = sel.createRange();
// Only TextRanges have a parentElement method.
if (range.parentElement) {
if (range.parentElement().document != doc) {
return _NULL;
}
} else if (!range.length || range.item(0).document != doc) {
// For ControlRanges, check that the range has items, and that
// the first item in the range is in the correct document.
return _NULL;
}
} catch (e) {
// If the selection is in the wrong document, and the wrong document is
// in a different domain, IE will throw an exception.
return _NULL;
}
// TODO(user|robbyw) Sometimes IE 6 returns a selection instance
// when there is no selection. This object has a 'type' property equals
// to 'None' and a typeDetail property bound to undefined. Ideally this
// function should not return this instance.
return sel;
}
return _NULL;
}
};
/**
* Tests if the given Object is a controlRange.
* @param {Object} range The range object to test.
* @return {boolean} Whether the given Object is a controlRange.
*/
goog.dom.AbstractRange.isNativeControlRange = function(range) {
// For now, tests for presence of a control range function.
return !!range && !!range.addElement;
};
/**
* @return {goog.dom.AbstractRange} A clone of this range.
*/
goog.dom.AbstractRange.prototype.clone = goog.abstractMethod;
/**
* @return {goog.dom.RangeType} The type of range represented by this object.
*/
goog.dom.AbstractRange.prototype.getType = goog.abstractMethod;
/**
* @return {Range|TextRange} The native browser range object.
*/
goog.dom.AbstractRange.prototype.getBrowserRangeObject = goog.abstractMethod;
/**
* Sets the native browser range object, overwriting any state this range was
* storing.
* @param {Range|TextRange} nativeRange The native browser range object.
* @return {boolean} Whether the given range was accepted. If not, the caller
* will need to call goog.dom.Range.createFromBrowserRange to create a new
* range object.
*/
goog.dom.AbstractRange.prototype.setBrowserRangeObject = function(nativeRange) {
return _FALSE;
};
/**
* @return {number} The number of text ranges in this range.
*/
goog.dom.AbstractRange.prototype.getTextRangeCount = goog.abstractMethod;
/**
* Get the i-th text range in this range. The behavior is undefined if
* i >= getTextRangeCount or i < 0.
* @param {number} i The range number to retrieve.
* @return {goog.dom.TextRange} The i-th text range.
*/
goog.dom.AbstractRange.prototype.getTextRange = goog.abstractMethod;
/**
* Gets an array of all text ranges this range is comprised of. For non-multi
* ranges, returns a single element array containing this.
* @return {Array.} Array of text ranges.
*/
goog.dom.AbstractRange.prototype.getTextRanges = function() {
var output = [];
for (var i = 0, len = this.getTextRangeCount(); i < len; i++) {
output.push(this.getTextRange(i));
}
return output;
};
/**
* @return {Node} The deepest node that contains the entire range.
*/
goog.dom.AbstractRange.prototype.getContainer = goog.abstractMethod;
/**
* @return {Node} The element or text node the range starts in. For text
* ranges, the range comprises all text between the start and end position.
* For other types of range, start and end give bounds of the range but
* do not imply all nodes in those bounds are selected.
*/
goog.dom.AbstractRange.prototype.getStartNode = goog.abstractMethod;
/**
* @return {number} The offset into the node the range starts in. For text
* nodes, this is an offset into the node value. For elements, this is
* an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getStartOffset = goog.abstractMethod;
/**
* @return {Node} The element or text node the range ends in.
*/
goog.dom.AbstractRange.prototype.getEndNode = goog.abstractMethod;
/**
* @return {number} The offset into the node the range ends in. For text
* nodes, this is an offset into the node value. For elements, this is
* an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getEndOffset = goog.abstractMethod;
/**
* @return {Node} The element or text node the range is anchored at.
*/
goog.dom.AbstractRange.prototype.getAnchorNode = function() {
return this.isReversed() ? this.getEndNode() : this.getStartNode();
};
/**
* @return {number} The offset into the node the range is anchored at. For
* text nodes, this is an offset into the node value. For elements, this
* is an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getAnchorOffset = function() {
return this.isReversed() ? this.getEndOffset() : this.getStartOffset();
};
/**
* @return {Node} The element or text node the range is focused at - i.e. where
* the cursor is.
*/
goog.dom.AbstractRange.prototype.getFocusNode = function() {
return this.isReversed() ? this.getStartNode() : this.getEndNode();
};
/**
* @return {number} The offset into the node the range is focused at - i.e.
* where the cursor is. For text nodes, this is an offset into the node
* value. For elements, this is an offset into the childNodes array.
*/
goog.dom.AbstractRange.prototype.getFocusOffset = function() {
return this.isReversed() ? this.getStartOffset() : this.getEndOffset();
};
/**
* @return {boolean} Whether the selection is reversed.
*/
goog.dom.AbstractRange.prototype.isReversed = function() {
return _FALSE;
};
/**
* @return {Document} The document this selection is a part of.
*/
goog.dom.AbstractRange.prototype.getDocument = function() {
// Using start node in IE was crashing the browser in some cases so use
// getContainer for that browser. It's also faster for IE, but still slower
// than start node for other browsers so we continue to use getStartNode when
// it is not problematic. See bug 1687309.
return goog.dom.getOwnerDocument(goog.userAgent.IE ?
this.getContainer() : this.getStartNode());
};
/**
* @return {Window} The window this selection is a part of.
*/
goog.dom.AbstractRange.prototype.getWindow = function() {
return goog.dom.getWindow(this.getDocument());
};
/**
* Tests if this range contains the given range.
* @param {goog.dom.AbstractRange} range The range to test.
* @param {boolean=} opt_allowPartial If true, the range can be partially
* contained in the selection, otherwise the range must be entirely
* contained.
* @return {boolean} Whether this range contains the given range.
*/
goog.dom.AbstractRange.prototype.containsRange = goog.abstractMethod;
/**
* Tests if this range contains the given node.
* @param {Node} node The node to test for.
* @param {boolean=} opt_allowPartial If not set or _FALSE, the node must be
* entirely contained in the selection for this function to return true.
* @return {boolean} Whether this range contains the given node.
*/
goog.dom.AbstractRange.prototype.containsNode = function(node,
opt_allowPartial) {
return this.containsRange(goog.dom.Range.createFromNodeContents(node),
opt_allowPartial);
};
/**
* Tests whether this range is valid (i.e. whether its endpoints are still in
* the document). A range becomes invalid when, after this object was created,
* either one or both of its endpoints are removed from the document. Use of
* an invalid range can lead to runtime errors, particularly in IE.
* @return {boolean} Whether the range is valid.
*/
goog.dom.AbstractRange.prototype.isRangeInDocument = goog.abstractMethod;
/**
* @return {boolean} Whether the range is collapsed.
*/
goog.dom.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
/**
* @return {string} The text content of the range.
*/
goog.dom.AbstractRange.prototype.getText = goog.abstractMethod;
/**
* Returns the HTML fragment this range selects. This is slow on all browsers.
* The HTML fragment may not be valid HTML, for instance if the user selects
* from a to b inclusively in the following html:
*
* >div<a>/div<b
*
* This method will return
*
* a</div>b
*
* If you need valid HTML, use {@link #getValidHtml} instead.
*
* @return {string} HTML fragment of the range, does not include context
* containing elements.
*/
goog.dom.AbstractRange.prototype.getHtmlFragment = goog.abstractMethod;
/**
* Returns valid HTML for this range. This is fast on IE, and semi-fast on
* other browsers.
* @return {string} Valid HTML of the range, including context containing
* elements.
*/
goog.dom.AbstractRange.prototype.getValidHtml = goog.abstractMethod;
/**
* Returns pastable HTML for this range. This guarantees that any child items
* that must have specific ancestors will have them, for instance all TDs will
* be contained in a TR in a TBODY in a TABLE and all LIs will be contained in
* a UL or OL as appropriate. This is semi-fast on all browsers.
* @return {string} Pastable HTML of the range, including context containing
* elements.
*/
goog.dom.AbstractRange.prototype.getPastableHtml = goog.abstractMethod;
/**
* Returns a RangeIterator over the contents of the range. Regardless of the
* direction of the range, the iterator will move in document order.
* @param {boolean=} opt_keys Unused for this iterator.
* @return {goog.dom.RangeIterator} An iterator over tags in the range.
*/
goog.dom.AbstractRange.prototype.__iterator__ = goog.abstractMethod;
// RANGE ACTIONS
/**
* Sets this range as the selection in its window.
*/
goog.dom.AbstractRange.prototype.select = goog.abstractMethod;
/**
* Removes the contents of the range from the document.
*/
goog.dom.AbstractRange.prototype.removeContents = goog.abstractMethod;
/**
* Inserts a node before (or after) the range. The range may be disrupted
* beyond recovery because of the way this splits nodes.
* @param {Node} node The node to insert.
* @param {boolean} before True to insert before, _FALSE to insert after.
* @return {Node} The node added to the document. This may be different
* than the node parameter because on IE we have to clone it.
*/
goog.dom.AbstractRange.prototype.insertNode = goog.abstractMethod;
/**
* Surrounds this range with the two given nodes. The range may be disrupted
* beyond recovery because of the way this splits nodes.
* @param {Element} startNode The node to insert at the start.
* @param {Element} endNode The node to insert at the end.
*/
goog.dom.AbstractRange.prototype.surroundWithNodes = goog.abstractMethod;
// SAVE/RESTORE
/**
* Saves the range so that if the start and end nodes are left alone, it can
* be restored.
* @return {goog.dom.SavedRange} A range representation that can be restored
* as long as the endpoint nodes of the selection are not modified.
*/
goog.dom.AbstractRange.prototype.saveUsingDom = goog.abstractMethod;
/**
* Saves the range using HTML carets. As long as the carets remained in the
* HTML, the range can be restored...even when the HTML is copied across
* documents.
* @return {goog.dom.SavedCaretRange?} A range representation that can be
* restored as long as carets are not removed. Returns null if carets
* could not be created.
*/
goog.dom.AbstractRange.prototype.saveUsingCarets = function() {
return (this.getStartNode() && this.getEndNode()) ?
new goog.dom.SavedCaretRange(this) : _NULL;
};
// RANGE MODIFICATION
/**
* Collapses the range to one of its boundary points.
* @param {boolean} toAnchor Whether to collapse to the anchor of the range.
*/
goog.dom.AbstractRange.prototype.collapse = goog.abstractMethod;
// RANGE ITERATION
/**
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
* adds functions to determine the portion of each text node that is selected.
* @param {Node} node The node to start traversal at. When null, creates an
* empty iterator.
* @param {boolean=} opt_reverse Whether to traverse nodes in reverse.
* @constructor
* @extends {goog.dom.TagIterator}
*/
goog.dom.RangeIterator = function(node, opt_reverse) {
goog.dom.TagIterator.call(this, node, opt_reverse, _TRUE);
};
goog.inherits(goog.dom.RangeIterator, goog.dom.TagIterator);
/**
* @return {number} The offset into the current node, or -1 if the current node
* is not a text node.
*/
goog.dom.RangeIterator.prototype.getStartTextOffset = goog.abstractMethod;
/**
* @return {number} The end offset into the current node, or -1 if the current
* node is not a text node.
*/
goog.dom.RangeIterator.prototype.getEndTextOffset = goog.abstractMethod;
/**
* @return {Node} node The iterator's start node.
*/
goog.dom.RangeIterator.prototype.getStartNode = goog.abstractMethod;
/**
* @return {Node} The iterator's end node.
*/
goog.dom.RangeIterator.prototype.getEndNode = goog.abstractMethod;
/**
* @return {boolean} Whether a call to next will fail.
*/
goog.dom.RangeIterator.prototype.isLast = goog.abstractMethod;
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for working with ranges comprised of multiple
* sub-ranges.
*
* @author robbyw@google.com (Robby Walker)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.AbstractMultiRange');
//goog.require('goog.array');
//goog.require('goog.dom');
//goog.require('goog.dom.AbstractRange');
/**
* Creates a new multi range with no properties. Do not use this
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
* @constructor
* @extends {goog.dom.AbstractRange}
*/
goog.dom.AbstractMultiRange = function() {
};
goog.inherits(goog.dom.AbstractMultiRange, goog.dom.AbstractRange);
/** @inheritDoc */
goog.dom.AbstractMultiRange.prototype.containsRange = function(
otherRange, opt_allowPartial) {
// TODO(user): This will incorrectly return _FALSE if two (or more) adjacent
// elements are both in the control range, and are also in the text range
// being compared to.
var ranges = this.getTextRanges();
var otherRanges = otherRange.getTextRanges();
var fn = opt_allowPartial ? goog.array.some : goog.array.every;
return fn(otherRanges, function(otherRange) {
return goog.array.some(ranges, function(range) {
return range.containsRange(otherRange, opt_allowPartial);
});
});
};
/** @inheritDoc */
goog.dom.AbstractMultiRange.prototype.insertNode = function(node, before) {
if (before) {
goog.dom.insertSiblingBefore(node, this.getStartNode());
} else {
goog.dom.insertSiblingAfter(node, this.getEndNode());
}
return node;
};
/** @inheritDoc */
goog.dom.AbstractMultiRange.prototype.surroundWithNodes = function(startNode,
endNode) {
this.insertNode(startNode, _TRUE);
this.insertNode(endNode, _FALSE);
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Iterator between two DOM text range positions.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.TextRangeIterator');
//goog.require('goog.array');
//goog.require('goog.dom.NodeType');
//goog.require('goog.dom.RangeIterator');
//goog.require('goog.dom.TagName');
//goog.require('goog.iter.StopIteration');
/**
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
* adds functions to determine the portion of each text node that is selected.
*
* @param {Node} startNode The starting node position.
* @param {number} startOffset The offset in to startNode. If startNode is
* an element, indicates an offset in to childNodes. If startNode is a
* text node, indicates an offset in to nodeValue.
* @param {Node} endNode The ending node position.
* @param {number} endOffset The offset in to endNode. If endNode is
* an element, indicates an offset in to childNodes. If endNode is a
* text node, indicates an offset in to nodeValue.
* @param {boolean=} opt_reverse Whether to traverse nodes in reverse.
* @constructor
* @extends {goog.dom.RangeIterator}
*/
goog.dom.TextRangeIterator = function(startNode, startOffset, endNode,
endOffset, opt_reverse) {
var goNext;
if (startNode) {
this.startNode_ = startNode;
this.startOffset_ = startOffset;
this.endNode_ = endNode;
this.endOffset_ = endOffset;
// Skip to the offset nodes - being careful to special case BRs since these
// have no children but still can appear as the startContainer of a range.
if (startNode.nodeType == goog.dom.NodeType.ELEMENT &&
startNode.tagName != goog.dom.TagName.BR) {
var startChildren = startNode.childNodes;
var candidate = startChildren[startOffset];
if (candidate) {
this.startNode_ = candidate;
this.startOffset_ = 0;
} else {
if (startChildren.length) {
this.startNode_ =
/** @type {Node} */ (goog.array.peek(startChildren));
}
goNext = _TRUE;
}
}
if (endNode.nodeType == goog.dom.NodeType.ELEMENT) {
this.endNode_ = endNode.childNodes[endOffset];
if (this.endNode_) {
this.endOffset_ = 0;
} else {
// The offset was past the last element.
this.endNode_ = endNode;
}
}
}
goog.dom.RangeIterator.call(this, opt_reverse ? this.endNode_ :
this.startNode_, opt_reverse);
if (goNext) {
try {
this.next();
} catch (e) {
if (e != goog.iter.StopIteration) {
throw e;
}
}
}
};
goog.inherits(goog.dom.TextRangeIterator, goog.dom.RangeIterator);
/**
* The first node in the selection.
* @type {Node}
* @private
*/
goog.dom.TextRangeIterator.prototype.startNode_ = _NULL;
/**
* The last node in the selection.
* @type {Node}
* @private
*/
goog.dom.TextRangeIterator.prototype.endNode_ = _NULL;
/**
* The offset within the first node in the selection.
* @type {number}
* @private
*/
goog.dom.TextRangeIterator.prototype.startOffset_ = 0;
/**
* The offset within the last node in the selection.
* @type {number}
* @private
*/
goog.dom.TextRangeIterator.prototype.endOffset_ = 0;
/** @inheritDoc */
goog.dom.TextRangeIterator.prototype.getStartTextOffset = function() {
// Offsets only apply to text nodes. If our current node is the start node,
// return the saved offset. Otherwise, return 0.
return this.node.nodeType != goog.dom.NodeType.TEXT ? -1 :
this.node == this.startNode_ ? this.startOffset_ : 0;
};
/** @inheritDoc */
goog.dom.TextRangeIterator.prototype.getEndTextOffset = function() {
// Offsets only apply to text nodes. If our current node is the end node,
// return the saved offset. Otherwise, return the length of the node.
return this.node.nodeType != goog.dom.NodeType.TEXT ? -1 :
this.node == this.endNode_ ? this.endOffset_ : this.node.nodeValue.length;
};
/** @inheritDoc */
goog.dom.TextRangeIterator.prototype.getStartNode = function() {
return this.startNode_;
};
/**
* Change the start node of the iterator.
* @param {Node} node The new start node.
*/
goog.dom.TextRangeIterator.prototype.setStartNode = function(node) {
if (!this.isStarted()) {
this.setPosition(node);
}
this.startNode_ = node;
this.startOffset_ = 0;
};
/** @inheritDoc */
goog.dom.TextRangeIterator.prototype.getEndNode = function() {
return this.endNode_;
};
/**
* Change the end node of the iterator.
* @param {Node} node The new end node.
*/
goog.dom.TextRangeIterator.prototype.setEndNode = function(node) {
this.endNode_ = node;
this.endOffset_ = 0;
};
/** @inheritDoc */
goog.dom.TextRangeIterator.prototype.isLast = function() {
return this.isStarted() && this.node == this.endNode_ &&
(!this.endOffset_ || !this.isStartTag());
};
/**
* Move to the next position in the selection.
* Throws {@code goog.iter.StopIteration} when it passes the end of the range.
* @return {Node} The node at the next position.
*/
goog.dom.TextRangeIterator.prototype.next = function() {
if (this.isLast()) {
throw goog.iter.StopIteration;
}
// Call the super function.
return goog.dom.TextRangeIterator.superClass_.next.call(this);
};
/** @inheritDoc */
goog.dom.TextRangeIterator.prototype.skipTag = function() {
goog.dom.TextRangeIterator.superClass_.skipTag.apply(this);
// If the node we are skipping contains the end node, we just skipped past
// the end, so we stop the iteration.
if (goog.dom.contains(this.node, this.endNode_)) {
throw goog.iter.StopIteration;
}
};
/**
* Replace this iterator's values with values from another.
* @param {goog.dom.TextRangeIterator} other The iterator to copy.
* @protected
*/
goog.dom.TextRangeIterator.prototype.copyFrom = function(other) {
this.startNode_ = other.startNode_;
this.endNode_ = other.endNode_;
this.startOffset_ = other.startOffset_;
this.endOffset_ = other.endOffset_;
this.isReversed_ = other.isReversed_;
goog.dom.TextRangeIterator.superClass_.copyFrom.call(this, other);
};
/**
* @return {goog.dom.TextRangeIterator} An identical iterator.
*/
goog.dom.TextRangeIterator.prototype.clone = function() {
var copy = new goog.dom.TextRangeIterator(this.startNode_,
this.startOffset_, this.endNode_, this.endOffset_, this.isReversed_);
copy.copyFrom(this);
return copy;
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Detection of JScript version.
*
*/
goog.provide('goog.userAgent.jscript');
//goog.require('goog.string');
/**
* @define {boolean} True if it is known at compile time that the runtime
* environment will not be using JScript.
*/
goog.userAgent.jscript.ASSUME_NO_JSCRIPT = _FALSE;
/**
* Initializer for goog.userAgent.jscript. Detects if the user agent is using
* Microsoft JScript and which version of it.
*
* This is a named function so that it can be stripped via the jscompiler
* option for stripping types.
* @private
*/
goog.userAgent.jscript.init_ = function() {
var hasScriptEngine = 'ScriptEngine' in goog.global;
/**
* @type {boolean}
* @private
*/
goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_ =
hasScriptEngine && goog.global['ScriptEngine']() == 'JScript';
/**
* @type {string}
* @private
*/
goog.userAgent.jscript.DETECTED_VERSION_ =
goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_ ?
(goog.global['ScriptEngineMajorVersion']() + '.' +
goog.global['ScriptEngineMinorVersion']() + '.' +
goog.global['ScriptEngineBuildVersion']()) :
'0';
};
if (!goog.userAgent.jscript.ASSUME_NO_JSCRIPT) {
goog.userAgent.jscript.init_();
}
/**
* Whether we detect that the user agent is using Microsoft JScript.
* @type {boolean}
*/
goog.userAgent.jscript.HAS_JSCRIPT = goog.userAgent.jscript.ASSUME_NO_JSCRIPT ?
_FALSE : goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_;
/**
* The installed version of JScript.
* @type {string}
*/
goog.userAgent.jscript.VERSION = goog.userAgent.jscript.ASSUME_NO_JSCRIPT ?
'0' : goog.userAgent.jscript.DETECTED_VERSION_;
/**
* Whether the installed version of JScript is as new or newer than a given
* version.
* @param {string} version The version to check.
* @return {boolean} Whether the installed version of JScript is as new or
* newer than the given version.
*/
goog.userAgent.jscript.isVersion = function(version) {
return goog.string.compareVersions(goog.userAgent.jscript.VERSION,
version) >= 0;
};
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utility for fast string concatenation.
*/
goog.provide('goog.string.StringBuffer');
//goog.require('goog.userAgent.jscript');
/**
* Utility class to facilitate much faster string concatenation in IE,
* using Array.join() rather than the '+' operator. For other browsers
* we simply use the '+' operator.
*
* @param {Object|number|string|boolean=} opt_a1 Optional first initial item
* to append.
* @param {...Object|number|string|boolean} var_args Other initial items to
* append, e.g., new goog.string.StringBuffer('foo', 'bar').
* @constructor
*/
goog.string.StringBuffer = function(opt_a1, var_args) {
/**
* Internal buffer for the string to be concatenated.
* @type {string|Array}
* @private
*/
this.buffer_ = goog.userAgent.jscript.HAS_JSCRIPT ? [] : '';
if (opt_a1 != _NULL) {
this.append.apply(this, arguments);
}
};
/**
* Sets the contents of the string buffer object, replacing what's currently
* there.
*
* @param {string} s String to set.
*/
goog.string.StringBuffer.prototype.set = function(s) {
this.clear();
this.append(s);
};
if (goog.userAgent.jscript.HAS_JSCRIPT) {
/**
* Length of internal buffer (faster than calling buffer_.length).
* Only used if buffer_ is an array.
* @type {number}
* @private
*/
goog.string.StringBuffer.prototype.bufferLength_ = 0;
/**
* Appends one or more items to the buffer.
*
* Calling this with null, undefined, or empty arguments is an error.
*
* @param {Object|number|string|boolean} a1 Required first string.
* @param {Object|number|string|boolean=} opt_a2 Optional second string.
* @param {...Object|number|string|boolean} var_args Other items to append,
* e.g., sb.append('foo', 'bar', 'baz').
* @return {goog.string.StringBuffer} This same StringBuffer object.
*/
goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
// IE version.
if (opt_a2 == _NULL) { // second argument is undefined (null == undefined)
// Array assignment is 2x faster than Array push. Also, use a1
// directly to avoid arguments instantiation, another 2x improvement.
this.buffer_[this.bufferLength_++] = a1;
} else {
this.buffer_.push.apply(/** @type {Array} */ (this.buffer_), arguments);
this.bufferLength_ = this.buffer_.length;
}
return this;
};
} else {
/**
* Appends one or more items to the buffer.
*
* Calling this with null, undefined, or empty arguments is an error.
*
* @param {Object|number|string|boolean} a1 Required first string.
* @param {Object|number|string|boolean=} opt_a2 Optional second string.
* @param {...Object|number|string|boolean} var_args Other items to append,
* e.g., sb.append('foo', 'bar', 'baz').
* @return {goog.string.StringBuffer} This same StringBuffer object.
* @suppress {duplicate}
*/
goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
// W3 version.
// Use a1 directly to avoid arguments instantiation for single-arg case.
this.buffer_ += a1;
if (opt_a2 != _NULL) { // second argument is undefined (null == undefined)
for (var i = 1; i < arguments.length; i++) {
this.buffer_ += arguments[i];
}
}
return this;
};
}
/**
* Clears the internal buffer.
*/
goog.string.StringBuffer.prototype.clear = function() {
if (goog.userAgent.jscript.HAS_JSCRIPT) {
this.buffer_.length = 0; // Reuse the array to avoid creating new object.
this.bufferLength_ = 0;
} else {
this.buffer_ = '';
}
};
/**
* Returns the length of the current contents of the buffer. In IE, this is
* O(n) where n = number of appends, so to avoid quadratic behavior, do not call
* this after every append.
*
* @return {number} the length of the current contents of the buffer.
*/
goog.string.StringBuffer.prototype.getLength = function() {
return this.toString().length;
};
/**
* Returns the concatenated string.
*
* @return {string} The concatenated string.
*/
goog.string.StringBuffer.prototype.toString = function() {
if (goog.userAgent.jscript.HAS_JSCRIPT) {
var str = this.buffer_.join('');
// Given a string with the entire contents, simplify the StringBuffer by
// setting its contents to only be this string, rather than many fragments.
this.clear();
if (str) {
this.append(str);
}
return str;
} else {
return /** @type {string} */ (this.buffer_);
}
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Simple struct for endpoints of a range.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.RangeEndpoint');
/**
* Constants for selection endpoints.
* @enum {number}
*/
goog.dom.RangeEndpoint = {
START: 1,
END: 0
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the browser range interface.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
* @author robbyw@google.com (Robby Walker)
* @author ojan@google.com (Ojan Vafai)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.browserrange.AbstractRange');
//goog.require('goog.dom');
//goog.require('goog.dom.NodeType');
//goog.require('goog.dom.RangeEndpoint');
//goog.require('goog.dom.TagName');
//goog.require('goog.dom.TextRangeIterator');
//goog.require('goog.iter');
//goog.require('goog.string');
//goog.require('goog.string.StringBuffer');
//goog.require('goog.userAgent');
/**
* The constructor for abstract ranges. Don't call this from subclasses.
* @constructor
*/
goog.dom.browserrange.AbstractRange = function() {
};
/**
* @return {goog.dom.browserrange.AbstractRange} A clone of this range.
*/
goog.dom.browserrange.AbstractRange.prototype.clone = goog.abstractMethod;
/**
* Returns the browser native implementation of the range. Please refrain from
* using this function - if you find you need the range please add wrappers for
* the functionality you need rather than just using the native range.
* @return {Range|TextRange} The browser native range object.
*/
goog.dom.browserrange.AbstractRange.prototype.getBrowserRange =
goog.abstractMethod;
/**
* Returns the deepest node in the tree that contains the entire range.
* @return {Node} The deepest node that contains the entire range.
*/
goog.dom.browserrange.AbstractRange.prototype.getContainer =
goog.abstractMethod;
/**
* Returns the node the range starts in.
* @return {Node} The element or text node the range starts in.
*/
goog.dom.browserrange.AbstractRange.prototype.getStartNode =
goog.abstractMethod;
/**
* Returns the offset into the node the range starts in.
* @return {number} The offset into the node the range starts in. For text
* nodes, this is an offset into the node value. For elements, this is
* an offset into the childNodes array.
*/
goog.dom.browserrange.AbstractRange.prototype.getStartOffset =
goog.abstractMethod;
/**
* Returns the node the range ends in.
* @return {Node} The element or text node the range ends in.
*/
goog.dom.browserrange.AbstractRange.prototype.getEndNode =
goog.abstractMethod;
/**
* Returns the offset into the node the range ends in.
* @return {number} The offset into the node the range ends in. For text
* nodes, this is an offset into the node value. For elements, this is
* an offset into the childNodes array.
*/
goog.dom.browserrange.AbstractRange.prototype.getEndOffset =
goog.abstractMethod;
/**
* Compares one endpoint of this range with the endpoint of another browser
* native range object.
* @param {Range|TextRange} range The browser native range to compare against.
* @param {goog.dom.RangeEndpoint} thisEndpoint The endpoint of this range
* to compare with.
* @param {goog.dom.RangeEndpoint} otherEndpoint The endpoint of the other
* range to compare with.
* @return {number} 0 if the endpoints are equal, negative if this range
* endpoint comes before the other range endpoint, and positive otherwise.
*/
goog.dom.browserrange.AbstractRange.prototype.compareBrowserRangeEndpoints =
goog.abstractMethod;
/**
* Tests if this range contains the given range.
* @param {goog.dom.browserrange.AbstractRange} abstractRange The range to test.
* @param {boolean=} opt_allowPartial If not set or _FALSE, the range must be
* entirely contained in the selection for this function to return true.
* @return {boolean} Whether this range contains the given range.
*/
goog.dom.browserrange.AbstractRange.prototype.containsRange =
function(abstractRange, opt_allowPartial) {
// IE sometimes misreports the boundaries for collapsed ranges. So if the
// other range is collapsed, make sure the whole range is contained. This is
// logically equivalent, and works around IE's bug.
var checkPartial = opt_allowPartial && !abstractRange.isCollapsed();
var range = abstractRange.getBrowserRange();
var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
/** {@preserveTry} */
try {
if (checkPartial) {
// There are two ways to not overlap. Being before, and being after.
// Before is represented by this.end before range.start: comparison < 0.
// After is represented by this.start after range.end: comparison > 0.
// The below is the negation of not overlapping.
return this.compareBrowserRangeEndpoints(range, end, start) >= 0 &&
this.compareBrowserRangeEndpoints(range, start, end) <= 0;
} else {
// Return true if this range bounds the parameter range from both sides.
return this.compareBrowserRangeEndpoints(range, end, end) >= 0 &&
this.compareBrowserRangeEndpoints(range, start, start) <= 0;
}
} catch (e) {
if (!goog.userAgent.IE) {
throw e;
}
// IE sometimes throws exceptions when one range is invalid, i.e. points
// to a node that has been removed from the document. Return _FALSE in this
// case.
return _FALSE;
}
};
/**
* Tests if this range contains the given node.
* @param {Node} node The node to test.
* @param {boolean=} opt_allowPartial If not set or _FALSE, the node must be
* entirely contained in the selection for this function to return true.
* @return {boolean} Whether this range contains the given node.
*/
goog.dom.browserrange.AbstractRange.prototype.containsNode = function(node,
opt_allowPartial) {
return this.containsRange(
goog.dom.browserrange.createRangeFromNodeContents(node),
opt_allowPartial);
};
/**
* Tests if the selection is collapsed - i.e. is just a caret.
* @return {boolean} Whether the range is collapsed.
*/
goog.dom.browserrange.AbstractRange.prototype.isCollapsed =
goog.abstractMethod;
/**
* @return {string} The text content of the range.
*/
goog.dom.browserrange.AbstractRange.prototype.getText =
goog.abstractMethod;
/**
* Returns the HTML fragment this range selects. This is slow on all browsers.
* @return {string} HTML fragment of the range, does not include context
* containing elements.
*/
goog.dom.browserrange.AbstractRange.prototype.getHtmlFragment = function() {
var output = new goog.string.StringBuffer();
goog.iter.forEach(this, function(node, ignore, it) {
if (node.nodeType == goog.dom.NodeType.TEXT) {
output.append(goog.string.htmlEscape(node.nodeValue.substring(
it.getStartTextOffset(), it.getEndTextOffset())));
} else if (node.nodeType == goog.dom.NodeType.ELEMENT) {
if (it.isEndTag()) {
if (goog.dom.canHaveChildren(node)) {
output.append('' + node.tagName + '>');
}
} else {
var shallow = node.cloneNode(_FALSE);
var html = goog.dom.getOuterHtml(shallow);
if (goog.userAgent.IE && node.tagName == goog.dom.TagName.LI) {
// For an LI, IE just returns "" with no closing tag
output.append(html);
} else {
var index = html.lastIndexOf('<');
output.append(index ? html.substr(0, index) : html);
}
}
}
}, this);
return output.toString();
};
/**
* Returns valid HTML for this range. This is fast on IE, and semi-fast on
* other browsers.
* @return {string} Valid HTML of the range, including context containing
* elements.
*/
goog.dom.browserrange.AbstractRange.prototype.getValidHtml =
goog.abstractMethod;
/**
* Returns a RangeIterator over the contents of the range. Regardless of the
* direction of the range, the iterator will move in document order.
* @param {boolean=} opt_keys Unused for this iterator.
* @return {goog.dom.RangeIterator} An iterator over tags in the range.
*/
goog.dom.browserrange.AbstractRange.prototype.__iterator__ = function(
opt_keys) {
return new goog.dom.TextRangeIterator(this.getStartNode(),
this.getStartOffset(), this.getEndNode(), this.getEndOffset());
};
// SELECTION MODIFICATION
/**
* Set this range as the selection in its window.
* @param {boolean=} opt_reverse Whether to select the range in reverse,
* if possible.
*/
goog.dom.browserrange.AbstractRange.prototype.select =
goog.abstractMethod;
/**
* Removes the contents of the range from the document. As a side effect, the
* selection will be collapsed. The behavior of content removal is normalized
* across browsers. For instance, IE sometimes creates extra text nodes that
* a W3C browser does not. That behavior is corrected for.
*/
goog.dom.browserrange.AbstractRange.prototype.removeContents =
goog.abstractMethod;
/**
* Surrounds the text range with the specified element (on Mozilla) or with a
* clone of the specified element (on IE). Returns a reference to the
* surrounding element if the operation was successful; returns null if the
* operation failed.
* @param {Element} element The element with which the selection is to be
* surrounded.
* @return {Element} The surrounding element (same as the argument on Mozilla,
* but not on IE), or null if unsuccessful.
*/
goog.dom.browserrange.AbstractRange.prototype.surroundContents =
goog.abstractMethod;
/**
* Inserts a node before (or after) the range. The range may be disrupted
* beyond recovery because of the way this splits nodes.
* @param {Node} node The node to insert.
* @param {boolean} before True to insert before, _FALSE to insert after.
* @return {Node} The node added to the document. This may be different
* than the node parameter because on IE we have to clone it.
*/
goog.dom.browserrange.AbstractRange.prototype.insertNode =
goog.abstractMethod;
/**
* Surrounds this range with the two given nodes. The range may be disrupted
* beyond recovery because of the way this splits nodes.
* @param {Element} startNode The node to insert at the start.
* @param {Element} endNode The node to insert at the end.
*/
goog.dom.browserrange.AbstractRange.prototype.surroundWithNodes =
goog.abstractMethod;
/**
* Collapses the range to one of its boundary points.
* @param {boolean} toStart Whether to collapse to the start of the range.
*/
goog.dom.browserrange.AbstractRange.prototype.collapse =
goog.abstractMethod;
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the W3C spec following range wrapper.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
* @author robbyw@google.com (Robby Walker)
* @author ojan@google.com (Ojan Vafai)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.browserrange.W3cRange');
//goog.require('goog.dom');
//goog.require('goog.dom.NodeType');
//goog.require('goog.dom.RangeEndpoint');
//goog.require('goog.dom.browserrange.AbstractRange');
//goog.require('goog.string');
/**
* The constructor for W3C specific browser ranges.
* @param {Range} range The range object.
* @constructor
* @extends {goog.dom.browserrange.AbstractRange}
*/
goog.dom.browserrange.W3cRange = function(range) {
this.range_ = range;
};
goog.inherits(goog.dom.browserrange.W3cRange,
goog.dom.browserrange.AbstractRange);
/**
* Returns a browser range spanning the given node's contents.
* @param {Node} node The node to select.
* @return {Range} A browser range spanning the node's contents.
* @protected
*/
goog.dom.browserrange.W3cRange.getBrowserRangeForNode = function(node) {
var nodeRange = goog.dom.getOwnerDocument(node).createRange();
if (node.nodeType == goog.dom.NodeType.TEXT) {
nodeRange.setStart(node, 0);
nodeRange.setEnd(node, node.length);
} else {
if (!goog.dom.browserrange.canContainRangeEndpoint(node)) {
var rangeParent = node.parentNode;
var rangeStartOffset = goog.array.indexOf(rangeParent.childNodes, node);
nodeRange.setStart(rangeParent, rangeStartOffset);
nodeRange.setEnd(rangeParent, rangeStartOffset + 1);
} else {
var tempNode, leaf = node;
while ((tempNode = leaf.firstChild) &&
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
leaf = tempNode;
}
nodeRange.setStart(leaf, 0);
leaf = node;
while ((tempNode = leaf.lastChild) &&
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
leaf = tempNode;
}
nodeRange.setEnd(leaf, leaf.nodeType == goog.dom.NodeType.ELEMENT ?
leaf.childNodes.length : leaf.length);
}
}
return nodeRange;
};
/**
* Returns a browser range spanning the given nodes.
* @param {Node} startNode The node to start with - should not be a BR.
* @param {number} startOffset The offset within the start node.
* @param {Node} endNode The node to end with - should not be a BR.
* @param {number} endOffset The offset within the end node.
* @return {Range} A browser range spanning the node's contents.
* @protected
*/
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes = function(startNode,
startOffset, endNode, endOffset) {
// Create and return the range.
var nodeRange = goog.dom.getOwnerDocument(startNode).createRange();
nodeRange.setStart(startNode, startOffset);
nodeRange.setEnd(endNode, endOffset);
return nodeRange;
};
/**
* Creates a range object that selects the given node's text.
* @param {Node} node The node to select.
* @return {goog.dom.browserrange.W3cRange} A Gecko range wrapper object.
*/
goog.dom.browserrange.W3cRange.createFromNodeContents = function(node) {
return new goog.dom.browserrange.W3cRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
};
/**
* Creates a range object that selects between the given nodes.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the start node.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the end node.
* @return {goog.dom.browserrange.W3cRange} A wrapper object.
*/
goog.dom.browserrange.W3cRange.createFromNodes = function(startNode,
startOffset, endNode, endOffset) {
return new goog.dom.browserrange.W3cRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
startOffset, endNode, endOffset));
};
/**
* @return {goog.dom.browserrange.W3cRange} A clone of this range.
*/
goog.dom.browserrange.W3cRange.prototype.clone = function() {
return new this.constructor(this.range_.cloneRange());
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getBrowserRange = function() {
return this.range_;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getContainer = function() {
return this.range_.commonAncestorContainer;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getStartNode = function() {
return this.range_.startContainer;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getStartOffset = function() {
return this.range_.startOffset;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getEndNode = function() {
return this.range_.endContainer;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getEndOffset = function() {
return this.range_.endOffset;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.compareBrowserRangeEndpoints =
function(range, thisEndpoint, otherEndpoint) {
return this.range_.compareBoundaryPoints(
otherEndpoint == goog.dom.RangeEndpoint.START ?
(thisEndpoint == goog.dom.RangeEndpoint.START ?
goog.global['Range'].START_TO_START :
goog.global['Range'].START_TO_END) :
(thisEndpoint == goog.dom.RangeEndpoint.START ?
goog.global['Range'].END_TO_START :
goog.global['Range'].END_TO_END),
/** @type {Range} */ (range));
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.isCollapsed = function() {
return this.range_.collapsed;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getText = function() {
return this.range_.toString();
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.getValidHtml = function() {
var div = goog.dom.getDomHelper(this.range_.startContainer).createDom('div');
div.appendChild(this.range_.cloneContents());
var result = div.innerHTML;
if (goog.string.startsWith(result, '<') ||
!this.isCollapsed() && !goog.string.contains(result, '<')) {
// We attempt to mimic IE, which returns no containing element when a
// only text nodes are selected, does return the containing element when
// the selection is empty, and does return the element when multiple nodes
// are selected.
return result;
}
var container = this.getContainer();
container = container.nodeType == goog.dom.NodeType.ELEMENT ? container :
container.parentNode;
var html = goog.dom.getOuterHtml(
/** @type {Element} */ (container.cloneNode(_FALSE)));
return html.replace('>', '>' + result);
};
// SELECTION MODIFICATION
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.select = function(reverse) {
var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
this.selectInternal(win.getSelection(), reverse);
};
/**
* Select this range.
* @param {Selection} selection Browser selection object.
* @param {*} reverse Whether to select this range in reverse.
* @protected
*/
goog.dom.browserrange.W3cRange.prototype.selectInternal = function(selection,
reverse) {
// Browser-specific tricks are needed to create reversed selections
// programatically. For this generic W3C codepath, ignore the reverse
// parameter.
selection.removeAllRanges();
selection.addRange(this.range_);
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.removeContents = function() {
var range = this.range_;
range.extractContents();
if (range.startContainer.hasChildNodes()) {
// Remove any now empty nodes surrounding the extracted contents.
var rangeStartContainer =
range.startContainer.childNodes[range.startOffset];
if (rangeStartContainer) {
var rangePrevious = rangeStartContainer.previousSibling;
if (goog.dom.getRawTextContent(rangeStartContainer) == '') {
goog.dom.removeNode(rangeStartContainer);
}
if (rangePrevious && goog.dom.getRawTextContent(rangePrevious) == '') {
goog.dom.removeNode(rangePrevious);
}
}
}
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.surroundContents = function(element) {
this.range_.surroundContents(element);
return element;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.insertNode = function(node, before) {
var range = this.range_.cloneRange();
range.collapse(before);
range.insertNode(node);
range.detach();
return node;
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.surroundWithNodes = function(
startNode, endNode) {
var win = goog.dom.getWindow(
goog.dom.getOwnerDocument(this.getStartNode()));
var selectionRange = goog.dom.Range.createFromWindow(win);
if (selectionRange) {
var sNode = selectionRange.getStartNode();
var eNode = selectionRange.getEndNode();
var sOffset = selectionRange.getStartOffset();
var eOffset = selectionRange.getEndOffset();
}
var clone1 = this.range_.cloneRange();
var clone2 = this.range_.cloneRange();
clone1.collapse(_FALSE);
clone2.collapse(_TRUE);
clone1.insertNode(endNode);
clone2.insertNode(startNode);
clone1.detach();
clone2.detach();
if (selectionRange) {
// There are 4 ways that surroundWithNodes can wreck the saved
// selection object. All of them happen when an inserted node splits
// a text node, and one of the end points of the selection was in the
// latter half of that text node.
//
// Clients of this library should use saveUsingCarets to avoid this
// problem. Unfortunately, saveUsingCarets uses this method, so that's
// not really an option for us. :( We just recompute the offsets.
var isInsertedNode = function(n) {
return n == startNode || n == endNode;
};
if (sNode.nodeType == goog.dom.NodeType.TEXT) {
while (sOffset > sNode.length) {
sOffset -= sNode.length;
do {
sNode = sNode.nextSibling;
} while (isInsertedNode(sNode));
}
}
if (eNode.nodeType == goog.dom.NodeType.TEXT) {
while (eOffset > eNode.length) {
eOffset -= eNode.length;
do {
eNode = eNode.nextSibling;
} while (isInsertedNode(eNode));
}
}
goog.dom.Range.createFromNodes(
sNode, /** @type {number} */ (sOffset),
eNode, /** @type {number} */ (eOffset)).select();
}
};
/** @inheritDoc */
goog.dom.browserrange.W3cRange.prototype.collapse = function(toStart) {
this.range_.collapse(toStart);
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the WebKit specific range wrapper. Inherits most
* functionality from W3CRange, but adds exceptions as necessary.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.browserrange.WebKitRange');
//goog.require('goog.dom.RangeEndpoint');
//goog.require('goog.dom.browserrange.W3cRange');
//goog.require('goog.userAgent');
/**
* The constructor for WebKit specific browser ranges.
* @param {Range} range The range object.
* @constructor
* @extends {goog.dom.browserrange.W3cRange}
*/
goog.dom.browserrange.WebKitRange = function(range) {
goog.dom.browserrange.W3cRange.call(this, range);
};
goog.inherits(goog.dom.browserrange.WebKitRange,
goog.dom.browserrange.W3cRange);
/**
* Creates a range object that selects the given node's text.
* @param {Node} node The node to select.
* @return {goog.dom.browserrange.WebKitRange} A WebKit range wrapper object.
*/
goog.dom.browserrange.WebKitRange.createFromNodeContents = function(node) {
return new goog.dom.browserrange.WebKitRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
};
/**
* Creates a range object that selects between the given nodes.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the start node.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the end node.
* @return {goog.dom.browserrange.WebKitRange} A wrapper object.
*/
goog.dom.browserrange.WebKitRange.createFromNodes = function(startNode,
startOffset, endNode, endOffset) {
return new goog.dom.browserrange.WebKitRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
startOffset, endNode, endOffset));
};
/** @inheritDoc */
goog.dom.browserrange.WebKitRange.prototype.compareBrowserRangeEndpoints =
function(range, thisEndpoint, otherEndpoint) {
// Webkit pre-528 has some bugs where compareBoundaryPoints() doesn't work the
// way it is supposed to, but if we reverse the sense of two comparisons,
// it works fine.
// https://bugs.webkit.org/show_bug.cgi?id=20738
if (goog.userAgent.isVersion('528')) {
return (goog.dom.browserrange.WebKitRange.superClass_.
compareBrowserRangeEndpoints.call(
this, range, thisEndpoint, otherEndpoint));
}
return this.range_.compareBoundaryPoints(
otherEndpoint == goog.dom.RangeEndpoint.START ?
(thisEndpoint == goog.dom.RangeEndpoint.START ?
goog.global['Range'].START_TO_START :
goog.global['Range'].END_TO_START) : // Sense reversed
(thisEndpoint == goog.dom.RangeEndpoint.START ?
goog.global['Range'].START_TO_END : // Sense reversed
goog.global['Range'].END_TO_END),
/** @type {Range} */ (range));
};
/** @inheritDoc */
goog.dom.browserrange.WebKitRange.prototype.selectInternal = function(
selection, reversed) {
// Unselect everything. This addresses a bug in Webkit where it sometimes
// caches the old selection.
// https://bugs.webkit.org/show_bug.cgi?id=20117
selection.removeAllRanges();
if (reversed) {
selection.setBaseAndExtent(this.getEndNode(), this.getEndOffset(),
this.getStartNode(), this.getStartOffset());
} else {
selection.setBaseAndExtent(this.getStartNode(), this.getStartOffset(),
this.getEndNode(), this.getEndOffset());
}
};
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Iterator subclass for DOM tree traversal.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.NodeIterator');
//goog.require('goog.dom.TagIterator');
/**
* A DOM tree traversal iterator.
*
* Starting with the given node, the iterator walks the DOM in order, reporting
* events for each node. The iterator acts as a prefix iterator:
*
*
* <div>1<span>2</span>3</div>
*
*
* Will return the following nodes:
*
* [div, 1, span, 2, 3]
*
* With the following depths
*
* [1, 1, 2, 2, 1]
*
* Imagining | represents iterator position, the traversal stops at
* each of the following locations:
*
* <div>|1|<span>|2|</span>3|</div>
*
* The iterator can also be used in reverse mode, which will return the nodes
* and states in the opposite order. The depths will be slightly different
* since, like in normal mode, the depth is computed *after* the last move.
*
* Lastly, it is possible to create an iterator that is unconstrained, meaning
* that it will continue iterating until the end of the document instead of
* until exiting the start node.
*
* @param {Node=} opt_node The start node. Defaults to an empty iterator.
* @param {boolean=} opt_reversed Whether to traverse the tree in reverse.
* @param {boolean=} opt_unconstrained Whether the iterator is not constrained
* to the starting node and its children.
* @param {number=} opt_depth The starting tree depth.
* @constructor
* @extends {goog.dom.TagIterator}
*/
goog.dom.NodeIterator = function(opt_node, opt_reversed,
opt_unconstrained, opt_depth) {
goog.dom.TagIterator.call(this, opt_node, opt_reversed, opt_unconstrained,
_NULL, opt_depth);
};
goog.inherits(goog.dom.NodeIterator, goog.dom.TagIterator);
/**
* Moves to the next position in the DOM tree.
* @return {Node} Returns the next node, or throws a goog.iter.StopIteration
* exception if the end of the iterator's range has been reached.
*/
goog.dom.NodeIterator.prototype.next = function() {
do {
goog.dom.NodeIterator.superClass_.next.call(this);
} while (this.isEndTag());
return this.node;
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the IE browser specific range wrapper.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
* @author robbyw@google.com (Robby Walker)
* @author ojan@google.com (Ojan Vafai)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.browserrange.IeRange');
//goog.require('goog.array');
//goog.require('goog.dom');
//goog.require('goog.dom.NodeIterator');
//goog.require('goog.dom.NodeType');
//goog.require('goog.dom.RangeEndpoint');
//goog.require('goog.dom.TagName');
//goog.require('goog.dom.browserrange.AbstractRange');
//goog.require('goog.iter');
//goog.require('goog.iter.StopIteration');
//goog.require('goog.string');
/**
* The constructor for IE specific browser ranges.
* @param {TextRange} range The range object.
* @param {Document} doc The document the range exists in.
* @constructor
* @extends {goog.dom.browserrange.AbstractRange}
*/
goog.dom.browserrange.IeRange = function(range, doc) {
/**
* The browser range object this class wraps.
* @type {TextRange}
* @private
*/
this.range_ = range;
/**
* The document the range exists in.
* @type {Document}
* @private
*/
this.doc_ = doc;
};
goog.inherits(goog.dom.browserrange.IeRange,
goog.dom.browserrange.AbstractRange);
/**
* Returns a browser range spanning the given node's contents.
* @param {Node} node The node to select.
* @return {TextRange} A browser range spanning the node's contents.
* @private
*/
goog.dom.browserrange.IeRange.getBrowserRangeForNode_ = function(node) {
var nodeRange = goog.dom.getOwnerDocument(node).body.createTextRange();
if (node.nodeType == goog.dom.NodeType.ELEMENT) {
// Elements are easy.
nodeRange.moveToElementText(node);
// Note(user) : If there are no child nodes of the element, the
// range.htmlText includes the element's outerHTML. The range created above
// is not collapsed, and should be collapsed explicitly.
// Example : node =
// But if the node is sth like
, it shouldnt be collapsed.
if (goog.dom.browserrange.canContainRangeEndpoint(node) &&
!node.childNodes.length) {
nodeRange.collapse(_FALSE);
}
} else {
// Text nodes are hard.
// Compute the offset from the nearest element related position.
var offset = 0;
var sibling = node;
while (sibling = sibling.previousSibling) {
var nodeType = sibling.nodeType;
if (nodeType == goog.dom.NodeType.TEXT) {
offset += sibling.length;
} else if (nodeType == goog.dom.NodeType.ELEMENT) {
// Move to the space after this element.
nodeRange.moveToElementText(sibling);
break;
}
}
if (!sibling) {
nodeRange.moveToElementText(node.parentNode);
}
nodeRange.collapse(!sibling);
if (offset) {
nodeRange.move('character', offset);
}
nodeRange.moveEnd('character', node.length);
}
return nodeRange;
};
/**
* Returns a browser range spanning the given nodes.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the start node.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the end node.
* @return {TextRange} A browser range spanning the node's contents.
* @private
*/
goog.dom.browserrange.IeRange.getBrowserRangeForNodes_ = function(startNode,
startOffset, endNode, endOffset) {
// Create a range starting at the correct start position.
var child, collapse = _FALSE;
if (startNode.nodeType == goog.dom.NodeType.ELEMENT) {
child = startNode.childNodes[startOffset];
collapse = !child;
startNode = child || startNode.lastChild || startNode;
startOffset = 0;
}
var leftRange = goog.dom.browserrange.IeRange.
getBrowserRangeForNode_(startNode);
// This happens only when startNode is a text node.
if (startOffset) {
leftRange.move('character', startOffset);
}
// The range movements in IE are still an approximation to the standard W3C
// behavior, and IE has its trickery when it comes to htmlText and text
// properties of the range. So we short-circuit computation whenever we can.
if (startNode == endNode && startOffset == endOffset) {
leftRange.collapse(_TRUE);
return leftRange;
}
// This can happen only when the startNode is an element, and there is no node
// at the given offset. We start at the last point inside the startNode in
// that case.
if (collapse) {
leftRange.collapse(_FALSE);
}
// Create a range that ends at the right position.
collapse = _FALSE;
if (endNode.nodeType == goog.dom.NodeType.ELEMENT) {
child = endNode.childNodes[endOffset];
endNode = child || endNode.lastChild || endNode;
endOffset = 0;
collapse = !child;
}
var rightRange = goog.dom.browserrange.IeRange.
getBrowserRangeForNode_(endNode);
rightRange.collapse(!collapse);
if (endOffset) {
rightRange.moveEnd('character', endOffset);
}
// Merge and return.
leftRange.setEndPoint('EndToEnd', rightRange);
return leftRange;
};
/**
* Create a range object that selects the given node's text.
* @param {Node} node The node to select.
* @return {goog.dom.browserrange.IeRange} An IE range wrapper object.
*/
goog.dom.browserrange.IeRange.createFromNodeContents = function(node) {
var range = new goog.dom.browserrange.IeRange(
goog.dom.browserrange.IeRange.getBrowserRangeForNode_(node),
goog.dom.getOwnerDocument(node));
if (!goog.dom.browserrange.canContainRangeEndpoint(node)) {
range.startNode_ = range.endNode_ = range.parentNode_ = node.parentNode;
range.startOffset_ = goog.array.indexOf(range.parentNode_.childNodes, node);
range.endOffset_ = range.startOffset_ + 1;
} else {
// Note(user) : Emulate the behavior of W3CRange - Go to deepest possible
// range containers on both edges. It seems W3CRange did this to match the
// IE behavior, and now it is a circle. Changing W3CRange may break clients
// in all sorts of ways.
var tempNode, leaf = node;
while ((tempNode = leaf.firstChild) &&
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
leaf = tempNode;
}
range.startNode_ = leaf;
range.startOffset_ = 0;
leaf = node;
while ((tempNode = leaf.lastChild) &&
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
leaf = tempNode;
}
range.endNode_ = leaf;
range.endOffset_ = leaf.nodeType == goog.dom.NodeType.ELEMENT ?
leaf.childNodes.length : leaf.length;
range.parentNode_ = node;
}
return range;
};
/**
* Static method that returns the proper type of browser range.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the start node.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the end node.
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
*/
goog.dom.browserrange.IeRange.createFromNodes = function(startNode,
startOffset, endNode, endOffset) {
var range = new goog.dom.browserrange.IeRange(
goog.dom.browserrange.IeRange.getBrowserRangeForNodes_(startNode,
startOffset, endNode, endOffset),
goog.dom.getOwnerDocument(startNode));
range.startNode_ = startNode;
range.startOffset_ = startOffset;
range.endNode_ = endNode;
range.endOffset_ = endOffset;
return range;
};
// Even though goog.dom.TextRange does similar caching to below, keeping these
// caches allows for better performance in the get*Offset methods.
/**
* Lazy cache of the node containing the entire selection.
* @type {Node}
* @private
*/
goog.dom.browserrange.IeRange.prototype.parentNode_ = _NULL;
/**
* Lazy cache of the node containing the start of the selection.
* @type {Node}
* @private
*/
goog.dom.browserrange.IeRange.prototype.startNode_ = _NULL;
/**
* Lazy cache of the node containing the end of the selection.
* @type {Node}
* @private
*/
goog.dom.browserrange.IeRange.prototype.endNode_ = _NULL;
/**
* Lazy cache of the offset in startNode_ where this range starts.
* @type {number}
* @private
*/
goog.dom.browserrange.IeRange.prototype.startOffset_ = -1;
/**
* Lazy cache of the offset in endNode_ where this range ends.
* @type {number}
* @private
*/
goog.dom.browserrange.IeRange.prototype.endOffset_ = -1;
/**
* @return {goog.dom.browserrange.IeRange} A clone of this range.
*/
goog.dom.browserrange.IeRange.prototype.clone = function() {
var range = new goog.dom.browserrange.IeRange(
this.range_.duplicate(), this.doc_);
range.parentNode_ = this.parentNode_;
range.startNode_ = this.startNode_;
range.endNode_ = this.endNode_;
return range;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getBrowserRange = function() {
return this.range_;
};
/**
* Clears the cached values for containers.
* @private
*/
goog.dom.browserrange.IeRange.prototype.clearCachedValues_ = function() {
this.parentNode_ = this.startNode_ = this.endNode_ = _NULL;
this.startOffset_ = this.endOffset_ = -1;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getContainer = function() {
if (!this.parentNode_) {
var selectText = this.range_.text;
// If the selection ends with spaces, we need to remove these to get the
// parent container of only the real contents. This is to get around IE's
// inconsistency where it selects the spaces after a word when you double
// click, but leaves out the spaces during execCommands.
var range = this.range_.duplicate();
// We can't use goog.string.trimRight, as that will remove other whitespace
// too.
var rightTrimmedSelectText = selectText.replace(/ +$/, '');
var numSpacesAtEnd = selectText.length - rightTrimmedSelectText.length;
if (numSpacesAtEnd) {
range.moveEnd('character', -numSpacesAtEnd);
}
// Get the parent node. This should be the end, but alas, it is not.
var parent = range.parentElement();
var htmlText = range.htmlText;
var htmlTextLen = goog.string.stripNewlines(htmlText).length;
if (this.isCollapsed() && htmlTextLen > 0) {
return (this.parentNode_ = parent);
}
// Deal with selection bug where IE thinks one of the selection's children
// is actually the selection's parent. Relies on the assumption that the
// HTML text of the parent container is longer than the length of the
// selection's HTML text.
// Also note IE will sometimes insert \r and \n whitespace, which should be
// disregarded. Otherwise the loop may run too long and return wrong parent
while (htmlTextLen > goog.string.stripNewlines(parent.outerHTML).length) {
parent = parent.parentNode;
}
// Deal with IE's selecting the outer tags when you double click
// If the innerText is the same, then we just want the inner node
while (parent.childNodes.length == 1 &&
parent.firstChild && // FTDUEDTR-1212
parent.innerText == goog.dom.browserrange.IeRange.getNodeText_(
parent.firstChild)) {
// A container should be an element which can have children or a text
// node. Elements like IMG, BR, etc. can not be containers.
if (!goog.dom.browserrange.canContainRangeEndpoint(parent.firstChild)) {
break;
}
parent = parent.firstChild;
}
// If the selection is empty, we may need to do extra work to position it
// properly.
if (selectText.length == 0) {
parent = this.findDeepestContainer_(parent);
}
this.parentNode_ = parent;
}
return this.parentNode_;
};
/**
* Helper method to find the deepest parent for this range, starting
* the search from {@code node}, which must contain the range.
* @param {Node} node The node to start the search from.
* @return {Node} The deepest parent for this range.
* @private
*/
goog.dom.browserrange.IeRange.prototype.findDeepestContainer_ = function(node) {
var childNodes = node.childNodes;
for (var i = 0, len = childNodes.length; i < len; i++) {
var child = childNodes[i];
if (goog.dom.browserrange.canContainRangeEndpoint(child)) {
var childRange =
goog.dom.browserrange.IeRange.getBrowserRangeForNode_(child);
var start = goog.dom.RangeEndpoint.START;
var end = goog.dom.RangeEndpoint.END;
// There are two types of erratic nodes where the range over node has
// different htmlText than the node's outerHTML.
// Case 1 - A node with magic child. In this case :
// nodeRange.htmlText shows ('
), while
// node.outerHTML doesn't show the magic node ().
// Case 2 - Empty span. In this case :
// node.outerHTML shows ''
// node.htmlText is just empty string ''.
var isChildRangeErratic = (childRange.htmlText != child.outerHTML);
// Moreover the inRange comparison fails only when the
var isNativeInRangeErratic = this.isCollapsed() && isChildRangeErratic;
// In case 2 mentioned above, childRange is also collapsed. So we need to
// compare start of this range with both start and end of child range.
var inChildRange = isNativeInRangeErratic ?
(this.compareBrowserRangeEndpoints(childRange, start, start) >= 0 &&
this.compareBrowserRangeEndpoints(childRange, start, end) <= 0) :
this.range_.inRange(childRange);
if (inChildRange) {
return this.findDeepestContainer_(child);
}
}
}
return node;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getStartNode = function() {
if (!this.startNode_) {
this.startNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.START);
if (this.isCollapsed()) {
this.endNode_ = this.startNode_;
}
}
return this.startNode_;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getStartOffset = function() {
if (this.startOffset_ < 0) {
this.startOffset_ = this.getOffset_(goog.dom.RangeEndpoint.START);
if (this.isCollapsed()) {
this.endOffset_ = this.startOffset_;
}
}
return this.startOffset_;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getEndNode = function() {
if (this.isCollapsed()) {
return this.getStartNode();
}
if (!this.endNode_) {
this.endNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.END);
}
return this.endNode_;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getEndOffset = function() {
if (this.isCollapsed()) {
return this.getStartOffset();
}
if (this.endOffset_ < 0) {
this.endOffset_ = this.getOffset_(goog.dom.RangeEndpoint.END);
if (this.isCollapsed()) {
this.startOffset_ = this.endOffset_;
}
}
return this.endOffset_;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.compareBrowserRangeEndpoints = function(
range, thisEndpoint, otherEndpoint) {
return this.range_.compareEndPoints(
(thisEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End') +
'To' +
(otherEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End'),
range);
};
/**
* Recurses to find the correct node for the given endpoint.
* @param {goog.dom.RangeEndpoint} endpoint The endpoint to get the node for.
* @param {Node=} opt_node Optional node to start the search from.
* @return {Node} The deepest node containing the endpoint.
* @private
*/
goog.dom.browserrange.IeRange.prototype.getEndpointNode_ = function(endpoint,
opt_node) {
/** @type {Node} */
var node = opt_node || this.getContainer();
// If we're at a leaf in the DOM, we're done.
if (!node || !node.firstChild) {
return node;
}
var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
var isStartEndpoint = endpoint == start;
// Find the first/last child that overlaps the selection.
// NOTE(user) : One of the children can be the magic node. This
// node will have only nodeType property as valid and accessible. All other
// dom related properties like ownerDocument, parentNode, nextSibling etc
// cause error when accessed. Therefore use the for-loop on childNodes to
// iterate.
for (var j = 0, length = node.childNodes.length; j < length; j++) {
var i = isStartEndpoint ? j : length - j - 1;
var child = node.childNodes[i];
var childRange;
try {
childRange = goog.dom.browserrange.createRangeFromNodeContents(child);
} catch (e) {
// If the child is the magic node, then the above will throw
// error. The magic node exists only when editing using keyboard, so can
// not add any unit test.
continue;
}
var ieRange = childRange.getBrowserRange();
// Case 1 : Finding end points when this range is collapsed.
// Note that in case of collapsed range, getEnd{Node,Offset} call
// getStart{Node,Offset}.
if (this.isCollapsed()) {
// Handle situations where caret is not in a text node. In such cases,
// the adjacent child won't be a valid range endpoint container.
if (!goog.dom.browserrange.canContainRangeEndpoint(child)) {
// The following handles a scenario like
[caret]
,
// where point should be (div, 1).
if (this.compareBrowserRangeEndpoints(ieRange, start, start) == 0) {
this.startOffset_ = this.endOffset_ = i;
return node;
}
} else if (childRange.containsRange(this)) {
// For collapsed range, we should invert the containsRange check with
// childRange.
return this.getEndpointNode_(endpoint, child);
}
// Case 2 - The first child encountered to have overlap this range is
// contained entirely in this range.
} else if (this.containsRange(childRange)) {
// If it is an element which can not be a range endpoint container, the
// current child offset can be used to deduce the endpoint offset.
if (!goog.dom.browserrange.canContainRangeEndpoint(child)) {
// Container can't be any deeper, so current node is the container.
if (isStartEndpoint) {
this.startOffset_ = i;
} else {
this.endOffset_ = i + 1;
}
return node;
}
// If child can contain range endpoints, recurse inside this child.
while(child.childNodes.length == 1) {
child = child.firstChild;
}
return this.getEndpointNode_(endpoint, child);
// Case 3 - Partial non-adjacency overlap.
} else if (this.compareBrowserRangeEndpoints(ieRange, start, end) < 0 &&
this.compareBrowserRangeEndpoints(ieRange, end, start) > 0) {
// If this child overlaps the selection partially, recurse down to find
// the first/last child the next level down that overlaps the selection
// completely. We do not consider edge-adjacency (== 0) as overlap.
while(child.childNodes.length == 1 && child.firstChild) {
child = child.firstChild;
}
return this.getEndpointNode_(endpoint, child);
}
}
// None of the children of this node overlapped the selection, that means
// the selection starts/ends in this node directly.
return node;
};
/**
* Compares one endpoint of this range with the endpoint of a node.
* For internal methods, we should prefer this method to containsNode.
* containsNode has a lot of _FALSE negatives when we're dealing with
* {@code
} tags.
*
* @param {Node} node The node to compare against.
* @param {goog.dom.RangeEndpoint} thisEndpoint The endpoint of this range
* to compare with.
* @param {goog.dom.RangeEndpoint} otherEndpoint The endpoint of the node
* to compare with.
* @return {number} 0 if the endpoints are equal, negative if this range
* endpoint comes before the other node endpoint, and positive otherwise.
* @private
*/
goog.dom.browserrange.IeRange.prototype.compareNodeEndpoints_ =
function(node, thisEndpoint, otherEndpoint) {
return this.range_.compareEndPoints(
(thisEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End') +
'To' +
(otherEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End'),
goog.dom.browserrange.createRangeFromNodeContents(node).
getBrowserRange());
};
/**
* Returns the offset into the start/end container.
* @param {goog.dom.RangeEndpoint} endpoint The endpoint to get the offset for.
* @param {Node=} opt_container The container to get the offset relative to.
* Defaults to the value returned by getStartNode/getEndNode.
* @return {number} The offset.
* @private
*/
goog.dom.browserrange.IeRange.prototype.getOffset_ = function(endpoint,
opt_container) {
var isStartEndpoint = endpoint == goog.dom.RangeEndpoint.START;
var container = opt_container ||
(isStartEndpoint ? this.getStartNode() : this.getEndNode());
if (container.nodeType == goog.dom.NodeType.ELEMENT) {
// Find the first/last child that overlaps the selection
var children = container.childNodes;
var len = children.length;
var edge = isStartEndpoint ? 0 : len - 1;
var sign = isStartEndpoint ? 1 : - 1;
// We find the index in the child array of the endpoint of the selection.
for (var i = edge; i >= 0 && i < len; i += sign) {
var child = children[i];
// Ignore the child nodes, which could be end point containers.
if (goog.dom.browserrange.canContainRangeEndpoint(child)) {
continue;
}
// Stop looping when we reach the edge of the selection.
var endPointCompare =
this.compareNodeEndpoints_(child, endpoint, endpoint);
if (endPointCompare == 0) {
return isStartEndpoint ? i : i + 1;
}
}
// When starting from the end in an empty container, we erroneously return
// -1: fix this to return 0.
return i == -1 ? 0 : i;
} else {
// Get a temporary range object.
var range = this.range_.duplicate();
// Create a range that selects the entire container.
var nodeRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(
container);
// Now, intersect our range with the container range - this should give us
// the part of our selection that is in the container.
range.setEndPoint(isStartEndpoint ? 'EndToEnd' : 'StartToStart', nodeRange);
var rangeLength = range.text.length;
return isStartEndpoint ? container.length - rangeLength : rangeLength;
}
};
/**
* Returns the text of the given node. Uses IE specific properties.
* @param {Node} node The node to retrieve the text of.
* @return {string} The node's text.
* @private
*/
goog.dom.browserrange.IeRange.getNodeText_ = function(node) {
return node.nodeType == goog.dom.NodeType.TEXT ?
node.nodeValue : node.innerText;
};
/**
* Tests whether this range is valid (i.e. whether its endpoints are still in
* the document). A range becomes invalid when, after this object was created,
* either one or both of its endpoints are removed from the document. Use of
* an invalid range can lead to runtime errors, particularly in IE.
* @return {boolean} Whether the range is valid.
*/
goog.dom.browserrange.IeRange.prototype.isRangeInDocument = function() {
var range = this.doc_.body.createTextRange();
range.moveToElementText(this.doc_.body);
return this.containsRange(
new goog.dom.browserrange.IeRange(range, this.doc_), _TRUE);
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.isCollapsed = function() {
// Note(user) : The earlier implementation used (range.text == ''), but this
// fails when (range.htmlText == '
')
// Alternative: this.range_.htmlText == '';
return this.range_.compareEndPoints('StartToEnd', this.range_) == 0;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getText = function() {
return this.range_.text;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.getValidHtml = function() {
return this.range_.htmlText;
};
// SELECTION MODIFICATION
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.select = function(opt_reverse) {
// IE doesn't support programmatic reversed selections.
this.range_.select();
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.removeContents = function() {
if (this.range_.htmlText) {
// Store some before-removal state.
var startNode = this.getStartNode();
var endNode = this.getEndNode();
var oldText = this.range_.text;
// IE sometimes deletes nodes unrelated to the selection. This trick fixes
// that problem most of the time. Even though it looks like a no-op, it is
// somehow changing IE's internal state such that empty unrelated nodes are
// no longer deleted.
var clone = this.range_.duplicate();
clone.moveStart('character', 1);
clone.moveStart('character', -1);
// However, sometimes when the range is empty, moving the start back and
// forth ends up changing the range. This indicates a case we need to
// handle manually.
if (clone.text != oldText) {
// Delete all nodes entirely contained in the range.
var iter = new goog.dom.NodeIterator(startNode, _FALSE, _TRUE);
var toDelete = [];
goog.iter.forEach(iter, function(node) {
// Any text node we encounter here is by definition contained entirely
// in the range.
if (node.nodeType != goog.dom.NodeType.TEXT &&
this.containsNode(node)) {
toDelete.push(node);
iter.skipTag();
}
if (node == endNode) {
throw goog.iter.StopIteration;
}
});
this.collapse(_TRUE);
goog.array.forEach(toDelete, goog.dom.removeNode);
this.clearCachedValues_();
return;
}
// Outside of the unfortunate cases where we have to handle deletion
// manually, we can use the browser's native deletion code.
this.range_ = clone;
this.range_.text = '';
this.clearCachedValues_();
// Unfortunately, when deleting a portion of a single text node, IE creates
// an extra text node unlike other browsers which just change the text in
// the node. We normalize for that behavior here, making IE behave like all
// the other browsers.
var newStartNode = this.getStartNode();
var newStartOffset = this.getStartOffset();
/** @preserveTry */
try {
var sibling = startNode.nextSibling;
if (startNode == endNode && startNode.parentNode &&
startNode.nodeType == goog.dom.NodeType.TEXT &&
sibling && sibling.nodeType == goog.dom.NodeType.TEXT) {
startNode.nodeValue += sibling.nodeValue;
goog.dom.removeNode(sibling);
// Make sure to reselect the appropriate position.
this.range_ = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(
newStartNode);
this.range_.move('character', newStartOffset);
this.clearCachedValues_();
}
} catch (e) {
// IE throws errors on orphaned nodes.
}
}
};
/**
* @param {TextRange} range The range to get a dom helper for.
* @return {goog.dom.DomHelper} A dom helper for the document the range
* resides in.
* @private
*/
goog.dom.browserrange.IeRange.getDomHelper_ = function(range) {
return goog.dom.getDomHelper(range.parentElement());
};
/**
* Pastes the given element into the given range, returning the resulting
* element.
* @param {TextRange} range The range to paste into.
* @param {Element} element The node to insert a copy of.
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper object for the document
* the range resides in.
* @return {Element} The resulting copy of element.
* @private
*/
goog.dom.browserrange.IeRange.pasteElement_ = function(range, element,
opt_domHelper) {
opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_(
range);
// Make sure the node has a unique id.
var id;
var originalId = id = element.id;
if (!id) {
id = element.id = goog.string.createUniqueString();
}
// Insert (a clone of) the node.
range.pasteHTML(element.outerHTML);
// Pasting the outerHTML of the modified element into the document creates
// a clone of the element argument. We want to return a reference to the
// clone, not the original. However we need to remove the temporary ID
// first.
element = opt_domHelper.getElement(id);
// If element is null here, we failed.
if (element) {
if (!originalId) {
element.removeAttribute('id');
}
}
return element;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.surroundContents = function(element) {
// Make sure the element is detached from the document.
goog.dom.removeNode(element);
// IE more or less guarantees that range.htmlText is well-formed & valid.
element.innerHTML = this.range_.htmlText;
element = goog.dom.browserrange.IeRange.pasteElement_(this.range_, element);
// If element is null here, we failed.
if (element) {
this.range_.moveToElementText(element);
}
this.clearCachedValues_();
return element;
};
/**
* Internal handler for inserting a node.
* @param {TextRange} clone A clone of this range's browser range object.
* @param {Node} node The node to insert.
* @param {boolean} before Whether to insert the node before or after the range.
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use.
* @return {Node} The resulting copy of node.
* @private
*/
goog.dom.browserrange.IeRange.insertNode_ = function(clone, node,
before, opt_domHelper) {
// Get a DOM helper.
opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_(
clone);
// If it's not an element, wrap it in one.
var isNonElement;
if (node.nodeType != goog.dom.NodeType.ELEMENT) {
isNonElement = _TRUE;
node = opt_domHelper.createDom(goog.dom.TagName.DIV, _NULL, node);
}
clone.collapse(before);
node = goog.dom.browserrange.IeRange.pasteElement_(clone,
/** @type {Element} */ (node), opt_domHelper);
// If we didn't want an element, unwrap the element and return the node.
if (isNonElement) {
// pasteElement_() may have returned a copy of the wrapper div, and the
// node it wraps could also be a new copy. So we must extract that new
// node from the new wrapper.
var newNonElement = node.firstChild;
opt_domHelper.flattenElement(node);
node = newNonElement;
}
return node;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.insertNode = function(node, before) {
var output = goog.dom.browserrange.IeRange.insertNode_(
this.range_.duplicate(), node, before);
this.clearCachedValues_();
return output;
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.surroundWithNodes = function(
startNode, endNode) {
var clone1 = this.range_.duplicate();
var clone2 = this.range_.duplicate();
goog.dom.browserrange.IeRange.insertNode_(clone1, startNode, _TRUE);
goog.dom.browserrange.IeRange.insertNode_(clone2, endNode, _FALSE);
this.clearCachedValues_();
};
/** @inheritDoc */
goog.dom.browserrange.IeRange.prototype.collapse = function(toStart) {
this.range_.collapse(toStart);
if (toStart) {
this.endNode_ = this.startNode_;
this.endOffset_ = this.startOffset_;
} else {
this.startNode_ = this.endNode_;
this.startOffset_ = this.endOffset_;
}
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the Gecko specific range wrapper. Inherits most
* functionality from W3CRange, but adds exceptions as necessary.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.browserrange.GeckoRange');
//goog.require('goog.dom.browserrange.W3cRange');
/**
* The constructor for Gecko specific browser ranges.
* @param {Range} range The range object.
* @constructor
* @extends {goog.dom.browserrange.W3cRange}
*/
goog.dom.browserrange.GeckoRange = function(range) {
goog.dom.browserrange.W3cRange.call(this, range);
};
goog.inherits(goog.dom.browserrange.GeckoRange, goog.dom.browserrange.W3cRange);
/**
* Creates a range object that selects the given node's text.
* @param {Node} node The node to select.
* @return {goog.dom.browserrange.GeckoRange} A Gecko range wrapper object.
*/
goog.dom.browserrange.GeckoRange.createFromNodeContents = function(node) {
return new goog.dom.browserrange.GeckoRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
};
/**
* Creates a range object that selects between the given nodes.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the node to start.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the node to end.
* @return {goog.dom.browserrange.GeckoRange} A wrapper object.
*/
goog.dom.browserrange.GeckoRange.createFromNodes = function(startNode,
startOffset, endNode, endOffset) {
return new goog.dom.browserrange.GeckoRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
startOffset, endNode, endOffset));
};
/** @inheritDoc */
goog.dom.browserrange.GeckoRange.prototype.selectInternal = function(
selection, reversed) {
var anchorNode = reversed ? this.getEndNode() : this.getStartNode();
var anchorOffset = reversed ? this.getEndOffset() : this.getStartOffset();
var focusNode = reversed ? this.getStartNode() : this.getEndNode();
var focusOffset = reversed ? this.getStartOffset() : this.getEndOffset();
selection.collapse(anchorNode, anchorOffset);
if (anchorNode != focusNode || anchorOffset != focusOffset) {
selection.extend(focusNode, focusOffset);
}
};
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the Opera specific range wrapper. Inherits most
* functionality from W3CRange, but adds exceptions as necessary.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
*/
goog.provide('goog.dom.browserrange.OperaRange');
//goog.require('goog.dom.browserrange.W3cRange');
/**
* The constructor for Opera specific browser ranges.
* @param {Range} range The range object.
* @constructor
* @extends {goog.dom.browserrange.W3cRange}
*/
goog.dom.browserrange.OperaRange = function(range) {
goog.dom.browserrange.W3cRange.call(this, range);
};
goog.inherits(goog.dom.browserrange.OperaRange, goog.dom.browserrange.W3cRange);
/**
* Creates a range object that selects the given node's text.
* @param {Node} node The node to select.
* @return {goog.dom.browserrange.OperaRange} A Opera range wrapper object.
*/
goog.dom.browserrange.OperaRange.createFromNodeContents = function(node) {
return new goog.dom.browserrange.OperaRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
};
/**
* Creates a range object that selects between the given nodes.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the node to start.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the node to end.
* @return {goog.dom.browserrange.OperaRange} A wrapper object.
*/
goog.dom.browserrange.OperaRange.createFromNodes = function(startNode,
startOffset, endNode, endOffset) {
return new goog.dom.browserrange.OperaRange(
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
startOffset, endNode, endOffset));
};
/** @inheritDoc */
goog.dom.browserrange.OperaRange.prototype.selectInternal = function(
selection, reversed) {
// Avoid using addRange as we have to removeAllRanges first, which
// blurs editable fields in Opera.
selection.collapse(this.getStartNode(), this.getStartOffset());
if (this.getEndNode() != this.getStartNode() ||
this.getEndOffset() != this.getStartOffset()) {
selection.extend(this.getEndNode(), this.getEndOffset());
}
// This can happen if the range isn't in an editable field.
if (selection.rangeCount == 0) {
selection.addRange(this.range_);
}
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the browser range namespace and interface, as
* well as several useful utility functions.
*
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
*
* @author robbyw@google.com (Robby Walker)
* @author ojan@google.com (Ojan Vafai)
* @author jparent@google.com (Julie Parent)
*
* @supported IE6, IE7, FF1.5+, Safari.
*/
goog.provide('goog.dom.browserrange');
goog.provide('goog.dom.browserrange.Error');
//goog.require('goog.dom');
//goog.require('goog.dom.browserrange.GeckoRange');
//goog.require('goog.dom.browserrange.IeRange');
//goog.require('goog.dom.browserrange.OperaRange');
//goog.require('goog.dom.browserrange.W3cRange');
//goog.require('goog.dom.browserrange.WebKitRange');
//goog.require('goog.userAgent');
/**
* Common error constants.
* @enum {string}
*/
goog.dom.browserrange.Error = {
NOT_IMPLEMENTED: 'Not Implemented'
};
// NOTE(robbyw): While it would be nice to eliminate the duplicate switches
// below, doing so uncovers bugs in the JsCompiler in which
// necessary code is stripped out.
/**
* Static method that returns the proper type of browser range.
* @param {Range|TextRange} range A browser range object.
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
*/
goog.dom.browserrange.createRange = function(range) {
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
return new goog.dom.browserrange.IeRange(
/** @type {TextRange} */ (range),
goog.dom.getOwnerDocument(range.parentElement()));
} else if (goog.userAgent.WEBKIT) {
return new goog.dom.browserrange.WebKitRange(
/** @type {Range} */ (range));
} else if (goog.userAgent.GECKO) {
return new goog.dom.browserrange.GeckoRange(
/** @type {Range} */ (range));
} else if (goog.userAgent.OPERA) {
return new goog.dom.browserrange.OperaRange(
/** @type {Range} */ (range));
} else {
// Default other browsers, including Opera, to W3c ranges.
return new goog.dom.browserrange.W3cRange(
/** @type {Range} */ (range));
}
};
/**
* Static method that returns the proper type of browser range.
* @param {Node} node The node to select.
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
*/
goog.dom.browserrange.createRangeFromNodeContents = function(node) {
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
return goog.dom.browserrange.IeRange.createFromNodeContents(node);
} else if (goog.userAgent.WEBKIT) {
return goog.dom.browserrange.WebKitRange.createFromNodeContents(node);
} else if (goog.userAgent.GECKO) {
return goog.dom.browserrange.GeckoRange.createFromNodeContents(node);
} else if (goog.userAgent.OPERA) {
return goog.dom.browserrange.OperaRange.createFromNodeContents(node);
} else {
// Default other browsers to W3c ranges.
return goog.dom.browserrange.W3cRange.createFromNodeContents(node);
}
};
/**
* Static method that returns the proper type of browser range.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the node to start. This is
* either the index into the childNodes array for element startNodes or
* the index into the character array for text startNodes.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the node to end. This is
* either the index into the childNodes array for element endNodes or
* the index into the character array for text endNodes.
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
*/
goog.dom.browserrange.createRangeFromNodes = function(startNode, startOffset,
endNode, endOffset) {
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
return goog.dom.browserrange.IeRange.createFromNodes(startNode, startOffset,
endNode, endOffset);
} else if (goog.userAgent.WEBKIT) {
return goog.dom.browserrange.WebKitRange.createFromNodes(startNode,
startOffset, endNode, endOffset);
} else if (goog.userAgent.GECKO) {
return goog.dom.browserrange.GeckoRange.createFromNodes(startNode,
startOffset, endNode, endOffset);
} else if (goog.userAgent.OPERA) {
return goog.dom.browserrange.OperaRange.createFromNodes(startNode,
startOffset, endNode, endOffset);
} else {
// Default other browsers to W3c ranges.
return goog.dom.browserrange.W3cRange.createFromNodes(startNode,
startOffset, endNode, endOffset);
}
};
/**
* Tests whether the given node can contain a range end point.
* @param {Node} node The node to check.
* @return {boolean} Whether the given node can contain a range end point.
*/
goog.dom.browserrange.canContainRangeEndpoint = function(node) {
// NOTE(user, bloom): This is not complete, as divs with style -
// 'display:inline-block' or 'position:absolute' can also not contain range
// endpoints. A more complete check is to see if that element can be partially
// selected (can be container) or not.
return goog.dom.canHaveChildren(node) ||
node.nodeType == goog.dom.NodeType.TEXT;
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for working with text ranges in HTML documents.
*
* @author robbyw@google.com (Robby Walker)
* @author ojan@google.com (Ojan Vafai)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.TextRange');
//goog.require('goog.array');
//goog.require('goog.dom');
//goog.require('goog.dom.AbstractRange');
//goog.require('goog.dom.RangeType');
//goog.require('goog.dom.SavedRange');
//goog.require('goog.dom.TagName');
//goog.require('goog.dom.TextRangeIterator');
//goog.require('goog.dom.browserrange');
//goog.require('goog.string');
//goog.require('goog.userAgent');
/**
* Create a new text selection with no properties. Do not use this constructor:
* use one of the goog.dom.Range.createFrom* methods instead.
* @constructor
* @extends {goog.dom.AbstractRange}
*/
goog.dom.TextRange = function() {
};
goog.inherits(goog.dom.TextRange, goog.dom.AbstractRange);
/**
* Create a new range wrapper from the given browser range object. Do not use
* this method directly - please use goog.dom.Range.createFrom* instead.
* @param {Range|TextRange} range The browser range object.
* @param {boolean=} opt_isReversed Whether the focus node is before the anchor
* node.
* @return {goog.dom.TextRange} A range wrapper object.
*/
goog.dom.TextRange.createFromBrowserRange = function(range, opt_isReversed) {
return goog.dom.TextRange.createFromBrowserRangeWrapper_(
goog.dom.browserrange.createRange(range), opt_isReversed);
};
/**
* Create a new range wrapper from the given browser range wrapper.
* @param {goog.dom.browserrange.AbstractRange} browserRange The browser range
* wrapper.
* @param {boolean=} opt_isReversed Whether the focus node is before the anchor
* node.
* @return {goog.dom.TextRange} A range wrapper object.
* @private
*/
goog.dom.TextRange.createFromBrowserRangeWrapper_ = function(browserRange,
opt_isReversed) {
var range = new goog.dom.TextRange();
// Initialize the range as a browser range wrapper type range.
range.browserRangeWrapper_ = browserRange;
range.isReversed_ = !!opt_isReversed;
return range;
};
/**
* Create a new range wrapper that selects the given node's text. Do not use
* this method directly - please use goog.dom.Range.createFrom* instead.
* @param {Node} node The node to select.
* @param {boolean=} opt_isReversed Whether the focus node is before the anchor
* node.
* @return {goog.dom.TextRange} A range wrapper object.
*/
goog.dom.TextRange.createFromNodeContents = function(node, opt_isReversed) {
return goog.dom.TextRange.createFromBrowserRangeWrapper_(
goog.dom.browserrange.createRangeFromNodeContents(node),
opt_isReversed);
};
/**
* Create a new range wrapper that selects the area between the given nodes,
* accounting for the given offsets. Do not use this method directly - please
* use goog.dom.Range.createFrom* instead.
* @param {Node} anchorNode The node to start with.
* @param {number} anchorOffset The offset within the node to start.
* @param {Node} focusNode The node to end with.
* @param {number} focusOffset The offset within the node to end.
* @return {goog.dom.TextRange} A range wrapper object.
*/
goog.dom.TextRange.createFromNodes = function(anchorNode, anchorOffset,
focusNode, focusOffset) {
var range = new goog.dom.TextRange();
range.isReversed_ = goog.dom.Range.isReversed(anchorNode, anchorOffset,
focusNode, focusOffset);
// Avoid selecting BRs directly
if (anchorNode.tagName == 'BR') {
var parent = anchorNode.parentNode;
anchorOffset = goog.array.indexOf(parent.childNodes, anchorNode);
anchorNode = parent;
}
if (focusNode.tagName == 'BR') {
var parent = focusNode.parentNode;
focusOffset = goog.array.indexOf(parent.childNodes, focusNode);
focusNode = parent;
}
// Initialize the range as a W3C style range.
if (range.isReversed_) {
range.startNode_ = focusNode;
range.startOffset_ = focusOffset;
range.endNode_ = anchorNode;
range.endOffset_ = anchorOffset;
} else {
range.startNode_ = anchorNode;
range.startOffset_ = anchorOffset;
range.endNode_ = focusNode;
range.endOffset_ = focusOffset;
}
return range;
};
// Representation 1: a browser range wrapper.
/**
* The browser specific range wrapper. This can be null if one of the other
* representations of the range is specified.
* @type {goog.dom.browserrange.AbstractRange?}
* @private
*/
goog.dom.TextRange.prototype.browserRangeWrapper_ = _NULL;
// Representation 2: two endpoints specified as nodes + offsets
/**
* The start node of the range. This can be null if one of the other
* representations of the range is specified.
* @type {Node}
* @private
*/
goog.dom.TextRange.prototype.startNode_ = _NULL;
/**
* The start offset of the range. This can be null if one of the other
* representations of the range is specified.
* @type {?number}
* @private
*/
goog.dom.TextRange.prototype.startOffset_ = _NULL;
/**
* The end node of the range. This can be null if one of the other
* representations of the range is specified.
* @type {Node}
* @private
*/
goog.dom.TextRange.prototype.endNode_ = _NULL;
/**
* The end offset of the range. This can be null if one of the other
* representations of the range is specified.
* @type {?number}
* @private
*/
goog.dom.TextRange.prototype.endOffset_ = _NULL;
/**
* Whether the focus node is before the anchor node.
* @type {boolean}
* @private
*/
goog.dom.TextRange.prototype.isReversed_ = _FALSE;
// Method implementations
/**
* @return {goog.dom.TextRange} A clone of this range.
*/
goog.dom.TextRange.prototype.clone = function() {
var range = new goog.dom.TextRange();
range.browserRangeWrapper_ = this.browserRangeWrapper_;
range.startNode_ = this.startNode_;
range.startOffset_ = this.startOffset_;
range.endNode_ = this.endNode_;
range.endOffset_ = this.endOffset_;
range.isReversed_ = this.isReversed_;
return range;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getType = function() {
return goog.dom.RangeType.TEXT;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getBrowserRangeObject = function() {
return this.getBrowserRangeWrapper_().getBrowserRange();
};
/** @inheritDoc */
goog.dom.TextRange.prototype.setBrowserRangeObject = function(nativeRange) {
// Test if it's a control range by seeing if a control range only method
// exists.
if (goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
return _FALSE;
}
this.browserRangeWrapper_ = goog.dom.browserrange.createRange(
nativeRange);
this.clearCachedValues_();
return _TRUE;
};
/**
* Clear all cached values.
* @private
*/
goog.dom.TextRange.prototype.clearCachedValues_ = function() {
this.startNode_ = this.startOffset_ = this.endNode_ = this.endOffset_ = _NULL;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getTextRangeCount = function() {
return 1;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getTextRange = function(i) {
return this;
};
/**
* @return {goog.dom.browserrange.AbstractRange} The range wrapper object.
* @private
*/
goog.dom.TextRange.prototype.getBrowserRangeWrapper_ = function() {
return this.browserRangeWrapper_ ||
(this.browserRangeWrapper_ = goog.dom.browserrange.createRangeFromNodes(
this.getStartNode(), this.getStartOffset(),
this.getEndNode(), this.getEndOffset()));
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getContainer = function() {
return this.getBrowserRangeWrapper_().getContainer();
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getStartNode = function() {
return this.startNode_ ||
(this.startNode_ = this.getBrowserRangeWrapper_().getStartNode());
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getStartOffset = function() {
return this.startOffset_ != _NULL ? this.startOffset_ :
(this.startOffset_ = this.getBrowserRangeWrapper_().getStartOffset());
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getEndNode = function() {
return this.endNode_ ||
(this.endNode_ = this.getBrowserRangeWrapper_().getEndNode());
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getEndOffset = function() {
return this.endOffset_ != _NULL ? this.endOffset_ :
(this.endOffset_ = this.getBrowserRangeWrapper_().getEndOffset());
};
/**
* Moves a TextRange to the provided nodes and offsets.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the node to start.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the node to end.
* @param {boolean} isReversed Whether the range is reversed.
*/
goog.dom.TextRange.prototype.moveToNodes = function(startNode, startOffset,
endNode, endOffset,
isReversed) {
this.startNode_ = startNode;
this.startOffset_ = startOffset;
this.endNode_ = endNode;
this.endOffset_ = endOffset;
this.isReversed_ = isReversed;
this.browserRangeWrapper_ = _NULL;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.isReversed = function() {
return this.isReversed_;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.containsRange = function(otherRange,
opt_allowPartial) {
var otherRangeType = otherRange.getType();
if (otherRangeType == goog.dom.RangeType.TEXT) {
return this.getBrowserRangeWrapper_().containsRange(
otherRange.getBrowserRangeWrapper_(), opt_allowPartial);
} else if (otherRangeType == goog.dom.RangeType.CONTROL) {
var elements = otherRange.getElements();
var fn = opt_allowPartial ? goog.array.some : goog.array.every;
return fn(elements, function(el) {
return this.containsNode(el, opt_allowPartial);
}, this);
}
return _FALSE;
};
/**
* Tests if the given node is in a document.
* @param {Node} node The node to check.
* @return {boolean} Whether the given node is in the given document.
*/
goog.dom.TextRange.isAttachedNode = function(node) {
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
var returnValue = _FALSE;
/** @preserveTry */
try {
returnValue = node.parentNode;
} catch (e) {
// IE sometimes throws Invalid Argument errors when a node is detached.
// Note: trying to return a value from the above try block can cause IE
// to crash. It is necessary to use the local returnValue
}
return !!returnValue;
} else {
return goog.dom.contains(node.ownerDocument.body, node);
}
};
/** @inheritDoc */
goog.dom.TextRange.prototype.isRangeInDocument = function() {
// Ensure any cached nodes are in the document. IE also allows ranges to
// become detached, so we check if the range is still in the document as
// well for IE.
return (!this.startNode_ ||
goog.dom.TextRange.isAttachedNode(this.startNode_)) &&
(!this.endNode_ ||
goog.dom.TextRange.isAttachedNode(this.endNode_)) &&
(!(goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) ||
this.getBrowserRangeWrapper_().isRangeInDocument());
};
/** @inheritDoc */
goog.dom.TextRange.prototype.isCollapsed = function() {
return this.getBrowserRangeWrapper_().isCollapsed();
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getText = function() {
return this.getBrowserRangeWrapper_().getText();
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getHtmlFragment = function() {
// TODO(robbyw): Generalize the code in browserrange so it is static and
// just takes an iterator. This would mean we don't always have to create a
// browser range.
return this.getBrowserRangeWrapper_().getHtmlFragment();
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getValidHtml = function() {
return this.getBrowserRangeWrapper_().getValidHtml();
};
/** @inheritDoc */
goog.dom.TextRange.prototype.getPastableHtml = function() {
// TODO(robbyw): Get any attributes the table or tr has.
var html = this.getValidHtml();
if (html.match(/^\s*';
} else if (html.match(/^\s* | ';
} else if (html.match(/^\s*
';
} else if (html.match(/^\s*', html, '', tagType, '>');
}
return html;
};
/**
* Returns a TextRangeIterator over the contents of the range. Regardless of
* the direction of the range, the iterator will move in document order.
* @param {boolean=} opt_keys Unused for this iterator.
* @return {goog.dom.TextRangeIterator} An iterator over tags in the range.
*/
goog.dom.TextRange.prototype.__iterator__ = function(opt_keys) {
return new goog.dom.TextRangeIterator(this.getStartNode(),
this.getStartOffset(), this.getEndNode(), this.getEndOffset());
};
// RANGE ACTIONS
/** @inheritDoc */
goog.dom.TextRange.prototype.select = function() {
this.getBrowserRangeWrapper_().select(this.isReversed_);
};
/** @inheritDoc */
goog.dom.TextRange.prototype.removeContents = function() {
this.getBrowserRangeWrapper_().removeContents();
this.clearCachedValues_();
};
/**
* Surrounds the text range with the specified element (on Mozilla) or with a
* clone of the specified element (on IE). Returns a reference to the
* surrounding element if the operation was successful; returns null if the
* operation failed.
* @param {Element} element The element with which the selection is to be
* surrounded.
* @return {Element} The surrounding element (same as the argument on Mozilla,
* but not on IE), or null if unsuccessful.
*/
goog.dom.TextRange.prototype.surroundContents = function(element) {
var output = this.getBrowserRangeWrapper_().surroundContents(element);
this.clearCachedValues_();
return output;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.insertNode = function(node, before) {
var output = this.getBrowserRangeWrapper_().insertNode(node, before);
this.clearCachedValues_();
return output;
};
/** @inheritDoc */
goog.dom.TextRange.prototype.surroundWithNodes = function(startNode, endNode) {
this.getBrowserRangeWrapper_().surroundWithNodes(startNode, endNode);
this.clearCachedValues_();
};
// SAVE/RESTORE
/** @inheritDoc */
goog.dom.TextRange.prototype.saveUsingDom = function() {
return new goog.dom.DomSavedTextRange_(this);
};
// RANGE MODIFICATION
/** @inheritDoc */
goog.dom.TextRange.prototype.collapse = function(toAnchor) {
var toStart = this.isReversed() ? !toAnchor : toAnchor;
if (this.browserRangeWrapper_) {
this.browserRangeWrapper_.collapse(toStart);
}
if (toStart) {
this.endNode_ = this.startNode_;
this.endOffset_ = this.startOffset_;
} else {
this.startNode_ = this.endNode_;
this.startOffset_ = this.endOffset_;
}
// Collapsed ranges can't be reversed
this.isReversed_ = _FALSE;
};
// SAVED RANGE OBJECTS
/**
* A SavedRange implementation using DOM endpoints.
* @param {goog.dom.AbstractRange} range The range to save.
* @constructor
* @extends {goog.dom.SavedRange}
* @private
*/
goog.dom.DomSavedTextRange_ = function(range) {
/**
* The anchor node.
* @type {Node}
* @private
*/
this.anchorNode_ = range.getAnchorNode();
/**
* The anchor node offset.
* @type {number}
* @private
*/
this.anchorOffset_ = range.getAnchorOffset();
/**
* The focus node.
* @type {Node}
* @private
*/
this.focusNode_ = range.getFocusNode();
/**
* The focus node offset.
* @type {number}
* @private
*/
this.focusOffset_ = range.getFocusOffset();
};
goog.inherits(goog.dom.DomSavedTextRange_, goog.dom.SavedRange);
/**
* @return {goog.dom.AbstractRange} The restored range.
*/
goog.dom.DomSavedTextRange_.prototype.restoreInternal = function() {
return goog.dom.Range.createFromNodes(this.anchorNode_, this.anchorOffset_,
this.focusNode_, this.focusOffset_);
};
/** @inheritDoc */
goog.dom.DomSavedTextRange_.prototype.disposeInternal = function() {
goog.dom.DomSavedTextRange_.superClass_.disposeInternal.call(this);
this.anchorNode_ = _NULL;
this.focusNode_ = _NULL;
};
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for working with W3C multi-part ranges.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.MultiRange');
goog.provide('goog.dom.MultiRangeIterator');
////goog.require('goog.array');
//goog.require('goog.dom.AbstractMultiRange');
//goog.require('goog.dom.AbstractRange');
//goog.require('goog.dom.RangeIterator');
//goog.require('goog.dom.RangeType');
//goog.require('goog.dom.SavedRange');
//goog.require('goog.dom.TextRange');
//goog.require('goog.iter.StopIteration');
/**
* Creates a new multi part range with no properties. Do not use this
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
* @constructor
* @extends {goog.dom.AbstractMultiRange}
*/
goog.dom.MultiRange = function() {
/**
* Array of browser sub-ranges comprising this multi-range.
* @type {Array.}
* @private
*/
this.browserRanges_ = [];
/**
* Lazily initialized array of range objects comprising this multi-range.
* @type {Array.}
* @private
*/
this.ranges_ = [];
/**
* Lazily computed sorted version of ranges_, sorted by start point.
* @type {Array.?}
* @private
*/
this.sortedRanges_ = _NULL;
/**
* Lazily computed container node.
* @type {Node}
* @private
*/
this.container_ = _NULL;
};
goog.inherits(goog.dom.MultiRange, goog.dom.AbstractMultiRange);
/**
* Creates a new range wrapper from the given browser selection object. Do not
* use this method directly - please use goog.dom.Range.createFrom* instead.
* @param {Selection} selection The browser selection object.
* @return {goog.dom.MultiRange} A range wrapper object.
*/
goog.dom.MultiRange.createFromBrowserSelection = function(selection) {
var range = new goog.dom.MultiRange();
for (var i = 0, len = selection.rangeCount; i < len; i++) {
range.browserRanges_.push(selection.getRangeAt(i));
}
return range;
};
/**
* Creates a new range wrapper from the given browser ranges. Do not
* use this method directly - please use goog.dom.Range.createFrom* instead.
* @param {Array.} browserRanges The browser ranges.
* @return {goog.dom.MultiRange} A range wrapper object.
*/
goog.dom.MultiRange.createFromBrowserRanges = function(browserRanges) {
var range = new goog.dom.MultiRange();
range.browserRanges_ = goog.array.clone(browserRanges);
return range;
};
/**
* Creates a new range wrapper from the given goog.dom.TextRange objects. Do
* not use this method directly - please use goog.dom.Range.createFrom* instead.
* @param {Array.} textRanges The text range objects.
* @return {goog.dom.MultiRange} A range wrapper object.
*/
goog.dom.MultiRange.createFromTextRanges = function(textRanges) {
var range = new goog.dom.MultiRange();
range.ranges_ = textRanges;
range.browserRanges_ = textRanges.map(function(range) { // modified
return range.getBrowserRangeObject();
});
return range;
};
// Method implementations
/**
* Clears cached values. Should be called whenever this.browserRanges_ is
* modified.
* @private
*/
goog.dom.MultiRange.prototype.clearCachedValues_ = function() {
this.ranges_ = [];
this.sortedRanges_ = _NULL;
this.container_ = _NULL;
};
/**
* @return {goog.dom.MultiRange} A clone of this range.
*/
goog.dom.MultiRange.prototype.clone = function() {
return goog.dom.MultiRange.createFromBrowserRanges(this.browserRanges_);
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getType = function() {
return goog.dom.RangeType.MULTI;
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getBrowserRangeObject = function() {
// NOTE(robbyw): This method does not make sense for multi-ranges.
return this.browserRanges_[0];
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.setBrowserRangeObject = function(nativeRange) {
// TODO(robbyw): Look in to adding setBrowserSelectionObject.
return _FALSE;
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getTextRangeCount = function() {
return this.browserRanges_.length;
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getTextRange = function(i) {
if (!this.ranges_[i]) {
this.ranges_[i] = goog.dom.TextRange.createFromBrowserRange(
this.browserRanges_[i]);
}
return this.ranges_[i];
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getContainer = function() {
if (!this.container_) {
var nodes = [];
for (var i = 0, len = this.getTextRangeCount(); i < len; i++) {
nodes.push(this.getTextRange(i).getContainer());
}
this.container_ = goog.dom.findCommonAncestor.apply(_NULL, nodes);
}
return this.container_;
};
/**
* @return {Array.} An array of sub-ranges, sorted by start
* point.
*/
goog.dom.MultiRange.prototype.getSortedRanges = function() {
if (!this.sortedRanges_) {
this.sortedRanges_ = this.getTextRanges();
this.sortedRanges_.sort(function(a, b) {
var aStartNode = a.getStartNode();
var aStartOffset = a.getStartOffset();
var bStartNode = b.getStartNode();
var bStartOffset = b.getStartOffset();
if (aStartNode == bStartNode && aStartOffset == bStartOffset) {
return 0;
}
return goog.dom.Range.isReversed(aStartNode, aStartOffset, bStartNode,
bStartOffset) ? 1 : -1;
});
}
return this.sortedRanges_;
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getStartNode = function() {
return this.getSortedRanges()[0].getStartNode();
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getStartOffset = function() {
return this.getSortedRanges()[0].getStartOffset();
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getEndNode = function() {
// NOTE(robbyw): This may return the wrong node if any subranges overlap.
return this.getSortedRanges().last().getEndNode(); // modified
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getEndOffset = function() {
// NOTE(robbyw): This may return the wrong value if any subranges overlap.
return this.getSortedRanges().last().getEndOffset(); // modified
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.isRangeInDocument = function() {
return this.getTextRanges().every(function(range) { // modified
return range.isRangeInDocument();
});
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.isCollapsed = function() {
return this.browserRanges_.length == 0 ||
this.browserRanges_.length == 1 && this.getTextRange(0).isCollapsed();
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getText = function() {
return this.getTextRanges().map(function(range) { // modified
return range.getText();
}).join('');
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getHtmlFragment = function() {
return this.getValidHtml();
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getValidHtml = function() {
// NOTE(robbyw): This does not behave well if the sub-ranges overlap.
return this.getTextRanges().map(function(range) { // modified
return range.getValidHtml();
}).join('');
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.getPastableHtml = function() {
// TODO(robbyw): This should probably do something smart like group TR and TD
// selections in to the same table.
return this.getValidHtml();
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.__iterator__ = function(opt_keys) {
return new goog.dom.MultiRangeIterator(this);
};
// RANGE ACTIONS
/** @inheritDoc */
goog.dom.MultiRange.prototype.select = function() {
var selection = goog.dom.AbstractRange.getBrowserSelectionForWindow(
this.getWindow());
selection.removeAllRanges();
for (var i = 0, len = this.getTextRangeCount(); i < len; i++) {
selection.addRange(this.getTextRange(i).getBrowserRangeObject());
}
};
/** @inheritDoc */
goog.dom.MultiRange.prototype.removeContents = function() {
this.getTextRanges().each(function(range) { // modified
range.removeContents();
});
};
// SAVE/RESTORE
/** @inheritDoc */
goog.dom.MultiRange.prototype.saveUsingDom = function() {
return new goog.dom.DomSavedMultiRange_(this);
};
// RANGE MODIFICATION
/**
* Collapses this range to a single point, either the first or last point
* depending on the parameter. This will result in the number of ranges in this
* multi range becoming 1.
* @param {boolean} toAnchor Whether to collapse to the anchor.
*/
goog.dom.MultiRange.prototype.collapse = function(toAnchor) {
if (!this.isCollapsed()) {
var range = toAnchor ? this.getTextRange(0) : this.getTextRange(
this.getTextRangeCount() - 1);
this.clearCachedValues_();
range.collapse(toAnchor);
this.ranges_ = [range];
this.sortedRanges_ = [range];
this.browserRanges_ = [range.getBrowserRangeObject()];
}
};
// SAVED RANGE OBJECTS
/**
* A SavedRange implementation using DOM endpoints.
* @param {goog.dom.MultiRange} range The range to save.
* @constructor
* @extends {goog.dom.SavedRange}
* @private
*/
goog.dom.DomSavedMultiRange_ = function(range) {
/**
* Array of saved ranges.
* @type {Array.}
* @private
*/
this.savedRanges_ = range.getTextRanges().map(function(range) { // modified
return range.saveUsingDom();
});
};
goog.inherits(goog.dom.DomSavedMultiRange_, goog.dom.SavedRange);
/**
* @return {goog.dom.MultiRange} The restored range.
*/
goog.dom.DomSavedMultiRange_.prototype.restoreInternal = function() {
var ranges = this.savedRanges_.map(function(savedRange) { // modified
return savedRange.restore();
});
return goog.dom.MultiRange.createFromTextRanges(ranges);
};
/** @inheritDoc */
goog.dom.DomSavedMultiRange_.prototype.disposeInternal = function() {
goog.dom.DomSavedMultiRange_.superClass_.disposeInternal.call(this);
this.savedRanges_.map(function(savedRange) { // modified
savedRange.dispose();
});
delete this.savedRanges_;
};
// RANGE ITERATION
/**
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
* adds functions to determine the portion of each text node that is selected.
*
* @param {goog.dom.MultiRange} range The range to traverse.
* @constructor
* @extends {goog.dom.RangeIterator}
*/
goog.dom.MultiRangeIterator = function(range) {
if (range) {
this.iterators_ = range.getSortedRanges().map(
function(r) {
return goog.iter.toIterator(r);
});
}
goog.dom.RangeIterator.call(
this, range ? this.getStartNode() : _NULL, _FALSE);
};
goog.inherits(goog.dom.MultiRangeIterator, goog.dom.RangeIterator);
/**
* The list of range iterators left to traverse.
* @type {Array.?}
* @private
*/
goog.dom.MultiRangeIterator.prototype.iterators_ = _NULL;
/**
* The index of the current sub-iterator being traversed.
* @type {number}
* @private
*/
goog.dom.MultiRangeIterator.prototype.currentIdx_ = 0;
/** @inheritDoc */
goog.dom.MultiRangeIterator.prototype.getStartTextOffset = function() {
return this.iterators_[this.currentIdx_].getStartTextOffset();
};
/** @inheritDoc */
goog.dom.MultiRangeIterator.prototype.getEndTextOffset = function() {
return this.iterators_[this.currentIdx_].getEndTextOffset();
};
/** @inheritDoc */
goog.dom.MultiRangeIterator.prototype.getStartNode = function() {
return this.iterators_[0].getStartNode();
};
/** @inheritDoc */
goog.dom.MultiRangeIterator.prototype.getEndNode = function() {
return this.iterators_.last().getEndNode();
};
/** @inheritDoc */
goog.dom.MultiRangeIterator.prototype.isLast = function() {
return this.iterators_[this.currentIdx_].isLast();
};
/** @inheritDoc */
goog.dom.MultiRangeIterator.prototype.next = function() {
/** @preserveTry */
try {
var it = this.iterators_[this.currentIdx_];
var next = it.next();
this.setPosition(it.node, it.tagType, it.depth);
return next;
} catch (ex) {
if (ex !== goog.iter.StopIteration ||
this.iterators_.length - 1 == this.currentIdx_) {
throw ex;
} else {
// In case we got a StopIteration, increment counter and try again.
this.currentIdx_++;
return this.next();
}
}
};
/**
* Replaces this iterator's values with values from another.
* @param {goog.dom.MultiRangeIterator} other The iterator to copy.
* @protected
*/
goog.dom.MultiRangeIterator.prototype.copyFrom = function(other) {
this.iterators_ = goog.array.clone(other.iterators_);
goog.dom.MultiRangeIterator.superClass_.copyFrom.call(this, other);
};
/**
* @return {goog.dom.MultiRangeIterator} An identical iterator.
*/
goog.dom.MultiRangeIterator.prototype.clone = function() {
var copy = new goog.dom.MultiRangeIterator(_NULL);
copy.copyFrom(this);
return copy;
};
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for working with IE control ranges.
*
* @author robbyw@google.com (Robby Walker)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.ControlRange');
goog.provide('goog.dom.ControlRangeIterator');
//goog.require('goog.dom');
//goog.require('goog.dom.AbstractMultiRange');
//goog.require('goog.dom.AbstractRange');
//goog.require('goog.dom.RangeIterator');
//goog.require('goog.dom.RangeType');
//goog.require('goog.dom.SavedRange');
//goog.require('goog.dom.TagWalkType');
//goog.require('goog.dom.TextRange');
//goog.require('goog.iter.StopIteration');
//goog.require('goog.userAgent');
/**
* Create a new control selection with no properties. Do not use this
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
* @constructor
* @extends {goog.dom.AbstractMultiRange}
*/
goog.dom.ControlRange = function() {
};
goog.inherits(goog.dom.ControlRange, goog.dom.AbstractMultiRange);
/**
* Create a new range wrapper from the given browser range object. Do not use
* this method directly - please use goog.dom.Range.createFrom* instead.
* @param {Object} controlRange The browser range object.
* @return {goog.dom.ControlRange} A range wrapper object.
*/
goog.dom.ControlRange.createFromBrowserRange = function(controlRange) {
var range = new goog.dom.ControlRange();
range.range_ = controlRange;
return range;
};
/**
* Create a new range wrapper that selects the given element. Do not use
* this method directly - please use goog.dom.Range.createFrom* instead.
* @param {...Element} var_args The element(s) to select.
* @return {goog.dom.ControlRange} A range wrapper object.
*/
goog.dom.ControlRange.createFromElements = function(var_args) {
var range = goog.dom.getOwnerDocument(arguments[0]).body.createControlRange();
for (var i = 0, len = arguments.length; i < len; i++) {
range.addElement(arguments[i]);
}
return goog.dom.ControlRange.createFromBrowserRange(range);
};
/**
* The IE control range obejct.
* @type {Object}
* @private
*/
goog.dom.ControlRange.prototype.range_ = _NULL;
/**
* Cached list of elements.
* @type {Array.?}
* @private
*/
goog.dom.ControlRange.prototype.elements_ = _NULL;
/**
* Cached sorted list of elements.
* @type {Array.?}
* @private
*/
goog.dom.ControlRange.prototype.sortedElements_ = _NULL;
// Method implementations
/**
* Clear cached values.
* @private
*/
goog.dom.ControlRange.prototype.clearCachedValues_ = function() {
this.elements_ = _NULL;
this.sortedElements_ = _NULL;
};
/**
* @return {goog.dom.ControlRange} A clone of this range.
*/
goog.dom.ControlRange.prototype.clone = function() {
return goog.dom.ControlRange.createFromElements.apply(this,
this.getElements());
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getType = function() {
return goog.dom.RangeType.CONTROL;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getBrowserRangeObject = function() {
return this.range_ || _DOC.body.createControlRange();
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.setBrowserRangeObject = function(nativeRange) {
if (!goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
return _FALSE;
}
this.range_ = nativeRange;
return _TRUE;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getTextRangeCount = function() {
return this.range_ ? this.range_.length : 0;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getTextRange = function(i) {
return goog.dom.TextRange.createFromNodeContents(this.range_.item(i));
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getContainer = function() {
return goog.dom.findCommonAncestor.apply(_NULL, this.getElements());
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getStartNode = function() {
return this.getSortedElements()[0];
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getStartOffset = function() {
return 0;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getEndNode = function() {
var sorted = this.getSortedElements();
var startsLast = /** @type {Node} */ (sorted.last()); // modified
return /** @type {Node} */ (sorted.find(function(el) {
return goog.dom.contains(el, startsLast);
}));
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getEndOffset = function() {
return this.getEndNode().childNodes.length;
};
// TODO(robbyw): Figure out how to unify getElements with TextRange API.
/**
* @return {Array.} Array of elements in the control range.
*/
goog.dom.ControlRange.prototype.getElements = function() {
if (!this.elements_) {
this.elements_ = [];
if (this.range_) {
for (var i = 0; i < this.range_.length; i++) {
this.elements_.push(this.range_.item(i));
}
}
}
return this.elements_;
};
/**
* @return {Array.} Array of elements comprising the control range,
* sorted by document order.
*/
goog.dom.ControlRange.prototype.getSortedElements = function() {
if (!this.sortedElements_) {
this.sortedElements_ = this.getElements().concat();
this.sortedElements_.sort(function(a, b) {
return a.sourceIndex - b.sourceIndex;
});
}
return this.sortedElements_;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.isRangeInDocument = function() {
var returnValue = _FALSE;
try {
returnValue = this.getElements().every(function(element) { // modified
// On IE, this throws an exception when the range is detached.
return goog.userAgent.IE ?
element.parentNode :
goog.dom.contains(element.ownerDocument.body, element);
});
} catch (e) {
// IE sometimes throws Invalid Argument errors for detached elements.
// Note: trying to return a value from the above try block can cause IE
// to crash. It is necessary to use the local returnValue.
}
return returnValue;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.isCollapsed = function() {
return !this.range_ || !this.range_.length;
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getText = function() {
// TODO(robbyw): What about for table selections? Should those have text?
return '';
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getHtmlFragment = function() {
return this.getSortedElements().map(goog.dom.getOuterHtml). // modified
join('');
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getValidHtml = function() {
return this.getHtmlFragment();
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.getPastableHtml =
goog.dom.ControlRange.prototype.getValidHtml;
/** @inheritDoc */
goog.dom.ControlRange.prototype.__iterator__ = function(opt_keys) {
return new goog.dom.ControlRangeIterator(this);
};
// RANGE ACTIONS
/** @inheritDoc */
goog.dom.ControlRange.prototype.select = function() {
if (this.range_) {
this.range_.select();
}
};
/** @inheritDoc */
goog.dom.ControlRange.prototype.removeContents = function() {
// TODO(robbyw): Test implementing with execCommand('Delete')
if (this.range_) {
var nodes = [];
for (var i = 0, len = this.range_.length; i < len; i++) {
nodes.push(this.range_.item(i));
}
nodes.each(goog.dom.removeNode); // modified
this.collapse(_FALSE);
}
};
// SAVE/RESTORE
/** @inheritDoc */
goog.dom.ControlRange.prototype.saveUsingDom = function() {
return new goog.dom.DomSavedControlRange_(this);
};
// RANGE MODIFICATION
/** @inheritDoc */
goog.dom.ControlRange.prototype.collapse = function(toAnchor) {
// TODO(robbyw): Should this return a text range? If so, API needs to change.
this.range_ = _NULL;
this.clearCachedValues_();
};
// SAVED RANGE OBJECTS
/**
* A SavedRange implementation using DOM endpoints.
* @param {goog.dom.ControlRange} range The range to save.
* @constructor
* @extends {goog.dom.SavedRange}
* @private
*/
goog.dom.DomSavedControlRange_ = function(range) {
/**
* The element list.
* @type {Array.}
* @private
*/
this.elements_ = range.getElements();
};
goog.inherits(goog.dom.DomSavedControlRange_, goog.dom.SavedRange);
/**
* @return {goog.dom.ControlRange} The restored range.
*/
goog.dom.DomSavedControlRange_.prototype.restoreInternal = function() {
var doc = this.elements_.length ?
goog.dom.getOwnerDocument(this.elements_[0]) : document;
var controlRange = doc.body.createControlRange();
for (var i = 0, len = this.elements_.length; i < len; i++) {
controlRange.addElement(this.elements_[i]);
}
return goog.dom.ControlRange.createFromBrowserRange(controlRange);
};
/** @inheritDoc */
goog.dom.DomSavedControlRange_.prototype.disposeInternal = function() {
goog.dom.DomSavedControlRange_.superClass_.disposeInternal.call(this);
delete this.elements_;
};
// RANGE ITERATION
/**
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
* adds functions to determine the portion of each text node that is selected.
*
* @param {goog.dom.ControlRange?} range The range to traverse.
* @constructor
* @extends {goog.dom.RangeIterator}
*/
goog.dom.ControlRangeIterator = function(range) {
if (range) {
this.elements_ = range.getSortedElements();
this.startNode_ = this.elements_.shift();
this.endNode_ = /** @type {Node} */ (this.elements_.last()) || // modified
this.startNode_;
}
goog.dom.RangeIterator.call(this, this.startNode_, _FALSE);
};
goog.inherits(goog.dom.ControlRangeIterator, goog.dom.RangeIterator);
/**
* The first node in the selection.
* @type {Node}
* @private
*/
goog.dom.ControlRangeIterator.prototype.startNode_ = _NULL;
/**
* The last node in the selection.
* @type {Node}
* @private
*/
goog.dom.ControlRangeIterator.prototype.endNode_ = _NULL;
/**
* The list of elements left to traverse.
* @type {Array.?}
* @private
*/
goog.dom.ControlRangeIterator.prototype.elements_ = _NULL;
/** @inheritDoc */
goog.dom.ControlRangeIterator.prototype.getStartTextOffset = function() {
return 0;
};
/** @inheritDoc */
goog.dom.ControlRangeIterator.prototype.getEndTextOffset = function() {
return 0;
};
/** @inheritDoc */
goog.dom.ControlRangeIterator.prototype.getStartNode = function() {
return this.startNode_;
};
/** @inheritDoc */
goog.dom.ControlRangeIterator.prototype.getEndNode = function() {
return this.endNode_;
};
/** @inheritDoc */
goog.dom.ControlRangeIterator.prototype.isLast = function() {
return !this.depth && !this.elements_.length;
};
/**
* Move to the next position in the selection.
* Throws {@code goog.iter.StopIteration} when it passes the end of the range.
* @return {Node} The node at the next position.
*/
goog.dom.ControlRangeIterator.prototype.next = function() {
// Iterate over each element in the range, and all of its children.
if (this.isLast()) {
throw goog.iter.StopIteration;
} else if (!this.depth) {
var el = this.elements_.shift();
this.setPosition(el,
goog.dom.TagWalkType.START_TAG,
goog.dom.TagWalkType.START_TAG);
return el;
}
// Call the super function.
return goog.dom.ControlRangeIterator.superClass_.next.call(this);
};
/**
* Replace this iterator's values with values from another.
* @param {goog.dom.ControlRangeIterator} other The iterator to copy.
* @protected
*/
goog.dom.ControlRangeIterator.prototype.copyFrom = function(other) {
this.elements_ = other.elements_;
this.startNode_ = other.startNode_;
this.endNode_ = other.endNode_;
goog.dom.ControlRangeIterator.superClass_.copyFrom.call(this, other);
};
/**
* @return {goog.dom.ControlRangeIterator} An identical iterator.
*/
goog.dom.ControlRangeIterator.prototype.clone = function() {
var copy = new goog.dom.ControlRangeIterator(_NULL);
copy.copyFrom(this);
return copy;
};
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for working with ranges in HTML documents.
*
* @author robbyw@google.com (Robby Walker)
* @author ojan@google.com (Ojan Vafai)
* @author jparent@google.com (Julie Parent)
*/
goog.provide('goog.dom.Range');
//goog.require('goog.dom');
//goog.require('goog.dom.AbstractRange');
//goog.require('goog.dom.ControlRange');
//goog.require('goog.dom.MultiRange');
//goog.require('goog.dom.NodeType');
//goog.require('goog.dom.TextRange');
//goog.require('goog.userAgent');
/**
* Create a new selection from the given browser window's current selection.
* Note that this object does not auto-update if the user changes their
* selection and should be used as a snapshot.
* @param {Window=} opt_win The window to get the selection of. Defaults to the
* window this class was defined in.
* @return {goog.dom.AbstractRange?} A range wrapper object, or null if there
* was an error.
*/
goog.dom.Range.createFromWindow = function(opt_win) {
var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow(
opt_win || window);
return sel && goog.dom.Range.createFromBrowserSelection(sel);
};
/**
* Create a new range wrapper from the given browser selection object. Note
* that this object does not auto-update if the user changes their selection and
* should be used as a snapshot.
* @param {!Object} selection The browser selection object.
* @return {goog.dom.AbstractRange?} A range wrapper object or null if there
* was an error.
*/
goog.dom.Range.createFromBrowserSelection = function(selection) {
var range;
var isReversed = _FALSE;
if (selection.createRange) {
/** @preserveTry */
try {
range = selection.createRange();
} catch (e) {
// Access denied errors can be thrown here in IE if the selection was
// a flash obj or if there are cross domain issues
return _NULL;
}
} else if (selection.rangeCount) {
if (selection.rangeCount > 1) {
return goog.dom.MultiRange.createFromBrowserSelection(
/** @type {Selection} */ (selection));
} else {
range = selection.getRangeAt(0);
isReversed = goog.dom.Range.isReversed(selection.anchorNode,
selection.anchorOffset, selection.focusNode, selection.focusOffset);
}
} else {
return _NULL;
}
return goog.dom.Range.createFromBrowserRange(range, isReversed);
};
/**
* Create a new range wrapper from the given browser range object.
* @param {Range|TextRange} range The browser range object.
* @param {boolean=} opt_isReversed Whether the focus node is before the anchor
* node.
* @return {goog.dom.AbstractRange} A range wrapper object.
*/
goog.dom.Range.createFromBrowserRange = function(range, opt_isReversed) {
// Create an IE control range when appropriate.
return goog.dom.AbstractRange.isNativeControlRange(range) ?
goog.dom.ControlRange.createFromBrowserRange(range) :
goog.dom.TextRange.createFromBrowserRange(range, opt_isReversed);
};
/**
* Create a new range wrapper that selects the given node's text.
* @param {Node} node The node to select.
* @param {boolean=} opt_isReversed Whether the focus node is before the anchor
* node.
* @return {goog.dom.AbstractRange} A range wrapper object.
*/
goog.dom.Range.createFromNodeContents = function(node, opt_isReversed) {
return goog.dom.TextRange.createFromNodeContents(node, opt_isReversed);
};
/**
* Create a new range wrapper that represents a caret at the given node,
* accounting for the given offset. This always creates a TextRange, regardless
* of whether node is an image node or other control range type node.
* @param {Node} node The node to place a caret at.
* @param {number} offset The offset within the node to place the caret at.
* @return {goog.dom.AbstractRange} A range wrapper object.
*/
goog.dom.Range.createCaret = function(node, offset) {
return goog.dom.TextRange.createFromNodes(node, offset, node, offset);
};
/**
* Create a new range wrapper that selects the area between the given nodes,
* accounting for the given offsets.
* @param {Node} startNode The node to start with.
* @param {number} startOffset The offset within the node to start.
* @param {Node} endNode The node to end with.
* @param {number} endOffset The offset within the node to end.
* @return {goog.dom.AbstractRange} A range wrapper object.
*/
goog.dom.Range.createFromNodes = function(startNode, startOffset, endNode,
endOffset) {
return goog.dom.TextRange.createFromNodes(startNode, startOffset, endNode,
endOffset);
};
/**
* Clears the window's selection.
* @param {Window=} opt_win The window to get the selection of. Defaults to the
* window this class was defined in.
*/
goog.dom.Range.clearSelection = function(opt_win) {
var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow(
opt_win || window);
if (!sel) {
return;
}
if (sel.empty) {
// We can't just check that the selection is empty, becuase IE
// sometimes gets confused.
try {
sel.empty();
} catch (e) {
// Emptying an already empty selection throws an exception in IE
}
} else {
sel.removeAllRanges();
}
};
/**
* Returns whether the focus position occurs before the anchor position.
* @param {Node} anchorNode The node to start with.
* @param {number} anchorOffset The offset within the node to start.
* @param {Node} focusNode The node to end with.
* @param {number} focusOffset The offset within the node to end.
* @return {boolean} Whether the focus position occurs before the anchor
* position.
*/
goog.dom.Range.isReversed = function(anchorNode, anchorOffset, focusNode,
focusOffset) {
if (anchorNode == focusNode) {
return focusOffset < anchorOffset;
}
var child;
if (anchorNode.nodeType == goog.dom.NodeType.ELEMENT && anchorOffset) {
child = anchorNode.childNodes[anchorOffset];
if (child) {
anchorNode = child;
anchorOffset = 0;
} else if (goog.dom.contains(anchorNode, focusNode)) {
// If focus node is contained in anchorNode, it must be before the
// end of the node. Hence we are reversed.
return _TRUE;
}
}
if (focusNode.nodeType == goog.dom.NodeType.ELEMENT && focusOffset) {
child = focusNode.childNodes[focusOffset];
if (child) {
focusNode = child;
focusOffset = 0;
} else if (goog.dom.contains(focusNode, anchorNode)) {
// If anchor node is contained in focusNode, it must be before the
// end of the node. Hence we are not reversed.
return _FALSE;
}
}
return (goog.dom.compareNodeOrder(anchorNode, focusNode) ||
anchorOffset - focusOffset) > 0;
};
goog.provide("export_dep");
//goog.require('goog.dom.ControlRange');
//goog.require('goog.dom.MultiRange');
//goog.require('goog.dom.Range');