On initialising JavaScript from markup

In Don’t initialise JavaScript automagically, Adam Silver argues that we should not rely on markup to trigger bits of JavaScript. I disagree with the advice he gives, and think markup is great place to trigger behaviour. Where else?

A quick disclaimer up front: I believe that when it comes to how to trigger JavaScript, there is no good or bad. There is personal preference though, and mine is a markup-based approach.

In this post I will go into three of Adam’s concerns with that approach. They are about what behaviour is being triggered, how many times and whether hiding complexity is a problem. His post does not go into any non-markup-based initialisation methods, so I can only guess what such methods would involve.

What you’re triggering

With markup to trigger behaviour, Adam explains, it is harder to know what you’re initialising. I think this depends on how your declare the behaviour. Enforcing a solid naming approach is essential here.

This is an approach I like:

<a href="#section1" 
   data-handler="open-in-overlay">
Section 1</a>

Let’s assume a website consistently uses the data-handler attribute (see my earlier post about handlers), with as its value a verb that corresponds to a function name. In the codebase for that website, it will be trivial to see what you are initialising.

In the example, the initialised JavaScript will open a thing called “Section 1” in an overlay. The function name is pretty much self-documenting, and it will live wherever your team decided functions to live.

Another approach is the syntax ConditionerJS uses:

<a href="http://maps.google.com/?ll=51.741,3.822"
   data-module="ui/Map"
   data-conditions="media:{(min-width:40em)} and element:{was visible}"> ... </a>

Conditioner (it’s frizz-free) does not use verbs as a naming convention, but expects module names in a data-module attribute. All modules use the same convention for declaring they exist. Again, there is no confusion as to which behaviour is being triggered.

In the above examples agreed upon data attributes are used to declare behaviour. The reason why I think they are intuitive, is that they look very similar to standard HTML. Take this link:

<a href="#section1" 
   title="Section">
Section</a>

We could see this as a ‘link’ module, that has two options set in its attributes: href tells the browser where it needs to take the user, title sets up which text to display in a tooltip when the user hovers a link.

Or this image:

<img src="temple.jpg" alt="Four monks in front of a temple" />

This ‘image’ module comes with two settings: src tells the browser where the image is, alt can be used by browsers to display to users if fetching the image fails. With srcset and sizes, even more attributes are available to trigger behaviour.

In summary: custom attributes are great to declare custom behaviour, because HTML already has lots of native attributes that declare native behaviour. This doesn’t mean we’re doing behaviour in HTML: we’re doing behaviour in JavaScript, and are merely using the markup to declare the nature and conditions of this behaviour.

How many times you’re triggering

When you trigger JavaScript for each occurrence of classname or attribute, you do not know how many times you are triggering it, says Adam.

Correct, but is this a problem? This phenomenon is quite common in other parts of our work. In a component based approach to markup, you also don’t know how many times a heading or a <p> is going to be on a given page. In CSS, you also style elements without knowing on which pages they will exist (or not), or how many times.

Hiding complexity

[Defining behaviour in markup] is magical. […] Automagical loops obfuscate complexity. They make things appear simple when they’re not.

It’s unfair to markup-based initialisation concepts to label them ‘magical’, as if that is something we should at all times avoid. The label makes it sound like there is a simpler and more sensical alternative, but is there?

We are always going to be applying script to DOM elements, whether we define where scripts run in the scripts themselves, or in the markup they apply to. Neither is more or less magical.

Besides, if using classes or attributes to trigger behaviour is magical, is using classes to trigger CSS also magical?

For example:

h2 { color: red; }

Your browser ‘automagically’ applies a red colour to all the headings. Its algorithms for this are hidden away. That’s good. We can declare a colour, without knowing exactly how our declaration is going to be applied. This is very powerful. It is this simplicity that has helped so many people publish great stuff on the web.

The same goes for my HTML examples earlier. The fact that link title tooltips and alt texts are just attributes, makes them easier to use. The fact that the browser’s logic to ‘use’ them is obfuscated is a benefit, if anything.

Abstracting the complexities of initialisation away also helps to keep code DRY. There is one method for initialisation, which is testable and maintainable. Everything else is just suitable function names declared on well-structured HTML, which themselves are also testable and maintainable.

Conclusion

I think defining behaviour in markup is absolutely fine, and it is probably one of the most powerful ways to define behaviour out there. Looping through elements with specific attributes helps developers write DRY, maintainable and testable JavaScript code. Not looping through elements would do away with most of those advantages, without any clear wins.

There is something magical about how DOM structures work together with the styles and scripts that are applied to them, but that will always be the case, regardless of where our initialisation happens. I would be interested to see examples of approaches that do not use markup to infer what behaviour a script needs to apply where, but until then, I’ll be more than happy to use some of the above methods.

Comments, likes & shares

No webmentions about this post yet! (Or I've broken my implementation)