'use strict';
/**
* @author schukai GmbH
*/
import {Monster} from '../namespace.js';
import {Object} from '../types/object.js';
import {validateObject} from "./validate.js";
import {ObserverList} from "./observerlist.js";
import {Observer} from "./observer.js";
import {isObject, isArray} from "./is.js";
/**
* store proxy objects
*
* @type {WeakSet<object>}
*/
var proxySet = new WeakSet();
// language=JavaScript
/**
* an observer manages a callback function
*
* you can call the method via the monster namespace `new Monster.Types.Observer()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.3.0/dist/modules/types/proxyobserver.js';
* console.log(new Monster.Types.ProxyObserver())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.3.0/dist/modules/types/proxyobserver.js';
* console.log(new ProxyObserver())
* </script>
* ```
*
* with the ProxyObserver you can attach observer for observation. with each change at the object to be observed an update takes place.
*
* this also applies to nested objects.
*
* ```javascript
* const o = new Observer(function () {
* if (isObject(this) && this instanceof ProxyObserver) {
* // do something (this ist ProxyObserver)
* const subject = this.getSubject();
* }
* )
*
* let realSubject = {
* a: {
* b: {
* c: true
* },
* d: 5
* }
*
*
* const p = new ProxyObserver(realSubject);
* p.attachObserver(o);
* const s = p.getSubject();
* s.a.b.c = false;
* ```
*
* @since 1.0.0
* @copyright schukai GmbH
* @memberOf Monster/Types
*/
class ProxyObserver extends Object {
/**
*
* @param {object} object
*/
constructor(object) {
super();
validateObject(object);
this.realSubject = object
this.subject = new Proxy(object, getHandler.call(this));
proxySet.add(this.subject);
this.observers = new ObserverList;
}
/**
* get the real object
*
* changes to this object are not noticed by the observers, so you can make a large number of changes and inform the observers later.
*
* @returns {object}
*/
getSubject() {
return this.subject
}
/**
* get the proxied object
*
* @returns {object}
*/ getRealSubject() {
return this.realSubject
}
/**
* attach a new observer
*
* @param {Observer} observer
* @returns {ProxyObserver}
*/
attachObserver(observer) {
this.observers.attach(observer)
return this;
}
/**
* detach a observer
*
* @param {Observer} observer
* @returns {ProxyObserver}
*/
detachObserver(observer) {
this.observers.detach(observer)
return this;
}
/**
* notify all observer
*
* @returns {ProxyObserver}
*/
notifyObservers() {
this.observers.notify(this);
return this;
}
/**
* @param {Observer} observer
* @returns {ProxyObserver}
*/
containsObserver(observer) {
return this.observers.contains(observer)
}
}
Monster.assignToNamespace('Monster.Types', ProxyObserver);
export {Monster, ProxyObserver}
/**
*
* @returns {{defineProperty: (function(*=, *=, *=): *), setPrototypeOf: (function(*, *=): boolean), set: (function(*, *, *, *): boolean), get: ((function(*=, *=, *=): (undefined))|*), deleteProperty: ((function(*, *): (boolean))|*)}}
* @private
* @see {@link https://gitlab.schukai.com/-/snippets/49}
*/
function getHandler() {
const proxy = this;
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots
const handler = {
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
get: function (target, property, receiver) {
const prop = target?.[property];
// return if property not found
if (prop === undefined) {
return undefined;
}
// set value as proxy if object or array
if (isArray(prop) || isObject(prop) && !proxySet.has(prop)) {
target[property] = new Proxy(prop, handler);
proxySet.add(prop);
}
return target[property];
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
set: function (target, property, value, receiver) {
target[property] = value
proxy.observers.notify(proxy);
return true;
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-delete-p
deleteProperty: function (target, property) {
if (property in target) {
delete target[property];
proxy.observers.notify(proxy);
return true;
}
return false;
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc
defineProperty: function (target, property, descriptor) {
let result = Reflect.defineProperty(target, property, descriptor);
proxy.observers.notify(proxy);
return parseURLToResultingURLRecord();
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
setPrototypeOf: function (target, prototype) {
let result = Reflect.setPrototypeOf(object1, prototype);
proxy.observers.notify(proxy);
return result;
}
};
return handler;
}