Playing Around with DOM Styles

parenttobias

Toby Parent

Posted on January 28, 2022

Playing Around with DOM Styles

If you've been at the front-end dev thing for a while, and you've learned all you need to know about manipulating the DOM and clever javascript, this is not for you. If you're brand-new, and you're learning that we can manipulate the DOM with javascript, this will eventually be for you, but it may be too much right now.

This is directed to a very particular set of learners: when we start tinkering with the DOM, and we find ourselves doing the same thing over and over again, we should consider how we can automate a solution to that. Any time we are repeating ourselves in code, it's a candidate worth refactoring. This is sort of an exploration of that refactoring thought process.

The Problem

When going through an online curriculum and learning about "DOM manipulation with javascript", you'll often see styles being set inline. At one point, it was considered bad form to apply styles inline, but that's a different animal: the reference is saying "don't apply styles directly in your HTML." For good reason: inline styles are the highest priority in the CSS realm. When we apply styles in the HTML, and we then move on to writing a stylesheet, we can't override those inline styles without resorting to !important. And, if we find ourselves using the !important style rule? We're doing something wrong.

But there is quite a lot of usefulness to setting styles inline, via javascript. We can do so in a few ways:

  1. We could toggle a css class on a particular DOM node on or off, setting the styles for that element as we like (so long as that CSS selector rule has been defined),
  2. We could directly tinker with the domNode.style attribute, setting styles inline.

That second one is what this is all about!

Option 1

There are times we want to be able to style DOM nodes, inline, with javascript. And that is fairly easily done. We can simply set all the styles we like, directly on the node, like this:

document.querySelector("#myNode").style = "background-color:red; color: aliceblue; font-size: 1.75em;";
Enter fullscreen mode Exit fullscreen mode

And with that line, we set the styles for the element. Works great. Except that, if we have existing styles on the element, we have replaced the styles. By setting the style directly like that, we have effectively said "Whatever you had in the style before? Toss it. Here's the new style."

So if there were styles for padding, or border, or line-spacing... they have been completely replaced. Let's try someting similar, but a little different.

Option 2

Rather than setting the entire style, we can set attributes of that style. With this, the only thing being replaced is the particular attribute we are updating:

const myNode = document.querySelector("#myNode");
myNode.style.backgroundColor = 'red';
myNode.style.color = 'aliceblue';
myNode.style.fontSize = '1.75em';
Enter fullscreen mode Exit fullscreen mode

This also works. We are setting each style as we like, we aren't blowing the entire style property away. So this is better, but we are far from best.

Since we're trying to find a way to optimize or automate that, and the core javascript system might not include the functionality we want built-in, it might be a great place to consider writing one or more functions. Suppose we wanted a function that simply lets us pass in a DOM node and a color, and updates the style.color for us:

const setColor = (domNode, color)=> domNode.style.color = color;
Enter fullscreen mode Exit fullscreen mode

Seems a little silly like that. We wrote a function in three times the characters, to do what we could simply do inline. Still, it is an automated process. Let's make that a little more functional. What if we add a parameter in there, call it styleProp? That might be the style property name. Given the DOM node, the style property, and the CSS value, we can set that directly:

const setCSSProperty = (domNode, styleProp, value) => domNode.style[styleProp] = value;
// and how it might get used:
const myNode = document.querySelector('#myNode');
setCSSProperty(myNode, "backgroundColor", "red");
setCSSProperty(myNode, "color", "aliceblue");
setCCSSProperty(myNode, "fontSize", "1.75em");
Enter fullscreen mode Exit fullscreen mode

It's more generic, maybe, but it's still longer than just editing the style properties ourselves. How is this saving us time?

It isn't any shorter to type, maybe, but it is a little more abstract. We can now automate that, if we want. For my own peace of mind, and because I like currying, let's rewrite that last one as a curried function:

const setCSSOn = (domNode) =>
  (styleProp) =>
    (value) => domNode.style[styleProp] = value;
// now, we can do this:
const myNode = document.querySelector("#myNode");
const styleMyNode = setCSSOn(myNode);
styleMyNode("backgroundColor")("red");
styleMyNode("color")("aliceblue");
styleMyNode("fontSize")("1.75em")
Enter fullscreen mode Exit fullscreen mode

A curried function is useful in a case like this - note how we only pass in which node we want to style the first time? We have created a closure in that outer function, which stores the value domNode locally and reuses that each time we call styleMyNode.

