简体   繁体   中英

Apps script: Show loader on button click in a form/page with components like dynamic drop downs, multiple inputs, etc

I am trying to show a loader in the google sheets sidebar on the button click either on the complete page or at least on the button so that users submitting the form should not press again until the earlier form is submitted. I have added loader with support from @Tanaike in this question . Although that works in some pages that have fewer and simple components but it fails to work in the page with more and complex components like multi-select with dynamic values that are fetched from sheet, adding/removing multiple inputs, multistep form, etc, and throws error in the console of the browser Uncaught TypeError: google.script.withSuccessHandler is not a function at copyFiles (userCodeAppPanel:91:23) at HTMLButtonElement.onclick (userCodeAppPanel:1:1) . I am wondering if I could make the one answered in the above-mentioned question work on this complex page as well.

[ 整页加载器 ] [1] 按钮加载器

complete HTML code with all components:

<body onload="addList()">

<div id="loader" class="loader" style="display:none;">
    <div class="loadingio-spinner-spinner-u1f174wamc"><div class="ldio-43p7dnbmew6">
      <div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
    </div></div>
</div>


<form id="regForm" style="padding-top:0px;"> 

  <div class="steps">
    <div class="step"></div>
    <div class="first_step_content">Copy FIles</div>
    <div class="step_line"></div>
    <div class="step"></div>
    <div class="second_step_content">Select Members</div>
  </div>

  <div class="tab">
    <div>
      
      <input list="dropdownList" name="list" placeholder="Choose Client" id="clientname" required class="client_list"> <!-- Modified -->
      <datalist id="dropdownList"> <!--  Dynamic list of clients --> </datalist>
      
    </div>

    <div id="files">
      
      <? var innerHTML= createInnerHTML(); ?>  
      <select name="langOpt[]" multiple id="langOpt" aria-label="JOINT" aria-required="true" required="">
        <!-- <option value=""></option> -->
        <? innerHTML.forEach(function(option) { ?>
          <option class="<?= option.clas ?> post" value="<?= option.value ?>"><?= option.text ?></option>
        <? }); ?>
      </select>
    </div>
  </div>

  <div class="tab">
    <div>
        <label for="List">Select Client:</label><br>
        <input list="dropdownList" name="list" placeholder="Choose Client" id="clientname" required class="client_list" onkeyup="myFunction()" > <!--  Modified -->
        <datalist id="dropdownList"> <!--  Dynamic list of clients  --> </datalist>
          
      </div>

      <div id="editors">
        <!-- Writers -->
        <label for="List">Select Writers</label><br>
        <? var innerHTML= writer_list(); ?>  
        <select name="writers[]" multiple id="writers" aria-label="JOINT" aria-required="true" required="">
          <!-- <option value=""></option> -->
          <? innerHTML.forEach(function(option) { ?>
            <option class="<?= option.clas ?> post" value="<?= option.value ?>"><?= option.text ?></option>
          <? }); ?>
        </select>

      </div>

      <label>Share with Others</label>
      <div class="field_wrapper">
        <div>
            <input type="text" name="field_name[]" value="" placeholder="example@gmail.com" style="width: 68%;"/>
            <a href="javascript:void(0);" class="add_button" title="Add field"><ion-icon name="add-outline" class="icon"></ion-icon></a>
        </div>
    </div>
  </div>

  <!-- <input type="button" value="Add templates" id="btn"> -->

  <div style="overflow:auto;">
    <div style="align-items: center; align-content: center; text-align: center; margin-top: 30px;">
      <button type="button" id="prevBtn" onclick="nextPrev(-1)">Previous</button>
      <button type="button" id="nextBtn" onclick="nextPrev(1)">Next</button>
    </div>
  </div>

  <script>
    $('#langOpt').multiselect({
      columns: 1,
      placeholder: 'Select Templates',
      search: true,
      selectAll: true,
      // id: 'temp',
    });

    $('#writers').multiselect({
        columns: 1,
        placeholder: 'Select Writers',
        search: true,
        selectAll: true,
        // id: 'temp',
    });

      
    // Creates the dynamic input Fields
    $(document).ready(function(){
      var maxField = 10; //Input fields increment limitation
      var addButton = $('.add_button'); //Add button selector
      var wrapper = $('.field_wrapper'); //Input field wrapper
      var fieldHTML = '<div><input type="text" name="field_name[]" value="" style="width: 68%;"/><a href="javascript:void(0);" class="remove_button"><ion-icon name="remove-outline" class="icon"></ion-icon></a></div>'; //New input field html 
      var x = 1; //Initial field counter is 1
      
      //Once add button is clicked
      $(addButton).click(function(){
        //Check maximum number of input fields
        if(x < maxField){ 
          x++; //Increment field counter
          $(wrapper).append(fieldHTML); //Add field html
        }
      });
      
      //Once remove button is clicked
      $(wrapper).on('click', '.remove_button', function(e){
        e.preventDefault();
        $(this).parent('div').remove(); //Remove field html
        x--; //Decrement field counter
      });
    });

    
    // The function that runs on button click
    function copyFiles() {
      const button = document.getElementById("btn");
      button.innerHTML = "Copying..";
      button.setAttribute('disabled', 'disabled');
      document.getElementById("loader").style.display = "block";
      var tempid = [];
      var tempname = [];
      $('#files input:checked').each(function() {
        tempid.push($(this).attr('value'));
        tempname.push($(this).attr('title'));
      });

      var email = [];
      $('#editors input:checked').each(function() {
        email.push($(this).attr('value'));
      }); 

      var input_array= $("input[name='field_name[]']").map(function copyFiles() {
        return this.value;
      }).get();
      var emails = [...email, ...input_array];
      console.log(emails)
      
      // client Name
      var name = document.getElementById('clientname');
      var client_name = name.value;
      // var folder_url = name.title;

      google.script.withSuccessHandler(_ => {
        
        // Please set the loading animation here.
        // In this sample modification, when the button is clicked, the button is disabled, when Google Apps Script is finished, the button is enabled.
        document.getElementById("loader").style.display = "none";
        button.removeAttribute('disabled');
        button.innerHTML = "Add";
      }).run.copy_files(tempid, tempname, client_name, emails);
      
      document.getElementById('clientname').value=''; //ms-options-wrap
      for (var checkbox of markedCheckbox) {
        checkbox.checked = false;
      }
      markedCheckbox.checked = false;
      
    }

  </script>
