9

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

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
tobynew
  • 337
  • 1
  • 3
  • 17
  • I think you are looking for jquery promises, which has `.done()` – jacquard Jan 10 '14 at 09:41
  • 1
    Do those other click events fire off ajax requests which you want to wait until they are completed before redirecting? – Richard Dalton Jan 10 '14 at 09:42
  • 2
    Why don't you add the redirect simply to the end of your event handler? Exactly what are you waiting for? – vinczemarton Jan 10 '14 at 09:42
  • One of the events does fire ajax yes. I'm waiting on an image to be streamed from canvas to a php script. The redirect then loads the newly created image for printing. Running through a very heavily modified version of SVG Edit – tobynew Jan 10 '14 at 09:45
  • 1
    .click(); is just the event handler and since you don't have any function associated with it, I think you're not using it correctly. If you need to actually trigger an actual click, you need .trigger('click'); – frenchie Jan 10 '14 at 09:54
  • @frenchie: `click()` without parameters just calls trigger, his code is correct :) – iCollect.it Ltd Jan 10 '14 at 10:01
  • @TrueBlueAussie: ah ok, didn't know. – frenchie Jan 10 '14 at 10:55

5 Answers5

13

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"
     });
 });
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • 1
    Adding extra timeout to it is hacky. Still this is the only answer yet that seems to comprehend the problem. – vinczemarton Jan 10 '14 at 09:55
  • 2
    I meant a timeout in the Ajax stop event (so both), but the "proper" way to do it is have events you can catch on the completion events *of each operation that the clicks trigger* and chain them together (or combine promises to run them in parallel and await all 3). basically there is not enough information yet to give a definitive answer. – iCollect.it Ltd Jan 10 '14 at 09:59
  • I'll give this a go and see what happens. - Will post shortly – tobynew Jan 10 '14 at 10:08
  • Just for the sake of correctness I have created an answer to include promises, since `setTimeout()` is either too long or too short. – vinczemarton Jan 10 '14 at 11:04
  • @SoonDead: "setTimeout() is either too long or too short" is a bit vague. That was only a short pause *after all ajax calls were completed* and *only if actually needed for his own extra processing*. Can you please explain, as your new answer is very complex for the stated problem? – iCollect.it Ltd Jan 10 '14 at 11:09
  • Yes I understand. I meant that if you add extra wait to a problem like this, you choose a timeout that is *much longer* than the operation you are waiting for, so it is actually much longer than it supposed to be. But if you wait for less, than there *will* be a client with a slower machine where this wait will complete *too soon*. In this specific case I think your answer would be fine even without the added timeout. I'm not trying to compete with your answer, I just wanted to describe a more general way of doing this (`.ajasStop()` also uses promises behind the scenes). – vinczemarton Jan 10 '14 at 11:17
  • Also I don't think using promises in JavaScirpt is *that* complex, in JS where "async stuff with callbacks and events and other stuff" happens all the time, it is important to be familiar with the concept to write efficient code. If you check my fiddle, you can see that the longest part of the code is actually writing 3 different event handlers. – vinczemarton Jan 10 '14 at 11:24
  • @SoonDead: I have no problem with promise-based solutions (and mentioned them already). I was just commenting that your example seemed a little complex. I will add a promise-based solution shortly as I have a few spare minutes. – iCollect.it Ltd Nov 20 '15 at 10:13
8

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/

vinczemarton
  • 7,756
  • 6
  • 54
  • 86
-1

try something like this

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

try to make asyc = false

$.ajax({
    url:"demo_test.txt",
    async: false,
    success:function(result){
        $("#div1").html(result);
    }
});
rajesh kakawat
  • 10,826
  • 1
  • 21
  • 40
  • Please note comment from SO: "One of the events does fire ajax yes." This solution will not wait for any async processes to complete, it will just redirect immediately. – iCollect.it Ltd Jan 10 '14 at 09:51
  • this is what i had origonally, and @TrueBlueAussie has the issue exactly. it redirects before ajax completes which is what i need to avoid – tobynew Jan 10 '14 at 10:05
  • Using `async: false` is a really bad solution to ajax problems (assuming that part of the code is under his control). It is always best to work *with* async events (they are asynchronous for good reason) :) – iCollect.it Ltd Jan 10 '14 at 10:25
-1

as i understand right you need to perform page redirection after click event completed. For this you need to simply add a callback function on the click event.

When click is completed, this callback function trigger and in this function you write your redirection code.

for example :

$("#printpng").click(function(){
   $('#tool_docprops').click(function(e){
      // write here your redirection code
   });
   $('#tool_docprops_save').click(function(e){
     // write here your redirection code
   });
   $('#tool_export').click(function(e){ 
     // write here your redirection code
   });
});

thanks

Arpit Jain
  • 455
  • 3
  • 8
-2

Just write your redirect code at the end of click handler.

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

If you need to wait for async process (triggered by click events) to finish before updating page location, you need to fix your solution.

i.e. your async process is an ajax call, which generates a request after #tool_docprops_save is clicked. Than you should attach a success event on that ajax-call, which will update window.location.href after ajax-call is finished.

Xardas
  • 142
  • 9