Planning Center Developers There are no mistakes. Just happy accidents.
In Favor of Explicit Markup
24 Sep 2014 — Web

Somewhere in the history of our young profession, <a onclick="doSomething(true);" /> was labeled as bad practice. I've started to question that assumption.

Consider the best practice alternative:

<!-- Some HTML file -->
<a class="js-hook-that-does-something" />
// Some JavaScript file
jQuery(function(){
  $('.js-hook-that-does-something').on('click', function(event){
    doSomething(true);
  });
});

This best practice ceremony erases the clarity of this code for my teammates (including my future-self). Also, this trivial example doesn't begin to address functionality we may want soon, such as passing arguments into doSomething.

The mantra of behavior in markup as bad practice emerged from the evangelism of unobtrusive javascript and progressive enhancement. While I agree with the end-goals of these strategies, tossing out the explicitness of declaring the unobtrusive-behavior in markup seems like an unnecessary and obfuscating side-effect.

Another objection is "you're polluting the global scope with all those functions!" Fair enough, that is a valid concern.

Batman.js has found a happy middle ground with their event bindings. It retains the explicitness of events being declared in the markup, with the isolation of concerns via creating subclasses of Batman.View. I don't want the overhead of an entire Batman app, but I want its explicit markup, so I created SimpleBehaviors.

The goal of SimpleBehaviors is to have the explicitness of markup for clarity when returning to code, with the cleanliness of global scope to be a responsible JavaScript citizen.

How does markup look with SimpleBehaviors?

<div data-behaviors="BluthActions">
  <ul>
    <li><a data-event-click="checkBananaStandForMoney">Check Banana Stand</a></li>
    <li><a data-event-click="driveStairCar">Drive the stair car</a></li>
  </ul>
</div>
# bluth_actions.coffee
BluthActions =
  checkBananaStandForMoney: (event) ->
    alert("There's always money here!")
  driveStairCar: (event) ->
    alert("Vroom!");

No functions leaked to the global scope, and clear intent when looking at the markup. You can even nest/override behaviors!

<div data-behaviors="BluthActions">
  <div data-behaviors="BusterActions">
    <ul>
      <li><a data-event-click="checkBananaStandForMoney">Check Banana Stand</a></li>
      <li><a data-event-click="driveStairCar">Drive the stair car</a></li>
    </ul>
  </div>
</div>
# buster_actions.coffee
BusterActions =
  driveStairCar: (event) ->
    alert("Buster is driving the stair car, mother!");

The function to execute will be looked for on BusterActions first, then bubble up to BluthActions if it's not found. Try it out:

There's still a few things I'd like to add to SimpleBehaviors.

  • Using event.stopPropogation() to control whether the event continues up the behaviors hierarchy. Presently, it simply stops after the first function is executed.
  • Passing arguments. So far I haven't needed them in Resources, but there are potential use cases where they'd be necessary.
  • Isolating scope even further, i.e. passing something like <div data-behaviors="Resources.somethingParticular" />

If you think this approach is useful, give it a shot and let me know your experience. The only dependency is jQuery (I'm using 1.11).

Thanks for stopping by.