PGrid docs

Working with Data

PGrid keeps your data in a DataTable that you reach via grid.data. It supports two input formats and exposes a small CRUD + search API.

Data formats

The dataModel config tells PGrid how to interpret your data.

Object rows (default)

dataModel: {
    format: 'rows',
    fields: ['name', 'email', 'role'],
    data: [
        { name: 'Ada',   email: 'ada@example.com',   role: 'Engineer' },
        { name: 'Linus', email: 'linus@example.com', role: 'Manager'  }
    ]
}

Tuple arrays

Useful when your data comes from CSV or a database driver that returns rows as arrays.

dataModel: {
    format: 'array',
    fields: ['name', 'email', 'role'],
    data: [
        ['Ada',   'ada@example.com',   'Engineer'],
        ['Linus', 'linus@example.com', 'Manager']
    ]
}

fields tells PGrid the column order so it can normalize each tuple into an object internally.

Reading values

PGrid distinguishes between row id (stable, assigned at insert) and row index (position in the currently visible projection — affected by sort/filter/search).

// By visible row index
const value = grid.data.getDataAt(rowIndex, 'email');

// By stable row id
const rowId = grid.data.getRowId(rowIndex);
const value = grid.data.getData(rowId, 'email');

// Whole row
const row = grid.data.getRowDataAt(rowIndex);   // by index
const row = grid.data.getRowData(rowId);        // by id
Why both? When you filter the grid, the row at index 5 may correspond to a different underlying record than before. Operations that need to survive filtering (like persisting edits to your backend) should use the row id.

Writing values

Use the model when you have visible coordinates; use the data table when you have a row id.

// By coordinates (visible)
grid.model.setDataAt(rowIndex, colIndex, newValue);

// By row id + field
grid.data.setData(rowId, 'salary', 80000);

If autoUpdate: true is set, the affected cell re-renders automatically.

Add, insert, remove

grid.data.addRow({ name: 'Grace', email: 'grace@example.com', role: 'Engineer' });
grid.data.insertRow(0, newRow);    // at the top
grid.data.removeRow(rowId);
grid.data.removeRowAt(rowIndex);
grid.data.removeAllRows();
grid.view.reRender();

Live demo: Dynamic data.

The DataTable maintains a separate visible projection that you can filter without losing the original data.

// Single regex pattern, all fields (case-insensitive)
grid.data.search('ada');

// Restrict to specific fields
grid.data.search('engineer', ['role', 'department']);

// Multiple patterns OR-match
grid.data.search(['^ada', '^linus']);

// Reset
grid.data.clearSearch();

After mutating the projection, call grid.view.reRender(). Live demo: Search & filter.

Listening for changes

The data table emits dataChanged with a list of updates.

grid.data.listen('dataChanged', (e) => {
    e.updates.forEach(u => {
        // u.changeType: 'fieldChange' | 'rowAdded' | 'rowRemoved' | 'global'
        // u.rowId, u.field, u.prevData, u.data
        console.log(u);
    });
});

You can also intercept and cancel writes by registering an extension:

const validateNumeric = {
    dataBeforeUpdate(e) {
        if (e.field === 'salary' && isNaN(parseFloat(e.data))) {
            e.cancel = true;
        } else if (e.field === 'salary') {
            e.data = parseFloat(e.data);
        }
    }
};

new PGrid({ /* … */ extensions: [validateNumeric] });

See Extensions for the full list of hooks.

Bulk updates

For batched mutations, use freeze / unfreeze to suppress per-write events.

grid.data.freeze();
for (const row of bigImport) grid.data.addRow(row);
grid.data.unfreeze();
grid.view.reRender();