Using jQuery load with promises
Asked Answered
M

5

22

I'm still trying to wrap my head around deferred and what not, so with this in mind I have a question on how to do the following.

My team and I have 3 separate .load() methods that each go grab a specific template and append that to the same container. Each load takes a different amount of time as you might think, so when the content loads, it loads in a 'stair step' fashion (1, then 2, then 3). I'd like to make use of deferred objects and wait until they are all done, then append them at the same time to remove the 'stair step' action.

$('<div>').load(baseInfoTemplate, function () {
    var baseData = {
        // build some object
    };

    $.tmpl(this, baseData).appendTo($generalContainer);
});

All three calls are similar to the call above.

How can I achieve this?

Murphy answered 28/6, 2011 at 13:26 Comment(4)
does the order in which they're appended matter?Wellman
also the solution would be easier if each of the three templates had its own container, rather than appending them each into the same one.Wellman
The order does matter... And I agree that it would be nice if they had their own :)Murphy
so could you do: <div id="c"> <div id="part1"/> <div id="part2"/> ... </div>Wellman
W
5

$.load() isn't designed for use with Deferred objects, and also it is intended specifically to drop stuff into the page immediately.

To solve that latter problem you either have to render the entire container outside the DOM, and then drop it in when they're all done, or you need to accumulate the three results and then put them all in in one go.

The process below uses the latter approach:

  1. Use $.get() instead, and create an array of the jqXHR objects returned by $.get()

  2. Store the return fragments from each $.get() in an array too

  3. Use $.when.apply($, myArray).done(function() { ... }) to apply the templates and put them into the DOM

See http://jsfiddle.net/alnitak/WW3ja/

Wellman answered 28/6, 2011 at 13:49 Comment(5)
The only issue with using .get() is that I cannot make use of load()s loading of page fragments using a hash...Murphy
@Mike right, so go for option 1, but render the templates into an off-screen DIV. Then add that DIV to the page in the final $.done() callback.Wellman
I like this idea but load() doesn't return a jqXHR object it only returns jQuery. So the .done() is firing immediately not waiting. Would I have to switch to a .get() and move the templates into their own files to achieve my goal?Murphy
@Mike no - you could create a $.Deferred()for each $.load() and then set def[n].resolve as the completion callback within $.load(). It would also stil be much better to have a separate div for each template.Wellman
@Wellman mostly :) I decided to move the templates into their own file and use $.get(), once I did that it fell out nicely... I appreciate your help!Murphy
O
9

I use next code for this case:

$.when(
    $.get('templates/navbar.tmpl.html', function(data) {
        $('#navbar').html(data);
    }),
    $.get('templates/footer.tmpl.html', function(data) {
        $('#footer').html(data);
    }),
    $.Deferred(function(deferred) {
        $(deferred.resolve);
    })
).done(function() {
    $.getScript("js/tools/jquery.min.js");
});

To my mind it looks more structured and pretty nice than previous implementations.

Oldworld answered 6/9, 2013 at 10:21 Comment(1)
yeah, I like being able to see what templates I am loading, good solutionArc
M
6

You can store the promise objects in an array and use $.when() to find out if those promises are fullfilled. This could look like this:

var templates = [ ];

function createPromise( baseInfoTemplate ) {
    return $.Deferred(function( promise ) {
        $('<div>').load(baseInfoTemplate, function() {
            var baseData = { /* foobar */ };

            templates.push( baseData );
            promise.resolve();
        });
    }).promise();
}

var myPromises = [ ];

myPromises.push( createPromise( 'some data' ) );
myPromises.push( createPromise( 'even moar data' ) );
myPromises.push( createPromise( 'foo bar heay' ) );

$.when.apply( null, myPromises ).done( function() {
    templates.forEach(function( template ) {
        $.tmpl(this, template).appendTo($generalContainer);
    });
});

I'm using .apply() here because it accepts an array as arguments for a function call. So basically, we're passing all promises objects to .when().

Example: http://jsfiddle.net/Hg77A/1/


Update:

as Alnitak pointed out, the above example wouldn't make much sense if there is no "success" callback handler. If it is just enough to fire the "all done" handler after you transfered the data with .load(), you just would need to .resolve() the promises within the success handler from .load(). Does that make any sense?

