Search code examples
javascriptecmascript-6custom-elementproxies

Using a proxy to access the dataset of a custom element


Imagine this trivial custom element:

<my-el data-cute-number="7" id="foo"></my-el>

document.getElementById('foo').dataset.cuteNumber, as expected, returns the String "7". I would like to create a proxy for accessing dataset properties that does the casting to Number for me because I'm using the property alot in the component code and would like to avoid repeatedly having to cast it manually every time I access it. I also do not want to create an additional getter for a new property (e.g. get cuteNumber() { return Number(this.dataset.cuteNumber); }) on the component itself since I will have to do all the synchronisation manually then (since I'd also need a setter), make sure I avoid infinite update loops, etc.

As I understand proxies, this is exactly where a proxy will help me.

customElements.define('my-el', class extends HTMLElement {
  constructor() {
    super();
    const proxy = new Proxy(this.dataset, {
      get: function(context, prop, receiver) {
        console.log(`Proxy getter executing for ${prop}`);
        switch (prop) {
          case 'cuteNumber':
            return Number(context[prop]);
            break;
          default: 
            return context[prop];
        }
      }
    });
  }
})

console.log(typeof document.getElementById('foo').dataset.cuteNumber);
<my-el data-cute-number="7" id="foo"></my-el>

This is where I'm stuck.

Accessing the dataset currently does not trigger the proxy (the inner console.log doesn't show up).

Can anyone point me in the right direction? Is it even possible to proxy the dataset of an element?


Solution

  • Creating a proxy does not mutate the target object, it doesn't become a proxy. The proxy is a new object that wraps around the target. In your code, you are just throwing away the proxy and never use it - the .dataset property is unaffected. You'll want to either overwrite it or create a new property:

    customElements.define('my-el', class extends HTMLElement {
      get dataset() {
    //^^^^^^^^^^^^^
        return new Proxy(super.dataset, {
    //  ^^^^^^
          get: function(target, prop, receiver) {
            console.log(`Proxy getter executing for ${prop}`);
            if (prop == 'cuteNumber')
              return Number(target.cuteNumber);
            return Reflect.get(target, prop, receiver);
          }
        });
      }
    });
    
    console.log(typeof document.getElementById('foo').dataset.cuteNumber);
    <my-el data-cute-number="7" id="foo"></my-el>
    customElements.define('my-el', class extends HTMLElement {
      constructor() {
        super();
        this.numberdata = new Proxy(this.dataset, {
    //  ^^^^^^^^^^^^^^^^^
          get: function(target, prop, receiver) {
            return Number(target[prop]);
          }
        });
      }
    });
    
    console.log(typeof document.getElementById('foo').numberdata.cuteNumber);
    <my-el data-cute-number="7" id="foo"></my-el>