or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

custom-editors.mdcustom-renderers.mdhelper-functions.mdhot-column.mdhot-table.mdindex.md
tile.json

custom-editors.mddocs/

Custom Cell Editors (BaseEditorComponent)

BaseEditorComponent provides a Vue-based foundation for creating custom cell editors that integrate seamlessly with Handsontable's editing system. It implements the Handsontable BaseEditor interface while providing Vue component lifecycle and data binding.

Capabilities

Base Editor Class

Foundation class for creating custom cell editors with Vue integration.

/**
 * Base class for creating custom Vue-based cell editors
 * Extends Vue and implements Handsontable.editors.BaseEditor interface
 */
class BaseEditorComponent extends Vue implements Handsontable.editors.BaseEditor {
  /** Component name identifier */
  name: string;
  
  /** Current Handsontable instance */
  instance: Handsontable | null;
  
  /** Current row index being edited */
  row: number | null;
  
  /** Current column index being edited */
  col: number | null;
  
  /** Property name for current cell */
  prop: string | null;
  
  /** Current table cell DOM element */
  TD: HTMLTableCellElement | null;
  
  /** Original cell value before editing */
  originalValue: any;
  
  /** Cell properties configuration object */
  cellProperties: Handsontable.CellProperties | null;
  
  /** Editor state information */
  state: any;
  
  /** Handsontable instance reference */
  hot: Handsontable | null;
}

Core Editor Methods

Essential methods that every custom editor must implement.

/**
 * Focus the editor input element
 * Called when editor becomes active
 */
focus(): void;

/**
 * Get the current value from the editor
 * @returns Current editor value
 */
getValue(): any;

/**
 * Set the editor value
 * @param value - Value to set in the editor
 */
setValue(value?: any): void;

/**
 * Open the editor for editing
 * @param event - Optional triggering event
 */
open(event?: Event): void;

/**
 * Close the editor and save changes
 */
close(): void;

Usage Example:

<template>
  <div class="custom-editor">
    <input 
      ref="input"
      v-model="editorValue"
      @keydown="onKeyDown"
      @blur="onBlur"
      type="text"
    />
  </div>
</template>

<script>
import { BaseEditorComponent } from '@handsontable/vue';

export default {
  extends: BaseEditorComponent,
  
  data() {
    return {
      editorValue: ''
    };
  },
  
  methods: {
    focus() {
      this.$refs.input.focus();
    },
    
    getValue() {
      return this.editorValue;
    },
    
    setValue(value) {
      this.editorValue = value || '';
    },
    
    open(event) {
      this.setValue(this.originalValue);
      this.$nextTick(() => {
        this.focus();
      });
    },
    
    close() {
      // Editor will be closed automatically
    },
    
    onKeyDown(event) {
      if (event.key === 'Enter') {
        this.finishEditing();
      } else if (event.key === 'Escape') {
        this.cancelChanges();
      }
    },
    
    onBlur() {
      this.finishEditing();
    }
  }
}
</script>

Editor Lifecycle Methods

Methods that control the editor lifecycle and integration with Handsontable.

/**
 * Initialize the editor with Handsontable instance
 * @param hotInstance - Handsontable instance
 * @param TD - Table cell element
 * @param row - Row index
 * @param col - Column index
 * @param prop - Property name
 * @param value - Initial value
 * @param cellProperties - Cell configuration
 */
init(hotInstance: Handsontable, TD: HTMLTableCellElement, row: number, col: number, prop: string, value: any, cellProperties: object): void;

/**
 * Prepare editor for editing a specific cell
 * @param row - Row index
 * @param col - Column index
 * @param prop - Property name
 * @param TD - Table cell element
 * @param originalValue - Original cell value
 * @param cellProperties - Cell properties
 */
prepare(row: number, col: number, prop: string, TD: HTMLTableCellElement, originalValue: any, cellProperties: Handsontable.CellProperties): void;

/**
 * Begin editing the cell
 * @param initialValue - Initial value to show in editor
 * @param event - Event that triggered editing
 */
