0

I've been trying to add 3rd and 4th level dependent dropdown using the code from Code with Curt(https://codewithcurt.com/create-dependent-drop-down-on-google-web-app/), but I'm running into some issues. In this code below, I'm trying to add a 3rd level, but it doesn't seem to work. This is the output I'm trying to achieve. I'm not sure if there's a fastest way to load the dropdown from Google sheets, as this codes loads in about 3 seconds, or a better way to fetch it from Sheets.

enter image description here

Here's the code:

Google Apps Script:

function doGet(e) {
  var htmlOutput = HtmlService.createTemplateFromFile('DependentSelect');
  var colors = getColors();
  htmlOutput.message = '';
  htmlOutput.colors = colors;
  return htmlOutput.evaluate();
}

function doPost(e) {

  Logger.log(JSON.stringify(e));

  var name = e.parameters.name.toString();
  var color = e.parameters.color.toString();
  var fruit = e.parameters.fruit.toString();
  var class = e.parameters.class.toString(); //class is a reserved word

  AddRecord(name, color, fruit, class);

  var htmlOutput = HtmlService.createTemplateFromFile('DependentSelect');
  var colors = getColors();
  htmlOutput.message = 'Record Added';
  htmlOutput.colors = colors;
  return htmlOutput.evaluate();

}

function getColors() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV");
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for (var i = 2; i <= getLastRow; i++) {
    if (return_array.indexOf(lovSheet.getRange(i, 1).getValue()) === -1) {
      return_array.push(lovSheet.getRange(i, 1).getValue());
    }
  }


  return return_array;
}

function getFruits(color) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV");
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for (var i = 2; i <= getLastRow; i++) {
    if (lovSheet.getRange(i, 1).getValue() === color) {
      return_array.push(lovSheet.getRange(i, 2).getValue());
    }
  }


  return return_array;
}
function getClass(fruit) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV");
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for (var i = 2; i <= getLastRow; i++) {
    if (lovSheet.getRange(i, 2).getValue() === fruit) {
      return_array.push(lovSheet.getRange(i, 3).getValue());
    }
  }


  return return_array.sort();
}


function AddRecord(name, color, fruit, class) {
  var url = '';   //URL OF GOOGLE SHEET;
  var ss = SpreadsheetApp.openByUrl(url);
  var dataSheet = ss.getSheetByName("DATA");
  dataSheet.appendRow([name, color, fruit, class, new Date()]);
}

function getUrl() {
  var url = ScriptApp.getService().getUrl();
  return url;
}

HTML:

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
</head>

<body>
  <script>
    function GetFruit(color) 
    {
    
    google.script.run.withSuccessHandler(function(ar) 
    {

    console.log(ar);
    
    fruit.length = 0;
    
    let option = document.createElement("option");
    option.value = "";
    option.text = "";
    fruit.appendChild(option);
    
    ar.forEach(function(item, index) 
    {    
      let option = document.createElement("option");
      option.value = item;
      option.text = item;
      fruit.appendChild(option);    
    });
    
    }).getFruits(color);
    
    };

 function getClass(queue)
{

google.script.run.withSuccessHandler(function(ar) 
{

console.log(ar);

class.length = 0;

let option = document.createElement("option");
option.value = "";
option.text = "";
class.appendChild(option);

ar.forEach(function(item, index) 
{    
  let option = document.createElement("option");
  option.value = item;
  option.text = item;
  class.appendChild(option);    
});

}).getClass(queue);

};
  </script>


  <h1>Web App Dependent Drop Down</h1>
  <?var url = getUrl();?>
  <form method="post" action="<?= url ?>">
    <label style="font-size: 20px" >Name</label><br>
    <input type="text" name="name" style="font-size: 20px" /><br><br>
    <label style="font-size: 20px" >Colors</label><br>
    <select name="color" style="font-size: 20px" onchange="GetFruit(this.value)" >
      <option value="" ></option>
      <? for(var i = 0; i < colors.length; i++) { ?>      
      <option value="<?= colors[i] ?>" ><?= colors[i] ?></option>
      <? } ?>
      </select><br><br>
    <label style="font-size: 20px" >Fruit</label><br>
    <select name="fruit" id="fruit" style="font-size: 20px" >
      </select><br><br>
    <label style="font-size: 20px" >Class</label><br>
    <select name="location" id="location" style="font-size: 20px" >
  <option value="" selected disabled>Select Class</option>
  </select><br><br>

    <label style="font-size: 20px" >Brand</label><br>
    <select name="location" id="location" style="font-size: 20px" >
  <option value="" selected disabled>Select Brand</option>
  </select><br><br>
    <input type="submit" name="submitButton" value="Submit" style="font-size: 20px" />
    <span style="font-size: 20px" ><?= message ?></span>
  </form>
