savedcaretrange.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. // Copyright 2008 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 An API for saving and restoring ranges as HTML carets.
  16. *
  17. * @author nicksantos@google.com (Nick Santos)
  18. */
  19. goog.provide('goog.dom.SavedCaretRange');
  20. goog.require('goog.array');
  21. goog.require('goog.dom');
  22. goog.require('goog.dom.SavedRange');
  23. goog.require('goog.dom.TagName');
  24. goog.require('goog.string');
  25. /**
  26. * A struct for holding context about saved selections.
  27. * This can be used to preserve the selection and restore while the DOM is
  28. * manipulated, or through an asynchronous call. Use goog.dom.Range factory
  29. * methods to obtain an {@see goog.dom.AbstractRange} instance, and use
  30. * {@see goog.dom.AbstractRange#saveUsingCarets} to obtain a SavedCaretRange.
  31. * For editor ranges under content-editable elements or design-mode iframes,
  32. * prefer using {@see goog.editor.range.saveUsingNormalizedCarets}.
  33. * @param {goog.dom.AbstractRange} range The range being saved.
  34. * @constructor
  35. * @extends {goog.dom.SavedRange}
  36. */
  37. goog.dom.SavedCaretRange = function(range) {
  38. goog.dom.SavedRange.call(this);
  39. /**
  40. * The DOM id of the caret at the start of the range.
  41. * @type {string}
  42. * @private
  43. */
  44. this.startCaretId_ = goog.string.createUniqueString();
  45. /**
  46. * The DOM id of the caret at the end of the range.
  47. * @type {string}
  48. * @private
  49. */
  50. this.endCaretId_ = goog.string.createUniqueString();
  51. /**
  52. * Whether the range is reversed (anchor at the end).
  53. * @private {boolean}
  54. */
  55. this.reversed_ = range.isReversed();
  56. /**
  57. * A DOM helper for storing the current document context.
  58. * @type {goog.dom.DomHelper}
  59. * @private
  60. */
  61. this.dom_ = goog.dom.getDomHelper(range.getDocument());
  62. range.surroundWithNodes(this.createCaret_(true), this.createCaret_(false));
  63. };
  64. goog.inherits(goog.dom.SavedCaretRange, goog.dom.SavedRange);
  65. /**
  66. * Gets the range that this SavedCaretRage represents, without selecting it
  67. * or removing the carets from the DOM.
  68. * @return {goog.dom.AbstractRange?} An abstract range.
  69. * @suppress {missingRequire} circular dependency
  70. */
  71. goog.dom.SavedCaretRange.prototype.toAbstractRange = function() {
  72. var range = null;
  73. var startCaret = this.getCaret(true);
  74. var endCaret = this.getCaret(false);
  75. if (startCaret && endCaret) {
  76. range = goog.dom.Range.createFromNodes(startCaret, 0, endCaret, 0);
  77. }
  78. return range;
  79. };
  80. /**
  81. * Gets carets.
  82. * @param {boolean} start If true, returns the start caret. Otherwise, get the
  83. * end caret.
  84. * @return {Element} The start or end caret in the given document.
  85. */
  86. goog.dom.SavedCaretRange.prototype.getCaret = function(start) {
  87. return this.dom_.getElement(start ? this.startCaretId_ : this.endCaretId_);
  88. };
  89. /**
  90. * Removes the carets from the current restoration document.
  91. * @param {goog.dom.AbstractRange=} opt_range A range whose offsets have already
  92. * been adjusted for caret removal; it will be adjusted if it is also
  93. * affected by post-removal operations, such as text node normalization.
  94. * @return {goog.dom.AbstractRange|undefined} The adjusted range, if opt_range
  95. * was provided.
  96. */
  97. goog.dom.SavedCaretRange.prototype.removeCarets = function(opt_range) {
  98. goog.dom.removeNode(this.getCaret(true));
  99. goog.dom.removeNode(this.getCaret(false));
  100. return opt_range;
  101. };
  102. /**
  103. * Sets the document where the range will be restored.
  104. * @param {!Document} doc An HTML document.
  105. */
  106. goog.dom.SavedCaretRange.prototype.setRestorationDocument = function(doc) {
  107. this.dom_.setDocument(doc);
  108. };
  109. /**
  110. * Reconstruct the selection from the given saved range. Removes carets after
  111. * restoring the selection. If restore does not dispose this saved range, it may
  112. * only be restored a second time if innerHTML or some other mechanism is used
  113. * to restore the carets to the dom.
  114. * @return {goog.dom.AbstractRange?} Restored selection.
  115. * @override
  116. * @protected
  117. */
  118. goog.dom.SavedCaretRange.prototype.restoreInternal = function() {
  119. var range = null;
  120. var anchorCaret = this.getCaret(!this.reversed_);
  121. var focusCaret = this.getCaret(this.reversed_);
  122. if (anchorCaret && focusCaret) {
  123. var anchorNode = anchorCaret.parentNode;
  124. var anchorOffset = goog.array.indexOf(anchorNode.childNodes, anchorCaret);
  125. var focusNode = focusCaret.parentNode;
  126. var focusOffset = goog.array.indexOf(focusNode.childNodes, focusCaret);
  127. if (focusNode == anchorNode) {
  128. // Compensate for the start caret being removed.
  129. if (this.reversed_) {
  130. anchorOffset--;
  131. } else {
  132. focusOffset--;
  133. }
  134. }
  135. /** @suppress {missingRequire} circular dependency */
  136. range = goog.dom.Range.createFromNodes(
  137. anchorNode, anchorOffset, focusNode, focusOffset);
  138. range = this.removeCarets(range);
  139. range.select();
  140. } else {
  141. // If only one caret was found, remove it.
  142. this.removeCarets();
  143. }
  144. return range;
  145. };
  146. /**
  147. * Dispose the saved range and remove the carets from the DOM.
  148. * @override
  149. * @protected
  150. */
  151. goog.dom.SavedCaretRange.prototype.disposeInternal = function() {
  152. this.removeCarets();
  153. this.dom_ = null;
  154. };
  155. /**
  156. * Creates a caret element.
  157. * @param {boolean} start If true, creates the start caret. Otherwise,
  158. * creates the end caret.
  159. * @return {!Element} The new caret element.
  160. * @private
  161. */
  162. goog.dom.SavedCaretRange.prototype.createCaret_ = function(start) {
  163. return this.dom_.createDom(
  164. goog.dom.TagName.SPAN,
  165. {'id': start ? this.startCaretId_ : this.endCaretId_});
  166. };
  167. /**
  168. * A regex that will match all saved range carets in a string.
  169. * @type {RegExp}
  170. */
  171. goog.dom.SavedCaretRange.CARET_REGEX = /<span\s+id="?goog_\d+"?><\/span>/ig;
  172. /**
  173. * Returns whether two strings of html are equal, ignoring any saved carets.
  174. * Thus two strings of html whose only difference is the id of their saved
  175. * carets will be considered equal, since they represent html with the
  176. * same selection.
  177. * @param {string} str1 The first string.
  178. * @param {string} str2 The second string.
  179. * @return {boolean} Whether two strings of html are equal, ignoring any
  180. * saved carets.
  181. */
  182. goog.dom.SavedCaretRange.htmlEqual = function(str1, str2) {
  183. return str1 == str2 ||
  184. str1.replace(goog.dom.SavedCaretRange.CARET_REGEX, '') ==
  185. str2.replace(goog.dom.SavedCaretRange.CARET_REGEX, '');
  186. };