Mopes answered 28/6, 2011 at 13:33 Comment(5)
this doesn't appear to be working - the pieces all get rendered one at a timeWellman
@Alnitak: true. I'm not very familiar with .tmpl(), but I hoped it offers some kind of "finish callback" where you could resolve a promise object. If that doesn't exist, the usage of promises doesn't make any sense at all. Good point.Mopes
it's still possible to use Deferred objects, but it's not as easy as it might be if the three templates have to be dropped in place in the right order.Wellman
and in any event $.load() does have a success handler, but your code is rendering the template as each one finishes, not when they're all done.Wellman
The better way is using jQuery AJAX with Promise check it out gist.github.com/Tusko/dfe1fcbbb71fc0a36c86cdd79530d1fcTemperate
W
5

$.load() isn't designed for use with Deferred objects, and also it is intended specifically to drop stuff into the page immediately.

To solve that latter problem you either have to render the entire container outside the DOM, and then drop it in when they're all done, or you need to accumulate the three results and then put them all in in one go.

The process below uses the latter approach:

  1. Use $.get() instead, and create an array of the jqXHR objects returned by $.get()

  2. Store the return fragments from each $.get() in an array too

  3. Use $.when.apply($, myArray).done(function() { ... }) to apply the templates and put them into the DOM

See http://jsfiddle.net/alnitak/WW3ja/

Wellman answered 28/6, 2011 at 13:49 Comment(5)
The only issue with using .get() is that I cannot make use of load()s loading of page fragments using a hash...Murphy
@Mike right, so go for option 1, but render the templates into an off-screen DIV. Then add that DIV to the page in the final $.done() callback.Wellman
I like this idea but load() doesn't return a jqXHR object it only returns jQuery. So the .done() is firing immediately not waiting. Would I have to switch to a .get() and move the templates into their own files to achieve my goal?Murphy
@Mike no - you could create a $.Deferred()for each $.load() and then set def[n].resolve as the completion callback within $.load(). It would also stil be much better to have a separate div for each template.Wellman
@Wellman mostly :) I decided to move the templates into their own file and use $.get(), once I did that it fell out nicely... I appreciate your help!Murphy
C
2

Here's a Gist for a little jQuery plugin that adds a loadThen function to a set of jQuery elements. It's basically load() without the callback and it returns a promise that is only resolved after the content is loaded and inserted into the set of selected elements.

It's basically a copy/paste of jQuery's own load() code except it returns the promise from the actual ajax call. This lets you get a rejected promise if the ajax fails.

Since it's based on the load() functionality, you can add a selector after the url seperated by a space to get only a fragment of the loaded html.


Example 1: Load the home page of this site into element with id="container"

$('#container').loadThen('/').then(function () {
    // loaded and ready.
}, function () {
    // error
});

Example 2: Load the home page's header into this page's header

$('h1').eq(0).loadThen('/ h1').then(function () {
    // loaded and ready.
}, function () {
    // error
});

Gist contents:

(function ($) {
    var _loadThen = $.fn.loadThen;
    $.fn.loadThen = function (url, params) {
        if (typeof url !== "string" && _loadThen) {
            return _loadThen.apply(this, arguments);
        }

        if(this.length <= 0) {
            return jQuery.Deferred().resolveWith(this, ['']);
        }

        var selector, type, response,
            self = this,
            off = url.indexOf(" ");

        if (off >= 0) {
            selector = jQuery.trim(url.slice(off));
            url = url.slice(0, off);
        }

        if (params && typeof params === "object") {
            type = "POST";
        }

        return jQuery.ajax({
            url: url,
            type: type,
            dataType: "html",
            data: params
        }).then(function (responseText) {
                self.html(selector ? jQuery("<div>").append(jQuery.parseHTML(responseText)).find(selector) : responseText);
            return self;
        });
    };
}(jQuery));
Couchant answered 11/4, 2014 at 14:25 Comment(0)
L
2

I use the following code as a generic library

var loader = {};
(function(){
  var fn = {  
    promises: [],
    templates: [],

    loadTemplate: function( name ) {
      fn.promises.push(
        $.get( `templates/${name}.tmpl.html`,
               (html) => fn.templates.push( html )
        )
      );
    },

    main: function( templates, callback ) {
      templates.forEach( (template) => fn.loadTemplate( template ));
      $.when.apply( $, fn.promises ).done( function() {
        $( '<div id="templates">' ).html( fn.templates.join() ).appendTo( 'body' );
        callback();
      });
    }
  };

  /* EXPORTS */
  loader.main = fn.main;
})();

then call it as the first function in the application's main js file.

function myMain() {
  ... // do all the things
}

$( document ).ready( function() {
  templates = [ 'thingies', 'wotsits', 'oojamaflips' ];
  loader.main( templates, () => myMain());
});
Laforge answered 22/7, 2014 at 10:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.