I have a dynamic HTML form that I generate from Google Apps Script. I expect the elements to appear in the same order that they appear in the input object, which is the same order by which I append the elements. But the elements actually appear in an unexpected order.
I followed this other post to try to sort this out but the elements still appear out of order when I run my code in GAS. The thing is, when I run my code in jsfiddle, it works as expected, i.e. the elements appear in the same order as they do in the input object. The elements just don't order as expected in GAS.
Why do the elements appear out of order in GAS but appear in order in jsfiddle? How do I resolve this in GAS using vanilla JS?
Copy of jsfiddle code:
HTML
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<div id='input_parent'></div>
</body>
<br><input type="button" value="Submit" onClick="test()">
</form>
JS
inputObj = {"first field":{"required":true,"dataType":"select","options":["first opt","second opt"]},"second field":{"required":true,"dataType":"text","options":"none"}}
// Section
section = document.getElementById("input_parent");
div = document.createElement("div");
div.setAttribute("id", "input_child");
section.appendChild(div);
var fields = Object.keys(inputObj);
Array.from(fields).forEach((arg) => {
// Label
section = document.getElementById("input_parent");
label = document.createElement("label");
label.setAttribute("id", "label_"+arg);
label.setAttribute("for", arg);
label_txt = document.createTextNode(arg+":");
label.appendChild(label_txt);
section.appendChild(label);
if (inputObj[arg].dataType == "select") {
// Create select element
section = document.getElementById("input_parent");
const select_element = document.createElement("select");
select_element.setAttribute("id", "select_"+arg);
section.appendChild(select_element);
var options = inputObj[arg].options
for(let o = 0; o < options.length; o++)
{
var element = document.getElementById("select_"+arg);
const option = document.createElement("option");
var text = document.createTextNode(arg+":");
option.textContent = options[o];
option.setAttribute("value", options[o]);
element.appendChild(option);
};
} else {
section = document.getElementById("input_parent");
input_field = document.createElement("input");
input_field.setAttribute("id", "input_"+arg);
input_field.setAttribute("type", inputObj[arg].dataType);
section.appendChild(input_field);
}
});
Additional info in response to @Nikko J. The following code should reproduce the results in the images below.
dynamHtmlTbrlsht.html
to render form.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div id='input_parent'></div>
</body>
<br><input type="button" value="Submit" onClick="test()">
</html>
<script type='text/javascript'>
window.onload = function(){
google.script.run.withSuccessHandler(addElements).getElementInfo();
}
</script>
<script type='text/javascript'>
function addElements(inputObj) {
// Section
section = document.getElementById("input_parent");
div = document.createElement("div");
div.setAttribute("id", "input_child");
section.appendChild(div);
var fields = Object.keys(inputObj);
Array.from(fields).forEach((arg) => {
// Label
section = document.getElementById("input_parent");
label = document.createElement("label");
label.setAttribute("id", "label_"+arg);
label.setAttribute("for", arg);
label_txt = document.createTextNode(arg+":");
label.appendChild(label_txt);
section.appendChild(label);
if (inputObj[arg].dataType == "select") {
// Create select element
section = document.getElementById("input_parent");
const select_element = document.createElement("select");
select_element.setAttribute("id", "select_"+arg);
section.appendChild(select_element);
var options = inputObj[arg].options
for(let o = 0; o < options.length; o++)
{
var element = document.getElementById("select_"+arg);
const option = document.createElement("option");
var text = document.createTextNode(arg+":");
option.textContent = options[o];
option.setAttribute("value", options[o]);
element.appendChild(option);
};
} else {
section = document.getElementById("input_parent");
input_field = document.createElement("input");
input_field.setAttribute("id", "input_"+arg);
input_field.setAttribute("type", inputObj[arg].dataType);
section.appendChild(input_field);
}
});
}
</script>
trblsht.gs
to create input object. (Note that this is simplified for resolving the problem at hand. In reality, inputObj
is generated by running a few functions that dynamically create the object and fetch options from an external source.)
function getElementInfo() {
var inputObj = {"first_fild":{"required":true,"dataType":"select","options":["option 1","option 2","option 3","option 4"]},"second_field":{"required":true,"dataType":"text","options":"none"},"third_field":{"required":true,"dataType":"text","options":"none"},"fourth_field":{"required":true,"dataType":"text","options":"none"},"fifth_field":{"required":false,"dataType":"select","options":["option 1","option 2","option 3","option 4","option 5","option 6","option 7","option 8","option 9","option10"]},"sixth_field":{"required":false,"options":"none"},"seventh_field":{"required":false,"dataType":"select","options":["option 1","option 2","option 3","option 4","option 5","option 6"]}}
Logger.log("inputObj: "+JSON.stringify(inputObj))
return inputObj;
}
For completeness, the following lines are in an onEdit
function that generates the form when the active cell == "troubleshoot"
.
function myOnEditTriggerFunc() {
// do other stuff
var currentRange = SpreadsheetApp.getActiveRange();
var currentVal =
currentRange.getValue().toString().replace(/(^\s+|\s+$)/g,"");
if (currentVal == "troubleshoot") {
openHTML("dynamHtmlTbrlsht","Troubleshoot",400)
return;
}
}
openHTML()
referenced in above function.
function openHTML(htmlFile,htmlTitle,height) {
var html = HtmlService.createHtmlOutputFromFile(htmlFile)
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setHeight(height);
SpreadsheetApp.getUi()
.showModalDialog(html, htmlTitle);
return;
};
Output form showing unexpected order of elements:
Output log showing expected order of elements:
The jsfiddle shows the normal js.
I started to wonder if the issue is with Array.from(fields).forEach((arg)
in dynamHtmlTbrlsht.html
. I intentionally used Array
instead of Object
since Array
is ordered whereas Object
is not always ordered (depends on ES). Maybe there's something related to V8 runtime that's affecting this and I'm not picking up on?