0

I've looked at this How-do-i-return-the-response-from-an-asynchronous-call and at why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron , but what I'm trying to do doesn't work.

Since some of our users use IE, it seems that we would have to rely on callbacks.

The background to this question comes from a previous post. Legacy code used VBscript's MsgBox, but now the same functionality must be ported to Javascript.

To provide context, there is a bunch of buttons on a toolbar (hence toolbar.asp) such as "New", "Update", "Delete". The user can navigate to certain parts of the system and for instance create a "New" record for a particular resource/person. The details and functionality are shown on the main part of the screen (hence main.asp). The system was originally written about 15 years ago (or more). When the user clicks "Update" on the toolbar.asp, it depends which screen the main.asp was showing. Parts of dovalidation() in main.asp can be swopped (as in a script is fetched from the database) and inserted into dovalidation() depending on what & where the user is. So some parts are still in Visual Basic 6, but it seems that they are trying to replace the VBscript with Javascript.

So the user is on a specific screen and clicks Update (the toolbar.asp's doupdate() is called). This doupdate() does a number of checks on the date and other variables and the calls the main.asp's dovalidation(). Depending on where the user finds himself, dovalidation looks different. In quite a few cases as in the specific example that I am talking about, there used to be a MsgBox in the VBscript code which gave the user a choice depending on the validation done up to that point. However, that VBscript should now be replaced by Javascript. It seems my boss doesn't want to use the normal window.confirm, because she wants to be able to customise the buttons.

VBscript's MsgBox blocked further execution, but now using a jquery confirm doesn't have the same results, because it is non-blocking.

If the validation occurs in such a way that the user is provided with the confirm dialog, and the user clicks on 'Cancel' then the next page should not be shown, but at present, whether the user clicks on 'Cancel' or not, the next page is shown after a couple of seconds. At the end of the doupdate() function there is: parentMain.screenform.submit(); Could that be part of why my callbacks don't work?

In a toolbar.asp file, this is called in the doupdate() funtion:

//... Other code that determines variables, fringe scenarios etc.
//... 

// Then dovalidation() (in which the blocking MsgBox used to be) is called:

var sErr = parentMain.dovalidation();
        if (sErr != ""){
            return;
        }


//Then the rest of the code which is executed irrespective of the jquery confirm.

//do update
    try {
        parentMain.document.all("Updating").value = "YES"
        parentMain.document.body.scrollTop = 0
        parentMain.document.body.scroll = 'no'
        parentMain.ShowBusy();
        document.getElementById("sysBusy").value = "true";
        //parentMain.document.all("clockFrame").style.display = "block";
    } catch(e) {
        return (e)
    }

    //do check for resource tag
    if (sScreenType.toUpperCase() == "RESOURCE TABLE") {
        if (lResource == "0") {
            parentMain.document.all("sysresource").value = lDetailResource
        }
        //alert("looping for resource tag");
        var sField = ""
        var sCheck = "Resource Tag"
        if (!(sScreen == "Resource")) {
            /*********************************************************************/
            /** loop through the fields and update resouce tag if empty - submit**/
            /*********************************************************************/
            var elements = parentMain.document.getElementById("screenform").elements;

            for (var i = 0, element; element = elements[i++];) {
               if ((element.name).indexOf(sCheck) > 0) {
                    var sValue = element.value
                    if (sValue.length == 0) {
                        element.value = lDetailResource
                    }
                }
                if ((element.tagName.toUpperCase()) == "SELECT") {
                    if (element.disabled == true) {
                        element.disabled = false;
                    }
                }
            }
        }
        
    }
  
    //submit screen

    parentMain.screenform.submit(); //<-- Could this be part of the problem?
    
    }

In the main.asp file the dovalidation function resides. A part of the dovalidation function is swapped out depending on the situation. That is marked between the //################

