Organize jQuery code without a framework
When I look for ways to organize my jQuery code on the internet, what I find are usually advices on what framework I should use. Using a framework is a good idea because good frameworks enforce good design practices. But the core of any framework is just a set of rules, so if you want to keep things really simple, you can adopt some of those rules to write good JavaScript without any additional libraries. This is particularly useful when you just need to add some dynamic functionality to a html page and using something like Backbone or Angular seems an overkill. I use this technique for small projects or when prototyping interfaces.
Here are two rules I adopted from frameworks for my jQuery code:
- Write isolated pieces of code that only control a specific element on the page. I will call these pieces controllers for convenience.
- Define behavior of a controller only by attaching event handlers to the element it controls.
So each controller is bound to a specific element and represents its behavior. It means the only way to interface with a controller is to trigger events on its element. This is a good thing, because each controller only acts within scope of its element and its children, so it’s easy to know where behavior of each element is defined. Controller cannot respond to events outside of its scope.
The only thing needed to implement such controller is jQuery event mechanism. I will use CoffeeScript for examples.
$ -> # this unnamed closure is actually our controller
# This is controller's element. We can also reference element
# by id but it's better to explicitly mark that this element is
# bound to a controller.
# We also get a chance to name our controller.
$element = $('[data-ctrl=MyCtrl]')
# Here we attach event handlers to $element.
# These event handlers act as controller's public interface
$element
.on('click', '.some-child-element', someEventHandler)
.on('customEvent', (event) ->
somePrivateMethod() #inline event handler
)
#Private functions of a controller
someEventHandler = (event) ->
alert('event!')
somePrivateMethod = ->
alert("you can't access me from outside")
here’s the element this controller controls:
<div data-ctrl="MyCtrl">
<a class="some-child-element">A link!</a>
</div>
This is remarkably close to a full featured controller object. It hides event handlers and rest of implementation in private methods and exposes its behavior via public methods. Since I want this controller to represent behavior of a specific DOM element, I expose its behavior through event handlers, so I can only interface with controller through his element. If the element is gone, the controller is left to live a life of oblivion unless another matching element comes to rescue it.
A controller designed this way can enclose functionality of any widget on HTML page, such as navbar, menu or a content block. What can I do with this controller? Everything! I can trigger its methods by interacting with DOM elements within its element, producing events like click
, hover
, etc. For example, clicking the link with class some-child-element
will produce message event!
. But i can also pass any message to the controller by triggering a custom event on its element, passing any number of parameters with this event. This way one controller can talk to another - by sending a message via its element. I can trigger action in MyCtrl from any other controller like this:
$('[data-ctrl=MyCtrl]').trigger 'customEvent', customParam
This resembles calling object’s public method to some point. You send a message to the object, and the object uses parameters you supply with this message to respond. Of course this approach has a lot of limitations. You don’t actually get a response back, you can only use events to trigger action. You don’t know if a controller can handle your message before or after sending it - it’s send and forget. Those are serious drawbacks, but this method is very well suited for many simple cases of adding behavior to web pages with jQuery. Sticking to these simple rules will keep your code simple but well structured and loosely coupled. Elements controlled by different controllers can be nested within each other - just be sure to interrupt events you handle if you don’t want them to be passed to a parent element’s controller.
This approach is extensible to some point - for example, it can be easily integrated with a lightweight DOM templating solution, such as http://rivetsjs.com - by binding your controller’s element to a Rivets template you can suddenly enter a whole new league. But if you find yourself in need of extending it much further, this may be a good time to consider using a real framework, like https://angularjs.org.