'google.script.run is not triggering the next function

I have a function that checks if the user fills out the form correctly and checks the user's questions and answers in the Google Form that was created. I created a loading modal that should transition to whether the form was valid or not. If not valid, it will display which question in the form had the issues. However, it will not go past the loading icon. If I comment out google.script.run.loading(), it will then go to the onExtractDataFormSuccess function and call the error function in the code.gs. Not sure what I am doing wrong.

The following is what I have done:

function checkForm(){
        var f_name = document.getElementById('first_name').value;
        var l_name = document.getElementById('last_name').value;
        //var email = document.getElementById('email_inline').value;
        var fullNme = f_name + " " + l_name;

        var isFNameValid = isValid(f_name);
        var isLNameValid = isValid(l_name);
        //var isEmailValid = validateEmail(email);

        if(isFNameValid && isLNameValid){
          google.script.run.loading();
          console.log("after loading");
          google.script.run.withSuccessHandler(onExtractDataFormSuccess).extractFormData(fullNme);
          console.log("after onExtractDataFormSuccess");
          //var response = google.script.run.extractFormData(fullNme);
          //google.script.run.extractFormData(fullNme);
          //setTimeout(confirmationPopUp,1350);
          return true;
        } else {
          displayErrors();
          return false;
        }
      }

      function onExtractDataFormSuccess(response) {
        console.log("ENTER onExtractDataFormSuccess");
        if(!response.Success){
          console.log("encountered error in form");
          google.script.run.error(response);
        }
      }

Following function is in the code.gs:

function extractFormData(fullNme) {
  loading();
  var form = FormApp.getActiveForm();
  var items = form.getItems();
  var quizAdded = new Date().toLocaleString("en-US");
  var uniqueQuizID = Utilities.getUuid(), question_listID = uniqueQuizID.concat("-questions");
  var constructedJSON = {};
  var answer_val = false;
  var errorJSON = {};
  var email = form.getEditors()[0].getEmail();

  var quizInfo = {
    "quiz_name": form.getTitle(),
    "quiz_owner": fullNme,
    "quiz_description": form.getDescription(),
    "quiz_owner_email": email,
    "quiz_submited_date":quizAdded
  };

  console.log('Starting for loop');
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    switch(item.getType()) {
      case FormApp.ItemType.MULTIPLE_CHOICE:
        var question = item.asMultipleChoiceItem();
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var question_type = "Multiple Choice";
        var optns = [];
        var answr;
        var answers = question.getChoices();
        answer_val = false;
         for (var j = 0; j < answers.length; j++) {
          var clean = answers[j].getValue().trim();
          optns.push(clean);
          if(answers[j].isCorrectAnswer()){
            answr = answers[j].getValue().trim();
            for(var x = 0; x < optns.length; x++){
                if(answr == optns[x]){
                  answer_val = true;
                  break;
                }
            }
          }
        }
        var multiJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON1: " + JSON.stringify(multiJSON));
        constructedJSON[i+1] = multiJSON;
        break;
      case FormApp.ItemType.CHECKBOX:
        var question = item.asCheckboxItem();
        //var ques = question.getTitle().trim();//.replace(/\s/g, "");
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var question_type = "CheckBox";
        var optns = [];
        var answr = [];
        var answers = question.getChoices();
        
         for (var j = 0; j < answers.length; j++) {
          var clean = answers[j].getValue().trim();//replace(/\s/g, "");
          optns.push(clean);
          if(answers[j].isCorrectAnswer()){
            answr.push(answers[j].getValue().trim());
          }
        }
        var checkJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON2: " + JSON.stringify(checkJSON));
        constructedJSON[i+1] = checkJSON;
        break;
      case FormApp.ItemType.PARAGRAPH_TEXT:
        var question = item.asParagraphTextItem();
        //var ques = question.getTitle().trim();//.replace(/\s/g, "");
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var question_type = "free response";
        var optns = [];
        var answr;
        var paraJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON3: " + JSON.stringify(paraJSON));
        constructedJSON[i+1] = paraJSON;
        break;
      case FormApp.ItemType.TEXT:
        var question = item.asTextItem();
        //var ques = question.getTitle().trim();
        var question_type = "free response";
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var optns = "";
        var answr = "";
        var textJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON4: " + JSON.stringify(textJSON));
        constructedJSON[i+1] = textJSON;
        break;
    }

    if(!answer_val){
      errorJSON = {'Question':ques, 'Options':optns, 'Answer': answr, 'Success': false};
      //error(ques);
      break; 
    }
  }

  //console.log("JSON: " + JSON.stringify(constructedJSON));

  console.log('what is answer_val? ' + answer_val);
  if(answer_val){ 
    Utilities.sleep(5 * 100)
    //sendExtractedData(uniqueQuizID, quizInfo, question_listID, constructedJSON); 
    //notifyUser();  
    return {'Success': true};
  } else {
    console.log('There was an error!');
    console.log(JSON.stringify(errorJSON));
    throw JSON.stringify(errorJSON);
  }
}

