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...
};