jquery.caret.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /*
  2. Implement Github like autocomplete mentions
  3. http://ichord.github.com/At.js
  4. Copyright (c) 2013 chord.luo@gmail.com
  5. Licensed under the MIT license.
  6. */
  7. /*
  8. 本插件操作 textarea 或者 input 内的插入符
  9. 只实现了获得插入符在文本框中的位置,我设置
  10. 插入符的位置.
  11. */
  12. (function() {
  13. (function(factory) {
  14. if (typeof define === 'function' && define.amd) {
  15. return define(['jquery'], factory);
  16. } else {
  17. return factory(window.jQuery);
  18. }
  19. })(function($) {
  20. "use strict";
  21. var Caret, Mirror, methods, pluginName;
  22. pluginName = 'caret';
  23. Caret = (function() {
  24. function Caret($inputor) {
  25. this.$inputor = $inputor;
  26. this.domInputor = this.$inputor[0];
  27. }
  28. Caret.prototype.getPos = function() {
  29. var end, endRange, inputor, len, normalizedValue, pos, range, start, textInputRange;
  30. inputor = this.domInputor;
  31. inputor.focus();
  32. if (document.selection) {
  33. /*
  34. #assume we select "HATE" in the inputor such as textarea -> { }.
  35. * start end-point.
  36. * /
  37. * < I really [HATE] IE > between the brackets is the selection range.
  38. * \
  39. * end end-point.
  40. */
  41. range = document.selection.createRange();
  42. pos = 0;
  43. if (range && range.parentElement() === inputor) {
  44. normalizedValue = inputor.value.replace(/\r\n/g, "\n");
  45. /* SOMETIME !!!
  46. "/r/n" is counted as two char.
  47. one line is two, two will be four. balalala.
  48. so we have to using the normalized one's length.;
  49. */
  50. len = normalizedValue.length;
  51. /*
  52. <[ I really HATE IE ]>:
  53. the whole content in the inputor will be the textInputRange.
  54. */
  55. textInputRange = inputor.createTextRange();
  56. /* _here must be the position of bookmark.
  57. /
  58. <[ I really [HATE] IE ]>
  59. [---------->[ ] : this is what moveToBookmark do.
  60. < I really [[HATE] IE ]> : here is result.
  61. \ two brackets in should be in line.
  62. */
  63. textInputRange.moveToBookmark(range.getBookmark());
  64. endRange = inputor.createTextRange();
  65. /* [--------------------->[] : if set false all end-point goto end.
  66. < I really [[HATE] IE []]>
  67. */
  68. endRange.collapse(false);
  69. /*
  70. ___VS____
  71. / \
  72. < I really [[HATE] IE []]>
  73. \_endRange end-point.
  74. " > -1" mean the start end-point will be the same or right to the end end-point
  75. * simplelly, all in the end.
  76. */
  77. if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
  78. start = end = len;
  79. } else {
  80. /*
  81. I really |HATE] IE ]>
  82. <-|
  83. I really[ [HATE] IE ]>
  84. <-[
  85. I reall[y [HATE] IE ]>
  86. will return how many unit have moved.
  87. */
  88. start = -textInputRange.moveStart("character", -len);
  89. end = -textInputRange.moveEnd("character", -len);
  90. }
  91. }
  92. } else {
  93. start = inputor.selectionStart;
  94. }
  95. return start;
  96. };
  97. Caret.prototype.setPos = function(pos) {
  98. var inputor, range;
  99. inputor = this.domInputor;
  100. if (document.selection) {
  101. range = inputor.createTextRange();
  102. range.move("character", pos);
  103. return range.select();
  104. } else {
  105. return inputor.setSelectionRange(pos, pos);
  106. }
  107. };
  108. Caret.prototype.getPosition = function(pos) {
  109. var $inputor, at_rect, format, h, html, mirror, start_range, x, y;
  110. $inputor = this.$inputor;
  111. format = function(value) {
  112. return value.replace(/</g, '&lt').replace(/>/g, '&gt').replace(/`/g, '&#96').replace(/"/g, '&quot').replace(/\r\n|\r|\n/g, "<br />");
  113. };
  114. pos = pos || this.getPos();
  115. start_range = $inputor.val().slice(0, pos);
  116. html = "<span>" + format(start_range) + "</span>";
  117. html += "<span id='caret'>|</span>";
  118. mirror = new Mirror($inputor);
  119. at_rect = mirror.create(html).rect();
  120. x = at_rect.left - $inputor.scrollLeft();
  121. y = at_rect.top - $inputor.scrollTop();
  122. h = at_rect.height;
  123. return {
  124. left: x,
  125. top: y,
  126. height: h
  127. };
  128. };
  129. Caret.prototype.getOffset = function(pos) {
  130. var $inputor, h, offset, position, range, x, y;
  131. $inputor = this.$inputor;
  132. if (document.selection) {
  133. range = this.domInputor.createTextRange();
  134. if (pos) {
  135. range.move('character', pos);
  136. }
  137. x = range.boundingLeft + $inputor.scrollLeft();
  138. y = range.boundingTop + $(window).scrollTop() + $inputor.scrollTop();
  139. h = range.boundingHeight;
  140. } else {
  141. offset = $inputor.offset();
  142. position = this.getPosition(pos);
  143. x = offset.left + position.left;
  144. y = offset.top + position.top;
  145. h = position.height;
  146. }
  147. return {
  148. left: x,
  149. top: y,
  150. height: h
  151. };
  152. };
  153. return Caret;
  154. })();
  155. Mirror = (function() {
  156. Mirror.prototype.css_attr = ["overflowY", "height", "width", "paddingTop", "paddingLeft", "paddingRight", "paddingBottom", "marginTop", "marginLeft", "marginRight", "marginBottom", "fontFamily", "borderStyle", "borderWidth", "wordWrap", "fontSize", "lineHeight", "overflowX", "text-align"];
  157. function Mirror($inputor) {
  158. this.$inputor = $inputor;
  159. }
  160. Mirror.prototype.mirrorCss = function() {
  161. var css,
  162. _this = this;
  163. css = {
  164. position: 'absolute',
  165. left: -9999,
  166. top: 0,
  167. zIndex: -20000,
  168. 'white-space': 'pre-wrap'
  169. };
  170. $.each(this.css_attr, function(i, p) {
  171. return css[p] = _this.$inputor.css(p);
  172. });
  173. return css;
  174. };
  175. Mirror.prototype.create = function(html) {
  176. this.$mirror = $('<div></div>');
  177. this.$mirror.css(this.mirrorCss());
  178. this.$mirror.html(html);
  179. this.$inputor.after(this.$mirror);
  180. return this;
  181. };
  182. Mirror.prototype.rect = function() {
  183. var $flag, pos, rect;
  184. $flag = this.$mirror.find("#caret");
  185. pos = $flag.position();
  186. rect = {
  187. left: pos.left,
  188. top: pos.top,
  189. height: $flag.height()
  190. };
  191. this.$mirror.remove();
  192. return rect;
  193. };
  194. return Mirror;
  195. })();
  196. methods = {
  197. pos: function(pos) {
  198. if (pos) {
  199. return this.setPos(pos);
  200. } else {
  201. return this.getPos();
  202. }
  203. },
  204. position: function(pos) {
  205. return this.getPosition(pos);
  206. },
  207. offset: function(pos) {
  208. return this.getOffset(pos);
  209. }
  210. };
  211. return $.fn.caret = function(method) {
  212. var caret;
  213. caret = new Caret(this);
  214. if (methods[method]) {
  215. return methods[method].apply(caret, Array.prototype.slice.call(arguments, 1));
  216. } else {
  217. return $.error("Method " + method + " does not exist on jQuery.caret");
  218. }
  219. };
  220. });
  221. }).call(this);