function error(response){
 // var ui = HtmlService.createHtmlOutputFromFile('error')
   //   .setWidth(420)
     // .setHeight(96);
  var html = HtmlService.createTemplateFromFile('answer-missing-error');
  html.id = response.Question;
  FormApp.getUi().showModalDialog(html.evaluate().setWidth(520).setHeight(250), "Error");
}

function loading() {
  var ui = HtmlService.createHtmlOutputFromFile('loading-icon')
      .setWidth(170)
      .setHeight(76);
  FormApp.getUi().showModalDialog(ui, 'Loading Quiz to MOATT Database');
}

The following is the loading icon:

enter image description here

It does not show the following to show the user that there are issues in the form they have created:

enter image description here

Update: The following belongs to the following files:

modal.html:

<script>
   function checkForm(){
        var f_name = document.getElementById('first_name').value;
        var l_name = document.getElementById('last_name').value;
        //var email = document.getElementById('email_inline').value;
        var fullNme = f_name + " " + l_name;

        var isFNameValid = isValid(f_name);
        var isLNameValid = isValid(l_name);
        //var isEmailValid = validateEmail(email);

        if(isFNameValid && isLNameValid){
         google.script.run.loading();
         //CallChangeTitle();
         //console.log("after loading");
          var myRunner1 = google.script.run.withFailureHandler(onExtractDataFormFailure);
          var myRunner2 = myRunner1.withSuccessHandler(onExtractDataFormSuccess);
          myRunner2.extractFormData(fullNme);
          return true;
        } else {
          displayErrors();
          return false;
        }
      }

      function onExtractDataFormSuccess(response) {
        console.log("ENTER onExtractDataFormSuccess");
        if(response.Success){
          console.log("successful");
          google.script.run.notifyUser();
        } else{
        }
      }

      function onExtractDataFormFailure(response) {
        console.log("ENTER onExtractDataFormSuccess");
        var errorJSON = JSON.parse(response.message);
        if(!errorJSON.Success){
          console.log("encountered error in form");
          google.script.run.error(errorJSON);
        } else{
        }
      }
</script>

Code.gs:

function onOpen(e) {
  FormApp.getUi()
      //.createAddonMenu()
      .createMenu("MOATT Add-On")
      .addItem('📄 Upload Quiz Q&A', 'showModeless')
      .addItem('About MOATT', 'showAbout')
      .addToUi();
}

function onInstall(e) {
  onOpen(e);
}

