opengridv0.13.0

Saving edits from the page

A grid that saves every cell on its own — naming the record, saying what became of an edit, and showing values the page computes.

The grid edits; the page saves. opengrid has no write path — the engine’s contract is a query — so an edit leaves the grid as an opengrid-cell-change event, and storing it is the page’s. This guide is about a page that stores every cell as soon as the reader leaves it: a grade entry grid, say, where each grade is saved on its own and an average next to it moves as the grades come in.

Such a page needs three things from the grid. The signatures are in The public API → Saving edits; this page is about using them.

Name the record

<opengrid-grid label="Grades" datasource="grades" columns="student,name,test_1,test_2,average"
               row-key="student"></opengrid-grid>

row-key names the field that identifies a record. With it, opengrid-cell-change carries key, the edited row’s value of that field, next to row:

grid.addEventListener("opengrid-cell-change", ({ detail }) => {
  detail.key;     // "s-1042" — the record
  detail.row;     // 7 — a position, right only until something sorts or reloads
  detail.column;  // "test_2"
  detail.value;   // "2.3", in the column's notation
});

Save by key. A save takes time, and in that time the reader may sort, filter or scroll — the seventh row is then someone else. The key field should be one of the grid’s columns; when the result does not carry it, key is null.

Say what became of the edit

const { module } = await loadOpengrid();

grid.addEventListener("opengrid-cell-change", async ({ detail }) => {
  const { key, column, value } = detail;
  module.set_cell_state(grid, key, column, "saving");
  try {
    await fetch(`/api/grades/${key}/${column}`, { method: "PUT", body: value });
    module.set_cell_state(grid, key, column, "saved");
  } catch {
    module.set_cell_state(grid, key, column, "error", `Grade for ${key} was not saved.`);
  }
});

Until the page says otherwise, an edited value is marked unsaved. saved takes the mark off — no reload needed. error keeps the value and the mark, since what the reader typed is still not stored, and says the message in the grid’s live region: the same single, polite region that announces results, so a screen reader hears it without the focus moving. A failed cell also carries aria-invalid="true", so a reader who comes back to it later hears that something is wrong with it.

Write the message for someone who is not looking at the cell: name the record and what failed.

Styling. Each state is a part of its own next to cell: cell-saving, cell-saved and cell-error. The grid’s own sheet draws them quietly — the muted ink while saving, a wavy underline on a failure. A page restyles them from outside:

opengrid-grid::part(cell-saving) { opacity: 0.7; }
opengrid-grid::part(cell-error) { background: #fde8e8; }

Don’t let colour alone carry a failure: the default underline is a shape, and the message and aria-invalid carry it for a screen reader.

Show values the page computes

module.set_columns(grid, { average: { readonly: true, align: "end" } });

function showAverages(students) {
  module.set_values(
    grid,
    students.map((s) => ({ key: s.id, column: "average", value: s.average })),
  );
}

set_values puts values into cells without making them edits: no unsaved mark, no opengrid-cell-change. Call it whenever the page has a new value — after a grade was saved, for instance. A value stays with its record and stands over the source’s value in every result that follows, sorted or filtered, until the page sets another.

A column the page fills is readonly: it never opens an editor (an edit there would be overwritten by the next value) and its cells say aria-readonly="true". The column still has to be one of the grid’s columns; what the source has in it is shown until the page sets a value.

Enter values quickly

A reader fills such a grid from the keyboard: focus a cell, type the grade, Enter. A typed character opens the editor with that character in it; in a column with set_choices it picks the one choice that is the character or starts with it — 3 picks 3 from 1–6 and from 3+, 3, 3- alike. Enter commits, as it does after Enter or F2 opened the editor; nothing is taken without it. The focus stays on the cell afterwards: the reader moves on with the arrows.

What stays and what goes

  • A state or a value names a record, so it outlives sorting, filtering and reloading.
  • Another row-key or another datasource forgets them: the keys no longer mean the same records.
  • A fresh result still replaces an edited value with the source’s — the grid shows what the source says. The state the page set stays on the cell.
  • Nothing here moves the focus.

Not part of this: undo, saving several cells as one batch, and conflicts between two readers editing the same record. Those are the page’s, together with the saving itself.

Edit this page on GitHub · Docs for v0.13.0