Introducing Event-Focused JS and CSS (EFJAC)
A modest proposal for organizing CSS and JS code interactions for maximum enjoyment
I’ve been building web sites professionally for fifteen years now. I’ve written a lot of CSS and Javascript over the course of my career. I’ve seen and used a lot of frameworks and methodologies that attempt to organize your stylesheets or clean up your Javascript. Following these CSS and JS guidelines still leads me to the same questions six months later: Did I fade out that box with CSS or Javascript? What does this class do again? Why do I have three different click event handlers on this button?
Recently I tried a new approach and as I was coding it I felt like I was on to something. It felt…different. I was building a page that presented a new user with some tasks we’d like to them complete before using the site: adding a profile photo, writing an introduction, etc. The user could open and close individual tasks, enter the information using the form fields inside, then save them. If the save was successful the task would be moved under a “Completed” heading. A task could also be marked as “Do Later” which would move it down under another heading.
I knew it would require a fair amount of styling, animation and interactivity to get the page to “feel good” as you used it, but I didn’t want to add something like Backbone.js to our app just for this one page. Besides, Backbone only organizes your Javascript—it doesn’t care about the styling of your page.
Likewise I could follow the guidelines in SMACSS but that would only help me keep my styles organized into several separate stylesheets. What about when I wanted to animate the closing of a task? Do I change the height in Javascript? Give it a new CSS class? Do I give it the class as a result of clicking the Save button? What if the form has an error and shouldn’t be saved? What if the user decides to close the task without filling out the form?
All of these questions and more were flying through my head. As I started coding the page a new structure seemed to form before my eyes. Which features stayed in CSS, which went into JS, and how the two interacted. I hadn’t yet seen any framework or methodology that aimed to unify the two but I appeared to be creating one as I typed.
The scheme I came up with when coding this task manager finally felt right. I organized my thoughts on the matter and it became this document. Event-Focused JS and CSS, or EFJAC (pronounced eff-jack) is a formalization of these coding guidelines.
The Caveats
My code examples assume you’re using jQuery. If not then you’ll need to substitute things like on() and trigger() with whatever your framework of choice provides, or use the native browser implementations.
Some of these rules are core concepts in EFJAC and others are just well-known practices that you’re hopefully already using. I’m formalizing them as rules here because I feel they’re also beneficial when using the EFJAC organizing principals.
EFJAC is not an all-encompassing methodology for organizing the CSS throughout your project, nor a new framework for client-side Javascript applications. It’s simply a suggestion for “what goes where” when you’re dealing with interactive pages that feature visual changes to the page and Javascript to trigger events in response to the user. This structure feels perfect for the way I work. Your mileage may vary.
Javascript code is in bold and CSS class names and HTML attributes are in italics.
The Rules
- Use the data-behavior HTML attribute to designate what an element does when clicked, touched or interacted with in some way.
- Visual changes happen via CSS only — size, color, opacity, and especially animation. Visual changes come about by adding or removing CSS classes via Javascript.
- Use class names with semantic meaning for your document (.title, .actions, .error), not literal meaning (.big, .hide, .red).
- Standard DOM events (click, change) are attached via .on() and in turn fire of meaningful custom events (open, save) via .trigger().
- Responding to custom events is what adds and removes CSS class names to control visual changes.
- When possible, events should be attached to the outer-most container of a “module”, not the actual item being interacted with.
The Explanation
That might seem like a lot to take in at once but let me explain each rule and then we’ll go over some code samples so we can see what this looks like when we put it all together.
Use the data-behavior HTML attribute
I’ll be going on and on about how CSS is for visuals and Javascript is for triggering and responding to user interaction. As a part of this separation of concerns we shouldn’t be attaching Javascript event handlers to HTML elements based on CSS classes. CSS classes are meant to be hooks for styling.
The HTML5 spec includes the ability to add custom attributes to a tag. It only asks that they be prefaced with data-. One usage pattern that has begun to emerge is to use data-behavior as the attribute that describes what the element does. You then attach a Javascript event listener based on the existence of the behavior element, not an element ID (which has to be unique across your entire document) and not a CSS class (which is meant to describe what the element is, not what it does).
Using data-behavior also makes it easy to add the behavior to new elements. If you have a dialog box that closes with a “Close” button and later decide to also add a big “X” up the top corner, give the “X” the same data-behavior attribute and it starts working the same as the “Close” button with no additional work—no need to add another class to a jQuery selector.
The standard syntax for this is $(‘[data-behavior~=open]’). This attaches to any HTML element that contains a data-behavior=”open” attribute. The ~= syntax means that “open” only needs to be somewhere in the data-behavior attribute. This means you can have multiple behaviors on one element.
Visual changes happen via CSS only
With CSS3 we’ve finally reached a point where we can animate pretty much anything via CSS only (thanks to keyframes). At this point I think it’s safe to say you can do anything you want visually via CSS. It will also look better: modern browsers can offload CSS animation for hardware acceleration and you end up with very smooth motion.
In addition this means that if an interaction can happen only via CSS, it should be. If an element changes in response to a hover, by all means add a :hover pseudo-element selector to your CSS and do it there. No need to get Javascript involved. If you want stay as pure as possible, however, you could actually have Javascript bind to the hover event, trigger a custom event (more on that soon) and manually add and remove a CSS class to designate that the element is being hovered over.
Since all animation and visual change is handled via CSS we should never see css(), fadeOut() or animate() in our Javascript. Any changes happen because you’re adding or removing a CSS class that defines a different visual appearance (background-color, height, opacity, etc.).
Use class names with semantic meaning
I love Bootstrap as much as the next guy. It’s completely changed how new apps are developed. (Remember how every other site you went to on the internet looked like the default Bootstrap UI?) Something I feel they got wrong, however, was using class names that were literal, not semantic: .small, .hide, .unstyled, and plenty more. (Plenty of styles are great: .alert, .danger, .warning, etc.)
You should give your elements class names that describe what they are, not what they look like. The actual CSS code itself decides what it looks like. If you add a class name like .small and then the product manager decides that text should actually be the same size as the proceeding paragraph, just grayed out a bit, you either change the CSS styles (which changes all .small elements across the site, even the ones you actually want to be small), remove the .small declaration from the HTML (but the whole promise of CSS is to change styles without touching the HTML) or create a new overriding CSS rule that changes that one instance of what it means to be .small. Now small no longer means small.
All of these options are hacks. The solution is not use a class name that describes what the text should look like in the first place. If the class had been something like .disclaimer instead, you wouldn’t need to touch the HTML and simple changes to the CSS would be all you need.
Standard DOM events are mapped to meaningful custom events
This is where EFJAC starts to come into its own. Standard browser DOM events like click or change are mapped to custom event names that are meaningful for your application (like open or saved). DOM events are a great ground-level interaction element for your application, but they’re a little too close to the bare metal.
Custom events make it much easier to form a consistent mental picture of what’s happening as users interact with your application. Yes, they clicked that button (labeled “Save”) but what that button actually does is save the form. This practice helps you think more about what your application is doing in respect to itself instead of responding to generic browser interaction events.
Think of native DOM events as “what the user did” or “the action” and your custom events as sort of “what the user wants to do” or “the intent.” While what the user did is important, you need to know what that means in the scope of your application. The user clicked their mouse. Great. What you really want to know is that the user tried to save the form. Custom events is all about getting at what the user intended to do with your app.
You can map the click event to a custom open event and call it a day, but you’ll find it even nicer to namespace your events. Namespacing is a feature of the jQuery event model that I don’t see used very often which is a shame because it’s quite awesome.
Instead of naming your event open you might call the event open.task . If you have several open events flying around now you can distinguish them. It especially helps when browsing through the code and looking for something specific (a huge time and mental drain that people often ignore).
Another benefit is that you can remove a specific namespaced event handler. If you attached two open events via a call to on() and later wanted to remove one with off(‘open’) you would be stuck: all open events are now gone. But calling off(‘open.task’) removes only that single event.
Responding to custom events adds/removes CSS classes
So we know we’re supposed to animate our elements using only CSS but how do we add or remove CSS classes to actually get an animation to happen? That’s where the custom events come in. CSS classes are added and removed in response to custom events.
Events should be attached to the outer-most container
Javascript events should almost always be attached on parent containers instead of the elements themselves. This creates far fewer event listeners floating around and is more performant. If you have 100 list items and each contains a checkbox, it is better to attach a single event listener to the parent <ul> that watches for checkbox changes in any of its children than to create 100 change events, one for each and every checkbox.
This is accomplished by passing a second parameter to the handler, before the callback function:
$('.task').on('click', '.open', function() { ... });
The listener is attached to the .task container, but only responds if what was clicked had the class .open . (I realize this breaks the first rule of using data-behavior to target elements but I just wanted the example code to be as short and readable as possible.)
You’ll find this technique to be a breath of fresh air when you look at your Javascript and see all of your events attached to a single .task element, rather than .task a, .task .title span, .task[class=open]…We’ve all seen these littered throughout others’ code (but never ours, of course).
You will run across situations where it isn’t possible, or doesn’t “feel” right to attach an event handler to a parent, but you should try whenever possible.
The Summary
Use CSS for anything visual. Bind event listeners to elements with a data-behavior attribute and fire your own custom events when things happen. Listen for those custom events and add/remove CSS classes to make visual changes. Got it?
The Code
I’ll be building something very similar to my task screen above, albeit a simplified version. First, some HTML:
<ul id="tasks">
<li class="task">
<h2>Task name</h2>
<p>Task description</h2>
<a href="#" class="open-task">Open</a>
<form action="/echo/json>
<input type="text" name="name">
<a href="#" class="save">Save</a>
</form>
</li>
</ul>
The form submits to a test endpoint that always returns successfully. This is just enough code to get us going. Let’s make it somewhat pretty:
body {
background-color: #eeeeee;
}ul {
margin: 0;
padding: 0;
list-style: none;
}.task {
overflow: auto;
background-color: #ffffff;
padding: 10px;
}h2 {
margin: 0;
font-size: 16px;
font-weight: bold;
}p {
margin: 0;
}.open-task {
display: block;
float: right;
}form {
text-align: center;
}
That gets us here:
You can follow along, use the interface and play with the code at JSFiddle:
The first thing we want to do is hide the form by default, so let’s add an attribute to remove it from display:
form {
text-align: center;
display: none;
}
Now we’ve established the base state of our task which is to be “closed” with no form visible. To designate our task as “open” we’re going to add a class .open to the .task container. Let’s create the style that makes that happen:
.task.open form {
display: block;
}
When something with the class .task also has the class .open any forms inside should be visible.
So how do we get that .open class added to the task? That’s a job for Javascript. Clicking the “Open” link should open the form. We need a data-behavior on the link:
<a href="#" class="open-task" data-behavior="open">Open</a>
Then we listen for the default browser click behavior by looking for elements with data-behavior=”open” and trigger our own custom event on the task container:
$('.task').on('click', '[data-behavior~=open]', function() {
$(this).closest('.task').trigger('open.task');
return false;
});
When someone clicks on an element with the behavior of open we trigger our custom event open.task on the task container itself, not the link that was clicked (closest() goes up the DOM and finds the first element that matches the parameter passed in). All of our events are collected on a single element which makes it easy to re-use this code (anywhere a task goes all the events go with it), easier to maintain in the future (we have one place to look to find all our application-specific logic) and just plain easier to think about (everything having to do with a task happens on the .task element).
return false makes sure that the link to # doesn’t actually get followed (which would make the browser jump to the top of the page).
Now we’re ready to show our form. We’ll watch for our new custom event on the task and add the class:
$('.task').on('open.task', function() {
$(this).addClass('open');
});
Ahhh, doesn’t that look nice? Try it out at JSFiddle:
Let’s add some functionality. We’ll allow the user to close the task back up without completing it. We need a close button:
...
<a href="#" class="open-task" data-behavior="open" href="#">Open</a>
<a class="close-task" data-behavior="close" href="#">Close</a>
...
And a style that will hide it by default:
.close-task {
display: none;
float: right;
}
The float: right; will put it in the same position the “Open” link is in currently. Remember that the task being closed is the default state, so this style means that the close button will be hidden by default. The functionality will work like so: when the task is open, show the form and show the “Close” link. When the task is closed hide the form and show only the “Open” link.
Let’s add a style that will hide the “Open” link and show the “Close” link when the task is open:
.task.open .close-task {
display: block;
}.task.open .open-task {
display: none;
}
And the Javascript that watches for the Close button to be clicked and removes the .open class on the task:
$('.task').on('click', '[data-behavior~=close]', function() {
$(this).closest('.task').trigger('close.task');
return false;
});$('.task').on('close.task', function() {
$(this).removeClass('open');
});
Okay, let’s focus on the form. Let’s pretend, for this example’s sake, that saving the form will always be successful. In that case we’ll close the form and dim the task to indicate that it’s complete.
We need a data-behavior on the “Save” link:
<a href="#" class="save" data-behavior="save">Save</a>
And we’ll attach a listener to that behavior and trigger our own event:
$('.task').on('click', '[data-behavior~=save]', function() {
$(this).closest('.task').trigger('save.task');
return false;
});
Saving a task means we’re going to make an AJAX call to the server to save the data. Listen for save.task and AJAX away:
$('.task').on('save.task', function() {
var $this = $(this);
$.post('/echo/json').success(function(data) {
$this.trigger('saved.task', data);
});
});
We fire our off our AJAX request (to a JSFiddle test endpoint) and assume it returns successfully. We listen for the success event and once again trigger our own custom event.
“Another custom event?!” you’re saying? Yes. Any time our task is in a new state we want an event to go with it. Next week our product manager requests that completing task #1 should also cause task #2 to be marked as complete. Easy: just trigger complete.task on task #2 and everything that needs to happen happens, no need to worry about appending the second task’s unique id attribute to a jQuery selector or messing with .next() and .prev() to try and select the right elements.
Notice that we’re passing a second parameter to our event trigger: $this.trigger(‘saved.task’, data); This is another under-used feature of jQuery’s event model. It allows you to pass around extra parameters to go along with the event itself.
When our form is saved we want to mark it as complete. Let’s create an event:
$('.task').on('saved.task', function(e, data) {
$(this).trigger('complete.task');
});
We’re receiving that extra data parameter that we passed in above (although we aren’t doing anything with it in our example here). Completing a task should close and dim it. We already have an event for closing a task so we can just trigger it when we hear the complete.task event:
$('.task').on('complete.task', function() {
$(this).trigger('close.task');
});
This is where these custom events start to be fun. We now have events with meaningful names that will put our task into known visual states. No more chaining a bunch of jQuery selectors together to add a class or change a background color or fade something out. Just close.task and everything happens for us.
Let’s also give the task a class of .complete so we can attach our “dimmed” visuals:
$('.task').on('complete.task', function() {
$(this).trigger('close.task');
$(this).addClass('complete');
});
Finally, we’ll add a CSS class that determines what a complete task looks like:
.task.complete {
background-color: #dddddd;
color: #999999;
}.task.complete .open-task {
display: none;
}
We dim the background and text and also hide the Open link so it can’t be changed.
The interaction on our box isn’t very exciting—everything pops in and out of existence as we click around. Check out one last Fiddle that includes a touch of animation to make the interaction a little more pleasant. It also tightens up the JS by chaining all of the listeners together instead of re-selecting $(‘task’) over and over again (thanks ultimatedelman for the suggestion). It does sacrifice some of the code scanability for performance, however:
A quick note on code organization
I like to organize the Javascript by keeping all of the DOM event listeners together and all of my custom event listeners together. The DOM events can usually be ignored completely when you come back to this code in the future. Reading through the custom event handlers almost tells a little story about how your UI works: “when a form is saved it’s marked as completed and closed.”
I use SASS in my own projects so my stylesheets feel much cleaner than the raw CSS used here (all the duplicate .task.open declarations, for example). Here’s what these stylesheets look like with SASS:
Conclusion
There you have it: what I hope is a sensible and semantic approach to organizing your CSS and JS for maximum enjoyment and maintainability. Hopefully the name makes sense now: all of our CSS and JS is focused around events that we create that have meaning for our application.
You’re probably thinking this is a lot more code than I’m used to writing for something that seems so simple. And you’re right, it is more code. But making something flexible and maintainable will almost always include more code than just writing a quick one-off solution. The hope is that the extra typing actually makes it easier to maintain the code in the future.
Don’t underestimate the amount of time and mental overhead involved in figuring out how your own code works six months or a year later. Sometimes having more, but cleaner and well-organized code actually makes the task easier. Writing everything as a one-off will more often than not give your future self nightmares. Notice that when you view http://jsfiddle.net/cannikin/BZNQ6/6/ you can just scan down the “Custom Events” section of the JS and see all your behaviors in one neat line: open.task, close.task, save.task, saved.task, complete.task. You can find everything with one quick scan and the story of your app unfolds as you read down the page.
I would appreciate any feedback and suggestions for improvement! Leave comments here or on my blog: http://goo.gl/R0Z6O6
Let’s make the web better.