-
Notifications
You must be signed in to change notification settings - Fork 144
Data Model API
Hypergrid 3 data models have a minimal required interface, as outlined below.
The minimum interface is an object with three methods, getRowCount()
, getSchema()
, and getValue(x, y)
.
Data model interface requirements fall into the following categories
- Required API methods
- Optional API methods with default implementations
- Optional API without default implementations
- Proposed API
- Utility methods
- Error object
These methods are required to be implemented by all data models:
Click the links for API details:
The following API methods are optional. When the data model an application is using does not implement these natively, the applicaiton can implement them in a subclass of that data model.
Failing this, Hypergrid injects "fallbacks" (default implementations) into the data model for all missing methods. Some of these merely fail silently, but most do something useful (though not necessarily smart or efficient).
Another option for the application is to override these injected default implementations at run-time by assigning new definitions. This is functionally equivalent to subclassing the data model (as recommended above). That approach, however, can sometimes feel a bit heavy when, for example, the need is to override just a single method (such as getCell
).
Click the links for API details:
-
apply
(default implementation fails silently) -
click
(default implementation fails silently) -
getCell
(overridable) -
getCellEditorAt
(overridable) getColumnCount
getData
getMetadataStore
getRowIndex
getRow
getRowMetadata
-
isDrillDown
(default implementation fails silently) setMetadataStore
setRowMetadata
Although all methods are technically overridable, the two labeled as such above, getCell
and getCellEditorAt
, are rarely implemented in a data model and are more typically overridden at run-time. These are hooks called by Hypergrid at cell render time and cell edit time, respectively. The concerns of these methods have much less to do with the data model than they have to do with application logic. Nevertheless, they are historically situated on the data model, and are called with the data model as their execution context.
The following API methods are optional. Hypergrid does not inject fallbacks for these. They are only called by Hypergrid under certain circumstances, as noted below.
If your application wants to call the following methods directly, you must implement them.
Click the links for API details:
-
setData
Called by Hypergrid when the application specifies thedata
option.* -
setSchema
Called by Hypergrid when the application specifies theschema
option.* (Invoking thebehavior.schema
setter also callssetSchema
.) -
setValue
Called by Hypergrid when the user edits a cell. To prevent Hypergrid from calling this method, make all cells non-editable (grid.properties.editable = false
).
* These options are accepted by the Hypergrid()
constructor, grid.setData()
, and behavior.setData()
.
The following API methods implement lazy loading and are optional. For performance reasons, to avoid computing the calling arguments, Hypergrid checks for implementation before calling these.
-
fetchData
Called by Hypergrid when implemented with a list of cell regions required by the next render and a callback to tell Hypergrid when the data has arrived. -
gotData
Called by Hypergrid when implemented with a list of cell regions required by the next render. Checks to see if the requested data are available. This method is needed because due to latency issues, fetches may overlap, finishing in a different order in which they were called.
The following API methods add/remove/modify rows. These methods are not called by Hypergrid. This API is therefore just a suggestion. Click the links for proposed API details:
The following subclass of Error
might be implemented by data models to use when they need to throw an error:
This helps identify the error as coming from the data model and not from Hypergrid (which uses its own HypergridError
) or the application.
Sample code for creating a DataError
object constructor:
// Create a new class
function DataError(message) {
this.message = message;
}
// Let it be a subclass of `Error'
DataError.prototype = Object.create(Error.prototype);
// Set the display name
DataError.prototype.name = 'DataError';
// Add to data model
MyDataModel.prototype.DataError = DataError;
Hypergrid listens for the following events, which may be triggered from a data model using the dispatchEvent
method injected into data models by Hypergrid.
On receipt, Hypergrid performs some internal actions before triggering grid event (actually on the grid's canvas element) with a similar event string (but with the addition of a fin-
prefix). So for example, on receipt of the data-changed
event from the data model, Hypergrid triggers fin-data-changed
on the grid, which applications can listen for using grid.addEventListener('fin-data-changed', handlerFunction)
.
- fin-hypergrid-schema-changed
- fin-hypergrid-data-changed
- fin-hypergrid-data-shape-changed
- fin-hypergrid-data-prereindex
- fin-hypergrid-data-postreindex
The following is a custom data model with its own data and a minimum implementation.
var data = [
{ symbol: 'APPL', name: 'Apple Inc.', prevclose: 93.13 },
{ symbol: 'MSFT', name: 'Microsoft Corporation', prevclose: 51.91 },
{ symbol: 'TSLA', name: 'Tesla Motors Inc.', prevclose: 196.40 },
{ symbol: 'IBM', name: 'International Business Machines Corp', prevclose: 155.35 }
];
var schema = ['symbol', 'name', 'prevclose']; // or: `Object.keys(data)` although order not guaranteed
dataModel = {
getSchema: function() {
if (!this.schema) {
this.schema = schema;
this.dispatchEvent('data-schema-changed');
}
return this.schema;
},
getValue: function(x, y) {
return data[y][this.schema[x].name];
},
getRowCount: function() {
return data.length;
}
};
This simple example is a hard-coded plain object namespace.
Although not a requirement, in practice data models are more typically class instances, making them a little bit more complicated than the above, but as they are not hard-coded, they're a lot more flexible.
Furthermore, data model classes typically are subclasses of DatasaurBase
(also not a requirement).
Subclassing DatasaurBase
provides the following:
- Supports flat or concatenated (aka stacked) data model structures
- Implements utility methods (see below)
- Implements
DataError
.
(For more on subclassing, see the next section.)
If your data model does not subclass DatasaurBase
and…
- …does not implement
install
:- Hypergrid injects a rudimentary
install
method
- Hypergrid injects a rudimentary
- …does not implement
addListener
:- Hypergrid injects the default methods
addListener
,removeListener
,removeAllListeners
for external use anddispatchEvent
for internal use.
- Hypergrid injects the default methods
Hypergrid then proceeds normally, calling install
to install the unimplemented method fallbacks.
JavaScript doesn't have real classes, which are creatures of compiled languages, involving compile-time declarations with compile-time semantics.
The phrase "subclass of" actually means "extending from" and refers to JavaScript prototypal inheritance, which all happens at run-time. To extend your data model from DatasaurBase
simply means that that DatasaurBase.prototype
is at the end of the data model's prototype chain (where "end" actually means second from the actual end, which is Object
). For simple data models, it usually becomes the data model's prototype's prototype. In more complex data models, there may be additional prototypes in between, but DatasaurBase
will still be at the "end."
There are many ways to "extend" a "class" in JavaScript. For instance, to make the plain object in the example above a subclass of DatasaurBase
:
Object.setPrototypeOf(dataModel, DatasaurBase.prototype);
Or, for a hypothetical DataModel
constructor:
Obect.setPrototypeOf(DataModel.prototype, DatasaurBase.prototype);
But the usual practice when working with Hypergrid data models is to use DatasaurBase.extend
(functionally similar to Backbone.Model.extend
.
For example, DatasaurLocal
, the default data model that comes with Hypergrid, does this.
Notes regarding extend
:
- Constructors extended in this way also get the
extend
shared method so they themselves can be subclassed. - All constructor code goes in a special method called
initialize
. - When a subclass is instantiated, all
initialize
functions are called in sequence, starting with the most senior prorotype's first.