DEV Community

Cover image for Vantage Point - Combining the imperative and the declerative
Ibrahim Tanyalcin
Ibrahim Tanyalcin

Posted on

Vantage Point - Combining the imperative and the declerative

vantage

I always take the new shiny bits from TC39 with a pinch of salt. I have been honest about some of the additions to the language before, but this will be a good one.

For me it has always been a pain, not to be able to respond to arbitrary property accesses without some safety net. Whether we are talking about a Class or an Object, your options were:

  • hardcode the property from an array-like using [[Set]] or defineProperties
  • dodge the error using optional chaining or nullish coalescence
  • create a method on the prototype chain that accepts an arbitrary string, corresponding to the property to be accessed.

None of the above feels natural, aka, you cannot do: someObj.someArbitraryString and return something other than undefined. For that reason Proxy was a great addition to the language simply because a polyfill was not possible.

Proxies empowered ALL of the objects in JS. Not just Arrays, Objects, Maps etc, but functions too. I want to talk about the function part, because a pattern that I unconsciously started using for over a year kept coming back and proved to be extremely useful. It stayed on the shelf for some time while I used it, and now I have at least something little to show.

Consider this:

ch.li /* returns a li DOM object */
ch.div /* returns a div DOM object */
Enter fullscreen mode Exit fullscreen mode

One would think it is some sort of a class or object that has a method, no it is not:

ch.whatever /* <whatever></whatever> */
ch(ch.whatever).set("innerHTML", "what?") /* <whatever>what?</whatever> */
Enter fullscreen mode Exit fullscreen mode

taking it a step further:

ch(ch.li)`
sappend ${ch.span}
set innerHTML ${"what?"}
style ${[["width","40dvw"],["height", "auto"]]}
`
Enter fullscreen mode Exit fullscreen mode

Above 'sappend' is short for 'select and then append'. A bit more with shortcuts to commands:

ch(ch.li)`
+-> ${ch.span}
>> innerHTML ${"what?"}
style ${[["width","40dvw"],["height", "auto"]]}
`
Enter fullscreen mode Exit fullscreen mode

and why not alternate between template literals and classic function calls:

ch(ch.li)`
+-> ${ch.span}
>> innerHTML ${"what?"}`
.style([["width","40dvw"],["height", "auto"]])
.select(ch.div)...
Enter fullscreen mode Exit fullscreen mode

or maybe this is better?:

ch.dom`
    <li>
        <span style="width: 40dvw;">what?</span>
    </li>
`
Enter fullscreen mode Exit fullscreen mode

what about children?:

ch.dom`
    <li>
        ${Array(10).fill().map(d => `<span style="width: 40dvw;">what?</span>`)}
    </li>
`
Enter fullscreen mode Exit fullscreen mode

wait a second, what if:

ch[`li{"attr":[["data-name", "I am a li"]]}`]
Enter fullscreen mode Exit fullscreen mode

styles, attributes may work like above, what about props??

ch[`li{
"attr": [["data-name", "I am a li"]],
"prop": [["a",${3}], ["b", ${4}]]
}`]
Enter fullscreen mode Exit fullscreen mode

does it mean you could do?:

ch[`li{
"attr": [["data-name", "I am a li"]],
"prop": [["innerHTML", "what?"]]
}`]
Enter fullscreen mode Exit fullscreen mode

can we combine them all?:

ch(ch[`li{
"attr": [["data-name", "I am a li"]],
"prop": [["innerHTML", "what?"]]
}`])`
    +> ${Array(5).fill().map((d,i) => {
        return ch.dom`
            <span>
                <i>HELLO!</i>
            </span>
        `
    })}
`.selected //logs the li element
Enter fullscreen mode Exit fullscreen mode

Marking a parentNode or any other variable to do something with it later:

ch`
-> myLi:${
    ch[`li{
        "attr": [["data-name", "I am a li"]],
        "prop": [["innerHTML", "what?"]]
    }`]
}
+> ${Array(5).fill().map((d,i) => {
    return ch(ch.dom`
        <span>
            <i>HELLO!</i>
        </span>
    `).on("pointerover@mynamespace", () => console.log("mouseover!")).selected
})}
=> ${({values}) => () => console.log(values.myLi)}
`
Enter fullscreen mode Exit fullscreen mode

And if you were to call function parameters inside a tagged template literal, you would need spreading. Sure, why not?:

ch`
-> ${ch.li}
on ...${["click@someNameSpace", (e) => {console.log(e.clientX)}]}
+< ${document.body}
`
Enter fullscreen mode Exit fullscreen mode

Here we created a li element, added a click event listener on it with someNameSpace (so that we can later remove all the listeners registered with that namespace) and appended that to the body. The event and the handler is passed in an array inside a template literal which is spread to on function.

In none of the examples there is a build step involved. It all is based on about 160 lines of ES6 - ES2018 JavaScript. Here is a codepen with some of the examples above.

At this point you might ask what a TODO list would look like, there you go:

TODO

Many of you are using frameworks, I won't be giving names here, and it is totally perfect when it comes to getting the job done. Programming is somewhat bit of an art when compared to positive sciences like physics and math, and for that reason, there is more than one optimal way of doing things, I definitely cherish the differences. Also different stacks for rendering the same "Hello World!" page has packed a lot of documentation through the years, so it is not possible for an average person like me to know everything.

Here is the thing, the examples you saw in this article are imperative compared to declarative. Due its nature, imperative programming has been a bit more verbose because the programmer has to instruct the how to rather than what to. But in the face of ES6+, it does not have to be like that anymore. I hope I could prove a point.

Why is the above important? Because imperative represents control and declarative represents abstraction. You want to abstract because you want to avoid the pain and make it easier. You want imperative because you want to specifically control the flow of your functions to create the exact effect desired. Both can be combined.

Using this small library, I was able to create complex data visualizations, interfaces using simple routines and web components. When it came to fine tuning and controlling rendering cycle, I was able to control the DOM operations because the setup is imperative by nature with declarative bits.

Complex visualizations or interfaces go through a process called Enter - update - exit cycle. Which is reminiscent of how D3 works, and most of the frameworks today use that logic behind the scenes by specifying a key value to decide with nodes to add/remove/change. This also can be done, here is a static example I put together. You can pause and call Array methods on the data in the inspector. See for yourself.

There is a lot of work to do. The helper methods that are mounted on the Proxy needs documentation in JSDoc. If you are interested, here is the repository:

Cahir

Almost a decade and half of developing open source/proprietary visualizations in bioinformatics has thought me that what works >>> what is trendy. I understand people refraining from taking risks of dunking into works of others and instead going for the safe route of frameworks because "they know better than you do." There is also a large cohort of young people who desperately need a paycheck and have no option other than investing into whichever soup of the day stack that lands a job in this VC poisoned market.

I get all of the above. I also get the idea of standing in the shoulder of giants. We all, whether we want or not, stand in one way or another in the shoulder of a giant. I believe the key is to find a balance, and sometimes try to ask the question "what if". I think the only sustainable leverage is a vantage point, a vantage point that can incorporate the old and the new, that can keep what works and discards what doesn't.

If you like the repository, please try to help me to make it better by submitting PRs and documentation suggestions. If you like it, a star would not hurt.

Have a great weekend,

Top comments (0)