</body>

</html>
coders_key
  • 61
  • 8
  • In your question, you say `I've been trying to add 3rd and 4th level dependent dropdown`. And, in your current script, it seems that your current issues are `I'm trying to add a 3rd level, but it doesn't seem to work.` and `I'm not sure if there's a fastest way to load the dropdown from Google sheets`. And, when I saw your showing script, on your HTML side, it seems that `getClass` is not called. And, I cannot find the script for the 4th level. And also, I cannot imagine your Spreadsheet. I apologize for this. Can I ask you the detail of your question? – Tanaike Oct 15 '22 at 00:56
  • Hi @Tanaike, thank you for your time. I did not include the script for the 4th level yet as I'm still trying to figure out the 3rd level, but I'm looking to have a 4th level dropdown as well. And regarding my sheet I only have two Sheets: "DATA" and "LOV" for the dropdown, it has 4 columns(Color, Fruit, Class, Brand). Example row data: Color: Red, Fruit: Apple, Class: A, Brand: X). – coders_key Oct 15 '22 at 01:25
  • Thank you for replying. From your reply, I proposed a modified script as an answer. Could you please confirm it? If I misunderstood your question and that was not useful, I apologize. Unfortunately, it seems that in the current stage, the image cannot be uploaded. So, now, I couldn't upload a test situation as the image. I apologize for this. – Tanaike Oct 15 '22 at 05:49
  • Hi @Tanaike. Apologies for late reply. Amazing, this loads much faster. Thank you so much. The only issue I've noticed is the level 4 dropdown which is the brand, it does not dynamically change based on Class selection. – coders_key Oct 18 '22 at 00:10
  • About `The only issue I've noticed is the level 4 dropdown which is the brand, it does not dynamically change based on Class selection.`, when "Class 1" is changed to "Class 2", the value of "Brand" is changed. I apologize for this. In order to correctly understand it, can you provide the sample Spreadsheet including my proposed script for correctly replicating it? By this, I would like to confirm it. – Tanaike Oct 18 '22 at 00:38

2 Answers2

2

I believe your goal is as follows.

  • You want to reduce the process cost of your script.

Modification points:

  • When a loop process is used using the HTML template, the process cost becomes high. Ref

    • In this case, the HTML template is used for replacing the values.
  • When google.script.run is used, the process cost becomes high.

    • In this case, google.script.run is used for sending the values to the Google Apps Script side instead of the form submission.
    • From your showing script, I thought that the values might not be required to be sent with the HTML request. So, in this modification, the values are sent with google.script.run.
  • Creations of the options in the select tag are done in Javascript using the 1st loaded values.

  • In your Google Apps Script side, getValue() is used in a loop. In this case, the process cost becomes high. Ref

When these points are reflected in your showing script, how about the following modification?

Google Apps Script side:

function doGet(e) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("LOV");
  var [, ...values] = sheet.getDataRange().getDisplayValues();
  var htmlOutput = HtmlService.createTemplateFromFile('DependentSelect');
  htmlOutput.values = JSON.stringify(values);
  htmlOutput.message = '';
  return htmlOutput.evaluate();
}

function addRecord({ name, color, fruit, clas, brand }) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DATA");
  sheet.appendRow([name, color, fruit, clas, brand, new Date()]);
}

HTML & Javascript side:

