Files
OpenNoodl/packages/noodl-runtime/src/collection.js
Michael Cartner b9c60b07dc Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com>
Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com>
Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com>
Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com>
Co-Authored-By: Johan  <4934465+joolsus@users.noreply.github.com>
Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com>
Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
2024-01-26 11:52:55 +01:00

465 lines
12 KiB
JavaScript

"use strict";
var Model = require("./model");
// Get and set proxy
/*const proxies = {}
const _collectionProxyHandler = {
get: function(target,prop,receiver) {
if(typeof target[prop] === 'function')
return target[prop].bind(target);
else if(prop === 'length')
return target.size()
else if(target.items[prop] !== undefined)
return target.get(prop)
else
return Reflect.get(target,prop,receiver)
},
set: function(obj,prop,value) {
if(prop === 'id') {
console.log(`Noodl.Object warning: id is readonly (Id is ${obj.id}, trying to set to ${value})`);
return true; //if a proxy doesn't return true an exception will be thrown
}
else
return Reflect.set(target,prop,receiver)
}
}
function Collection(id) {
this.id = id;
this.items = [];
}
var collections = Collection._collections = {};
Collection.create = function(items) {
const name = Model.guid();
collections[name] = new Collection(name);
if(items) {
collections[name].set(items);
}
return collections[name];
}
Collection.get = function(name) {
if(name === undefined) name = Model.guid();
if(!collections[name]) {
collections[name] = new Collection(name);
proxies[name] = new Proxy(collections[name],_collectionProxyHandler);
}
return proxies[name];
}
Collection.instanceOf = function(collection) {
return collection && (collection instanceof Collection || collection.target instanceof Collection);
}
Collection.exists = function(name) {
return collections[name] !== undefined;
}
Collection.prototype.getId = function() {
return this.id;
}
Collection.prototype.on = function(event,listener) {
if(!this.listeners) this.listeners = {};
if(!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(listener);
}
Collection.prototype.off = function(event,listener) {
if(!this.listeners) return;
if(!this.listeners[event]) return;
var idx = this.listeners[event].indexOf(listener);
if(idx!==-1) this.listeners[event].splice(idx,1);
}
Collection.prototype.notify = async function(event,args) {
if(!this.listeners) return;
if(!this.listeners[event]) return;
var l = this.listeners[event].slice(); //clone in case listeners array is modified in the callbacks
for(var i = 0; i < l.length; i++) {
await l[i](args);
}
}
Collection.prototype.set = function(src) {
var length,i;
if(src === this) return;
function keyIndex(a) {
var keys = {};
var length = a.length;
for(var i = 0; i < length; i++) {
var item = a[i];
keys[item.getId()] = item;
}
return keys;
}
// Src can be a collection, or an array
var src = Collection.instanceOf(src)?src.items:src;
var bItems = [];
length = src.length;
for(i = 0; i < length; i++) {
var item = src[i];
if(Model.instanceOf(item))
bItems.push(item);
else
bItems.push(Model.create(item));
}
var aItems = this.items;
var aKeys = keyIndex(aItems);
var bKeys = keyIndex(bItems);
// First remove all items not in the new collection
length = aItems.length;
for(i = 0; i < length; i++) {
if(!bKeys.hasOwnProperty(aItems[i].getId())) {
// This item is not present in new collection, remove it
this.removeAtIndex(i);
i--;
length--;
}
}
// Reorder items
for(i = 0; i < Math.min(aItems.length,bItems.length); i++) {
if(aItems[i] !== bItems[i]) {
if(aKeys.hasOwnProperty(bItems[i].getId())) {
// The bItem exist in the collection but is in the wrong place
this.remove(bItems[i]);
}
// This is a new item, add it at correct index
this.addAtIndex(bItems[i],i);
}
}
// Add remaining items
for(i = aItems.length; i < bItems.length; i++) {
this.add(bItems[i]);
}
}
Collection.prototype.contains = function(item) {
return this.items.indexOf(item)!==-1;
}
Collection.prototype.add = async function(item) {
if(this.contains(item)) return; // Already contains item
this.items.push(item);
await this.notify('add',{item:item,index:this.items.length-1});
await this.notify('change');
await item.notify('add',{collection:this});
}
Collection.prototype.addAtIndex = async function(item,index) {
if(this.contains(item)) return; // Already contains item
this.items.splice(index,0,item);
await this.notify('add',{item:item,index:index});
await this.notify('change');
await item.notify('add',{collection:this,index:index});
}
Collection.prototype.removeAtIndex = async function(idx) {
var item = this.items[idx];
this.items.splice(idx,1);
await this.notify('remove',{item:item,index:idx});
await this.notify('change');
await item.notify('remove',{collection:this});
}
Collection.prototype.remove = function(item) {
var idx = this.items.indexOf(item);
if(idx !== -1) this.removeAtIndex(idx);
}
Collection.prototype.size = function() {
return this.items.length;
}
Collection.prototype.get = function(index) {
return this.items[index];
}
Collection.prototype.each = function(callback) {
for(var i = 0; i < this.items.length; i++) {
callback(this.items[i],i);
}
}
Collection.prototype.forEach = Collection.prototype.each;
Collection.prototype.map = function(fn) {
return this.items.map(fn);
}
Collection.prototype.filter = function(fn) {
return this.items.filter(fn);
}
Collection.prototype.find = function(predicate, thisArg) {
return this.items.find(predicate, thisArg);
}
Collection.prototype.findIndex = function(predicate, thisArg) {
return this.items.findIndex(predicate, thisArg);
}
Collection.prototype.toJSON = function() {
return this.map(function(m) {
return m.toJSON()
})
}*/
// ----
Object.defineProperty(Array.prototype, "items", {
enumerable: false,
get() {
return this;
},
set(data) {
this.set(data);
},
});
Object.defineProperty(Array.prototype, "each", {
enumerable: false,
writable: false,
value: Array.prototype.forEach,
});
Object.defineProperty(Array.prototype, "size", {
enumerable: false,
writable: false,
value: function () {
return this.length;
},
});
Object.defineProperty(Array.prototype, "get", {
enumerable: false,
writable: false,
value: function (index) {
return this[index];
},
});
Object.defineProperty(Array.prototype, "getId", {
enumerable: false,
writable: false,
value: function () {
return this._id;
},
});
Object.defineProperty(Array.prototype, "id", {
enumerable: false,
get() {
return this.getId();
},
});
Object.defineProperty(Array.prototype, "set", {
enumerable: false,
writable: false,
value: function (src) {
var length, i;
if (src === this) return;
src = src || []; //handle if src is undefined
function keyIndex(a) {
var keys = {};
var length = a.length;
for (var i = 0; i < length; i++) {
var item = a[i];
keys[item.getId()] = item;
}
return keys;
}
// Src can be a collection, or an array
var bItems = [];
length = src.length;
for (i = 0; i < length; i++) {
var item = src[i];
if (Model.instanceOf(item)) bItems.push(item);
else bItems.push(Model.create(item));
}
var aItems = this.items;
var aKeys = keyIndex(aItems);
var bKeys = keyIndex(bItems);
// First remove all items not in the new collection
length = aItems.length;
for (i = 0; i < length; i++) {
if (!bKeys.hasOwnProperty(aItems[i].getId())) {
// This item is not present in new collection, remove it
this.removeAtIndex(i);
i--;
length--;
}
}
// Reorder items
for (i = 0; i < Math.min(aItems.length, bItems.length); i++) {
if (aItems[i] !== bItems[i]) {
if (aKeys.hasOwnProperty(bItems[i].getId())) {
// The bItem exist in the collection but is in the wrong place
this.remove(bItems[i]);
}
// This is a new item, add it at correct index
this.addAtIndex(bItems[i], i);
}
}
// Add remaining items
for (i = aItems.length; i < bItems.length; i++) {
this.add(bItems[i]);
}
},
});
Object.defineProperty(Array.prototype, "notify", {
enumerable: false,
writable: false,
value: async function (event, args) {
if (!this._listeners) return;
if (!this._listeners[event]) return;
var l = this._listeners[event].slice(); //clone in case listeners array is modified in the callbacks
for (var i = 0; i < l.length; i++) {
await l[i](args);
}
},
});
Object.defineProperty(Array.prototype, "contains", {
enumerable: false,
writable: false,
value: function (item) {
return this.indexOf(item) !== -1;
},
});
Object.defineProperty(Array.prototype, "add", {
enumerable: false,
writable: false,
value: async function (item) {
if (this.contains(item)) return; // Already contains item
this.items.push(item);
await this.notify("add", { item: item, index: this.items.length - 1 });
await this.notify("change");
await item.notify("add", { collection: this });
},
});
Object.defineProperty(Array.prototype, "remove", {
enumerable: false,
writable: false,
value: function (item) {
var idx = this.items.indexOf(item);
if (idx !== -1) this.removeAtIndex(idx);
},
});
Object.defineProperty(Array.prototype, "addAtIndex", {
enumerable: false,
writable: false,
value: async function (item, index) {
if (this.contains(item)) return; // Already contains item
this.items.splice(index, 0, item);
await this.notify("add", { item: item, index: index });
await this.notify("change");
await item.notify("add", { collection: this, index: index });
},
});
Object.defineProperty(Array.prototype, "removeAtIndex", {
enumerable: false,
writable: false,
value: async function (idx) {
var item = this.items[idx];
this.items.splice(idx, 1);
await this.notify("remove", { item: item, index: idx });
await this.notify("change");
await item.notify("remove", { collection: this });
},
});
Object.defineProperty(Array.prototype, "on", {
enumerable: false,
writable: false,
value: function (event, listener) {
if (!this._listeners)
Object.defineProperty(this, "_listeners", {
enumerable: false,
writable: false,
value: {},
});
if (!this._listeners[event]) this._listeners[event] = [];
this._listeners[event].push(listener);
},
});
Object.defineProperty(Array.prototype, "off", {
enumerable: false,
writable: false,
value: function (event, listener) {
if (!this._listeners) return;
if (!this._listeners[event]) return;
var idx = this._listeners[event].indexOf(listener);
if (idx !== -1) this._listeners[event].splice(idx, 1);
},
});
class Collection extends Array {}
var collections = (Collection._collections = {});
Collection.create = function (items) {
const name = Model.guid();
collections[name] = new Collection();
Object.defineProperty(collections[name], "_id", {
enumerable: false,
writable: false,
value: name,
});
if (items) {
collections[name].set(items);
}
return collections[name];
};
Collection.get = function (name) {
if (name === undefined) name = Model.guid();
if (!collections[name]) {
collections[name] = new Collection();
Object.defineProperty(collections[name], "_id", {
enumerable: false,
writable: false,
value: name,
});
}
return collections[name];
};
Collection.instanceOf = function (collection) {
return collection instanceof Collection;
};
Collection.exists = function (name) {
return collections[name] !== undefined;
};
module.exports = Collection;