Tables

DataTable-like functionality for interactive data grids with sorting, filtering, pagination, and more.

Creating Tables

Transform a table element into an interactive data grid.

// Create a table instance
const table = Domma.tables.create('#my-table', {
    data: [
        { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin' },
        { id: 2, name: 'Bob', email: 'bob@example.com', role: 'User' },
        // ...more rows
    ],
    columns: [
        { key: 'id', title: 'ID', sortable: true },
        { key: 'name', title: 'Name', sortable: true },
        { key: 'email', title: 'Email', sortable: true, editable: true },
        { key: 'role', title: 'Role', filterable: true },
        {
            key: 'actions',
            title: '',
            render: (val, row) => `Edit`
        }
    ],
    pagination: true,
    pageSize: 10,
    selectable: true
});

// HTML

Column Options

Option Type Description
key string Property name in data object
title string Column header text
sortable boolean Enable sorting on this column
filterable boolean Enable filtering on this column
editable boolean Enable inline editing
render function Custom cell renderer (val, row) => html

Custom Render Examples

Use the render function to transform cell values - perfect for date formatting, badges, or custom HTML.

// Format dates using Domma's date module
{
    key: 'joined',
    title: 'Joined',
    sortable: true,
    render: (val) => D(val).format('DD/MM/YYYY')  // 15/01/2024
}

// Status badge with conditional styling
{
    key: 'status',
    title: 'Status',
    render: (val) => `${val}`
}

// Action buttons with row data
{
    key: 'actions',
    title: '',
    render: (val, row) => `Edit ${row.name}`
}

Tip: Use width: '100px' or width: '20%' to set column widths.

Table Options

Option Type Default Description
data array [] Array of row objects
columns array [] Column configuration
pagination boolean false Enable pagination
pageSize number 10 Rows per page
selectable boolean false Enable row selection
selectionMode string 'multiple' 'single' or 'multiple'
columnToggle boolean false Show column visibility dropdown

Interactive Demo

This demo includes the Columns dropdown (to show/hide columns) and the Export Panel (Copy/Download toggle). Note the Joined column uses a custom render function to format dates as DD/MM/YYYY.

Interactive Demo
No rows selected
<div id="demo-table"></div>
<div class="flex gap-2">
    <select id="role-filter" class="form-select" style="width: 150px;">
        <option value="">All Roles</option>
        <option value="Admin">Admin</option>
        <option value="Manager">Manager</option>
        <option value="Developer">Developer</option>
        <option value="Designer">Designer</option>
    </select>
    <button type="button" class="btn btn-outline btn-sm" id="clear-filters">Clear Filters</button>
</div>
<div class="flex gap-2">
    <button type="button" class="btn btn-outline btn-sm" id="select-all">Select All</button>
    <button type="button" class="btn btn-outline btn-sm" id="deselect-all">Deselect All</button>
    <button type="button" class="btn btn-danger btn-sm" id="delete-selected" disabled="">Delete Selected</button>
</div>
<div id="selected-info" class="text-sm text-muted mt-3">No rows selected</div>
/**
 * Interactive Demo - from public/showcase/tables.
 */
export function mount(root) {
    const sampleData = [
            {
                id: 1,
                name: 'Alice Johnson',
                email: 'alice@example.com',
                role: 'Admin',
                status: 'Active',
                joined: '2024-01-15'
            },
            {id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Developer', status: 'Active', joined: '2024-02-20'},
            {
                id: 3,
                name: 'Carol White',
                email: 'carol@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-03-10'
            },
            {
                id: 4,
                name: 'David Brown',
                email: 'david@example.com',
                role: 'Manager',
                status: 'Inactive',
                joined: '2024-01-05'
            },
            {
                id: 5,
                name: 'Eva Martinez',
                email: 'eva@example.com',
                role: 'Developer',
                status: 'Active',
                joined: '2024-04-01'
            },
            {
                id: 6,
                name: 'Frank Wilson',
                email: 'frank@example.com',
                role: 'Admin',
                status: 'Active',
                joined: '2024-02-14'
            },
            {
                id: 7,
                name: 'Grace Lee',
                email: 'grace@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-05-22'
            },
            {
                id: 8,
                name: 'Henry Davis',
                email: 'henry@example.com',
                role: 'Developer',
                status: 'Inactive',
                joined: '2024-03-30'
            },
            {id: 9, name: 'Ivy Chen', email: 'ivy@example.com', role: 'Manager', status: 'Active', joined: '2024-06-15'},
            {
                id: 10,
                name: 'Jack Taylor',
                email: 'jack@example.com',
                role: 'Developer',
                status: 'Active',
                joined: '2024-04-18'
            },
            {
                id: 11,
                name: 'Kate Anderson',
                email: 'kate@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-07-01'
            },
            {id: 12, name: 'Leo Thomas', email: 'leo@example.com', role: 'Admin', status: 'Inactive', joined: '2024-01-28'},
            {
                id: 13,
                name: 'Mia Jackson',
                email: 'mia@example.com',
                role: 'Developer',
                status: 'Active',
                joined: '2024-08-10'
            },
            {
                id: 14,
                name: 'Noah Garcia',
                email: 'noah@example.com',
                role: 'Manager',
                status: 'Active',
                joined: '2024-05-05'
            },
            {
                id: 15,
                name: 'Olivia Moore',
                email: 'olivia@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-09-20'
            }
        ];
        // Create table with export panel
    const table = Domma.tables.create('#demo-table', {
            data: sampleData,
            columns: [
                {key: 'id', title: 'ID', sortable: true, width: '60px'},
                {key: 'name', title: 'Name', sortable: true},
                {key: 'email', title: 'Email', sortable: true},
                {key: 'role', title: 'Role', sortable: true, filterable: true},
                {
                    key: 'status',
                    title: 'Status',
                    render: (val) => `<span class="badge ${val === 'Active' ? 'badge-success' : 'badge-secondary'}">${val}</span>`
                },
                {
                    key: 'joined',
                    title: 'Joined',
                    sortable: true,
                    render: (val) => D(val).format('DD/MM/YYYY')
                }
            ],
            pagination: true,
            pageSize: 10,
            selectable: true,
            selectionMode: 'multiple',
            // Column visibility toggle
            columnToggle: true,
            // Regex search toggle
            regexSearch: true,
            // Export panel options
            exportPanel: true,
            exportOptions: ['copy', 'csv', 'excel', 'json'],
            onExport: (info) => {
                console.log(`Exported ${info.rowCount} rows as ${info.format}`);
            }
        });
    function updateSelectedInfo() {
            const selected = table.getSelected();
            if (selected.length === 0) {
                $(root).find('#selected-info').text('No rows selected');
                $(root).find('#delete-selected').prop('disabled', true);
            } else {
                const names = selected.slice(0, 3).map(r => r.name).join(', ');
                const extra = selected.length > 3 ? ` and ${selected.length - 3} more` : '';
                $(root).find('#selected-info').text(`${selected.length} row(s) selected: ${names}${extra}`);
                $(root).find('#delete-selected').prop('disabled', false);
            }
        }
        // Table events
    table.on('select', () => {
            updateSelectedInfo();
        });
        // Role filter
    $(root).find('#role-filter').on('change', function () {
            const role = $(this).val();
            if (role) {
                table.filter('role', role);
            } else {
                table.clearFilters();
            }
        });
    $(root).find('#clear-filters').on('click', function () {
            $(root).find('#role-filter').val('');
            table.clearFilters();
        });
        // Selection buttons
    $(root).find('#select-all').on('click', () => table.selectAll());
    $(root).find('#deselect-all').on('click', () => table.deselectAll());
    $(root).find('#delete-selected').on('click', function () {
            const selected = table.getSelected();
            if (selected.length && confirm(`Delete ${selected.length} row(s)?`)) {
                // Remove by row key (id)
                selected.forEach(row => table.removeRow(row.id));
            }
        });
    const names = ['Alex', 'Blake', 'Casey', 'Dana', 'Ellis', 'Finn'];
        // ============================================
        // Stripe Styling Demo
        // ============================================
        // Sample data for stripe demo
    const stripeData = sampleData.slice(0, 6);
}

Sorting

Sort data by one or multiple columns.

// Single column sort
table.sort('name', 'asc');
table.sort('age', 'desc');

// Multi-column sort
table.sortMultiple([
    { column: 'department', direction: 'asc' },
    { column: 'name', direction: 'asc' }
]);

// Clear sorting
table.clearSort();

// Listen for sort events
table.on('sort', (column, direction) => {
    console.log(`Sorted by ${column} ${direction}`);
});
Sorting

Click column headers in the demo table above to sort. Click again to reverse order.

<p class="text-sm text-muted mb-3">Click column headers in the demo table above to sort. Click again to
    reverse order.</p>
<div class="flex gap-2">
    <button type="button" class="btn btn-outline btn-sm" id="sort-name">Sort by Name</button>
    <button type="button" class="btn btn-outline btn-sm" id="sort-role">Sort by Role</button>
    <button type="button" class="btn btn-outline btn-sm" id="sort-date">Sort by Date</button>
    <button type="button" class="btn btn-outline btn-sm" id="clear-sort">Clear Sort</button>
</div>
/**
 * Sorting - from public/showcase/tables.
 */
export function mount(root) {
        // Sorting buttons
    $(root).find('#sort-name').on('click', () => table.sort('name', 'asc'));
    $(root).find('#sort-role').on('click', () => table.sort('role', 'asc'));
    $(root).find('#sort-date').on('click', () => table.sort('joined', 'desc'));
    $(root).find('#clear-sort').on('click', () => table.clearSort());
}

Filtering

Filter data with text search or column-specific filters.

// Global text search
table.search('alice');

// Column filter
table.filter('role', 'Admin');
table.filter('status', 'active', 'equals');

// Filter operators
table.filter('age', 25, 'gt');      // Greater than
table.filter('age', 30, 'lt');      // Less than
table.filter('age', 25, 'gte');     // Greater or equal
table.filter('name', 'al', 'contains');

// Custom filter function
table.filterBy((row) => {
    return row.age >= 25 && row.status === 'active';
});

// Clear filters
table.clearFilters();

// Listen for filter events
table.on('filter', (filters) => {
    console.log('Active filters:', filters);
});

Pagination

Navigate through large datasets with pagination controls.

// Create table with pagination
const table = Domma.tables.create('#table', {
    data: largeDataset,
    pagination: true,
    pageSize: 25
});

// Navigate pages
table.page(1);          // Go to page 1 (0-indexed)
table.nextPage();
table.prevPage();
table.firstPage();
table.lastPage();

// Change page size
table.pageSize(50);

// Get pagination info
const info = table.pageInfo();
// {
//   currentPage: 0,
//   pageSize: 25,
//   totalPages: 10,
//   totalRows: 250,
//   startRow: 1,
//   endRow: 25
// }

// Listen for page events
table.on('page', (page, pageSize) => {
    console.log(`Page ${page + 1}`);
});
Pagination
Page Size:
<div class="flex gap-2 items-center">
    <span class="text-sm">Page Size:</span>
    <select id="page-size" class="form-select" style="width: 80px;">
        <option value="5">5</option>
        <option value="10" selected="">10</option>
        <option value="25">25</option>
        <option value="50">50</option>
    </select>
    <button type="button" class="btn btn-outline btn-sm" id="first-page">First</button>
    <button type="button" class="btn btn-outline btn-sm" id="prev-page">Prev</button>
    <button type="button" class="btn btn-outline btn-sm" id="next-page">Next</button>
    <button type="button" class="btn btn-outline btn-sm" id="last-page">Last</button>
</div>

Row Selection

Select single or multiple rows for bulk operations.

// Enable selection
const table = Domma.tables.create('#table', {
    data: data,
    selectable: true,
    selectionMode: 'multiple'  // or 'single'
});

// Select rows
table.select(0);           // Select first row
table.select([0, 1, 2]);   // Select multiple rows
table.selectAll();

// Deselect
table.deselect(0);
table.deselectAll();

// Toggle selection
table.toggleSelect(0);

// Get selected rows
const selected = table.getSelected();
// Returns array of selected row data

const selectedIndices = table.getSelectedIndices();
// Returns array of selected row indices

// Listen for selection events
table.on('select', (selectedRows, selectedIndices) => {
    console.log(`${selectedRows.length} rows selected`);
});
Row Selection

Use checkboxes in the demo table to select rows. Selected rows are highlighted.

No rows selected
<p class="text-sm text-muted mb-3">Use checkboxes in the demo table to select rows. Selected rows are
    highlighted.</p>
<div id="selected-info" class="text-sm">No rows selected</div>

Data Management

Add, update, and remove data dynamically.

// Set all data
table.setData(newDataArray);

// Get all data (filtered/sorted state)
const currentData = table.getData();

// Get original data
const originalData = table.getOriginalData();

// Add rows
table.addRow({ id: 100, name: 'New User' });
[{ id: 101, name: 'Maya' }, { id: 102, name: 'Alex' }]
    .forEach(row => table.addRow(row));

// Update row
table.updateRow(0, { name: 'Updated Name' });
table.updateRow(0, newRowData, true);  // Replace entire row

// Remove rows
table.removeRow(0);
[2, 1, 0].forEach(index => table.removeRow(index));

// Clear all data
table.setData([]);

// Refresh table (re-render)
table.refresh();
Data Management
<div class="flex gap-2">
    <button type="button" class="btn btn-success btn-sm" id="add-row">Add Random Row</button>
    <button type="button" class="btn btn-warning btn-sm" id="update-first">Update First Row</button>
    <button type="button" class="btn btn-danger btn-sm" id="remove-last">Remove Last Row</button>
</div>
/**
 * Data Management - from public/showcase/tables.
 */
export function mount(root) {
        // Data manipulation
    let nextId = 100;
    const roles = ['Admin', 'Manager', 'Developer', 'Designer'];
    const names = ['Alex', 'Blake', 'Casey', 'Dana', 'Ellis', 'Finn'];
    $(root).find('#add-row').on('click', function () {
            const name = _.sample(names) + ' ' + _.sample(names);
            table.addRow({
                id: nextId++,
                name: name,
                email: name.toLowerCase().replace(' ', '.') + '@example.com',
                role: _.sample(roles),
                status: _.random(0, 9) > 2 ? 'Active' : 'Inactive',
                joined: D().format('YYYY-MM-DD')
            });
        });
    $(root).find('#update-first').on('click', function () {
            table.updateRow(0, {name: 'Updated Name ' + _.random(0, 99)});
        });
    $(root).find('#remove-last').on('click', function () {
            const data = table.getData();
            if (data.length > 0) {
                table.removeRow(data.length - 1);
            }
        });
}

Export Panel

Enable a built-in export toolbar with Copy/Download toggle and format buttons.

// Enable export panel
const table = Domma.tables.create('#my-table', {
    data: myData,
    columns: [...],
    exportPanel: true,  // Enable the export toolbar
    exportOptions: ['copy', 'csv', 'excel', 'json'],  // Choose which buttons to show
    onExport: (info) => {
        console.log(`Exported ${info.rowCount} rows as ${info.format}`);
    }
});

// The panel shows:
// [ Copy (•)──Download ]  |  📋 Text  📄 CSV  📊 Excel  { } JSON
//
// Toggle LEFT  = Copy mode (copies to clipboard)
// Toggle RIGHT = Download mode (downloads file)

Export Panel Options

Option Type Default Description
exportPanel boolean false Show the export toolbar
exportOptions array ['copy', 'csv', 'excel', 'json'] Which export buttons to display
onExport function null Callback when data is exported

Column Visibility

Enable a dropdown to show/hide columns dynamically. Users can toggle column visibility without reloading the table.

// Enable column toggle dropdown
const table = Domma.tables.create('#my-table', {
    data: myData,
    columns: [
        { key: 'id', title: 'ID' },
        { key: 'name', title: 'Name' },
        { key: 'email', title: 'Email', visible: false },  // Hidden by default
        { key: 'role', title: 'Role' }
    ],
    columnToggle: true  // Enable the Columns dropdown
});

// Programmatic control
table.showColumn('email');      // Show a hidden column
table.hideColumn('role');       // Hide a visible column
table.toggleColumn('name');     // Toggle visibility

// Get column info
table.getVisibleColumns();      // Array of visible column configs
table.getHiddenColumns();       // Array of hidden column configs
table.getColumns();             // All columns

Column Visibility Options

Option Type Default Description
columnToggle boolean false Show the Columns dropdown in the toolbar
regexSearch boolean false Show the regex toggle button next to search input
visible (column) boolean true Initial visibility of a column
Column Visibility Options

Try the Columns dropdown in the Interactive Demo above to show/hide columns.

<p class="text-sm text-muted mb-3">Try the <strong>Columns</strong> dropdown in the Interactive Demo
    above to show/hide columns.</p>

Stripe Styling Configuration

Customise row stripe and hover colours using named variants or custom hex/RGB values.

// Named variants (uses CSS variables)
const table1 = Domma.tables.create('#table', {
    data: data,
    striped: true,
    evenRowColor: 'lighter',   // var(--dm-gray-100)
    oddRowColor: 'light',      // var(--dm-gray-200)
    hoverColor: 'medium'       // var(--dm-gray-300)
});

// Custom hex colors
const table2 = Domma.tables.create('#table', {
    data: data,
    striped: true,
    evenRowColor: '#ffffff',
    oddRowColor: '#f9f9f9',
    hoverColor: '#e6f2ff'
});

// Semantic tints
const table3 = Domma.tables.create('#table', {
    data: data,
    striped: true,
    evenRowColor: 'none',
    oddRowColor: 'primary-tint',
    hoverColor: 'light'
});

// Named variants: 'none', 'lighter', 'light', 'medium', 'dark',
//                 'primary-tint', 'success-tint', 'warning-tint',
//                 'danger-tint', 'info-tint'

Stripe Colour Options

Option Type Default Description
evenRowColor string null (transparent) Colour for even rows (0, 2, 4...). Named variant or hex/rgb value.
oddRowColor string null ('#f9f9f9') Colour for odd rows (1, 3, 5...). Named variant or hex/rgb value.
hoverColor string null ('#f0f0f0') Colour for row hover state. Named variant or hex/rgb value.
Stripe Colour Options

Interactive demo with different stripe configurations:

<p class="text-sm text-muted mb-3">Interactive demo with different stripe configurations:</p>

<div class="mb-4">
    <label class="text-sm font-semibold mb-2 block">Choose Stripe Style:</label>
    <div class="flex gap-2 flex-wrap">
        <button type="button" class="btn btn-sm btn-outline" id="stripe-default">Default</button>
        <button type="button" class="btn btn-sm btn-outline" id="stripe-light">Light Grey</button>
        <button type="button" class="btn btn-sm btn-outline" id="stripe-medium">Medium Grey</button>
        <button type="button" class="btn btn-sm btn-outline" id="stripe-primary">Primary Tint</button>
        <button type="button" class="btn btn-sm btn-outline" id="stripe-success">Success Tint</button>
        <button type="button" class="btn btn-sm btn-outline" id="stripe-custom">Custom Hex</button>
    </div>
</div>

<div id="stripe-demo-table"></div>
/**
 * Stripe Colour Options - from public/showcase/tables.
 */
export function mount(root) {
    const sampleData = [
            {
                id: 1,
                name: 'Alice Johnson',
                email: 'alice@example.com',
                role: 'Admin',
                status: 'Active',
                joined: '2024-01-15'
            },
            {id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Developer', status: 'Active', joined: '2024-02-20'},
            {
                id: 3,
                name: 'Carol White',
                email: 'carol@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-03-10'
            },
            {
                id: 4,
                name: 'David Brown',
                email: 'david@example.com',
                role: 'Manager',
                status: 'Inactive',
                joined: '2024-01-05'
            },
            {
                id: 5,
                name: 'Eva Martinez',
                email: 'eva@example.com',
                role: 'Developer',
                status: 'Active',
                joined: '2024-04-01'
            },
            {
                id: 6,
                name: 'Frank Wilson',
                email: 'frank@example.com',
                role: 'Admin',
                status: 'Active',
                joined: '2024-02-14'
            },
            {
                id: 7,
                name: 'Grace Lee',
                email: 'grace@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-05-22'
            },
            {
                id: 8,
                name: 'Henry Davis',
                email: 'henry@example.com',
                role: 'Developer',
                status: 'Inactive',
                joined: '2024-03-30'
            },
            {id: 9, name: 'Ivy Chen', email: 'ivy@example.com', role: 'Manager', status: 'Active', joined: '2024-06-15'},
            {
                id: 10,
                name: 'Jack Taylor',
                email: 'jack@example.com',
                role: 'Developer',
                status: 'Active',
                joined: '2024-04-18'
            },
            {
                id: 11,
                name: 'Kate Anderson',
                email: 'kate@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-07-01'
            },
            {id: 12, name: 'Leo Thomas', email: 'leo@example.com', role: 'Admin', status: 'Inactive', joined: '2024-01-28'},
            {
                id: 13,
                name: 'Mia Jackson',
                email: 'mia@example.com',
                role: 'Developer',
                status: 'Active',
                joined: '2024-08-10'
            },
            {
                id: 14,
                name: 'Noah Garcia',
                email: 'noah@example.com',
                role: 'Manager',
                status: 'Active',
                joined: '2024-05-05'
            },
            {
                id: 15,
                name: 'Olivia Moore',
                email: 'olivia@example.com',
                role: 'Designer',
                status: 'Active',
                joined: '2024-09-20'
            }
        ];
        // ============================================
        // Stripe Styling Demo
        // ============================================
        // Sample data for stripe demo
    const stripeData = sampleData.slice(0, 6);
        // Create initial stripe demo table
    let stripeTable = Domma.tables.create('#stripe-demo-table', {
            data: stripeData,
            columns: [
                {key: 'name', title: 'Name'},
                {key: 'email', title: 'Email'},
                {key: 'role', title: 'Role'}
            ],
            pagination: false,
            striped: true
        });
        // Helper to recreate table with new stripe config
    function updateStripeDemo(config) {
            const buttons = ['stripe-default', 'stripe-light', 'stripe-medium', 'stripe-primary', 'stripe-success', 'stripe-custom'];
            buttons.forEach(id => {
                $('#' + id).removeClass('btn-primary').addClass('btn-outline');
            });
            $('#' + config.activeButton).removeClass('btn-outline').addClass('btn-primary');

            stripeTable = Domma.tables.create('#stripe-demo-table', {
                data: stripeData,
                columns: [
                    {key: 'name', title: 'Name'},
                    {key: 'email', title: 'Email'},
                    {key: 'role', title: 'Role'}
                ],
                pagination: false,
                striped: true,
                evenRowColor: config.evenRowColor,
                oddRowColor: config.oddRowColor,
                hoverColor: config.hoverColor
            });
        }
        // Stripe style buttons
    $(root).find('#stripe-default').on('click', () => {
            updateStripeDemo({
                activeButton: 'stripe-default',
                evenRowColor: null,
                oddRowColor: null,
                hoverColor: null
            });
        });
    $(root).find('#stripe-light').on('click', () => {
            updateStripeDemo({
                activeButton: 'stripe-light',
                evenRowColor: 'lighter',
                oddRowColor: 'light',
                hoverColor: 'medium'
            });
        });
    $(root).find('#stripe-medium').on('click', () => {
            updateStripeDemo({
                activeButton: 'stripe-medium',
                evenRowColor: 'light',
                oddRowColor: 'medium',
                hoverColor: 'dark'
            });
        });
    $(root).find('#stripe-primary').on('click', () => {
            updateStripeDemo({
                activeButton: 'stripe-primary',
                evenRowColor: 'none',
                oddRowColor: 'primary-tint',
                hoverColor: 'light'
            });
        });
    $(root).find('#stripe-success').on('click', () => {
            updateStripeDemo({
                activeButton: 'stripe-success',
                evenRowColor: 'none',
                oddRowColor: 'success-tint',
                hoverColor: 'light'
            });
        });
    $(root).find('#stripe-custom').on('click', () => {
            updateStripeDemo({
                activeButton: 'stripe-custom',
                evenRowColor: '#ffffff',
                oddRowColor: '#e8f4f8',
                hoverColor: '#d4e9f2'
            });
        });
}

Export Methods

Programmatically export data in various formats.

// Copy to clipboard
table.copyToClipboard();        // Tab-separated (paste into Excel)
table.copyToClipboard('csv');   // CSV format
table.copyToClipboard('json');  // JSON format

// Get data as string
const csv = table.toCSV();      // CSV string
const json = table.toJSON();    // JSON string
const excel = table.toExcel();  // HTML table (Excel-compatible)

// Download files
table.download('csv', 'users.csv');
table.download('excel', 'users.xls');
table.download('json', 'users.json');

// Export callback
table.on('export', (info) => {
    console.log(`Exported ${info.rowCount} rows as ${info.format}`);
});
Export Methods

Try the export buttons in the Interactive Demo above, or use these:

<p class="text-sm text-muted mb-3">Try the export buttons in the Interactive Demo above, or use
    these:</p>
<div class="flex gap-2 flex-wrap">
    <button type="button" class="btn btn-primary btn-sm" id="export-csv">Download CSV</button>
    <button type="button" class="btn btn-primary btn-sm" id="export-excel">Download Excel</button>
    <button type="button" class="btn btn-primary btn-sm" id="export-json">Download JSON</button>
    <button type="button" class="btn btn-outline btn-sm" id="copy-clipboard">Copy to Clipboard</button>
</div>
/**
 * Export Methods - from public/showcase/tables.
 */
export function mount(root) {
        // Export buttons
    $(root).find('#export-csv').on('click', () => table.download('csv', 'users.csv'));
    $(root).find('#export-excel').on('click', () => table.download('excel', 'users.xls'));
    $(root).find('#export-json').on('click', () => table.download('json', 'users.json'));
    $(root).find('#copy-clipboard').on('click', async function () {
            await table.copyToClipboard();
            $(this).text('Copied!').addClass('btn-success').removeClass('btn-outline');
            setTimeout(() => {
                $(this).text('Copy to Clipboard').removeClass('btn-success').addClass('btn-outline');
            }, 1500);
        });
}

Events

Listen to table events for custom behavior.

// Available events
table.on('sort', (column, direction) => { });
table.on('filter', (activeFilters) => { });
table.on('search', (query) => { });
table.on('page', (page, pageSize) => { });
table.on('select', (rows, indices) => { });
table.on('edit', (row, column, oldValue, newValue) => { });
table.on('render', () => { });
table.on('dataChange', (data) => { });

// Remove event listener
table.off('sort', handler);

// One-time listener
table.once('render', () => {
    console.log('Table rendered for the first time');
});

Declarative Table Configuration

While tables use Domma.tables.create(), you can still leverage Domma's config engine to declaratively manage table setups, event handlers, and interactions.

Centralized Table Config

// Define all table configurations in one place
const tableConfigs = {
    users: {
        selector: '#users-table',
        options: {
            data: userData,
            columns: [
                { key: 'id', title: 'ID', sortable: true },
                { key: 'name', title: 'Name', sortable: true },
                { key: 'email', title: 'Email', sortable: true },
                { key: 'role', title: 'Role', filterable: true }
            ],
            pagination: true,
            pageSize: 20,
            selectable: true,
            exportPanel: true
        },
        events: {
            select: (rows) => {
                console.log(`${rows.length} users selected`);
            },
            edit: (row, column, oldVal, newVal) => {
                // Save to server
                api.updateUser(row.id, { [column]: newVal });
            }
        }
    },
    products: {
        selector: '#products-table',
        options: {
            data: productData,
            columns: [
                { key: 'sku', title: 'SKU', sortable: true },
                { key: 'name', title: 'Product', sortable: true },
                { key: 'price', title: 'Price', sortable: true, render: (val) => `$${val}` },
                { key: 'stock', title: 'Stock', sortable: true }
            ],
            pagination: true,
            pageSize: 50
        }
    }
};

// Initialize all tables
Object.values(tableConfigs).forEach(config => {
    const table = Domma.tables.create(config.selector, config.options);

    // Attach event handlers
    if (config.events) {
        Object.entries(config.events).forEach(([event, handler]) => {
            table.on(event, handler);
        });
    }
});

Using $.setup() for Table Containers

// Enhance table containers with config engine
$.setup({
    '.table-container': {
        initial: {
            css: {
                background: 'var(--dm-gray-50)',
                padding: 'var(--dm-space-4)',
                borderRadius: 'var(--dm-radius-lg)'
            }
        },
        events: {
            ready: (e, $el) => {
                // Create table when container is ready
                const tableId = $el.find('table').attr('id');
                const config = tableConfigs[tableId];

                if (config) {
                    const table = Domma.tables.create(`#${tableId}`, config.options);
                    $el.data('tableInstance', table);
                }
            }
        }
    },

    '#export-controls': {
        events: {
            'click .export-btn': (e, $el) => {
                const format = $(e.target).data('format');
                const table = $('.table-container').data('tableInstance');

                if (table) {
                    table.download(format);
                    Domma.elements.toast.success(`Exported as ${format.toUpperCase()}`);
                }
            }
        }
    }
});

Dynamic Table Factory

// Create a reusable table factory with config
class TableFactory {
    static presets = {
        users: {
            columns: [
                { key: 'id', title: 'ID', sortable: true },
                { key: 'name', title: 'Name', sortable: true },
                { key: 'email', title: 'Email', sortable: true, editable: true },
                { key: 'role', title: 'Role', filterable: true }
            ],
            pagination: true,
            pageSize: 20,
            exportPanel: true
        },
        products: {
            columns: [
                { key: 'sku', title: 'SKU', sortable: true },
                { key: 'name', title: 'Product', sortable: true },
                { key: 'price', title: 'Price', sortable: true, render: (val) => `$${val}` },
                { key: 'stock', title: 'Stock', sortable: true }
            ],
            pagination: true,
            pageSize: 50
        }
    };

    static create(selector, preset, data, customOptions = {}) {
        const presetConfig = this.presets[preset];
        if (!presetConfig) {
            throw new Error(`Unknown preset: ${preset}`);
        }

        return Domma.tables.create(selector, {
            ...presetConfig,
            ...customOptions,
            data
        });
    }
}

// Usage - clean and declarative
const usersTable = TableFactory.create('#users', 'users', userData);
const productsTable = TableFactory.create('#products', 'products', productData, {
    pageSize: 100  // Override preset
});

State-Driven Table Management

// Use Domma models for reactive table state
const tableState = M.create({
    data: { type: 'array', default: [] },
    sortColumn: { type: 'string', default: '' },
    sortDirection: { type: 'string', default: 'asc' },
    currentPage: { type: 'number', default: 1 },
    pageSize: { type: 'number', default: 20 },
    selectedRows: { type: 'array', default: [] }
});

// Create table
const table = Domma.tables.create('#state-table', {
    data: tableState.get('data'),
    pageSize: tableState.get('pageSize'),
    pagination: true
});

// Sync table events with state
table.on('sort', (column, direction) => {
    tableState.set({ sortColumn: column, sortDirection: direction });
});

table.on('page', (page) => {
    tableState.set({ currentPage: page });
});

table.on('select', (rows) => {
    tableState.set({ selectedRows: rows });
});

// React to state changes
tableState.onChange('data', (newData) => {
    table.setData(newData);
});

tableState.onChange('selectedRows', (rows) => {
    $('#selection-count').text(`${rows.length} selected`);
});

Benefits of Configuration-Driven Tables

  • Maintainability - All table configs in centralized location
  • Reusability - Share column definitions and presets across tables
  • Testability - Easy to test config objects independently
  • Type Safety - Config validation before table creation
  • State Management - Integrate with reactive models for data flow

Imperative vs Configuration-Driven

Imperative (Scattered)

// Table 1
const table1 = Domma.tables.create('#t1', {
    data: data1,
    columns: [/*...*/],
    pagination: true
});

table1.on('select', (rows) => {
    console.log(rows);
});

// Table 2 - elsewhere in code
const table2 = Domma.tables.create('#t2', {
    data: data2,
    columns: [/*...*/],
    pageSize: 50
});

// Configurations spread out

Configuration-Driven (Centralized)

// One config object
const config = {
    table1: {
        selector: '#t1',
        options: {
            data: data1,
            columns: [/*...*/],
            pagination: true
        },
        events: {
            select: (rows) => console.log(rows)
        }
    },
    table2: {
        selector: '#t2',
        options: { data: data2, pageSize: 50 }
    }
};

// Single init
initTables(config);

Complete Method Reference

Data Methods

setData()

getData()

addRow()

updateRow()

removeRow()

refresh()

Sorting Methods

sort()

sortMultiple()

clearSort()

Filtering Methods

search()

filter()

filterBy()

clearFilters()

Pagination Methods

page()

pageSize()

nextPage()

prevPage()

firstPage()

lastPage()

pageInfo()

Selection Methods

select()

deselect()

selectAll()

deselectAll()

toggleSelect()

getSelected()

Column Methods

showColumn()

hideColumn()

toggleColumn()

getColumns()

getVisibleColumns()

getHiddenColumns()

Export Methods

toCSV()

toJSON()

toExcel()

download()

copyToClipboard()

Event Methods

on()

off()

Static Methods

tables.create()

tables.get()

tables.destroy()