<h1>Web App Dependent Drop Down</h1>
<form>
  <label style="font-size: 20px" >Name</label><br>
  <input type="text" name="name" style="font-size: 20px" /><br><br>
  <label style="font-size: 20px" >Colors</label><br>
  <select id="colors" name="color" style="font-size: 20px" onchange="setOptions('fruit', getValues(this.value, 0))" ></select><br><br>
  <label style="font-size: 20px" >Fruit</label><br>
  <select name="fruit" id="fruit" style="font-size: 20px" onchange="setOptions('clas', getValues(this.value, 1))" ></select><br><br>
  <label style="font-size: 20px" >Class</label><br>
  <select name="clas" id="clas" style="font-size: 20px" onchange="setOptions('brand', getValues(this.value, 2))" ></select><br><br>
  <label style="font-size: 20px" >Brand</label><br>
  <select name="brand" id="brand" style="font-size: 20px" ></select><br><br>
  <input type="button" name="submitButton" value="Submit" style="font-size: 20px" onclick="sample(this.parentNode)" >
  <span style="font-size: 20px" ><?= message ?></span>
</form>
<script>
const values = JSON.parse(<?= values ?>);

function setOptions(id, v) {
  const s = document.getElementById(id);
  s.innerHTML = "";
  v.forEach(a => {
    const option = document.createElement("option");
    option.value = a;
    option.innerHTML = a;
    s.appendChild(option);
 });
}

function getValues(e, i) {
  return ["", ...new Set(values.reduce((ar, r) => (r[i] == e && ar.push(r[i + 1]), ar), []))];
}

window.onload = function() {
  setOptions("colors", ["", ...new Set(values.map(([a]) => a))]);
}

function sample(e) {
  google.script.run.addRecord(e);
}
</script>

Note:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • ! Apologies for late reply. Amazing, this loads much faster. Thank you so much. The only issue I've noticed is the level 4 dropdown which is the brand, it does not dynamically change based on Class selection. – coders_key Oct 18 '22 at 00:09
  • @coders_key Thank you for replying. About `The only issue I've noticed is the level 4 dropdown which is the brand, it does not dynamically change based on Class selection.`, when I tested my proposed script, I cannot replicate your situation. I apologize for this. In order to correctly understand your current issue of `it does not dynamically change based on Class selection`, can you provide your sample Spreadsheet for correctly replicating it? By this, I would like to confirm it. – Tanaike Oct 18 '22 at 00:11
  • Hi, for example. I have this set of data. When i tried to Filter exampla. Red->Apple->Class 1> the brand dropdowns shosw Brand 1 Brand X and Brand 3. Instead of just Brand 1 and X. Red Apple Class 1 Brand 1 Red Apple Class 1 Brand X Yellow Banana Class 1 Brand 3 – coders_key Oct 18 '22 at 00:21
  • @coders_key Thank you for replying. Unfortunately, I couldn't understand your reply. Can I ask you about the detail of your reply? – Tanaike Oct 18 '22 at 00:23
  • I've updated the screen capture on my original post. – coders_key Oct 18 '22 at 00:32
  • @coders_key Thank you for replying. When I saw your updated image, I think that "Class 1" is not "Brand 1, Brand 3, Brand X" from your provided sample data. I think that when it's "Class 1", it is "Brand 1, Brand 2, Brand X". So I cannot understand your current issue. I apologize for this. Can I ask you about the detail of your current issue and your goal? By this, I would like to confirm it. By the way, when "Class 1" is changed to "Class 2", the value of "Brand" is changed. How about this? – Tanaike Oct 18 '22 at 00:34
  • @coders_key Oh! I noticed that your provided data in your reply was changed, just now. I apologize for this. But, when "Class 1" is changed to "Class 2", the value of "Brand" is changed. I apologize for this. In order to correctly understand it, can you provide the sample Spreadsheet including my proposed script for correctly replicating it? By this, I would like to confirm it. – Tanaike Oct 18 '22 at 00:45
1

You may use the following GAS and HTML:

Google Apps Script

I have added the onlyUnique function (from another SO post) to filter out similar entries. I also added other functions to cater the Class and Brand columns. I also changed the variable class to classParam since class is a reserved word.

