I began to cringe before I even finished the title. Data-binding, sure, but jQuery data-binding? The fuck? So, yeah, bear with me here. This is a weekend project that I did because I could, but there’s no way in the world I would use this on a production server. The code is brittle and has only a single directive ( click ) tied to a single state ( modal:show/hide ).

jQuery is good and bad all at once: while jQuery is peerless for low-level imperative work, it has no concept of state. You do something, and it’s done. jQuery leaves it to you to build your own bindings and record of state. This leads to the anti-pattern of any jQuery code base being that of sweaty grunt work to manage them, a cheerless repetition that in turn causes the size of any pure jQuery application bloat off to infinity (and beyond).

Example: Featherlight advertises itself a slim lightbox. It uses jQuery, and it isn’t much more complex than a modal container into which you drop content. Despite this, its source code is 557 lines because the lightbox has to bring its own state management and bindings.

This is not the fault of jQuery: the entire point of jQuery that it manipulates the DOM, and that’s all jQuery does. The actual bad is that there are developers who use it use it like a hammer to glue their site together. Other developers (me) are stuck with this, but shikata ga nai: no matter what I feel about jQuery, I still have to use it.

Bindings

Data bindings are an emergent solution to problems of state and abstraction. Time and again, when faced with this problem, developers write frameworks which permit them to focus on data and functionality, AngularJS and Knockout being two examples. I have dabbled with Angular, and I used Knockout here in the past, although at the time I did not understand the concepts used by either.

jQuery Bindings

As it happens, I ran into this same emergence at work when I had to write an email editor. Depending on where you are on the site, the page will use one three of parts: the email editor, the email getter and sender, and the email renderer. My solution was to break the email functionality into three modules which work in isolation, and broadcast their data payload once they finish their work.

I decided that since is that since my code already uses a pub/sub model, it would be fun to write my own bindings framework.

I began with Ben Alman’s pub/sub mediator, and the notion that CSS classes are bad binding. Data attributes are the future, and the future is now.

I took further inspiration from PubSubJS, a vanilla JavaScript event mediator. PubSubJS broadcasts different levels of specificity for each event, and you subscribe in to whatever fragment you need. Take the lightboxed image on this page:

Images on my site have a directive, click="modal:show:lightbox" . A click on such an image triggers an modal:show:lightbox event.

. A click on such an image triggers an event. My broadcaster breaks up the event into three components, modal , modal:show , and modal:show:lightbox . Each of these events contains full information for the original event.

, , and . Each of these events contains full information for the original event. A hook into modal:show ( target: lightbox ) displays the lightbox modal.

( ) displays the lightbox modal. The image set by a hook into modal:show:lightbox that looks at the forwarded image that triggered the event. It copies the image’s src and title attributes and sets them in the lightbox.

The rest of this article will walk through my code. I detail what happens from the click on the directive to the lightbox displaying, illustrated by code snippets, with a link to the complete code at the end.

Directives

A directive is a marker placed on a HTML element. Directives direct the controller to execute its action when the element triggers the directive’s DOM event. A data-input=" directive will fire when I type or paste, and a data-click= event will fire when I click or type. Examples with my syntax:

data-click="modal:lightbox:show" data-input="delay:game:half-life-3" data-change="release:the:hounds"

Etcetera.

Directives reverse the jQuery norm, where you write an action that targets a given event on a given CSS selector:

$('#comment-form').on('submit', function(event) { $.post('/comments/new', $(this).val()); });

My directives look like this:

data-click="modal:show:lightbox"

$(document).on('click tap', '[data-click]', function(event) { event.preventDefault(); $.broadcast($(event.currentTarget).data('click'), event.currentTarget); });

I get the value of data-click and I broadcast the directive’s action along with the triggering element. Knockout and Angular click directives supplant the normal click action of an element while permitting propagation. I treat directives similarly.

I have only added a click directive because that’s all my site needs. It is is trivial for you to add your own based on the example above. I add my directives to images on the server in a short function.

Broadcasts

As I said above, I use a simple pub/sub mediator. Although the subscribe and unsubscribe methods are identical, I have modified the broadcast method to fit my needs:

$.broadcast = function() { $.observer.triggers(arguments).forEach(function(action) { $.observer.trigger.apply($.observer, action); }); }

Trial and error made me realize that I needed to present a consistent set of arguments to subscribed methods, and I also found it useful for fragmentary subscribers to have some information about the complete event. $.observer.triggers calls $.observers.triggers.arr and $.observers.triggers.data to assist. It:

Breaks down the passed event. Duplicates the data for each fragmentary event. Adds an object with data about the complete event: { action: 'modal', method: 'show', target: lightbox } Adds all other original data (elements, strings) to the end. Returns this array of events to each be broadcast.

The event modal:show:lightbox , with a payload of the triggering element, changes from

['modal:lightbox:show', <img>]

to

[ ['modal', [{ data }, <img>]], ['modal:lightbox', [{ data }, <img>]], ['modal:lightbox:show', [{ data }, <img>]] ]

Extra parameters for the jQuery .trigger() method must be inside an array.

Subscriptions

Directives create events and broadcasts propagate them. Subscribers are the final piece of the data bindings: they react to the event. My modal lightbox has two subscribers: one to show the modal and the other to set the image.

The first subscriber listens to the modal:show event:

$.subscribe('modal:show', function(event, data) { if (data.target) { $.broadcast('modal:hide'); $('[data-modal="' + data.target + '"]').removeClass('hidden').show(); } });

The second subscriber sets the lightbox image:

$.subscribe('modal:show:lightbox', function(event, data, element) { data = { src: $(element).data('src') || $(element).attr('src'), alt: $(element).data('alt') || $(element).attr('alt') }; $('.lightbox__image').attr(data); });

Broadcasts are unidirectional: the subscriber finishes what the directive begins. The idea that a subscriber should trigger a directive didn’t make any sense to me, so I have enforced a one-way flow of events.

Conclusions

The first thing I learned is that there’s a minimum complexity that benefits from observers and directives. This code is nuclear overkill for my small site, but a richly-interactive site absolutely needs a data binding framework. Data binding removes a huge amount of pointless clutter from consideration.

My other takeaway is just how easy it is to write a (bad) data binding framework if you already have a foundation. My framework is 96 lines with all comments removed, although there is a hidden cost:

I need a server function to add directives to images.

I need more CSS to style modals.

I have to trawl through template files to find directives if I have to change them.

The distribution of directives and dependent code is a pain in the ass, but that’s a low cost for a better solution to binding function to element.

Code

Sheepie: https://github.com/bhalash/sheepie

Gist: https://git.io/vrGaD