'use strict';
/**
* @author schukai GmbH
*/
import {Monster} from '../namespace.js';
import {Base} from './base.js';
import {validateObject} from "./validate.js";
import {ObserverList} from "./observerlist.js";
import {Observer} from "./observer.js";
import {isObject, isArray, isPrimitive} from "./is.js";
/**
* 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.5.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.5.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 Base {
/**
*
* @param {object} object
*/
constructor(object) {
super();
validateObject(object);
this.realSubject = object
this.subject = new Proxy(object, getHandler.call(this));
this.objectMap = new WeakMap();
this.objectMap.set(this.realSubject, 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, key, receiver) {
const value = Reflect.get(target, key, receiver);
if (typeof key === "symbol") {
return value;
}
if (isPrimitive(value)) {
return value;
}
// set value as proxy if object or array
if ((isArray(value) || isObject(value))) {
if (proxy.objectMap.has(value)) {
return proxy.objectMap.get(value);
} else {
let p = new Proxy(value, handler);
proxy.objectMap.set(value, p);
return p;
}
}
return value;
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
set: function (target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
if (typeof property !== "symbol") {
proxy.observers.notify(proxy);
}
return result;
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-delete-p
deleteProperty: function (target, key) {
if (key in target) {
delete target[key];
if (typeof key !== "symbol") {
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, key, descriptor) {
let result = Reflect.defineProperty(target, key, descriptor);
if (typeof key !== "symbol") {
proxy.observers.notify(proxy);
}
return result;
},
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
setPrototypeOf: function (target, key) {
let result = Reflect.setPrototypeOf(object1, key);
if (typeof key !== "symbol") {
proxy.observers.notify(proxy);
}
return result;
}
};
return handler;
}