function dovalidation() {
msErr = "";
         
//#################
if (msErr.length == 0) {

var vEffectiveDate="";
var vLastRunDate="";
var sStatus="";
var vMsg="";
var sResponse="";
vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
sStatus  = document.getElementById("Smoke.Calc Status").value;
vMsg = "";
if ((sStatus).length == 0  ){
    sStatus = "SUCCESFUL";
    //document.getElementById("Smoke.Calc Status").value= sStatus;
}
if ((vEffectiveDate).length > 0  ){
    if (!isDate(vEffectiveDate)  ){
        vMsg = vMsg+"[Effective Date] Is not a date." + ";\r\n";
    } else if (  moment( toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate)) ){
        vMsg = vMsg+"[Effective Date] cannot be on/before "+vLastRunDate+"." + ";\r\n";
    }
}
if (sStatus.toUpperCase() != "SUCCESFUL") {
    $.confirm({
        title:  "Confirmation",
        columnClass: 'col-md-6 col-md-offset-3', 
        content:"Forecast calculation still busy. Results might not be accurate. Continue?",
        buttons: {
            confirm: function() {
                sResponse= "1";
                vMsg = "Response 1";                
                processMessage(); // <--- added this
            },
            cancel: function() {
                sResponse= "2";
                vMsg = "Response 2";                
                // Moved code here, as it needs to execute when Cancel is clicked
                $.alert({
                    title: "INFORMATION",
                    columnClass: 'col-md-6 col-md-offset-3',
                    content: "Screen will refresh. Please click on Update to try again.",
                    // Code that should execute when alert is closed:
                    onAction: function () {
                        document.getElementById("Smoke.Calc Status").value= "REFRESH";
                        msErr = "ABORT";
                        processMessage(); // <--- added this
                    }
                });
            },
        }
    });
} else { // <-- added
    processMessage();
}

function processMessage() {
    // Moved code in a function, as it should only execute after confirm/alert is closed 
    if (vMsg != "") {
        $.alert({
            title: 'Validation Message',
            columnClass: 'col-md-6 col-md-offset-3',    
            content: vMsg,
        });
        msErr = "ERROR";
        
    }
  }
 }                     
//#################
          
return msErr;   
}

So I think my problem lies with msErr being returned long before the user has had chance to decide which button on the confirm dialog to choose. If I don't set breakpoints and click on the confirm's cancel then I do see that the alerts are shown, but the page is not refreshed (document.getElementById("Smoke.Calc Status").value= "REFRESH";) and the next page is shown. I think this comes from the sErr == "" in the toolbar.asp file and then the program flow just continues.

Anycase, I tried using callbacks, but the situation hasn't changed.

Here is what I tried to do:

parentMain.dovalidation(function(result){
    if (result != ""){
        return;
    }
    });

In main.asp the dovalidation function:

function dovalidation(callback) {
 msErr = "";
         
//#################
if (msErr.length == 0) {

    var vEffectiveDate="";
    var vLastRunDate="";
    var sStatus="";
    var vMsg="";
    var sResponse="";
    vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
    vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
    sStatus  = document.getElementById("Smoke.Calc Status").value;
    vMsg = "";
    if ((sStatus).length == 0  ){
        sStatus = "SUCCESFUL";
        document.getElementById("Smoke.Calc Status").value= sStatus;
    }
    if ((vEffectiveDate).length > 0  ){
        if (!isDate(vEffectiveDate)  ){
            vMsg = vMsg+"[Effective Date] Is not a date." + ";\r\n";
        } else if (  moment( toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate)) ){
            vMsg = vMsg+"[Effective Date] cannot be on/before "+vLastRunDate+"." + ";\r\n";
        }
    }
    if (sStatus.toUpperCase() != "SUCCESFUL") {
        $.confirm({
            title:  "Confirmation",
            columnClass: 'col-md-6 col-md-offset-3', 
            content:"Forecast calculation still busy. Results might not be accurate. Continue?",
            buttons: {
                confirm: function() {
                    sResponse= 1;
                    vMsg = "Response 1";
                    processMessage(); // <--- added this
                },
                cancel: function() {
                    sResponse= 2;
                    vMsg = "Response 2";
                    // Moved code here, as it needs to execute when Cancel is clicked
                    $.alert({
                        title: "INFORMATION",
                        columnClass: 'col-md-6 col-md-offset-3',
                        content: "Screen will refresh. Please click on Update to try again.",
                        // Code that should execute when alert is closed:
                        onAction: function () {
                            document.getElementById("Smoke.Calc Status").value= "REFRESH";
                            msErr = "ABORT";
                            processMessage(); // <--- added this
                        }
                    });
                },
            }
        });
    } else { // <-- added
        processMessage();
    }
    
    function processMessage() {
        // Moved code in a function, as it should only execute after confirm/alert is closed 
        if (vMsg != "") {
            $.alert({
                title: 'Validation Message',
                columnClass: 'col-md-6 col-md-offset-3',    
                content: vMsg,
            });
            msErr = "ERROR";
        }
    }
}
//#################         
callback(msErr);   
} 

So, it isn't working as it should, and I don't know what I've done wrong, but I suppose I haven't used the callbacks correctly. Does it make a difference that it is in two different files? This has to work given that the parts between the //######### are swopped.

