Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 9, 2021 09:06 pm GMT

Leveraging JS Proxies for the DOM

The Problem

A recurring problem for many front-end developers is choosing what framework to use. Maybe your mind skipped to React, or the new star, Vue. Or maybe you're into Ember and Mithril. No one cares about Angular though. We all know it's a bloated relic living somewhere in the Great Pacific Garbage Patch.

It's strange how we always skip over to create-[framework]-app or another boilerplate template without noticing the extreme amounts of overhead. Relatively simple side or personal projects don't require a framework at all. Choosing the vanilla JS option is considerably more responsible (we're not killing the client's poor Nokia browser with our 10 GB library) and requires no extensive bundler configuration. The browser was built for JavaScript, so use JavaScript.

Frameworks were created to boost productivity, modularize elements into reusable components, provide a novel way of manipulating data, ensure faster rendering through the virtual DOM, and supply a well supported developer toolset. We're missing out on a lot if we pick vanilla. Using native JS APIs is also an absolute nightmare. Who wants to write document.querySelectorAll 50 times?

Regardless, there isn't a need to re-invent the wheel. Although it may seem cool to have a functioning SPA, what you're really doing is writing another hundred lines of code or importing a heavy library with extensive polyfills just to rewrite the JS history API. It's not like the user cares if the url changed without refreshing the page. It's "smooth", but not if the page can't even load because of all of the crap you packed into it. Even Webpack can't save your file sizes now.

Creating Elements

There are several ways to tackle vanilla JS's lack of maintainability and ease of use. You could use this simple function I described in an earlier post on jQuery.

const $ = (query) => document.querySelectorAll(query)

However, querying elements is not the only tool we need as developers. Oftentimes, it's creating the elements that's the problem.

// create a div elementconst div = document.createElement("div")div.classList.add("test")// create a paragraph element & fill it with "Hello World!"const p = document.createElement("p")p.textContent = "Hello World!"// append nodes to div and then to the body elementdiv.appendChild(p)document.body.appendChild(div)

Vanilla JS gets really ugly. Really fast. Feeling the itch to go back to React yet?

Proxies

Here's where the proxies come in. Proxies in JS allow you to "intercept and redefine fundamental operations for that object". As a bonus, it's supported by all the major browsers. Obviously, now that IE is dead, we don't have to worry about it anymore. Kinda like Angular!

I highly recommend reading the first few paragraphs of the MDN docs I linked above.

You can create proxies with the built-in Proxy class. It takes two arguments: a target object and a handler function that indicates how the target should be manipulated.

I like to think proxies are useful for "listening" to when a property in an object is accessed or changed. For example, you could extend arrays to support negative indexes, similar to Python.

export const allowNegativeIndex = (arr) => new Proxy(arr, {    get(target, prop) {        if (!isNaN(prop)) {            prop = parseInt(prop, 10)            if (prop < 0) {                prop += target.length            }        }        return target[prop]    }})allowNegativeIndex([1, 2, 3])[-1]

DOM Manipulation

I randomly stumbled upon this code snippet when I was scrolling through my Twitter feed. I can't explain how genius this is.

image

Using a proxy to create elements! While this clearly applies to Hyperapp (a "tiny framework for building hypertext applications"), there's no reason why this couldn't apply to vanilla JS.

Imagine writing this instead of document.createElement.

document.body.appendChild(div({},     h1({ id: "test" }, "Hello World"),    p({}, "This is a paragraph")))/*<div>    <h1 id="test">Hello World</h1>    <p>This is a paragraph</p></div>*/

It doesn't require JSX or a fancy framework, and using functions based on the literal HTML5 tag actually makes a lot of sense.

The Code

You can find a working demo on Codepen and Replit.

First we need to have some logic to easily create elements. I'll call it h. h should accept three arguments: an HTML tag, a list of attributes/event listeners that should be applied to the element, and an array of children that should be appended to the element.

const h = (tag, props={}, children=[]) => {  // create the element  const element = document.createElement(tag)  // loop through the props  for(const [key, value] of Object.entries(props)) {    // if the prop starts with "on" then add it is an event listener    // otherwise just set the attribute    if(key.startsWith("on")) {      element.addEventListener(key.substring(2).toLowerCase(), value)    } else {      element.setAttribute(key, value)    }  }  // loop through the children  for(const child of children) {    // if the child is a string then add it as a text node    // otherwise just add it as an element    if(typeof child == "string") {      const text = document.createTextNode(child)      element.appendChild(text)    } else {      element.appendChild(child)    }  }  // return the element  return element}

You could use this function as-is and immediately see some benefits.

h("main", {},     h("h1", {}, "Hello World"))

This is much more developer friendly, but we can still make it better with proxies. Let's create a proxy called elements. Every time we access a property from elements, we want to return our newly created h function using the property as the default tag.

const elements = new Proxy({}, {  get: (_, tag) =>     (props, ...children) =>       h(tag, props, children)})

Now we can write stuff that looks kinda like HTML directly in our vanilla JS. Looks amazing doesn't it?

const { button, div, h1, p } = elementsdocument.body.appendChild(div({},  h1({ id: "red" }, "Hello World"),  p({ class: "blue" }, "This is a paragraph"),  button({ onclick: () => alert("bruh") }, "click me")))// this also works but destructuring is cleaner// elements.h1({}, "")

State Management

Proxies also have a set method, meaning you can trigger an action (ie: a re-render) when a variable is changed. Sound familiar? I immediately thought of state management. In a brief attempt to marry proxies with web components, I went on to build a library called stateful components. It uses this concept of proxy-based state and "functional" elements to easily create web components. I haven't looked at it in a while, so if you're looking for something a little more fleshed out you should give Hyperapp a go. I know this article railed on frameworks a lot, but that doesn't mean I don't recognize their utility and purpose in a given context.

Closing

I hope you enjoyed this short article. A lot of thanks to Matej Fandl for discovering this awesome hack, and I look forward to seeing what you build with proxies!


Original Link: https://dev.to/phamn23/leveraging-js-proxies-for-the-dom-3ppm

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To