DataTable

Organism

Table + SearchBar + Pagination in a single component. Client-side search and pagination with filtered result counter and rows-per-page selector. rowActions adds a per-row actions menu (id, label, onClick or href, hidden, disabled, disabledReason, destructive, group, order).

With title & pagination

Preview

Users

87 total

NameRoleStatusJoined
Alice JohnsonAdminActiveJan 2024
Bob SmithEditorActiveMar 2024
Carol WhiteViewerInactiveJun 2024
Dan BrownEditorPendingSep 2024

Showing 1–20 of 87

Code
<%- include('modules/ui/DataTable', {
  title: 'Users',
  subtitle: total + ' total',
  columns: [
    { key: 'name',   header: 'Name' },
    { key: 'role',   header: 'Role' },
    { key: 'status', header: 'Status' },
    { key: 'joined', header: 'Joined', align: 'right' },
  ],
  rows: users,
  page: page,       // current page (1-based), from req.query
  total: total,     // total record count, from DB
  pageSize: 20,
  qs: qs,           // query string without 'page', from route
}) %>

<%-- In your Express route: --%>
<%
  // const page = parseInt(req.query.page) || 1;
  // const { users, total } = await UserService.list({ page, pageSize: 20 });
  // const qs = new URLSearchParams(req.query); qs.delete('page');
  // res.render('users/index', { users, total, page, qs: qs.toString() });
%>

Clickable rows

Preview

Users

NameRoleStatusJoined
Alice JohnsonAdminActiveJan 2024
Bob SmithEditorActiveMar 2024
Carol WhiteViewerInactiveJun 2024
Dan BrownEditorPendingSep 2024

Showing 1–4 of 4

Code
<%- include('modules/ui/DataTable', {
  columns: [...],
  rows: users,
  page: page,
  total: total,
  pageSize: 20,
  qs: qs,
  getRowHref: function(row) { return '/users/' + row.userId; }
}) %>

Custom cell render

Preview
NameRoleStatusJoined
Alice JohnsonAdminActiveJan 2024
Bob SmithEditorActiveMar 2024
Carol WhiteViewerInactiveJun 2024
Dan BrownEditorPendingSep 2024

Showing 1–4 of 4

Code
<%- include('modules/ui/DataTable', {
  columns: [
    { key: 'name', header: 'User',
      render: function(row) {
        return '<div class="flex items-center gap-2">'
          + '<span class="h-7 w-7 rounded-full bg-primary-subtle text-primary text-xs flex items-center justify-center">'
          + row.name.charAt(0).toUpperCase()
          + '</span>'
          + '<span class="font-medium text-text-primary">' + row.name + '</span>'
          + '</div>';
      }
    },
    { key: 'status', header: 'Status',
      render: function(row) {
        var v = row.status === 'Active' ? 'bg-success-subtle text-success-fg'
               : row.status === 'Pending' ? 'bg-warning-subtle text-warning-fg'
               : 'bg-surface-overlay text-text-secondary';
        return '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ' + v + '">'
          + row.status + '</span>';
      }
    },
  ],
  rows: users,
  page: page,
  total: total,
  pageSize: 20,
  qs: qs,
}) %>

Empty state

Preview

Users

NameRoleStatusJoined
No users found.

Showing 0–0 of 0

Code
<%- include('modules/ui/DataTable', {
  columns: [...],
  rows: [],
  page: 1,
  total: 0,
  pageSize: 20,
  qs: qs,
  emptyMessage: 'No users found.'
}) %>

Row actions (rowActions)

Preview
Companies
Company Email Status Actions
Northwind Traders buy@northwind.example Active
Contoso Ltd — Active
Fabrikam info@fabrikam.example Archived
Code
<%# A trailing column with one menu per row. Every per-row field takes a function of the row. %>
<%- include('modules/ui/DataTable', {
  rows: companies,
  columns: columns,
  rowActionsLabel: function (row) { return 'Actions for ' + row.name; },
  rowActions: [
    { id: 'open', label: 'Open', href: function (row) { return '/companies/' + row.id; } },
    { id: 'call', label: 'Call', group: 'contact', order: 10, onClick: function (row) { return "callCompany('" + row.id + "')"; } },
    { id: 'sign', label: 'Send for signature', group: 'contact', order: 20,
      disabled: function (row) { return !row.email; }, disabledReason: 'Add an email address first' },
    { id: 'restore', label: 'Restore', hidden: function (row) { return row.status !== 'Archived'; } },
    { id: 'delete', label: 'Delete', group: 'danger', order: 1000, destructive: true },
  ]
}) %>
Source modules/ui/Table/partials/paginated.ejs