beginEditing(initialValue?: any, event?: Event): void;

/**
 * Finish editing and save the value
 * @param restoreOriginalValue - Whether to restore original value
 * @param ctrlDown - Whether Ctrl key is pressed
 * @param callback - Completion callback
 */
finishEditing(restoreOriginalValue?: boolean, ctrlDown?: boolean, callback?: Function): void;

/**
 * Cancel editing and restore original value
 */
cancelChanges(): void;

/**
 * Save the current editor value to the cell
 * @param value - Value to save
 * @param ctrlDown - Whether Ctrl key is pressed
 */
saveValue(value?: any, ctrlDown?: boolean): void;

Editor State Methods

Methods for checking and managing editor state.

/**
 * Check if editor is currently open for editing
 * @returns True if editor is open
 */
isOpened(): boolean;

/**
 * Check if editor is in full edit mode
 * @returns True if in full edit mode
 */
isInFullEditMode(): boolean;

/**
 * Check if editor is waiting for user input
 * @returns True if waiting
 */
isWaiting(): boolean;

/**
 * Enable full edit mode
 */
enableFullEditMode(): void;

/**
 * Discard the editor without saving
 * @param validationResult - Validation result
 */
discardEditor(validationResult?: any): void;

Editor Extension Methods

Methods for extending editor functionality and integration.

/**
 * Extend editor prototype with additional methods
 * @param methods - Methods to add to prototype
 */
extend(methods: object): void;

/**
 * Check which section of the editor is being interacted with
 * @param event - DOM event
 * @returns Editor section identifier
 */
checkEditorSection(event: Event): string;

Hook Management

Methods for managing Handsontable hooks within the editor.

/**
 * Add a hook to the editor
 * @param key - Hook name
 * @param callback - Hook callback function
 */
addHook(key: string, callback: Function): void;

/**
 * Remove hooks by key
 * @param key - Hook name to remove
 */
removeHooksByKey(key: string): void;

/**
 * Clear all registered hooks
 */
clearHooks(): void;

Cell Information Methods

Methods for getting information about the cell being edited.

/**
 * Get the DOM element of the cell being edited
 * @returns Cell DOM element
 */
getEditedCell(): HTMLTableCellElement;

/**
 * Get the bounding rectangle of the edited cell
 * @returns Cell rectangle coordinates
 */
getEditedCellRect(): { top: number; left: number; width: number; height: number };

/**
 * Get the z-index for the edited cell's layer
 * @returns Z-index value
 */
getEditedCellsZIndex(): number;

/**
 * Get the CSS class for the edited cell's layer
 * @returns CSS class name
 */
getEditedCellsLayerClass(): string;

Advanced Editor Example

Complex custom editor with validation and special handling:

<template>
  <div class="date-range-editor" :class="{ 'has-error': hasError }">
    <input 
      ref="startDate"
      v-model="startDate"
      type="date"
      @change="onDateChange"
      :min="minDate"
      :max="maxDate"
    />
    <span class="separator">to</span>
    <input 
      ref="endDate"
      v-model="endDate"
      type="date"
      @change="onDateChange"
      :min="startDate"
      :max="maxDate"
    />
    <div v-if="hasError" class="error-message">
      {{ errorMessage }}
    </div>
  </div>
</template>

<script>
import { BaseEditorComponent } from '@handsontable/vue';

