import {KEY_CODES} from './../helpers/unicode'; import {stopImmediatePropagation, isImmediatePropagationStopped} from './../helpers/dom/event'; import { addClass, getScrollLeft, getScrollTop, hasClass, isChildOf, offset, outerHeight, outerWidth, removeClass, setCaretPosition, } from './../helpers/dom/element'; import BaseEditor from './_baseEditor'; import EventManager from './../eventManager'; const MobileTextEditor = BaseEditor.prototype.extend(); const domDimensionsCache = {}; /** * @private * @editor MobileTextEditor * @class MobileTextEditor */ var createControls = function() { this.controls = {}; this.controls.leftButton = document.createElement('DIV'); this.controls.leftButton.className = 'leftButton'; this.controls.rightButton = document.createElement('DIV'); this.controls.rightButton.className = 'rightButton'; this.controls.upButton = document.createElement('DIV'); this.controls.upButton.className = 'upButton'; this.controls.downButton = document.createElement('DIV'); this.controls.downButton.className = 'downButton'; for (var button in this.controls) { if (Object.prototype.hasOwnProperty.call(this.controls, button)) { this.positionControls.appendChild(this.controls[button]); } } }; MobileTextEditor.prototype.valueChanged = function() { return this.initValue != this.getValue(); }; MobileTextEditor.prototype.init = function() { var that = this; this.eventManager = new EventManager(this.instance); this.createElements(); this.bindEvents(); this.instance.addHook('afterDestroy', () => { that.destroy(); }); }; MobileTextEditor.prototype.getValue = function() { return this.TEXTAREA.value; }; MobileTextEditor.prototype.setValue = function(newValue) { this.initValue = newValue; this.TEXTAREA.value = newValue; }; MobileTextEditor.prototype.createElements = function() { this.editorContainer = document.createElement('DIV'); this.editorContainer.className = 'htMobileEditorContainer'; this.cellPointer = document.createElement('DIV'); this.cellPointer.className = 'cellPointer'; this.moveHandle = document.createElement('DIV'); this.moveHandle.className = 'moveHandle'; this.inputPane = document.createElement('DIV'); this.inputPane.className = 'inputs'; this.positionControls = document.createElement('DIV'); this.positionControls.className = 'positionControls'; this.TEXTAREA = document.createElement('TEXTAREA'); addClass(this.TEXTAREA, 'handsontableInput'); this.inputPane.appendChild(this.TEXTAREA); this.editorContainer.appendChild(this.cellPointer); this.editorContainer.appendChild(this.moveHandle); this.editorContainer.appendChild(this.inputPane); this.editorContainer.appendChild(this.positionControls); createControls.call(this); document.body.appendChild(this.editorContainer); }; MobileTextEditor.prototype.onBeforeKeyDown = function(event) { var instance = this; var that = instance.getActiveEditor(); if (event.target !== that.TEXTAREA || isImmediatePropagationStopped(event)) { return; } switch (event.keyCode) { case KEY_CODES.ENTER: that.close(); event.preventDefault(); // don't add newline to field break; case KEY_CODES.BACKSPACE: stopImmediatePropagation(event); // backspace, delete, home, end should only work locally when cell is edited (not in table context) break; default: break; } }; MobileTextEditor.prototype.open = function() { this.instance.addHook('beforeKeyDown', this.onBeforeKeyDown); addClass(this.editorContainer, 'active'); removeClass(this.cellPointer, 'hidden'); this.updateEditorPosition(); }; MobileTextEditor.prototype.focus = function() { this.TEXTAREA.focus(); setCaretPosition(this.TEXTAREA, this.TEXTAREA.value.length); }; MobileTextEditor.prototype.close = function() { this.TEXTAREA.blur(); this.instance.removeHook('beforeKeyDown', this.onBeforeKeyDown); removeClass(this.editorContainer, 'active'); }; MobileTextEditor.prototype.scrollToView = function() { var coords = this.instance.getSelectedRange().highlight; this.instance.view.scrollViewport(coords); }; MobileTextEditor.prototype.hideCellPointer = function() { if (!hasClass(this.cellPointer, 'hidden')) { addClass(this.cellPointer, 'hidden'); } }; MobileTextEditor.prototype.updateEditorPosition = function(x, y) { if (x && y) { x = parseInt(x, 10); y = parseInt(y, 10); this.editorContainer.style.top = `${y}px`; this.editorContainer.style.left = `${x}px`; } else { var selection = this.instance.getSelected(), selectedCell = this.instance.getCell(selection[0], selection[1]); // cache sizes if (!domDimensionsCache.cellPointer) { domDimensionsCache.cellPointer = { height: outerHeight(this.cellPointer), width: outerWidth(this.cellPointer) }; } if (!domDimensionsCache.editorContainer) { domDimensionsCache.editorContainer = { width: outerWidth(this.editorContainer) }; } if (selectedCell !== undefined) { var scrollLeft = this.instance.view.wt.wtOverlays.leftOverlay .trimmingContainer == window ? 0 : getScrollLeft(this.instance.view.wt.wtOverlays.leftOverlay.holder); var scrollTop = this.instance.view.wt.wtOverlays.topOverlay .trimmingContainer == window ? 0 : getScrollTop(this.instance.view.wt.wtOverlays.topOverlay.holder); var selectedCellOffset = offset(selectedCell), selectedCellWidth = outerWidth(selectedCell), currentScrollPosition = { x: scrollLeft, y: scrollTop }; this.editorContainer.style.top = `${parseInt(selectedCellOffset.top + outerHeight(selectedCell) - currentScrollPosition.y + domDimensionsCache.cellPointer.height, 10)}px`; this.editorContainer.style.left = `${parseInt((window.innerWidth / 2) - (domDimensionsCache.editorContainer.width / 2), 10)}px`; if (selectedCellOffset.left + (selectedCellWidth / 2) > parseInt(this.editorContainer.style.left, 10) + domDimensionsCache.editorContainer.width) { this.editorContainer.style.left = `${window.innerWidth - domDimensionsCache.editorContainer.width}px`; } else if (selectedCellOffset.left + (selectedCellWidth / 2) < parseInt(this.editorContainer.style.left, 10) + 20) { this.editorContainer.style.left = `${0}px`; } this.cellPointer.style.left = `${parseInt(selectedCellOffset.left - (domDimensionsCache.cellPointer.width / 2) - offset(this.editorContainer).left + (selectedCellWidth / 2) - currentScrollPosition.x, 10)}px`; } } }; MobileTextEditor.prototype.updateEditorData = function() { var selected = this.instance.getSelected(), selectedValue = this.instance.getDataAtCell(selected[0], selected[1]); this.row = selected[0]; this.col = selected[1]; this.setValue(selectedValue); this.updateEditorPosition(); }; MobileTextEditor.prototype.prepareAndSave = function() { var val; if (!this.valueChanged()) { return; } if (this.instance.getSettings().trimWhitespace) { val = [ [String.prototype.trim.call(this.getValue())] ]; } else { val = [ [this.getValue()] ]; } this.saveValue(val); }; MobileTextEditor.prototype.bindEvents = function() { var that = this; this.eventManager.addEventListener(this.controls.leftButton, 'touchend', (event) => { that.prepareAndSave(); that.instance.selection.transformStart(0, -1, null, true); that.updateEditorData(); event.preventDefault(); }); this.eventManager.addEventListener(this.controls.rightButton, 'touchend', (event) => { that.prepareAndSave(); that.instance.selection.transformStart(0, 1, null, true); that.updateEditorData(); event.preventDefault(); }); this.eventManager.addEventListener(this.controls.upButton, 'touchend', (event) => { that.prepareAndSave(); that.instance.selection.transformStart(-1, 0, null, true); that.updateEditorData(); event.preventDefault(); }); this.eventManager.addEventListener(this.controls.downButton, 'touchend', (event) => { that.prepareAndSave(); that.instance.selection.transformStart(1, 0, null, true); that.updateEditorData(); event.preventDefault(); }); this.eventManager.addEventListener(this.moveHandle, 'touchstart', function(event) { if (event.touches.length == 1) { var touch = event.touches[0]; var onTouchPosition = { x: that.editorContainer.offsetLeft, y: that.editorContainer.offsetTop }; var onTouchOffset = { x: touch.pageX - onTouchPosition.x, y: touch.pageY - onTouchPosition.y }; that.eventManager.addEventListener(this, 'touchmove', (event) => { var touch = event.touches[0]; that.updateEditorPosition(touch.pageX - onTouchOffset.x, touch.pageY - onTouchOffset.y); that.hideCellPointer(); event.preventDefault(); }); } }); this.eventManager.addEventListener(document.body, 'touchend', (event) => { if (!isChildOf(event.target, that.editorContainer) && !isChildOf(event.target, that.instance.rootElement)) { that.close(); } }); this.eventManager.addEventListener(this.instance.view.wt.wtOverlays.leftOverlay.holder, 'scroll', (event) => { if (that.instance.view.wt.wtOverlays.leftOverlay.trimmingContainer != window) { that.hideCellPointer(); } }); this.eventManager.addEventListener(this.instance.view.wt.wtOverlays.topOverlay.holder, 'scroll', (event) => { if (that.instance.view.wt.wtOverlays.topOverlay.trimmingContainer != window) { that.hideCellPointer(); } }); }; MobileTextEditor.prototype.destroy = function() { this.eventManager.clear(); this.editorContainer.parentNode.removeChild(this.editorContainer); }; export default MobileTextEditor;