function extractFormData(fullNme) {
  loading();
  var form = FormApp.getActiveForm();
  var items = form.getItems();
  var quizAdded = new Date().toLocaleString("en-US");
  var uniqueQuizID = Utilities.getUuid(), question_listID = uniqueQuizID.concat("-questions");
  var constructedJSON = {};
  var answer_val = false;
  var errorJSON = {};
  var email = form.getEditors()[0].getEmail();

  var quizInfo = {
    "quiz_name": form.getTitle(),
    "quiz_owner": fullNme,
    "quiz_description": form.getDescription(),
    "quiz_owner_email": email,
    "quiz_submited_date":quizAdded
  };

  console.log('Starting for loop');
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    switch(item.getType()) {
      case FormApp.ItemType.MULTIPLE_CHOICE:
        var question = item.asMultipleChoiceItem();
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var question_type = "Multiple Choice";
        var optns = [];
        var answr;
        var answers = question.getChoices();
        answer_val = false;
         for (var j = 0; j < answers.length; j++) {
          var clean = answers[j].getValue().trim();
          optns.push(clean);
          if(answers[j].isCorrectAnswer()){
            answr = answers[j].getValue().trim();
            for(var x = 0; x < optns.length; x++){
                if(answr == optns[x]){
                  answer_val = true;
                  break;
                }
            }
          }
        }
        var multiJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON1: " + JSON.stringify(multiJSON));
        constructedJSON[i+1] = multiJSON;
        break;
      case FormApp.ItemType.CHECKBOX:
        var question = item.asCheckboxItem();
        //var ques = question.getTitle().trim();//.replace(/\s/g, "");
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var question_type = "CheckBox";
        var optns = [];
        var answr = [];
        var answers = question.getChoices();
        
         for (var j = 0; j < answers.length; j++) {
          var clean = answers[j].getValue().trim();//replace(/\s/g, "");
          optns.push(clean);
          if(answers[j].isCorrectAnswer()){
            answr.push(answers[j].getValue().trim());
          }
        }
        var checkJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON2: " + JSON.stringify(checkJSON));
        constructedJSON[i+1] = checkJSON;
        break;
      case FormApp.ItemType.PARAGRAPH_TEXT:
        var question = item.asParagraphTextItem();
        //var ques = question.getTitle().trim();//.replace(/\s/g, "");
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var question_type = "free response";
        var optns = [];
        var answr;
        var paraJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON3: " + JSON.stringify(paraJSON));
        constructedJSON[i+1] = paraJSON;
        break;
      case FormApp.ItemType.TEXT:
        var question = item.asTextItem();
        //var ques = question.getTitle().trim();
        var question_type = "free response";
        var ques = quesCleanUp(question.getTitle().trim());//replace(/\s/g, "");
        var optns = "";
        var answr = "";
        var textJSON = makeJSON(ques, question_type, optns, answr);
        console.log("JSON4: " + JSON.stringify(textJSON));
        constructedJSON[i+1] = textJSON;
        break;
    }

    if(!answer_val){
      errorJSON = {'Question':ques, 'Options':optns, 'Answer': answr, 'Success': false};
      //error(ques);
      break; 
    }
  }

  //console.log("JSON: " + JSON.stringify(constructedJSON));

  console.log('what is answer_val? ' + answer_val);
  if(answer_val){ 
    Utilities.sleep(5 * 100)
    //sendExtractedData(uniqueQuizID, quizInfo, question_listID, constructedJSON); 
    //notifyUser();  
    return {'Success': true};
  } else {
    console.log('There was an error!');
    console.log(JSON.stringify(errorJSON));
    throw JSON.stringify(errorJSON);
  }
}
    function showModeless() {
      var ui = HtmlService.createHtmlOutputFromFile('modal')
          //.setTitle('Extract Quiz Q&A');
          .setWidth(550)
          .setHeight(440)
          FormApp.getUi().showModelessDialog(ui, "Upload Quiz Q&A");
    }

    function notifyUser(){
      var ui = HtmlService.createHtmlOutputFromFile('confirmation-message')
          .setWidth(420)
          .setHeight(120);
      FormApp.getUi().showModalDialog(ui, 'Quiz Uploaded Successfully');
    }
    
    function error(response){
    
      var html = HtmlService.createTemplateFromFile('answer-missing-error');
      html.id = response.Question;
      FormApp.getUi().showModalDialog(html.evaluate().setWidth(520).setHeight(250), "Error");
    }
    
    function loading() {
      var ui = HtmlService.createHtmlOutputFromFile('loading-icon')
          .setWidth(170)
          .setHeight(76);
      FormApp.getUi().showModalDialog(ui, 'Loading Quiz to MOATT Database');
    }

