Skip to content

feat(table): support Hinode's wrap argument on data tables#277

Merged
markdumay merged 4 commits into
mainfrom
feat/table-wrap
Jul 14, 2026
Merged

feat(table): support Hinode's wrap argument on data tables#277
markdumay merged 4 commits into
mainfrom
feat/table-wrap

Conversation

@markdumay

Copy link
Copy Markdown
Contributor

Hinode's table wrap argument moves a wide table's last column onto a full-width row of its own below the site's main breakpoint. Until now it was incompatible with data tables, because Hinode rendered a pre-split, ragged table that simple-datatables' uniform row model cannot represent.

Hinode now renders a wrapped data table uniformly and marks it with data-table-wrap / data-table-wrap-breakpoint. This PR makes the module build the wrapped layout itself, in the browser, through simple-datatables' tableRender hook.

Why the hook

tableRender rewrites the virtual DOM on each render, whereas sorting, searching and paging operate on the library's internal row model. Transforming the virtual DOM is therefore purely cosmetic: the row model stays uniform, so the pager keeps counting records rather than rendered rows, sorting keeps each wrapped cell attached to its record, and search still matches text in the wrapped column.

Crossing the breakpoint redraws via dt.update(true), which preserves the active sort, page and search term — refresh() would clear the search.

Note on the second commit

The first draft removed the wrapped column's <th>. That desynchronized the rendered header count from the row model, and since fixedColumns defaults to true, _measureWidths() re-renders through tableRender and then walks the DOM headers in lockstep with data.headings, dereferencing activeDOMHeadings[N-1].offsetWidth on an undefined element. Every wrapped data table threw a TypeError — on load, on crossing the breakpoint, and on any window resize (via the library's own _onResize). The header cell is now kept and hidden with d-none, restoring the invariant and matching Hinode's plain wrap path.

Verification

Driven in a real browser against the Hinode exampleSite: zero console errors; the pager reports 2 pages for 4 records at perPage=2 (records, not rendered rows); sorting keeps descriptions attached to their records; searching a word present only in the wrapped column still matches; the no-results message renders cleanly; sort, page and search all survive a breakpoint crossing; and no class accumulates across repeated sorts, searches and resizes.

Requires Hinode with the corresponding wrap rework (gethinode/hinode).

🤖 Generated with Claude Code

markdumay and others added 3 commits July 14, 2026 10:40
Wrap the last column onto a row of its own below the main breakpoint when the
table declares data-table-wrap. The transform runs in simple-datatables'
tableRender hook, so it rewrites the virtual DOM and never the row model:
sorting, searching and paging keep operating on the unmodified data, and the
pager keeps counting records rather than rendered rows.

Crossing the breakpoint redraws through update(true), which preserves the
active sort, page and search term.
The wrap transform removed the last `<th>`, leaving the rendered header one cell shorter than
the row model. simple-datatables measures column widths whenever `fixedColumns` is truthy - it
defaults to true - by walking the rendered headers in lockstep with `data.headings`, so the
final iteration dereferenced an undefined element and threw:

  Cannot read properties of undefined (reading 'offsetWidth')

This hit every wrapped data table: on load below the breakpoint, on crossing the breakpoint,
and on any resize below it via the library's own debounced `_onResize`.

Hide the heading with Bootstrap's `d-none` instead of deleting it, so the header keeps all of
its cells and the DOM stays in step with the row model. This matches Hinode's plain-table wrap,
which also retains the heading and hides it. The transform only runs while the media query
matches, so an unconditional `d-none` is correct.

Also carry the source row's attributes onto the synthesized colspan row, so `data-index`
survives and the library's row-selection handler keeps reporting the right record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hinode assembles module JS with `resources.Concat`, not a bundler, so every module's `.load.js`
shares one top-level scope in one classic script. A duplicate top-level `const` is a parse-time
SyntaxError that takes down the entire site bundle, and `breakpoints` is a far more collision-prone
name than the file's existing `tableOptions` and `tableFilterInstances`.

Rename it to `dataTableBreakpoints`. No behaviour change.
Picks up the corrected wrap argument comment, which no longer claims that wrap
is incompatible with data tables.
@markdumay

Copy link
Copy Markdown
Contributor Author

Bumped mod-utils to the released v6.4.2 (fcb6529), which carries the corrected wrap argument comment from gethinode/mod-utils#344.

@markdumay markdumay merged commit 00786da into main Jul 14, 2026
8 checks passed
@markdumay markdumay deleted the feat/table-wrap branch July 14, 2026 10:56
@markdumay

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 4.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant