0

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>
daniel p
  • 741
  • 7
  • 12
  • +1 for Proxies! But why not do: ``customElements.define("B" , class extends customElements.get("A"){ ... });`` blog [here](https://dev.to/dannyengelman/how-to-cheat-with-web-components-295o) – Danny '365CSI' Engelman Nov 12 '22 at 14:59
  • hmmm, not sure how I can modify setters, eg. making `querySelector('my-B-element').myProp="365csi"` do something programmed in A with your suggestion. Goal is, any property set in B will undergo a process in A. A is the "framework". B is clueless. – daniel p Nov 12 '22 at 18:25
  • ``super.connectedCallback()`` calls the method in A from B, similar for other properties and methods you add/overload – Danny '365CSI' Engelman Nov 12 '22 at 19:30
  • I didn't manage to solve it :\ Little code snippet too much of an ask? :) – daniel p Nov 13 '22 at 11:07
  • Also tried `customElements.define("my-b-element", new Proxy(B,{set (target, name, value) { console.log("Setting property"); return Reflect.set(target, name, value);}}));` but setting a property `querySelector('my-B-element').myProp="365csi"` doesn't log anything – daniel p Nov 13 '22 at 11:57
  • ?? You want to execute a Method in a Parent class, that [the method] doesn't exist ?? – Danny '365CSI' Engelman Nov 13 '22 at 13:50
  • No :) I can observe any attribute change from the parent and execute an existing method in the child, thanks to `MutationObserver`. I now need the same for property changes. Made this to explain: https://stackblitz.com/edit/js-m5sfo5?file=index.html – daniel p Nov 13 '22 at 18:01
  • Looks complex. You are learning JS fast, you need [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) for properties – Danny '365CSI' Engelman Nov 13 '22 at 18:39

1 Answers1

0

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>
daniel p
  • 741
  • 7
  • 12