Displaying data in a Grid
Every Bryntum component uses Store data containers for holding data.
A store uses a Model as the blueprint for each row (called record) it holds.
In this section, the focus will be on the fundamental process of presenting data within the Grid. For a comprehensive understanding of the Store and its functionalities, refer to Using a store.
This section outlines the available approaches to populate a Grid with data, that includes three methods:
- Using inline or preloaded data
- Loading remote data over HTTP(S)
- Responding to Store data requests
Using inline or preloaded data
If you have inline data, or data already fully loaded in a custom way, you can supply it directly when creating a grid:
const grid = new Grid({
columns : [/*...*/],
data : [
{ id : 1, name : 'Batman' },
{ id : 2, name : 'Wolverine' },
/*...*/
]
});
Another option if you need to configure the store is to supply a store config object (for info on available configs, see API docs for Store):
const grid = new Grid({
store : {
sorters : [
{ field : 'name' }
],
data : [
{ id : 1, name : 'Batman' },
...
]
}
});
A third option is to supply an already existing Store instance:
const store = new Store({
someConfig : "...",
data : [
{ id : 1, name : 'Batman' },
/*...*/
]
});
const grid = new Grid({
store
});
Inline data is expected to be an array of JavaScript objects. If no model/fields are defined for the store
more info the properties of the first entry in the array are used as fields
(id and name in the examples above).
If the data is not available at configuration time, and you do not want to use the remote loading capabilities described below, you can load data any custom way you want and then plug it into the store later:
const grid = new Grid({
columns : [/*...*/]
});
// Using native fetch to load data
const response = await fetch('backend/load.php');
const data = await response.json();
// Maybe do some custom processing before plugging into grids store
data.forEach((row, index) => {
row.index = index;
row.someValue = Math.random();
/*...*/
});
// Plug it in as inline data
grid.store.data = data;
Loading remote data over HTTP(S)
The easiest way to load remote data is to use and AjaxStore. Provide it with a
readUrl and it takes care of the loading. There are a few different ways to set it up. Either supply a store config
containing a readUrl:
const grid = new Grid({
store : {
// When Grid finds readUrl in the store config it will create an AjaxStore
readUrl : 'backend/load.php',
// Load upon creation
autoLoad : true
}
});
Or create the store prior to creating the grid:
const store = new AjaxStore({
readUrl : 'backend/load.aspx'
});
const grid = new Grid({
store
});
store.load();
The data returned from the backend is expected to have the following format:
{
"success" : true,
"data" : [
{ "id" : 1, "name" : "Batman" },
{ "..." : "..." }
]
}
Responding to Store data requests (advanced usage)
If you do not use an AjaxStore, and you need to use lazy loading, remote sorting, remote
filtering and/or remote paging, there is a third option. If any of lazyLoad,
remoteSort, remoteFilter or
remotePaging configs are set to true on a non-AjaxStore, the store will
request data when needed.
The lazy loading functionality has its own guide. For remote sorting, filtering and paging, please read on.
The requestData function will be called when the Store needs new data, which will happen:
- for remoteSort, on a sort operation.
- for remoteFilter, on a filter operation.
- for remotePaging, when current page is changed.
When implementing this, it is expected that what is returned is an object with a data property containing the
records requested. What is requested will be specified in the params object, which will differ depending on the
source of the request.
For remotePaging, the params object will contain a page and a pageSize
param. It is expected for the implementation of this function to provide a data property containing the number of
records specified in the pageSize param starting from the specified page. It is also required, to include a total
property which reflects the total amount of records available to load.
class MyStore extends Store {
static configurable = {
remotePaging : true
}
requestData({page, pageSize}){
const start = (page - 1) * pageSize;
const data = allRecords.splice(start, start + pageSize);
return {
data,
total : allRecords.length
}
}
}
If remoteSort is active, the params object will contain a sorters param,
containing a number of sorters objects. The sorter objects will look like this:
{
"field": "name",
"ascending": true
}
Use the sorters param to sort the data before returning:
const store = new Store({
remoteSort : true,
remotePaging : true,
requestData({ sorters, page, pageSize }){
const sortedRecords = [...allRecords];
sorters?.forEach(sorter => sortedRecords.sort((a,b) => {
const { field, ascending } = sorter;
if (!ascending) {
([b, a] = [a, b]);
}
return a[field] > b[field] ? 1 : (a[field] < b[field] ? -1 : 0)
});
const start = (page - 1) * pageSize;
const data = sortedRecords.splice(start, start + pageSize);
return {
data,
total : allRecords.length
}
}
})
If remoteFilter is active, the params object will contain a filters param,
containing a number of filter objects. The filter objects will look like this:
{
"field": "country",
"operator": "=",
"value": "sweden",
"caseSensitive": false
}
Use the filters to filter the data before returning:
const store = new Store({
remoteFilter : true,
remoteSort : true,
remotePaging : true,
requestData({ filters, sorters, page, pageSize }){
let filteredRecords = [...allRecords];
filters?.forEach(filter => {
const { field, operator, value, caseSensitive } = filter;
if(operator === '='){
filteredRecords = filteredRecords.filter(r => r[field] === value);
}
else {
/// ... implement other filter operators
}
});
sorters?.forEach(sorter => filteredRecords.sort((a,b) => {
const { field, ascending } = sorter;
if (!ascending) {
([b, a] = [a, b]);
}
return a[field] > b[field] ? 1 : (a[field] < b[field] ? -1 : 0)
}));
const start = (page - 1) * pageSize;
const data = filteredRecords.splice(start, start + pageSize);
return {
data,
total : filteredRecords.length
}
}
})
Framework 2-way binding
For some framework users, where the data property of the Grid has been bound to a state-monitored data source,
implementing the requestData function is not a viable option. In these cases,
it is better to add a listener to the requestData event instead.
The main difference is that a requestData event listener cannot return the data directly. Instead, the data property should be updated (which will be done by the framework), and if the Store is paged, the totalCount property be set (will not be done by the framework).
const store = new Store({
remoteFilter : true,
remoteSort : true,
remotePaging : true,
listeners: {
requestData({
source,
filters,
sorters,
page,
pageSize
}) {
let filteredRecords = [...allRecords];
filters?.forEach(filter => {
const {
field,
operator,
value,
caseSensitive
} = filter;
if (operator === '=') {
filteredRecords = filteredRecords.filter(r => r[field] === value);
}
else {
/// ... implement other filter operators
}
});
sorters?.forEach(sorter => filteredRecords.sort((a, b) => {
const {
field,
ascending
} = sorter;
if (!ascending) {
([b, a] = [a, b]);
}
return a[field] > b[field] ? 1 : (a[field] < b[field] ? -1 : 0)
}));
const start = (page - 1) * pageSize;
const data = filteredRecords.splice(start, start + pageSize);
source.data = data;
source.totalCount = filteredRecords.length;
}
}
})