blob: 3956f3a5fc959eadd491f6a98d1e31f4b595def3 [file] [log] [blame]
// 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)
*/
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;