the:chris:walker ↩

JS Proxies and getElementById

I was chatting to a mate of mine over the weekend, and he touched on the use of Proxy object in some code he had been working with. I pointed him to my favourite use of a Proxy, which exists in poof (My secure one time secret sharing service), and is useful in any VanillaJS scenario.

The code is on Github but is short enough to add here:

// getElementById helper
// basically you can dereference elements by id.
export const dom = new Proxy(
  {},
  {
    get: (target, prop) => {
      if (typeof prop !== "string") {
        return target[prop];
      }
      const id = prop.replace(/^\$/, ""); // allow dom.$id
      return document.getElementById(id);
    },
  }
);

In vanillajs code you often query and cache DOM elements by id, so that you can attach event handlers or read values from inputs, etc… This (in my code) usually leads to a big block of code like this:

<form id="form">
  <input id="input" />
  <button id="button">Click</button>
</form>
<script>
const $form = document.getElementById("form");
const $input = document.getElementById("input");
const $button = document.getElementById("button");

// $form.onsubmit = ... etc...
</script>

But with the awesome DOM helper the script part can change to:

const { $form, $input, $button } = dom;

Isn’t that beautiful. The idea could be extended with querySelector and querySelectorAll functionality - say a proxy called qs / qsa, so you could dereference with a new variable name like:

const { ["div.foo ul > li"]: $list } = qsa;

This wouldn’t be hard to build, in fact it would be almost the same (simpler even):

const qs = new Proxy(
  {},
  {
    get: (target, prop) => {
      if (typeof prop !== "string") {
        return target[prop];
      }
      return document.querySelector(prop);
    },
  }
);

I have half a mind to make a tiny library out of this, but even without that please copy/paste or just steal the idea!

Edit: I just realised that we can wrap this idea as the form for the two Proxies are almost identical.

function makeProxy(fn) {
    return new Proxy({}, {
        get: (t, p) => typeof p !== "string" ? t[p] : fn(p);
    });
}

const dom = makeProxy(id => document.getElementById(id.replace(/^\$/, "")));
const qs = makeProxy(sel => window.querySelector(sel));
const qsa = makeProxy(sel => window.querySelectorAll(sel));