397 lines
13 KiB
JavaScript
397 lines
13 KiB
JavaScript
"use strict";
|
|
|
|
exports.__esModule = true;
|
|
exports.checkboxRenderer = checkboxRenderer;
|
|
require("core-js/modules/es.array.push.js");
|
|
var _baseRenderer = require("../baseRenderer");
|
|
var _eventManager = _interopRequireDefault(require("../../eventManager"));
|
|
var _element = require("../../helpers/dom/element");
|
|
var _mixed = require("../../helpers/mixed");
|
|
var _editorManager = require("../../editorManager");
|
|
var _pluginHooks = _interopRequireDefault(require("../../pluginHooks"));
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
const isListeningKeyDownEvent = new WeakMap();
|
|
const isCheckboxListenerAdded = new WeakMap();
|
|
const BAD_VALUE_CLASS = 'htBadValue';
|
|
const ATTR_ROW = 'data-row';
|
|
const ATTR_COLUMN = 'data-col';
|
|
const SHORTCUTS_GROUP = 'checkboxRenderer';
|
|
const RENDERER_TYPE = 'checkbox';
|
|
exports.RENDERER_TYPE = RENDERER_TYPE;
|
|
_pluginHooks.default.getSingleton().add('modifyAutoColumnSizeSeed', function (bundleSeed, cellMeta, cellValue) {
|
|
const {
|
|
label,
|
|
type,
|
|
row,
|
|
column,
|
|
prop
|
|
} = cellMeta;
|
|
if (type !== RENDERER_TYPE) {
|
|
return;
|
|
}
|
|
if (label) {
|
|
const {
|
|
value: labelValue,
|
|
property: labelProperty
|
|
} = label;
|
|
let labelText = cellValue;
|
|
if (labelValue) {
|
|
labelText = typeof labelValue === 'function' ? labelValue(row, column, prop, cellValue) : labelValue;
|
|
} else if (labelProperty) {
|
|
const 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}).
|
|
*/
|
|
function checkboxRenderer(instance, TD, row, col, prop, value, cellProperties) {
|
|
const {
|
|
rootDocument
|
|
} = instance;
|
|
_baseRenderer.baseRenderer.apply(this, [instance, TD, row, col, prop, value, cellProperties]);
|
|
registerEvents(instance);
|
|
let input = createInput(rootDocument);
|
|
const labelOptions = cellProperties.label;
|
|
let badValue = false;
|
|
if (typeof cellProperties.checkedTemplate === 'undefined') {
|
|
cellProperties.checkedTemplate = true;
|
|
}
|
|
if (typeof cellProperties.uncheckedTemplate === 'undefined') {
|
|
cellProperties.uncheckedTemplate = false;
|
|
}
|
|
(0, _element.empty)(TD); // TODO identify under what circumstances this line can be removed
|
|
|
|
if (value === cellProperties.checkedTemplate || (0, _mixed.stringify)(value).toLocaleLowerCase(cellProperties.locale) === (0, _mixed.stringify)(cellProperties.checkedTemplate).toLocaleLowerCase(cellProperties.locale)) {
|
|
input.checked = true;
|
|
} else if (value === cellProperties.uncheckedTemplate || (0, _mixed.stringify)(value).toLocaleLowerCase(cellProperties.locale) === (0, _mixed.stringify)(cellProperties.uncheckedTemplate).toLocaleLowerCase(cellProperties.locale)) {
|
|
input.checked = false;
|
|
} else if ((0, _mixed.isEmpty)(value)) {
|
|
// default value
|
|
(0, _element.addClass)(input, 'noValue');
|
|
} else {
|
|
input.style.display = 'none';
|
|
(0, _element.addClass)(input, BAD_VALUE_CLASS);
|
|
badValue = true;
|
|
}
|
|
input.setAttribute(ATTR_ROW, row);
|
|
input.setAttribute(ATTR_COLUMN, col);
|
|
if (!badValue && labelOptions) {
|
|
let labelText = '';
|
|
if (labelOptions.value) {
|
|
labelText = typeof labelOptions.value === 'function' ? labelOptions.value.call(this, row, col, prop, value) : labelOptions.value;
|
|
} else if (labelOptions.property) {
|
|
const labelValue = instance.getDataAtRowProp(row, labelOptions.property);
|
|
labelText = labelValue !== null ? labelValue : '';
|
|
}
|
|
const 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);
|
|
registerShortcuts();
|
|
}
|
|
|
|
/**
|
|
* Register shortcuts responsible for toggling checkbox state.
|
|
*
|
|
* @private
|
|
*/
|
|
function registerShortcuts() {
|
|
const shortcutManager = instance.getShortcutManager();
|
|
const gridContext = shortcutManager.getContext('grid');
|
|
const config = {
|
|
group: SHORTCUTS_GROUP
|
|
};
|
|
gridContext.addShortcuts([{
|
|
keys: [['space']],
|
|
callback: () => {
|
|
changeSelectedCheckboxesState();
|
|
return !areSelectedCheckboxCells(); // False blocks next action associated with the keyboard shortcut.
|
|
}
|
|
}, {
|
|
keys: [['enter']],
|
|
callback: () => {
|
|
changeSelectedCheckboxesState();
|
|
return !areSelectedCheckboxCells(); // False blocks next action associated with the keyboard shortcut.
|
|
},
|
|
|
|
runOnlyIf: () => instance.getSettings().enterBeginsEditing
|
|
}, {
|
|
keys: [['delete'], ['backspace']],
|
|
callback: () => {
|
|
changeSelectedCheckboxesState(true);
|
|
return !areSelectedCheckboxCells(); // False blocks next action associated with the keyboard shortcut.
|
|
},
|
|
|
|
relativeToGroup: _editorManager.SHORTCUTS_GROUP_EDITOR,
|
|
position: 'before'
|
|
}], config);
|
|
}
|
|
|
|
/**
|
|
* Change checkbox checked property.
|
|
*
|
|
* @private
|
|
* @param {boolean} [uncheckCheckbox=false] The new "checked" state for the checkbox elements.
|
|
*/
|
|
function changeSelectedCheckboxesState() {
|
|
let uncheckCheckbox = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
const selRange = instance.getSelectedRange();
|
|
if (!selRange) {
|
|
return;
|
|
}
|
|
for (let key = 0; key < selRange.length; key++) {
|
|
const {
|
|
row: startRow,
|
|
col: startColumn
|
|
} = selRange[key].getTopStartCorner();
|
|
const {
|
|
row: endRow,
|
|
col: endColumn
|
|
} = selRange[key].getBottomEndCorner();
|
|
const changes = [];
|
|
for (let visualRow = startRow; visualRow <= endRow; visualRow += 1) {
|
|
for (let visualColumn = startColumn; visualColumn <= endColumn; visualColumn += 1) {
|
|
const 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;
|
|
}
|
|
const 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether all selected cells are with checkbox type.
|
|
*
|
|
* @returns {boolean}
|
|
* @private
|
|
*/
|
|
function areSelectedCheckboxCells() {
|
|
const selRange = instance.getSelectedRange();
|
|
if (!selRange) {
|
|
return;
|
|
}
|
|
for (let key = 0; key < selRange.length; key++) {
|
|
const topLeft = selRange[key].getTopStartCorner();
|
|
const bottomRight = selRange[key].getBottomEndCorner();
|
|
for (let visualRow = topLeft.row; visualRow <= bottomRight.row; visualRow++) {
|
|
for (let visualColumn = topLeft.col; visualColumn <= bottomRight.col; visualColumn++) {
|
|
const cachedCellProperties = instance.getCellMeta(visualRow, visualColumn);
|
|
if (cachedCellProperties.type !== 'checkbox') {
|
|
return false;
|
|
}
|
|
const cell = instance.getCell(visualRow, visualColumn);
|
|
if (cell === null || cell === void 0) {
|
|
return true;
|
|
} else {
|
|
const checkboxes = cell.querySelectorAll('input[type=checkbox]');
|
|
if (checkboxes.length > 0 && !cachedCellProperties.readOnly) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
checkboxRenderer.RENDERER_TYPE = RENDERER_TYPE;
|
|
|
|
/**
|
|
* Register checkbox listeners.
|
|
*
|
|
* @param {Core} instance The Handsontable instance.
|
|
* @returns {EventManager}
|
|
*/
|
|
function registerEvents(instance) {
|
|
let eventManager = isCheckboxListenerAdded.get(instance);
|
|
if (!eventManager) {
|
|
const {
|
|
rootElement
|
|
} = instance;
|
|
eventManager = new _eventManager.default(instance);
|
|
eventManager.addEventListener(rootElement, 'click', event => onClick(event, instance));
|
|
eventManager.addEventListener(rootElement, 'mouseup', event => onMouseUp(event, instance));
|
|
eventManager.addEventListener(rootElement, 'change', event => onChange(event, instance));
|
|
isCheckboxListenerAdded.set(instance, eventManager);
|
|
}
|
|
return eventManager;
|
|
}
|
|
|
|
/**
|
|
* Create input element.
|
|
*
|
|
* @param {Document} rootDocument The document owner.
|
|
* @returns {Node}
|
|
*/
|
|
function createInput(rootDocument) {
|
|
const 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) {
|
|
const label = rootDocument.createElement('label');
|
|
label.className = `htCheckboxRendererLabel ${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) {
|
|
const {
|
|
target
|
|
} = event;
|
|
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) {
|
|
const {
|
|
target
|
|
} = event;
|
|
if (!isCheckboxInput(target)) {
|
|
return;
|
|
}
|
|
if (!target.hasAttribute(ATTR_ROW) || !target.hasAttribute(ATTR_COLUMN)) {
|
|
return;
|
|
}
|
|
const row = parseInt(target.getAttribute(ATTR_ROW), 10);
|
|
const col = parseInt(target.getAttribute(ATTR_COLUMN), 10);
|
|
const 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) {
|
|
const {
|
|
target
|
|
} = event;
|
|
if (!isCheckboxInput(target)) {
|
|
return;
|
|
}
|
|
if (!target.hasAttribute(ATTR_ROW) || !target.hasAttribute(ATTR_COLUMN)) {
|
|
return;
|
|
}
|
|
const row = parseInt(target.getAttribute(ATTR_ROW), 10);
|
|
const col = parseInt(target.getAttribute(ATTR_COLUMN), 10);
|
|
const cellProperties = instance.getCellMeta(row, col);
|
|
if (!cellProperties.readOnly) {
|
|
let 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';
|
|
} |