abstractrange.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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 Interface definitions for working with ranges
  16. * in HTML documents.
  17. *
  18. * @author robbyw@google.com (Robby Walker)
  19. */
  20. goog.provide('goog.dom.AbstractRange');
  21. goog.provide('goog.dom.RangeIterator');
  22. goog.provide('goog.dom.RangeType');
  23. goog.require('goog.dom');
  24. goog.require('goog.dom.NodeType');
  25. goog.require('goog.dom.SavedCaretRange');
  26. goog.require('goog.dom.TagIterator');
  27. goog.require('goog.userAgent');
  28. /**
  29. * Types of ranges.
  30. * @enum {string}
  31. */
  32. goog.dom.RangeType = {
  33. TEXT: 'text',
  34. CONTROL: 'control',
  35. MULTI: 'mutli'
  36. };
  37. /**
  38. * Creates a new selection with no properties. Do not use this constructor -
  39. * use one of the goog.dom.Range.from* methods instead.
  40. * @constructor
  41. */
  42. goog.dom.AbstractRange = function() {};
  43. /**
  44. * Gets the browser native selection object from the given window.
  45. * @param {Window} win The window to get the selection object from.
  46. * @return {Object} The browser native selection object, or null if it could
  47. * not be retrieved.
  48. */
  49. goog.dom.AbstractRange.getBrowserSelectionForWindow = function(win) {
  50. if (win.getSelection) {
  51. // W3C
  52. return win.getSelection();
  53. } else {
  54. // IE
  55. var doc = win.document;
  56. var sel = doc.selection;
  57. if (sel) {
  58. // IE has a bug where it sometimes returns a selection from the wrong
  59. // document. Catching these cases now helps us avoid problems later.
  60. try {
  61. var range = sel.createRange();
  62. // Only TextRanges have a parentElement method.
  63. if (range.parentElement) {
  64. if (range.parentElement().document != doc) {
  65. return null;
  66. }
  67. } else if (
  68. !range.length ||
  69. /** @type {ControlRange} */ (range).item(0).document != doc) {
  70. // For ControlRanges, check that the range has items, and that
  71. // the first item in the range is in the correct document.
  72. return null;
  73. }
  74. } catch (e) {
  75. // If the selection is in the wrong document, and the wrong document is
  76. // in a different domain, IE will throw an exception.
  77. return null;
  78. }
  79. // TODO(user|robbyw) Sometimes IE 6 returns a selection instance
  80. // when there is no selection. This object has a 'type' property equals
  81. // to 'None' and a typeDetail property bound to undefined. Ideally this
  82. // function should not return this instance.
  83. return sel;
  84. }
  85. return null;
  86. }
  87. };
  88. /**
  89. * Tests if the given Object is a controlRange.
  90. * @param {Object} range The range object to test.
  91. * @return {boolean} Whether the given Object is a controlRange.
  92. */
  93. goog.dom.AbstractRange.isNativeControlRange = function(range) {
  94. // For now, tests for presence of a control range function.
  95. return !!range && !!range.addElement;
  96. };
  97. /**
  98. * @return {!goog.dom.AbstractRange} A clone of this range.
  99. */
  100. goog.dom.AbstractRange.prototype.clone = goog.abstractMethod;
  101. /**
  102. * @return {goog.dom.RangeType} The type of range represented by this object.
  103. */
  104. goog.dom.AbstractRange.prototype.getType = goog.abstractMethod;
  105. /**
  106. * @return {Range|TextRange} The native browser range object.
  107. */
  108. goog.dom.AbstractRange.prototype.getBrowserRangeObject = goog.abstractMethod;
  109. /**
  110. * Sets the native browser range object, overwriting any state this range was
  111. * storing.
  112. * @param {Range|TextRange} nativeRange The native browser range object.
  113. * @return {boolean} Whether the given range was accepted. If not, the caller
  114. * will need to call goog.dom.Range.createFromBrowserRange to create a new
  115. * range object.
  116. */
  117. goog.dom.AbstractRange.prototype.setBrowserRangeObject = function(nativeRange) {
  118. return false;
  119. };
  120. /**
  121. * @return {number} The number of text ranges in this range.
  122. */
  123. goog.dom.AbstractRange.prototype.getTextRangeCount = goog.abstractMethod;
  124. /**
  125. * Get the i-th text range in this range. The behavior is undefined if
  126. * i >= getTextRangeCount or i < 0.
  127. * @param {number} i The range number to retrieve.
  128. * @return {goog.dom.TextRange} The i-th text range.
  129. */
  130. goog.dom.AbstractRange.prototype.getTextRange = goog.abstractMethod;
  131. /**
  132. * Gets an array of all text ranges this range is comprised of. For non-multi
  133. * ranges, returns a single element array containing this.
  134. * @return {!Array<goog.dom.TextRange>} Array of text ranges.
  135. */
  136. goog.dom.AbstractRange.prototype.getTextRanges = function() {
  137. var output = [];
  138. for (var i = 0, len = this.getTextRangeCount(); i < len; i++) {
  139. output.push(this.getTextRange(i));
  140. }
  141. return output;
  142. };
  143. /**
  144. * @return {Node} The deepest node that contains the entire range.
  145. */
  146. goog.dom.AbstractRange.prototype.getContainer = goog.abstractMethod;
  147. /**
  148. * Returns the deepest element in the tree that contains the entire range.
  149. * @return {Element} The deepest element that contains the entire range.
  150. */
  151. goog.dom.AbstractRange.prototype.getContainerElement = function() {
  152. var node = this.getContainer();
  153. return /** @type {Element} */ (
  154. node.nodeType == goog.dom.NodeType.ELEMENT ? node : node.parentNode);
  155. };
  156. /**
  157. * @return {Node} The element or text node the range starts in. For text
  158. * ranges, the range comprises all text between the start and end position.
  159. * For other types of range, start and end give bounds of the range but
  160. * do not imply all nodes in those bounds are selected.
  161. */
  162. goog.dom.AbstractRange.prototype.getStartNode = goog.abstractMethod;
  163. /**
  164. * @return {number} The offset into the node the range starts in. For text
  165. * nodes, this is an offset into the node value. For elements, this is
  166. * an offset into the childNodes array.
  167. */
  168. goog.dom.AbstractRange.prototype.getStartOffset = goog.abstractMethod;
  169. /**
  170. * @return {goog.math.Coordinate} The coordinate of the selection start node
  171. * and offset.
  172. */
  173. goog.dom.AbstractRange.prototype.getStartPosition = goog.abstractMethod;
  174. /**
  175. * @return {Node} The element or text node the range ends in.
  176. */
  177. goog.dom.AbstractRange.prototype.getEndNode = goog.abstractMethod;
  178. /**
  179. * @return {number} The offset into the node the range ends in. For text
  180. * nodes, this is an offset into the node value. For elements, this is
  181. * an offset into the childNodes array.
  182. */
  183. goog.dom.AbstractRange.prototype.getEndOffset = goog.abstractMethod;
  184. /**
  185. * @return {goog.math.Coordinate} The coordinate of the selection end
  186. * node and offset.
  187. */
  188. goog.dom.AbstractRange.prototype.getEndPosition = goog.abstractMethod;
  189. /**
  190. * @return {Node} The element or text node the range is anchored at.
  191. */
  192. goog.dom.AbstractRange.prototype.getAnchorNode = function() {
  193. return this.isReversed() ? this.getEndNode() : this.getStartNode();
  194. };
  195. /**
  196. * @return {number} The offset into the node the range is anchored at. For
  197. * text nodes, this is an offset into the node value. For elements, this
  198. * is an offset into the childNodes array.
  199. */
  200. goog.dom.AbstractRange.prototype.getAnchorOffset = function() {
  201. return this.isReversed() ? this.getEndOffset() : this.getStartOffset();
  202. };
  203. /**
  204. * @return {Node} The element or text node the range is focused at - i.e. where
  205. * the cursor is.
  206. */
  207. goog.dom.AbstractRange.prototype.getFocusNode = function() {
  208. return this.isReversed() ? this.getStartNode() : this.getEndNode();
  209. };
  210. /**
  211. * @return {number} The offset into the node the range is focused at - i.e.
  212. * where the cursor is. For text nodes, this is an offset into the node
  213. * value. For elements, this is an offset into the childNodes array.
  214. */
  215. goog.dom.AbstractRange.prototype.getFocusOffset = function() {
  216. return this.isReversed() ? this.getStartOffset() : this.getEndOffset();
  217. };
  218. /**
  219. * @return {boolean} Whether the selection is reversed.
  220. */
  221. goog.dom.AbstractRange.prototype.isReversed = function() {
  222. return false;
  223. };
  224. /**
  225. * @return {!Document} The document this selection is a part of.
  226. */
  227. goog.dom.AbstractRange.prototype.getDocument = function() {
  228. // Using start node in IE was crashing the browser in some cases so use
  229. // getContainer for that browser. It's also faster for IE, but still slower
  230. // than start node for other browsers so we continue to use getStartNode when
  231. // it is not problematic. See bug 1687309.
  232. return goog.dom.getOwnerDocument(
  233. goog.userAgent.IE ? this.getContainer() : this.getStartNode());
  234. };
  235. /**
  236. * @return {!Window} The window this selection is a part of.
  237. */
  238. goog.dom.AbstractRange.prototype.getWindow = function() {
  239. return goog.dom.getWindow(this.getDocument());
  240. };
  241. /**
  242. * Tests if this range contains the given range.
  243. * @param {goog.dom.AbstractRange} range The range to test.
  244. * @param {boolean=} opt_allowPartial If true, the range can be partially
  245. * contained in the selection, otherwise the range must be entirely
  246. * contained.
  247. * @return {boolean} Whether this range contains the given range.
  248. */
  249. goog.dom.AbstractRange.prototype.containsRange = goog.abstractMethod;
  250. /**
  251. * Tests if this range contains the given node.
  252. * @param {Node} node The node to test for.
  253. * @param {boolean=} opt_allowPartial If not set or false, the node must be
  254. * entirely contained in the selection for this function to return true.
  255. * @return {boolean} Whether this range contains the given node.
  256. */
  257. goog.dom.AbstractRange.prototype.containsNode = goog.abstractMethod;
  258. /**
  259. * Tests whether this range is valid (i.e. whether its endpoints are still in
  260. * the document). A range becomes invalid when, after this object was created,
  261. * either one or both of its endpoints are removed from the document. Use of
  262. * an invalid range can lead to runtime errors, particularly in IE.
  263. * @return {boolean} Whether the range is valid.
  264. */
  265. goog.dom.AbstractRange.prototype.isRangeInDocument = goog.abstractMethod;
  266. /**
  267. * @return {boolean} Whether the range is collapsed.
  268. */
  269. goog.dom.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
  270. /**
  271. * @return {string} The text content of the range.
  272. */
  273. goog.dom.AbstractRange.prototype.getText = goog.abstractMethod;
  274. /**
  275. * Returns the HTML fragment this range selects. This is slow on all browsers.
  276. * The HTML fragment may not be valid HTML, for instance if the user selects
  277. * from a to b inclusively in the following html:
  278. *
  279. * &lt;div&gt;a&lt;/div&gt;b
  280. *
  281. * This method will return
  282. *
  283. * a&lt;/div&gt;b
  284. *
  285. * If you need valid HTML, use {@link #getValidHtml} instead.
  286. *
  287. * @return {string} HTML fragment of the range, does not include context
  288. * containing elements.
  289. */
  290. goog.dom.AbstractRange.prototype.getHtmlFragment = goog.abstractMethod;
  291. /**
  292. * Returns valid HTML for this range. This is fast on IE, and semi-fast on
  293. * other browsers.
  294. * @return {string} Valid HTML of the range, including context containing
  295. * elements.
  296. */
  297. goog.dom.AbstractRange.prototype.getValidHtml = goog.abstractMethod;
  298. /**
  299. * Returns pastable HTML for this range. This guarantees that any child items
  300. * that must have specific ancestors will have them, for instance all TDs will
  301. * be contained in a TR in a TBODY in a TABLE and all LIs will be contained in
  302. * a UL or OL as appropriate. This is semi-fast on all browsers.
  303. * @return {string} Pastable HTML of the range, including context containing
  304. * elements.
  305. */
  306. goog.dom.AbstractRange.prototype.getPastableHtml = goog.abstractMethod;
  307. /**
  308. * Returns a RangeIterator over the contents of the range. Regardless of the
  309. * direction of the range, the iterator will move in document order.
  310. * @param {boolean=} opt_keys Unused for this iterator.
  311. * @return {!goog.dom.RangeIterator} An iterator over tags in the range.
  312. */
  313. goog.dom.AbstractRange.prototype.__iterator__ = goog.abstractMethod;
  314. // RANGE ACTIONS
  315. /**
  316. * Sets this range as the selection in its window.
  317. */
  318. goog.dom.AbstractRange.prototype.select = goog.abstractMethod;
  319. /**
  320. * Removes the contents of the range from the document.
  321. */
  322. goog.dom.AbstractRange.prototype.removeContents = goog.abstractMethod;
  323. /**
  324. * Inserts a node before (or after) the range. The range may be disrupted
  325. * beyond recovery because of the way this splits nodes.
  326. * @param {Node} node The node to insert.
  327. * @param {boolean} before True to insert before, false to insert after.
  328. * @return {Node} The node added to the document. This may be different
  329. * than the node parameter because on IE we have to clone it.
  330. */
  331. goog.dom.AbstractRange.prototype.insertNode = goog.abstractMethod;
  332. /**
  333. * Replaces the range contents with (possibly a copy of) the given node. The
  334. * range may be disrupted beyond recovery because of the way this splits nodes.
  335. * @param {Node} node The node to insert.
  336. * @return {Node} The node added to the document. This may be different
  337. * than the node parameter because on IE we have to clone it.
  338. */
  339. goog.dom.AbstractRange.prototype.replaceContentsWithNode = function(node) {
  340. if (!this.isCollapsed()) {
  341. this.removeContents();
  342. }
  343. return this.insertNode(node, true);
  344. };
  345. /**
  346. * Surrounds this range with the two given nodes. The range may be disrupted
  347. * beyond recovery because of the way this splits nodes.
  348. * @param {Element} startNode The node to insert at the start.
  349. * @param {Element} endNode The node to insert at the end.
  350. */
  351. goog.dom.AbstractRange.prototype.surroundWithNodes = goog.abstractMethod;
  352. // SAVE/RESTORE
  353. /**
  354. * Saves the range so that if the start and end nodes are left alone, it can
  355. * be restored.
  356. * @return {!goog.dom.SavedRange} A range representation that can be restored
  357. * as long as the endpoint nodes of the selection are not modified.
  358. */
  359. goog.dom.AbstractRange.prototype.saveUsingDom = goog.abstractMethod;
  360. /**
  361. * Saves the range using HTML carets. As long as the carets remained in the
  362. * HTML, the range can be restored...even when the HTML is copied across
  363. * documents.
  364. * @return {goog.dom.SavedCaretRange?} A range representation that can be
  365. * restored as long as carets are not removed. Returns null if carets
  366. * could not be created.
  367. */
  368. goog.dom.AbstractRange.prototype.saveUsingCarets = function() {
  369. return (this.getStartNode() && this.getEndNode()) ?
  370. new goog.dom.SavedCaretRange(this) :
  371. null;
  372. };
  373. // RANGE MODIFICATION
  374. /**
  375. * Collapses the range to one of its boundary points.
  376. * @param {boolean} toAnchor Whether to collapse to the anchor of the range.
  377. */
  378. goog.dom.AbstractRange.prototype.collapse = goog.abstractMethod;
  379. // RANGE ITERATION
  380. /**
  381. * Subclass of goog.dom.TagIterator that iterates over a DOM range. It
  382. * adds functions to determine the portion of each text node that is selected.
  383. * @param {Node} node The node to start traversal at. When null, creates an
  384. * empty iterator.
  385. * @param {boolean=} opt_reverse Whether to traverse nodes in reverse.
  386. * @constructor
  387. * @extends {goog.dom.TagIterator}
  388. */
  389. goog.dom.RangeIterator = function(node, opt_reverse) {
  390. goog.dom.TagIterator.call(this, node, opt_reverse, true);
  391. };
  392. goog.inherits(goog.dom.RangeIterator, goog.dom.TagIterator);
  393. /**
  394. * @return {number} The offset into the current node, or -1 if the current node
  395. * is not a text node.
  396. */
  397. goog.dom.RangeIterator.prototype.getStartTextOffset = goog.abstractMethod;
  398. /**
  399. * @return {number} The end offset into the current node, or -1 if the current
  400. * node is not a text node.
  401. */
  402. goog.dom.RangeIterator.prototype.getEndTextOffset = goog.abstractMethod;
  403. /**
  404. * @return {Node} node The iterator's start node.
  405. */
  406. goog.dom.RangeIterator.prototype.getStartNode = goog.abstractMethod;
  407. /**
  408. * @return {Node} The iterator's end node.
  409. */
  410. goog.dom.RangeIterator.prototype.getEndNode = goog.abstractMethod;
  411. /**
  412. * @return {boolean} Whether a call to next will fail.
  413. */
  414. goog.dom.RangeIterator.prototype.isLast = goog.abstractMethod;