2

I'm checking to see if a user is logged in before I prepare the page in a particular for members or guests. If they have the cookie, they're a member, if not they're a guest.

Have I done this properly?

function check_if_member(){

    var dfd = new $.Deferred();
    if (readCookie('isco')){
        //adds jquery data() element "user_information"
        //after making a jquery ajax post to retrieve it
        //and using a template to write data to the page
        //with the `success()` callback
        prepare_member('member');
    } else {
        //reveals some HTML on the page
        prepare_member('guest');
    }  
}

$(document).ready(function(){
    //before page loads, check if user is member
    $.when(check_if_member())
     .then(function(){
        console.log($('body').data('user_information'));
        return false; 
     });
});

I think I'm finally getting a bit of an understanding of deferred, but they still confuse me and I wonder if I've structured this appropriately, or if I need add a resolve or return line to any of my ajax requests or the line that saves the collected information to the jquery data(). Thank you.

EDIT

prepare_member function

function prepare_member(type) {

    if (type == 'member') {
        var user_information = readCookie('isco')

        $('body').data('user_information', user_information);

        var user_id = $('body').data('user_information').user_id;

        $.ajax({
            type: 'post',
            url: "/address.php",
            data: {
                type: "retrieve",
                user_id: user_id,
                isbilling: true
            },
            dataType: 'json',
            success: function (returnedData) {

                $('body').data('user_information').billing_information = returnedData['address_0'];

                //populate member billing fields
                $('#member_billing_container').append('<div>').children(':last')
                                                              .vkTemplate('/member_billing_template.tmpl', returnedData, function () {
                                                                  //some callback - possibly resolve promise
                                                              });
                //populate member contact fields
                $('#member_contact_container').append('<div>').children(':last')
                                                              .vkTemplate('/member_contact_template.tmpl', JSON.stringify(user_information), function () {
                                                                  //some callback - possibly resolve promise  
                                                               });
            }
        });

        $('.guest_container, .guest').hide();
        $('.member_container, .member').show();

    } else {

        $('.guest_container, .guest').show();
        $('.member_container, .member').hide();

    }

}
1252748
  • 14,597
  • 32
  • 109
  • 229
  • Yes, you have to resolve the Deferred, otherwise the function passed to `then()` will never be called. You also need to return the Deferred's promise from `check_if_member()`. – Frédéric Hamidi Feb 26 '13 at 19:32
  • @FrédéricHamidi Thanks. Can you show me exactly where to put these lines. `resolve in the ajax success callback? – 1252748 Feb 26 '13 at 19:34
  • I can try if you update your question with the code of `prepare_member()` :) – Frédéric Hamidi Feb 26 '13 at 19:34
  • @FrédéricHamidi Sure. Two seconds. Let me make a shortened version of it. – 1252748 Feb 26 '13 at 19:38
  • @FrédéricHamidi Updated, thanks for taking a look! – 1252748 Feb 26 '13 at 19:45
  • I have an answer underway, but it will be much simpler if your templating framework (`vkTemplate()`) returns a promise in addition to taking a callback. Do you know if that is the case? Otherwise, secondary Deferred objects will have to be introduced in the mix. – Frédéric Hamidi Feb 26 '13 at 20:15
  • @FrédéricHamidi I ran into this problem once before. And received [this](http://stackoverflow.com/a/13462869/1252748) solution on stackover flow recommending that I should use `resolveWith(context, [$(el)]);`, but I honestly didn't really understand what was happening. I imagine I will have to do something like this again. – 1252748 Feb 26 '13 at 20:27
  • Indeed, but the catch is that you may want to do that only after `prepare_member()` has completely finished its work (I'm assuming so in my answer), so we have to resolve the Deferred after both calls to `vkTemplate()` return, and we cannot chain them because them running in parallel is desirable. Thus, my previous question: Do you know if `vkTemplate()` returns a promise or do we have to work around this? :) – Frédéric Hamidi Feb 26 '13 at 20:32
  • @FrédéricHamidi Actually, the only thing I want to ensure happens before the promise is resolved is that cookie is read and the `data()` attached to the `body`. Once that happens, the I don't really mind when the ajax calls fire or complete. So from that perspective, does it make more sense to simply wrap the `data()` attachment in another function, and attach the `$.Deferred` to _that_? Then when it is resolved, it can trigger the ajax (which will use the `$('body').data('user_information).userid`? Does that make sense. Because vk definitely doesn't return a promise. Thanks so much! – 1252748 Feb 26 '13 at 20:41
  • Well, if you want to resolve the Deferred without performing any kind of asynchronous operation, there is no benefit from using a Deferred in the first place. It would be more interesting to resolve it after either the AJAX request or the template calls have succeeded. (I can provide a reasonably simple answer in the first case, or a more complicated one in the second.) – Frédéric Hamidi Feb 26 '13 at 20:50
  • @FrédéricHamidi Yes, I think I prefer the simpler explanation. No need to overcomplicate it, interesting though deferreds are :) – 1252748 Feb 26 '13 at 20:51

