v2.0.0

<bmx-dashboard>

A dashboard layout: tiles on a grid of columns that the reader can drag, resize, expand and remove, with the other tiles moving out of the way and closing up. Each tile holds an element of the page's own - a chart, a gauge, a grid, anything - and the layout is JSON to save and restore.

13 properties · 3 events · 5 methods · 5 parts

Example

Add a note Reset the layout

1,284

This week

All systems running. This tile is locked: the others move around it.

Show markup
<div class="row" style="gap: 0.75rem; flex-wrap: wrap; align-items: center; margin-block-end: 0.75rem">
  <label><input type="checkbox" id="ex-db-edit" checked> Arrange</label>
  <bmx-button variant="outline" tone="neutral" id="ex-db-add">Add a note</bmx-button>
  <bmx-button variant="outline" tone="neutral" id="ex-db-reset">Reset the layout</bmx-button>
</div>

<bmx-dashboard id="ex-db" editable removable columns="12" row-height="70" label="Operations overview">
  <section data-tile="revenue" data-title="Revenue this year" data-w="8" data-h="4">
    <bmx-chart id="ex-db-chart" type="column" label="Revenue and cost, thousands" style="--bmx-chart-height: 10rem"></bmx-chart>
  </section>
  <section data-tile="cpu" data-title="CPU" data-w="4" data-h="4" style="display: grid; place-items: center">
    <bmx-gauge id="ex-db-cpu" value="72" unit="%" ranges="0-60:success:Normal,60-85:warning:Busy,85-:danger:Overloaded" target="80" style="--bmx-gauge-size: 12rem"></bmx-gauge>
  </section>
  <section data-tile="disk" data-title="Storage" data-w="4" data-h="2">
    <bmx-gauge type="linear" value="640" max="1000" unit=" GB" ranges="0-700:success,700-900:warning,900-:danger" label="Disk used"></bmx-gauge>
  </section>
  <section data-tile="sales" data-title="Sales against target" data-w="4" data-h="2">
    <bmx-gauge type="bullet" value="270" max="400" target="300" format="currency:GBP" ranges="0-200:neutral:Poor,200-300:neutral:Fair,300-:neutral:Good" label="Quarter to date (thousands)"></bmx-gauge>
  </section>
  <section data-tile="signups" data-title="Sign-ups" data-w="4" data-h="2">
    <p style="margin: 0; font-size: 1.75rem; font-weight: 600">1,284</p>
    <p style="margin: 0">This week <bmx-sparkline values="140,162,158,171,190,203,260" label="Sign-ups, last seven days"></bmx-sparkline></p>
  </section>
  <section data-tile="banner" data-locked data-w="12" data-h="1" style="display: flex; align-items: center; gap: 0.5rem">
    <strong>All systems running.</strong> <span>This tile is locked: the others move around it.</span>
  </section>
</bmx-dashboard>

<p class="note" id="ex-db-out" role="status"></p>

<script type="module">
  await customElements.whenDefined('bmx-dashboard');
  const db = document.getElementById('ex-db');
  const out = document.getElementById('ex-db-out');
  const initial = [
    { id: 'banner', x: 0, y: 0, w: 12, h: 1, locked: true },
    { id: 'revenue', x: 0, y: 1, w: 8, h: 4 },
    { id: 'cpu', x: 8, y: 1, w: 4, h: 4 },
    { id: 'disk', x: 0, y: 5, w: 4, h: 2 },
    { id: 'sales', x: 4, y: 5, w: 4, h: 2 },
    { id: 'signups', x: 8, y: 5, w: 4, h: 2 },
  ];
  db.layout = initial;

  await customElements.whenDefined('bmx-chart');
  const chart = document.getElementById('ex-db-chart');
  chart.data = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'].map((month, i) => ({ month, revenue: 120 + ((i * 37) % 80), cost: 90 + ((i * 23) % 50) }));
  chart.xField = 'month';
  chart.yFields = ['revenue', 'cost'];

  db.addEventListener('bmxDashboardChange', e => {
    out.textContent = `Layout ${e.detail.kind === 'set' ? 'set' : `changed (${e.detail.kind} ${e.detail.id})`}: ${JSON.stringify(e.detail.layout.map(({ id, x, y, w, h }) => ({ id, x, y, w, h })))}`;
  });
  document.getElementById('ex-db-edit').addEventListener('change', e => (db.editable = e.target.checked));
  document.getElementById('ex-db-reset').addEventListener('click', () => db.setLayout(initial));
  let notes = 0;
  document.getElementById('ex-db-add').addEventListener('click', () => {
    notes += 1;
    const note = document.createElement('section');
    note.dataset.tile = `note-${notes}`;
    note.dataset.title = `Note ${notes}`;
    note.textContent = 'A tile added from script: it takes the first free place.';
    db.addTile(note, { w: 4, h: 2 });
  });
  setInterval(() => {
    const cpu = document.getElementById('ex-db-cpu');
    if (cpu) cpu.value = Math.round(45 + Math.random() * 50);
  }, 4000);
