UNPKG

handsontable

Version:

Handsontable is a JavaScript Spreadsheet Component available for React, Angular and Vue.

443 lines (367 loc) • 13.7 kB
import "core-js/modules/es.array.iterator.js"; import "core-js/modules/es.object.to-string.js"; import "core-js/modules/es.string.iterator.js"; import "core-js/modules/es.weak-map.js"; import "core-js/modules/web.dom-collections.iterator.js"; import "core-js/modules/es.array.includes.js"; import "core-js/modules/es.string.includes.js"; import "core-js/modules/es.regexp.to-string.js"; import "core-js/modules/web.timers.js"; import { baseRenderer } from "../baseRenderer/index.mjs"; import EventManager from "../../eventManager.mjs"; import { empty, addClass } from "../../helpers/dom/element.mjs"; import { stopImmediatePropagation, isImmediatePropagationStopped } from "../../helpers/dom/event.mjs"; import { partial } from "../../helpers/function.mjs"; import { isEmpty, stringify } from "../../helpers/mixed.mjs"; import { isKey } from "../../helpers/unicode.mjs"; import Hooks from "../../pluginHooks.mjs"; var isListeningKeyDownEvent = new WeakMap(); var isCheckboxListenerAdded = new WeakMap(); var BAD_VALUE_CLASS = 'htBadValue'; var ATTR_ROW = 'data-row'; var ATTR_COLUMN = 'data-col'; export var RENDERER_TYPE = 'checkbox'; Hooks.getSingleton().add('modifyAutoColumnSizeSeed', function (bundleSeed, cellMeta, cellValue) { var label = cellMeta.label, type = cellMeta.type, row = cellMeta.row, column = cellMeta.column, prop = cellMeta.prop; if (type !== RENDERER_TYPE) { return; } if (label) { var labelValue = label.value, labelProperty = label.property; var labelText = cellValue; if (labelValue) { labelText = typeof labelValue === 'function' ? labelValue(row, column, prop, cellValue) : labelValue; } else if (labelProperty) { var labelData = this.getDataAtRowProp(row, labelProperty); labelText = labelData !== null ? labelData : cellValue; } bundleSeed = labelText; } return bundleSeed; }); /** * Checkbox renderer. * * @private * @param {Core} instance The Handsontable instance. * @param {HTMLTableCellElement} TD The rendered cell element. * @param {number} row The visual row index. * @param {number} col The visual column index. * @param {number|string} prop The column property (passed when datasource is an array of objects). * @param {*} value The rendered value. * @param {object} cellProperties The cell meta object ({@see Core#getCellMeta}). */ export function checkboxRenderer(instance, TD, row, col, prop, value, cellProperties) { var rootDocument = instance.rootDocument; baseRenderer.apply(this, [instance, TD, row, col, prop, value, cellProperties]); registerEvents(instance); var input = createInput(rootDocument); var labelOptions = cellProperties.label; var badValue = false; if (typeof cellProperties.checkedTemplate === 'undefined') { cellProperties.checkedTemplate = true; } if (typeof cellProperties.uncheckedTemplate === 'undefined') { cellProperties.uncheckedTemplate = false; } empty(TD); // TODO identify under what circumstances this line can be removed if (value === cellProperties.checkedTemplate || stringify(value).toLocaleLowerCase(cellProperties.locale) === stringify(cellProperties.checkedTemplate).toLocaleLowerCase(cellProperties.locale)) { input.checked = true; } else if (value === cellProperties.uncheckedTemplate || stringify(value).toLocaleLowerCase(cellProperties.locale) === stringify(cellProperties.uncheckedTemplate).toLocaleLowerCase(cellProperties.locale)) { input.checked = false; } else if (isEmpty(value)) { // default value addClass(input, 'noValue'); } else { input.style.display = 'none'; addClass(input, BAD_VALUE_CLASS); badValue = true; } input.setAttribute(ATTR_ROW, row); input.setAttribute(ATTR_COLUMN, col); if (!badValue && labelOptions) { var labelText = ''; if (labelOptions.value) { labelText = typeof labelOptions.value === 'function' ? labelOptions.value.call(this, row, col, prop, value) : labelOptions.value; } else if (labelOptions.property) { var labelValue = instance.getDataAtRowProp(row, labelOptions.property); labelText = labelValue !== null ? labelValue : ''; } var label = createLabel(rootDocument, labelText, labelOptions.separated !== true); if (labelOptions.position === 'before') { if (labelOptions.separated) { TD.appendChild(label); TD.appendChild(input); } else { label.appendChild(input); input = label; } } else if (!labelOptions.position || labelOptions.position === 'after') { if (labelOptions.separated) { TD.appendChild(input); TD.appendChild(label); } else { label.insertBefore(input, label.firstChild); input = label; } } } if (!labelOptions || labelOptions && !labelOptions.separated) { TD.appendChild(input); } if (badValue) { TD.appendChild(rootDocument.createTextNode('#bad-value#')); } if (!isListeningKeyDownEvent.has(instance)) { isListeningKeyDownEvent.set(instance, true); instance.addHook('beforeKeyDown', onBeforeKeyDown); } /** * On before key down DOM listener. * * @private * @param {Event} event The keyboard event object. */ function onBeforeKeyDown(event) { var toggleKeys = 'SPACE|ENTER'; var switchOffKeys = 'DELETE|BACKSPACE'; var isKeyCode = partial(isKey, event.keyCode); if (!instance.getSettings().enterBeginsEditing && isKeyCode('ENTER')) { return; } if (isKeyCode("".concat(toggleKeys, "|").concat(switchOffKeys)) && !isImmediatePropagationStopped(event)) { eachSelectedCheckboxCell(function () { stopImmediatePropagation(event); event.preventDefault(); }); } if (isKeyCode(toggleKeys)) { changeSelectedCheckboxesState(); } if (isKeyCode(switchOffKeys)) { changeSelectedCheckboxesState(true); } } /** * Change checkbox checked property. * * @private * @param {boolean} [uncheckCheckbox=false] The new "checked" state for the checkbox elements. */ function changeSelectedCheckboxesState() { var uncheckCheckbox = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var selRange = instance.getSelectedRange(); if (!selRange) { return; } for (var key = 0; key < selRange.length; key++) { var _selRange$key$getTopL = selRange[key].getTopLeftCorner(), startRow = _selRange$key$getTopL.row, startColumn = _selRange$key$getTopL.col; var _selRange$key$getBott = selRange[key].getBottomRightCorner(), endRow = _selRange$key$getBott.row, endColumn = _selRange$key$getBott.col; var changes = []; for (var visualRow = startRow; visualRow <= endRow; visualRow += 1) { for (var visualColumn = startColumn; visualColumn <= endColumn; visualColumn += 1) { var cachedCellProperties = instance.getCellMeta(visualRow, visualColumn); if (cachedCellProperties.type !== 'checkbox') { return; } /* eslint-disable no-continue */ if (cachedCellProperties.readOnly === true) { continue; } if (typeof cachedCellProperties.checkedTemplate === 'undefined') { cachedCellProperties.checkedTemplate = true; } if (typeof cachedCellProperties.uncheckedTemplate === 'undefined') { cachedCellProperties.uncheckedTemplate = false; } var dataAtCell = instance.getDataAtCell(visualRow, visualColumn); if (uncheckCheckbox === false) { if ([cachedCellProperties.checkedTemplate, cachedCellProperties.checkedTemplate.toString()].includes(dataAtCell)) { // eslint-disable-line max-len changes.push([visualRow, visualColumn, cachedCellProperties.uncheckedTemplate]); } else if ([cachedCellProperties.uncheckedTemplate, cachedCellProperties.uncheckedTemplate.toString(), null, void 0].includes(dataAtCell)) { // eslint-disable-line max-len changes.push([visualRow, visualColumn, cachedCellProperties.checkedTemplate]); } } else { changes.push([visualRow, visualColumn, cachedCellProperties.uncheckedTemplate]); } } } if (changes.length > 0) { instance.setDataAtCell(changes); } } } /** * Call callback for each found selected cell with checkbox type. * * @private * @param {Function} callback The callback function. */ function eachSelectedCheckboxCell(callback) { var selRange = instance.getSelectedRange(); if (!selRange) { return; } for (var key = 0; key < selRange.length; key++) { var topLeft = selRange[key].getTopLeftCorner(); var bottomRight = selRange[key].getBottomRightCorner(); for (var visualRow = topLeft.row; visualRow <= bottomRight.row; visualRow++) { for (var visualColumn = topLeft.col; visualColumn <= bottomRight.col; visualColumn++) { var cachedCellProperties = instance.getCellMeta(visualRow, visualColumn); if (cachedCellProperties.type !== 'checkbox') { return; } var cell = instance.getCell(visualRow, visualColumn); if (cell === null || cell === void 0) { callback(visualRow, visualColumn, cachedCellProperties); } else { var checkboxes = cell.querySelectorAll('input[type=checkbox]'); if (checkboxes.length > 0 && !cachedCellProperties.readOnly) { callback(checkboxes); } } } } } } } checkboxRenderer.RENDERER_TYPE = RENDERER_TYPE; /** * Register checkbox listeners. * * @param {Core} instance The Handsontable instance. * @returns {EventManager} */ function registerEvents(instance) { var eventManager = isCheckboxListenerAdded.get(instance); if (!eventManager) { var rootElement = instance.rootElement; eventManager = new EventManager(instance); eventManager.addEventListener(rootElement, 'click', function (event) { return onClick(event, instance); }); eventManager.addEventListener(rootElement, 'mouseup', function (event) { return onMouseUp(event, instance); }); eventManager.addEventListener(rootElement, 'change', function (event) { return onChange(event, instance); }); isCheckboxListenerAdded.set(instance, eventManager); } return eventManager; } /** * Create input element. * * @param {Document} rootDocument The document owner. * @returns {Node} */ function createInput(rootDocument) { var input = rootDocument.createElement('input'); input.className = 'htCheckboxRendererInput'; input.type = 'checkbox'; input.setAttribute('autocomplete', 'off'); input.setAttribute('tabindex', '-1'); return input.cloneNode(false); } /** * Create label element. * * @param {Document} rootDocument The document owner. * @param {string} text The label text. * @param {boolean} fullWidth Determines whether label should have full width. * @returns {Node} */ function createLabel(rootDocument, text, fullWidth) { var label = rootDocument.createElement('label'); label.className = "htCheckboxRendererLabel ".concat(fullWidth ? 'fullWidth' : ''); label.appendChild(rootDocument.createTextNode(text)); return label.cloneNode(true); } /** * `mouseup` callback. * * @private * @param {Event} event `mouseup` event. * @param {Core} instance The Handsontable instance. */ function onMouseUp(event, instance) { var target = event.target; if (!isCheckboxInput(target)) { return; } if (!target.hasAttribute(ATTR_ROW) || !target.hasAttribute(ATTR_COLUMN)) { return; } setTimeout(instance.listen, 10); } /** * `click` callback. * * @private * @param {MouseEvent} event `click` event. * @param {Core} instance The Handsontable instance. */ function onClick(event, instance) { var target = event.target; if (!isCheckboxInput(target)) { return; } if (!target.hasAttribute(ATTR_ROW) || !target.hasAttribute(ATTR_COLUMN)) { return; } var row = parseInt(target.getAttribute(ATTR_ROW), 10); var col = parseInt(target.getAttribute(ATTR_COLUMN), 10); var cellProperties = instance.getCellMeta(row, col); if (cellProperties.readOnly) { event.preventDefault(); } } /** * `change` callback. * * @param {Event} event `change` event. * @param {Core} instance The Handsontable instance. */ function onChange(event, instance) { var target = event.target; if (!isCheckboxInput(target)) { return; } if (!target.hasAttribute(ATTR_ROW) || !target.hasAttribute(ATTR_COLUMN)) { return; } var row = parseInt(target.getAttribute(ATTR_ROW), 10); var col = parseInt(target.getAttribute(ATTR_COLUMN), 10); var cellProperties = instance.getCellMeta(row, col); if (!cellProperties.readOnly) { var newCheckboxValue = null; if (event.target.checked) { newCheckboxValue = cellProperties.uncheckedTemplate === void 0 ? true : cellProperties.checkedTemplate; } else { newCheckboxValue = cellProperties.uncheckedTemplate === void 0 ? false : cellProperties.uncheckedTemplate; } instance.setDataAtCell(row, col, newCheckboxValue); } } /** * Check if the provided element is the checkbox input. * * @private * @param {HTMLElement} element The element in question. * @returns {boolean} */ function isCheckboxInput(element) { return element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox'; }