Event Usage
About 3 min
Event Usage
This chapter explains how to listen to and handle WebCAD events.
Basic Event Listening
import { Engine, CadEvents } from 'vjcad';
// Access the event manager through Engine (recommended)
Engine.eventManager.on(CadEvents.EntityAdded, (args) => {
console.log('Entity added:', args.entity.type, 'ID:', args.entity.id);
});
// Listen to command execution
Engine.eventManager.on(CadEvents.CommandStarted, (args) => {
console.log('Command started:', args.commandName);
});
Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
console.log('Command completed:', args.commandName);
});
// One-time listener
Engine.eventManager.once(CadEvents.DocumentOpened, (args) => {
console.log('Document opened for the first time:', args.document.name);
});Two Access Methods
// Method 1: through Engine (recommended)
import { Engine, CadEvents } from 'vjcad';
Engine.eventManager.on(CadEvents.EntityAdded, handler);
// Method 2: directly use CadEventManager
import { CadEventManager, CadEvents } from 'vjcad';
const events = CadEventManager.getInstance();
events.on(CadEvents.EntityAdded, handler);These two methods are equivalent; Engine.eventManager internally calls CadEventManager.getInstance().
Cancelable Events
Some events support canceling the operation:
// Prevent deleting entities on a protected layer
Engine.eventManager.on(CadEvents.EntityErasing, (args) => {
if (args.entity.layer === 'ProtectedLayer') {
args.cancel = true; // Cancel erase
console.log('Deleting entities on protected layer is not allowed');
}
});
// Prevent saving, for example when validation fails
Engine.eventManager.on(CadEvents.DocumentSaving, (args) => {
const entities = Engine.getEntities();
if (entities.length === 0) {
args.cancel = true;
alert('The document is empty and cannot be saved');
}
});
// Confirm close
Engine.eventManager.on(CadEvents.DocumentClosing, (args) => {
if (args.document.DBMOD === 1) { // Unsaved changes exist
const confirmed = confirm('There are unsaved changes. Close anyway?');
if (!confirmed) {
args.cancel = true;
}
}
});Remove Event Listeners
// Keep a handler reference so it can be removed
const entityHandler = (args) => {
console.log('Entity added:', args.entity.type);
};
// Register
Engine.eventManager.on(CadEvents.EntityAdded, entityHandler);
// Remove
Engine.eventManager.off(CadEvents.EntityAdded, entityHandler);
// Remove all listeners of one event
Engine.eventManager.offAll(CadEvents.EntityAdded);
// Remove all listeners of all events
Engine.eventManager.offAll();Suspend Events During Batch Operations
// Suspend event dispatch during batch operations for better performance
Engine.eventManager.suspendEvents();
try {
// Execute many operations...
for (let i = 0; i < 1000; i++) {
const line = new LineEnt(...);
Engine.pcanvas.addEntity(line);
}
} finally {
Engine.eventManager.resumeEvents();
}
// Check current status
if (Engine.eventManager.isSuspended()) {
console.log('Events are suspended');
}Event Argument Types
import { Engine, CadEvents } from 'vjcad';
import type {
EntityEventArgs,
DocumentEventArgs,
CommandEventArgs,
CancellableEventArgs
} from 'vjcad';
// Entity event arguments
Engine.eventManager.on(CadEvents.EntityAdded, (args: EntityEventArgs) => {
const entity = args.entity;
const timestamp = args.timestamp;
});
// Document event arguments
Engine.eventManager.on(CadEvents.DocumentOpened, (args: DocumentEventArgs) => {
const doc = args.document;
});
// Command event arguments
Engine.eventManager.on(CadEvents.CommandStarted, (args: CommandEventArgs) => {
const cmdName = args.commandName;
});
// Cancelable event arguments
Engine.eventManager.on(CadEvents.EntityErasing, (args: CancellableEventArgs & EntityEventArgs) => {
args.cancel = true; // Cancel operation
});Engine.eventManager API
| Method | Description |
|---|---|
on(event, handler) | Listen to an event |
off(event, handler) | Remove a listener |
once(event, handler) | One-time listener |
offAll(event?) | Remove all listeners |
emit(event, args) | Emit an event, for internal use |
suspendEvents() | Suspend events |
resumeEvents() | Resume events |
isSuspended() | Check whether events are suspended |
hasListeners(event) | Check whether an event has listeners |
listenerCount(event) | Get the number of listeners |
Use Cases
Auto Save
Engine.eventManager.on(CadEvents.DocumentModified, (args) => {
scheduleAutoSave();
});Operation Logging
Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
log(`User executed command: ${args.commandName}`);
});Entity Validation
Engine.eventManager.on(CadEvents.EntityAdding, (args) => {
if (!validateEntity(args.entity)) {
args.cancel = true;
}
});React to Selection Changes
Engine.eventManager.on(CadEvents.SelectionChanged, (args) => {
updatePropertyPanel(args.selection);
});Event Timing in Interactive Commands
Important: Event Timing for PLINE and Other Interactive Commands
Interactive drawing commands (such as PLINE and SPLINE) fire events at different stages during user input. Taking the PLINE command as an example:
- When the user clicks the 2nd point → entity is created →
EntityAddedfires (the entity has only 2 points) - When the user clicks the 3rd, 4th, … points → the existing entity is modified →
EntityModifiedfires - When the user presses Enter to finish →
CommandEndedfires (the entity now contains all points)
If you read points inside the EntityAdded callback, you will only get the initial 2 points, not the full data.
// ❌ Wrong: reading full points in EntityAdded
Engine.eventManager.on(CadEvents.EntityAdded, (args) => {
if (args.entity.type === 'PLINE') {
// Only the first 2 points exist at this stage!
console.log(args.entity.bulgePoints.items.length); // Output: 2
}
});
// ✅ Correct approach 1: read full data in CommandEnded
Engine.eventManager.on(CadEvents.CommandEnded, (args) => {
if (args.commandName === 'PLINE') {
const plines = Engine.getEntities(e => e.type === 'PLINE');
const lastPline = plines[plines.length - 1];
console.log(lastPline.bulgePoints.items.length); // Output: all points
}
});
// ✅ Correct approach 2: track point changes in real time
Engine.eventManager.on(CadEvents.EntityModified, (args) => {
if (args.entity.type === 'PLINE') {
console.log('Current point count:', args.entity.bulgePoints.items.length);
}
});Entity vs. toDb() Point Structures
The entity object and the toDb() serialized output use different structures for storing points:
| Property | Entity Object | toDb() Output |
|---|---|---|
| Point collection | entity.bulgePoints.items | dbPline.dbBulgePoints.pts |
| Single point coordinate | bp.point2d.x / .y | pts[i][0] / pts[i][1] |
| Bulge | bp.bulge | bulges?[i] (omitted when all zero) |
// Read points from toDb()
const dbDoc = Engine.currentDoc.toDb();
const modelBlock = dbDoc.dbBlocks['*Model'];
const dbPline = modelBlock.items.find(item => item.type === 'PLINE');
if (dbPline) {
const pts = dbPline.dbBulgePoints.pts; // [[x,y], [x,y], ...]
const bulges = dbPline.dbBulgePoints.bulges; // [b1, b2, ...] or undefined
pts.forEach((pt, i) => {
const x = pt[0], y = pt[1];
const bulge = bulges ? bulges[i] : 0;
console.log(`Point ${i}: (${x}, ${y}), bulge=${bulge}`);
});
}
// Recreate an entity from DB data
const newPline = new PolylineEnt(new BulgePoints());
newPline.fromDb(dbPline);Online Example
For a full interactive demo see Polyline Points & Event Timing{target="_blank"}
Next Steps
- Reactor System - entity relationships
- Plugin System - use events in plugins