Editor.js provides a comprehensive event system for subscribing to editor changes, user interactions, and block mutations. This enables reactive behavior and integration with external systems.
Methods for subscribing to and unsubscribing from editor events.
/**
* Subscribe to editor event
* @param eventName - Name of the event to listen for
* @param callback - Function to execute when event occurs
*/
on(eventName: string, callback: (data?: any) => void): void;
/**
* Unsubscribe from editor event
* @param eventName - Name of the event to stop listening for
* @param callback - Specific callback function to remove
*/
off(eventName: string, callback: (data?: any) => void): void;
/**
* Emit custom event
* @param eventName - Name of the event to trigger
* @param data - Data to pass to event listeners
*/
emit(eventName: string, data: any): void;Usage Examples:
// Subscribe to block changes
const handleBlockChange = (event) => {
console.log('Block changed:', event.detail);
};
editor.events.on('block-changed', handleBlockChange);
// Unsubscribe when no longer needed
editor.events.off('block-changed', handleBlockChange);
// Emit custom events
editor.events.emit('custom-save', { timestamp: Date.now() });
// Subscribe to custom events
editor.events.on('custom-save', (data) => {
console.log('Custom save triggered:', data.timestamp);
});Editor.js emits several built-in events that you can subscribe to:
Events related to block creation, modification, and deletion.
Usage Examples:
// Block added event
editor.events.on('block-added', (event) => {
console.log('New block added:', {
blockId: event.detail.target.id,
blockType: event.detail.target.name,
index: event.detail.index
});
});
// Block changed event
editor.events.on('block-changed', (event) => {
console.log('Block content changed:', {
blockId: event.detail.target.id,
blockType: event.detail.target.name
});
});
// Block removed event
editor.events.on('block-removed', (event) => {
console.log('Block removed:', {
blockId: event.detail.target.id,
index: event.detail.index
});
});
// Block moved event
editor.events.on('block-moved', (event) => {
console.log('Block moved:', {
blockId: event.detail.target.id,
fromIndex: event.detail.fromIndex,
toIndex: event.detail.toIndex
});
});Events related to overall editor state changes.
Usage Examples:
// Editor ready event
editor.events.on('editor-ready', () => {
console.log('Editor is fully initialized');
});
// Editor focus/blur events
editor.events.on('editor-focus', () => {
console.log('Editor gained focus');
});
editor.events.on('editor-blur', () => {
console.log('Editor lost focus');
});Events typically include detailed information about the change:
interface BlockMutationEvent {
type: BlockMutationType;
detail: {
target: BlockAPI;
index?: number;
fromIndex?: number;
toIndex?: number;
};
}
type BlockMutationType =
| 'block-added'
| 'block-changed'
| 'block-removed'
| 'block-moved';Usage Examples:
// Detailed event handling
editor.events.on('block-added', (event) => {
const { target, index } = event.detail;
console.log(`Block "${target.name}" added at index ${index}`);
console.log('Block ID:', target.id);
console.log('Block data:', target.save());
});
// Handle different mutation types
editor.events.on('block-changed', (event) => {
switch (event.type) {
case 'block-changed':
console.log('Content modified');
break;
default:
console.log('Other change type:', event.type);
}
});// Implement auto-save with debouncing
let saveTimeout: NodeJS.Timeout;
const autoSave = async () => {
try {
const data = await editor.save();
localStorage.setItem('editor-draft', JSON.stringify(data));
console.log('Auto-saved at', new Date().toLocaleTimeString());
} catch (error) {
console.error('Auto-save failed:', error);
}
};
const debouncedAutoSave = () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(autoSave, 1000); // Save 1 second after last change
};
// Listen for any content changes
editor.events.on('block-changed', debouncedAutoSave);
editor.events.on('block-added', debouncedAutoSave);
editor.events.on('block-removed', debouncedAutoSave);
editor.events.on('block-moved', debouncedAutoSave);// Track changes for undo/redo functionality
const changeHistory: any[] = [];
let currentHistoryIndex = -1;
const saveHistoryState = async () => {
const data = await editor.save();
// Remove any future history if we're not at the end
changeHistory.splice(currentHistoryIndex + 1);
// Add new state
changeHistory.push(data);
currentHistoryIndex = changeHistory.length - 1;
// Limit history size
if (changeHistory.length > 50) {
changeHistory.shift();
currentHistoryIndex--;
}
};
// Subscribe to changes
editor.events.on('block-changed', saveHistoryState);
editor.events.on('block-added', saveHistoryState);
editor.events.on('block-removed', saveHistoryState);// Real-time collaboration event broadcasting
const broadcastChange = (event) => {
const changeData = {
type: event.type,
blockId: event.detail.target?.id,
timestamp: Date.now(),
userId: getCurrentUserId()
};
// Send to collaboration server
websocket.send(JSON.stringify({
type: 'editor-change',
data: changeData
}));
};
// Broadcast all mutation events
['block-added', 'block-changed', 'block-removed', 'block-moved'].forEach(eventName => {
editor.events.on(eventName, broadcastChange);
});// Validate blocks on change
editor.events.on('block-changed', async (event) => {
const block = event.detail.target;
try {
const data = await block.save();
const isValid = await block.validate(data);
if (!isValid) {
console.warn(`Block ${block.id} contains invalid data`);
// Could highlight block or show warning
}
} catch (error) {
console.error(`Validation failed for block ${block.id}:`, error);
}
});// Track user interactions for analytics
const trackingData = {
blocksCreated: 0,
blocksDeleted: 0,
blocksModified: 0,
sessionStart: Date.now()
};
editor.events.on('block-added', (event) => {
trackingData.blocksCreated++;
// Send analytics event
analytics.track('block_created', {
blockType: event.detail.target.name,
totalBlocks: editor.blocks.getBlocksCount()
});
});
editor.events.on('block-removed', () => {
trackingData.blocksDeleted++;
analytics.track('block_deleted', {
totalBlocks: editor.blocks.getBlocksCount()
});
});
editor.events.on('block-changed', () => {
trackingData.blocksModified++;
});
// Send session summary periodically
setInterval(() => {
analytics.track('editor_session_summary', {
...trackingData,
sessionDuration: Date.now() - trackingData.sessionStart
});
}, 60000); // Every minuteImportant to clean up event listeners to prevent memory leaks:
Usage Examples:
// Store references for cleanup
const eventHandlers = {
blockChanged: (event) => console.log('Block changed:', event),
blockAdded: (event) => console.log('Block added:', event)
};
// Subscribe
editor.events.on('block-changed', eventHandlers.blockChanged);
editor.events.on('block-added', eventHandlers.blockAdded);
// Cleanup when component unmounts or editor is destroyed
function cleanup() {
editor.events.off('block-changed', eventHandlers.blockChanged);
editor.events.off('block-added', eventHandlers.blockAdded);
}
// In React useEffect cleanup
useEffect(() => {
// Setup events...
return cleanup; // Cleanup on unmount
}, []);You can create your own event systems on top of Editor.js events:
Usage Examples:
// Custom event manager
class EditorEventManager {
private handlers = new Map<string, Set<Function>>();
constructor(private editor: EditorJS) {
this.setupBuiltinEvents();
}
private setupBuiltinEvents() {
// Bridge built-in events to custom system
['block-added', 'block-changed', 'block-removed'].forEach(eventName => {
this.editor.events.on(eventName, (event) => {
this.emit(`editor:${eventName}`, event);
});
});
}
on(eventName: string, handler: Function) {
if (!this.handlers.has(eventName)) {
this.handlers.set(eventName, new Set());
}
this.handlers.get(eventName)!.add(handler);
}
off(eventName: string, handler: Function) {
this.handlers.get(eventName)?.delete(handler);
}
emit(eventName: string, data: any) {
this.handlers.get(eventName)?.forEach(handler => {
handler(data);
});
}
}
const eventManager = new EditorEventManager(editor);
// Use custom event system
eventManager.on('editor:block-added', (event) => {
console.log('Custom handler for block added:', event);
});