353 lines
12 KiB
JavaScript
353 lines
12 KiB
JavaScript
// 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.array');
|
|
goog.require('goog.asserts');
|
|
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.math.Coordinate');
|
|
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;
|
|
|
|
|
|
/**
|
|
* @return {goog.math.Coordinate} The coordinate of the selection start node
|
|
* and offset.
|
|
*/
|
|
goog.dom.browserrange.AbstractRange.prototype.getStartPosition = function() {
|
|
goog.asserts.assert(this.range_.getClientRects,
|
|
'Getting selection coordinates is not supported.');
|
|
|
|
var rects = this.range_.getClientRects();
|
|
if (rects.length) {
|
|
return new goog.math.Coordinate(rects[0]['left'], rects[0]['top']);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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;
|
|
|
|
|
|
/**
|
|
* @return {goog.math.Coordinate} The coordinate of the selection end node
|
|
* and offset.
|
|
*/
|
|
goog.dom.browserrange.AbstractRange.prototype.getEndPosition = function() {
|
|
goog.asserts.assert(this.range_.getClientRects,
|
|
'Getting selection coordinates is not supported.');
|
|
|
|
var rects = this.range_.getClientRects();
|
|
if (rects.length) {
|
|
var lastRect = goog.array.peek(rects);
|
|
return new goog.math.Coordinate(lastRect['right'], lastRect['bottom']);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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 "<li>" 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;
|