mirror of https://github.com/keeweb/keeweb.git
models and collections
This commit is contained in:
parent
87eb0468a6
commit
00291da933
|
@ -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 };
|
|
@ -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 };
|
Loading…
Reference in New Issue