Neo.mjs Grids
The Neo.grid.Container is a powerful and highly performant component for displaying tabular data. It is designed to
handle large datasets with ease, thanks to its virtual rendering engine, which only renders the DOM for the visible
rows and columns.
Key Features
- High Performance: Optimized for handling large amounts of data through virtual rendering.
- Flexible Data Model: Integrates seamlessly with
Neo.data.StoreandNeo.data.Model. - Rich Column Types: Supports various column types, including component-based columns.
- Advanced Selection Models: Offers a variety of selection models for rows, cells, and columns.
- Sorting and Filtering: Built-in support for column sorting and data filtering, including header filters.
- Cell Editing: Built-in support for editing cell values directly within the grid.
- Customizable: Easily extendable and customizable to fit your needs.
Basic Grid Setup
Creating a grid is straightforward. You need a Neo.grid.Container, define your columns, and provide a store.
In this example, we create a simple grid with two columns. The dataField in each column configuration maps to a
field in the store's model. You can further customize the grid's behavior and appearance
by providing body and headerToolbarConfig.
Integrating with Stores
The store config is central to the Neo.grid.Container, as it provides the data to be displayed.
You have several flexible ways to define and provide a store:
1. Inline Store Configuration (Plain JavaScript Object)
This is the most common approach for simple grids, where the store's model and data are defined directly within the
grid's configuration. The grid automatically creates a Neo.data.Store instance from this object.
store: {
model: {
fields: [
{name: 'id', type: 'Number'},
{name: 'name', type: 'String'}
]
},
data: [
{id: 1, name: 'Item 1'},
{id: 2, name: 'Item 2'}
]
}
2. Store Class Reference
For more complex data handling or reusable store logic, you can define a separate Neo.data.Store class and provide a
reference to it. The grid will then instantiate this class.
import MyCustomStore from './MyCustomStore.mjs'; // Assuming MyCustomStore extends Neo.data.Store
// ...
store: MyCustomStore
3. Pre-created Store Instance
If you need to share a single store instance across multiple grids or manage its lifecycle externally, you can create the store instance beforehand and pass it directly to the grid.
import Store from '../data/Store.mjs';
const mySharedStore = Neo.create(Store, {
model: { /* ... */ },
data: [ /* ... */ ]
});
// ...
store: mySharedStore
Regardless of the method chosen, the grid's beforeSetStore hook (as seen in Neo.grid.Container.mjs) ensures that the
store property always resolves to a valid Neo.data.Store instance, providing a consistent and robust API.
4. Centralized Store Management with Neo.state.Provider
For more complex applications, Neo.state.Provider offers a powerful way to manage multiple stores centrally and share
them across various components using the binding system. This approach promotes better organization and reusability of
your data.
First, define your stores within a Neo.state.Provider's stores config:
import Provider from '../state/Provider.mjs';
import Store from '../data/Store.mjs';
class AppStateProvider extends Provider {
static config = {
className: 'AppStateProvider',
stores: {
users: {
module: Store,
model : {fields: ['id', 'name']},
data : [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]
},
products: {
module: Store,
model : {fields: ['id', 'item', 'price']},
data : [{id: 1, item: 'Laptop', price: 1200}, {id: 2, item: 'Mouse', price: 25}]
}
}
}
}
Neo.setupClass(AppStateProvider);
Then, in your GridContainer (or any other component), you can bind to these stores using the bind config. The
Neo.state.Provider will automatically inject the correct store instance.
This pattern is particularly beneficial for:
- Centralized State: All application-level stores are defined in one place, making them easy to locate and manage.
- Reusability: Stores can be easily shared and reused across different parts of your application without manual
- instantiation and passing.
- Decoupling: Components become more decoupled from direct store instantiation, relying instead on the state provider to inject the necessary data.
- Testability: Centralized stores can be more easily mocked or swapped for testing purposes.
Columns
Sorting
Neo.mjs grids provide built-in support for sorting data by one or more columns. Sorting is primarily managed by the
grid's underlying Neo.data.Store.
Enabling Sorting
To enable sorting for a column, ensure the sortable config is set to true on the Neo.grid.Container (which is its
default value). Then, simply click on a column header to sort the data by that column. Clicking again will reverse the
sort direction.
Initial Sorting
You can define an initial sort order for your store using the sorters config.
store: {
model: { /* ... */ },
data: [ /* ... */ ],
sorters: [{
property : 'name',
direction: 'ASC' // 'ASC' for ascending, 'DESC' for descending
}]
}
Programmatic Sorting
You can also sort the store programmatically using the sort method of the store instance.
myGrid.getStore().sort({
property : 'age',
direction: 'DESC'
});
Columns
Columns are the building blocks of a grid. You can configure them with various options.
Column Types
Neo.mjs provides several specialized column types:
Neo.grid.column.Base: The default column type.Neo.grid.column.Index: Displays the row number.Neo.grid.column.Component: Renders a Neo.mjs component inside each cell.Neo.grid.column.Currency: For formatting currency values.Neo.grid.column.AnimatedChange: Animates cell value changes.Neo.grid.column.AnimatedCurrency: Animates currency cell value changes.Neo.grid.column.Progress: Renders a progress bar component.
You can specify the column type using the type config:
{
type: 'index',
text: '#'
}
Custom Rendering
For more complex cell content, you can use a renderer function. The renderer receives an object with details
about the cell, record, and store.
{
text : 'Full Name',
renderer: data => `${data.record.firstname} ${data.record.lastname}`
}
Nested Record Fields
The grid supports displaying data from nested objects within your records. Simply use a dot-separated path for the
dataField config.
Given a record like:
{
"id": 1,
"user": {
"firstname": "John",
"lastname" : "Doe"
}
}
You can define your columns like this:
columns: [
{text: 'Firstname', dataField: 'user.firstname'},
{text: 'Lastname', dataField: 'user.lastname'}
]
The examples/grid/nestedRecordFields example provides a live demonstration of this feature.
Header Filters
Grids can include header filters, allowing users to filter data directly from the column headers. To enable this feature,
set the showHeaderFilters config to true on the Neo.grid.Container.
For a column to be filterable, you must also set its filterable config to true. The grid automatically provides a
text input filter for string fields and a number input filter for number fields. More complex filter types can be
implemented by extending Neo.grid.header.Filter.
Sorting
Neo.mjs grids provide built-in support for sorting data by one or more columns. Sorting is primarily managed by the
grid's underlying Neo.data.Store.
Enabling Sorting
To enable sorting for a column, ensure the sortable config is set to true on the Neo.grid.Container (which is its
default value). Then, simply click on a column header to sort the data by that column. Clicking again will reverse the
sort direction.
Initial Sorting
You can define an initial sort order for your store using the sorters config.
store: {
model: { /* ... */ },
data: [ /* ... */ ],
sorters: [{
property : 'name',
direction: 'ASC' // 'ASC' for ascending, 'DESC' for descending
}]
}
Programmatic Sorting
You can also sort the store programmatically using one of the following 2 options:
// Option 1: Directly modifying the existing sorter
myGrid.getSorter('name').direction = 'DESC';
// Option 2: Assigning a new value to the sorters config
myGrid.sorters = [{
property : 'name',
direction: 'DESC'
}];
Filtering
Beyond header filters, you can programmatically filter the grid's data using the filters config on the store.
This allows for more complex filtering logic and dynamic updates.
Applying Filters
To apply filters, set the filters config on your store. This config accepts an array of filter objects.
Each filter object typically has:
property: The data field to filter on.operator: The comparison operator (e.g.,'=','>','<=','like').value: The value to compare against.
Clearing Filters
To clear all filters, simply set the filters config to null or an empty array.
myGrid.getStore().filters = null;
// or
myGrid.getStore().filters = [];
Adding Filters Programmatically
You can dynamically add or modify filters by getting the current filters, adding new ones, and then setting the filters
config again.
const store = myGrid.getStore();
const currentFilters = store.filters ? [...store.filters] : [];
currentFilters.push({
property: 'product',
operator: 'like',
value : 'o' // Filter products containing 'o'
});
store.filters = currentFilters;
Plugins
Neo.mjs grids support various plugins to extend their functionality. Plugins are typically enabled by setting a
configuration property on the Neo.grid.Container or Neo.grid.Body.
Cell Editing
Enable cell editing by setting the cellEditing config to true on the Neo.grid.Container.
const myGrid = Neo.create(GridContainer, {
cellEditing: true,
// ...
});
Animated Row Sorting
To animate row sorting, set the animatedRowSorting config to true on the Neo.grid.Body (via body).
const myGrid = Neo.create(GridContainer, {
body: {
animatedRowSorting: true
},
// ...
});
Selection Models
The grid's selection behavior is controlled by a selection model, which you can configure on the body.
Available selection models in Neo.selection.grid:
RowModel: Selects entire rows.CellModel: Selects individual cells.ColumnModel: Selects entire columns.- And combinations like
CellRowModel,CellColumnModel,CellColumnRowModel.
import {RowModel} from '../../../src/selection/grid/_export.mjs';
const myGrid = Neo.create(GridContainer, {
// ...
body: {
selectionModel: RowModel
}
});
Performance and Big Data
The grid is designed for exceptional performance, especially when dealing with large datasets. Its virtual rendering engine ensures that only the visible parts of the grid (rows and columns) are rendered in the DOM, significantly reducing memory consumption and improving rendering speed.
You### Optimizing Virtual Rendering
You can fine-tune the virtual rendering behavior with the bufferRowRange and bufferColumnRange configs in the
body. These settings define how many extra rows and columns to render outside the visible area to provide a
smoother scrolling experience.
const myGrid = Neo.create(GridContainer, {
body: {
bufferRowRange : 5, // Render 5 extra rows above and below the visible area
bufferColumnRange: 2 // Render 2 extra columns to the left and right of the visible area
},
// ...
});
Row Height
The rowHeight config on the Neo.grid.Container plays a crucial role in the grid's rendering calculations. Ensuring
an accurate rowHeight is essential for the virtual rendering engine to correctly determine the number of visible rows
and the scrollable area. While the default value of 32px is often suitable, you should adjust it if your grid rows
have a different fixed height.
The examples/grid/bigData example showcases the grid's performance with a large dataset, allowing you to
dynamically adjust the number of rows and columns.