Events
ChamevoJS provides two event layers: core events on the ChamevoJS instance and web component events on the <cv-customizer> element.
Core Events (ChamevoJS)
Subscribe with .on(), unsubscribe with .off():
const handler = ({ element, view }) => {
console.log(`Added ${element.title} to ${view.title}`);
};
chamevo.on('elementAdd', handler);
chamevo.off('elementAdd', handler);
Complete Event Reference
Lifecycle
| Event | Payload | Description |
|---|---|---|
ready | void | ChamevoJS fully initialized. |
loading | { isLoading: boolean } | Loading operation started or ended. |
Product
| Event | Payload | Description |
|---|---|---|
productCreate | { product: CVProduct } | New product loaded. |
productChange | { product: CVProduct } | Current product data changed. |
Views
| Event | Payload | Description |
|---|---|---|
viewCreate | { view: CVView, index: number } | View created or added. |
viewSelect | { view: CVView, index: number } | View selected (switched to). |
viewUpdate | { view: CVView } | View content updated. |
viewLockChange | { viewIndex: number, locked: boolean, view: CVView } | View locked or unlocked (optional views). |
viewAdd | { view: CVView, index: number } | Dynamic view added. |
viewRemove | { view: CVView, index: number } | Dynamic view removed. |
viewDuplicate | { sourceIndex: number, newView: CVView, newIndex: number } | View duplicated. |
viewResize | { viewIndex: number, width: number, height: number, unit: string } | Dynamic view resized. |
viewReorder | { fromIndex: number, toIndex: number } | Views reordered via drag-and-drop. |
printAreasReady | { viewIndex: number, printAreas: CVPrintAreaConfig[] } | Print areas set up on the canvas after view activation. Includes full print area config (printingBox, output, mask, etc.). |
Elements
| Event | Payload | Description |
|---|---|---|
elementAdd | { element: CVElementData, view: CVView } | Element added to a view. |
elementRemove | { element: CVElementData, view: CVView } | Element removed from a view. |
elementSelect | { element: Record<string, unknown> | null } | Selection changed. null = deselected. The element is the raw FabricJS object. |
elementModify | { element: CVElementData, changes: Partial<ElementParameters> } | Element modified (moved, scaled, edited, etc.). |
Preserved-Element Lifecycle
These fire during a product switch when replaceInitialElements preserves user-added (custom or fixed) elements and the new product has a different print-area layout.
| Event | Payload | Description |
|---|---|---|
globalElementRebound | { element: CVElementData, viewIndex: number, oldPrintAreaId: string, newPrintAreaId: string } | A preserved element's original printAreaId no longer exists on the new product. It is rebound to the print area at the same ordinal index (or the first one as a fallback) and its boundingBox is rewritten to the new printing box. |
globalElementDropped | { element: CVElementData, viewIndex: number, reason: 'no-print-areas' } | A preserved element could not be re-added because the target view has no print areas at all. It is silently dropped from the canvas. |
History
| Event | Payload | Description |
|---|---|---|
undo | void | After an undo operation. |
redo | void | After a redo operation. |
Pricing
| Event | Payload | Description |
|---|---|---|
priceChange | { price, singleProductPrice, pricingRulesPrice, breakdown, orderQuantity } | Price updated. price includes quantity multiplier, singleProductPrice is per-unit from element prices, pricingRulesPrice is the additional amount from pricing rules, breakdown contains one entry per matched pricing rule, orderQuantity is the current quantity. |
Image Upload Hooks
| Event | Payload | Description |
|---|---|---|
beforeImageUpload | { source: string, title: string, params: Record<string, unknown>, cancel: () => void } | Fired before an uploaded image is added to the canvas. Call cancel() to prevent. Handlers can be async and can mutate params. |
afterImageUpload | { element: CVElementData, source: string, title: string, view: CVView } | Fired after an uploaded image has been added to the canvas. |
Customization
| Event | Payload | Description |
|---|---|---|
customizationChange | { valid: boolean, rule: 'any' | 'all', views: boolean[] } | Customization validity changed. valid reflects whether the product meets the active rule. |
UI
| Event | Payload | Description |
|---|---|---|
moduleChange | { module: string } | Active UI module changed. |
Web Component Events (<cv-customizer>)
The <cv-customizer> element re-emits core events as CustomEvent with a cv prefix:
customizer.addEventListener('cvReady', (e) => {
const { chamevo, canvas } = e.detail;
});
customizer.addEventListener('cvPriceChange', (e) => {
const { price } = e.detail;
});
| Web Component Event | Core Event | e.detail |
|---|---|---|
cvReady | ready | { chamevo: ChamevoJS, canvas: ChamevoCanvas } |
cvProductCreate | productCreate | { product: CVProduct } |
cvProductChange | productChange | { product: CVProduct } |
cvViewSelect | viewSelect | { view: CVView, index: number } |
cvElementSelect | elementSelect | { element: object | null } |
cvPriceChange | priceChange | { price, singleProductPrice, pricingRulesPrice, breakdown, orderQuantity } |
Common Patterns
Auto-Save on Change
chamevo.on('productChange', ({ product }) => {
// Debounce and save
clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
fetch('/api/save', {
method: 'POST',
body: JSON.stringify(product),
});
}, 1000);
});
Price Display
chamevo.on('priceChange', ({ price, singleProductPrice, orderQuantity }) => {
document.getElementById('price').textContent =
`$${singleProductPrice.toFixed(2)} x ${orderQuantity} = $${price.toFixed(2)}`;
});
Custom Toolbar
chamevo.on('elementSelect', ({ element }) => {
if (!element) {
toolbar.hidden = true;
return;
}
toolbar.hidden = false;
const type = element.type as string;
// Show/hide controls based on element type
textControls.hidden = !type.includes('text');
imageControls.hidden = type.includes('text');
});
Image Upload Validation
// Validate uploads with a server before adding to canvas
chamevo.on('beforeImageUpload', async ({ source, title, params, cancel }) => {
const res = await fetch('/api/validate-image', {
method: 'POST',
body: JSON.stringify({ url: source }),
});
if (!res.ok) {
cancel(); // Prevent the image from being added
showError('Image not allowed');
}
});
// Mutate params before the image is added
chamevo.on('beforeImageUpload', ({ params }) => {
params.watermark = true; // Add a watermark flag
});
// Track uploads after they're added
chamevo.on('afterImageUpload', ({ element, source, view }) => {
analytics.track('image_uploaded', {
title: element.title,
viewId: view.id,
});
});
Validation Before Submit
submitBtn.addEventListener('click', async () => {
const product = chamevo.getProduct();
if (!product) return;
// Check if user made any customizations
const hasCustom = product.views.some(v =>
v.elements.some(el => el.parameters.editable !== false)
);
if (!hasCustom) {
alert('Please customize your product first');
return;
}
await submitOrder(product);
});
Next Steps
- ChamevoJS API — Full method reference
- Full Customizer — Web component event handling