Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQuery .done on a click event

I'm not a javascript developer, so bear with me on this one...

I need to perform a redirect after a jQuery click event has completed. This is what I have, but I can't apply .done to .click. Wrapping the whole thing in $.when doesn't work ether...

$("#printpng").click(function(){
            $('#tool_docprops').click();
            $('#tool_docprops_save').click();
            $('#tool_export').click()
        }).done(function(){
                window.location.href = "<?php echo $base_url ?>/sign/print"
        });

does anyone have any better solutions?

Thanks

like image 201
tobynew Avatar asked Jan 10 '14 09:01

tobynew


2 Answers

Assuming each click is triggering an Ajax call out of your control (else why would you have this problem), you can simply await any/all Ajax requests to complete with:

$(document).ajaxStop(function () {
     window.location.href = "<?php echo $base_url ?>/sign/print"
  });

You can also add a timeout, if required, before you change the URL in case there is additional processing after the Ajax load. If it is not an Ajax issue please clarify and I will adjust (or remove) this answer.

Full version (with a 1 second additional delay after Ajax wait) might look something like:

$("#printpng").click(function(){
    $('#tool_docprops').click();
    $('#tool_docprops_save').click();
    $('#tool_export').click();
    $(document).ajaxStop(function () {
        setTimeout(function(){
             window.location.href = "<?php echo $base_url ?>/sign/print"
        }, 1000);
    });
});

As promised [sic] a better solution using promises

As the full code was never provided, the solution was guesswork, assuming multiple ajax calls.

A generic solution is to not fire click events, but to simply call the related code for each click, in a promise-friendly way.

Assuming that each click handler has a dedicated function, just make each function return a promise:

e.g.

 function loadPropsViaAjax(){
     // simply return the ajax call as $.ajax returns a promise
     return $.ajax({parameters here});
 }

 function saveDocPropsViaAjax(){
     // simply return the ajax call as $.ajax returns a promise
     return $.ajax({parameters here});
 }

 function waitForImageToload(){
     // create a deferred object
     var def = $.Deferred();

     // When the image eventually loads, resolve the promise
     $('#someimageselector').on('load', function(){
        def.resolve();
     });

     // Return the promise immediately
     return def.promise();
 }

Then to make use of it in your example (running sequentially using .then()):

 // On click button event...
 $("#printpng").click(function(){
    // Run operations sequentially
    loadPropsViaAjax().then(saveDocPropsViaAjax).then(waitForImageToload)
        .done(function(){
            window.location.href = "<?php echo $base_url ?>/sign/print"
     });
 });

Or if they can run in parallel, use $.when:

 // On click button event...
 $("#printpng").click(function(){
    // Run operations in parallel
    $.when(loadPropsViaAjax, saveDocPropsViaAjax, waitForImageToload)
        .done(function(){
            window.location.href = "<?php echo $base_url ?>/sign/print"
     });
 });
like image 112
Gone Coding Avatar answered Sep 28 '22 11:09

Gone Coding


Just for the sake of correctness this can be done in a clean asynchrounous way with jQuery Deferreds. These objects represent a task that will be completed in the future, and event handlers can be attached to it's completion. You can call complete on a Deferred manually.

These objects are what jQuery works with behind the scenes for most async task that has callbacks, and you can actually wait for them to be completed.

You need to change your code a little bit.

In the click eventhandler for #printpng, you need to create deferred objects for every task you want to be completed.

Let's say all 3 click events you trigger manually have something to wait for, so we create 3 deferreds.

$('#printpng').click(function () {
    var def1 = $.Deferred();
    var def2 = $.Deferred();
    var def3 = $.Deferred();

    .......... other code not yet included
});

Now we have 3 objects that represent tasks. If you call .resolve() on them, it means that the task is completed. We want these tasks to be completed when the #tool_export's click eventhandler is done.

Let's say this #tool_export has a click event handler and we are somehow able to pass the deferred object to it.

$('#tool_export').click(function (e, deferred) {
    $.ajax({
        url: 'your_url',
        success: function(result){

            // your code does stuff with the result

            if (deferred !== undefined) {
                deferred.resolve();
            }
        },
        error: function(result){
            if (deferred !== undefined) {
                deferred.reject();
            }
        },
    });
});

As you can see it makes an AJAX call, and calls deferred.resolve() if the call was successful and deferred.reject() if something went wrong. Very simple.

Now the problem is: how to pass this deferred parameter to the click event handlers?

You have written:

$('#tool_docprops').click();

Which is a shorthand for:

$('#tool_docprops').trigger('click');

To pass the def1 object as a parameter, you can simply write:

$('#tool_docprops').trigger('click', def1);

So your event handler gets modified to:

$('#printpng').click(function () {
    var def1 = $.Deferred();
    var def2 = $.Deferred();
    var def3 = $.Deferred();

    $('#tool_docprops').trigger('click', def1);
    $('#tool_docprops_save').trigger('click', def2);
    $('#tool_export').trigger('click', def3);

    ..... something is still missing from here
});

You can pass as many parameters as you want.

The last thing to do is sumbscribing to this deferreds to complete. There is a very cool helper method called .when() which waits for any number of deferreds to be resolved. Since it also creates a deferred, you can call deferred.done() on it to get a callback. So to wait all 3 deferreds you have created earlier, you could write:

$.when(def1, def2, def3).done(function() { 
    window.location.href = "<?php echo $base_url ?>/sign/print";
});

So your final click event handler for #printpng would be:

$('#printpng').click(function () {
    var def1 = $.Deferred();
    var def2 = $.Deferred();
    var def3 = $.Deferred();

    $('#tool_docprops').trigger('click', def1);
    $('#tool_docprops_save').trigger('click', def2);
    $('#tool_export').trigger('click', def3);

    $.when(def1, def2, def3).done(function() { 
        window.location.href = "<?php echo $base_url ?>/sign/print";
    });
});

I have made a very simple example to show this. There is no ajax call there only a timeout, but you can get the idea.

If you click on start, you will need to wait 4 seconds for it to complete:

http://jsfiddle.net/3ZDEe/2/

like image 39
vinczemarton Avatar answered Sep 28 '22 10:09

vinczemarton