editorManager.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import { CellCoords } from './3rdparty/walkontable/src';
  2. import { KEY_CODES, isMetaKey, isCtrlKey } from './helpers/unicode';
  3. import { stopPropagation, stopImmediatePropagation, isImmediatePropagationStopped } from './helpers/dom/event';
  4. import { getEditorInstance } from './editors';
  5. import EventManager from './eventManager';
  6. import { EditorState } from './editors/_baseEditor';
  7. function EditorManager(instance, priv, selection) {
  8. var _this = this,
  9. destroyed = false,
  10. eventManager,
  11. activeEditor;
  12. eventManager = new EventManager(instance);
  13. function moveSelectionAfterEnter(shiftKey) {
  14. selection.setSelectedHeaders(false, false, false);
  15. var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves;
  16. if (shiftKey) {
  17. // move selection up
  18. selection.transformStart(-enterMoves.row, -enterMoves.col);
  19. } else {
  20. // move selection down (add a new row if needed)
  21. selection.transformStart(enterMoves.row, enterMoves.col, true);
  22. }
  23. }
  24. function moveSelectionUp(shiftKey) {
  25. if (shiftKey) {
  26. if (selection.selectedHeader.cols) {
  27. selection.setSelectedHeaders(selection.selectedHeader.rows, false, false);
  28. }
  29. selection.transformEnd(-1, 0);
  30. } else {
  31. selection.setSelectedHeaders(false, false, false);
  32. selection.transformStart(-1, 0);
  33. }
  34. }
  35. function moveSelectionDown(shiftKey) {
  36. if (shiftKey) {
  37. // expanding selection down with shift
  38. selection.transformEnd(1, 0);
  39. } else {
  40. selection.setSelectedHeaders(false, false, false);
  41. selection.transformStart(1, 0);
  42. }
  43. }
  44. function moveSelectionRight(shiftKey) {
  45. if (shiftKey) {
  46. selection.transformEnd(0, 1);
  47. } else {
  48. selection.setSelectedHeaders(false, false, false);
  49. selection.transformStart(0, 1);
  50. }
  51. }
  52. function moveSelectionLeft(shiftKey) {
  53. if (shiftKey) {
  54. if (selection.selectedHeader.rows) {
  55. selection.setSelectedHeaders(false, selection.selectedHeader.cols, false);
  56. }
  57. selection.transformEnd(0, -1);
  58. } else {
  59. selection.setSelectedHeaders(false, false, false);
  60. selection.transformStart(0, -1);
  61. }
  62. }
  63. function onKeyDown(event) {
  64. var ctrlDown, rangeModifier;
  65. if (!instance.isListening()) {
  66. return;
  67. }
  68. instance.runHooks('beforeKeyDown', event);
  69. if (destroyed) {
  70. return;
  71. }
  72. if (isImmediatePropagationStopped(event)) {
  73. return;
  74. }
  75. priv.lastKeyCode = event.keyCode;
  76. if (!selection.isSelected()) {
  77. return;
  78. }
  79. // catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  80. ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;
  81. if (activeEditor && !activeEditor.isWaiting()) {
  82. if (!isMetaKey(event.keyCode) && !isCtrlKey(event.keyCode) && !ctrlDown && !_this.isEditorOpened()) {
  83. _this.openEditor('', event);
  84. return;
  85. }
  86. }
  87. rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart;
  88. switch (event.keyCode) {
  89. case KEY_CODES.A:
  90. if (!_this.isEditorOpened() && ctrlDown) {
  91. selection.selectAll();
  92. event.preventDefault();
  93. stopPropagation(event);
  94. }
  95. break;
  96. case KEY_CODES.ARROW_UP:
  97. if (_this.isEditorOpened() && !activeEditor.isWaiting()) {
  98. _this.closeEditorAndSaveChanges(ctrlDown);
  99. }
  100. moveSelectionUp(event.shiftKey);
  101. event.preventDefault();
  102. stopPropagation(event);
  103. break;
  104. case KEY_CODES.ARROW_DOWN:
  105. if (_this.isEditorOpened() && !activeEditor.isWaiting()) {
  106. _this.closeEditorAndSaveChanges(ctrlDown);
  107. }
  108. moveSelectionDown(event.shiftKey);
  109. event.preventDefault();
  110. stopPropagation(event);
  111. break;
  112. case KEY_CODES.ARROW_RIGHT:
  113. if (_this.isEditorOpened() && !activeEditor.isWaiting()) {
  114. _this.closeEditorAndSaveChanges(ctrlDown);
  115. }
  116. moveSelectionRight(event.shiftKey);
  117. event.preventDefault();
  118. stopPropagation(event);
  119. break;
  120. case KEY_CODES.ARROW_LEFT:
  121. if (_this.isEditorOpened() && !activeEditor.isWaiting()) {
  122. _this.closeEditorAndSaveChanges(ctrlDown);
  123. }
  124. moveSelectionLeft(event.shiftKey);
  125. event.preventDefault();
  126. stopPropagation(event);
  127. break;
  128. case KEY_CODES.TAB:
  129. selection.setSelectedHeaders(false, false, false);
  130. var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves;
  131. if (event.shiftKey) {
  132. // move selection left
  133. selection.transformStart(-tabMoves.row, -tabMoves.col);
  134. } else {
  135. // move selection right (add a new column if needed)
  136. selection.transformStart(tabMoves.row, tabMoves.col, true);
  137. }
  138. event.preventDefault();
  139. stopPropagation(event);
  140. break;
  141. case KEY_CODES.BACKSPACE:
  142. case KEY_CODES.DELETE:
  143. selection.empty(event);
  144. _this.prepareEditor();
  145. event.preventDefault();
  146. break;
  147. case KEY_CODES.F2:
  148. /* F2 */
  149. _this.openEditor(null, event);
  150. if (activeEditor) {
  151. activeEditor.enableFullEditMode();
  152. }
  153. event.preventDefault(); // prevent Opera from opening 'Go to Page dialog'
  154. break;
  155. case KEY_CODES.ENTER:
  156. /* return/enter */
  157. if (_this.isEditorOpened()) {
  158. if (activeEditor && activeEditor.state !== EditorState.WAITING) {
  159. _this.closeEditorAndSaveChanges(ctrlDown);
  160. }
  161. moveSelectionAfterEnter(event.shiftKey);
  162. } else if (instance.getSettings().enterBeginsEditing) {
  163. _this.openEditor(null, event);
  164. if (activeEditor) {
  165. activeEditor.enableFullEditMode();
  166. }
  167. } else {
  168. moveSelectionAfterEnter(event.shiftKey);
  169. }
  170. event.preventDefault(); // don't add newline to field
  171. stopImmediatePropagation(event); // required by HandsontableEditor
  172. break;
  173. case KEY_CODES.ESCAPE:
  174. if (_this.isEditorOpened()) {
  175. _this.closeEditorAndRestoreOriginalValue(ctrlDown);
  176. }
  177. event.preventDefault();
  178. break;
  179. case KEY_CODES.HOME:
  180. selection.setSelectedHeaders(false, false, false);
  181. if (event.ctrlKey || event.metaKey) {
  182. rangeModifier(new CellCoords(0, priv.selRange.from.col));
  183. } else {
  184. rangeModifier(new CellCoords(priv.selRange.from.row, 0));
  185. }
  186. event.preventDefault(); // don't scroll the window
  187. stopPropagation(event);
  188. break;
  189. case KEY_CODES.END:
  190. selection.setSelectedHeaders(false, false, false);
  191. if (event.ctrlKey || event.metaKey) {
  192. rangeModifier(new CellCoords(instance.countRows() - 1, priv.selRange.from.col));
  193. } else {
  194. rangeModifier(new CellCoords(priv.selRange.from.row, instance.countCols() - 1));
  195. }
  196. event.preventDefault(); // don't scroll the window
  197. stopPropagation(event);
  198. break;
  199. case KEY_CODES.PAGE_UP:
  200. selection.setSelectedHeaders(false, false, false);
  201. selection.transformStart(-instance.countVisibleRows(), 0);
  202. event.preventDefault(); // don't page up the window
  203. stopPropagation(event);
  204. break;
  205. case KEY_CODES.PAGE_DOWN:
  206. selection.setSelectedHeaders(false, false, false);
  207. selection.transformStart(instance.countVisibleRows(), 0);
  208. event.preventDefault(); // don't page down the window
  209. stopPropagation(event);
  210. break;
  211. default:
  212. break;
  213. }
  214. }
  215. function init() {
  216. instance.addHook('afterDocumentKeyDown', onKeyDown);
  217. eventManager.addEventListener(document.documentElement, 'keydown', function (event) {
  218. if (!destroyed) {
  219. instance.runHooks('afterDocumentKeyDown', event);
  220. }
  221. });
  222. function onDblClick(event, coords, elem) {
  223. // may be TD or TH
  224. if (elem.nodeName == 'TD') {
  225. _this.openEditor();
  226. if (activeEditor) {
  227. activeEditor.enableFullEditMode();
  228. }
  229. }
  230. }
  231. instance.view.wt.update('onCellDblClick', onDblClick);
  232. instance.addHook('afterDestroy', function () {
  233. destroyed = true;
  234. });
  235. }
  236. /**
  237. * Destroy current editor, if exists.
  238. *
  239. * @function destroyEditor
  240. * @memberof! Handsontable.EditorManager#
  241. * @param {Boolean} revertOriginal
  242. */
  243. this.destroyEditor = function (revertOriginal) {
  244. this.closeEditor(revertOriginal);
  245. };
  246. /**
  247. * Get active editor.
  248. *
  249. * @function getActiveEditor
  250. * @memberof! Handsontable.EditorManager#
  251. * @returns {*}
  252. */
  253. this.getActiveEditor = function () {
  254. return activeEditor;
  255. };
  256. /**
  257. * Prepare text input to be displayed at given grid cell.
  258. *
  259. * @function prepareEditor
  260. * @memberof! Handsontable.EditorManager#
  261. */
  262. this.prepareEditor = function () {
  263. var row, col, prop, td, originalValue, cellProperties, editorClass;
  264. if (activeEditor && activeEditor.isWaiting()) {
  265. this.closeEditor(false, false, function (dataSaved) {
  266. if (dataSaved) {
  267. _this.prepareEditor();
  268. }
  269. });
  270. return;
  271. }
  272. row = priv.selRange.highlight.row;
  273. col = priv.selRange.highlight.col;
  274. prop = instance.colToProp(col);
  275. td = instance.getCell(row, col);
  276. originalValue = instance.getSourceDataAtCell(instance.runHooks('modifyRow', row), col);
  277. cellProperties = instance.getCellMeta(row, col);
  278. editorClass = instance.getCellEditor(cellProperties);
  279. if (editorClass) {
  280. activeEditor = getEditorInstance(editorClass, instance);
  281. activeEditor.prepare(row, col, prop, td, originalValue, cellProperties);
  282. } else {
  283. activeEditor = void 0;
  284. }
  285. };
  286. /**
  287. * Check is editor is opened/showed.
  288. *
  289. * @function isEditorOpened
  290. * @memberof! Handsontable.EditorManager#
  291. * @returns {Boolean}
  292. */
  293. this.isEditorOpened = function () {
  294. return activeEditor && activeEditor.isOpened();
  295. };
  296. /**
  297. * Open editor with initial value.
  298. *
  299. * @function openEditor
  300. * @memberof! Handsontable.EditorManager#
  301. * @param {String} initialValue
  302. * @param {DOMEvent} event
  303. */
  304. this.openEditor = function (initialValue, event) {
  305. if (activeEditor && !activeEditor.cellProperties.readOnly) {
  306. activeEditor.beginEditing(initialValue, event);
  307. } else if (activeEditor && activeEditor.cellProperties.readOnly) {
  308. // move the selection after opening the editor with ENTER key
  309. if (event && event.keyCode === KEY_CODES.ENTER) {
  310. moveSelectionAfterEnter();
  311. }
  312. }
  313. };
  314. /**
  315. * Close editor, finish editing cell.
  316. *
  317. * @function closeEditor
  318. * @memberof! Handsontable.EditorManager#
  319. * @param {Boolean} restoreOriginalValue
  320. * @param {Boolean} [ctrlDown]
  321. * @param {Function} [callback]
  322. */
  323. this.closeEditor = function (restoreOriginalValue, ctrlDown, callback) {
  324. if (activeEditor) {
  325. activeEditor.finishEditing(restoreOriginalValue, ctrlDown, callback);
  326. } else if (callback) {
  327. callback(false);
  328. }
  329. };
  330. /**
  331. * Close editor and save changes.
  332. *
  333. * @function closeEditorAndSaveChanges
  334. * @memberof! Handsontable.EditorManager#
  335. * @param {Boolean} ctrlDown
  336. */
  337. this.closeEditorAndSaveChanges = function (ctrlDown) {
  338. return this.closeEditor(false, ctrlDown);
  339. };
  340. /**
  341. * Close editor and restore original value.
  342. *
  343. * @function closeEditorAndRestoreOriginalValue
  344. * @memberof! Handsontable.EditorManager#
  345. * @param {Boolean} ctrlDown
  346. */
  347. this.closeEditorAndRestoreOriginalValue = function (ctrlDown) {
  348. return this.closeEditor(true, ctrlDown);
  349. };
  350. init();
  351. }
  352. export default EditorManager;