function doGet(e) {
  var htmlOutput =  HtmlService.createTemplateFromFile('DependentSelect');
  var colors = getColors();
  var fruits = getFruits();
  var classParams = getClasses();
  var brands = getBrands();
  htmlOutput.message = '';
  htmlOutput.colors = colors;
  htmlOutput.fruits = fruits;
  htmlOutput.classParams = classParams;
  htmlOutput.brands = brands;
  return htmlOutput.evaluate();
}

function doPost(e) {
  
  Logger.log(JSON.stringify(e));
  
  var name = e.parameters.name.toString();
  var color = e.parameters.color.toString();
  var fruit = e.parameters.fruit.toString();
  var classParam = e.parameters.classParam.toString();
  var brand = e.parameters.brand.toString();
  
  AddRecord(name, color, fruit, classParam, brand);
  
  var htmlOutput =  HtmlService.createTemplateFromFile('DependentSelect');
  var colors = getColors();
  var fruits = getFruits();
  var classParams = getClasses();
  var brands = getBrands();
  htmlOutput.message = 'Record Added';
  htmlOutput.colors = colors;
  htmlOutput.fruits = fruits;
  htmlOutput.classParams = classParams;
  htmlOutput.brands = brands;

  return htmlOutput.evaluate(); 
  
}

function onlyUnique(value, index, self) {
  return self.indexOf(value) === index;
}

function getColors() { 
  var ss= SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV"); 
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for(var i = 2; i <= getLastRow; i++)
  {
      if(return_array.indexOf(lovSheet.getRange(i, 1).getValue()) === -1) {
        return_array.push(lovSheet.getRange(i, 1).getValue());
      }
  }


  return return_array.filter(onlyUnique);  
}

function getFruits(color) { 
  var ss= SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV"); 
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for(var i = 2; i <= getLastRow; i++)
  {
      if(lovSheet.getRange(i, 1).getValue() === color) {
        return_array.push(lovSheet.getRange(i, 2).getValue());
      }
  }


  return return_array.filter(onlyUnique);  
}

function getClasses(color, fruit) { 
  var ss= SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV"); 
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for(var i = 2; i <= getLastRow; i++)
  {
      if((lovSheet.getRange(i, 1).getValue() === color) && (lovSheet.getRange(i, 2).getValue() === fruit)) {
        return_array.push(lovSheet.getRange(i, 3).getValue());
      }
  }

  return return_array.filter(onlyUnique);  
}

function getBrands(color, fruit, classParam) { 
  var ss= SpreadsheetApp.getActiveSpreadsheet();
  var lovSheet = ss.getSheetByName("LOV"); 
  var getLastRow = lovSheet.getLastRow();
  var return_array = [];
  for(var i = 2; i <= getLastRow; i++)
  {
      if((lovSheet.getRange(i, 1).getValue() === color) && (lovSheet.getRange(i, 2).getValue() === fruit) && (lovSheet.getRange(i, 3).getValue() === classParam)) {
        return_array.push(lovSheet.getRange(i, 4).getValue());
      }
  }


  return return_array.filter(onlyUnique);  
}

function AddRecord(name, color, fruit, classParam, brand) {
  var url = "";   //INSERT SPREADSHEET URL HERE <---------
  var ss = SpreadsheetApp.openByUrl(url);
  var dataSheet = ss.getSheetByName("DATA");
  dataSheet.appendRow([name, color, fruit, classParam, brand, new Date()]);
}

function getUrl() {
 var url = ScriptApp.getService().getUrl();
 return url;
}