I would appreciate any feedback and guidance.

Igavshne
  • 699
  • 7
  • 33
  • yaooooeeee. Just saying, thats some... interesting code. – Pytth Oct 22 '20 at 16:38
  • My guess is you are expecting that return to exit some function or something? The code after that line `parentMain.dovalidation(fu...` is still going to execute without waiting.All that return does in that callback doe is exit that callback function. – epascarello Oct 22 '20 at 16:42
  • @epascarello, yes, I think you are right. It needs to exit the outer function, but that is not what takes place at present. – Igavshne Oct 23 '20 at 11:10
  • @Pytth, yeah... talk about my predecessors creating some technical debt. – Igavshne Oct 23 '20 at 11:23
  • @epascarello, I don't know how to stop the outer function's execution. There is more code after the call to `dovalidation`. I tried putting it inside an else clause (matching the `if (result != "")` but I got a bunch of other errors in the browser. – Igavshne Oct 23 '20 at 11:42
  • @epascarello, I see that there is no need for result to be a string. It might as well be a boolean, so I am going to change that. – Igavshne Oct 23 '20 at 12:04
  • @Igavshne I see you added some more code, but... you know, your code are blowing my mind =) what you are trying to do is to get spaghetti-code and make it async. Throw this away and let's go to the upper level. We call it "program on an interface level". Example: replace all your big code blocks with functions (with correct name). Like code between `//#################` will be converted to `function dovalidation(callback) { msErr = getValidationResult(); callback(msErr); }`. What do I do here is just throw away all your low-level code, because your problem is in the order of execution. – Anton Oct 28 '20 at 13:07

3 Answers3

0

There is no need to involve jQuery.

JavaScript has the functions alert, confirm and prompt which are synchronous (i.e. blocking execution until they return) just like MsgBox was.

So if this is easier to you, you can keep your original code structure this way. Because as it was explained in the "how do I return a response from an asynchronous call" article, you cannot make the dovalidation function return anything that depends on the result of an asynchronous operation like the $.confirm that you currently use, since that would require time travel ;) - but you can make it dependent on synchronous operations like JS' built-in confirm.

CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • This solves my question. However, my boss doesn't want to use the normal window.confirm because she says she wants to customise the "Confirm/Cancel". From your answer it seems that it would be impossible for me to manage the program flow using the jquery-confirm (with the more aesthetic looking buttons to keep my boss happy)? It seems to me that my boss would have to choose between nice buttons and a program that works correctly. Is this right? – Igavshne Oct 27 '20 at 11:25
  • Would one be able to use a modal dialog to keep the blocking feature, but to customise the buttons? I don't know much about Javascript etc. – Igavshne Oct 27 '20 at 11:51
  • No - everything is asynchronous nowadays, these things like synchronous `alert` are from an old time – CherryDT Oct 27 '20 at 13:21
  • so are you sure that the above scenario can in _no way_ work unless I use a blocking/synchronous dialog like window.confirm? – Igavshne Oct 27 '20 at 18:39
  • It can work but all your outer code needs to be written asynchronously too. You only show a part of the code though. – CherryDT Oct 28 '20 at 01:23
  • Okay, I'll update my question with more context and information. – Igavshne Oct 28 '20 at 10:28
0

You are mixing sync code with async... Because $.confirm is async, but you call dovalidation as sync.

Note: your code is very strange, maybe because of small JS experience, so I will try to guess what you need. You can ask if something in my code is incorrect.

Your second example with callback is more close to working solution, but you need to call the callback only if you know the user's answer.

So let's change your code a bit. Extract processMessage function (now it is async) and call processMessage with correct parameters:

function processMessage(vMsg, msErr, callback) {
  // Moved code in a function, as it should only execute after confirm/alert is closed 
  if (vMsg) {
    $.alert({
      title: 'Validation Message',
      columnClass: 'col-md-6 col-md-offset-3',
      content: vMsg,
    });
    msErr = "ERROR";
  }
  callback(msErr); // <-- return result
}