answer-missing-error.html:

   <body>
    <div class="container">
        <div class="row">
            <div class="input-field col s12">
              <span style="font-size:15px">Following Question Need Revision:</span><br />
              <?=id?><br /><br />
              <span style="font-size:15px">Answer provided does not match any of the available options. Please correct error and resubmit.</span>
            </div>
          </div>
          <div class="col s12 bottom">
            <div class="floatImg"><img alt="Add-on logo" class="logo" width="25" src="https://drive.google.com/uc?id=1pkKdxeGRAAlotOnP8LIFO3dmbU6giI7h"></div>
            <div class="floatMOATTText"><span class="gray branding-text">Powered By MOATT</span></div>
          </div>
        </div>
      </div>
  </body>

loading-icon.html:

  <body>
    <div class="container">
        <div class="row">
            <div style="text-align:center;">
              <div class="lds-dual-ring"></div>
              </div>
          </div>
        </div>
      </div>
  </body>


Solution 1:[1]

When I saw your script, I thought that the reason of your issue might be due to that google.script.run is run with the asynchronous process. So, in your script, how about the following modification?

From:

google.script.run.loading();
console.log("after loading");
google.script.run.withSuccessHandler(onExtractDataFormSuccess).extractFormData(fullNme);
console.log("after onExtractDataFormSuccess");

To:

google.script.run.withSuccessHandler(_ => {
  console.log("after loading");
  google.script.run.withSuccessHandler(e => {
    onExtractDataFormSuccess(e);
    console.log("after onExtractDataFormSuccess");
  }).extractFormData(fullNme);
}).loading();

Reference:

Added 1:

Now, I had noticed that you had updated your question. When I saw your updated question, I think that the reason of your issue is due to the Google Apps Script side. In your situation, it seems that you are using 3 HTMLs. And, the HTML is loaded from each function of Google Apps Script. In this case, it is required to write Javascript in each HTML. I think that this is the reason of your issue.

Although, unfortunately, I cannot understand your actual script, as a sample modification, I added the modified script by using your showing scripts.

Modified script:

Google Apps Script side: Code.gs

// In order to test this script, please run this function.
function main() {
  var ui = HtmlService.createHtmlOutputFromFile('index');
  FormApp.getUi().showModalDialog(ui, 'main');
}

function loading(e) {
  var html = HtmlService.createTemplateFromFile('loading-icon');
  html.e = e;
  FormApp.getUi().showModalDialog(html.evaluate().setWidth(170).setHeight(76), 'Loading Quiz to MOATT Database');
}

function extractFormData(e) {

  // do something

  return { Success: false, Question: "Question" }; // or {Success: true, Question: "Question"} sample response.
}

function error(response) {
  var html = HtmlService.createTemplateFromFile('answer-missing-error');
  html.id = response.Question;
  FormApp.getUi().showModalDialog(html.evaluate().setWidth(520).setHeight(250), "Error");
}

HTML&Javascript side: index.html

main

<script>
var fullNme = "sample";
google.script.run.loading(fullNme);
</script>

HTML&Javascript side: loading-icon.html

loading-icon

<script>
var fullNme = "<?= e ?>";
console.log("At loading-icon: " + fullNme)
google.script.run.withSuccessHandler(onExtractDataFormSuccess).extractFormData(fullNme);

function onExtractDataFormSuccess(response) {
  console.log("ENTER onExtractDataFormSuccess");
  if (!response.Success) {
    console.log("encountered error in form");
    google.script.run.withSuccessHandler(_ => {
      console.log("after onExtractDataFormSuccess");
    }).error(response);
  } else {
    console.log("after onExtractDataFormSuccess");
  }
}
</script>

