Once Upon a Browser
Browse through some truly web-native artwork by Eric, and read all about it:
There is a lot, and I mean a lot, of room for variability in web technologies. We work very hard to tame it, to deny it, to shun it.
Browse through some truly web-native artwork by Eric, and read all about it:
There is a lot, and I mean a lot, of room for variability in web technologies. We work very hard to tame it, to deny it, to shun it.
I’ve worked with Web Components a little bit over the last few, but really struggled to understand the use case for them.
Until this week.
Between Jeremy Keith’s article on HTML Web Components, plus using one for a client project with NASA, something just clicked in my brain finally.
I’m now convinced that they’re the best way to author DOM manipulation libraries.
Eric mentioned the JavaScript closest
method. I use it all the time.
When I wrote the book DOM Scripting back in 2005, I’d estimate that 90% of the JavaScript I was writing boiled down to:
It wasn’t just me either. I reckon that was 90% of most JavaScript on the web: progressive disclosure widgets, accordions, carousels, and so on.
That’s one of the reasons why jQuery became so popular. That first step (“find these particular elements in the DOM”) used to be a finicky affair involving getElementsByTagName
, getElementById
, and other long-winded DOM methods. jQuery came along and allowed us to use CSS selectors.
These days, we don’t need jQuery for that because we’ve got querySelector
and querySelectorAll
(and we can thank jQuery for their existence).
Let’s say you want to add some behaviour to every button
element with a class
of special
. Or maybe you use a data-
attribute instead of the class
attribute; the same principle applies. You want something special to happen when the user clicks on one of those buttons.
querySelectorAll('button.special')
to get a list of all the right elements,addEventListener('click')
to each element.That’s fine for a while. But if you’ve got a lot of special buttons, you’ve now got a lot of event listeners. You might be asking the browser to do a lot of work.
There’s another complication. The code you’ve written runs once, when the page loads. Suppose the contents of the page have changed in the meantime. Maybe elements are swapped in and out using Ajax. If a new special button shows up on the page, it won’t have an event handler attached to it.
You can switch things around. Instead of adding lots of event handlers to lots of elements, you can add one event handler to the root element. Then figure out whether the element that just got clicked is special or not.
That’s where closest
comes in. It makes this kind of event handling pretty straightforward.
To start with, attach the event listener to the document:
document.addEventListener('click', doSomethingSpecial, false);
That function doSomethingSpecial
will be executed whenever the user clicks on anything. Meanwhile, if the contents of the document are updated via Ajax, no problem!
Use the closest
method in combination with the target
property of the event to figure out whether that click was something you’re interested in:
function doSomethingSpecial(event) {
if (event.target.closest('button.special')) {
// do something
}
}
There you go. Like querySelectorAll
, the closest
method takes a CSS selector—thanks again, jQuery!
Oh, and if you want to reduce the nesting inside that function, you can reverse the logic and return
early like this:
function doSomethingSpecial(event) {
if (!event.target.closest('button.special')) return;
// do something
}
There’s a similar method to closest
called matches
. But that will only work if the user clicks directly on the element you’re interested in. If the element is nested within other elements, matches
might not work, but closest
will.
Like Eric said:
Very nice.
I can see myself almost certainly needing to use this clever technique at some point so I’m going to squirrel it away now for future me.
An interesting alternative to using the full Vue library, courtesy of Vue’s creator:
petite-vue
is an alternative distribution of Vue optimized for progressive enhancement. It provides the same template syntax and reactivity mental model with standard Vue. However, it is specifically optimized for “sprinkling” small amount of interactions on an existing HTML page rendered by a server framework.
This is very handy indeed! Quick one-line JavaScript helpers categorised by type.
And, no, you don’t need to npm install
any of these. Try “vendoring” them instead (that’s copying and pasting to you and me).
Find out how much smaller your JavaScript could be.
I’m a big fan of HTML5’s datalist
element and its elegant design. It’s a way to progressively enhance any input
element into a combobox.
You use the list
attribute on the input
element to point to the ID of the associated datalist
element.
<label for="homeworld">Your home planet</label>
<input type="text" name="homeworld" id="homeworld" list="planets">
<datalist id="planets">
<option value="Mercury">
<option value="Venus">
<option value="Earth">
<option value="Mars">
<option value="Jupiter">
<option value="Saturn">
<option value="Uranus">
<option value="Neptune">
</datalist>
It even works on input type="color"
, which is pretty cool!
The most common use case is as an autocomplete widget. That’s how I’m using it over on The Session, where the datalist
is updated via Ajax every time the input is updated.
But let’s stick with a simple example, like the list of planets above. Suppose the user types “jup” …the datalist will show “Jupiter” as an option. The user can click on that option to automatically complete their input.
It would be handy if you could automatically submit the form when the user chooses a datalist option like this.
Well, tough luck.
The datalist
element emits no events. There’s no way of telling if it has been clicked. This is something I’ve been trying to find a workaround for.
I got my hopes up when I read Amber’s excellent article about document.activeElement
. But no, the focus stays on the input when the user clicks on an option in a datalist.
So if I can’t detect whether a datalist has been used, this best I can do is try to infer it. I know it’s not exactly the same thing, and it won’t be as reliable as true detection, but here’s my logic:
Here’s how that translates into DOM scripting code:
document.querySelectorAll('input[list]').forEach( function (formfield) {
var datalist = document.getElementById(formfield.getAttribute('list'));
var lastlength = formfield.value.length;
var checkInputValue = function (inputValue) {
if (inputValue.length - lastlength > 1) {
datalist.querySelectorAll('option').forEach( function (item) {
if (item.value === inputValue) {
formfield.form.submit();
}
});
}
lastlength = inputValue.length;
};
formfield.addEventListener('input', function () {
checkInputValue(this.value);
}, false);
});
I’ve made a gist with some added feature detection and mustard-cutting at the start. You should be able to drop it into just about any page that’s using datalist. It works even if the options in the datalist are dynamically updated, like the example on The Session.
It’s not foolproof. The inference relies on the difference between what was previously typed and what’s autocompleted to be more than one character. So in the planets example, if someone has type “Jupite” and then they choose “Jupiter” from the datalist, the form won’t automatically submit.
But still, I reckon it covers most common use cases. And like the datalist
element itself, you can consider this functionality a progressive enhancement.
I decided to implement almost all of the UI by just adding & removing CSS classes, and using CSS transitions if I want to animate a transition.
Yup. It’s remarkable how much can be accomplished with that one DOM scripting pattern.
I was pretty surprised by how much I could get done with just plain JS. I ended up writing about 50 lines of JS to do everything I wanted to do.
I made the website for the Clearleft podcast last week. The design is mostly lifted straight from the rest of the Clearleft website. The main difference is the masthead. If the browser window is wide enough, there’s a background image on the right hand side.
I mostly added that because I felt like the design was a bit imbalanced without something there. On the home page, it’s a picture of me. Kind of cheesy. But the image can be swapped out. On other pages, there are different photos. All it takes is a different class name on that masthead.
I thought about having the image be completely random (and I still might end up doing this). I’d need to use a bit of JavaScript to choose a class name at random from a list of possible values. Something like this:
var names = ['jeremy','katie','rich','helen','trys','chris'];
var name = names[Math.floor(Math.random() * names.length)];
document.querySelector('.masthead').classList.add(name);
(You could paste that into the dev tools console to see it in action on the podcast site.)
Then I read something completely unrelated. Cassie wrote a fantastic article on her site called Making lil’ me - part 1. In it, she describes how she made the mouse-triggered animation of her avatar in the footer of her home page.
It’s such a well-written technical article. She explains the logic of what she’s doing, and translates that logic into code. Then, after walking you through the native code, she shows how you could use the Greeksock library to achieve the same effect. That’s the way to do it! Instead of saying, “Here’s a library that will save you time—don’t worry about how it works!”, she’s saying “Here’s it works without a library; here’s how it works with a library; now you can make an informed choice about what to use.” It’s a very empowering approach.
Anyway, in the article, Cassie demonstrates how you can use custom properties as a bridge between JavaScript and CSS. JavaScript reads the mouse position and updates some custom properties accordingly. Those same custom properties are used in CSS for positioning. Voila! Now you’ve got the position of an element responding to mouse movements.
That’s what made me think of the code snippet I wrote above to update a class name from JavaScript. I automatically thought of updating a class name because, frankly, that’s how I’ve always done it. I’d say about 90% of the DOM scripting I’ve ever done involves toggling the presence of class values: accordions, fly-out menus, tool-tips, and other progressive disclosure patterns.
That’s fine. But really, I should try to avoid touching the DOM at all. It can have performance implications, possibly triggering unnecessary repaints and reflows.
Now with custom properties, there’s a direct line of communication between JavaScript and CSS. No need to use the HTML as a courier.
This made me realise that I need to be aware of automatically reaching for a solution just because that’s the way I’ve done something in the past. I should step back and think about the more efficient solutions that are possible now.
It also made me realise that “CSS variables” is a very limiting way of thinking about custom properties. The fact that they can be updated in real time—in CSS or JavaScript—makes them much more powerful than, say, Sass variables (which are more like constants).
But I too have been guilty of underselling them. I almost always refer to them as “CSS custom properties” …but a lot of their potential comes from the fact that they’re not confined to CSS. From now on, I’m going to try calling them custom properties, without any qualification.
Amber documents a very handy bit of DOM scripting when it comes to debugging focus management: document.activeElement
.
Here’s a short clear introduction to DOM scripting.
This is a great way to organise code snippets—listed by use case, and searchable too!
Next time you’re stuck on some DOM scripting, before reaching for a framework or library, check here first.
I’m constantly forgetting the difference between the async
attribute and the defer
attribute on script
elements—this is a handy explanation.
HTML lets you create the structure of a website.
CSS lets you make the website look nice.
JavaScript lets you change HTML and CSS. Because it lets you change HTML and CSS, it can do tons of things.
If you ignore the slightly insulting and condescending clickbaity title, this is a handy run-down of eight browser features with good support:
addEventListener()
,scrollTo()
,setTimeout()
and setInterval()
,defaultChecked
property for checkboxes,normalize()
and wholeText
for strings of text,insertAdjacentElement()
and insertAdjacentText()
,event.detail
, andscrollHeight
and scrollWidth
.Scott writes up that super smart transclusion trick of his.
Woah! This is one smart hack!
Scott has figured out a way to get all the benefits of pointing to an external SVG file …that then gets embedded. This means you can get all the styling and scripting benefits that only apply to embedded SVGs (like using fill
).
The fallback is very graceful indeed: you still get the SVG (just not embedded).
Now imagine using this technique for chunks of HTML too …transclusion, baby!
We have a saying at Clearleft:
Everything is a tiny lesson.
I bet you learn something new every day, even if it’s something small. These small tips and techniques can easily get lost. They seem almost not worth sharing. But it’s the small stuff that takes the least effort to share, and often provides the most reward for someone else out there. Take for example, this great tip for getting assets out of Sketch that Cassie shared with me.
Cassie was working on a piece of JavaScript yesterday when we spotted a tiny lesson that tripped up both of us. The script was a fairly straightforward piece of DOM scripting. As a general rule, we do a sort of feature detection near the start of the script. Let’s say you’re using querySelector
to get a reference to an element in the DOM:
var someElement = document.querySelector('.someClass');
Before going any further, check to make sure that the reference isn’t falsey (in other words, make sure that DOM node actually exists):
if (!someElement) return;
That will exit the script if there’s no element with a class of someClass
on the page.
The situation that tripped us up was like this:
var myLinks = document.querySelectorAll('a.someClass');
if (!myLinks) return;
That should exit the script if there are no A
elements with a class of someClass
, right?
As it turns out, querySelectorAll
is subtly different to querySelector
. If you give querySelector
a reference to non-existent element, it will return a value of null
(I think). But querySelectorAll
always returns an array (well, technically it’s a NodeList but same difference mostly). So if the selector you pass to querySelectorAll
doesn’t match anything, it still returns an array, but the array is empty. That means instead of just testing for its existence, you need to test that it’s not empty by checking its length
property:
if (!myLinks.length) return;
That’s a tiny lesson.
Chris Ferdinandi is a machine!
A vanilla JS roadmap, along with learning resources and project ideas to help you get started.