Javascript – Triggering custom events in AJAX callbacks

ajaxeventevent-programmingjavascriptobject-oriented-design

I'm pretty new to JavaScript, but one of the things that's been frustrating is that our AJAX callbacks have been getting packed with different functionality, making it difficult to keep everything separated and organized.

I'm really new to programming, I have a feeling learning MVC a bit more would help me, but for now using custom events seems like it could help me keep my code a lot cleaner and prevent some problems. Here's what I'm talking about:

function myAjaxFunctionThatGetsTestData(){
    $.post('ajax/test.html', function(data) {
        $(document).trigger('testDataLoaded',data);
    });
}

function myOtherFunctionThatsDependentUponAjax(){
    getSomeTestData(); //start process of retrieving the data from ajax
    //then let me know when data has been retrieved
    $(document).one('testDataLoaded', function(data){
        alert (data);
    }
}

function getSomeTestData(){
    //maybe data was already retrieved by a different module, so check first
    if (data){
        $(document).trigger('testDataLoaded',data); 
    }
    else{
        myAjaxFunctionThatGetsTestData(); 
    }
}

I also don't know if it's ok that I'm triggering document or not…

Are there any patterns that look like this that I can read more about? What are the potential problems with this?

Best Answer

This is called an Event Bus, and it is a very useful way to decouple your code when you don't want each AJAX post (or any other action for that matter) to have to explicitly state what has to happen as a result.

// We've consolidated this functionality in an event manager, which gives us more power
// to log and recover from errors in event handlers.
EventManager.subscribe('ContentAdded', function(e) {
    // color every other row whenever content is added to the DOM.
    $('table.grid>tbody>tr:nth-child(even)', e.Content).addClass('even-row');
});

Note how I can publish this event anywhere I want in code, and none of those places need to be aware of this code at all. I can also handle the event anywhere else I want, without being tightly coupled to the publishing code.

On the (ASP.NET MVC) project I work on, we have found this to be such an effective approach that we have almost eliminated any other kind of AJAX calls. We've got our event-driven framework to the point where most of our AJAX links use a single, global callback handler that simply publishes whatever events are returned from the server. Any modules that are interested in being notified when specific events occur can subscribe to these events globally.

I might create a link using code like this:

@Html.EventAction("Create Task", "CreateTask")

... which my framework can render as HTML like this:

<a href="/.../CreateTask" data-ajax="true" data-ajax-success="EventHandlers.Success">
    Create Task
</a>

When the user clicks this link, it is hijacked to perform an AJAX request to an action like this:

public ActionResult CreateTask()
{
    if(!UserCanCreateTasks())
        return Events.ErrorResult("You don't have rights to create tasks.");

    Events.OpenModalDialog("Create Task", "TaskEditView", new TaskEditViewModel());
    return Events.Result();
}

This would in turn generate a JSON object with a ModalDialogOpenedEvent, which includes the rendered contents of the dialog.

The EventHandlers.Success method will simply publish all the events that come back, indirectly causing a handler like this to be invoked:

EventManager.subscribe('ModalDialogOpenedEvent', function(e) {
    createDialog(e.Title, e.Contents);
});

So our controlling logic can actually stay in our controller, and we have dramatically reduced the amount of custom javascript that we need to write for a given piece of functionality. We no longer need to write custom handler code for each type of button in our UI, and we don't have to worry about what happens when the server returns a response we didn't expect.

For example, if the user's session has timed out, the action code won't even get invoked: the server will immediately redirect the AJAX request to a login handler. We've written the login handler to be smart enough to detect if it's in an event-driven AJAX request and return an event that causes a login dialog to be opened. The request that the user was trying to make gets saved until the server returns an event to indicate that the login succeeded, and then it automatically gets retried. You can imagine how much extra code this would require if every AJAX request in our system had to be ready to handle this sort of corner case. But with an event-based system, it's a breeze.

Related Topic