6867faa71a09ad9a3db984ef717bdecd0960c27ddd152c254e0d9edc4dd3d12fb8d7037b3d160f4dd4fb572ffd557e0d3b293c86e5c7f3d76b6f44956612fc 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import moment from 'moment';
  2. import Pikaday from 'pikaday';
  3. import 'pikaday/css/pikaday.css';
  4. import {addClass, outerHeight} from './../helpers/dom/element';
  5. import {deepExtend} from './../helpers/object';
  6. import EventManager from './../eventManager';
  7. import {isMetaKey} from './../helpers/unicode';
  8. import {stopPropagation} from './../helpers/dom/event';
  9. import TextEditor from './textEditor';
  10. /**
  11. * @private
  12. * @editor DateEditor
  13. * @class DateEditor
  14. * @dependencies TextEditor moment pikaday
  15. */
  16. class DateEditor extends TextEditor {
  17. /**
  18. * @param {Core} hotInstance Handsontable instance
  19. * @private
  20. */
  21. constructor(hotInstance) {
  22. super(hotInstance);
  23. // TODO: Move this option to general settings
  24. this.defaultDateFormat = 'DD/MM/YYYY';
  25. this.isCellEdited = false;
  26. this.parentDestroyed = false;
  27. }
  28. init() {
  29. if (typeof moment !== 'function') {
  30. throw new Error('You need to include moment.js to your project.');
  31. }
  32. if (typeof Pikaday !== 'function') {
  33. throw new Error('You need to include Pikaday to your project.');
  34. }
  35. super.init();
  36. this.instance.addHook('afterDestroy', () => {
  37. this.parentDestroyed = true;
  38. this.destroyElements();
  39. });
  40. }
  41. /**
  42. * Create data picker instance
  43. */
  44. createElements() {
  45. super.createElements();
  46. this.datePicker = document.createElement('DIV');
  47. this.datePickerStyle = this.datePicker.style;
  48. this.datePickerStyle.position = 'absolute';
  49. this.datePickerStyle.top = 0;
  50. this.datePickerStyle.left = 0;
  51. this.datePickerStyle.zIndex = 9999;
  52. addClass(this.datePicker, 'htDatepickerHolder');
  53. document.body.appendChild(this.datePicker);
  54. this.$datePicker = new Pikaday(this.getDatePickerConfig());
  55. const eventManager = new EventManager(this);
  56. /**
  57. * Prevent recognizing clicking on datepicker as clicking outside of table
  58. */
  59. eventManager.addEventListener(this.datePicker, 'mousedown', (event) => stopPropagation(event));
  60. this.hideDatepicker();
  61. }
  62. /**
  63. * Destroy data picker instance
  64. */
  65. destroyElements() {
  66. this.$datePicker.destroy();
  67. }
  68. /**
  69. * Prepare editor to appear
  70. *
  71. * @param {Number} row Row index
  72. * @param {Number} col Column index
  73. * @param {String} prop Property name (passed when datasource is an array of objects)
  74. * @param {HTMLTableCellElement} td Table cell element
  75. * @param {*} originalValue Original value
  76. * @param {Object} cellProperties Object with cell properties ({@see Core#getCellMeta})
  77. */
  78. prepare(row, col, prop, td, originalValue, cellProperties) {
  79. this._opened = false;
  80. super.prepare(row, col, prop, td, originalValue, cellProperties);
  81. }
  82. /**
  83. * Open editor
  84. *
  85. * @param {Event} [event=null]
  86. */
  87. open(event = null) {
  88. super.open();
  89. this.showDatepicker(event);
  90. }
  91. /**
  92. * Close editor
  93. */
  94. close() {
  95. this._opened = false;
  96. this.instance._registerTimeout(setTimeout(() => {
  97. this.instance.selection.refreshBorders();
  98. }, 0));
  99. super.close();
  100. }
  101. /**
  102. * @param {Boolean} [isCancelled=false]
  103. * @param {Boolean} [ctrlDown=false]
  104. */
  105. finishEditing(isCancelled = false, ctrlDown = false) {
  106. if (isCancelled) { // pressed ESC, restore original value
  107. // var value = this.instance.getDataAtCell(this.row, this.col);
  108. let value = this.originalValue;
  109. if (value !== void 0) {
  110. this.setValue(value);
  111. }
  112. }
  113. this.hideDatepicker();
  114. super.finishEditing(isCancelled, ctrlDown);
  115. }
  116. /**
  117. * Show data picker
  118. *
  119. * @param {Event} event
  120. */
  121. showDatepicker(event) {
  122. this.$datePicker.config(this.getDatePickerConfig());
  123. let offset = this.TD.getBoundingClientRect();
  124. let dateFormat = this.cellProperties.dateFormat || this.defaultDateFormat;
  125. let datePickerConfig = this.$datePicker.config();
  126. let dateStr;
  127. let isMouseDown = this.instance.view.isMouseDown();
  128. let isMeta = event ? isMetaKey(event.keyCode) : false;
  129. this.datePickerStyle.top = `${window.pageYOffset + offset.top + outerHeight(this.TD)}px`;
  130. this.datePickerStyle.left = `${window.pageXOffset + offset.left}px`;
  131. this.$datePicker._onInputFocus = function() {};
  132. datePickerConfig.format = dateFormat;
  133. if (this.originalValue) {
  134. dateStr = this.originalValue;
  135. if (moment(dateStr, dateFormat, true).isValid()) {
  136. this.$datePicker.setMoment(moment(dateStr, dateFormat), true);
  137. }
  138. // workaround for date/time cells - pikaday resets the cell value to 12:00 AM by default, this will overwrite the value.
  139. if (this.getValue() !== this.originalValue) {
  140. this.setValue(this.originalValue);
  141. }
  142. if (!isMeta && !isMouseDown) {
  143. this.setValue('');
  144. }
  145. } else if (this.cellProperties.defaultDate) {
  146. dateStr = this.cellProperties.defaultDate;
  147. datePickerConfig.defaultDate = dateStr;
  148. if (moment(dateStr, dateFormat, true).isValid()) {
  149. this.$datePicker.setMoment(moment(dateStr, dateFormat), true);
  150. }
  151. if (!isMeta && !isMouseDown) {
  152. this.setValue('');
  153. }
  154. } else {
  155. // if a default date is not defined, set a soft-default-date: display the current day and month in the
  156. // datepicker, but don't fill the editor input
  157. this.$datePicker.gotoToday();
  158. }
  159. this.datePickerStyle.display = 'block';
  160. this.$datePicker.show();
  161. }
  162. /**
  163. * Hide data picker
  164. */
  165. hideDatepicker() {
  166. this.datePickerStyle.display = 'none';
  167. this.$datePicker.hide();
  168. }
  169. /**
  170. * Get date picker options.
  171. *
  172. * @returns {Object}
  173. */
  174. getDatePickerConfig() {
  175. let htInput = this.TEXTAREA;
  176. let options = {};
  177. if (this.cellProperties && this.cellProperties.datePickerConfig) {
  178. deepExtend(options, this.cellProperties.datePickerConfig);
  179. }
  180. const origOnSelect = options.onSelect;
  181. const origOnClose = options.onClose;
  182. options.field = htInput;
  183. options.trigger = htInput;
  184. options.container = this.datePicker;
  185. options.bound = false;
  186. options.format = options.format || this.defaultDateFormat;
  187. options.reposition = options.reposition || false;
  188. options.onSelect = (dateStr) => {
  189. if (!isNaN(dateStr.getTime())) {
  190. dateStr = moment(dateStr).format(this.cellProperties.dateFormat || this.defaultDateFormat);
  191. }
  192. this.setValue(dateStr);
  193. this.hideDatepicker();
  194. if (origOnSelect) {
  195. origOnSelect();
  196. }
  197. };
  198. options.onClose = () => {
  199. if (!this.parentDestroyed) {
  200. this.finishEditing(false);
  201. }
  202. if (origOnClose) {
  203. origOnClose();
  204. }
  205. };
  206. return options;
  207. }
  208. }
  209. export default DateEditor;