abstractrange.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Definition of the browser range interface.
  16. *
  17. * DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
  18. *
  19. * @author robbyw@google.com (Robby Walker)
  20. */
  21. goog.provide('goog.dom.browserrange.AbstractRange');
  22. goog.require('goog.array');
  23. goog.require('goog.asserts');
  24. goog.require('goog.dom');
  25. goog.require('goog.dom.NodeType');
  26. goog.require('goog.dom.RangeEndpoint');
  27. goog.require('goog.dom.TagName');
  28. goog.require('goog.dom.TextRangeIterator');
  29. goog.require('goog.iter');
  30. goog.require('goog.math.Coordinate');
  31. goog.require('goog.string');
  32. goog.require('goog.string.StringBuffer');
  33. goog.require('goog.userAgent');
  34. /**
  35. * The constructor for abstract ranges. Don't call this from subclasses.
  36. * @constructor
  37. */
  38. goog.dom.browserrange.AbstractRange = function() {};
  39. /**
  40. * @return {goog.dom.browserrange.AbstractRange} A clone of this range.
  41. */
  42. goog.dom.browserrange.AbstractRange.prototype.clone = goog.abstractMethod;
  43. /**
  44. * Returns the browser native implementation of the range. Please refrain from
  45. * using this function - if you find you need the range please add wrappers for
  46. * the functionality you need rather than just using the native range.
  47. * @return {Range|TextRange} The browser native range object.
  48. */
  49. goog.dom.browserrange.AbstractRange.prototype.getBrowserRange =
  50. goog.abstractMethod;
  51. /**
  52. * Returns the deepest node in the tree that contains the entire range.
  53. * @return {Node} The deepest node that contains the entire range.
  54. */
  55. goog.dom.browserrange.AbstractRange.prototype.getContainer =
  56. goog.abstractMethod;
  57. /**
  58. * Returns the node the range starts in.
  59. * @return {Node} The element or text node the range starts in.
  60. */
  61. goog.dom.browserrange.AbstractRange.prototype.getStartNode =
  62. goog.abstractMethod;
  63. /**
  64. * Returns the offset into the node the range starts in.
  65. * @return {number} The offset into the node the range starts in. For text
  66. * nodes, this is an offset into the node value. For elements, this is
  67. * an offset into the childNodes array.
  68. */
  69. goog.dom.browserrange.AbstractRange.prototype.getStartOffset =
  70. goog.abstractMethod;
  71. /**
  72. * @return {goog.math.Coordinate} The coordinate of the selection start node
  73. * and offset.
  74. */
  75. goog.dom.browserrange.AbstractRange.prototype.getStartPosition = function() {
  76. return this.getPosition_(true);
  77. };
  78. /**
  79. * Returns the node the range ends in.
  80. * @return {Node} The element or text node the range ends in.
  81. */
  82. goog.dom.browserrange.AbstractRange.prototype.getEndNode = goog.abstractMethod;
  83. /**
  84. * Returns the offset into the node the range ends in.
  85. * @return {number} The offset into the node the range ends in. For text
  86. * nodes, this is an offset into the node value. For elements, this is
  87. * an offset into the childNodes array.
  88. */
  89. goog.dom.browserrange.AbstractRange.prototype.getEndOffset =
  90. goog.abstractMethod;
  91. /**
  92. * @return {goog.math.Coordinate} The coordinate of the selection end node
  93. * and offset.
  94. */
  95. goog.dom.browserrange.AbstractRange.prototype.getEndPosition = function() {
  96. return this.getPosition_(false);
  97. };
  98. /**
  99. * @param {boolean} start Whether to get the position of the start or end.
  100. * @return {goog.math.Coordinate} The coordinate of the selection point.
  101. * @private
  102. */
  103. goog.dom.browserrange.AbstractRange.prototype.getPosition_ = function(start) {
  104. goog.asserts.assert(
  105. this.range_.getClientRects,
  106. 'Getting selection coordinates is not supported.');
  107. var rects = this.range_.getClientRects();
  108. if (rects.length) {
  109. var r = start ? rects[0] : goog.array.peek(rects);
  110. return new goog.math.Coordinate(
  111. start ? r.left : r.right, start ? r.top : r.bottom);
  112. }
  113. return null;
  114. };
  115. /**
  116. * Compares one endpoint of this range with the endpoint of another browser
  117. * native range object.
  118. * @param {Range|TextRange} range The browser native range to compare against.
  119. * @param {goog.dom.RangeEndpoint} thisEndpoint The endpoint of this range
  120. * to compare with.
  121. * @param {goog.dom.RangeEndpoint} otherEndpoint The endpoint of the other
  122. * range to compare with.
  123. * @return {number} 0 if the endpoints are equal, negative if this range
  124. * endpoint comes before the other range endpoint, and positive otherwise.
  125. */
  126. goog.dom.browserrange.AbstractRange.prototype.compareBrowserRangeEndpoints =
  127. goog.abstractMethod;
  128. /**
  129. * Tests if this range contains the given range.
  130. * @param {goog.dom.browserrange.AbstractRange} abstractRange The range to test.
  131. * @param {boolean=} opt_allowPartial If not set or false, the range must be
  132. * entirely contained in the selection for this function to return true.
  133. * @return {boolean} Whether this range contains the given range.
  134. */
  135. goog.dom.browserrange.AbstractRange.prototype.containsRange = function(
  136. abstractRange, opt_allowPartial) {
  137. // IE sometimes misreports the boundaries for collapsed ranges. So if the
  138. // other range is collapsed, make sure the whole range is contained. This is
  139. // logically equivalent, and works around IE's bug.
  140. var checkPartial = opt_allowPartial && !abstractRange.isCollapsed();
  141. var range = abstractRange.getBrowserRange();
  142. var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
  143. try {
  144. if (checkPartial) {
  145. // There are two ways to not overlap. Being before, and being after.
  146. // Before is represented by this.end before range.start: comparison < 0.
  147. // After is represented by this.start after range.end: comparison > 0.
  148. // The below is the negation of not overlapping.
  149. return this.compareBrowserRangeEndpoints(range, end, start) >= 0 &&
  150. this.compareBrowserRangeEndpoints(range, start, end) <= 0;
  151. } else {
  152. // Return true if this range bounds the parameter range from both sides.
  153. return this.compareBrowserRangeEndpoints(range, end, end) >= 0 &&
  154. this.compareBrowserRangeEndpoints(range, start, start) <= 0;
  155. }
  156. } catch (e) {
  157. if (!goog.userAgent.IE) {
  158. throw e;
  159. }
  160. // IE sometimes throws exceptions when one range is invalid, i.e. points
  161. // to a node that has been removed from the document. Return false in this
  162. // case.
  163. return false;
  164. }
  165. };
  166. /**
  167. * Tests if this range contains the given node.
  168. * @param {Node} node The node to test.
  169. * @param {boolean=} opt_allowPartial If not set or false, the node must be
  170. * entirely contained in the selection for this function to return true.
  171. * @return {boolean} Whether this range contains the given node.
  172. * @suppress {missingRequire} Cannot depend on goog.dom.browserrange because it
  173. * creates a circular dependency.
  174. */
  175. goog.dom.browserrange.AbstractRange.prototype.containsNode = function(
  176. node, opt_allowPartial) {
  177. /** @suppress {missingRequire} Circular dep with browserrange */
  178. return this.containsRange(
  179. goog.dom.browserrange.createRangeFromNodeContents(node),
  180. opt_allowPartial);
  181. };
  182. /**
  183. * Tests if the selection is collapsed - i.e. is just a caret.
  184. * @return {boolean} Whether the range is collapsed.
  185. */
  186. goog.dom.browserrange.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
  187. /**
  188. * @return {string} The text content of the range.
  189. */
  190. goog.dom.browserrange.AbstractRange.prototype.getText = goog.abstractMethod;
  191. /**
  192. * Returns the HTML fragment this range selects. This is slow on all browsers.
  193. * @return {string} HTML fragment of the range, does not include context
  194. * containing elements.
  195. */
  196. goog.dom.browserrange.AbstractRange.prototype.getHtmlFragment = function() {
  197. var output = new goog.string.StringBuffer();
  198. goog.iter.forEach(this, function(node, ignore, it) {
  199. if (node.nodeType == goog.dom.NodeType.TEXT) {
  200. output.append(
  201. goog.string.htmlEscape(
  202. node.nodeValue.substring(
  203. it.getStartTextOffset(), it.getEndTextOffset())));
  204. } else if (node.nodeType == goog.dom.NodeType.ELEMENT) {
  205. if (it.isEndTag()) {
  206. if (goog.dom.canHaveChildren(node)) {
  207. output.append('</' + node.tagName + '>');
  208. }
  209. } else {
  210. var shallow = node.cloneNode(false);
  211. var html = goog.dom.getOuterHtml(shallow);
  212. if (goog.userAgent.IE && node.tagName == goog.dom.TagName.LI) {
  213. // For an LI, IE just returns "<li>" with no closing tag
  214. output.append(html);
  215. } else {
  216. var index = html.lastIndexOf('<');
  217. output.append(index ? html.substr(0, index) : html);
  218. }
  219. }
  220. }
  221. }, this);
  222. return output.toString();
  223. };
  224. /**
  225. * Returns valid HTML for this range. This is fast on IE, and semi-fast on
  226. * other browsers.
  227. * @return {string} Valid HTML of the range, including context containing
  228. * elements.
  229. */
  230. goog.dom.browserrange.AbstractRange.prototype.getValidHtml =
  231. goog.abstractMethod;
  232. /**
  233. * Returns a RangeIterator over the contents of the range. Regardless of the
  234. * direction of the range, the iterator will move in document order.
  235. * @param {boolean=} opt_keys Unused for this iterator.
  236. * @return {!goog.dom.RangeIterator} An iterator over tags in the range.
  237. */
  238. goog.dom.browserrange.AbstractRange.prototype.__iterator__ = function(
  239. opt_keys) {
  240. return new goog.dom.TextRangeIterator(
  241. this.getStartNode(), this.getStartOffset(), this.getEndNode(),
  242. this.getEndOffset());
  243. };
  244. // SELECTION MODIFICATION
  245. /**
  246. * Set this range as the selection in its window.
  247. * @param {boolean=} opt_reverse Whether to select the range in reverse,
  248. * if possible.
  249. */
  250. goog.dom.browserrange.AbstractRange.prototype.select = goog.abstractMethod;
  251. /**
  252. * Removes the contents of the range from the document. As a side effect, the
  253. * selection will be collapsed. The behavior of content removal is normalized
  254. * across browsers. For instance, IE sometimes creates extra text nodes that
  255. * a W3C browser does not. That behavior is corrected for.
  256. */
  257. goog.dom.browserrange.AbstractRange.prototype.removeContents =
  258. goog.abstractMethod;
  259. /**
  260. * Surrounds the text range with the specified element (on Mozilla) or with a
  261. * clone of the specified element (on IE). Returns a reference to the
  262. * surrounding element if the operation was successful; returns null if the
  263. * operation failed.
  264. * @param {Element} element The element with which the selection is to be
  265. * surrounded.
  266. * @return {Element} The surrounding element (same as the argument on Mozilla,
  267. * but not on IE), or null if unsuccessful.
  268. */
  269. goog.dom.browserrange.AbstractRange.prototype.surroundContents =
  270. goog.abstractMethod;
  271. /**
  272. * Inserts a node before (or after) the range. The range may be disrupted
  273. * beyond recovery because of the way this splits nodes.
  274. * @param {Node} node The node to insert.
  275. * @param {boolean} before True to insert before, false to insert after.
  276. * @return {Node} The node added to the document. This may be different
  277. * than the node parameter because on IE we have to clone it.
  278. */
  279. goog.dom.browserrange.AbstractRange.prototype.insertNode = goog.abstractMethod;
  280. /**
  281. * Surrounds this range with the two given nodes. The range may be disrupted
  282. * beyond recovery because of the way this splits nodes.
  283. * @param {Element} startNode The node to insert at the start.
  284. * @param {Element} endNode The node to insert at the end.
  285. */
  286. goog.dom.browserrange.AbstractRange.prototype.surroundWithNodes =
  287. goog.abstractMethod;
  288. /**
  289. * Collapses the range to one of its boundary points.
  290. * @param {boolean} toStart Whether to collapse to the start of the range.
  291. */
  292. goog.dom.browserrange.AbstractRange.prototype.collapse = goog.abstractMethod;