HTML

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
    function GetFruit(color) 
    {
    
    google.script.run.withSuccessHandler(function(ar) 
    {

    console.log(ar);
    
    fruit.length = 0;
    
    let option = document.createElement("option");
    option.value = "";
    option.text = "";
    fruit.appendChild(option);
    
    ar.forEach(function(item, index) 
    {    
      let option = document.createElement("option");
      option.value = item;
      option.text = item;
      fruit.appendChild(option);    
    });
    
    }).getFruits(color);
    
    };

    function GetClass(color, fruit) 
    {
    
    google.script.run.withSuccessHandler(function(ar) 
    {

    console.log(ar);
    
    classParam.length = 0;
    
    let option = document.createElement("option");
    option.value = "";
    option.text = "";
    classParam.appendChild(option);
    
    ar.forEach(function(item, index) 
    {    
      let option = document.createElement("option");
      option.value = item;
      option.text = item;
      classParam.appendChild(option);    
    });
    
    }).getClasses(color, fruit);
    
    };

    function GetBrand(color, fruit, classParam) 
    {
    
    google.script.run.withSuccessHandler(function(ar) 
    {

    console.log(ar);
    
    brand.length = 0;
    
    let option = document.createElement("option");
    option.value = "";
    option.text = "";
    brand.appendChild(option);
    
    ar.forEach(function(item, index) 
    {    
      let option = document.createElement("option");
      option.value = item;
      option.text = item;
      brand.appendChild(option);    
    });
    
    }).getBrands(color, fruit, classParam);
    
    };
  </script>  
  </head>
  <body>
    <h1>Web App Dependent Drop Down</h1>
    <?var url = getUrl();?>
    <form method="post" action="<?= url ?>" >
      <!-- name -->
      <label style="font-size: 20px" >Name</label><br>
      <input type="text" name="name" style="font-size: 20px" /><br><br>

      <!-- color -->
      <label style="font-size: 20px" >Colors</label><br>
      <select name="color" id="color" style="font-size: 20px" onchange="GetFruit(this.value)" >
      <option value="" ></option>
      <? for(var i = 0; i < colors.length; i++) { ?>      
      <option value="<?= colors[i] ?>" ><?= colors[i] ?></option>
      <? } ?>
      </select><br><br>

      <!-- fruit -->
      <label style="font-size: 20px" >Fruits</label><br>
      <select name="fruit" id="fruit" style="font-size: 20px" onchange="GetClass(color.value, this.value)" >
      <option value="" ></option>
      <? for(var i = 0; i < fruits.length; i++) { ?>      
      <option value="<?= fruits[i] ?>" ><?= fruits[i] ?></option>
      <? } ?>
      </select><br><br>

      <!-- class -->
      <label style="font-size: 20px" >Classes</label><br>
      <select name="classParam" id="classParam" style="font-size: 20px" onchange="GetBrand(color.value, fruit.value, this.value)" >
      <option value="" ></option>
      <? for(var i = 0; i < classParams.length; i++) { ?>      
      <option value="<?= classParams[i] ?>" ><?= classParams[i] ?></option>
      <? } ?>
      </select><br><br>

      <!-- brand -->
      <label style="font-size: 20px" >Brand</label><br>
      <select name="brand" id="brand" style="font-size: 20px" >
      </select><br><br>

      <input type="submit" name="submitButton" value="Submit" style="font-size: 20px" /> 
      <span style="font-size: 20px" ><?= message ?></span>
      
    </form>
  </body>
</html>

Sample Data

enter image description here

Web App

enter image description here

Output

enter image description here

PatrickdC
  • 1,385
  • 1
  • 6
  • 17
  • Feel free to ask if you have any questions with regards to my answer. – PatrickdC Oct 15 '22 at 03:16
  • Wooh! Thank you Patrick! Appreciate you man! I just noticed that the brand does not dynamically change based on classes selection. – coders_key Oct 15 '22 at 05:01
  • Thank you for pointing out the bug. Apparently, the filtering for each column is only dependent on the first column to its left. I'll look into it – PatrickdC Oct 15 '22 at 05:25
  • Updated both the GAS and HTML. I added additional codes to the conditional statements as well as adding input variables to the other functions. Kindly test it on your side. – PatrickdC Oct 15 '22 at 05:46
  • Hey PatrickdC. Apologies for delayed response. You're amazing!! It worked perfectly well. Appreciate your help big time. – coders_key Oct 18 '22 at 00:16
  • That is good to hear. If my answer helped you, please feel free to accept my answer or give it an upvote so that other users (which may have the same concerns as you) will know that they may use this method that I have provided. – PatrickdC Oct 19 '22 at 03:42