HTML&Javascript side: answer-missing-error.html

answer-missing-error
<?!= id ?>

Added 2:

By guessing your expected flow using your showing script, I added one more sample script.

Unfortunately, when a dialog is opened using modal.html, no script is run. By this, the script cannot be used.

So, as a simple sample script, I added 2 buttons for executing checkForm() with and without the error.

When I saw your Google Apps Script and Javascript, when checkForm() is run and loading() of Google Apps Script is run, the initial dialog is overwritten. By this, the functions of withFailureHandler and withSuccessHandler on modal.html are not run. I think that this is the reason for your issue.

If you want to open other dialogs of notifyUser() and error(), it is required to run the script on the Google Apps Script side.

So, in this additional sample script, by simplifying your script, I propose a sample script for achieving your goal.

Google Apps Script side: Code.gs

From your script, please run the function showModeless().

function extractFormData(e) {
  loading();


  // do something.
  Utilities.sleep(2000);


  if (e) {
    dispError("Sample error.");
    return;
  }

  return notifyUser();
}

function showModeless() {
  var ui = HtmlService.createHtmlOutputFromFile('modal').setWidth(550).setHeight(440);
  FormApp.getUi().showModelessDialog(ui, "Upload Quiz Q&A");
}

function notifyUser() {
  var ui = HtmlService.createHtmlOutputFromFile('confirmation-message').setWidth(420).setHeight(120);
  FormApp.getUi().showModalDialog(ui, 'Quiz Uploaded Successfully');
}

function dispError(response) {
  var html = HtmlService.createTemplateFromFile('answer-missing-error');
  html.id = response.Question;
  FormApp.getUi().showModalDialog(html.evaluate().setWidth(520).setHeight(250), "Error");
}

function loading() {
  var ui = HtmlService.createHtmlOutputFromFile('loading-icon').setWidth(170).setHeight(76);
  FormApp.getUi().showModalDialog(ui, 'Loading Quiz to MOATT Database');
}

HTML & Javascript side:

modal.html

As a sample, "run checkForm() with no error" button runs without error. "run checkForm() with error" button runs with error. You can test both situations.

<input type="button" value="run checkForm() with no error" onclick="checkForm()">
<input type="button" value="run checkForm() with error" onclick="checkForm('Error')">

<script>
function checkForm(e) {
  google.script.run.withFailureHandler(onExtractDataFormFailure).withSuccessHandler(onExtractDataFormSuccess).extractFormData(e);
}

function onExtractDataFormSuccess() {
  console.log("ENTER onExtractDataFormSuccess");
  google.script.run.notifyUser();
}

function onExtractDataFormFailure() {
  console.log("ENTER onExtractDataFormSuccess");
  google.script.run.dispError(errorJSON);
}
</script>

answer-missing-error.html

answer-missing-error
<input type="button" value="ok" onclick="google.script.run.showModeless()">

loading-icon.html

loading-icon

confirmation-message.html

confirmation-message
<input type="button" value="ok" onclick="google.script.run.showModeless()">

Testing

When the function of showModeless() at Google Apps Script is run, a dialog is opened using modal.html.

  1. When a button of "run checkForm() with no error" is clicked, a dialog of loading-icon.html is opened, and the Google Apps Script continues to be run. And, when the script is finished, a dialog of confirmation-message.html is opened. When the ok button is clicked, a dialog of modal.html is run.

  2. When a button of "run checkForm() with error" is clicked, a dialog of loading-icon.html is opened, and the Google Apps Script continues to be run. And, when the script is finished, a dialog of answer-missing-error.html is opened. When the ok button is clicked, a dialog of modal.html is run.

Note:

  • From your provided information, I thought that this flow might be your expected flow.
  • This sample script is a simple sample script for explaining your current issue and your goal. So, please modify this for your actual situation.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1