Lattice Grid Buy a licence

developer guide

Saved Views in a JavaScript Data Grid

A view captures how the grid is arranged, the sort, the filter, column order and width, grouping and the formatting rules, under a name a reader can return to. Views can live in your own storage or in the browser, and one of them can be the default.

Developer guidePresenting and saved views › Saved Views in a JavaScript Data Grid

Saved views

A view is a named grid state: sort, filters, grouping, column order, widths, visibility. There are two kinds and the picker keeps them apart.

Views you ship, and views the user saves

views: {
  saved: [
    { id: 'escalations', name: 'Escalations',
      description: 'Escalated circuits, worst SLA first',
      state: {
        filters: { col: 'statusId', op: 'eq', value: 4 },
        sort: [{ col: 'utilisation', dir: 'desc' }],
      }},
    { id: 'commercial', name: 'Commercial', isDefault: true,
      state: { columns: [{ id: 'notes', hidden: true }] }},
  ],
  allowSave: true,
}

Views in saved are defined views: part of the application, listed under their own heading, and neither renamable nor deletable: refused by the model as well as hidden in the interface. A view flagged isDefault is applied on load. Everything the user saves sits below, with rename, share, make-default and delete.

Applying a view is a destination, not a patch. A view's state names only the sections it cares about, so applying one resets to the grid's starting state first. Without that, clicking "APAC capacity" after "Commercial" would inherit Commercial's hidden columns, the same view giving a different grid depending on what preceded it, which is the one thing a named view must not do.

When the columns change underneath a saved view

A saved view is user data written months ago against a column set that has since moved on. A release adds columns, renames one, drops another; the views people saved must survive it.

What changedWhat a saved view does
A column was addedIt appears, in the state its definition declares, placed after every column the view names. A view is not a whitelist, it says nothing about columns it has never seen, and silence is not an instruction to hide.
A column was added, and should not appear yetDeclare it layout: { hidden: true }. The view does not mention it, so nothing overrides that, and it stays hidden until the user shows it.
A column was removedThe entries naming it are skipped and reported; the rest of the view applies. A sort or grouping on the missing column is dropped rather than left pointing at nothing.
A column was renamedThat is a removal and an addition. The old id is skipped, the new column appears at the end, and any width or pinning the user had set is lost with the old id.

Applying a view never throws and never refuses. It returns a report, { applied, skipped }: naming each thing it could not use and why. Refusing the whole view because one column has gone would lose a layout the user built deliberately, and throwing during a page load would lose the page. So a view degrades to as much of itself as still makes sense, and the host decides whether the user needs telling.

Telling the user their view has aged

const report = grid.state.apply(saved.state);
if (report.skipped.length) {
  // e.g. [{ key: 'columns.legacyRef', reason: 'unknown column' }]
  notify(`This view was saved against an older layout; ${report.skipped.length} setting(s) no longer apply.`);
}

The consequence worth planning for is the first one: a column added in a new release is visible to everyone, including users with a saved view. That is usually what you want (a new field nobody can see is a field nobody uses) but if a release adds several at once, every saved view gains them all at the right-hand end. Ship them hidden if that is not the introduction you want.

Persisting them

The grid makes no network calls. It tells you what happened and you decide what that means.

To a server

grid.on('view:saved',   e => api.post('/views', e.view));
grid.on('view:renamed', e => api.patch(`/views/${e.view.id}`, { name: e.view.name }));
grid.on('view:removed', e => api.delete(`/views/${e.view.id}`));
grid.on('view:default', e => api.patch(`/views/${e.view.id}`, { isDefault: true }));

Each event carries the one view that moved, so you send a single record rather than diffing two lists. Since the grid does not track whether your write landed, catch the failure and call grid.views.reload().

With no backend at all

createGrid(el, {
  views: { local: true, allowSave: true },
});

The other half of the same seam. views.storage above is where a developer plugs in their own backend, a real server, reached over the network. Not every grid has one to plug in, and a picker offering "Save" that quietly does nothing until a backend exists is worse than not offering it. views.local: true is the no-backend answer: saved views live in this browser's own localStorage, under a default key shared by every grid on the origin unless you pass one of your own, views: { local: { key: 'orders-grid-views' } }: to keep two grids' views apart. Given alongside an explicit storage, the explicit adapter always wins and local is silently (well, not silently: it warns once) ignored, so a page cannot end up writing to both without meaning to. The adapter itself is exported as createLocalViewStorage(opts), for anyone who wants it directly, a custom key without the shorthand, or a different Storage-shaped backing such as sessionStorage for views scoped to one tab rather than persisted across visits.