1165d101617401d27826f43c649a3590f9f5faf9ee7c81c7a6ea991fc06666e07e8cbb7e951271b63c191557a1f8366f25e8f1370f475d9fd9e53ec44f60d7 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import {KEY_CODES} from './../helpers/unicode';
  2. import {stopImmediatePropagation, isImmediatePropagationStopped} from './../helpers/dom/event';
  3. import {
  4. addClass,
  5. getScrollLeft,
  6. getScrollTop,
  7. hasClass,
  8. isChildOf,
  9. offset,
  10. outerHeight,
  11. outerWidth,
  12. removeClass,
  13. setCaretPosition,
  14. } from './../helpers/dom/element';
  15. import BaseEditor from './_baseEditor';
  16. import EventManager from './../eventManager';
  17. const MobileTextEditor = BaseEditor.prototype.extend();
  18. const domDimensionsCache = {};
  19. /**
  20. * @private
  21. * @editor MobileTextEditor
  22. * @class MobileTextEditor
  23. */
  24. var createControls = function() {
  25. this.controls = {};
  26. this.controls.leftButton = document.createElement('DIV');
  27. this.controls.leftButton.className = 'leftButton';
  28. this.controls.rightButton = document.createElement('DIV');
  29. this.controls.rightButton.className = 'rightButton';
  30. this.controls.upButton = document.createElement('DIV');
  31. this.controls.upButton.className = 'upButton';
  32. this.controls.downButton = document.createElement('DIV');
  33. this.controls.downButton.className = 'downButton';
  34. for (var button in this.controls) {
  35. if (Object.prototype.hasOwnProperty.call(this.controls, button)) {
  36. this.positionControls.appendChild(this.controls[button]);
  37. }
  38. }
  39. };
  40. MobileTextEditor.prototype.valueChanged = function() {
  41. return this.initValue != this.getValue();
  42. };
  43. MobileTextEditor.prototype.init = function() {
  44. var that = this;
  45. this.eventManager = new EventManager(this.instance);
  46. this.createElements();
  47. this.bindEvents();
  48. this.instance.addHook('afterDestroy', () => {
  49. that.destroy();
  50. });
  51. };
  52. MobileTextEditor.prototype.getValue = function() {
  53. return this.TEXTAREA.value;
  54. };
  55. MobileTextEditor.prototype.setValue = function(newValue) {
  56. this.initValue = newValue;
  57. this.TEXTAREA.value = newValue;
  58. };
  59. MobileTextEditor.prototype.createElements = function() {
  60. this.editorContainer = document.createElement('DIV');
  61. this.editorContainer.className = 'htMobileEditorContainer';
  62. this.cellPointer = document.createElement('DIV');
  63. this.cellPointer.className = 'cellPointer';
  64. this.moveHandle = document.createElement('DIV');
  65. this.moveHandle.className = 'moveHandle';
  66. this.inputPane = document.createElement('DIV');
  67. this.inputPane.className = 'inputs';
  68. this.positionControls = document.createElement('DIV');
  69. this.positionControls.className = 'positionControls';
  70. this.TEXTAREA = document.createElement('TEXTAREA');
  71. addClass(this.TEXTAREA, 'handsontableInput');
  72. this.inputPane.appendChild(this.TEXTAREA);
  73. this.editorContainer.appendChild(this.cellPointer);
  74. this.editorContainer.appendChild(this.moveHandle);
  75. this.editorContainer.appendChild(this.inputPane);
  76. this.editorContainer.appendChild(this.positionControls);
  77. createControls.call(this);
  78. document.body.appendChild(this.editorContainer);
  79. };
  80. MobileTextEditor.prototype.onBeforeKeyDown = function(event) {
  81. var instance = this;
  82. var that = instance.getActiveEditor();
  83. if (event.target !== that.TEXTAREA || isImmediatePropagationStopped(event)) {
  84. return;
  85. }
  86. switch (event.keyCode) {
  87. case KEY_CODES.ENTER:
  88. that.close();
  89. event.preventDefault(); // don't add newline to field
  90. break;
  91. case KEY_CODES.BACKSPACE:
  92. stopImmediatePropagation(event); // backspace, delete, home, end should only work locally when cell is edited (not in table context)
  93. break;
  94. default:
  95. break;
  96. }
  97. };
  98. MobileTextEditor.prototype.open = function() {
  99. this.instance.addHook('beforeKeyDown', this.onBeforeKeyDown);
  100. addClass(this.editorContainer, 'active');
  101. removeClass(this.cellPointer, 'hidden');
  102. this.updateEditorPosition();
  103. };
  104. MobileTextEditor.prototype.focus = function() {
  105. this.TEXTAREA.focus();
  106. setCaretPosition(this.TEXTAREA, this.TEXTAREA.value.length);
  107. };
  108. MobileTextEditor.prototype.close = function() {
  109. this.TEXTAREA.blur();
  110. this.instance.removeHook('beforeKeyDown', this.onBeforeKeyDown);
  111. removeClass(this.editorContainer, 'active');
  112. };
  113. MobileTextEditor.prototype.scrollToView = function() {
  114. var coords = this.instance.getSelectedRange().highlight;
  115. this.instance.view.scrollViewport(coords);
  116. };
  117. MobileTextEditor.prototype.hideCellPointer = function() {
  118. if (!hasClass(this.cellPointer, 'hidden')) {
  119. addClass(this.cellPointer, 'hidden');
  120. }
  121. };
  122. MobileTextEditor.prototype.updateEditorPosition = function(x, y) {
  123. if (x && y) {
  124. x = parseInt(x, 10);
  125. y = parseInt(y, 10);
  126. this.editorContainer.style.top = `${y}px`;
  127. this.editorContainer.style.left = `${x}px`;
  128. } else {
  129. var selection = this.instance.getSelected(),
  130. selectedCell = this.instance.getCell(selection[0], selection[1]);
  131. // cache sizes
  132. if (!domDimensionsCache.cellPointer) {
  133. domDimensionsCache.cellPointer = {
  134. height: outerHeight(this.cellPointer),
  135. width: outerWidth(this.cellPointer)
  136. };
  137. }
  138. if (!domDimensionsCache.editorContainer) {
  139. domDimensionsCache.editorContainer = {
  140. width: outerWidth(this.editorContainer)
  141. };
  142. }
  143. if (selectedCell !== undefined) {
  144. var scrollLeft = this.instance.view.wt.wtOverlays.leftOverlay
  145. .trimmingContainer == window ? 0 : getScrollLeft(this.instance.view.wt.wtOverlays.leftOverlay.holder);
  146. var scrollTop = this.instance.view.wt.wtOverlays.topOverlay
  147. .trimmingContainer == window ? 0 : getScrollTop(this.instance.view.wt.wtOverlays.topOverlay.holder);
  148. var selectedCellOffset = offset(selectedCell),
  149. selectedCellWidth = outerWidth(selectedCell),
  150. currentScrollPosition = {
  151. x: scrollLeft,
  152. y: scrollTop
  153. };
  154. this.editorContainer.style.top = `${parseInt(selectedCellOffset.top + outerHeight(selectedCell) -
  155. currentScrollPosition.y + domDimensionsCache.cellPointer.height, 10)}px`;
  156. this.editorContainer.style.left = `${parseInt((window.innerWidth / 2) - (domDimensionsCache.editorContainer.width / 2), 10)}px`;
  157. if (selectedCellOffset.left + (selectedCellWidth / 2) > parseInt(this.editorContainer.style.left, 10) +
  158. domDimensionsCache.editorContainer.width) {
  159. this.editorContainer.style.left = `${window.innerWidth - domDimensionsCache.editorContainer.width}px`;
  160. } else if (selectedCellOffset.left + (selectedCellWidth / 2) < parseInt(this.editorContainer.style.left, 10) + 20) {
  161. this.editorContainer.style.left = `${0}px`;
  162. }
  163. this.cellPointer.style.left = `${parseInt(selectedCellOffset.left - (domDimensionsCache.cellPointer.width / 2) -
  164. offset(this.editorContainer).left + (selectedCellWidth / 2) - currentScrollPosition.x, 10)}px`;
  165. }
  166. }
  167. };
  168. MobileTextEditor.prototype.updateEditorData = function() {
  169. var selected = this.instance.getSelected(),
  170. selectedValue = this.instance.getDataAtCell(selected[0], selected[1]);
  171. this.row = selected[0];
  172. this.col = selected[1];
  173. this.setValue(selectedValue);
  174. this.updateEditorPosition();
  175. };
  176. MobileTextEditor.prototype.prepareAndSave = function() {
  177. var val;
  178. if (!this.valueChanged()) {
  179. return;
  180. }
  181. if (this.instance.getSettings().trimWhitespace) {
  182. val = [
  183. [String.prototype.trim.call(this.getValue())]
  184. ];
  185. } else {
  186. val = [
  187. [this.getValue()]
  188. ];
  189. }
  190. this.saveValue(val);
  191. };
  192. MobileTextEditor.prototype.bindEvents = function() {
  193. var that = this;
  194. this.eventManager.addEventListener(this.controls.leftButton, 'touchend', (event) => {
  195. that.prepareAndSave();
  196. that.instance.selection.transformStart(0, -1, null, true);
  197. that.updateEditorData();
  198. event.preventDefault();
  199. });
  200. this.eventManager.addEventListener(this.controls.rightButton, 'touchend', (event) => {
  201. that.prepareAndSave();
  202. that.instance.selection.transformStart(0, 1, null, true);
  203. that.updateEditorData();
  204. event.preventDefault();
  205. });
  206. this.eventManager.addEventListener(this.controls.upButton, 'touchend', (event) => {
  207. that.prepareAndSave();
  208. that.instance.selection.transformStart(-1, 0, null, true);
  209. that.updateEditorData();
  210. event.preventDefault();
  211. });
  212. this.eventManager.addEventListener(this.controls.downButton, 'touchend', (event) => {
  213. that.prepareAndSave();
  214. that.instance.selection.transformStart(1, 0, null, true);
  215. that.updateEditorData();
  216. event.preventDefault();
  217. });
  218. this.eventManager.addEventListener(this.moveHandle, 'touchstart', function(event) {
  219. if (event.touches.length == 1) {
  220. var touch = event.touches[0];
  221. var onTouchPosition = {
  222. x: that.editorContainer.offsetLeft,
  223. y: that.editorContainer.offsetTop
  224. };
  225. var onTouchOffset = {
  226. x: touch.pageX - onTouchPosition.x,
  227. y: touch.pageY - onTouchPosition.y
  228. };
  229. that.eventManager.addEventListener(this, 'touchmove', (event) => {
  230. var touch = event.touches[0];
  231. that.updateEditorPosition(touch.pageX - onTouchOffset.x, touch.pageY - onTouchOffset.y);
  232. that.hideCellPointer();
  233. event.preventDefault();
  234. });
  235. }
  236. });
  237. this.eventManager.addEventListener(document.body, 'touchend', (event) => {
  238. if (!isChildOf(event.target, that.editorContainer) && !isChildOf(event.target, that.instance.rootElement)) {
  239. that.close();
  240. }
  241. });
  242. this.eventManager.addEventListener(this.instance.view.wt.wtOverlays.leftOverlay.holder, 'scroll', (event) => {
  243. if (that.instance.view.wt.wtOverlays.leftOverlay.trimmingContainer != window) {
  244. that.hideCellPointer();
  245. }
  246. });
  247. this.eventManager.addEventListener(this.instance.view.wt.wtOverlays.topOverlay.holder, 'scroll', (event) => {
  248. if (that.instance.view.wt.wtOverlays.topOverlay.trimmingContainer != window) {
  249. that.hideCellPointer();
  250. }
  251. });
  252. };
  253. MobileTextEditor.prototype.destroy = function() {
  254. this.eventManager.clear();
  255. this.editorContainer.parentNode.removeChild(this.editorContainer);
  256. };
  257. export default MobileTextEditor;