function dovalidation(callback) {
  var vMsg = "", msErr = "";

  //#################
  if (msErr.length == 0) {

    var vEffectiveDate = "";
    var vLastRunDate = "";
    var sStatus = "";
    var sResponse = "";
    vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
    vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
    sStatus = document.getElementById("Smoke.Calc Status").value;
    vMsg = "";
    if ((sStatus).length == 0) {
      sStatus = "SUCCESFUL";
      document.getElementById("Smoke.Calc Status").value = sStatus;
    }
    if ((vEffectiveDate).length > 0) {
      if (!isDate(vEffectiveDate)) {
        vMsg = vMsg + "[Effective Date] Is not a date." + ";\r\n";
      } else if (moment(toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate))) {
        vMsg = vMsg + "[Effective Date] cannot be on/before " + vLastRunDate + "." + ";\r\n";
      }
    }
    if (sStatus.toUpperCase() != "SUCCESFUL") {
      $.confirm({
        title: "Confirmation",
        columnClass: 'col-md-6 col-md-offset-3',
        content: "Forecast calculation still busy. Results might not be accurate. Continue?",
        buttons: {
          confirm: function() {
            sResponse = 1;
            vMsg = "Response 1";
            processMessage(vMsg, msErr, callback); // <--- added this
          },
          cancel: function() {
            sResponse = 2;
            vMsg = "Response 2";
            // Moved code here, as it needs to execute when Cancel is clicked
            $.alert({
              title: "INFORMATION",
              columnClass: 'col-md-6 col-md-offset-3',
              content: "Screen will refresh. Please click on Update to try again.",
              // Code that should execute when alert is closed:
              onAction: function() {
                document.getElementById("Smoke.Calc Status").value = "REFRESH";
                msErr = "ABORT";
                processMessage(vMsg, msErr, callback); // <--- added this
              }
            });
          },
        }
      });
    } else { // <-- added
      processMessage(vMsg, msErr, callback); // <--- added this
    }
  }
  //#################         
}

Note: this code is not "clean". If this function dovalidation you showed us has full code, then you can clean the code. This code is runnable (better use it in fullscreen), but I still do not understand what you are trying to do...

const isDate = (x) => true; // Mock
const toDate = (x) => x; // Mock

function processMessage(mType, vMsg, msErr, callback, sResponse) {
  if (vMsg) {
    $.alert({
      title: 'Validation Message',
      columnClass: 'col-md-6 col-md-offset-3',
      content: vMsg,
    });
    msErr = "ERROR"; // ???
  }
  callback(mType, msErr, vMsg, sResponse); // <-- return result
}

function dovalidation(callback) {
  var sResponse = "";
  let vMsg = '';
  let msErr = '';
  let vEffectiveDate = document.getElementById("Smoke.Effective Date").value;
  let vLastRunDate = document.getElementById("Smoke.Last Run Date").value;
  let sStatus = document.getElementById("Smoke.Calc Status").value;

  if (!sStatus) {
    sStatus = "SUCCESFUL";
    document.getElementById("Smoke.Calc Status").value = sStatus;
  }

  if (vEffectiveDate) {
    if (!isDate(vEffectiveDate)) {
      vMsg = vMsg + "[Effective Date] Is not a date.;\r\n";
    } else if (moment(toDate(vEffectiveDate)).isBefore(toDate(vLastRunDate))) {
      vMsg = vMsg + "[Effective Date] cannot be on/before " + vLastRunDate + ".;\r\n";
    }
  }

  if (sStatus.toUpperCase() != "SUCCESFUL") {
    $.confirm({
      title: "Confirmation",
      columnClass: 'col-md-6 col-md-offset-3',
      content: "Forecast calculation still busy. Results might not be accurate. Continue?",
      buttons: {
        confirm: () => {
          sResponse = 1;
          vMsg = "Response 1";
          processMessage('Confirm', vMsg, msErr, callback, sResponse);
        },
        cancel: function() {
          sResponse = 2;
          vMsg = "Response 2";
          $.alert({
            title: "INFORMATION",
            columnClass: 'col-md-6 col-md-offset-3',
            content: "Screen will refresh. Please click on Update to try again.",
            // Code that should execute when alert is closed:
            onAction: () => {
              document.getElementById("Smoke.Calc Status").value = "REFRESH";
              msErr = "ABORT";
              processMessage('Abort', vMsg, msErr, callback, sResponse);
            }
          });
        },
      }
    });
  } else {
    processMessage('Success', vMsg, msErr, callback, sResponse);
  }
}

function test() {
  dovalidation(function(mType, msErr, vMsg, sResponse) {
    console.log('[TYPE]', mType, '[RESULT]', msErr || '?', '[MESSAGE]', vMsg || '?');
    //if (result != "") {
    //  return;
    //}
  });
}
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>

