models and collections

This commit is contained in:
antelle 2019-09-17 19:01:24 +02:00
parent 87eb0468a6
commit 00291da933
2 changed files with 236 additions and 0 deletions

View File

@ -0,0 +1,142 @@
import EventEmitter from 'events';
const SymbolEvents = Symbol('events');
function emitSet(target, property, value, receiver, prevValue) {
const emitter = target[SymbolEvents];
if (!emitter.paused) {
const updates = { added: [], removed: [] };
if (prevValue) {
emitter.emit('remove', prevValue, receiver);
updates.removed.push(prevValue);
}
if (value) {
emitter.emit('add', value, receiver);
updates.added.push(value);
}
emitter.emit('change', updates, this);
}
}
function emitRemoved(target, removed, receiver) {
const emitter = target[SymbolEvents];
if (!emitter.paused) {
for (const item of removed) {
emitter.emit('remove', item, receiver);
}
emitter.emit('change', { added: [], removed }, this);
}
}
const ProxyDef = {
set(target, property, value, receiver) {
if (property === 'length') {
if (value < target.length) {
const removed = target.slice(value);
emitRemoved(target, removed, receiver);
}
target.length = value;
return true;
}
const numProp = parseInt(property);
if (isNaN(numProp)) {
return false;
}
const prevValue = target[property];
if (prevValue !== value) {
target[property] = value;
emitSet(target, property, value, receiver, prevValue);
}
return true;
}
};
class Collection extends Array {
constructor(...args) {
super(...args);
const emitter = new EventEmitter();
emitter.setMaxListeners(100);
const properties = {
[SymbolEvents]: { value: emitter }
};
Object.defineProperties(this, properties);
return new Proxy(this, ProxyDef);
}
push(...items) {
if (items.length) {
this[SymbolEvents].paused = true;
super.push(...items);
this[SymbolEvents].paused = false;
for (const item of items) {
this[SymbolEvents].emit('add', item, this);
}
this[SymbolEvents].emit('change', { added: items, removed: [] }, this);
}
}
pop() {
this[SymbolEvents].paused = true;
const item = super.pop();
this[SymbolEvents].paused = false;
if (item) {
this[SymbolEvents].emit('remove', item, this);
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
}
}
shift() {
this[SymbolEvents].paused = true;
const item = super.shift();
this[SymbolEvents].paused = false;
if (item) {
this[SymbolEvents].emit('remove', item, this);
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
}
}
unshift(...items) {
if (items.length) {
this[SymbolEvents].paused = true;
super.unshift(...items);
this[SymbolEvents].paused = false;
for (const item of items) {
this[SymbolEvents].emit('add', item, this);
}
this[SymbolEvents].emit('change', { added: items, removed: [] }, this);
}
}
splice(start, deleteCount, ...items) {
this[SymbolEvents].paused = true;
const removed = super.splice(start, deleteCount, ...items);
this[SymbolEvents].paused = false;
for (const item of removed) {
this[SymbolEvents].emit('remove', item, this);
}
for (const item of items) {
this[SymbolEvents].emit('add', item, this);
}
if (removed.length || items.length) {
this[SymbolEvents].emit('change', { added: items, removed }, this);
}
}
on(eventName, listener) {
this[SymbolEvents].on(eventName, listener);
}
once(eventName, listener) {
this[SymbolEvents].once(eventName, listener);
}
off(eventName, listener) {
this[SymbolEvents].off(eventName, listener);
}
}
export { Collection };

View File

@ -0,0 +1,94 @@
import EventEmitter from 'events';
const SymbolEvents = Symbol('events');
const SymbolDefaults = Symbol('defaults');
function emitPropChange(target, property, value, receiver) {
const emitter = target[SymbolEvents];
if (!emitter.paused) {
emitter.emit('change:' + property, receiver, value);
if (!emitter.noChange) {
emitter.emit('change', receiver, { [property]: value });
}
}
}
const ProxyDef = {
deleteProperty(target, property, receiver) {
if (Object.prototype.hasOwnProperty.call(target, property)) {
const value = target[SymbolDefaults][property];
if (target[property] !== value) {
target[property] = value;
emitPropChange(target, property, value, receiver);
}
return false;
}
return false;
},
set(target, property, value, receiver) {
if (Object.prototype.hasOwnProperty.call(target, property)) {
if (target[property] !== value) {
target[property] = value;
emitPropChange(target, property, value, receiver);
}
return true;
}
return false;
}
};
class Model {
constructor() {
const emitter = new EventEmitter();
emitter.setMaxListeners(100);
const properties = {
[SymbolEvents]: { value: emitter }
};
for (const [propName, defaultValue] of Object.entries(this[SymbolDefaults])) {
properties[propName] = {
enumerable: true,
writable: true,
value: defaultValue
};
}
Object.defineProperties(this, properties);
return new Proxy(this, ProxyDef);
}
set(props, { silent } = {}) {
const emitter = this[SymbolEvents];
if (silent) {
emitter.paused = true;
}
emitter.noChange = true;
for (const [prop, value] of Object.entries(props)) {
this[prop] = value;
}
emitter.noChange = false;
if (silent) {
emitter.paused = false;
} else {
emitter.emit('change', this, props);
}
}
on(eventName, listener) {
this[SymbolEvents].on(eventName, listener);
}
once(eventName, listener) {
this[SymbolEvents].once(eventName, listener);
}
off(eventName, listener) {
this[SymbolEvents].off(eventName, listener);
}
static defineModelProperties(properties) {
this.prototype[SymbolDefaults] = properties;
}
}
export { Model };