<bmx-bom-editor>
A multi-level bill of materials, edited in the page: the structure as an indented tree grid, the parts master, the summarised parts list for a build, where each part is used, and what changed against another version.
28 properties · 4 events · 23 methods · 9 parts
Example
Try it with your own BOM files in confidence: they are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.
Rolling up...
Show markup
<bmx-bom-editor id="ex-bom" label="Coastline e-bike, rev D" name="bom" quantity="25" compare-label="Rev C" description="Cost, weight and lead time roll up as you edit, for the build quantity and the day chosen. Try changing a quantity, dragging a line into another assembly, or pasting rows from a spreadsheet."></bmx-bom-editor>
<p class="note" id="ex-bom-out" role="status" style="margin: 12px 0 0">Rolling up...</p>
<script type="module">
await customElements.whenDefined('bmx-bom-editor');
const editor = document.getElementById('ex-bom');
const out = document.getElementById('ex-bom-out');
// A made-up e-bike. Both wheels use the same wheel assembly, so it is listed once.
const part = (id, name, type, more = {}) => ({ id, name, type, ...more });
const bike = {
name: 'Coastline e-bike',
currency: 'GBP',
parts: [
part('CB-100', 'Coastline e-bike', 'make', { revision: 'D', cost: 38, leadTime: 2 }),
part('FRM-20', 'Frame, aluminium 6061, size M', 'buy', { cost: 142, weight: 2.1, leadTime: 42, supplier: 'Northgate Tube' }),
part('FRK-20', 'Fork, rigid', 'buy', { cost: 38, weight: 0.9, leadTime: 28 }),
part('WHL-700', 'Wheel assembly 700c', 'make', { cost: 9, leadTime: 1 }),
part('RIM-700', 'Rim 700c, 32 hole', 'buy', { cost: 17.5, weight: 0.52, leadTime: 21 }),
part('SPK-292', 'Spoke 292 mm, stainless', 'buy', { cost: 0.31, weight: 0.007, leadTime: 14 }),
part('HUB-F', 'Hub, front', 'buy', { cost: 14, weight: 0.31, leadTime: 21 }),
part('TYR-700', 'Tyre 700 x 40c', 'buy', { cost: 11.8, weight: 0.48, leadTime: 10 }),
part('DRV-250', 'Drive unit 250 W', 'make', { cost: 6, leadTime: 2 }),
part('MOT-250', 'Hub motor 250 W', 'buy', { cost: 118, weight: 2.9, leadTime: 56, supplier: 'Eastwind Motors' }),
part('CTL-36', 'Controller 36 V', 'buy', { cost: 34, weight: 0.21, leadTime: 35 }),
part('BAT-418', 'Battery 36 V 418 Wh', 'buy', { cost: 186, weight: 2.6, leadTime: 49 }),
part('BAT-500', 'Battery 36 V 500 Wh', 'buy', { cost: 214, weight: 2.8, leadTime: 49 }),
part('HRN-1', 'Wiring harness', 'buy', { unit: 'm', cost: 2.4, weight: 0.06, leadTime: 14 }),
part('KIT-FX', 'Fastener kit', 'phantom'),
part('BLT-M5', 'Bolt M5 x 12', 'buy', { cost: 0.06, weight: 0.004, leadTime: 7 }),
part('BLT-M5-SS', 'Bolt M5 x 12, stainless', 'buy', { cost: 0.11, weight: 0.004, leadTime: 7 }),
part('NUT-M5', 'Nut M5, nyloc', 'buy', { cost: 0.03, weight: 0.002, leadTime: 7 }),
part('PNT-B', 'Paint, harbour blue', 'buy', { unit: 'l', cost: 31, weight: 1.3, leadTime: 5 }),
part('DWG-CB', 'General arrangement drawing'),
],
lines: [
{ id: 'L1', parent: 'CB-100', part: 'FRM-20', quantity: 1, findNumber: '10' },
{ id: 'L2', parent: 'CB-100', part: 'FRK-20', quantity: 1, findNumber: '20' },
{ id: 'L3', parent: 'CB-100', part: 'WHL-700', quantity: 2, findNumber: '30' },
{ id: 'L4', parent: 'WHL-700', part: 'RIM-700', quantity: 1, findNumber: '10' },
{ id: 'L5', parent: 'WHL-700', part: 'SPK-292', quantity: 32, scrap: 2, findNumber: '20' },
{ id: 'L6', parent: 'WHL-700', part: 'HUB-F', quantity: 1, findNumber: '30' },
{ id: 'L7', parent: 'WHL-700', part: 'TYR-700', quantity: 1, findNumber: '40' },
{ id: 'L8', parent: 'CB-100', part: 'DRV-250', quantity: 1, findNumber: '40' },
{ id: 'L9', parent: 'DRV-250', part: 'MOT-250', quantity: 1, findNumber: '10' },
{ id: 'L10', parent: 'DRV-250', part: 'CTL-36', quantity: 1, findNumber: '20' },
{ id: 'L11', parent: 'DRV-250', part: 'HRN-1', quantity: 1800, unit: 'mm', findNumber: '30' },
// The bigger battery replaces the smaller one from the new year.
{ id: 'L12', parent: 'CB-100', part: 'BAT-418', quantity: 1, findNumber: '50', effectiveTo: '2026-12-31' },
{ id: 'L13', parent: 'CB-100', part: 'BAT-500', quantity: 1, findNumber: '50', effectiveFrom: '2027-01-01' },
{ id: 'L14', parent: 'CB-100', part: 'KIT-FX', quantity: 1, findNumber: '60' },
{ id: 'L15', parent: 'KIT-FX', part: 'BLT-M5', quantity: 8, alternates: [{ part: 'BLT-M5-SS' }] },
{ id: 'L16', parent: 'KIT-FX', part: 'NUT-M5', quantity: 8 },
{ id: 'L17', parent: 'CB-100', part: 'PNT-B', quantity: 0.35, findNumber: '70' },
{ id: 'L18', parent: 'CB-100', part: 'DWG-CB', quantity: 1, reference: true },
],
};
// Rev C, last month: 28 spokes and a shorter harness.
const revC = structuredClone(bike);
revC.parts.find(p => p.id === 'CB-100').revision = 'C';
revC.lines.find(l => l.id === 'L5').quantity = 28;
revC.lines.find(l => l.id === 'L11').quantity = 1500;
editor.bom = bike;
editor.compareWith = revC;
const money = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' });
const report = async () => {
const r = await editor.getRollup();
out.textContent = r.unitCost === null
? 'Add parts with costs to see the roll-up.'
: `${money.format(r.unitCost)} a bike, ${money.format(r.totalCost)} for ${r.quantity}; ${r.totalWeight.toFixed(1)} kg in all; ${r.leadTime} days from order to the first bike.`;
};
editor.addEventListener('bmxChange', report);
await report();
</script>
<bmx-bom-editor label="Pump 100, rev C" name="bom" quantity="25"></bmx-bom-editor>
<script>
editor.bom = { currency: 'GBP', parts: [...], lines: [...] };
editor.compareWith = revisionB;
</script>
A part is listed once and every assembly that uses it sees the same part, so changing a sub-assembly changes it everywhere it is used (the details panel says how many places). Cost, weight and cumulative lead time are rolled up as you type, for the build quantity and the day chosen (effectivity), with the critical path of lead time marked. Lines carry quantities in their own unit (converted to the part's stock unit), scrap, find numbers, reference designators, effectivity dates, reference-only lines and alternates. Problems - parts missing, cycles, units that do not convert, designators that do not match the quantity - are listed and marked on their rows.
Files come from ERP and PLM exports - indented or parent-child CSV, JSON,
a nested JSON tree - and go out as JSON, CSV in each layout, a change list
and a printable PDF. Rows pasted from a spreadsheet become lines. Nothing
leaves the page. In a form, name receives the BOM as JSON.
Structure keyboard: arrows move (Right opens, then steps into the cells; Left steps back and closes), Enter or F2 edits the cell, typing starts an edit, Insert adds a component, Delete deletes the line, Alt+Up/Down moves it, Alt+Right puts it into the line above, Alt+Left takes it out of its assembly, Ctrl+D duplicates, Ctrl+C copies, Ctrl+V pastes rows, Shift+F10 opens actions.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
bom |
bom |
BmxBom | string | null |
null |
The bill of materials: { parts, lines } as an object or JSON (a nested tree or a CSV text are read too). Two-way: bmxChange reports every edit. |
columns |
columns |
string[] | string |
— | The structure's columns, in order, as an array, JSON or a comma list (part, find, name, qty, ..., and attr:<key> for your own). Two-way: the Columns menu changes it. |
compareLabel |
compare-label |
string |
— | What the other version is called. |
compareWith |
compare-with |
BmxBom | string |
— | Another version to compare with (an object, JSON or CSV text). |
currency |
currency |
string |
— | ISO 4217 code for costs. Default: the BOM's currency. |
description |
description |
string |
— | Help text below the editor. |
disabled |
disabled |
boolean |
false |
Disable the editor. |
effectiveDate |
effective-date |
string |
— | The day lines must be in effect on (YYYY-MM-DD), all for every line. Default: today. Two-way. |
errorText |
error-text |
string |
— | An error supplied by the page. |
expandDepth |
expand-depth |
number |
2 |
How many levels start open. |
hideIneffective |
hide-ineffective |
boolean |
false |
Leave out lines not in effect on the day (by default they are shown, dimmed). |
hideLabel |
hide-label |
boolean |
false |
Hide the label visually while keeping it as the accessible name. |
hideSummary |
hide-summary |
boolean |
false |
Hide the totals under the views. |
hideToolbar |
hide-toolbar |
boolean |
false |
Hide the toolbar. |
label |
label |
string |
— | The field's label: names the whole control. |
locale |
locale |
string |
— | BCP 47 locale for numbers and dates. Default: the page's language. |
messages |
messages |
Record<string, string> | string |
— | Problem messages in place of the English ones, by code (missing-part, cycle, ...), as an object or JSON. |
name |
name |
string |
— | The field's name in a form: it receives the BOM as JSON. |
panel |
panel |
boolean |
true |
Show the details panel. Two-way. |
pdfStrings |
pdf-strings |
Partial<BmxBomPdfStrings> | string |
— | Words for the printed PDF, as an object or JSON. |
quantity |
quantity |
number |
1 |
How many of the top assembly are built. Two-way. |
readonly |
readonly |
boolean |
false |
Allow looking, finding, comparing and saving, but not changing. |
requireValid |
require-valid |
boolean |
false |
Keep the form from submitting while any problem is an error. |
root |
root |
string |
— | The top assembly shown, by part number. Default: the BOM's root, else its first top assembly. Two-way. |
src |
src |
string |
— | An address to read the BOM from (the page's own server): JSON or CSV. |
strings |
strings |
Partial<BmxBomEditorStrings> | string |
— | Words to show instead of the English ones, as an object or JSON. |
view |
view |
BmxBomEditorView |
'structure' |
The view shown. Two-way. |
views |
views |
BmxBomEditorView[] | string |
— | The views offered, as an array, JSON or a comma list. Default: all, compare only with compare-with. |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxBomChangeDetail |
Fired after the BOM changes. |
bmxEdit |
BmxBomEditDetail |
Fired before an edit. Cancel it to refuse the edit. |
bmxSelect |
BmxBomSelectDetail |
Fired when the selected row or part changes. |
bmxValidate |
BmxBomValidateDetail |
Fired after each check. |
Methods
| Method | Signature | Description |
|---|---|---|
addLine |
addLine(parent: string, line: Partial<Omit<BmxBomLine, "parent">> & { part: string; }, index?: number) => Promise<string | null> |
Add a line to an assembly (the part is added to the parts list when it is new). Returns the line's id, or null when refused. |
collapseAll |
collapseAll() => Promise<void> |
|
compare |
compare(other?: BmxBom | string) => Promise<BmxBomComparison | null> |
What differs from compare-with (or another BOM given here). |
expandAll |
expandAll() => Promise<void> |
|
export |
export(kind: BmxBomExportKind) => Promise<Blob> |
A file of the BOM: JSON, CSV in each layout, the change list against compare-with, or a PDF. |
getBom |
getBom() => Promise<BmxBom> |
The BOM now. |
getPartsList |
getPartsList(include?: "leaves" | "bought" | "all") => Promise<BmxBomFlatList> |
The summarised parts list for the build. |
getProblems |
getProblems() => Promise<BmxBomProblem[]> |
The problems found by the last check. |
getRollup |
getRollup() => Promise<{ root: string | null; quantity: number; unitCost: number | null; totalCost: number | null; costComplete: boolean; unitWeight: number | null; totalWeight: number | null; weightComplete: boolean; leadTime: number; }> |
The top assembly's roll-up for the build quantity and day. |
getSelection |
getSelection() => Promise<BmxBomSelectDetail> |
The selected row. |
load |
load(file: Blob | string, fileName?: string) => Promise<BmxBomProblem[]> |
Read a file (JSON, a nested JSON tree, or CSV in the indented, parent-child or parts layout), as an edit that can be undone. Returns what reading found. |
redo |
redo() => Promise<boolean> |
|
removeLine |
removeLine(id: string) => Promise<boolean> |
Delete a line. |
replacePart |
replacePart(from: string, to: string) => Promise<number | null> |
Use another part wherever one is used. Returns how many lines changed (null when refused). |
save |
save(kind?: BmxBomExportKind, fileName?: string) => Promise<void> |
Download a file of the BOM (see export). |
select |
select(target: string) => Promise<boolean> |
Select a row by its key (L1/L5), a line id, or a part number (its first place), and show it. Returns whether it was found. |
setBom |
setBom(bom: BmxBom | string, options?: { keepHistory?: boolean; fileName?: string; }) => Promise<void> |
Replace the BOM (an object, JSON, a nested tree or CSV text). With keepHistory, it can be undone. |
setFocus |
setFocus() => Promise<void> |
|
undo |
undo() => Promise<boolean> |
|
updateLine |
updateLine(id: string, patch: Partial<Omit<BmxBomLine, "id" | "parent">>) => Promise<boolean> |
Change fields of a line. Returns whether it changed. |
updatePart |
updatePart(id: string, patch: Partial<BmxBomPart>) => Promise<boolean> |
Change a part (a new id renumbers it everywhere). Returns whether it changed. |
validate |
validate() => Promise<BmxBomProblem[]> |
Check now. Returns every problem. |
whereUsed |
whereUsed(part: string) => Promise<BmxBomWhereUsed> |
Where a part is used. |
Slots
| Slot | Description |
|---|---|
description |
Rich help text, in place of the description property. |
label |
Rich label content, in place of the label property. |
CSS shadow parts
| Part | Description |
|---|---|
description |
The help text under the field. |
details |
The details panel. |
grid |
Each view's grid. |
label |
The label element. |
menu |
The actions and columns menus. |
problems |
The problems panel. |
row |
A grid row; also row-selected, row-problem. |
summary |
The totals under the views. |
toolbar |
The bars of views, settings and commands. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-bom-editor-background |
The views' background. |
--bmx-bom-editor-details-width |
The width of the details panel. |
--bmx-bom-editor-height |
The height of the views' scrolling area. |
--bmx-bom-editor-indent |
How far each level of the structure is indented. |
--bmx-bom-editor-label-font-size |
The label's font size. |
--bmx-bom-editor-radius |
Corner radius of the frame. |
--bmx-bom-editor-row-height |
The height of one row. |
--bmx-bom-editor-selected-background |
The selected row. |
--bmx-bom-editor-support-font-size |
Font size of the description and error message. |