1 Answers1

1

First, since we're talking about Deferreds, we won't use a success handler. $.ajax() returns a promise, so we can chain it into done() and keep using the deferred pattern.

Then, let's say you want to resolve your Deferred as soon as member data becomes available (this actually makes the answer much simpler, given that vkTemplate() does not return a promise and we would probably have to write a wrapper in order to keep the code readable).

The result would be something like:

function check_if_member()
{
    var dfd = $.Deferred(),           // Can be called without 'new'.
        cookie = readCookie("isco");  // Avoid reading cookie twice.
    prepare_member(cookie, dfd);
    return dfd.promise();             // Return promise.
}

function prepare_member(user_information, dfd) {
    if (user_information) {
        // Member mode.
        $("body").data("user-information", user_information);
        $.ajax({
            type: "post",
            url: "/address.php",
            data: {
                type: "retrieve",
                user_id: user_information.user_id,
                isbilling: true
            },
            dataType: "json"
        }).done(function(returnedData) {
            user_information.billing_information = returnedData["address_0"];
            dfd.resolve();  // Member data is available, resolve Deferred.
            $("#member_billing_container").append("<div>").children(":last")
                .vkTemplate("/member_billing_template.tmpl", returnedData,
                    function() {
                        // Maybe chain widgets or handlers...
                    });
            $("#member_contact_container").append("<div>").children(":last")
                .vkTemplate("/member_contact_template.tmpl",
                    JSON.stringify(user_information),
                    function () {
                        // Maybe chain widgets or handlers...
                    });
        });
        $(".guest_container, .guest").hide();
        $(".member_container, .member").show();

    } else {
        // Guest mode.
        dfd.resolve();  // No member data available, resolve Deferred
                        // (synchronously here). You might also want
                        // to reject() it instead.
        $(".guest_container, .guest").show();
        $(".member_container, .member").hide();
    }
}

$(document).ready(function() {
    $.when(check_if_member())
     .then(function() {
          console.log($("body").data("user-information"));
     });
});

Now, maybe you do not have to use the <body> element's data to convey the user information to your then() handler. Deferred objects can be resolved (or rejected, or notified) along with any information, for instance:

user_information.billing_information = returnedData["address_0"];
dfd.resolve(user_information);  // Member data is available, resolve Deferred
                                // and pass user information through promise.

Then you would only have to write:

$(document).ready(function() {
    $.when(check_if_member())
     .then(function(user_information) {
          console.log(user_information);  // Will be undefined in guest mode.
     });
});
Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
  • This is very clear. Thank you very much. Can I ask you what change you made in your edit. It seems like you removed the line where you attached `data()` to the body? Is this correct? – 1252748 Feb 26 '13 at 21:17
  • @thomas, absolutely, I removed the second call to `data()` because it was not necessary. `user_information` is still the same object that was associated with `data()` the first time (right before the call to `$.ajax()`), so changes made to it are automatically reflected "on the other side". – Frédéric Hamidi Feb 26 '13 at 21:18
  • aha, of course. I can't thank you enough for all the help. I might continue to sort of ask a few questions as I implement it, but this is incredibly clear helpful. :) – 1252748 Feb 26 '13 at 21:26
  • Why do you have to return the promise in the 6th line? What does that really mean? Where does that return value go? – 1252748 Feb 26 '13 at 21:46
  • @thomas, you have to return your promise so `$.when()` can consume it, turn it into its own promise and, in turn, return that, so `then()` is applied to it. There is nothing magic about return values here (though the asynchronous implications are arguably hard to catch). – Frédéric Hamidi Feb 26 '13 at 21:50
  • Okay. This makes perfect sense. If in my `then()` statement I have another function that does a simple `return $.get('/file.php)`, which I want to perform another function after it's data is returned, can I put something like `$.when(check_if_member()).then(function () { console.log($("body").data("user_information")); append_line_item().then(function(line_item_data){ $('#line_items').append(line_item_data['html'])}.done(function(){ initialize_uploader(); });` so that append_line_item is done, then initialize uploader is done? – 1252748 Feb 26 '13 at 21:54
  • If `append_line_item()` returns a promise (for instance, the return value of `$.ajax()`, which is a promise, remember), then yes. – Frédéric Hamidi Feb 26 '13 at 21:55
  • hmm..unfortunately `append_line_item().then(function(line_item_data){ $('#line_items').append(line_item_data); }).then(function(){ var parent = $('#line_items fieldset.line_item_enclosure:last-child'); populateMainProductSelect(parent); });` did not work. Do you know what could be wrong? – 1252748 Feb 26 '13 at 21:59
  • 1
    my mistake. mistyped function name. Everything it tickety-boo! once again, thanks a million! – 1252748 Feb 26 '13 at 22:01