"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;