37

I've a for-loop I'm looping through.

I want to make a custom modal and wait for a response before continue it.

How can I achieve this? I know I've to wait for a callback.

Like this example:

 for(var x in array){
            alert(x);
            console.log(x);
        }

It does exactly what I want to. But I want to have three buttons. But alert is not part of javascript(? It's in the browser.)

So, do you guys have an idea?

I was thinking about doing something like this:

var run = true;
function foo(){
    if (run){
        setTimeout(foo, 500);
    }
}

function stop(){
    run = false;
}

foo();

and then wait for a stop which calls on a button click before continue. But is this really good practice?

Or use a lambda function as a parameter to the customAlert and a "global" variable that holds the current position of the array I'm going through and do this with functions. Like: Check if array is still holding keys greater than X. Then do the function again and each time increase the global X.

Thank you lostsource for the code: Oh, I got an idea; I'll simply use lostsource's solution inside an anonymous function, so I don't get global variables. Excellent.

(function(){

})();
Brian
  • 14,610
  • 7
  • 35
  • 43
Muqito
  • 1,369
  • 3
  • 13
  • 27
  • Are you saying you can't actually use alert because it does not support enough options? – Charlie Wallace Jan 18 '13 at 22:35
  • It is not possible to simply replace `alert()` with a custom modal and have it perform the same way alert does – Kevin B Jan 18 '13 at 22:36
  • `alert`s are synchronous BTW. You won't be able to halt a function execution like that - unless using JS1.7's coroutines/[`yield`](https://developer.mozilla.org/en-US/docs/JavaScript/New_in_JavaScript/1.7#Generators) but that isn't very cross-browser and requires defining a `version` on your script type. You should probably store the `i` inside an execution context and pass it to a function that does one more iteration with it which calls your styled alert which once again passes an incremented `i` back to your iterator function. – Fabrício Matté Jan 18 '13 at 22:41
  • @FabrícioMatté I edited my post. do you think it would be good practice? :/ – Muqito Jan 18 '13 at 22:49
  • +1 for improving the question. Yeah, Lostsource's code template is even tidier than what I thought. – Fabrício Matté Jan 18 '13 at 23:07

2 Answers2

89

Assuming this is your array

var list = ['one','two','three'];

You can try using this loop / callback approach

var x = 0;
var loopArray = function(arr) {
    customAlert(arr[x],function(){
        // set x to next item
        x++;

        // any more items in array? continue loop
        if(x < arr.length) {
            loopArray(arr);   
        }
    }); 
}

function customAlert(msg,callback) {
    // code to show your custom alert
    // in this case its just a console log
    console.log(msg);

    // do callback when ready
    callback();
}

Usage:

// start 'loop'
loopArray(list);

JSFiddle here: http://jsfiddle.net/D9AXp/

lostsource
  • 21,070
  • 8
  • 66
  • 88
  • Yeah, I had an idea like that too (about using a lambda function) :) Thank you very much. – Muqito Jan 18 '13 at 23:00
  • 3
    Extremely helpful thank you, awesome i just used arr.lenght-1 – Bakly Feb 11 '15 at 19:03
  • Actually, I do not think this works if the callback has to do anything asynchronously. Please check this out, I'd love to know if there is a solution for this, as I'm trying to load image files one by one that take a bit of time each: https://jsfiddle.net/evejweinberg/tydfbop6/1/ – EJW Nov 30 '16 at 15:10
  • You just need to put callback in success return or error return. – Andrew Paes Mar 17 '17 at 21:17
  • The idea is: "Write a loop that does not advance to the next index/item in the array (like regular for-loop) until each request calls/completes its callback". The example above is very simple yet effective. Thank you @lostsource – Panini Luncher Jun 03 '18 at 02:18
  • tried the same thing but not working can you provide some guidance https://stackoverflow.com/questions/51347216/then-function-getting-executed-before-all-the-asynchronous-calls-are-executed – Raphael Jul 15 '18 at 11:06
4

MaggiQall, I know you have an answer but I have a flexible solution that may be of interest to you or maybe to someone else.

The solution depends on jQuery (1.7+) and jQuery UI's dialog, but is implemented as a custom method of the Array prototype, not as a jQuery plugin.

Here's the Array method :

Array.prototype.runDialogSequence = function(dialogCallback, startIndex, endIndex){
    startIndex = Math.max(0, startIndex || 0);
    endIndex = Math.min(this.length - 1, endIndex || this.length - 1);
    var sequenceIndex = 0,
        arr = this,
        dfrd = $.Deferred().resolve(startIndex);
    function makeCloseFn(seqData){
        return function(event, ui){
            if(seqData.continue_) { seqData.dfrd.resolve(seqData.arrayIndex+1, seqData); } //continue dialog sequence
            else { seqData.dfrd.reject(seqData.arrayIndex, seqData); } //break dialog sequence
        }
    }
    $.each(this, function(i){
        if(i < startIndex || i > endIndex) { return true; }//continue
        dfrd = dfrd.then(function(arrayIndex){
            var seqData = {
                dfrd: $.Deferred(),
                arrayIndex: arrayIndex,
                sequenceIndex: ++sequenceIndex,
                length: 1 + endIndex - startIndex,
                item: arr[arrayIndex],
                continue_: false
            };
            dialogCallback(seqData).on("dialogclose", makeCloseFn(seqData));
            return seqData.dfrd.promise();
        });
    });
    return dfrd.promise();
}

Array.runDialogSequence() relies on :

  • A dialog template in the document's body, fit to be populated with text/values.
  • an array of similar items (typically javascript objects) containing the data required to populate the dialog, in sequence.
  • passing, as the first argument, a correctly constructed "openDialog" function.

Here's a sample "openDialog" function with explanatory comments :

function openDialog(seqData){
    /*
    seqData is an object with the following properties:
        dfrd: A Deferred object associated with the current dialog. Normally resolved by Array.runDialogSequence() to run through the sequence or rejected to break it, but can be resolved/rejected here to force the dialog sequence to continue/break. If resolved, then pass (seqData.arrayIndex+1, seqData), or custom values. If rejected, typically pass (seqData.arrayIndex, seqData).
        arrayIndex: The current array index.
        sequenceIndex: The current index within the sequence. Normally the same as arrayIndex but Differs when a non-zero startIndex is specified.
        length: The full length of the dialog sequence.
        item: The current item from the array.
        continue_: (false) Set to true to allow the sequence to continue.
    */
    var item = seqData.item;
    var $d = $("#d");
    $d.dialog({
        title: 'Dialog (' + seqData.sequenceIndex + ' of ' + seqData.length + ')',
        modal: true,
        buttons: {
            "Continue": function(){
                seqData.continue_ = true;//set to true before closing to go to next dialog.
                $(this).dialog("close");
            },
            "Cancel": function(){
                $(this).dialog('close');//closing with seqData.continue_ set to its default value false will break the dialog sequence.
            }
        }
    }).find("#message").text(item);//Typically you will do a lot more here to populate the dialog with item data.
    return $d;//openDialog() must return a dialog container, jQuery-wrapped.
}

Array.runDialogSequence() returns a jQuery promise, allowing custom actions to be performed when the dialog sequence completes (done function) or is broken in mid-sequence (fail function).

Here are a couple of sample calls :

//Simplest possible
$("#myButton1").click(function(){
    myArray.runDialogSequence(openDialog);
});

//Call with custom startIndex and endIndex, and chanined `.then()` to provide custom actions on break/completion.
$("#myButton2").click(function(){
    myArray.runDialogSequence(openDialog, 1, 3).then(function(i, seqData){
        alert('All dialogs complete - last item = "' + seqData.item + '"');
    }, function(i, seqData){
        alert('Dialog sequence was broken at item ' + i + ' "' + seqData.item + '"');
    });
});

DEMO

This is as close to a generalized solution as my imagination allows.

Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44