</script>
...

A tile's place comes from layout ([{ id, x, y, w, h }]), or the first free place for its data-w by data-h. On a narrower screen the columns shrink and the tiles follow; on a phone they stack in reading order.

Keyboard: on a tile's grip, the arrow keys move it a cell at a time and Shift with them resizes it; Delete removes it when removable. Escape restores an expanded tile.

Properties

PropertyAttributeTypeDefaultDescription
autosave autosave string — A name to keep the reader's layout under in this browser, and restore it from next time (over layout; setLayout() replaces it).
columns columns number 12 Columns of the grid at full width.
compact compact BmxDashboardCompact 'vertical' How tiles close up after a change: vertical (float up, default) or none.
editable editable boolean false The reader can move and resize tiles.
gap gap number 12 The space between tiles, in pixels.
label label string — The dashboard's accessible name.
layout layout BmxDashboardItem[] | string [] Every tile's place and size: [{ id, x, y, w, h, minW, minH, maxW, maxH, locked }], as a property or JSON. Changes write back here.
maximizable maximizable boolean true Tiles with a title have a button to fill the dashboard.
minColumnWidth min-column-width number 64 The narrowest a column may be, in pixels: below it the grid has fewer columns and the tiles follow.
removable removable boolean false With editable, tiles have a button to remove them.
rowHeight row-height number 80 How tall a row is, in pixels.
stackBelow stack-below number 560 Below this width, in pixels, the tiles stack in one column.
strings strings Partial<BmxDashboardStrings> | string — Replacements for the dashboard's wording, as a property or JSON.

Events

EventDetailDescription
bmxDashboardChange BmxDashboardChangeDetail The layout changed.
bmxDashboardExpand BmxDashboardExpandDetail A tile was expanded to fill the dashboard, or restored.
bmxDashboardRemove BmxDashboardRemoveDetail The reader is removing a tile. Cancel it to keep the tile.

Methods

MethodSignatureDescription
addTile addTile(element: HTMLElement, place?: Partial<Omit<BmxDashboardItem, "id">>) => Promise<string> Adds an element as a tile, at a place or the first free one. Gives the tile's id.
expandTile expandTile(id: string | null) => Promise<void> Makes a tile fill the dashboard, or (with null) restores it.
getLayout getLayout() => Promise<BmxDashboardItem[]> Every tile's place and size.
removeTile removeTile(id: string) => Promise<boolean> Removes a tile and its element. Whether it was removed (bmxDashboardRemove can be cancelled).
setLayout setLayout(layout: BmxDashboardItem[] | string) => Promise<void> Replaces the layout.

CSS shadow parts

PartDescription
grid the area the tiles sit in.
resize-handle the corner a tile is resized by.
tile a tile.
tile-body what a tile holds.
tile-header a tile's title and buttons.

CSS custom properties

PropertyDescription
--bmx-dashboard-accent The drop place and the resize corner.
--bmx-dashboard-tile-background Behind a tile.
--bmx-dashboard-tile-border A tile's edge.
--bmx-dashboard-tile-padding The space inside a tile's body.
--bmx-dashboard-tile-radius A tile's corners.
--bmx-dashboard-tile-shadow A tile's shadow.