Embedding the components
Four custom elements bring slotting into your own pages:
| Element | What it is |
|---|---|
<slotting-station> |
The operator's scan screen: scan a unit, see the bin to put it in, very large, on a drawing of the wall |
<slotting-wall> |
A drawing of one wall with its bins (free, held, disabled, highlighted), and optionally its put-to-light lights |
<slotting-setup> |
The bin setup editor: import (DXF, CSV, grid, photo), preview, edit the bins in a table, validate, save |
<slotting-wave-monitor> |
The progress of a wave: stats, the walls with the held bins, the orders, updated every few seconds |
They are standard custom elements in one ES module, with no framework runtime, so they work in plain HTML and in Angular,
React, Vue, Svelte and every other framework. The station, the setup editor and the monitor call the install's API
themselves (api-base); the wall draws what you give it, and calls the API only to show the lights.
Loading the script#
One ES module defines all four components. Your install serves it at two URLs, both with
Access-Control-Allow-Origin: *:
| URL | Caching | Use |
|---|---|---|
/widget/1.0.0/slotting-components.js |
public, max-age=31536000, immutable |
Production: pin the version and add SRI |
/widget/slotting-components.js |
no-cache |
Always the installed version; fine for development |
For this install (version 1.0.0), with its Subresource Integrity hash:
<script type="module" src="https://slotting.internal.example/widget/1.0.0/slotting-components.js"
integrity="sha384-1hTDsE/7ThzvFzX8J+xwUx0AjF16gdOWnKgPbt25wtRtY34ViTnLgJWgo3mfCZpU" crossorigin="anonymous"></script>Replace the host with your install's. The hash belongs to this version: after an upgrade, take the new one from these pages on the upgraded install, or compute it from the versioned URL:
curl -s https://slotting.internal.example/widget/1.0.0/slotting-components.js | openssl dgst -sha384 -binary | openssl base64 -A- Where the components send their requests.
api-basedefaults to the origin the script was loaded from, so components loaded from the install talk to the install. Setapi-baseto send them elsewhere, for example to your reverse proxy. - Pages on another origin. When your page (for example
https://wms.example.internal) is not served by the install, the browser only lets the components call the API if the install allows that origin, and the install refuses changes from origins it doesn't know (403 origin_not_allowed): setSLOTTING_CORS_ORIGINS=https://wms.example.internal(configuration). Alternatively, proxy/apiand/v1/infothrough your own server and setapi-baseto it. A proxy that lets only some routes through must allow what the operator components call:GET /v1/info,GET /api/waves/{waveId},.../pendingand.../orders/{orderId},GET /api/layouts/{layoutId},.../binsand.../lights, andPOST /api/waves/{waveId}/scan,.../undoand.../confirm(the wall'ssimulate-buttonsalsoPOST /api/lights/press). - Content-Security-Policy. The components work under a strict CSP with no
'unsafe-inline'or'unsafe-eval': they use constructed stylesheets in their shadow roots and set no inline styles. Allow the install inscript-srcand, for the API components,connect-src:script-src 'self' https://slotting.internal.example; connect-src https://slotting.internal.example; style-src 'self'. - Types. The declarations are served next to the script at
/widget/1.0.0/slotting-components.d.ts. Copy the file into your frontend's sources to type the elements, their properties and events.
The station#
<slotting-station wave-id="WAVE-0815" operator="station-1"></slotting-station>| Attribute / property | |
|---|---|
wave-id / waveId |
The wave to scan into. Required |
layout-id / layoutId |
Only when two layouts have a wave with the same id |
operator / operator |
Sent with every scan (the station or the operator's name) |
no-keyboard |
On handhelds: don't open the on-screen keyboard when the scan field takes the focus |
api-base, theme |
See Loading the script and Styling |
- Scanners. The scan field keeps the focus and takes keyboard-wedge input: the scanner types the code and Enter (or Tab). Several codes separated by line breaks work too. A quantity field next to it sends several units with one scan.
- The answer. The target bin is shown very large, highlighted on the wall drawing, with the order's progress. When the scan completes the order, the station lists the order's bins.
- Errors are shown so the operator knows what to do: put the unit aside (not in the wave, fully slotted, no bin free,
too large), retry (offline, no answer, busy), or a general error. Scans run one at a time, in order; each has its own
scanId, so Retry after "offline" can never put a unit twice. - Undo reverts the last successful scan.
- Methods:
scan(sku, quantity?)scans as if the scanner sent the code;undo();focus(). Read-only:wave(the wave as loaded, updated by every scan),lastResultandpendingConfirmation(below).
const station = document.querySelector('slotting-station');
station.addEventListener('put', e => console.log(`put ${e.detail.sku} into ${e.detail.bin.id}`));
station.addEventListener('order-complete', e => console.log(`order ${e.detail.orderId} complete:`, e.detail.bins.map(b => b.id)));
station.addEventListener('scan-error', e => {
if (e.detail.kind === 'aside') {
console.warn(`put ${e.detail.sku} aside: ${e.detail.message}`);
}
});Confirmed puts at the station#
In a wave with confirmPuts (Confirmed puts) the station asks for the bin scan by
itself; nothing changes in your page:
- The operator scans an item. The target bin is shown with Scan bin A-01-01 to confirm (parts
target-pendingandconfirm-prompt, colour--sl-target-pending). - The operator puts the unit in and scans the bin's label. The station sends it to
POST /api/waves/{waveId}/confirm, shows the put as confirmed (parttarget-confirmed) and firesconfirm. A put split over several bins asks for each bin in turn. - A wrong bin shows "Wrong bin" with the bin to use, and the station keeps waiting (
scan-errorwith codewrong_bin). - An item scanned meanwhile is not sent: the station asks to confirm the put first, or to press Undo (
scan-errorwith codeconfirm_first). - The confirmation of the order's last put fires
order-completeand lists the order's bins.
After a reload of the page, the station picks up where it was: it reads the wave's pending puts
(GET /api/waves/{waveId}/pending) and asks again for the oldest one of its operator (of anyone, without operator).
It reads them again each time a put is fully confirmed, so a put whose scan answer never reached the station (a timeout,
then the operator scanned the next unit) is asked for next instead of staying pending.
If the operator presses the bin's light button instead of scanning the label, the station notices it (it polls the light
state every 1.5 seconds while a put is pending) and goes on. pendingConfirmation is the put waiting for its bin scan:
{ scanId, waveId, orderId, sku, quantity, bins, result }, with bins the bins still to confirm; null otherwise.
station.addEventListener('confirm', e => console.log(`confirmed ${e.detail.bin.id} for ${e.detail.orderId}`));The wall#
<slotting-wall wall-id="A" order-id="ORD-1001"></slotting-wall>Set layout (the layout JSON, from GET /api/layouts/{layoutId}) and bins (the occupancy, from
GET /api/layouts/{layoutId}/bins) as properties, not attributes. The wall is drawn to the element's width, its height
capped by --sl-wall-height.
| Attribute / property | |
|---|---|
layout |
The layout (property only) |
wall-id / wallId |
The wall to draw; default the layout's first wall |
bins |
The occupancy (property only); bins without an entry are free |
highlight |
Bin ids to highlight, pulsing (steady with reduced motion), as an array (property only) |
order-id / orderId |
The order whose bins are emphasised |
selected |
The selected (outlined) bin |
lights / lights |
Poll and show the layout's lights |
lights-interval / lightsInterval |
How often to poll the lights, in milliseconds (default 1000, at least 500) |
simulate-buttons / simulateButtons |
A click on a lit bin presses its light button |
layout-id / layoutId |
The layout whose lights are polled; default the id of layout |
api-base, theme |
See Loading the script and Styling |
Clicking a bin fires bin-select with { binId, wallId, bin, state }.
Lights on the wall#
<slotting-wall lights simulate-buttons wall-id="A"></slotting-wall>With lights, the wall polls GET {api-base}/api/layouts/{layout-id}/lights (the light state)
and shows the lit bins: put pulsing in --sl-light-put with the quantity, complete steady in --sl-light-complete
(steady, with a thicker outline, for users who prefer reduced motion). It pauses while the page is hidden, stops when the
element is removed, and backs off when the install can't be reached (the message is in part lights-status). Without
lights the wall never calls the API.
With simulate-buttons, a click on a lit bin (or Enter or Space on it) presses its button, as a light module would:
POST /api/lights/press, which confirms the bin's pending put or releases its complete order
(Button presses). Then it fires light-press with { layoutId, binId, wallId, address, state, result, error }: result is the press route's answer, or error ({ status, code, message }) when it failed, for
example not_pending. A press the server ignored (result.action ignored: it repeated a press of a moment ago) is not
an error. While a bin's press is in flight, and for one second after its answer, further clicks and keys on that bin are
ignored, so a double click doesn't confirm the next put or release the order. Use it as a
simulator, or as a touch-screen alternative to the buttons.
lightStates is the light state of the last poll.
The setup editor#
<slotting-setup layout-id="PUTWALL-A"></slotting-setup>With layout-id, the editor loads that layout and Save replaces it (PUT); without it, Save creates a new layout
(POST). Users import walls (a DXF or CSV file, a grid form, or a photo with their measurements), see them on the wall
drawing, edit the bins in a table, validate and save. Files are read in the browser and sent to the install as JSON;
imports return drafts and never save. The photo import is disabled, with an explanation, when the install has no AI
provider configured (Bin setups).
- The bin table has a Light address column for put-to-light: empty means the bin id.
- Properties and methods:
layout(the draft, a copy; setting it replaces the draft),validate()(returns the problems and shows them),save()(validates and saves; resolves with the saved layout). - Events:
layout-imported({ source, layout, warnings }) andlayout-saved({ layout, created }).
The wave monitor#
<slotting-wave-monitor wave-id="WAVE-0815" interval="5000"></slotting-wave-monitor>Shows the wave's stats, its walls with the held bins and the list of orders with a status filter. It polls
GET /api/waves/{waveId} every interval milliseconds (default 3000, at least 1000) while the page is visible, until the
wave is closed. refresh() polls at once; every poll fires wave-update with the wave. layout-id is needed only when two
layouts have a wave with the same id.
Events#
All events are CustomEvents that bubble and are composed, so a listener on document sees them too.
| Event | From | detail |
|---|---|---|
scan |
station | { sku, quantity, scanId }: a code was scanned, the request is about to be sent |
put |
station | The scan's answer (ScanResult, as in the API reference) |
confirm |
station | The confirmation's answer (ConfirmResult), for each confirmed bin (Confirmed puts) |
order-complete |
station | { orderId, bins, result, confirm? }: the order is complete; bins nearest first. result is the scan that completed it (with confirmed puts: the scan whose confirmation did), confirm that confirmation (absent when the light button confirmed it) |
scan-error |
station | { sku, quantity, scanId, status, code, message, kind }; kind is aside, retry, blocked or error, code the API's code (also wrong_bin, not_pending) or offline, timeout, client_error, confirm_first (an item scanned while a put waits for its bin scan; not sent) |
bin-select |
wall | { binId, wallId, bin, state } |
light-press |
wall | { layoutId, binId, wallId, address, state, result, error } (with simulate-buttons) |
layout-imported |
setup | { source, layout, warnings }, source = dxf, csv, grid or photo |
layout-saved |
setup | { layout, created } |
wave-update |
monitor | The wave (WaveState) |
License#
The components that call the API read GET {api-base}/v1/info (cached for 10 minutes) and:
- on an evaluation license, show a TRIAL mark;
- when the install is not licensed (expired, missing or invalid), show a neutral "not licensed, contact your
administrator" notice instead of their content (part
blocked). The API answers402anyway; - when
/v1/infocan't be reached, work normally (they fail open; the API decides).
After installing a license key, clearInstallInfo() (exported by the module) makes the next component ask again.
Frameworks#
The pattern is the same everywhere: load the script once, let the framework pass the custom elements through, set object
values (layout, bins, highlight) as DOM properties, set the rest as attributes, and listen to the events.
Plain HTML: as above. Nothing else is needed.
Angular: add
CUSTOM_ELEMENTS_SCHEMAto the standalone component (or module) that uses them; bind properties with[layout]="layout"and events with(order-complete)="onComplete($event)".React 19: custom elements work as they are; React sets object props as properties. Type the tags with the helper in the declarations:
tsimport type { SlottingIntrinsicElements } from './slotting-components'; declare module 'react' { namespace JSX { interface IntrinsicElements extends SlottingIntrinsicElements {} } }Events with dashes (
order-complete) need a ref andaddEventListenerin auseEffect.Vue 3: tell the compiler the tags are custom elements (
compilerOptions.isCustomElement: tag => tag.startsWith('slotting-')in the Vue plugin options), bind objects with.prop(:layout.prop="layout") and listen with@order-complete.Svelte: works as it is:
<slotting-station wave-id={waveId} bind:this={station} />sets attributes and properties; listen to the events withstation.addEventListener('order-complete', ...)(oron:order-completein Svelte 4).