DataGrid is a Qooxdoo UI component that provides a virtual, scrolling, spreadsheet-like and tree-like control; the main features are:
- Virtual, unlimited data source - supports millions (or billions) of rows, loaded only as needed, on demand and asynchronously
- Rich columns - define columns which determine the name and presentation of data (eg a date column etc)
- Grid or Tree style - operates as a spreadsheet-like control of rows and columns of data, or as a tree of data where each node in the tree is row and can have multiple columns
- Fully stylable using Qooxdoo themes
NOTE:: This is beta release, but will be in active use and development over he next few months.
- The current version is read only, but it will be possible to add edit cells inline in the near future.
Included in this repo is a demo application - it's running live at http://qooxdoo.org/qxl-datagrid.qooxdoo.github.io/
The included demo application will create a tabview showing several styles of DataGrid usage; if you would like to try it out on your own computer, check out this repo and run it:
$ git clone https://github.com/qooxdoo/qxl.datagrid.git
$ cd qxl.datagrid
$ qx serve
and browse to http://localhost:8080
Currently the following demos are included:
- Array - displays a simple array of model objects using qxl.datagrid.source.ArrayDataSource
- Tree - displays the file system at
./source
using qxl.datagrid.source.tree.TreeDataSource - Big Grid - displays a large (10 billion entry) grid of data using a custom qxl.datagrid.source.IDataSource implementation
To create and use a DataGrid, you must:
- create a list of one or more columns - these will be instances of classes in
qxl.datagrid.column.*
- create an instance of
qxl.datagrid.DataGrid
and give it your list of columns - provide a DataSource (see below) for the DataGrid to get your data from
There are two styles of grid - spreadsheet-style and tree-style; which you have depends on the type of DataSource you choose
(although if you have a tree-style, you will probably want to use the special qxl.datagrid.column.tree.ExpansionColumn
as
your first column so that the user can click to expand/collapse parts of the tree).
For an example of a spreadsheet-like DataGrid, take a look at qxl.datagrid.demo.biggrid.BigGridDemo
For an example of a tree-like DataGrid, take a look at qxl.datagrid.demo.tree.TreeDemo
The DataGrid needs some custom appearances, decorators, and colours - these are defined in qxl.datagrid.theme.*
and can be included
into your own application's theme. For example, if you app is called myapp
, you might make this change to your Appearance:
qx.Theme.define("myapp.theme.Appearance", {
extend: qx.theme.tangible.Appearance,
include: [qxl.datagrid.theme.tangible.MAppearance]
});
Currently, the following themes are supported:
- Tangible (light & dark) - use
qxl.datagrid.theme.tangible.*
mixins - Indigo & Indigo Dark - use
qxl.datagrid.theme.indigo.*
mixins - Simple - use
qxl.datagrid.theme.simple.*
mixins
For each theme, there are three available mixins; *.MAppearance
, *.MColor
, and *.MDecoration
.
A key concept to get to grips with is the DataSource, which provides your data to the DataGrid in a two dimensional array; this data is expected to be fetched asynchronously and on demand, which means that you can have truely massive amounts of data stored on a slow server somewhere, and only spend time loading those parts of the data that are visible to the user. The DataSource is expected to cache the data which is on display so that it can be obtained immediately and synchronously - although whether the DataSource chooses to cache data which is not on display is entirely up to the implementation of the DataSource.
To create a DataSource, you have to write your own implementation of qxl.datagrid.source.IDataSource
- but that's quite
straightforward and you can see an example in qxl.datagrid.demo.biggrid.DummyDataSource.
The TL;DR is that you provide an asynchronous method makeAvailable
which will make sure that there is the required
range of data available; isAvailable
is synchronous and tests whether a range is already available; and then methods
to say how big the data is and get values from your array.
One crucial point to note is that although makeAvailable
is called with a range of columns & rows, your DataSource
MUST always also provide a model for column 0 (zero) for each row in that range - this is to allow selection by row.
Array data sources use a simple one-dimensional array of model objects, with each model in the array representing one row of data. This same model is then passed to each column of the corresponding row in the DataGrid, satisfying the requirement for two-dimensional data. Each column will display some property of that object as configured, allowing for a simplified list-like or table-like presentation.
For most use cases, it will be sufficient to instantiate qxl.datagrid.source.ArrayDataSource and
set it's model
property to your array of model objects. However some cases with larger datasets may prefer to make
trips to the server; typically this can be handled by extending qxl.datagrid.source.ArrayDataSource and overriding
the IsAvailable
, makeAvailable
, and getSize
members.
For the former use case, the implementation is quite simple. Below is a snippet containing a very basic example of how to use an ArrayDataSource with a DataGrid - bear in mind this snippet is intended to be illustrative, not effective.
const myColumns = new qxl.datagrid.column.Columns(); // create a new columns object
myColumns.add(/* ... */); // add columns to the columns object as required
const myGrid = new qxl.datagrid.DataGrid(myColumns); // create a new datagrid
const myDataSource = new qxl.datagrid.source.ArrayDataSource(); // create a new array data source
myGrid.set({ dataSource: myDataSource }); // set the data source on the grid
const myDataArray = myapp.model.MyModel.getAll(); // generate/fetch some `qx.data.Array` of model objects
myDataSource.setModel(myDataArray); // set the model on the data source
// Done!
For a more purpose-built example, take a look at qxl.datagrid.demo.array.ArrayDemo
For a spreadsheet-style datagrid, your data is already in a two-dimensional array, but tree-style datagrid has a heirarchy that needs to be navigated; the DataGrid still needs the data to be provided as a two-dimensional array, so the datasource needs to be able to manipulate the tree data into that two-dimensional array.
For most use cases, the qxl.datagrid.source.tree.TreeDataSource
data source will do all the hard work for you, provided that
you have a class that has some kind of children
property - qxl.datagrid.source.tree.NodeInspector
can provide all the
information that the TreeDataSource needs to navigate most structures.
However, you may need to derive from the NodeInspector
to fine tune the presentation.
When using a spreadsheet-style DataGrid, then every cell can have it's own model object; this is easy to implement, and the
example DummyDataSource
uses this concept. It is not always necessary (or practical) however to go down to this much
detail - the other possibility is that you have one model object per row, and use bindings to choose the values which are
displayed in each column for that row.
The TreeDataSource enforces exactly this kind of "one model per row" style of data.
Every datagrid has at least one column, each of which is derived from qxl.datagrid.column.Column
and controls the heading,
the sizing, the binding, and display of individual cells.
The column is able to choose how to display and bind the widgets, so you can easily provide a uniform experience for the whole grid.
The DataGrid display is divided into a header area and a "widget pane" - the header has a widgets for each of the column headers, and the widget pane has widgets for each cell in the spreadsheet.
When adding data to the header and the pane, these widgets are created via an instance of qxl.datagrid.ui.factory.IWidgetFactory
-
typically, you can use the defaults which are qxl.datagrid.ui.factory.HeaderWidgetFactory
for headers and
qxl.datagrid.ui.factory.SimpleWidgetFactory
for the widget pane
The DataGrid has multiple layers of widgets, one for the widgets in the pane itself (ie the widgets for each of the cells) and also a layer of row widgets - this is how the rows backgrounds are styled. When you customise the appearance of cells, leave the background transparent unless you are specifically applying indicators such as coloring to show selection.