@quantlab/handsontable
Version:
Spreadsheet-like data grid editor that provides copy/paste functionality compatible with Excel/Google Docs
246 lines (209 loc) • 6.95 kB
JavaScript
import moment from 'moment';
import Pikaday from 'pikaday';
import 'pikaday/css/pikaday.css';
import {addClass, outerHeight} from './../helpers/dom/element';
import {deepExtend} from './../helpers/object';
import EventManager from './../eventManager';
import {isMetaKey} from './../helpers/unicode';
import {stopPropagation} from './../helpers/dom/event';
import TextEditor from './textEditor';
/**
* @private
* @editor DateEditor
* @class DateEditor
* @dependencies TextEditor moment pikaday
*/
class DateEditor extends TextEditor {
/**
* @param {Core} hotInstance Handsontable instance
* @private
*/
constructor(hotInstance) {
super(hotInstance);
// TODO: Move this option to general settings
this.defaultDateFormat = 'DD/MM/YYYY';
this.isCellEdited = false;
this.parentDestroyed = false;
}
init() {
if (typeof moment !== 'function') {
throw new Error('You need to include moment.js to your project.');
}
if (typeof Pikaday !== 'function') {
throw new Error('You need to include Pikaday to your project.');
}
super.init();
this.instance.addHook('afterDestroy', () => {
this.parentDestroyed = true;
this.destroyElements();
});
}
/**
* Create data picker instance
*/
createElements() {
super.createElements();
this.datePicker = document.createElement('DIV');
this.datePickerStyle = this.datePicker.style;
this.datePickerStyle.position = 'absolute';
this.datePickerStyle.top = 0;
this.datePickerStyle.left = 0;
this.datePickerStyle.zIndex = 9999;
addClass(this.datePicker, 'htDatepickerHolder');
document.body.appendChild(this.datePicker);
this.$datePicker = new Pikaday(this.getDatePickerConfig());
const eventManager = new EventManager(this);
/**
* Prevent recognizing clicking on datepicker as clicking outside of table
*/
eventManager.addEventListener(this.datePicker, 'mousedown', (event) => stopPropagation(event));
this.hideDatepicker();
}
/**
* Destroy data picker instance
*/
destroyElements() {
this.$datePicker.destroy();
}
/**
* Prepare editor to appear
*
* @param {Number} row Row index
* @param {Number} col Column index
* @param {String} prop Property name (passed when datasource is an array of objects)
* @param {HTMLTableCellElement} td Table cell element
* @param {*} originalValue Original value
* @param {Object} cellProperties Object with cell properties ({@see Core#getCellMeta})
*/
prepare(row, col, prop, td, originalValue, cellProperties) {
this._opened = false;
super.prepare(row, col, prop, td, originalValue, cellProperties);
}
/**
* Open editor
*
* @param {Event} [event=null]
*/
open(event = null) {
super.open();
this.showDatepicker(event);
}
/**
* Close editor
*/
close() {
this._opened = false;
this.instance._registerTimeout(setTimeout(() => {
this.instance.selection.refreshBorders();
}, 0));
super.close();
}
/**
* @param {Boolean} [isCancelled=false]
* @param {Boolean} [ctrlDown=false]
*/
finishEditing(isCancelled = false, ctrlDown = false) {
if (isCancelled) { // pressed ESC, restore original value
// var value = this.instance.getDataAtCell(this.row, this.col);
let value = this.originalValue;
if (value !== void 0) {
this.setValue(value);
}
}
this.hideDatepicker();
super.finishEditing(isCancelled, ctrlDown);
}
/**
* Show data picker
*
* @param {Event} event
*/
showDatepicker(event) {
this.$datePicker.config(this.getDatePickerConfig());
let offset = this.TD.getBoundingClientRect();
let dateFormat = this.cellProperties.dateFormat || this.defaultDateFormat;
let datePickerConfig = this.$datePicker.config();
let dateStr;
let isMouseDown = this.instance.view.isMouseDown();
let isMeta = event ? isMetaKey(event.keyCode) : false;
this.datePickerStyle.top = `${window.pageYOffset + offset.top + outerHeight(this.TD)}px`;
this.datePickerStyle.left = `${window.pageXOffset + offset.left}px`;
this.$datePicker._onInputFocus = function() {};
datePickerConfig.format = dateFormat;
if (this.originalValue) {
dateStr = this.originalValue;
if (moment(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment(moment(dateStr, dateFormat), true);
}
// workaround for date/time cells - pikaday resets the cell value to 12:00 AM by default, this will overwrite the value.
if (this.getValue() !== this.originalValue) {
this.setValue(this.originalValue);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else if (this.cellProperties.defaultDate) {
dateStr = this.cellProperties.defaultDate;
datePickerConfig.defaultDate = dateStr;
if (moment(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment(moment(dateStr, dateFormat), true);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else {
// if a default date is not defined, set a soft-default-date: display the current day and month in the
// datepicker, but don't fill the editor input
this.$datePicker.gotoToday();
}
this.datePickerStyle.display = 'block';
this.$datePicker.show();
}
/**
* Hide data picker
*/
hideDatepicker() {
this.datePickerStyle.display = 'none';
this.$datePicker.hide();
}
/**
* Get date picker options.
*
* @returns {Object}
*/
getDatePickerConfig() {
let htInput = this.TEXTAREA;
let options = {};
if (this.cellProperties && this.cellProperties.datePickerConfig) {
deepExtend(options, this.cellProperties.datePickerConfig);
}
const origOnSelect = options.onSelect;
const origOnClose = options.onClose;
options.field = htInput;
options.trigger = htInput;
options.container = this.datePicker;
options.bound = false;
options.format = options.format || this.defaultDateFormat;
options.reposition = options.reposition || false;
options.onSelect = (dateStr) => {
if (!isNaN(dateStr.getTime())) {
dateStr = moment(dateStr).format(this.cellProperties.dateFormat || this.defaultDateFormat);
}
this.setValue(dateStr);
this.hideDatepicker();
if (origOnSelect) {
origOnSelect();
}
};
options.onClose = () => {
if (!this.parentDestroyed) {
this.finishEditing(false);
}
if (origOnClose) {
origOnClose();
}
};
return options;
}
}
export default DateEditor;