Custom Editors

Custom editors are one of the most powerful features in Jspreadsheet, enabling developers to create rich, interactive interfaces for data entry within spreadsheet cells. By integrating any custom widget or UI component, you can build dynamic applications that enhance productivity and improve user experience.

Editor Lifecycle

Custom editors in Jspreadsheet follow a defined lifecycle composed of key method hooks that manage the creation, behavior, and cleanup of cell editors.

Custom editors

Customizing cell data entry using custom editors enhances the user experience and data collection in your data grid. A custom editor is defined as a JavaScript object with several methods described below.

Method Description
createCell When a new cell is created.
createCell(cell: Object, value: Any, x: Number, y: Number, instance: Object, options: Object) : void
updateCell When the cell value changes.
updateCell(cell: Object, value: Any, x: Number, y: Number, instance: Object, options: Object) : void
openEditor When the user starts editing a cell.
openEditor(cell: Object, value: Any, x: Number, y: Number, instance: Object, options: Object) : void
closeEditor When the user finalizes the edit of a cell.
closeEditor(cell: Object, confirmChanges: Boolean, x: Number, y: Number, instance: Object, options: Object) : void
destroyCell When a cell is destroyed.
destroyCell(cell: Object, x: Number, y: Number, instance: Object) : void
get Transform the raw data into processed data. It will show text instead of an id in the type dropdown, for example.
get(options: Object, value: Any) : Any

Lifecycle Diagram

The following diagram illustrates the complete custom editor lifecycle:

┌─────────────────────────────────────────────────────────────────────────────────┐ │ CUSTOM EDITOR LIFECYCLE │ └─────────────────────────────────────────────────────────────────────────────────┘ 1. CELL CREATION & DISPLAY ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────────────┐ │ Raw Value │───►│ createCell() │───►│ <td> textContent + CSS classes │ └─────────────┘ └──────────────┘ └─────────────────────────────────────┘ │ ▼ ┌─────────────┐ │ Cell Visible│ │ to User │ └─────────────┘ │ 2. EDITING PROCESS │ ┌─────────────┐ ┌──────────────┐ ┌─────────────▼───────────────────────┐ │ User action │───►│ openEditor() │───►│ spreadsheet.input (floating widget) │ └─────────────┘ └──────────────┘ └─────────────────────────────────────┘ │ ▼ ┌─────────────────┐ │ User Interacts │ │ with Widget │ └─────────────────┘ │ ┌──────────────┐ ┌───────────────┐ │ │ Save/Cancel │◄───│ closeEditor() │◄────────────────┘ │ Decision │ └───────────────┘ └──────────────┘ │ │ │ ▼ ▼ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────────────┐ │ If SAVE: │───►│ updateCell() │───►│ <td> textContent updated │ │ New Value │ └──────────────┘ └─────────────────────────────────────┘ └─────────────┘ 3. PROGRAMMATIC UPDATES ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────────────┐ │ setValue() │───►│ updateCell() │───►│ <td> textContent updated │ │ setData() │ └──────────────┘ └─────────────────────────────────────┘ └─────────────┘ 4. DATA EXPORT (Virtualization Support) ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────────────┐ │ Export Call │───►│ get() │───►│ Formatted Value (no <td> required) │ └─────────────┘ └──────────────┘ └─────────────────────────────────────┘ 5. CLEANUP & DESTRUCTION ┌──────────────┐ ┌───────────────┐ ┌─────────────────────────────────────┐ │ Cell Removal │───►│ destroyCell() │───►│ Remove CSS + <td> from DOM │ └──────────────┘ └───────────────┘ └─────────────────────────────────────┘ 

Practical Examples

Enhanced Checkbox Editor

This example shows a robust checkbox implementation with proper event handling and accessibility:

const checkbox = { // Normalize to boolean isTrue: value => !!(value === 1 || value === true || value === '1' || value === 'true' || value === 'TRUE'), // Create a new cell with the input element createCell(cell, value, x, y, instance) { value = this.isTrue(value); let element = document.createElement('input'); element.type = 'checkbox'; element.checked = value; element.addEventListener('change', () => { if (instance.isReadOnly(x, y) || !instance.isEditable()) { element.checked = !!instance.getValueFromCoords(x, y); } else { instance.setValue(cell, element.checked); } }); cell.textContent = ''; cell.appendChild(element); }, // Update the cell content updateCell(cell, value) { value = this.isTrue(value); let element = cell.firstChild; if (element.checked !== value) { element.checked = value; } }, // Do not open the editor but toggle the checkbox value openEditor(cell, value, x, y, instance) { instance.setValue(cell, !cell.firstChild.checked); return false; }, // This does not applied to checkboxes closeEditor() { return false; }, // Return a verbose content when exporting get(options, value) { return this.isTrue(value) ? 'true' : 'false'; } }; 

Web Component Custom Editor

A modern approach using Web Components for better encapsulation:

E.switch = { createCell: function(cell, value, x, y, instance) { // Create a dropdown on the spreadsheet and reuse across all cells let component = document.createElement('lm-switch') component.onchange = function(element, v) { instance.setValueFromCoords(x, y, v); } // Define the value on the cell component.value = value; // Rating cell.appendChild(component) }, updateCell: function(cell, value) { let component = cell?.firstChild; if (component) { if (value !== component.value) { component.value = value; } } }, openEditor: function(cell, value, x, y, instance) { instance.setValueFromCoords(x, y, ! value); return false; }, closeEditor: function() { return false; } } 

Accessibility Support

Make your editors accessible to all users:

const accessibleEditor = { createCell(cell, value, x, y, instance, options) { const input = document.createElement('input'); input.setAttribute('aria-label', `Cell ${x},${y} value`); input.setAttribute('role', 'gridcell'); // Add keyboard navigation input.addEventListener('keydown', (e) => { if (e.key === 'Tab' && !e.shiftKey) { // Move to next cell instance.right(); e.preventDefault(); } }); cell.appendChild(input); } }; 

Troubleshooting

Common Issues

Editor doesn't close properly

Problem: Editor remains open after clicking elsewhere Solution: Ensure blur event handling and proper cleanup

openEditor(cell, value, x, y, instance, options) { // (...) // Add blur handler to auto-close const blurHandler = () => { instance.closeEditor(cell, true); }; const input = document.createElement('input'); input.addEventListener('blur', blurHandler); // (...) } 

Debug Mode

Enable debug mode to troubleshoot editor lifecycle:

const debugEditor = { debug: true, log(method, ...args) { if (this.debug) { console.log(`[Editor:${method}]`, ...args); } }, createCell(cell, value, x, y, instance, options) { this.log('createCell', { cell, value, x, y, options }); // Your implementation }, openEditor(cell, value, x, y, instance, options) { this.log('openEditor', { cell, value, x, y, options }); // Your implementation } // Add logging to all methods... }; 

See Also