A Hugo module to enhance tables powered by simple-datatables to your Hinode site
Hinode is a clean blog theme for Hugo, an open-source static site generator. Hinode is available as a template, and a main theme. This repository maintains a Hugo module to add simple-datatables to a Hinode site. Visit the Hinode documentation site for installation instructions.
This module uses semantic-release to automate the release of new versions. The package uses husky and commitlint to ensure commit messages adhere to the Conventional Commits specification. You can run npx git-cz from the terminal to help prepare the commit message.
Simple datatables is compatible with Bootstrap tables. It uses Hugo's i18n folder for the translation tables of multilingual sites. Add the attribute data-table to the class of any table. The following arguments are supported:
| Argument | Default | Description |
|---|---|---|
| data-table-sortable | true |
Toggle the ability to sort the columns. |
| data-table-paging | true |
Whether paging is enabled for the table. |
| data-table-paging-option-perPage | 10 |
Paging option: Sets the maximum number of rows to display on each page. Type: int |
| data-table-paging-option-perPageSelect | [5, 10, 20, 50, ["{{ T "tablePerPageSelectAll" }}", -1]] |
Paging option: Sets the per page options in the dropdown. i18 translation id for all: tablePerPageSelectAll |
| data-table-searchable | true |
Toggle the ability to search the dataset. |
Set data-table-controls-layout="list" for full-width search above the table and a
bottom toolbar containing page size, result count, and navigation. The toolbar stacks
on small screens. Omitting the attribute retains the compact arrangement.
Override data-table-search-label and data-table-search-placeholder to customize
individual tables. The label remains available to assistive technology after the
placeholder disappears. Values are treated as plain text, and omitted values use
the active language's translations. These settings also apply to manually initialized
tables using window.hinodeDatatables.options(table).
Place a table in its own Bootstrap .table-responsive or .table-responsive-{sm|md|lg|xl|xxl}
wrapper. When the table is initialized, the module moves responsive classes from its dedicated
ancestor wrappers onto the generated table container. This also handles nested responsive wrappers,
including plain wrappers between them. The selector, search input, and pagination stay outside the
scrolling area, so their focus rings remain visible. Breakpoint settings and unrelated wrapper classes are preserved, and
destroying the DataTable restores each responsive class to its original wrapper.
Wrappers containing other elements keep their responsive classes, so those elements retain their scrolling behavior. Tables without a responsive wrapper are unchanged.
A page with a category filter button group applies a filter from the URL: ?filter=<value>
selects the button with that data-filter-value, ignoring case, when the page loads. An
unknown value leaves the table unfiltered. If the page has several button groups, the first
one with a matching value is selected.
Default initialization leaves column sizing to CSS table layout (fixedColumns: false)
and the list template emits no inline style attributes. Filtering without a DataTable
instance uses the native hidden attribute. Style and constrain table containers through
external stylesheets.
Manually enabling the library's fixedColumns, fixedHeight, or scrollY features can
produce inline styles and is outside these strict-CSP defaults. The list layout uses its
natural content height rather than rendering the library's inline scrollY container style.
The list-control browser tests serve their scripts and styles as external assets under
script-src 'self'; style-src 'self'; style-src-attr 'none'. They assert both zero CSP
violations and zero generated inline style attributes after initialization and interaction.
The module initializes every .data-table present when its script runs. A script that adds tables afterwards, for example tables swapped in by htmx, can build them with the same labels, classes and rendering through window.hinodeDatatables:
| Member | Description |
|---|---|
options(table) |
Returns the complete simple-datatables options for table, derived from its data-table-* attributes: the site's locale, the localized labels (the search label is visually hidden, the input's placeholder names it), the Bootstrap classes, sortable, paging, searchable, perPage, perPageSelect, and the tableRender hook that styles the header and wraps columns. Each call returns fresh objects, so you can add or override options such as columns before passing them on. |
attach(table, dataTable) |
Wires a constructed DataTable into the behavior that lives outside its options: a redraw when a wrapped table crosses its breakpoint, responsive scrolling, and the category filter button group. It is idempotent: calling it again with the same DataTable changes nothing. The module binds the filter buttons once, when its script runs. A table is therefore filtered only by a button group (data-filter-table) that was on the page at that point. A group swapped in later is not wired. |
The module's own page-load pass uses the same two functions, so a table built through them is identical to one the module built itself.
To keep the page-load pass away from a table that your script builds itself, for example because it adds columns or event handlers of its own, mark the table with data-table-init="manual". The pass skips any .data-table with that attribute and leaves it for your script to build through options and attach. Without the attribute, a table already in the page when the module script runs is built by the pass, even if your script meant to build it. Once built, a table cannot be rebuilt with different options.
The module script loads async, so it may run before or after your script. When window.hinodeDatatables is not set yet, wait for the hinode:datatables-ready event on document. Its detail holds the same API. The event fires after the page-load pass, so by then every table the pass built carries the datatable-table class. The pass builds each table on its own: a table it cannot build, for example because of a malformed attribute, is logged to the console and left as it is, and the event still fires.
The event fires only on a page that loads the module's script, which Hinode adds to pages that use the module. If your script also runs on other pages, or the script fails to load, the event never arrives. Keep your wait bounded, as in this example, and decide what a table should look like when the module is absent:
function withDatatables (callback, timeout = 10000) {
if (window.hinodeDatatables) {
callback(window.hinodeDatatables)
return
}
const onReady = event => {
clearTimeout(timer)
callback(event.detail)
}
const timer = setTimeout(() => {
document.removeEventListener('hinode:datatables-ready', onReady)
console.warn('simple-datatables did not load; tables stay unenhanced')
}, timeout)
document.addEventListener('hinode:datatables-ready', onReady, { once: true })
}
withDatatables(api => {
document.querySelectorAll('.data-table:not(.datatable-table)').forEach(table => {
const dataTable = new window.simpleDatatables.DataTable(table, api.options(table))
api.attach(table, dataTable)
})
})Run pnpm test:browser to build the example site and check responsive focus rings, scrolling,
breakpoints, and the attach/destroy lifecycle in headless Chrome or Chromium. Set CHROME_BIN to
the browser executable if it is not installed in a standard location.
