182 lines
5.7 KiB
JavaScript
182 lines
5.7 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @classdesc Concatenated data model base class.
|
|
* @param {Datasaur} [next] - Omit for origin (actual data source). Otherwise, point to source you are transforming.
|
|
* @param {object} [options] - Not used here at this time. Define properties as needed for custom datasaurs.
|
|
*/
|
|
var DatasaurBase = require('extend-me').Base.extend('DatasaurBase', {
|
|
isNullObject: true,
|
|
|
|
drillDownCharMap: {
|
|
true: '\u25bc', // BLACK DOWN-POINTING TRIANGLE aka '▼'
|
|
false: '\u25b6', // BLACK RIGHT-POINTING TRIANGLE aka '▶'
|
|
undefined: '', // leaf rows have no control glyph
|
|
null: ' ' // indent
|
|
},
|
|
|
|
DataError: DataError,
|
|
|
|
initialize: function(next, options) {
|
|
if (next) {
|
|
this.handlers = next.handlers;
|
|
this.next = next;
|
|
while (next) {
|
|
this.source = next;
|
|
next = next.next;
|
|
}
|
|
} else {
|
|
this.handlers = [];
|
|
this.source = this;
|
|
}
|
|
|
|
this.install(Object.getPrototypeOf(this));
|
|
},
|
|
|
|
/**
|
|
* @implements dataModelAPI#install
|
|
* @see {@link https://fin-hypergrid.github.io/core/doc/dataModelAPI.html#install|install}
|
|
*/
|
|
install: function(api, options) {
|
|
options = options || {};
|
|
|
|
var dataModel = this,
|
|
keys = getFilteredKeys(api),
|
|
injectable = options.inject && !Array.isArray(api);
|
|
|
|
keys.forEach(function(key) {
|
|
if (injectable) {
|
|
if (!findMethod(dataModel, key, options.force)) {
|
|
this.source[key] = api[key];
|
|
}
|
|
}
|
|
|
|
if (!DatasaurBase.prototype[key]) {
|
|
DatasaurBase.prototype[key] = function() {
|
|
if (this.next) {
|
|
return this.next[key].apply(this.next, arguments);
|
|
}
|
|
};
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
dispatchEvent: function(nameOrEvent) {
|
|
this.handlers.forEach(function(handler) {
|
|
handler.call(this, nameOrEvent);
|
|
}, this);
|
|
},
|
|
|
|
addListener: function(handler) {
|
|
if (this.handlers.indexOf(handler) < 0) {
|
|
this.handlers.push(handler);
|
|
}
|
|
},
|
|
|
|
removeListener: function(handler) {
|
|
var index = this.handlers.indexOf(handler);
|
|
if (index >= 0) {
|
|
delete this.handlers[index];
|
|
}
|
|
},
|
|
|
|
removeAllListeners: function() {
|
|
this.handlers.length = 0;
|
|
},
|
|
|
|
|
|
// DEBUGGING AIDS
|
|
|
|
dump: function(max) {
|
|
max = Math.min(this.getRowCount(), max || Math.max(100, this.getRowCount()));
|
|
var data = [];
|
|
var schema = this.getSchema();
|
|
var fields = schema ? schema.map(function(cs) { return cs.name; }) : this.getHeaders();
|
|
var cCount = this.getColumnCount();
|
|
var viewMakesSense = this.viewMakesSense;
|
|
for (var r = 0; r < max; r++) {
|
|
var row = {};
|
|
for (var c = 0; c < cCount; c++) {
|
|
var val = this.getValue(c, r);
|
|
if (c === 0 && viewMakesSense) {
|
|
val = this.fixIndentForTableDisplay(val);
|
|
}
|
|
row[fields[c]] = val;
|
|
}
|
|
data[r] = row;
|
|
}
|
|
console.table(data);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Searches linked list of objects for implementation of property `key` anywhere on their prototype chain.
|
|
* The search excludes members of `DatasaurBase.prototype` (previously installed forwarding catchers).
|
|
* @param {object} transformer - Data model transformer list, linked backwards one to the previous one by `next` property.
|
|
* The first transformer, the actual data source, has null `next`, meaning start-of-list.
|
|
* @param {string} key - Property to search for.
|
|
* @param {boolean} [remove] - Delete all implementations along prototype chains of all transformers and return falsy.
|
|
* @returns {undefined|function} - Found method implementation or `undefined` if not found.
|
|
*/
|
|
function findMethod(transformer, key, remove) {
|
|
do {
|
|
if (transformer[key]) {
|
|
if (remove) {
|
|
for (var link = transformer; link && link !== Object.prototype; link = Object.getPrototypeOf(link)) {
|
|
delete link[key];
|
|
}
|
|
} else if (transformer[key] !== DatasaurBase.prototype[key]) {
|
|
return transformer[key];
|
|
}
|
|
}
|
|
transformer = transformer.next;
|
|
} while (transformer);
|
|
}
|
|
|
|
var blacklistAlways = ['constructor', 'initialize', '!keys', '!!keys'];
|
|
|
|
/**
|
|
* The following keys (array elements or object keys) are filtered out:
|
|
* * Defined as something other than a function, including an accessor (getter and/or setter)
|
|
* * Keys missing from whitelist (not listed in string array `api['!!keys']`, when defined)
|
|
* * Keys blacklisted (listed in string array `api['!keys']` or `blacklistAlways`)
|
|
* @param {string[]|object} api
|
|
* @returns {string[]}
|
|
*/
|
|
function getFilteredKeys(api) {
|
|
var whitelist = api.hasOwnProperty('!!keys') && api['!!keys'],
|
|
blacklist = blacklistAlways.concat(api.hasOwnProperty('!keys') && api['!keys'] || []),
|
|
keys;
|
|
|
|
if (Array.isArray(api)) {
|
|
keys = api;
|
|
} else {
|
|
keys = Object.keys(api).filter(function(key) {
|
|
return typeof Object.getOwnPropertyDescriptor(api, key).value === 'function';
|
|
});
|
|
}
|
|
|
|
return keys.filter(function(key) {
|
|
return !(
|
|
whitelist && whitelist.indexOf(key) < 0 ||
|
|
blacklist.indexOf(key) >= 0
|
|
);
|
|
});
|
|
}
|
|
|
|
|
|
// DataError
|
|
|
|
function DataError(message) {
|
|
this.message = message;
|
|
}
|
|
|
|
// extend from `Error'
|
|
DataError.prototype = Object.create(Error.prototype);
|
|
|
|
// override error name displayed in console
|
|
DataError.prototype.name = 'DataError';
|
|
|
|
|
|
module.exports = DatasaurBase;
|