v2.6.0

<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

PropertyAttributeTypeDefaultDescription
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

EventDetailDescription
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

MethodSignatureDescription
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

PartDescription
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

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