Stop toggling classes with JS, use behaviour driven DOM manipulation with data-states

JavaScript Tagged in JavaScript Dec 17, 2013 5 mins read by Todd Motto

Using a class to manipulate the DOM? What about this idea. Using classes have many issues, the main one for me is that adding classes to elements to change their state crosses the behaviour and styling paradigm. Behaviour is separate to style and as our web becomes richer with functionality, the line between styling and state is a challenge, and also at times messy.

Anything beyond using :hover pseudo to style your website components introduces JavaScript, you might add an open class for your menu, a toggled class for your tabs, and so on. This is a nice semantic name for our hover event, but the two don’t really meet at a real solution for managing our code’s behaviour.

You’re probably doing this:

elem1.onclick = toggleClass(elem2, 'open');

This is a simple example of what we regularly do to achieve DOM state differences. This sucks!

It’s messy and hard to maintain, we have to keep writing scripts for each component and might end up repeating ourselves a lot. It also introduces styling issues if you’re adding ‘active’ class as it might conflict with another element elsewhere. It also doesn’t tell me anything about what that element’s behaviour is from looking at it in the stylesheet.

Thinking in states

When I build web applications/sites, I think about the element states. It might be open, visible, toggled or maybe selected - it all depends on what your components are doing. There are many class naming conventions that represent state that people have tried to implement, for example:

.myClass {}
.myClass.isSelected {
  // do something
}

I think this is better than using a random ‘selected’ class, it’s tied in closer to the element.

Introduce the boolean state

Boolean states in your development I highly recommend, true or false, on or off, or with our latest thinking, open or closed.

Let’s look at some selectors that I wish we could integrate and have control of…

Pseudo events

Wouldn’t it be nice to have things like this?

elem:closed {
  /* some styles */
}
elem:visible {
  /* some styles */
}
elem:open {
  /* some styles */
}
elem:toggled {
  /* some styles */
}
elem:selected {
  /* some styles */
}

Descriptive, behaviour driven, semantical CSS?…

For our menu, wouldn’t it be awesome to do this:

.menu {
  /* generic styles */
}
.menu:closed {
  display: none;
  background: blue;
}
.menu:open {
  display: inherit;
  background: red;
}

This keeps so many semantic values, as well as being so easy to read and maintain. There are many awesome pseudo events that we could semantically introduce to our code that would keep things maintainable and semantic.

Unfortunately this isn’t going to work, as this CSS is invalid…

So here’s my idea, data-state attributes for managing this problem.

data-state attributes

Using data-* attributes for managing behaviour is a really neat way of abstracting the interactive layer of our code. Reading the data-* value is supported in all browsers (IE7), but targeting HTML using attribute selectors is supported in IE8+, so bye bye IE7 on this one (it’s dead anyway). Let’s get clever!

If I told you I could replicate the above, now, wouldn’t that be sweet? Well, I can:

.menu {
  /* generic styles */
}
.menu[data-state=closed] {
  display: none;
  background: blue;
}
.menu[data-state=open] {
  display: inherit;
  background: red;
}

At first you might be thinking “what on earth…“

But, I would say that is pretty clean, and helps us a lot with our coding. I can easily tell what the code is doing, and there are no adding or removing of classes happening here. I am merely going to be toggling the value of the data-state attribute, and the CSS will do its work.

Toggling the data-state

This is the easy part, and requires only a few lines of code to actually do it. As because we are using a data-state namespace, I can create a reusable function, pass it some arguments and bind it to events:

elem.setAttribute('data-state', elem.getAttribute('data-state') === A ? B : A);

This line of code sets a data-state attribute, checks the current value and then uses the alternate value - the world’s most simplest toggle! A and B here are of course our two values (states) that we want to toggle, which might look like this:

elem.setAttribute('data-state', elem.getAttribute('data-state') === 'open' ? 'closed' : 'open');

This method uses the ternary operator, a shorthand if statement.

Putting it altogether, we might do the following and create a function tied in with our menu:

var nav = document.querySelector('.nav__toggle');
var toggleState = function (elem, one, two) {
  var elem = document.querySelector(elem);
  elem.setAttribute('data-state', elem.getAttribute('data-state') === one ? two : one);
};

nav.onclick = function (e) {
  toggleState('.nav ul', 'closed', 'open');
  e.preventDefault();
};

// ES5 using .bind() #ftw
// nav.addEventListener('click', toggleState.bind(null, '.nav ul', 'closed', 'open'), false);

I’ve created a real quick toggleState function which passes in a selector, and the two values to toggle, you’ll then need to declare the markup:

<nav class="nav">
    <a href="#" class="nav__toggle">Menu</a>
    <ul data-state="closed">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
        <li>Item 4</li>
    </ul>
</nav>

I’ve declared that the navigation shall be closed, which indicates I’ll have an event that then opens it.

Some CSS to see how it integrates:

.nav {
    background: #2284B5;
    color: #fff;
    border-radius: 3px;
}
.nav a {
    padding: 5px 10px;
    display: block;
    color: #fff;
    text-decoration: none;
}
.nav ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
.nav ul li {
    padding: 5px 10px;
}
/* semantic data states! */
.nav ul[data-state=closed] {
    display: none;
}
.nav ul[data-state=open] {
    display: inherit;
}

Output below:

If you inspect element and then check the value of the data-state attribute being toggled, you’ll see the simplicity of the boolean state.

Of course this is looking at the future of how we can structure our website and webapp components, but I’ve been using it for a long time and am really happy with how seamless it fits into a workflow - and how much code and time I save.

:)

Angular Online Courses Angular shields
Ultimate Angular

Learn Angular the right way

Join my online courses to fully master Angular, TypeScript and NGRX.

Explore Courses Navigation arrow