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 guide › Presenting 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 changed | What a saved view does |
|---|---|
| A column was added | It 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 yet | Declare 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 removed | The 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 renamed | That 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.