UNPKG

pragma-views2

Version:

404 lines (326 loc) 11 kB
import { getPropertiesFromExpression } from "../actions/expression-parser.js"; import { ExecutionCompiler } from "../actions/code-compiler.js"; window.observers = new WeakMap(); window.expressions = new Map(); if (window.compiler == undefined) { window.compiler = new ExecutionCompiler(); } export function observe(obj) { if (obj == undefined) return obj; const result = createProxy(obj); let observer = window.observers.get(obj); if (observer == undefined) { observer = new Map(); window.observers.set(result, observer); } return result; } export function createFunctionForExpression(expression, requester) { if (window.expressions.has(expression)) { const result = window.expressions.get(expression); result.requesters.add(requester); return result.fn; } const isExpression = expression.indexOf("${") != -1; const fn = window.compiler.add(expression, isExpression); const exp = { fn: fn, requesters: new Set() }; exp.requesters.add(requester); window.expressions.set(expression, exp); return fn; } export async function removeFunctionForExpression(expression, requester) { if (window.expressions.has(expression)) { const exp = window.expressions.get(expression); if (exp.requesters.has(requester)) { exp.requesters.delete(requester); } if (exp.requesters.size == 0) { window.expressions.delete(expression); exp.fn = null; } } } export function getObjOnPath(obj, stack) { if (stack.length == 1) { return obj; } let result = obj; for (let i = 0; i < stack.length - 1; i++) { const field = stack[i]; if (result[field] == undefined) { const obj = observe({}); result[field] = obj; } else if (window.observers.has(result[field]) == false) { const observer = observe(result[field]); result[field] = observer; } result = result[field]; } return result; } export async function removeObserversFromCache(proxies) { if (proxies == undefined) return; for (let i = 0; i < proxies.length; i++) { removeObserverFromCache(proxies[i]); } } export async function removeObserverFromCache(proxy) { if (proxy == undefined) return; if (window.observers.has(proxy)) { window.observers.delete(proxy); const keys = Reflect.ownKeys(proxy); for (let key of keys) { removeObserverFromCache(proxy[key]); } } } export function addObserverTrigger(proxy, prop, trigger) { const stack = prop.split("."); const field = stack[stack.length - 1]; const obj = getObjOnPath(proxy, stack); const observer = window.observers.get(obj); if (observer == undefined) { return; } let callbacks = observer.get(field); if (callbacks == undefined) { callbacks = new Set(); observer.set(field, callbacks); } if (callbacks.has(trigger) == false) { callbacks.add(trigger); } } export function removeObserverTrigger(proxy, prop, trigger) { const stack = prop.split("."); const field = stack[stack.length - 1]; const obj = getObjOnPath(proxy, stack); const observer = window.observers.get(obj); if (observer == undefined) return; let callbacks = observer.get(field); if (callbacks == undefined) return; if (callbacks.has(trigger)) { callbacks.delete(trigger); } } export function addTriggers(proxy, expression, fn) { const properties = getPropertiesFromExpression(expression); for (let property of properties) { addObserverTrigger(proxy, property, fn); } } export function addExpressionTriggers(proxy, expression, fn) { const expressionFn = createFunctionForExpression(expression, proxy); const callback = () => { if (expressionFn(proxy) == true) { fn(); } }; addTriggers(proxy, expression, callback); } export function removeTriggers(proxy, expression, fn) { if (proxy == undefined || expression == undefined || fn == undefined) return; const properties = getPropertiesFromExpression(expression); for (let property of properties) { removeObserverTrigger(proxy, property, fn); } } export function proxyToObject(proxy) { return JSON.parse(JSON.stringify(proxy)); } async function triggerCallbacks(proxy, prop, newValue, oldValue) { const observer = window.observers.get(proxy); if (observer == undefined) return; const callbacks = observer.get(prop); if (callbacks == undefined) { if (window.observers.has(proxy[prop])) { const keys = Reflect.ownKeys(proxy[prop]); for (let key of keys) { const target = Reflect.get(proxy, prop); if (target != proxy) { triggerCallbacks(proxy[prop], key, newValue, oldValue); } } } } else { for (let callback of callbacks) { if (newValue == undefined && oldValue == undefined) { continue; } callback(newValue, oldValue); } } } function swapObservers(oldProxy, newProxy) { const oldObserver = window.observers.get(oldProxy); window.observers.set(newProxy, oldObserver); } function createProxy(obj) { return new Proxy(obj, { get, set }); } function get(target, key, receiver) { return Reflect.get(target, key, receiver); } function set(target, key, value, receiver) { const oldValue = Reflect.get(target, key, receiver); if (window.observers.has(oldValue)) { value = observe(value || {}); swapObservers(oldValue, value); } const result = Reflect.set(target, key, value, receiver); if (result == true) { const fnName = `${key}Changed`; if (target[fnName] != undefined && typeof target[fnName] === "function") { target[fnName](value, oldValue); } triggerCallbacks(receiver, key, value, oldValue); } if (oldValue != undefined) { if (typeof oldValue.dispose == "function") { oldValue.dispose.call(oldValue); } else { removeObserverFromCache(oldValue); } } return result; } class Observers { get hasCallbacks() { return this._callbacks.length > 0; } constructor(model, propName) { this._model = model; this._propName = propName; this._callbacks = []; } dispose() { this._reversePropertyTrap(); this._callbacks = null; this._model = null; } registerCallback(callback) { if (callback == undefined) return; if (this._callbacks.indexOf(callback) == -1) { this._callbacks.push(callback); } } unregisterCallback(callback) { if (callback == undefined) return; const index = this._callbacks.indexOf(callback); if (index != -1) { this._callbacks.splice(index, 1); } } _setPropertyTrap() {} _reversePropertyTrap() {} async _callCallbacks(newValue, oldValue) { for (let callback of this._callbacks) { callback(newValue, oldValue); } } } export class ArrayObserver extends Observers { constructor(model, propName) { super(model, propName); this._removeCallbacks = []; this._arrayToWatch = this._model[this._propName]; this._setPropertyTrap(); } dispose() { super.dispose(); if (this._arrayToWatch != undefined) { removeObserversFromCache(this._arrayToWatch); } this._removeCallbacks = null; this._arrayToWatch = null; } [Symbol.iterator]() { this.__index = -1; return { next: args => { this.__index++; if (this.__index < this._arrayToWatch.length) { return { value: this._arrayToWatch[this.__index], done: false }; } else { this.__index = -1; return { done: true }; } } }; } _setPropertyTrap() { this._arrayToWatch.pop = this.pop.bind(this); this._arrayToWatch.push = this.push.bind(this); this._arrayToWatch.splice = this.splice.bind(this); } _reversePropertyTrap() { if (this._arrayToWatch == undefined) return; this._arrayToWatch.pop = Array.prototype.pop; this._arrayToWatch.push = Array.prototype.push; this._arrayToWatch.splice = Array.prototype.splice; } registerAddCallback(callback) { this.registerCallback(callback); } unregisterAddCallback(callback) { this.unregisterCallback(callback); } registerRemovedCallback(callback) { if (callback == undefined) return; if (this._removeCallbacks.indexOf(callback) == -1) { this._removeCallbacks.push(callback); } } unregisterRemovedCallback(callback) { if (callback == undefined) return; const index = this._removeCallbacks.indexOf(callback); if (index != -1) { this._removeCallbacks.splice(index, 1); } } pop(...args) { const result = Array.prototype.pop.call(this._arrayToWatch, ...args); this.itemsRemoved(result); return result; } push(...args) { const itemsToAdd = []; for (let i = 0; i < args.length; i++) { const proxy = observe(args[i]); Array.prototype.push.call(this._arrayToWatch, proxy); itemsToAdd.push(proxy); } this.itemsAdded(itemsToAdd); return this._arrayToWatch.length; } splice(...args) { const removeProperties = args.slice(0, 2); const addProperties = args.slice(2, args.length); const result = Array.prototype.splice.call(this._arrayToWatch, removeProperties[0], removeProperties[1]); this.itemsRemoved(result); for (let obj of addProperties) { this.push(obj); } return result; } itemsAdded(items) { this._callCallbacks(items); } async itemsRemoved(items) { removeObserversFromCache(items); for (let callback of this._removeCallbacks) { callback(items); } } }