<input type="text" id="Smoke.Effective Date" value="2020-01-02"/>
<input type="text" id="Smoke.Last Run Date" value="2020-01-01"/>
<input type="text" id="Smoke.Calc Status" value="WTF?"/>
<button onclick="test()">TEST</button>
Anton
  • 2,669
  • 1
  • 7
  • 15
  • Thank you @Anton. I have tried to add extra information and context to my question, so please have a look at it again. I did a preliminary test with your code, but it wasn't successful, so I will continue to check, perhaps I missed something. Could the line `parentMain.screenform.submit();` in doupdate() make a big difference? – Igavshne Oct 28 '20 at 11:51
0

Well, ok. I started to write a comment, but it is too small for all I need to say, so I will continue in an answer.

I see you added some more code, but... you know, your code are blowing my mind =) what you are trying to do is to get spaghetti-code and make it async.

Throw this away and let's go to the upper level. We call it "program on an interface level".

Example: replace all your big code blocks with functions (with correct name). Like code between //################# will be converted to

function dovalidation(callback) {
  msErr = getValidationResult();
  callback(msErr);
}

What do I do here is just throw away all your low-level code, because your problem is in the order of execution.

But this is just the first step of converting your code to something other. Next step is realizing that our "virtual" function getValidationResult has some $.confirm and $.alert inside, so it is async. That's why we need to use getValidationResult as async. Two possible ways - convert to Promise or use callbacks. Let's use callbacks. Then our simplified code will convert to:

function dovalidation(callback) {
  getValidationResult(callback);
}

That is what I was trying to show on my previous answer.

And now you've added some more code with such a comment: //Then the rest of the code which is executed irrespective of the jquery confirm.. Ok, good, we will name all this code as theRestOfTheCodeIrrespectiveToConfirm(). So your original function call will be converted from this:

//... Other code that determines variables, fringe scenarios etc.
//... 

// Then dovalidation() (in which the blocking MsgBox used to be) is called:

var sErr = parentMain.dovalidation();
        if (sErr != ""){
            return;
        }

theRestOfTheCodeIrrespectiveToConfirm();

to this:

//... Other code that determines variables, fringe scenarios etc.
//... 

// Then dovalidation() (in which the blocking MsgBox used to be) is called:

parentMain.dovalidation(sErr => {
    if (sErr != ""){
        return;
    }
    theRestOfTheCodeIrrespectiveToConfirm(); // <- this will execute only if sErr is empty
});

Am I going to the right direction?

P.S. One thing to ask you - why do you have } in the end of you example code, but no correcsponding {?

Maybe you show us not all code?

Anton
  • 2,669
  • 1
  • 7
  • 15
  • I know this code is _terrible_. Fortunately I don't usually work on this part of the system (hence also my ignorance of Javascript). Unfortunately, I'm a contractor and I only work a few hours per day for this company, so even if they allowed me to rewrite everything, it won't be feasible for me to do it. – Igavshne Oct 29 '20 at 11:08
  • As for the brackets { and }, I probably made a copy & paste error. I'll have a look. – Igavshne Oct 29 '20 at 11:08
  • I'll try to use your outline and see whether I come right. Thank you for being willing to help me. I'll let you know what happens. – Igavshne Oct 29 '20 at 11:09
  • I've awarded you the bounty since you've put in the effort to help me and give me a good foundation to work from. I haven't managed to get my code to work yet. It still continues on to the next screen irrespective of the user's choice regarding the confirm, but I'll keep on trying. – Igavshne Nov 02 '20 at 10:42
  • @Igavshne well, if you can create a jsfiddle with your code, we can fix that (like I tried to create Code snippet in my last answer). But I do not know how hard it will be for you to create a separate example. Of course you will need to replace all AJAX, POST and so on requests to just `console.log`, but all the rest you should be able to reproduce on the foreign site. – Anton Nov 02 '20 at 11:40
  • I might try to create a code snippet. I want to try a few things before I go that route though. What happens to the vMsg and sResponse variables that are passed back to the callback function? As I understand, and as the function is at present, namely `sErr => { if (sErr != ""){ return; } theRestOfTheCodeIrrespectiveToConfirm();` I actually only need the sErr variable? Is the value of msErr automatically passed as the value for sErr? Hope what I'm asking makes sense. – Igavshne Nov 03 '20 at 10:42
  • I got it to work!!! :-D The final problem was when the code was swopped with something other, then in effect the callback was "shortcircuited", and that's why it didn't work. But now I have everything "nested" and each little excape hole blocked, so it can only go through the callback. Thank you once again. – Igavshne Nov 04 '20 at 13:41