Path: blob/trunk/third_party/closure/goog/dom/range.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Utilities for working with ranges in HTML documents.16*17* @author [email protected] (Robby Walker)18*/1920goog.provide('goog.dom.Range');2122goog.require('goog.dom');23goog.require('goog.dom.AbstractRange');24goog.require('goog.dom.BrowserFeature');25goog.require('goog.dom.ControlRange');26goog.require('goog.dom.MultiRange');27goog.require('goog.dom.NodeType');28goog.require('goog.dom.TextRange');293031/**32* Create a new selection from the given browser window's current selection.33* Note that this object does not auto-update if the user changes their34* selection and should be used as a snapshot.35* @param {Window=} opt_win The window to get the selection of. Defaults to the36* window this class was defined in.37* @return {goog.dom.AbstractRange?} A range wrapper object, or null if there38* was an error.39*/40goog.dom.Range.createFromWindow = function(opt_win) {41var sel =42goog.dom.AbstractRange.getBrowserSelectionForWindow(opt_win || window);43return sel && goog.dom.Range.createFromBrowserSelection(sel);44};454647/**48* Create a new range wrapper from the given browser selection object. Note49* that this object does not auto-update if the user changes their selection and50* should be used as a snapshot.51* @param {!Object} selection The browser selection object.52* @return {goog.dom.AbstractRange?} A range wrapper object or null if there53* was an error.54*/55goog.dom.Range.createFromBrowserSelection = function(selection) {56var range;57var isReversed = false;58if (selection.createRange) {5960try {61range = selection.createRange();62} catch (e) {63// Access denied errors can be thrown here in IE if the selection was64// a flash obj or if there are cross domain issues65return null;66}67} else if (selection.rangeCount) {68if (selection.rangeCount > 1) {69return goog.dom.MultiRange.createFromBrowserSelection(70/** @type {!Selection} */ (selection));71} else {72range = selection.getRangeAt(0);73isReversed = goog.dom.Range.isReversed(74selection.anchorNode, selection.anchorOffset, selection.focusNode,75selection.focusOffset);76}77} else {78return null;79}8081return goog.dom.Range.createFromBrowserRange(range, isReversed);82};838485/**86* Create a new range wrapper from the given browser range object.87* @param {Range|TextRange} range The browser range object.88* @param {boolean=} opt_isReversed Whether the focus node is before the anchor89* node.90* @return {!goog.dom.AbstractRange} A range wrapper object.91*/92goog.dom.Range.createFromBrowserRange = function(range, opt_isReversed) {93// Create an IE control range when appropriate.94return goog.dom.AbstractRange.isNativeControlRange(range) ?95goog.dom.ControlRange.createFromBrowserRange(range) :96goog.dom.TextRange.createFromBrowserRange(range, opt_isReversed);97};9899100/**101* Create a new range wrapper that selects the given node's text.102* @param {Node} node The node to select.103* @param {boolean=} opt_isReversed Whether the focus node is before the anchor104* node.105* @return {!goog.dom.AbstractRange} A range wrapper object.106*/107goog.dom.Range.createFromNodeContents = function(node, opt_isReversed) {108return goog.dom.TextRange.createFromNodeContents(node, opt_isReversed);109};110111112/**113* Create a new range wrapper that represents a caret at the given node,114* accounting for the given offset. This always creates a TextRange, regardless115* of whether node is an image node or other control range type node.116* @param {Node} node The node to place a caret at.117* @param {number} offset The offset within the node to place the caret at.118* @return {!goog.dom.AbstractRange} A range wrapper object.119*/120goog.dom.Range.createCaret = function(node, offset) {121return goog.dom.TextRange.createFromNodes(node, offset, node, offset);122};123124125/**126* Create a new range wrapper that selects the area between the given nodes,127* accounting for the given offsets.128* @param {Node} anchorNode The node to anchor on.129* @param {number} anchorOffset The offset within the node to anchor on.130* @param {Node} focusNode The node to focus on.131* @param {number} focusOffset The offset within the node to focus on.132* @return {!goog.dom.AbstractRange} A range wrapper object.133*/134goog.dom.Range.createFromNodes = function(135anchorNode, anchorOffset, focusNode, focusOffset) {136return goog.dom.TextRange.createFromNodes(137anchorNode, anchorOffset, focusNode, focusOffset);138};139140141/**142* Clears the window's selection.143* @param {Window=} opt_win The window to get the selection of. Defaults to the144* window this class was defined in.145*/146goog.dom.Range.clearSelection = function(opt_win) {147var sel =148goog.dom.AbstractRange.getBrowserSelectionForWindow(opt_win || window);149if (!sel) {150return;151}152if (sel.empty) {153// We can't just check that the selection is empty, because IE154// sometimes gets confused.155try {156sel.empty();157} catch (e) {158// Emptying an already empty selection throws an exception in IE159}160} else {161try {162sel.removeAllRanges();163} catch (e) {164// This throws in IE9 if the range has been invalidated; for example, if165// the user clicked on an element which disappeared during the event166// handler.167}168}169};170171172/**173* Tests if the window has a selection.174* @param {Window=} opt_win The window to check the selection of. Defaults to175* the window this class was defined in.176* @return {boolean} Whether the window has a selection.177*/178goog.dom.Range.hasSelection = function(opt_win) {179var sel =180goog.dom.AbstractRange.getBrowserSelectionForWindow(opt_win || window);181return !!sel &&182(goog.dom.BrowserFeature.LEGACY_IE_RANGES ? sel.type != 'None' :183!!sel.rangeCount);184};185186187/**188* Returns whether the focus position occurs before the anchor position.189* @param {Node} anchorNode The node to anchor on.190* @param {number} anchorOffset The offset within the node to anchor on.191* @param {Node} focusNode The node to focus on.192* @param {number} focusOffset The offset within the node to focus on.193* @return {boolean} Whether the focus position occurs before the anchor194* position.195*/196goog.dom.Range.isReversed = function(197anchorNode, anchorOffset, focusNode, focusOffset) {198if (anchorNode == focusNode) {199return focusOffset < anchorOffset;200}201var child;202if (anchorNode.nodeType == goog.dom.NodeType.ELEMENT && anchorOffset) {203child = anchorNode.childNodes[anchorOffset];204if (child) {205anchorNode = child;206anchorOffset = 0;207} else if (goog.dom.contains(anchorNode, focusNode)) {208// If focus node is contained in anchorNode, it must be before the209// end of the node. Hence we are reversed.210return true;211}212}213if (focusNode.nodeType == goog.dom.NodeType.ELEMENT && focusOffset) {214child = focusNode.childNodes[focusOffset];215if (child) {216focusNode = child;217focusOffset = 0;218} else if (goog.dom.contains(focusNode, anchorNode)) {219// If anchor node is contained in focusNode, it must be before the220// end of the node. Hence we are not reversed.221return false;222}223}224return (goog.dom.compareNodeOrder(anchorNode, focusNode) ||225anchorOffset - focusOffset) > 0;226};227228229