export default {
  extends: BaseEditorComponent,
  
  data() {
    return {
      startDate: '',
      endDate: '',
      hasError: false,
      errorMessage: '',
      minDate: '2020-01-01',
      maxDate: '2030-12-31'
    };
  },
  
  computed: {
    dateRangeValue() {
      if (this.startDate && this.endDate) {
        return `${this.startDate} - ${this.endDate}`;
      }
      return '';
    }
  },
  
  methods: {
    focus() {
      this.$refs.startDate.focus();
    },
    
    getValue() {
      return this.dateRangeValue;
    },
    
    setValue(value) {
      if (typeof value === 'string' && value.includes(' - ')) {
        const [start, end] = value.split(' - ');
        this.startDate = start.trim();
        this.endDate = end.trim();
      } else {
        this.startDate = '';
        this.endDate = '';
      }
      this.validateDates();
    },
    
    open(event) {
      this.setValue(this.originalValue);
      this.$nextTick(() => {
        this.focus();
      });
    },
    
    close() {
      if (this.hasError) {
        // Prevent closing if validation fails
        return false;
      }
    },
    
    onDateChange() {
      this.validateDates();
      if (!this.hasError && this.startDate && this.endDate) {
        // Auto-save when both dates are valid
        this.finishEditing();
      }
    },
    
    validateDates() {
      this.hasError = false;
      this.errorMessage = '';
      
      if (this.startDate && this.endDate) {
        const start = new Date(this.startDate);
        const end = new Date(this.endDate);
        
        if (start > end) {
          this.hasError = true;
          this.errorMessage = 'Start date must be before end date';
        }
      }
    },
    
    beginEditing(initialValue, event) {
      // Custom begin editing logic
      this.setValue(initialValue);
      
      // Handle different trigger events
      if (event && event.type === 'dblclick') {
        // Double-click: focus on end date
        this.$nextTick(() => {
          this.$refs.endDate.focus();
        });
      } else {
        // Other events: focus on start date
        this.$nextTick(() => {
          this.focus();
        });
      }
    },
    
    finishEditing(restoreOriginalValue, ctrlDown, callback) {
      if (this.hasError && !restoreOriginalValue) {
        // Don't finish editing if there are validation errors
        return;
      }
      
      // Call parent implementation
      BaseEditorComponent.prototype.finishEditing.call(
        this, 
        restoreOriginalValue, 
        ctrlDown, 
        callback
      );
    }
  }
}
</script>

<style scoped>
.date-range-editor {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px;
  background: white;
  border: 1px solid #ccc;
}

.date-range-editor.has-error {
  border-color: #e74c3c;
}

.error-message {
  color: #e74c3c;
  font-size: 12px;
  margin-top: 4px;
}

.separator {
  font-weight: bold;
  color: #666;
}
</style>

Editor Integration with HotColumn

Using custom editors with column configuration:

<template>
  <hot-table :data="projectData">
    <!-- Column using custom date range editor -->
    <hot-column title="Project Duration" data="duration" width="200">
      <date-range-editor hot-editor></date-range-editor>
    </hot-column>
    
    <!-- Column using custom dropdown editor -->
    <hot-column title="Priority" data="priority" width="120">
      <priority-editor hot-editor :options="priorityOptions"></priority-editor>
    </hot-column>
  </hot-table>
</template>

<script>
import { HotTable, HotColumn } from '@handsontable/vue';
import DateRangeEditor from './DateRangeEditor.vue';
import PriorityEditor from './PriorityEditor.vue';

export default {
  components: {
    HotTable,
    HotColumn,
    DateRangeEditor,
    PriorityEditor
  },
  
  data() {
    return {
      priorityOptions: ['Low', 'Medium', 'High', 'Critical'],
      projectData: [
        { name: 'Project A', duration: '2024-01-01 - 2024-03-31', priority: 'High' }
      ]
    };
  }
}
</script>

Editor Component Interface

Interface that custom editors should implement for optimal integration.

/**
 * Interface for custom editor components
 */
interface EditorComponent extends Vue {
  /** Focus the editor */
  focus(): void;
  
  /** Open editor for editing */
  open(event?: Event): void;
  
  /** Close editor */
  close(): void;
  
  /** Get current editor value */
  getValue(): any;
  
  /** Set editor value */
  setValue(newValue?: any): void;
  
  /** Additional properties available for customization */
  [additional: string]: any;
}

The BaseEditorComponent provides a complete foundation for building sophisticated custom editors that integrate seamlessly with both Vue's component system and Handsontable's editing infrastructure.