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]]
ordefineProperties
- 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 */
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> */
taking it a step further:
ch(ch.li)`
sappend ${ch.span}
set innerHTML ${"what?"}
style ${[["width","40dvw"],["height", "auto"]]}
`
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"]]}
`
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)...
or maybe this is better?:
ch.dom`
<li>
<span style="width: 40dvw;">what?</span>
</li>
`
what about children?:
ch.dom`
<li>
${Array(10).fill().map(d => `<span style="width: 40dvw;">what?</span>`)}
</li>
`
wait a second, what if:
ch[`li{"attr":[["data-name", "I am a li"]]}`]
styles, attributes may work like above, what about props??
ch[`li{
"attr": [["data-name", "I am a li"]],
"prop": [["a",${3}], ["b", ${4}]]
}`]
does it mean you could do?:
ch[`li{
"attr": [["data-name", "I am a li"]],
"prop": [["innerHTML", "what?"]]
}`]
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
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)}
`
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}
`
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:
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:
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)