<bmx-workflow-designer>
Draws approval and process workflows: states in swimlanes by role, the
transitions between them (the actions people take, with conditions,
who may take them, automatic routing and required comments), values the
workflow carries and sets on entry, and time limits with escalation. A
problems list checks the workflow as it is drawn, and Simulate runs it -
the same engine bmx-workflow-engine and bmx-workflow.mjs (Node) use -
acting as each role, so a process is tried before anyone relies on it.
4 properties · 3 events · 10 methods · 7 parts
Example
Drag a state, connect two from a state's handle, or switch to Simulate and run a claim.
Show markup
<bmx-workflow-designer id="ex-wd" style="--bmx-workflow-designer-height: 32rem"></bmx-workflow-designer>
<p class="note" id="ex-wd-out" role="status" style="margin: 12px 0 0">Drag a state, connect two from a state's handle, or switch to Simulate and run a claim.</p>
<script type="module">
await customElements.whenDefined('bmx-workflow-designer');
const wd = document.getElementById('ex-wd');
const def = {
id: 'expenses', name: 'Expense claim', revision: 3,
roles: [
{ id: 'employee', name: 'Employee', color: '#a5d6a7' },
{ id: 'manager', name: 'Manager', color: '#90caf9' },
{ id: 'finance', name: 'Finance', color: '#ffcc80' },
],
variables: [
{ name: 'amount', type: 'number', label: 'Amount', default: 0 },
{ name: 'region', type: 'text', label: 'Region', default: 'UK' },
{ name: 'tier', type: 'text', label: 'Tier' },
{ name: 'paid', type: 'boolean', label: 'Paid', default: false },
],
states: [
{ id: 'start', name: 'Submitted', kind: 'start', x: 160, y: 460 },
{ id: 'route', name: 'Route by amount', kind: 'decision', x: 340, y: 460 },
{ id: 'fix', name: 'Fix and resubmit', kind: 'task', role: 'employee', x: 340, y: 40 },
{ id: 'manager', name: 'Manager review', kind: 'approval', role: 'manager', description: 'Check the receipts against the policy.', sla: { hours: 24, escalateTo: 'finance' }, actions: [{ type: 'emit', event: 'notify', data: { to: 'manager' } }], x: 560, y: 180 },
{ id: 'finance', name: 'Finance review', kind: 'approval', role: 'finance', description: 'Check the budget and pay.', actions: [{ type: 'set', variable: 'tier', value: '=IF(amount > 5000, "high", "standard")' }], x: 560, y: 320 },
{ id: 'hold', name: 'Wait for budget', kind: 'wait', role: 'finance', sla: { days: 5 }, x: 820, y: 320 },
{ id: 'paid', name: 'Paid', kind: 'end', actions: [{ type: 'set', variable: 'paid', value: true }], x: 820, y: 460 },
{ id: 'rejected', name: 'Rejected', kind: 'end', x: 1040, y: 180 },
],
transitions: [
{ id: 'go', from: 'start', to: 'route', auto: true },
{ id: 'small', from: 'route', to: 'manager', guard: '=amount <= 1000' },
{ id: 'large', from: 'route', to: 'finance' },
{ id: 'approve', from: 'manager', to: 'paid', name: 'Approve' },
{ id: 'reject', from: 'manager', to: 'rejected', name: 'Reject', requireComment: true },
{ id: 'back', from: 'manager', to: 'fix', name: 'Send back', requireComment: true },
{ id: 'pay', from: 'finance', to: 'paid', name: 'Pay', guard: '=AND(amount > 0, region = "UK")' },
{ id: 'park', from: 'finance', to: 'hold', name: 'Hold' },
{ id: 'release', from: 'hold', to: 'paid', name: 'Release' },
{ id: 'freject', from: 'finance', to: 'rejected', name: 'Reject', requireComment: true },
{ id: 'resubmit', from: 'fix', to: 'route', name: 'Resubmit' },
],
};
wd.definition = def;
const out = document.getElementById('ex-wd-out');
wd.addEventListener('bmxWorkflowChange', e => {
const { definition, reason, problems } = e.detail;
const errors = problems.filter(p => p.severity === 'error').length;
out.textContent = `${reason}: ${definition.states.length} states, ${definition.transitions.length} transitions, ${errors ? `${errors} to fix` : 'ready to publish'} - the page saves the JSON as a draft.`;
});
</script>
<bmx-workflow-designer id="designer"></bmx-workflow-designer>
<script>
designer.definition = await api.workflow('expenses');
designer.addEventListener('bmxWorkflowChange', e => api.saveDraft(e.detail.definition));
publish.onclick = async () => api.publish(await designer.bumpRevision());
</script>
The definition is plain JSON (exportJson; a Mermaid diagram with
exportMermaid). Drag a state to move it - into another lane to give it
that role; drag from its handle to another state to connect them. Every
part can also be made and changed from the panel beside the drawing, with
no pointer at all.
Keyboard: the drawing is one tab stop; arrows move between states, ] and
[ through a state's transitions, Ctrl+arrows move the state, Enter opens
its properties, Delete deletes, Ctrl+Z and Ctrl+Y undo and redo, + - and 0
zoom.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
definition |
definition |
BmxWorkflowDefinition | string |
'' |
The workflow, as an object or JSON. Mutable: edits write it back. |
mode |
mode |
'design' | 'simulate' |
'design' |
design to draw and edit; simulate to run it. Mutable. |
readonly |
readonly |
boolean |
false |
Show the workflow without allowing changes (simulating still works). |
strings |
strings |
Partial<BmxWorkflowDesignerStrings> | string |
— | Words to show instead of the English ones, as an object or JSON. |
Events
| Event | Detail | Description |
|---|---|---|
bmxWorkflowChange |
BmxWorkflowDesignerChangeDetail |
Fired after every change: save the definition. |
bmxWorkflowSelect |
BmxWorkflowSelection |
Fired when the selection changes. |
bmxWorkflowSimulate |
{ instance: BmxWorkflowInstance | null; events: BmxWorkflowEmitted[]; } |
Fired as the simulator moves. |
Methods
| Method | Signature | Description |
|---|---|---|
autoLayout |
autoLayout() => Promise<void> |
Lay every state out again, by distance from the start and by lane. |
bumpRevision |
bumpRevision() => Promise<BmxWorkflowDefinition> |
Raise the revision by one (when the page publishes it). Resolves the definition. |
exportJson |
exportJson() => Promise<string> |
The workflow as JSON. |
exportMermaid |
exportMermaid() => Promise<string> |
The workflow as a Mermaid state diagram. |
fitView |
fitView() => Promise<void> |
Show the whole workflow. |
getDefinition |
getDefinition() => Promise<BmxWorkflowDefinition> |
The workflow as it stands. |
redo |
redo() => Promise<boolean> |
Redo the last undone change. |
select |
select(id: string | null) => Promise<void> |
Select a state or transition by id (null for none). |
undo |
undo() => Promise<boolean> |
Undo the last change. |
validate |
validate() => Promise<BmxWorkflowProblem[]> |
Everything wrong with the workflow now. |
CSS shadow parts
| Part | Description |
|---|---|
canvas |
The box the workflow is drawn in. |
lane |
A swimlane. |
panel |
The properties or simulator panel. |
problems |
The problems list. |
state |
A state; also state-selected and state-current (simulating). |
toolbar |
The bar of tools. |
transition |
A transition. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-workflow-designer-approval |
The colour of approval states. |
--bmx-workflow-designer-canvas |
The canvas background. |
--bmx-workflow-designer-current |
The colour that marks where a simulation is. |
--bmx-workflow-designer-decision |
The colour of decisions. |
--bmx-workflow-designer-end |
The colour of end states. |
--bmx-workflow-designer-height |
The height of the canvas. |
--bmx-workflow-designer-panel-width |
The width of the properties panel. |
--bmx-workflow-designer-start |
The colour of start states. |
--bmx-workflow-designer-task |
The colour of task states. |
--bmx-workflow-designer-wait |
The colour of wait states. |