data router starter kit
A support board where nobody hunts for their work.
A support team wants one board with a lane per queue, so each person sees only their own tickets and the whole desk sees the backlog at a glance. This template drives the board from one ticket stream, so every lane and the trend beside it move off the same source.
Live above, running in your browser with no backend. The feed is the mock socket that ships with the grid; going live is the one line that builds it.
why it fits the desk
One feed, the whole screen.
Split the ticket stream by queue and each lane is a plain grid of just its tickets, live off the one source everyone is already on. A ticket that is resolved leaves its lane on its own, and a new one drops in without a refresh.
Every change lands on exactly the ticket it touches and flashes as it moves, so a lane stays readable while it fills and each keeps its own sort and filters through the stream.
It runs with no backend, so you can shape the board around your queues and point it at your real ticket feed when you are ready. Going live is a single line.
what the one feed fills
Everything on the screen, off one source.
Billing
Billing tickets, newest and longest-waiting in view.
Technical
Technical tickets in their own lane.
Onboarding
Onboarding tickets, kept apart from the rest.
Open tickets
A live chart of the whole desk backlog.
the whole thing, to paste
The code that runs it.
This is the entire template: two script tags for the modules, plain grids, one router in front, and the mock socket driving them. It is what the sandbox opens and what runs above. Paste it into an empty page and it works. On localhost the grid is free to use; a deployed site is licensed per domain.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/lattice-grid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/modules/charts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/modules/data-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/modules/mock-socket.min.js"></script>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #f6f7f9; color: #1b2430; }
.board { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.panel { background: #fff; border: 1px solid #e3e6ea; border-radius: 10px; overflow: hidden; min-width: 0; }
.panel h2 { font-size: 13px; margin: 0; padding: 8px 12px; border-bottom: 1px solid #eef0f3; color: #3a4250; font-weight: 600; }
.grid { height: 320px; }
#chart { height: 200px; margin-top: 12px; background: #fff; border: 1px solid #e3e6ea; border-radius: 10px; }
@media (max-width: 900px) { .board { grid-template-columns: 1fr; } }
</style>
<div class="board">
<div class="panel"><h2>Billing</h2><div id="billing" class="grid"></div></div>
<div class="panel"><h2>Technical</h2><div id="technical" class="grid"></div></div>
<div class="panel"><h2>Onboarding</h2><div id="onboarding" class="grid"></div></div>
</div>
<div id="chart"></div>
<script>
var g = LatticeGrid;
var rng = LatticeGridMockSocket.rng;
// One ticket stream, split into a lane per queue so nobody scrolls to find
// their work. Every record carries a 'queue', which the router splits on, and
// an 'id', its row key. To go live, swap the feed for a real socket.
function* deskFeed(seed) {
var rand = rng(seed || 5);
var QUEUES = ['billing', 'technical', 'onboarding'];
var PRIORITIES = ['low', 'normal', 'high', 'urgent'];
var AGENTS = ['Ada', 'Boris', 'Chen', 'Dara', 'Ege', 'Faye'];
var SUBJECTS = {
billing: ['Refund request', 'Invoice query', 'Card declined', 'Plan change', 'Duplicate charge'],
technical: ['Login fails', 'Export timeout', 'API 500', 'Slow dashboard', 'Sync stuck'],
onboarding: ['SSO setup', 'Import data', 'Invite team', 'Configure roles', 'First report'],
};
var pick = function (list) { return list[Math.floor(rand() * list.length)]; };
var seq = 0, t = 0;
var open = {};
QUEUES.forEach(function (q) { open[q] = []; });
var ticket = function (queue) {
var id = 'T-' + (1000 + seq++);
open[queue].push(id);
return { id: id, queue: queue, subject: pick(SUBJECTS[queue]), priority: pick(PRIORITIES), status: 'open', agent: pick(AGENTS), waitMins: Math.round(rand() * 90) };
};
var totalOpen = function () { return QUEUES.reduce(function (n, q) { return n + open[q].length; }, 0); };
var metric = function () { return { id: 'M-' + t, queue: 'metric', t: t, open: totalOpen() }; };
var rows = [];
QUEUES.forEach(function (q) { for (var i = 0; i < 8; i++) rows.push(ticket(q)); });
rows.push(metric());
yield { kind: 'snapshot', rows: rows };
for (;;) {
var changes = [];
var n = 1 + Math.floor(rand() * 3);
for (var i = 0; i < n; i++) changes.push({ op: 'upsert', row: ticket(pick(QUEUES)) });
// Now and then an agent resolves a ticket, which leaves its lane.
var q = pick(QUEUES);
if (rand() < 0.6 && open[q].length) {
var id = open[q].shift();
changes.push({ op: 'upsert', row: { id: id, queue: q, subject: 'Resolved', priority: 'normal', status: 'resolved', agent: pick(AGENTS), waitMins: 0 } });
}
t++;
changes.push({ op: 'upsert', row: metric() });
yield { kind: 'delta', changes: changes };
}
}
var cols = [
{ field: 'id', title: 'Ticket', layout: { width: 100 } },
{ field: 'subject', title: 'Subject', layout: { flex: 1, min: 130 } },
{ field: 'priority', title: 'Priority', filter: { type: 'set' } },
{ field: 'agent', title: 'Agent', filter: { type: 'set' } },
{ field: 'waitMins', title: 'Wait', type: 'number', total: 'max' },
];
var mk = function (elId) {
return g.createGrid(document.getElementById(elId), {
rowKey: 'id', theme: 'light', selection: 'single', highlightOnChange: { duration: 400 }, columns: cols,
});
};
var billing = mk('billing'), technical = mk('technical'), onboarding = mk('onboarding');
var metrics = g.createHeadlessGrid({
rowKey: 't',
columns: [{ field: 't', type: 'number' }, { field: 'open', type: 'number' }],
});
// One router, keyed on the queue. Each lane is a plain grid of just its tickets.
var router = LatticeGridDataRouter.createDataRouter({ key: 'queue', rowKey: 'id' });
router.attach(billing, 'billing');
router.attach(technical, 'technical');
router.attach(onboarding, 'onboarding');
router.attach(metrics, 'metric', { rowKey: 't' });
g.createChart({ grid: metrics, container: '#chart', type: 'area', x: 't', y: 'open', title: 'Open tickets across the desk' });
var socket = new LatticeGridMockSocket.MockWebSocket({ feed: deskFeed(5), rate: 1100, jitter: 300 });
socket.onmessage = function (event) {
var message = JSON.parse(event.data);
if (message.kind === 'snapshot') router.load(message.rows);
else router.apply(message.changes);
};
</script>
Open it in the sandbox and edit it live → How the Data Router works →
more starter kits