Code Highlighting

Thursday, August 9, 2012

Chaining javascript function array with callbacks

I'm still working on the MVC website I mentioned earlier. It's one of those sites that, rather than just opening a new page, swoops in the new content through neat jQuery animation and ajax loading of data. Magic!
One of the challenges was the chaining of the necessary animations to render the loaded content.
If we're simply opening another content page in the same language (EN, FR, NL, DE) there are just the  following three animations:
  1. Slide away the current content pane,
  2. Slide in the new content, and
  3. Set the selected node of the menu to the new page
The menu and the set of backgrounds stay the same. If we're switching to the home page of another language though, we're looking at:
  1. Slide away the current content pane
  2. Slide away the current menu
  3. Slide away the current background
  4. Load the backgrounds for the new language, slide in the first
  5. Slide in the new menu
  6. Slide in the content of the home page
  7. Finally set the selected language and page
So depending on the page we're switching from and switching to, we need to run through from 3 up to 10 animations that need to be chained through jQuery/various other javascript callbacks.
I wrote functions that reduce the two pages to their common root, and return an array of animation functions that all take a callback parameter (CPS style). Then I use the following functions to run through them sequentially:

function startAnimation(animationFunctions, finalFunction) {
    var status = new animationStatus(animationFunctions, finalFunction);
    
    status.Delegates[0](function() {
        continueAnimation(status);
    });
}

function continueAnimation(status) {
    status.CurrentIndex++;
    if (status.CurrentIndex >= status.Delegates.length) {
        if (status.FinalFunction)
            status.FinalFunction();
    } else {
        status.Delegates[status.CurrentIndex](function() {
            continueAnimation(status);
        });
    }
}

function animationStatus(delegates, finalFunction) {
    this.Delegates = delegates;
    this.CurrentIndex = 0;
    this.FinalFunction = finalFunction;
}

I use the 'continueAnimation' function to chain the functions in the array. The callback closes over the 'status' variable. There's an optional 'finalFunction' parameter to be called after the end of the array has been reached. Obviously you need to make sure the callback always gets called in your animation function. Here is, as an example, the function to show the menu:

function(animationDone) {
    if (targetConfig.Menu == 'LanguageRoot') {
        $("#languagemenu").hide();
        $("#menu").append(renderMenu(data.MainMenuItems));
        $("#rootmenu").show();
    } else {
        $("#languagemenu").show();
        $("#rootmenu").hide();
    }
    $("#menu").animate({
        left: '0px'
    }, 400, animationDone);
}

Obviously functions with callbacks are nothing new. Loading them all into an array makes it easier to dynamically compose an animation though. It also eliminates some mental overhead: just set your animations in the right order, and fire away. You can think of your entire array as just one function, with the finalFunction callback as the only callback.

Menno

No comments:

Post a Comment