Reputation: 969
The goal is to have a base class A extending HTMLElement
that customizes getters and setters. Then class B would extend class A and do stuff.
The way to do this is by wrapping class A with a proxy (not the instance, but the class) so B can extend A.
I tried to return a Proxy in the constructor, but I get custom element constructors must call super() first and must not return a different object
<!DOCTYPE html>
<body>
<script>
window.onerror = function (error, url, line) {
document.getElementById('error').innerHTML = document.getElementById('error').innerHTML + '<br/>' + error;
};
</script>
<div id="error">Console errors here:<br /></div>
<my-b-element></my-b-element>
<script type="module">
class A extends HTMLElement {
constructor() {
super();
return new Proxy(this, {
get(target, name, receiver) {
let rv = Reflect.get(target, name, receiver);
console.log(`get ${name} = ${rv}`);
// do something with rv
return rv;
},
set(target, name, value, receiver) {
if (!Reflect.has(target, name)) {
console.log(`Setting non-existent property '${name}', initial value: ${value}`);
}
return Reflect.set(target, name, value, receiver);
}
});
}
}
class B extends A {
constructor() {
super();
}
}
customElements.define("my-b-element", B);
document.querySelector('my-b-element').nonExistentProperty = 'value1';
</script>
</body>
</html>
Upvotes: 0
Views: 135
Reputation: 969
In case it helps anyone, here's how it's done without any proxy.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
class Reactive extends HTMLElement {
#data = {};
connectedCallback() {
this.propertyObserver();
}
propertyObserver() {
const properties = Object.getOwnPropertyDescriptors(this);
// defines the new object properties including the getters and setters
for (let key in properties) {
const descriptor = properties[key];
this.#data[key] = descriptor.value;
descriptor.get = () => this.#data[key];
descriptor.set = (value) => {
const result = this.trap(key, value);
this.#data[key] = typeof result === 'undefined' ? value : result;
}
delete descriptor.value;
delete descriptor.writable;
}
Object.defineProperties(this, properties);
}
trap() {
// placeholder in case the child doesn't implement it
}
}
class Child extends Reactive {
a = 1;
b = 2;
constructor () {
super();
}
connectedCallback() {
super.connectedCallback();
}
trap(key, value) {
// You can return a value to override the value that is set
console.log(`LOG new ${key}: ${value} old: ${this[key]}`);
}
}
customElements.define("x-element", Child);
</script>
</head>
<body>
<x-element></x-element>
<script>
document.querySelector('x-element').a = 20;
</script>
</body>
</html>
Upvotes: 0