Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Data Model API · fin-hypergrid/core Wiki · GitHub
Skip to content

Data Model API

Jonathan Eiten edited this page Jun 5, 2018 · 3 revisions

Hypergrid 3 Data Model API

Hypergrid 3 data models have a minimal required interface, as outlined below.

TL;DR

The minimum interface is an object with three methods, getRowCount(), getSchema(), and getValue(x, y).

Interface

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

Required API methods

These methods are required to be implemented by all data models:

Click the links for API details:

Optional API methods with default implementations

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:

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.

Optional API without default implementations

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.

Optional API called conditionally

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 the data option.*
  • setSchema
    Called by Hypergrid when the application specifies the schema option.* (Invoking the behavior.schema setter also calls setSchema.)
  • 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().

Optional Lazy loading API

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.

Supplemental API

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:

Utility methods

DataError object

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 classfunctionDataError(message){this.message=message;}// Let it be a subclass of `Error'DataError.prototype=Object.create(Error.prototype);// Set the display nameDataError.prototype.name='DataError';// Add to data modelMyDataModel.prototype.DataError=DataError;

Data Model Events

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).

Example

The following is a custom data model with its own data and a minimum implementation.

vardata=[{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}];varschema=['symbol','name','prevclose'];// or: `Object.keys(data)` although order not guaranteeddataModel={getSchema: function(){if(!this.schema){this.schema=schema;this.dispatchEvent('data-schema-changed');}returnthis.schema;},getValue: function(x,y){returndata[y][this.schema[x].name];},getRowCount: function(){returndata.length;}};

This simple example is a hard-coded plain object namespace.

Data model base class

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
  • …does not implement addListener:
    • Hypergrid injects the default methods addListener, removeListener, removeAllListeners for external use and dispatchEvent for internal use.

Hypergrid then proceeds normally, calling install to install the unimplemented method fallbacks.

Subclasses in JavaScript

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:

  1. Constructors extended in this way also get the extend shared method so they themselves can be subclassed.
  2. All constructor code goes in a special method called initialize.
  3. When a subclass is instantiated, all initialize functions are called in sequence, starting with the most senior prorotype's first.

Clone this wiki locally