Separation of behavior from markup. Are we using best practices for front-end code?

Petr Gazarov
policygenius-engineering
4 min readJun 16, 2016

--

Hello everyone. This is my first-ever dev post, so give me some love! :-) Ty. Next, here we go:

JavaScript is a large part of web development, so it’s important that we apply best practices to developing front-end code. I think most people agree with the benefits of Unobtrusive JavaScript for both end users and developers. One of its core principles which we’ll talk about here is separation of behavior from markup.

Why is it important?

Separation of structure and behavior helps keep the codebase manageable and developers happy. Part of it is simply code organization. You wouldn’t want to pile all your files together in one folder, would you?

Another part is that it helps make changes easy. By separating JavaScript from HTML, we reduce coupling and dependency. Below I describe a pattern that we use at PolicyGenius to keep our front-end code fresh and easily changeable.

Problem: where do you put JavaScript for mostly static web pages?

If you have ever written JavaScript for your web application, you have likely gone through the cycle — think about where to put your script, then don’t like the idea, but nevertheless proceed to write it directly in the template or include a script tag there. Sound familiar?

Well, you say, how about React, Backbone and other frameworks that are designed to turn your front-end mess into something that others can look at without shrugging?

I love frameworks, and I have worked with a few front-end frameworks. However, consider an app where you have a modest amount of JavaScript and most of it is presentational (not AJAX-heavy). In cases like this, it’s common to use “vanilla” JavaScript.

Try and fail: page-specific JavaScript

One of the patterns we’ve used in the past is page specific JavaScript. The idea is to use server-side controller and action names to generate unique page identifiers that can be then used to run specific (accordingly-named) JavaScript files or objects. This gives a bit “magical” but effective way to run only desired script. However, the flexibility it offers comes with several trade-offs:

  • In order to reuse code, we’d have to extract it from the namespaced files, which in the end adds complexity and makes it hard to find what you’re looking for.
  • Some JavaScript views become large; breaking them up into partials once again creates more files.

In the end, page-specific JavaScript introduced more complexity in terms of code organization than it was bringing us benefits.

Clean JavaScript behaviors using elemental.js

elemental.js is a tiny library that helps you encapsulate JavaScript behaviors in a modular way. These behaviors are functions that get run whenever the like-named data attribute is present on an html element, relative to that element.

Generally, behaviors should not have coupling or have knowledge of the outside scope. I also try to make them as general as possible, so they are more reusable down the line.

In Ruby world, we love that everything is an object and for me, this pattern struck a pleasant resemblance with how we think about methods and objects.

An example

Let’s say that we want to make our buttons shake on click when they are disabled. Here is how this can be implemented.

behaviors/shaking_button.coffee

YourApp.Behaviors.ShakingButton = (element) -> 
new ShakingButton(element).run()
class ShakingButton
constructor: (element) ->
@element = element

run: ->
@element.click => @_onClick()
_onClick: ->
if @element.is(‘:disabled’)
@element.addClass(‘shaking’)
@element.one ‘animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd’, =>
@element.removeClass(‘shaking’)

We defined and initialized a class, but strictly speaking it’s not necessary — just the top function would suffice. Notice the element that is passed to the behavior function: it is a JQuery object containing the html element and is the only reference to the HTML element it is “attached” to.

views/our_template.html

<input type='button' disabled='disabled' value='I love Unobtrusive Javascript' data-behavior='ShakingButton'/>

application.js.coffee

Elemental.addNamespace(YourApp.Behaviors)

Tip: Namespacing your behaviors helps to avoid name collisions.

Finally, we load all behaviors on the page. This tells Elemental to scan the page for data-behavior attributes and run the appropriate code.

Elemental.load()

shaking_button.scss

.shaking {
animation: button-shake 0.5s ease-out;
}

mixins.scss

A little bonus tip to keep our stylesheets DRY:

$button-shaking-offset: 6px;@mixin keyframes( $animationName ) {
@-webkit-keyframes $animationName {
@content;
}
@-moz-keyframes $animationName {
@content;
}
@-o-keyframes $animationName {
@content;
}
@keyframes $animationName {
@content;
}
}
@keyframes button-shake {
0% { transform: translateX(0); }
20% { transform: translateX(-$button-shaking-offset); }
40% { transform: translateX($button-shaking-offset); }
60% { transform: translateX(-$button-shaking-offset); }
80% { transform: translateX($button-shaking-offset); }
100% { transform: translateX(0); }
}

Summarizing

Here are some implications and benefits of this pattern:

  • We can now think: which behavior? instead of which page does the code belong to?
  • The scope of a particular behavior is clear, we can make it as small (a leaf element) or as large (the entire body element) as we want.
  • We can easily reuse behaviors, combine them on one page, and can quickly find the code.
  • Unit testing is straightforward — each behavior is an independent function.

Javascript attached to html elements and the ease of maintaining it makes this pattern appealing to me. I like finding ways to write behaviors that “do only one thing” and then reusing them and feeling great about it.

Like any pattern, it may or may not fit the needs and the overall design of your app, but it does provide an effective way for client-side separation of concerns.

That’s it! Would love to hear your feedback in the comments.

--

--