Sortable, editable, paginated, tree-capable grids connected to live dstore collections — the complete production grid toolkit
dgrid/editor — Inline Editingdgrid/selector — Row & Cell Selectiondgrid/tree — Hierarchical Griddstoredstore/Memory + Trackable collection.
| dgrid (modern) | dojox/grid/DataGrid (legacy) | |
|---|---|---|
| Architecture | Mixin-based — compose features you need | Monolithic — all features always loaded |
| Store API | dstore (cleaner API) | dojo/data (deprecated) |
| Performance | Virtual scrolling, on-demand rows | Renders all rows upfront |
| Inline editing | dgrid/editor mixin | Built in but inflexible |
| Custom cells | Full DOM/widget freedom | Limited formatter functions |
| Touch/mobile | Supported | Not supported |
| Actively maintained | Yes (1.17 era) | Legacy, no new features |
dojox/grid/DataGrid in legacy code. Know it well enough to read it, but write new grids with dgrid. The two grids use completely different APIs — they are not interchangeable.
<!-- dgrid CSS — REQUIRED for layout and styling -->
<link rel="stylesheet" href="dgrid/css/dgrid.css">
<link rel="stylesheet" href="dgrid/css/skins/claro.css"> <!-- match Dijit theme -->
// dojoConfig packages — dgrid and dstore side by side
var dojoConfig = {
packages: [
{ name: "dgrid", location: "dgrid" },
{ name: "dstore", location: "dstore" },
{ name: "put-selector", location: "put-selector" }, // dgrid dependency
{ name: "xstyle", location: "xstyle" } // dgrid dependency
]
};
| Class | When to Use | Key Feature |
|---|---|---|
dgrid/Grid | Static data, small datasets (<500 rows) | All rows rendered at once |
dgrid/OnDemandGrid | Large datasets, REST-backed stores | Virtual scrolling — renders only visible rows |
dgrid/List | Single-column lists | No column headers |
dgrid/OnDemandList | Large lists (e.g., feeds) | Virtual scrolling + no columns |
require([
"dgrid/OnDemandGrid",
"dstore/Memory",
"dstore/Trackable"
], function(OnDemandGrid, Memory, Trackable) {
// dstore/Trackable makes the store notify dgrid of changes
var TrackableMemory = Memory.createSubclass([Trackable]);
var store = new TrackableMemory({
data: employees,
idProperty: "id"
});
// Minimal grid — just columns + store
var grid = new OnDemandGrid({
collection: store, // dstore collection (not dojo/store!)
columns: {
name: "Name",
dept: "Department",
salary: "Salary"
}
}, "gridNode"); // DOM node to replace
grid.startup();
});
dstore collections, not dojo/store objects. They have a similar API but are different packages. dstore/Memory vs dojo/store/Memory — the grid won't work with the wrong one. Use dstore/RequestMemory for REST-backed data.
Columns can be defined as a simple object (shorthand) or an array of full column definition objects. The array form gives full control over ordering, widths, CSS, and behaviour.
require(["dgrid/OnDemandGrid"], function(OnDemandGrid) {
// ── Shorthand: object form ─────────────────────────────────────
// key = field name in store, value = column header label
var grid = new OnDemandGrid({
collection: store,
columns: {
id: "#",
name: "Full Name",
dept: "Department",
salary: "Annual Salary"
}
}, "gridNode");
// ── Full control: array form ───────────────────────────────────
var grid2 = new OnDemandGrid({
collection: store,
columns: [
{
field: "id",
label: "#",
sortable: false,
style: "width: 50px; text-align: center;",
className:"col-id"
},
{
field: "name",
label: "Full Name",
sortable: true, // clickable header sorts by this field
style: "width: 200px;"
},
{
field: "dept",
label: "Department",
sortable: true,
style: "width: 150px;"
},
{
field: "salary",
label: "Annual Salary",
sortable: true,
style: "width: 120px; text-align: right;",
// formatter: transform value to display string
formatter: function(value) {
return "$" + value.toLocaleString();
}
},
{
field: "hireDate",
label: "Hire Date",
formatter: function(value) {
return value ? new Date(value).toLocaleDateString("en-US") : "—";
}
}
]
}, "gridNode2");
grid2.startup();
// ── Column sets — group columns (for frozen left columns) ──────
// Use columnSets instead of columns for freezing:
var grid3 = new OnDemandGrid({
collection: store,
columnSets: [
// First set: frozen (stays visible when scrolling horizontally)
[[
{ field: "id", label: "#", style: "width:50px" },
{ field: "name", label: "Name",style: "width:200px" }
]],
// Second set: scrollable
[[
{ field: "dept", label: "Department" },
{ field: "salary", label: "Salary" },
{ field: "email", label: "Email" },
{ field: "phone", label: "Phone" }
]]
]
}, "gridNode3");
});
The renderCell function gives you complete DOM control over how a cell's content looks. It receives the row object and must return a DOM node.
require([
"dgrid/OnDemandGrid",
"dojo/dom-construct",
"dojo/dom-class",
"dojo/on"
], function(OnDemandGrid, domConstruct, domClass, on) {
var grid = new OnDemandGrid({
collection: store,
columns: [
// ── Badge renderer — colored dept label ────────────────────
{
field: "dept",
label: "Department",
renderCell: function(object, value, node) {
var colors = {
"Engineering": "cyan",
"Marketing": "purple",
"HR": "pink",
"Finance": "green"
};
var span = domConstruct.create("span", {
textContent: value,
className: "dept-badge dept-" + (colors[value] || "default")
});
return span;
}
},
// ── Status indicator ────────────────────────────────────────
{
field: "active",
label: "Status",
renderCell: function(object, value, node) {
var dot = domConstruct.create("span", {
textContent: value ? "�? Active" : "�? Inactive",
style: "color:" + (value ? "#34d399" : "#fbbf24")
});
return dot;
}
},
// ── Progress bar renderer ───────────────────────────────────
{
field: "performance", // 0–100
label: "Performance",
renderCell: function(object, value) {
var wrap = domConstruct.create("div", { style: "background:#111827;border-radius:4px;overflow:hidden" });
var fill = domConstruct.create("div", {
style: "width:" + (value || 0) + "%;height:8px;background:linear-gradient(90deg,#6366f1,#22d3ee);border-radius:4px;"
}, wrap);
return wrap;
}
},
// ── Action buttons renderer ─────────────────────────────────
{
label: "Actions",
sortable: false,
renderCell: function(object, value, node, options) {
var wrap = domConstruct.create("div");
var editBtn = domConstruct.create("button", {
textContent: "Edit",
className: "grid-action-btn",
"data-id": object.id
}, wrap);
var delBtn = domConstruct.create("button", {
textContent: "Delete",
className: "grid-action-btn grid-action-btn--danger",
"data-id": object.id
}, wrap);
// Wire events on the nodes
on(editBtn, "click", function(e) {
e.stopPropagation();
topic.publish("employee/editRequested", { id: +this.dataset.id });
});
on(delBtn, "click", function(e) {
e.stopPropagation();
topic.publish("employee/deleteRequested", { id: +this.dataset.id });
});
return wrap;
}
},
// ── Avatar initials renderer ────────────────────────────────
{
field: "name",
label: "Employee",
renderCell: function(object) {
var wrap = domConstruct.create("div", { style: "display:flex;align-items:center;gap:8px" });
var initials = object.name.split(" ").map(function(w){ return w[0]; }).join("");
domConstruct.create("span", {
textContent: initials,
style: "width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#22d3ee);" +
"color:#fff;font-weight:800;font-size:.78rem;display:flex;align-items:center;justify-content:center;"
}, wrap);
domConstruct.create("span", { textContent: object.name }, wrap);
return wrap;
}
}
]
}, "gridNode");
grid.startup();
});
dgrid/editor — Inline EditingThe dgrid/editor mixin wraps column definitions to make cells editable. It supports native HTML inputs ("text", "number", "checkbox", "select") and Dijit widgets.
require([
"dgrid/OnDemandGrid",
"dgrid/editor",
"dijit/form/ValidationTextBox",
"dijit/form/Select",
"dijit/form/NumberSpinner",
"dstore/Memory",
"dstore/Trackable"
], function(OnDemandGrid, editor, ValidationTextBox, Select, NumberSpinner,
Memory, Trackable) {
var TrackableMemory = Memory.createSubclass([Trackable]);
var store = new TrackableMemory({ data: employees });
var grid = new OnDemandGrid({
collection: store,
columns: [
// ── Text input — inline editable ─────────────────────────
editor({
field: "name",
label: "Full Name",
autoSave: true, // save on every change (not just blur)
editor: "text" // native
}),
// ── Select dropdown editor ────────────────────────────────
editor({
field: "dept",
label: "Department",
editor: Select, // Dijit widget as editor
editorArgs: {
options: [
{ label: "Engineering", value: "Engineering" },
{ label: "Marketing", value: "Marketing" },
{ label: "HR", value: "HR" },
{ label: "Finance", value: "Finance" }
]
},
autoSave: true,
editOn: "click" // activate editor on click (default: dblclick for non-autoSave)
}),
// ── Number editor with constraints ────────────────────────
editor({
field: "salary",
label: "Salary",
editor: NumberSpinner,
editorArgs: {
constraints: { min: 0, max: 500000, places: 0 }
},
autoSave: false, // save only on blur/Enter
editOn: "click"
}),
// ── Checkbox editor ───────────────────────────────────────
editor({
field: "active",
label: "Active",
editor: "checkbox", // native
autoSave: true // saves immediately on toggle
}),
// ── Validation text box editor ────────────────────────────
editor({
field: "email",
label: "Email",
editor: ValidationTextBox,
editorArgs: {
regExp: "\\S+@\\S+\\.\\S+",
invalidMessage: "Invalid email"
},
autoSave: false,
editOn: "dblclick" // activate only on double-click
})
]
}, "gridNode");
grid.startup();
// Listen to cell value changes
grid.on("dgrid-datachange", function(e) {
// e.cell — the cell object
// e.oldValue — previous value
// e.value — new value
// e.cell.row.data — full row data object
console.log("Changed:", e.cell.column.field, e.oldValue, "→", e.value);
// Persist to server
var updated = lang.mixin({}, e.cell.row.data, {
[e.cell.column.field]: e.value
});
api.update("employees", updated.id, updated)
.otherwise(function(err) {
// Revert on server error
e.cell.row.data[e.cell.column.field] = e.oldValue;
grid.refresh();
});
});
});
dgrid/selector — Row & Cell Selectionrequire([
"dgrid/OnDemandGrid",
"dgrid/selector",
"dgrid/Selection"
], function(OnDemandGrid, selector, Selection) {
// Mix in Selection for selection state management
var SelectableGrid = declare([OnDemandGrid, Selection]);
var grid = new SelectableGrid({
collection: store,
selectionMode: "multiple", // "none","single","multiple","extended"
allowSelectAll: true, // header checkbox to select all visible rows
columns: [
// Add checkbox column as first column
selector({ label: "" }), // renders a checkbox; no field needed
{ field: "name", label: "Name" },
{ field: "dept", label: "Department" },
{ field: "salary", label: "Salary" }
]
}, "gridNode");
grid.startup();
// ── Reading selection ──────────────────────────────────────────
// grid.selection is an object: { rowId: true, rowId2: true, ... }
function getSelectedIds() {
return Object.keys(grid.selection).filter(function(id) {
return grid.selection[id];
});
}
function getSelectedObjects() {
return getSelectedIds().map(function(id) {
return store.getSync(id);
});
}
// ── Bulk action button ─────────────────────────────────────────
on(dom.byId("bulkDeleteBtn"), "click", function() {
var ids = getSelectedIds();
if (!ids.length) { alert("Select at least one row"); return; }
if (!confirm("Delete " + ids.length + " employee(s)?")) return;
// Delete all selected from store — Trackable notifies grid
ids.forEach(function(id) { store.remove(id); });
grid.clearSelection();
});
// ── Selection events ───────────────────────────────────────────
grid.on("dgrid-select", function(e) {
// e.rows = array of selected row objects
console.log("Selected rows:", e.rows.map(function(r){ return r.data.name; }));
updateBulkActionsBar(getSelectedIds().length);
});
grid.on("dgrid-deselect", function(e) {
console.log("Deselected:", e.rows.length, "rows");
updateBulkActionsBar(getSelectedIds().length);
});
// ── Programmatic selection ─────────────────────────────────────
grid.select(rowId); // select by store id
grid.deselect(rowId);
grid.selectAll();
grid.clearSelection();
});
dgrid/tree — Hierarchical Gridrequire([
"dgrid/OnDemandGrid",
"dgrid/tree",
"dstore/Memory",
"dstore/Trackable"
], function(OnDemandGrid, tree, Memory, Trackable) {
// Hierarchical data — children array or parentId reference
var deptData = [
{ id: "eng", name: "Engineering", type: "dept", hasChildren: true },
{ id: "mkt", name: "Marketing", type: "dept", hasChildren: true },
{ id: "e1", name: "Alice Chen", type: "emp", parent: "eng", salary: 95000 },
{ id: "e2", name: "Carol Davis", type: "emp", parent: "eng", salary: 105000 },
{ id: "e3", name: "Bob Smith", type: "emp", parent: "mkt", salary: 72000 }
];
var TrackableMemory = Memory.createSubclass([Trackable]);
var store = new TrackableMemory({
data: deptData,
idProperty: "id"
});
// getChildren — how to find children of a row
store.getChildren = function(parent) {
return this.filter({ parent: parent.id });
};
// mayHaveChildren — whether to show expand arrow
store.mayHaveChildren = function(item) {
return item.hasChildren;
};
var grid = new OnDemandGrid({
collection: store,
columns: [
// tree() wraps the first column to add expand/collapse
tree({
field: "name",
label: "Name / Department",
style: "width: 220px;"
}),
{
field: "salary",
label: "Salary",
formatter: function(v) { return v ? "$" + v.toLocaleString() : "—"; }
}
]
}, "treeGridNode");
grid.startup();
// Expand/collapse programmatically
grid.expand(store.get("eng"), true); // true = expand, false = collapse
grid.expand(store.get("mkt"), true);
});
require(["dgrid/OnDemandGrid"], function(OnDemandGrid) {
var grid = new OnDemandGrid({
collection: store,
sort: [{ property: "name", descending: false }], // initial sort
columns: [
{ field: "name", label: "Name", sortable: true },
{ field: "dept", label: "Dept", sortable: true },
{ field: "salary", label: "Salary", sortable: true },
{ field: "active", label: "Active", sortable: false } // unsortable column
]
}, "gridNode");
grid.startup();
// Programmatic sort
grid.set("sort", [{ property: "salary", descending: true }]);
// Multi-column sort (shift+click in UI also does this)
grid.set("sort", [
{ property: "dept", descending: false },
{ property: "salary", descending: true }
]);
// ── Custom comparator ──────────────────────────────────────────
// For client-side Memory stores, you can supply a custom sort function
var TrackableMemory = Memory.createSubclass([Trackable]);
var store2 = new TrackableMemory({ data: employees });
// Sort "salary" by number but display locale-formatted
store2.sort = function(data, queryOptions) {
if (queryOptions && queryOptions.sort) {
var s = queryOptions.sort[0];
data.sort(function(a, b) {
if (s.property === "salary") {
return s.descending ? b.salary - a.salary : a.salary - b.salary;
}
var va = a[s.property] || "";
var vb = b[s.property] || "";
return s.descending
? vb.localeCompare(va)
: va.localeCompare(vb);
});
}
return data;
};
// Listen to sort changes
grid.on("dgrid-sort", function(e) {
console.log("Sorted by:", e.sort);
});
});
Pagination (dgrid/extensions/Pagination) | On-Demand Scrolling (OnDemandGrid) | |
|---|---|---|
| UX | Classic page 1/2/3 controls | Infinite scroll — load more as you scroll |
| Best for | Business reports, audit tables | Feeds, large browsable datasets |
| Memory use | Low — only current page in DOM | Very low — only visible rows in DOM |
| REST | Range headers or skip/limit params | Range headers or skip/limit params |
// ── Pagination extension ───────────────────────────────────────
require([
"dgrid/Grid",
"dgrid/extensions/Pagination"
], function(Grid, Pagination) {
var PagingGrid = declare([Grid, Pagination]);
var grid = new PagingGrid({
collection: store,
rowsPerPage: 25, // rows per page
pagingLinks: 5, // number of page links to show (1 2 3 ... 5)
pagingTextBox: true, // show "Go to page" input box
firstLastArrows: true, // first/last page arrows
previousNextArrows: true,
columns: [
{ field: "name", label: "Name" },
{ field: "dept", label: "Department" },
{ field: "salary", label: "Salary" }
]
}, "gridNode");
grid.startup();
// Navigate programmatically
grid.gotoPage(3);
console.log("Current page:", grid.get("currentPage"));
// Change rows per page
grid.set("rowsPerPage", 50);
});
// ── OnDemandGrid — infinite scroll ────────────────────────────
require(["dgrid/OnDemandGrid"], function(OnDemandGrid) {
var grid = new OnDemandGrid({
collection: store,
minRowsPerPage: 25, // load at least 25 rows each time
maxRowsPerPage: 100, // load at most 100 rows
farOffRemoval: 500, // remove rows more than 500px off screen (memory mgmt)
columns: [
{ field: "name", label: "Name" },
{ field: "salary", label: "Salary" }
]
}, "gridNode");
grid.startup();
});
dstorerequire([
"dstore/Memory",
"dstore/Trackable",
"dstore/Rest",
"dstore/Cache",
"dstore/RequestMemory"
], function(Memory, Trackable, Rest, Cache, RequestMemory) {
// ── Memory + Trackable (in-memory, live updates) ───────────────
var TrackableMemory = Memory.createSubclass([Trackable]);
var localStore = new TrackableMemory({
data: employees,
idProperty: "id"
});
// Mutations on localStore (put, add, remove) automatically update grid
// ── REST store (server-backed) ─────────────────────────────────
var restStore = new Rest({
target: "/api/employees/",
idProperty: "id",
// sortParam: "sort", // query param name for sort
// rangeStartParam: "start",
// rangeCountParam: "count"
});
// dgrid requests data with Range headers: Range: items=0-24
// ── Cached REST store (fetch once, then serve from memory) ─────
var cachedStore = new Cache({
masterStore: restStore,
cacheStore: new Memory({ idProperty: "id" })
});
// ── RequestMemory (load all, then work in-memory) ──────────────
// Fetches entire collection from server once, then filters/sorts locally
var reqMemStore = new RequestMemory({
target: "/api/employees/"
});
// Good for small-medium datasets where you want client-side filtering
// ── Assigning store to grid ────────────────────────────────────
var grid = new OnDemandGrid({
collection: localStore,
columns: [....]
}, "gridNode");
grid.startup();
// Swap collection at runtime (e.g., filter applied)
var filteredStore = localStore.filter({ dept: "Engineering" });
grid.set("collection", filteredStore);
});
require([
"dstore/Memory",
"dstore/Trackable",
"dgrid/OnDemandGrid",
"dojo/dom",
"dojo/on"
], function(Memory, Trackable, OnDemandGrid, dom, on) {
var TrackableMemory = Memory.createSubclass([Trackable]);
var masterStore = new TrackableMemory({ data: employees });
var grid = new OnDemandGrid({
collection: masterStore,
columns: [...]
}, "gridNode");
grid.startup();
// ── Filter by department ────────────────────────────────────────
on(dom.byId("deptFilter"), "change", function() {
var val = this.value;
var filtered = val
? masterStore.filter({ dept: val })
: masterStore; // no filter = show all
grid.set("collection", filtered);
});
// ── Text search filter ──────────────────────────────────────────
on(dom.byId("searchInput"), "input", function() {
var text = this.value.toLowerCase().trim();
var filtered = text
? masterStore.filter(function(emp) {
return emp.name.toLowerCase().indexOf(text) > -1 ||
emp.dept.toLowerCase().indexOf(text) > -1;
})
: masterStore;
grid.set("collection", filtered);
});
// ── Combine filters ─────────────────────────────────────────────
function applyFilters(textSearch, deptFilter, activeOnly) {
var col = masterStore;
if (deptFilter) col = col.filter({ dept: deptFilter });
if (activeOnly) col = col.filter({ active: true });
if (textSearch) col = col.filter(function(emp) {
return emp.name.toLowerCase().indexOf(textSearch.toLowerCase()) > -1;
});
grid.set("collection", col);
}
// ── Manual refresh ──────────────────────────────────────────────
grid.refresh(); // re-request all data from store
grid.refresh({ keepScrollPosition: true }); // refresh without scrolling to top
// ── Refresh a single row ────────────────────────────────────────
grid.refreshRow(rowId); // re-render just one row (after an external update)
// ── Tracking mutations (Trackable auto-updates grid) ───────────
masterStore.put({ id: 1, name: "Alice Chen Updated", dept: "Engineering", salary: 98000 });
// Grid row for id=1 updates automatically — no refresh needed
masterStore.add({ id: 10, name: "New Person", dept: "HR", salary: 55000 });
// New row appears in grid automatically
masterStore.remove(2);
// Row disappears from grid automatically
});
// EmployeeGrid.js — complete grid module
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dijit/_Widget",
"dgrid/OnDemandGrid",
"dgrid/editor",
"dgrid/selector",
"dgrid/Selection",
"dstore/Memory",
"dstore/Trackable",
"dojo/dom",
"dojo/dom-construct",
"dojo/on",
"dojo/topic"
], function(
declare, lang, _Widget,
OnDemandGrid, editor, selector, Selection,
Memory, Trackable,
dom, domConstruct, on, topic
) {
var TrackableMemory = Memory.createSubclass([Trackable]);
// Compose the grid class with the mixins we need
var EditableSelectableGrid = declare([OnDemandGrid, Selection]);
return declare([_Widget], {
constructor: function() {
this._store = new TrackableMemory({
data: [],
idProperty: "id"
});
},
buildRendering: function() {
this.domNode = domConstruct.create("div");
this._toolbar = domConstruct.create("div", { className: "grid-toolbar" }, this.domNode);
this._gridNode = domConstruct.create("div", { style: "height:500px;" }, this.domNode);
},
postCreate: function() {
this.inherited(arguments);
// ── Toolbar ─────────────────────────────────────────────
var addBtn = domConstruct.create("button", {
textContent: "+ Add Employee", className: "btn-primary"
}, this._toolbar);
var deleteBtn = domConstruct.create("button", {
textContent: "Delete Selected", className: "btn-danger", disabled: true,
id: "bulkDeleteBtn"
}, this._toolbar);
var searchInput = domConstruct.create("input", {
type: "text", placeholder: "Search...", className: "search-input"
}, this._toolbar);
// ── Grid ─────────────────────────────────────────────────
this._grid = new EditableSelectableGrid({
collection: this._store,
selectionMode: "multiple",
sort: [{ property: "name", descending: false }],
columns: [
selector({ label: "" }),
{
field: "name",
label: "Employee",
renderCell: function(obj) {
var wrap = domConstruct.create("div", { style: "display:flex;align-items:center;gap:8px;" });
var initials = obj.name.split(" ").map(function(w){ return w[0]; }).join("");
domConstruct.create("span", {
textContent: initials,
style: "width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#22d3ee);" +
"color:#fff;font-weight:800;font-size:.75rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;"
}, wrap);
domConstruct.create("span", { textContent: obj.name }, wrap);
return wrap;
}
},
editor({
field: "dept", label: "Department",
editor: "select",
editorArgs: { options: [
{ value: "Engineering", label: "Engineering" },
{ value: "Marketing", label: "Marketing" },
{ value: "HR", label: "HR" }
]},
autoSave: true, editOn: "click"
}),
editor({
field: "salary", label: "Salary",
editor: "number",
formatter: function(v) { return "$" + (+v || 0).toLocaleString(); },
autoSave: false, editOn: "dblclick"
}),
{
field: "active", label: "Status",
renderCell: function(obj) {
var s = obj.active;
return domConstruct.create("span", {
textContent: s ? "�? Active" : "�? Inactive",
style: "color:" + (s ? "#34d399" : "#fbbf24")
});
}
},
{
label: "Actions", sortable: false,
renderCell: function(obj) {
var wrap = domConstruct.create("span");
var edit = domConstruct.create("button",
{ textContent: "Edit", "data-id": obj.id, className: "grid-btn" }, wrap);
var del = domConstruct.create("button",
{ textContent: "Del", "data-id": obj.id, className: "grid-btn grid-btn--red" }, wrap);
on(edit, "click", function(e) {
e.stopPropagation();
topic.publish("employee/editRequested", { id: +this.dataset.id });
});
on(del, "click", function(e) {
e.stopPropagation();
if (confirm("Delete?")) {
store.remove(+this.dataset.id);
}
}.bind(del));
return wrap;
}
}
]
}, this._gridNode);
// ── Wire toolbar events ───────────────────────────────────
on(addBtn, "click", function() {
topic.publish("employee/addRequested");
});
on(deleteBtn, "click", lang.hitch(this, function() {
var ids = Object.keys(this._grid.selection)
.filter(function(k){ return this._grid.selection[k]; }, this);
if (!ids.length || !confirm("Delete " + ids.length + " record(s)?")) return;
ids.forEach(lang.hitch(this, function(id) { this._store.remove(id); }));
this._grid.clearSelection();
}));
on(searchInput, "input", lang.hitch(this, function(e) {
var text = e.target.value.trim().toLowerCase();
this._grid.set("collection", text
? this._store.filter(function(emp) {
return emp.name.toLowerCase().indexOf(text) > -1;
})
: this._store
);
}));
// ── Enable/disable bulk delete based on selection ─────────
this._grid.on("dgrid-select dgrid-deselect", lang.hitch(this, function() {
var count = Object.values(this._grid.selection).filter(Boolean).length;
deleteBtn.disabled = count === 0;
deleteBtn.textContent = count
? "Delete Selected (" + count + ")"
: "Delete Selected";
}));
// ── Subscribe to store changes from Dialog ─────────────────
this.own(
topic.subscribe("employee/saved", lang.hitch(this, function(d) {
this._store.put(d.employee);
})),
topic.subscribe("employee/added", lang.hitch(this, function(d) {
this._store.add(d.employee);
}))
);
},
startup: function() {
if (this._started) return;
this.inherited(arguments);
this._grid.startup();
},
loadData: function(employees) {
var self = this;
employees.forEach(function(e) { self._store.add(e); });
},
destroy: function(preserveDom) {
if (this._grid) this._grid.destroyRecursive();
this.inherited(arguments);
}
});
});
| Concept | Key Rule |
|---|---|
| Grid class | Use OnDemandGrid for large/REST data; Grid for small static sets |
| Store | dgrid needs dstore not dojo/store — always use dstore/Memory |
Trackable | Mixin on Memory store so grid auto-updates on put/add/remove |
| Column shorthand | { field: label } — OK for simple grids |
renderCell(obj, val, node) | Returns a DOM node — full freedom over cell content |
editor(colDef) | Wraps column def — supports native inputs and Dijit widgets |
autoSave: true | Saves on every change; false = saves on blur/Enter |
dgrid-datachange | Event fired when inline edit value changes |
selector() | Checkbox column — needs Selection mixin |
grid.selection | Object of { id: true } pairs — iterate to get selected ids |
grid.set("collection", ...) | Swap data source at runtime — use for filtering |
grid.refresh() | Re-fetch all rows; grid.refreshRow(id) for one row |
Time: 9–11 hrs
Pages: 25–30
Demo apps: 1
Grid features: 10+