welcome to Windoz
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* @author Nicolas Stein
|
||||
* @category MyEic
|
||||
* @subcategory Libraries
|
||||
* @extends WindozModel
|
||||
*/
|
||||
class WindozPluralModel extends WindozModel {
|
||||
|
||||
constructor(businessObject, privileges, singletonClass) {
|
||||
super(businessObject, privileges);
|
||||
|
||||
this.collection = [];
|
||||
//this.api = app.config.api.filter(item => item.businessObject == businessObject);
|
||||
this.singletonClass = singletonClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all ids in the current collection
|
||||
* @return {Array} Ids (keys) of the current collection.
|
||||
*/
|
||||
get ids() { return this.collection.map(item => item.itemData.id); }
|
||||
|
||||
|
||||
/**
|
||||
* Creates empty singletons at the end of the collection.
|
||||
* @param {Array} ids - Ids of the singletons to create
|
||||
*/
|
||||
create(ids){
|
||||
for(let id of ids){
|
||||
this.collection[id] =new this.singletonClass;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the data in a singleton & adds it to the collection.
|
||||
* If the id exists, will just override keys from the server (preserving eventual overrides)
|
||||
* @param {Object} data - key-value data used to create the singleton.
|
||||
* @return {Object} The collection length after insertion.
|
||||
*/
|
||||
add(data, id=null){
|
||||
id = data.id || null;
|
||||
let item = this.collection.find(item => item.id == id);
|
||||
if(item){
|
||||
Object.assign(item.itemData, data);
|
||||
} else {
|
||||
item = new this.singletonClass();
|
||||
item.itemData = data;
|
||||
this.collection.push(item);
|
||||
}
|
||||
return(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and returns a singleton out of the collection.
|
||||
* @param {string} id - id of the singleton to pop.
|
||||
* @return {Object} The popped singleton.
|
||||
*/
|
||||
pop(id) {
|
||||
|
||||
if(id in this.collection) {
|
||||
let singleton = this.collection[id];
|
||||
this.delete(this.collection[id]);
|
||||
return(singleton);
|
||||
} else return(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of singletons in the collection.
|
||||
* @return {Int} Number of singletons.
|
||||
*/
|
||||
get length() { return this.collection.length; }
|
||||
/**
|
||||
* Empties the collection
|
||||
*/
|
||||
empty() { this.collection = []; }
|
||||
|
||||
/**
|
||||
* Search the collection for matching singletons
|
||||
* @param {Object} matchValues - { {string} key: {string} valueToMatch }
|
||||
* @param {Object} matchExact - { strin{key}: {Boolean} } Defaults to exact match if parameter absent or if key absent
|
||||
* @return {Array} Array of matching singletons.
|
||||
*/
|
||||
localFind(matchValues, matchExact){
|
||||
return(
|
||||
this.collection.filter((singleton) => singleton.match(matchValues, matchExact))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads singletons from their IDs at the end of the current collection.
|
||||
* if no ids given, loads all
|
||||
* Expected server-contract:
|
||||
* [ {"id":"xxx", ...other singleton data...}, {"id":"yyy", ...other singleton data...}, ...]
|
||||
* @param {array} ids - singleton ids to be loaded
|
||||
* @return {promise} Promise returning the model
|
||||
*/
|
||||
load(endpoint, payload){
|
||||
return(
|
||||
this.request(endpoint.uri, endpoint.method, payload)
|
||||
.then(async serverData => {
|
||||
this.fill(serverData);
|
||||
return(this);
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fill(serverData) {
|
||||
if(serverData && serverData.length){
|
||||
for(let singletonData of serverData) this.add(this.sanitize(singletonData));
|
||||
}
|
||||
}
|
||||
|
||||
sanitize(item) { return item; }
|
||||
|
||||
/**
|
||||
* Loads singletons from search criteria at the end of the current collection.
|
||||
* @param {Object} matchValues - { {string} key: {string} valueToMatch }
|
||||
* @param {Object} matchExact - { strin{key}: {Boolean} } Defaults to exact match if parameter absent or if key absent
|
||||
* @return {Array} Array of matching singletons.
|
||||
*/
|
||||
remotefind(matchValues, matchExact){
|
||||
return(
|
||||
this.post(this.baseUrl+'search', { 'matchValues': matchValues, 'matchExact': matchExact })
|
||||
.then(async serverData => {
|
||||
if(serverData){
|
||||
for(let singletonData of serverData) this.add(singletonData);
|
||||
}
|
||||
return(this);
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
save(){
|
||||
return(
|
||||
this.put(this.baseUrl, this.collection)
|
||||
.then(async serverData => {
|
||||
if(serverData){
|
||||
for(let singletonData of data) this.add(singletonData);
|
||||
}
|
||||
return(this);
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fills the collection from a raw CSV string.
|
||||
* (adapted from https://stackoverflow.com/users/10549827/matthew-e-brown)
|
||||
* @param {string} csv The raw CSV string.
|
||||
* @param {string[]} headers An optional array of headers to use. If none are
|
||||
* given, they are pulled from the first line of `text`.
|
||||
* @param {string} idName The name of the column to be used as id.
|
||||
* @param {string} quoteChar A character to use as the encapsulating character.
|
||||
* @param {string} delimiter A character to use between columns.
|
||||
*/
|
||||
//TODO NIKE: test ! & understand why in accumulator, merge uses [key] instead of key
|
||||
fromCsv(csv, headers = null, idName='id', quoteChar = '"', delimiter = ',') {
|
||||
const regex = new RegExp(`\\s*(${quoteChar})?(.*?)\\1\\s*(?:${delimiter}|$)`, 'gs');
|
||||
const match = line => [...line.matchAll(regex)]
|
||||
.map(m => m[2]) // we only want the second capture group
|
||||
.slice(0, -1); // cut off blank match at the end
|
||||
|
||||
const lines = csv.split('\n');
|
||||
const firstLine = lines.shift();
|
||||
const heads = headers || match(firstLine);
|
||||
if(heads.indexOf(idName)<0) {
|
||||
console.warn(`id column "${idName}" not found in CSV headers !`);
|
||||
return;
|
||||
}
|
||||
heads[heads.indexOf(idName)]='id'; let cols;
|
||||
for(let line of lines){
|
||||
cols = match(line);
|
||||
if(cols.length>=heads.length){
|
||||
let singletonData = cols.reduce((acc, cur, i) => {
|
||||
// Attempt to parse as a number; replace blank matches with `null`
|
||||
const val = cur.length <= 0 ? null : Number(cur) || cur;
|
||||
const key = heads[i] [[]] `extra_${i}`;
|
||||
return { ...acc, [key]: val };
|
||||
}, {});
|
||||
this.add(singletonData);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a CSV in a string from the collection.
|
||||
* @param {Array} keys model properties you want to include in the csv. It defaults to the least common denominator of properties across the collection.
|
||||
* @param {boolean} withHeaders If true, the first line has the properties names.
|
||||
* @param {string} quoteChar Will surround all values (and properties if withHeader).
|
||||
* @param {string} delimiter A character to use between columns.
|
||||
*/
|
||||
//TODO NIKE: test !
|
||||
toCsv(keys=null, withHeader=true, quoteChar = '"', delimiter = ';', downloadName=null){
|
||||
const _dwnldCsv = function(data, fname) {
|
||||
const blob = new Blob([data], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', url);
|
||||
a.setAttribute('download', fname+'.csv');
|
||||
a.click();
|
||||
}
|
||||
if(!keys) { // take min. common set of keys in collection (take 1st then successively intersect to itself)
|
||||
let ids = Object.keys(this.collection);
|
||||
keys = Object.keys(this.collection[ids[0]].itemData);
|
||||
for(let id of ids) {
|
||||
keys = keys.filter(x => (this.collection[id].itemData.hasOwnProperty(x)))
|
||||
}
|
||||
}
|
||||
let csv=''; let row=[];
|
||||
if(withHeader===true){
|
||||
for(let key of keys){
|
||||
row.push(quoteChar+key+quoteChar);
|
||||
}
|
||||
csv += row.join(delimiter)+'\n';
|
||||
} else if(Array.isArray(withHeader)) {
|
||||
for(let title of withHeader){
|
||||
row.push(quoteChar+title+quoteChar);
|
||||
}
|
||||
csv += row.join(delimiter)+'\n';
|
||||
}
|
||||
for(let id in this.collection){
|
||||
row = [];
|
||||
for(let key of keys){
|
||||
let item = ''
|
||||
if(typeof(key)=='string'){ // normal column
|
||||
item = this.collection[id].itemData
|
||||
for(let k of key.split('.')) { // Allow for sub-objects with dotted keys notation
|
||||
if(!item) break
|
||||
item = item[k]
|
||||
}
|
||||
} else if(typeof(key)=='function') { // computed column
|
||||
item = key(this.collection[id].itemData)
|
||||
} else console.warn('CSV: Bad column: ',key)
|
||||
row.push(quoteChar+item+quoteChar);
|
||||
}
|
||||
csv += row.join(delimiter)+'\n';
|
||||
}
|
||||
if(downloadName) _dwnldCsv(csv, downloadName)
|
||||
return(csv);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
app.registerClass('WindozPluralModel',WindozPluralModel);
|
||||
Reference in New Issue
Block a user