content_editable.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. // NOTE: TextComplete plugin has contenteditable support but it does not work
  2. // fine especially on old IEs.
  3. // Any pull requests are REALLY welcome.
  4. +function ($) {
  5. 'use strict';
  6. // ContentEditable adapter
  7. // =======================
  8. //
  9. // Adapter for contenteditable elements.
  10. function ContentEditable (element, completer, option) {
  11. this.initialize(element, completer, option);
  12. }
  13. $.extend(ContentEditable.prototype, $.fn.textcomplete.Adapter.prototype, {
  14. // Public methods
  15. // --------------
  16. // Update the content with the given value and strategy.
  17. // When an dropdown item is selected, it is executed.
  18. select: function (value, strategy, e) {
  19. var pre = this.getTextFromHeadToCaret();
  20. var sel = window.getSelection()
  21. var range = sel.getRangeAt(0);
  22. var selection = range.cloneRange();
  23. selection.selectNodeContents(range.startContainer);
  24. var content = selection.toString();
  25. var post = content.substring(range.startOffset);
  26. var newSubstr = strategy.replace(value, e);
  27. if (typeof newSubstr !== 'undefined') {
  28. if ($.isArray(newSubstr)) {
  29. post = newSubstr[1] + post;
  30. newSubstr = newSubstr[0];
  31. }
  32. pre = pre.replace(strategy.match, newSubstr);
  33. range.selectNodeContents(range.startContainer);
  34. range.deleteContents();
  35. // create temporary elements
  36. var preWrapper = document.createElement("div");
  37. preWrapper.innerHTML = pre;
  38. var postWrapper = document.createElement("div");
  39. postWrapper.innerHTML = post;
  40. // create the fragment thats inserted
  41. var fragment = document.createDocumentFragment();
  42. var childNode;
  43. var lastOfPre;
  44. while (childNode = preWrapper.firstChild) {
  45. lastOfPre = fragment.appendChild(childNode);
  46. }
  47. while (childNode = postWrapper.firstChild) {
  48. fragment.appendChild(childNode);
  49. }
  50. // insert the fragment & jump behind the last node in "pre"
  51. range.insertNode(fragment);
  52. range.setStartAfter(lastOfPre);
  53. range.collapse(true);
  54. sel.removeAllRanges();
  55. sel.addRange(range);
  56. }
  57. },
  58. // Private methods
  59. // ---------------
  60. // Returns the caret's relative position from the contenteditable's
  61. // left top corner.
  62. //
  63. // Examples
  64. //
  65. // this._getCaretRelativePosition()
  66. // //=> { top: 18, left: 200, lineHeight: 16 }
  67. //
  68. // Dropdown's position will be decided using the result.
  69. _getCaretRelativePosition: function () {
  70. var range = window.getSelection().getRangeAt(0).cloneRange();
  71. var node = document.createElement('span');
  72. range.insertNode(node);
  73. range.selectNodeContents(node);
  74. range.deleteContents();
  75. var $node = $(node);
  76. var position = $node.offset();
  77. position.left -= this.$el.offset().left;
  78. position.top += $node.height() - this.$el.offset().top;
  79. position.lineHeight = $node.height();
  80. $node.remove();
  81. return position;
  82. },
  83. // Returns the string between the first character and the caret.
  84. // Completer will be triggered with the result for start autocompleting.
  85. //
  86. // Example
  87. //
  88. // // Suppose the html is '<b>hello</b> wor|ld' and | is the caret.
  89. // this.getTextFromHeadToCaret()
  90. // // => ' wor' // not '<b>hello</b> wor'
  91. getTextFromHeadToCaret: function () {
  92. var range = window.getSelection().getRangeAt(0);
  93. var selection = range.cloneRange();
  94. selection.selectNodeContents(range.startContainer);
  95. return selection.toString().substring(0, range.startOffset);
  96. }
  97. });
  98. $.fn.textcomplete.ContentEditable = ContentEditable;
  99. }(jQuery);