123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- // 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)
- */
- goog.provide('goog.dom.ControlRange');
- goog.provide('goog.dom.ControlRangeIterator');
- goog.require('goog.array');
- 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}
- * @final
- */
- goog.dom.ControlRange = function() {
- /**
- * The IE control range obejct.
- * @private {Object}
- */
- this.range_ = null;
- /**
- * Cached list of elements.
- * @private {Array<Element>}
- */
- this.elements_ = null;
- /**
- * Cached sorted list of elements.
- * @private {Array<Element>}
- */
- this.sortedElements_ = null;
- };
- 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);
- };
- // Method implementations
- /**
- * Clear cached values.
- * @private
- */
- goog.dom.ControlRange.prototype.clearCachedValues_ = function() {
- this.elements_ = null;
- this.sortedElements_ = null;
- };
- /** @override */
- goog.dom.ControlRange.prototype.clone = function() {
- return goog.dom.ControlRange.createFromElements.apply(
- this, this.getElements());
- };
- /** @override */
- goog.dom.ControlRange.prototype.getType = function() {
- return goog.dom.RangeType.CONTROL;
- };
- /** @override */
- goog.dom.ControlRange.prototype.getBrowserRangeObject = function() {
- return this.range_ || document.body.createControlRange();
- };
- /** @override */
- goog.dom.ControlRange.prototype.setBrowserRangeObject = function(nativeRange) {
- if (!goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
- return false;
- }
- this.range_ = nativeRange;
- return true;
- };
- /** @override */
- goog.dom.ControlRange.prototype.getTextRangeCount = function() {
- return this.range_ ? this.range_.length : 0;
- };
- /** @override */
- goog.dom.ControlRange.prototype.getTextRange = function(i) {
- return goog.dom.TextRange.createFromNodeContents(this.range_.item(i));
- };
- /** @override */
- goog.dom.ControlRange.prototype.getContainer = function() {
- return goog.dom.findCommonAncestor.apply(null, this.getElements());
- };
- /** @override */
- goog.dom.ControlRange.prototype.getStartNode = function() {
- return this.getSortedElements()[0];
- };
- /** @override */
- goog.dom.ControlRange.prototype.getStartOffset = function() {
- return 0;
- };
- /** @override */
- goog.dom.ControlRange.prototype.getEndNode = function() {
- var sorted = this.getSortedElements();
- var startsLast = /** @type {Node} */ (goog.array.peek(sorted));
- return /** @type {Node} */ (goog.array.find(sorted, function(el) {
- return goog.dom.contains(el, startsLast);
- }));
- };
- /** @override */
- goog.dom.ControlRange.prototype.getEndOffset = function() {
- return this.getEndNode().childNodes.length;
- };
- // TODO(robbyw): Figure out how to unify getElements with TextRange API.
- /**
- * @return {!Array<Element>} 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<Element>} 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_;
- };
- /** @override */
- goog.dom.ControlRange.prototype.isRangeInDocument = function() {
- var returnValue = false;
- try {
- returnValue = goog.array.every(this.getElements(), function(element) {
- // 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;
- };
- /** @override */
- goog.dom.ControlRange.prototype.isCollapsed = function() {
- return !this.range_ || !this.range_.length;
- };
- /** @override */
- goog.dom.ControlRange.prototype.getText = function() {
- // TODO(robbyw): What about for table selections? Should those have text?
- return '';
- };
- /** @override */
- goog.dom.ControlRange.prototype.getHtmlFragment = function() {
- return goog.array.map(this.getSortedElements(), goog.dom.getOuterHtml)
- .join('');
- };
- /** @override */
- goog.dom.ControlRange.prototype.getValidHtml = function() {
- return this.getHtmlFragment();
- };
- /** @override */
- goog.dom.ControlRange.prototype.getPastableHtml =
- goog.dom.ControlRange.prototype.getValidHtml;
- /** @override */
- goog.dom.ControlRange.prototype.__iterator__ = function(opt_keys) {
- return new goog.dom.ControlRangeIterator(this);
- };
- // RANGE ACTIONS
- /** @override */
- goog.dom.ControlRange.prototype.select = function() {
- if (this.range_) {
- this.range_.select();
- }
- };
- /** @override */
- 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));
- }
- goog.array.forEach(nodes, goog.dom.removeNode);
- this.collapse(false);
- }
- };
- /** @override */
- goog.dom.ControlRange.prototype.replaceContentsWithNode = function(node) {
- // Control selections have to have the node inserted before removing the
- // selection contents because a collapsed control range doesn't have start or
- // end nodes.
- var result = this.insertNode(node, true);
- if (!this.isCollapsed()) {
- this.removeContents();
- }
- return result;
- };
- // SAVE/RESTORE
- /** @override */
- goog.dom.ControlRange.prototype.saveUsingDom = function() {
- return new goog.dom.DomSavedControlRange_(this);
- };
- // RANGE MODIFICATION
- /** @override */
- 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<Element>}
- * @private
- */
- this.elements_ = range.getElements();
- };
- goog.inherits(goog.dom.DomSavedControlRange_, goog.dom.SavedRange);
- /** @override */
- 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);
- };
- /** @override */
- 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}
- * @final
- */
- goog.dom.ControlRangeIterator = function(range) {
- /**
- * The first node in the selection.
- * @private {Node}
- */
- this.startNode_ = null;
- /**
- * The last node in the selection.
- * @private {Node}
- */
- this.endNode_ = null;
- /**
- * The list of elements left to traverse.
- * @private {Array<Element>?}
- */
- this.elements_ = null;
- if (range) {
- this.elements_ = range.getSortedElements();
- this.startNode_ = this.elements_.shift();
- this.endNode_ = /** @type {Node} */ (goog.array.peek(this.elements_)) ||
- this.startNode_;
- }
- goog.dom.ControlRangeIterator.base(
- this, 'constructor', this.startNode_, false);
- };
- goog.inherits(goog.dom.ControlRangeIterator, goog.dom.RangeIterator);
- /** @override */
- goog.dom.ControlRangeIterator.prototype.getStartTextOffset = function() {
- return 0;
- };
- /** @override */
- goog.dom.ControlRangeIterator.prototype.getEndTextOffset = function() {
- return 0;
- };
- /** @override */
- goog.dom.ControlRangeIterator.prototype.getStartNode = function() {
- return this.startNode_;
- };
- /** @override */
- goog.dom.ControlRangeIterator.prototype.getEndNode = function() {
- return this.endNode_;
- };
- /** @override */
- 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.
- * @override
- */
- 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);
- };
- /** @override */
- 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.
- * @override
- */
- goog.dom.ControlRangeIterator.prototype.clone = function() {
- var copy = new goog.dom.ControlRangeIterator(null);
- copy.copyFrom(this);
- return copy;
- };
|