My goal is to create a class ObservableElement
extending HTMLElement
such that it can be used to define custom elements, e g:
customElements.define('an-observable-elem', class extends ObservableElement {
construct() {
super()
...
}
...
})
The thing about any element based on ObservableElement
is that it should have some particular behaviours wrt to properties.
First, 'whatever' in myElem
should always be true. In other words, I'd like a proxy trap for has
on the instance, which just always returns true.
Second, setting and getting any props should work. But whenever a prop which is not explicitly defined on the element or in the prototype chain is set, I want to emit a custom event with the name set${propname}
and detail: theValue
.
It seems there must be a way using es6-proxies. Naively, I first tried:
class ObservableElement extends HTMLElement {
constructor () {
super()
const vals = {}
return Proxy(this, {
has: _ => true,
get: name => {
if (name in this) return this[name]
if (name in vals) return vals[name]
return null
},
set: (name, value) => {
if (name in this) {
this[name] = value
return
}
if (vals[name] === value) return
vals[name] = value
this.dispatchEvent(new CustomEvent(`set${name}`, {detail: value}))
}
})
}
}
But of course that didn't work. Returning the proxy from the constructor did not change the this
value in extending classes' constructors. I fumbled around with all sorts of combinations of proxying construct
on the class, Object.setPrototypeOf(...)
et c to no avail.
I'd much appreciate it if anyone who understands how these things can fit together to achieve what I want would explain it to me. Thanks!
According to §2.4 of the W3C Web Components specification, step 10.6.2:
If
observedAttributesIterable
is notundefined
, then setobservedAttributes
to the result of convertingobservedAttributesIterable
to asequence<DOMString>
. Rethrow any exceptions from the conversion.
This occurs during the customElements.define()
execution, which means that you'd have to define an iterable of every conceivable string when the class is defined in order to intercept all attribute changes.
If the component does not find the changed property in that sequence, it does not call the attributeChangedCallback()
.
Similarly it is impossible to return a proxy from the constructor
because of the restriction in §2.2:
A
return
statement must not appear anywhere inside the constructor body, unless it is a simple early-return (return
orreturn this
).