Products

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