I like this, this is a good direction. But it would be nice to not have to do each line like that, it's kind of ugly and not really saving us anything. So far, there is no benefit. But that last function, setCSSOn()? We want to keep that in mind. It is going to be in the sequel.

Option 3

So we have a way of setting a style on a node, functionally. That's great. But what if, like in that last one, we want to set a bunch of styles? Our function is useful, but not much more than simply setting the styles directly. We still haven't gained much.

Ah, Grasshopper, we're nearly there. What if we had a way to do this:

const myNode = document.querySelector("#myNode");
const styleMyNode= setStylesOn(myNode);

styleMyNode({
  backgroundColor: 'red',
  color: 'aliceblue',
  fontSize: '1.75em'
});
Enter fullscreen mode Exit fullscreen mode

That starts to look pretty darn juicy, doesn't it? It is clean, it says exactly what we're trying to do, it is very functional, it can be automated... and let's face it: it's darn pretty. So how do we get there?

Let's look at what it is we want to do, given the function signature. We show a function, styleMyNode, that takes a DOM node, just like the setCSSOn function we just wrote. But then, rather than taking a property name and value, we have passed in an object. That object contains the style names in javascript format (so camelCase, rather than CSS's kebab-case), as keys to the data.

So we want to go through that object, pull each "key/value" pair out, and apply it to that DOM node. Seems pretty straightforward when we talk it through like that, huh? Let's try:

const styleMyNode = (domNode) =>
  (styleObject) => {
  Object.entries(styleObject).forEach( ([key, value])=>{
    setCSSOn(domNode)(key)(value) 
  })
}
Enter fullscreen mode Exit fullscreen mode

so Object.entries takes an object, and returns an array of [key, value] pairs. In the case of our passed styleObject in the example, it looks like:

[
  ['backgroundColor', 'red'],
  ['color', 'aliceblue'],
  ['fontSize', '1.75em']
]
Enter fullscreen mode Exit fullscreen mode

Then we forEach over that outer array, giving us each of those pairs in turn. Within that forEach, we destructure the array pair into a key and a value variable - which we then pass into our handy-dandy setCSSOn function.

A nice way to use something like this might be if we need to apply similar styles to all nodes of a given class:

const myStyles = {
  border: "1px solid silver",
  borderRadius: "10px",
  margin: "5px auto",
  padding: ".25rem .5rem 0 .75rem"
};

const myCells = document.querySelectorAll(".cell");

myCells.forEach((cell)=>styleMyNode(cell)(myStyles));
Enter fullscreen mode Exit fullscreen mode

Now, that is cleaner - we can see at a glance what we're doing. For each element in myCells, we are calling styleMyNode and applying that style object we created. Wonderful!

Of course, not entirely what we like. Ideally, we might have been able to just call myCells.forEach(applyMyStyles) and not define that function inline. The problem we have is that we arranged the curried parameters in a particular order. Nothing wrong with that, until we see it in use.

As a rule, it's useful to arrange curried parameters from those less likely to change (the "static parameters") to those more likely to change (the "dynamic parameters"). In this case, as each element is getting the same style object, the static parameter is that style object - because each time we call the function, we want the same style object, but a different DOM node.

In order to fix this, in order to use this as a passable function, we need simply reorder the parameters:

const applyStylePair = ((domNode)=>
                        ([key, value])=> setCssOn(domNode)(key)(value))

const setStyles = (styleObject) =>
  (domNode) =>Object.entries(styleObject).forEach(applyStylePair(domNode))

// with that, we can now:
const applyMyStyles = setStyles(myStyles);

const myCells = document.querySelectorAll(".cell");

// now, when we pass the applyMyStyles function, it already
//  encloses that `myStyles` reference. We simply tell each
//  member of `myCells` to run that function, on that cell.
myCells.forEach(applyMyStyles)
Enter fullscreen mode Exit fullscreen mode

Conclusion

Yes, it seems like work. Have we gained anything for all this? Well, sort of. Hopefully, we gained some understanding of ways to tinker with the DOM, and with objects in general. We have had a bit of a thought experiment, we've seen some impact regarding the order of parameters, and we've had a bit of fun.

Is this something you'll use every day? Not likely. Being able to apply multiple styles to multiple DOM nodes is fun and educational, but practically speaking, there may be other and better ways up this mountain. Tools like React's styled components do this same thing, and we often use them without necessarily thinking about what that represents.

💖 💪 🙅 🚩
parenttobias
Toby Parent

Posted on January 28, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related