7

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?

Sabrina Gelbart
  • 189
  • 1
  • 6

6 Answers6

3

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.

StriplingWarrior
  • 2,407
  • 1
  • 16
  • 21
2

If I didn't misunderstood your question, you are doing something like this (with jQuery):

// Bind to some arbitrary event name
$("body").bind("arbitrary", function(evt){
  alert("o/");
});

// Create a random event
var e = jQuery.Event("arbitrary");

// Fire the event
$("body").trigger(e);

...and asking if it is ok. It is a common technique, used in rails, for example. Also, jQuery offers these events a nice set of properties to play with. I don't see any problem with binding to 'document'.

1

i'm gonna try answer your question. I think whats happening here is your AJAX methods are setup to get data asynchronously therefore when you try and use the data variable its not set because the AJAX call is still being executed in the background. What you need to do is setup your ajax call not to be asynchronous but synchronous therefore your code will wait till AJAX call has been completed before it moves on.

use Firebug plugin for FireFox to debug your Javascript code it will help you understand how AJAX works and a lot more other things related to using jQuery library.

update this method:

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

To:

function myAjaxFunctionThatGetsTestData(){
   $.ajax({
    type: "POST",
    dataType: "html",
    url: "ajax/test.html",
    data: "",
    async: false, //by default all AJAX calls are set to true
    contentType:  "text/html",
    success: function(data){
     //successful call to server side and back
     //add data to you _data global variable
     //or do your data check here
     _data = data;
    },
    error: function(jqXHR, textStatus, errorThrown){
     //display error message, ajax call failed
     alert(textStatus);
    }
   });
}
zulucoda
  • 111
  • 1
  • in a sense you're right, but when I have lots of ajax like this it makes the app a lot slower to load if I'm doing all ajax synchronically. If there were a way to make my whole function asynchronic so other functions to could procede, and the call to ajax synchronic, that would be interesting (in multiple processes, essentially) – Sabrina Gelbart Jun 21 '12 at 10:57
0

Ideally you want to attach your event handlers to what they relate. In bigger application it can be a dispatcher object or an object that handles communication between javascript objects.

In more simple case, like bind specific UI elements you can simply bind it to them, that way when you set it or trigger it you know the element to with it relates. Or a root element if the UI element is part of a widget.

Otherwise I don't think there is any issue with using the document element as such, maybe it's better to use the triggerHandler() instead depends if you just want to trigger a function or emit a full event object.

GillesC
  • 101
  • 1
0

This is jquery probably, which unfortunately changes the way you write code to more php-ish like.

Normally when using mootools i'd pack the call and handlers into an object. For example.

function edit_handler(a,b,c)
{

    var that = this; //priv member
    this.onProcess = function(data) {}; //public member
    this.onOk = function() {};
    this.onFailure = function() {};

    function process(data) // priv method
    {
        that.onProcess();
        //do processing
    }

    this.run = function (param) //public method
    {
        AJAX.call => onSuccess => process(ret) ; onFail => that.onFailure();
    }

}

var edit_amount = new edit_handler(1,2,3);
edit_amount.onFailure = function() {alert("Update failed");}
form field=> on edit => edit_amount.process(value);

My point - use objects and private methods / properties. Making many functions is always creating a mess.

Izkata
  • 6,048
  • 6
  • 28
  • 43
Slawek
  • 2,884
  • 16
  • 16
0

Here's a simplified version of what you're trying to do. And it's much easier to follow:

function myAjaxFunction(){
    $.post('ajax/test.html', function(data) {
        myOtherFunctionThatsDependentUponAjax(data);
    });
}

function myOtherFunctionThatsDependentUponAjax(){
    alert(data);
}

This should also be possible, although I'm uncertain about JQuery idioms:

function myAjaxFunction(){
    $.post('ajax/test.html', myOtherFunctionThatsDependentUponAjax);
}

function myOtherFunctionThatsDependentUponAjax(){
    alert(data);
}
Izkata
  • 6,048
  • 6
  • 28
  • 43