Modular DOMReady

A year ago, I wrote “Smart DOMReady — Unmess Your JavaScripts Events” and let’s face it: it’s a mess. I’ve come to realize that JavaScript shouldn’t be treated different than CSS. If you don’t want to get lost in your CSS, you’ll want to module your classes in a way that allows you to reuse each piece of layout independently no matter where it is in your DOM instead of styling each page one by one.

What about them interactive modules?

Use case

I have this search tool that I want to reuse in different pages. I happen to know my way in JavaScript and want to progressively enhance the user experience with some nifty autocompletion and live search frontend scripts.

With the Smart DOMReady technique, I would do something like this:

$(document).on 'home:ready, search:ready, contact:ready', (e) ->
  # When in home, search, contact

Every time I want to use the search tool in an other page, I’d have to add the said page :ready event. This can quickly become a mess depending on what kind of module you’re working on and how many times you need it.

All hail Modular DOMReady

<form action="search">
  <input type="search">
  <input type="submit" value="Search">
</form>

<script>$(document).trigger('search:rendered')</script>
$(document).on 'search:rendered', (e) ->
  # Doesn’t matter what page we’re in

That’s basically it!

The great thing about this modularity is that whether your module is in a partial or in an ajax/pjax/turbolinks response, the event will always be dispatched when this piece of HTML is going to be printed. Yay for JavaScript being tied to its HTML!

Edge case

Let’s say this time you have custom selects. You’ll most likely be in a situation where you have multiple $(document).trigger('custom-select:rendered') in one page coming from different partials. It’s also quite easy to imagine with Turbolinks or any other AJAX requests.

Here’s how I do it:

$(document).on 'custom-select:rendered', (e) ->
  for select in $('.custom-select')
    continue if select.data('initiated') == true
    select.data('initiated', true)

    # Initialize element behavior

You can now trigger as many custom-select:rendered events as you want without any risks, it’ll only initialize the newly added HTML elements or the ones that haven’t been initiated yet.

Let me know if you have simliar or completely different solutions, I’d love to hear about them.

*Edit*

See an example using Rails’ content_for.
Useful when jQuery/Zepto is loaded at the bottom of your <body>.