Skip to main content

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

EventPayloadDescription
readyvoidChamevoJS fully initialized.
loading{ isLoading: boolean }Loading operation started or ended.

Product

EventPayloadDescription
productCreate{ product: CVProduct }New product loaded.
productChange{ product: CVProduct }Current product data changed.

Views

EventPayloadDescription
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

EventPayloadDescription
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.

EventPayloadDescription
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

EventPayloadDescription
undovoidAfter an undo operation.
redovoidAfter a redo operation.

Pricing

EventPayloadDescription
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

EventPayloadDescription
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

EventPayloadDescription
customizationChange{ valid: boolean, rule: 'any' | 'all', views: boolean[] }Customization validity changed. valid reflects whether the product meets the active rule.

UI

EventPayloadDescription
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 EventCore Evente.detail
cvReadyready{ chamevo: ChamevoJS, canvas: ChamevoCanvas }
cvProductCreateproductCreate{ product: CVProduct }
cvProductChangeproductChange{ product: CVProduct }
cvViewSelectviewSelect{ view: CVView, index: number }
cvElementSelectelementSelect{ element: object | null }
cvPriceChangepriceChange{ 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