9/03/2014

The jQuery Radio: Custom Triggers For a Pub/Sub Model

I like jQuery. It's the first library I used that gave me the "JavaScript is Awesome" tingle. Putting aside the many arguments for and against, jQuery is still used all around the web. When building from scratch we may choose more modern frameworks. However, building from scratch is a fine luxury and the exception to the norm. Many JavaScript programmers, if not most, spend their time improving existing sites and adding additional functionality. The result is that many of us still use jQuery on a daily basis. jQuery is also a great library for JavaScript beginners to get started with. So this post will visit one of jQuery's neatest features, "trigger".

The Pub/Sub, or Radio, event model is based on the idea of one element letting everyone who is listening know it has accomplished something. One example I recently worked with was an accordion. I needed to have a wizard-like interface navigate to another section when a data call completed. The data completion function closed the accordion. I could have had the same function that closed one accordion section open the next session but that would not be extensible and would be hard to maintain. To make it more complex, the open and close animation was also asynchronous. I could have risked callback confusion by passing a callback inside the callback, or passing the name of the function to call next. However, that is too complicated for this simple task. My answer was a custom trigger. I modified the accordion object to issue a "closed" event to anyone who was listening when a section closed. The navigation to the next section fired when it heard the "closed" event. I didn't have to create a potentially confusing code path, and everything was kept modular and extensible.

This post is going to walk through a simple example. Imagine a settings page with a Save button. When a field is changed, the Save button should become active to tell the user to save their changes. We don't want the fields to update the button because it shouldn't be the fields' responsibility to do so. Tying the two together is messy and could cause trouble later on. So, we are going to have the field issue an event trigger, and the document will be a listener for that event. The code for this project is on Cloud9 (http://c9.io) at https://ide.c9.io/dposin/custom_jquery_triggers.

This is the simple form we will start with: 1 input field and 1 div to store the save button. We are going to add JavaScript to the input field first. The fields change event will issue a message to the document. To keep things simple, we are going to both trigger and listen at the document level. The project contains a second example, which is a little more complex but more in line with best practices that takes advantage of event bubbling. The syntax is straight-forward:

    $('input').change(function (e){
        $(document).trigger ('changeMade', e.target.id);
    });

For future extensibility we are going to assign the listener to all input fields. When any input field changes, it will trigger a message to the document calling the changeMade event. Since our document is listening for that event, our listener function will fire. One important thing to notice is the additional parameter being sent out with the trigger call. The jQuery trigger function provides a mechanism to send additional data with the trigger. The additional data is picked up by the listener as additional parameters. Let's look at the listener:

    $(document).on('changeMade', function (e, target){
        $('.fieldsChanged').append ( '<p>' + target + '</p>');
        $('.saveWarning').show();
    });

Whenever the document receives the "changesMade" event it is going to show a save button and append the target parameter to the "fieldsChanged" div. This will happen every time that the document receives that event. Now that it is listening, we can trigger that event anywhere. For example, let's add a button, and let's have the button also trigger the "changesMade" event:

    $('button').click(function (e){
        $('input').trigger ('changeMade', e.target.id);
    });

Clicking the button will now append the button's id to the "fieldsChanged" div. We now have a common functionality being invoked from two different elements without either element being tied to any other. If the document were not listening to that event, it would still trigger but nothing would happen. Our code is compartmentalized and each piece knows enough to take care of itself.

Custom triggers is on of the great things that jQuery brought to the JavaScript world. My humble suggestion is to consider techniques like this the next time you are faced with a new JavaScript problem. Take a look at the code you are dealing with and consider that the answer might already be there with jQuery, and a large code overhaul is not required. If you are working on a legacy application, building a new application, or trying to improve your JavaScript skills, techniques like this keep jQuery an important tool in every developer's toolbox.


Screencast and walk through can be found on my youtube channel at http://youtu.be/276ffoVkyE0