pragma-views2
Version:
404 lines (326 loc) • 11 kB
JavaScript
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);
}
}
}