Lattice Grid Buy a licence

blog

We Migrated 166 Grids off AG Grid. Here's What Broke.

We moved DemandFlow, TOCLOCO’s own B2B platform, off AG Grid and onto Lattice Grid, the grid we also make. That makes us an interested party, so weigh the good results accordingly. It also means every defect below was found by us, in our own product, before a customer could hit it.

Every grid fix it produced shipped in Lattice Grid 1.56 and 1.57; the current release is 1.85.

The result

All 166 grids moved in about a week, and three libraries left the codebase. That week covered building the conversion layer, converting each grid and re-testing each one afterwards.

  • AG Grid is gone from all 166 grids.
  • FullCalendar, which ran the scheduling screens, is replaced by Lattice Grid’s calendar module.
  • DHTMLX Kanban, which ran the boards, is replaced by Lattice Grid’s kanban module, reading the same data as the grids.
  • State handling for sort, filter, grouping and column layout went from about 700 lines to about 190, built on Lattice Grid’s state object and state.apply().

Why a week was enough

Every grid in DemandFlow was already created through one house function, df_lattice_create_grid. It took the AG Grid options a screen already had and translated them into a Lattice Grid configuration. That turned 166 separate integrations into one conversion function and 166 call sites.

Around it sat a small set of shared helpers: one that builds context menus, generators that turn a field list into column definitions, and a few more for jobs every screen repeats. Converting a helper converted every screen that used it. Most per-grid work came down to checking the result, not rewriting the screen.

It also let us get basic grids running quickly, then move each helper across as its own small piece of work instead of one large cut-over. The scheduling screens show how far that goes: moving them off FullCalendar took about an hour.

If you are planning a move like this, copy that part first. Put every grid behind your own helpers while you are still on the old library, and the migration becomes a change to the helpers.

What broke in our grid

Converting 166 real grids put configurations in front of Lattice Grid that our own tests never had reason to write: options carried over verbatim from AG Grid, values typed from memory, and edge cases that only appear with real users. Most of what surfaced followed one pattern, and it is the lesson worth keeping: rejected input should never become state. A grid that refuses a bad value but quietly keeps it is worse than one that never checked, because the host believes it is protected.

Four examples show the pattern:

  • A sort on a column that did not exist was rejected for sorting but still written into saved state, so the rejection repeated on every reload. It is now dropped before it is stored.
  • An unknown filter operator was accepted and matched every row, while the filter panel showed as active. It is now refused, and the same check covers relative-date tokens.
  • An unrecognised column key or column type was silently ignored. The grid now warns once, naming the key, the column and, for types, what it fell back to.
  • The state:changed event fired for only some state changes, so auto-save built on it missed a user’s own sorts and column moves. It now fires once per change, whether from a gesture or an API call.

The rejected-input cases now print a warning naming what was rejected and why, listed in the warnings reference. The full list of fixes, including smaller ones, is in the changelog for 1.56 and 1.57.

Those warnings turned out to be the part of the migration that paid off fastest: “the warning messages are the best I have seen in a grid library.”

The migration also drove three additions: checkboxOnly, so a click outside the checkbox column no longer selects the row; defaults(), for house-wide settings applied once instead of on 166 call sites; and documentation of which features run headlessly with no DOM.

What broke in our own code

A conversion is also an audit of the code being converted. One screen’s AG Grid options defined getRowStyle twice. A JavaScript object literal keeps only the last definition of a repeated key, so the row shading tied to a ranking field never ran in production. Nobody noticed, because a grid without shading does not look broken; it looks plain.

The lesson is to read every key before you carry it across. A mechanical migration through a shared seam is fast precisely because it trusts the old configuration, and that same trust copies old mistakes forward. Treat the source configuration as something to check, not as ground truth.

If you are considering the same move

This is one application’s experience, not a benchmark; we will not publish one without a runnable public harness behind it. Three places to start:

  • The AG Grid migration guide maps AG Grid options, events and API calls to their Lattice Grid equivalents, with one real configuration converted line by line.
  • The AG Grid alternative page covers licensing, feature coverage and where each grid is ahead.
  • The warnings reference lists the warning you will see for each problem described above, and what it means.

If anything else turns up from this migration, we will add it here.

Read next

All posts RSS