Organize jQuery code without a framework
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
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.