Calcumate (original) hooks in Calcumate3D
Sites written for Calcumate (original) only need to replace the snippet. Every hook Calcumate (original) exposed keeps working in Calcumate3D; the new calcumate:* events are the upgrade path, not a requirement.
The rule for every payload is superset: the keys Calcumate (original) sent are there with the same meaning, and Calcumate3D’s own keys sit beside them.
Hook-by-hook status
Section titled “Hook-by-hook status”| Calcumate (original) hook | Direction | Calcumate3D | Status |
|---|---|---|---|
CalcumateInitialized |
calculator → page | on every successful load, right after calcumate:ready; detail.isReady === true plus the identity keys and a state object shaped like the Calcumate (original) page documents |
✅ |
CalculationFinished |
calculator → page | after every calculation, right after calcumate:calculated; the packed containers verbatim (unit.unitDetails.unit.unitName, unitDetails.dimensions, item placements x/y/z…) |
✅ |
Booking |
calculator → page | at the CTA click (before the lead form / redirect) and again after the lead-form submit, as before; detail.selectedCalculation |
✅ |
userDetails |
calculator → page | on lead-form submit; name / email / phone + every field + the Calcumate (original) context (selectedItems, unit, unitDetails, int, domain, ref, sessionId) |
✅ |
RE-INIT |
page → calculator | window.dispatchEvent(new CustomEvent('RE-INIT')) reloads the configuration and re-reads the container attributes; also accepted as postMessage('RE-INIT') into an iframe |
✅ |
calcumateLoad (disable units) |
page → calculator | window.dispatchEvent(new CustomEvent('calcumateLoad', { detail: [<unit ID>, …] })) excludes those units from the recommendations; [] re-enables all. If results are on screen the calculation re-runs immediately |
✅ |
window.listUnits / window.disableUnits |
globals | the calculator’s units (the Builder unit rows, ID / unitName / unitWidth …) and the current disabled list |
✅ |
data-disable-list-results="true" |
container attribute | the results screen shows the unit (and the unit pager for a mix) without the recommendation list and CTA; toggled at runtime + RE-INIT as before |
✅ |
| CTA URL params | redirect | cm-qty, cm-id, cm-volume, cm-used-volume, the {unitId}={quantity} pairs of Unit Quantity (Multiple), and cm-location on CalcumateGEO calculators — plus the new cm-ids, cm-volume-unit, cm-recommendation |
✅ |
| Per-unit reserve URL | redirect | when the recommended unit has its own URL in the Builder (unit “reserve url”), the CTA opens it instead of the global CTA URL, with no params — as before | ✅ |
sessionStorage['FACILITY-ID'] |
internal | not used. Calcumate (original) kept its facility pick there; it was never meant as an integration point. Calcumate3D remembers the pick in localStorage['calcumate-3d-storage'], bound to the calculator, so another calculator on another route is unaffected and no SPA cleanup is needed. |
Calcumate (original) internal |
.select-locations-wrap CSS overrides |
styling | not reproduced — the facility selector is part of the entry screen and the calculator’s CSS is isolated from the page. Brand it through the Builder’s Design step. | Calcumate (original) only |
Payloads
Section titled “Payloads”CalcumateInitialized
Section titled “CalcumateInitialized”Calcumate (original) dispatched { isReady: true }. Calcumate3D dispatches:
{ isReady: true, calculatorId: 'abc123', // the calculator's id language: 'en', measurementSystem: 'metric' | 'imperial', selectedItems: [{ id, product, quantity }], // items restored from the visitor's last session (usually []) location: { locationId, id, name, url, distance, distanceUnit } | null, // CalcumateGEO facility calculating: false, state: { // the shape documented for Calcumate (original) isReady: true, calculating: false, selectedItems: [ /* the calculator's item rows + product, thumbnail, quantity */ ], location, isLocationEnabled, userGeo: null, selectedCategory: null, selectedCategoryList: [], customItems: [], primaryCalculation: null, secondaryCalculation: null, showDialog: false, isCSP: false }}It fires on every successful load (first boot, RE-INIT, each CalcumateGEO facility change); make handlers idempotent.
CalculationFinished
Section titled “CalculationFinished”An array, one entry per packed container of the best recommendation, in the same order as Calcumate (original) sent them, with extra keys added:
[{ unit: { id: 'unit-m', name: 'Medium Unit', quantity: 1, // how many containers of this unit the recommendation uses unitDetails: { // Calcumate (original) nested shape id: 'unit-m', name: 'Medium Unit', unit: { ID: 'unit-m', unitName: 'Medium Unit', unitWidth: '2', unitDepth: '3', unitHeight: '2.4', unitReserve: '' }, dimensions: { width: 200, depth: 300, height: 240 }, // cm // flat unit keys, as sent by Calcumate (original) ID: 'unit-m', unitName: 'Medium Unit', unitWidth: '2', unitDepth: '3', unitHeight: '2.4', // Calcumate3D cm keys width: 200, height: 240, depth: 300, volume: 14.4 // volume in m³ / ft³ } }, items: [{ id: 'bbq', product: 'BBQ', x: 0, y: 0, z: 0, width: 49.5, height: 120, depth: 60, orientation: 'normal', scale: false, volume: 0.36, quantity: 1, image: null, instanceId: 'bbq-0' }], totalItemsWeight: 908.4, totalVolume: 15.77, usedSpacePct: 72.5, measurementSystem: 'metric', animationDone: true}]Notes: unitWidth / unitDepth / unitHeight are your unit strings (metres or feet) exactly as before, while dimensions and the flat width / height / depth are centimetres. When the Calcumate service was unreachable and the calculator shows a local packing, the array is []. When a re-calculation did not change the packing, the entries are the previous full result’s.
Booking
Section titled “Booking”Fires at the CTA click (whatever the redirect and lead-capture settings) and again after the lead form is submitted — the same two emit points as Calcumate (original).
{ selectedCalculation: [ /* the CalculationFinished entries of the clicked recommendation, each + `location` */ ], recommendation: [ /* the same entries */ ], items: [{ id, product, quantity }], // the whole inventory location: { locationId, id, name, url, distance, distanceUnit } | null, cta: { url, label, resolvedUrl } // resolvedUrl = what the calculator opens (null when no redirect)}userDetails
Section titled “userDetails”{ // every answered field under its field id (Builder forms use opaque ids: "1", "2", …) "1": "Jane Doe", "2": "jane@example.com", "3": "+61 400000000", // canonical keys, filled from the first field of each type name: "Jane Doe", email: "jane@example.com", phone: "+61 400000000", bookingDate: "2026-11-01", "additional-info": "Ground floor please", // the first notes / message / comment field, when present // Calcumate (original) context selectedItems: [ /* item rows + product, thumbnail, quantity */ ], unit: { id, name, quantity, unitDetails }, // first unit of the recommendation the CTA was pressed for unitDetails: { … }, // its unitDetails int: '<integration id>' | null, domain: 'example.com', domain2: 'www.example.com', ref: '<token>', cEmail: null, sessionId: '<session id of this load>'}Lead data is never put in the CTA URL; this event (or calcumate:lead-captured) is the way to get it.
Inbound hooks
Section titled “Inbound hooks”calcumateLoad: filter the recommended units
Section titled “calcumateLoad: filter the recommended units”// exclude two units (ids as shown in the Builder / window.listUnits[].ID)window.dispatchEvent(new CustomEvent('calcumateLoad', { detail: ['unit-l', 'unit-xl'] }))// re-enable everythingwindow.dispatchEvent(new CustomEvent('calcumateLoad', { detail: [] }))
window.listUnits // the calculator's units: [{ ID, unitName, unitWidth, unitDepth, unitHeight, … }]window.disableUnits // the ids currently excludedA same-origin host can reach the globals through iframe.contentWindow.listUnits; a cross-origin host cannot, and gets the CALCUMATE_* postMessages instead. Dispatch calcumateLoad into the iframe’s window the same way, or embed with the direct script snippet so the calculator shares the page’s window.
Ids that do not name a unit of the loaded calculator are ignored (numbers are accepted and compared as strings). The list is a page-level setting: it survives RE-INIT and CalcumateGEO facility changes and is intersected with the current units on every calculation. If results are on screen when the event arrives, the calculation re-runs immediately; otherwise the list applies to the next one. Dispatch it after CalcumateInitialized (the unit list is known then).
data-disable-list-results: hide the recommendation list
Section titled “data-disable-list-results: hide the recommendation list”<div data-calcumate data-calcumate-ref="…" data-disable-list-results="true"></div>const el = document.querySelector('[data-calcumate]')el.setAttribute('data-disable-list-results', 'true') // or 'false' / removeAttributewindow.dispatchEvent(new CustomEvent('RE-INIT', {})) // re-reads the attribute, as beforeOnly the exact string "true" enables it. The results screen then shows the 3D unit (and the unit pager for a multi-unit recommendation) without the recommendation list and CTA; the item editor stays.
CTA redirect parity
Section titled “CTA redirect parity”| Calcumate (original) | Calcumate3D |
|---|---|
cm-qty = number of units of the first unit id |
cm-qty = units in the recommendation |
cm-id = the first unit’s id |
cm-id = the first unit’s id; cm-ids = every unit id of a mixed recommendation, comma-joined |
Unit Quantity (Multiple) → one {unitId}={count} param per unit id |
the same pairs, plus cm-recommendation=1.Small Unit,4.Large Unit |
cm-volume, cm-used-volume (m³ / ft³) |
the same, plus cm-volume-unit=m3|ft3 |
cm-location = selected facility id (CalcumateGEO) |
the same, whenever a facility is selected |
| unit “reserve url” set in the Builder → the CTA opens it, no params | the same |
Params are added with the URL API, so query keys already on the CTA URL are kept (a key spelled like a unit id would be overwritten by the pair above).
Host code that keeps working unchanged
Section titled “Host code that keeps working unchanged”window.addEventListener('CalcumateInitialized', (e) => { if (e.detail.isReady) init() })window.addEventListener('CalculationFinished', (e) => { const [first] = e.detail console.log(first.unit.unitDetails.unit.unitName, first.unit.unitDetails.dimensions, first.totalVolume)})window.addEventListener('Booking', (e) => { const { selectedCalculation } = e.detail })window.addEventListener('userDetails', (e) => { const { name, email, phone } = e.detail })window.dispatchEvent(new CustomEvent('calcumateLoad', { detail: [123, 456] }))window.dispatchEvent(new CustomEvent('RE-INIT', {}))