</form>

<script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>

<script>

    // Multi step form
    var currentTab = 0; // Current tab is set to be the first tab (0)
    showTab(currentTab); // Display the current tab

    function showTab(n) {
      // This function will display the specified tab of the form...
      var x = document.getElementsByClassName("tab");
      x[n].style.display = "block";
      //... and fix the Previous/Next buttons:
      if (n == 0) {
        document.getElementById("prevBtn").style.display = "none";
      } else {
        document.getElementById("prevBtn").style.display = "inline";
      }
      if (n == (x.length - 1)) {
        document.getElementById("nextBtn").innerHTML = "Copy Files";
        document.getElementById("nextBtn").setAttribute('id', 'btn');
        document.getElementById("btn").setAttribute('onclick', 'copyFiles()');
        // document.getElementById("btn").removeAttribute('onclick')
      } else {
        document.getElementById("btn").innerHTML = "Next";
        document.getElementById("btn").setAttribute('id', 'nextBtn');
        document.getElementById("nextBtn").setAttribute('onclick', 'nextPrev(1)');
      }
      //... and run a function that will display the correct step indicator:
      fixStepIndicator(n)
    }

    function nextPrev(n) {
      // This function will figure out which tab to display
      var x = document.getElementsByClassName("tab");
      // Exit the function if any field in the current tab is invalid:
      if (n == 1 && !validateForm()) return false;
      // Hide the current tab:
      x[currentTab].style.display = "none";
      // Increase or decrease the current tab by 1:
      currentTab = currentTab + n;
      // if you have reached the end of the form...

      showTab(currentTab);
    }

    function validateForm() {
      // This function deals with validation of the form fields
      var x, y, i, valid = true;
      x = document.getElementsByClassName("tab");
      y = x[currentTab].getElementsByTagName("input");
      // A loop that checks every input field in the current tab:
      for (i = 0; i < y.length; i++) {
        // If a field is empty...
        if (y[i].value == "") {
          // add an "invalid" class to the field:
          y[i].className += " invalid";
          // and set the current valid status to false
          valid = true;
        }
      }
      // If the valid status is true, mark the step as finished and valid:
      if (valid) {
        document.getElementsByClassName("step")[currentTab].className += " finish";
      }
      return valid; // return the valid status
    }

    function fixStepIndicator(n) {
      // This function removes the "active" class of all steps...
      var i, x = document.getElementsByClassName("step");
      for (i = 0; i < x.length; i++) {
        x[i].className = x[i].className.replace(" active", "");
      }
      //... and adds the "active" class on the current step:
      x[n].className += " active";
    }
  </script>
</body>

<script>
   function addList() {
      google.script.run
      .withFailureHandler(onFailure)
      .withSuccessHandler(addListValues)
      .getList();
   }

  function addListValues(values) {
  var list = document.getElementById('dropdownList');
  var temp_name = values.value;
  var url_link = values.url_link;
  
  for (var i = 0; i < temp_name.length; i++) {
    var option = document.createElement("option"); // Modified
    option.value = temp_name[i]; // Modified
    option.title = url_link[i];
    list.appendChild(option); // Modified
  }
  
}

function onFailure(err) {
  alert('There was an error!' + err.message);
}

Although this may be a long script to read and go through it. But helping me resolve it would mean a lot to me and I will be highly thank full to you. Thanks in advance.

Upon checking your code I saw a few typographical errors which are the following (see inline comments):

 google.script.withSuccessHandler(_ => { //I think this should be google.script.run.withSuccessHandler
        
        // Please set the loading animation here.
        // In this sample modification, when the button is clicked, the button is disabled, when Google Apps Script is finished, the button is enabled.
        document.getElementById("loader").style.display = "none";
        button.removeAttribute('disabled');
        button.innerHTML = "Add";
      }).run.copy_files(tempid, tempname, client_name, emails);

Here are also three tags that seems to be inputted incorrectly:

<option class="<?= option.clas ?> post" value="<?= option.value ?>"><?= option.text ?></option>

This should be:

<option class="<?= option.class ?> post" value="<?= option.value ?>"><?= option.text ?></option>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM