GAS: Is it possible to build html on server side - google-apps-script

I'm working on a complex synamic form with multiple inputs. I was thinking it might be easier to build in on the server side and inject into an HTMLservice form on the client side. I have:
function buildOptionList(id, options) {
// http://jsfiddle.net/4pwvg/
var document = '<div id="myDiv">Append here</div>';
// var myDiv = document.getElementById("myDiv");
//Create array of options to be added
// var array = ["Volvo","Saab","Mercades","Audi"];
var array = options;
//Create and append select list
var selectList = document.createElement("select");
selectList.setAttribute("id", id);
// myDiv.appendChild(selectList);
//Create and append the options
for (var i = 0; i < array.length; i++) {
var option = document.createElement("option");
option.setAttribute("value", array[i]);
option.text = array[i];
selectList.appendChild(option);
}
Logger.log(selectList)
return selectList;
}
But when I run it I get:
TypeError: Cannot find function createElement in object <div id="myDiv">Append here</div>
so I'm wondering if server side GAS has the ability to build HTML?

Related

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data on App Script web app

I am attempting to create an unordered list on my app script web app by pulling an array from a range on a google sheet.
I have a function in a gs file that works properly when used within the google sheet itself:
function listTest(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var invSheet = sheet.getSheetByName('Inventory');
var values = invSheet.getRange(2, 3, 25).getValues();
return JSON.stringify(values);
}
I have a javascript function that I am trying to implement based on the answer given here: Create a <ul> and fill it based on a passed array
my code is pretty much exactly this, minus the options variable:
function makeUL(array) {
// Create the list element:
var list = document.createElement('ul');
for(var i = 0; i < array.length; i++) {
// Create the list item:
var item = document.createElement('li');
// Set its contents:
item.appendChild(document.createTextNode(array[i]));
// Add it to the list:
list.appendChild(item);
}
// Finally, return the constructed list:
return list;
}
I ran a simplified version of it with just one list item, and it works:
function makeList() {
var list = document.createElement('ul');
var item = document.createElement('li');
item.appendChild(document.createTextNode("This is a test."));
list.appendChild(item);
return list;
}
document.getElementById("testDiv").appendChild(makeList());
However, when I try to pull the JSON array into the function to create the unordered list using the method in the link above, I get the error message: SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
My attempt at a function in my html to do this is:
function createList() {
var myJSON = google.script.run.listTest();
var array = JSON.parse(myJSON);
document.getElementById("testDiv").appendChild(makeUL(array));
}
createList();
I started off not using the JSON.stringify method in my listTest function. I was just ending with:
return values;
I was then getting the error 'array is undefined'.
I'm think JSON is the way to go with this, but I'm stuck.
Any help is appreciated.
Check the HTML Documentation for google.script.run. When you call google.script.run.listTest(); it doesn't actually return anything. You have to use the Success Handler which will receive returned data as a parameter. This is why your array is undefined.
EDIT: Updated This hasn't been completely tested:
function listTest(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var invSheet = sheet.getSheetByName('Inventory');
var values = invSheet.getRange(2, 3, 25).getValues();
return values;
}
function createList() {
google.script.run.withSuccessHandler(onSuccess)
.listTest();
}
function onSuccess(dataFromListTest){
document.getElementById("testDiv").appendChild(makeUL(dataFromListTest));
}
function makeUL(array) {
var list = document.createElement('ul');
for(var i = 0; i < array.length; i++) {
var item = document.createElement('li');
item.appendChild(document.createTextNode(array[i]));
list.appendChild(item);
}
return list;
}
See a different, basic example here: Gist - Using withSuccessHandler to Return Data
EDIT: Here is another example of an HTML File that calls a script back in the main code and puts that into a list in a div. This has been tested.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
google.script.run.withSuccessHandler(onSuccess)
.getSomeData(); //getSomeData() returns [["Cell 1"],["Cell 2"],["Cell 3"],["Cell 4"],["Cell 5"]] TO onSucess() AS A PARAMETER!
function onSuccess(data) {
var div = document.getElementById('output');
var list = document.createElement('ul'); // Create a <li> node
for(var i = 0; i < data.length; i++){
var item = document.createElement('li');
item.appendChild(document.createTextNode(data[i]));
list.appendChild(item);
}
div.appendChild(list);
}
</script>
</head>
<body>
<div id="output">
</div>
</body>
</html>
See the full example here on Gist with the main code.

How can I present customer data from spreadsheet into form in app maker for update?

I have struggling to present available data for selected customer from spreadsheet into app maker form incase staff want to change it or update empty fields.
Client side code:
function getDetails() {
var props = app.currentPage.properties;
var page = app.pages.Search;
var Channel = app.datasources.Update.items;
var Customer = page.descendants.Sheets.value;
props.Loading = true;
props.Error = null;
google.script.run
.withFailureHandler(function(error) {
props.Loading = false;
props.Error = JSON.stringify(error);
console.error(error);
})
.withSuccessHandler(function(Channel) {
props.Loading = false;
page.Channel = Channel;
var items = [];
items = getChannels(props.SelectedSheet);
Channel.items.load(); // this line dosen't work and it doesn't load the data into form
if (Channel && Channel.length > 0) {
page.SelectedSheet = Channel[0];
} })
.getDetails(props.SelectedSheet);
}
Server side code:
function getDetails()(customer){
var spreadSheet = SpreadsheetApp.openById("***").getSheetByName('TRACKER');
var data=spreadSheet.getDataRange().getValues();
var channels = [];
var Name = customer;
var string1 = Name;
var array1 = string1.split(";"); // in here I extract row number belong to customer to get data
var destrow = [];
destrow.push(data[array1[0]][0],data[array1[0]][1],data[array1[0]][2],data[array1[0]][3],data[array1[0]][4],data[array1[0]][5]);
channels.push(destrow);
// return channels;
return channels.map(function(Channel){
return Channel;}); // return array of field data to presented in app maker form
}
Thank you for any answer or suggestion.
Cheers
In theory, this code should throw exception, since Channel is array and array doesn't have load method:
function getDetails() {
...
var Channel = app.datasources.Update.items;
...
// your first Channel variable is never used and is overridden with
// Channel callback parameter
.withSuccessHandler(function(Channel) {
// this line does nothing, since all App Maker objects are sealed
page.Channel = Channel;
// TypeError: load is not a function
Channel.items.load();
...
}
It is not clear from you code, what you are trying to do... Try to debug it and look into browser console more often (F12 or Ctrl + Shift + J).
Further reading:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal

GUI form does not show up

I'm stuck... My code doesn't want to show me my GUI form.
If I'm trying to add this line: sh=SpreadsheetApp.getActive() before sh.show(app) - script works fine in the script editor. But! If I'm trying to deploy as a web app - script doesn't work.
function doGet() {
var app = UiApp.createApplication().setTitle('test online').setHeight(400).setWidth(600);
var dFormat = UiApp.DateTimeFormat.DATE_LONG
var sh = SpreadsheetApp.openById(spreadsheetID);
var settings = sh.getSheetByName('Name');
var lastrowTime = sh.getSheetByName('Name').getLastRow();
var settings = settings.getRange("A2:A" + lastrowTime).getValues();
var main2 = app.createGrid(1, 4);
var status = app.createLabel().setId('status').setWidth('200');
var card = app.createTextBox().setName('card').setId('card').setWidth('50');
var main1 = app.createGrid(6, 3);
var placeA = app.createTextBox().setId('placeA').setName('placeA').setWidth('400');
var placeB = app.createTextBox().setId('placeB').setName('placeB').setWidth('400');
var phone = app.createTextBox().setId(('phone')).setName('phone').setWidth('200');
var timeTo = app.createListBox(false).setWidth(200).setName('timeTo').addItem("...").setVisibleItemCount(1);
for (var i = 0; i < settings.length; i++) {
timeTo.addItem(settings[i]);
}
var main = app.createGrid(4, 5);
var date = app.createDateBox().setName('date').setFormat(dFormat);
var hour = app.createListBox().setName('hour').setWidth('100');
var min = app.createListBox().setName('min').setWidth('100');
for (h=0;h<24;++h){
if(h<10){var hourstr='0'+h}else{var hourstr=h.toString()}
hour.addItem(hourstr)
}
for (m=0;m<60;++m){
if(m<10){var minstr='0'+m}else{var minstr=m.toString()}
min.addItem(minstr)
}
var refresh = app.createButton('Refresh')
var button = app.createButton('Submit')
var main3 = app.createGrid(1,3);
var price = app.createLabel().setId('price').setWidth('400');
var finalStatus = app.createLabel().setId('finalPrice').setWidth('400');
main2.setWidget(0,0, app.createLabel('Client card: ')).setWidget(0,1, card).setWidget(0,3, status);
main1.setWidget(1,0, app.createLabel('From')).setWidget(1,1,placeA);
main1.setWidget(2,0, app.createLabel('To')).setWidget(2,1,placeB);
main1.setWidget(4,0, app.createLabel('Mobile')).setWidget(4,1,phone);
main1.setWidget(5,0, app.createLabel('Make a call?')).setWidget(5,1,timeTo);
main.setWidget(1,0,app.createLabel('Data')).setWidget(1,1,app.createLabel('hour')).setWidget(1,2,app.createLabel('min'))
main.setWidget(2,0,date).setWidget(2,1,hour).setWidget(2,2,min)
main.setWidget(2,3,refresh).setWidget(2,4, button)
main3.setWidget(0,0, price);
main3.setWidget(0,1, finalStatus);
var serverHandler = app.createServerHandler('show').addCallbackElement(main).addCallbackElement(main1).addCallbackElement(main2).addCallbackElement(main3);
button.addClickHandler(serverHandler)
var handler1 = app.createServerHandler('refresh').addCallbackElement(main).addCallbackElement(main1).addCallbackElement(main2).addCallbackElement(main3);
refresh.addClickHandler(handler1)
var handler2 = app.createServerHandler('checkDate').addCallbackElement(main).addCallbackElement(main1).addCallbackElement(main2).addCallbackElement(main3);
date.addValueChangeHandler(handler2)
app.add(main2)
app.add(main1)
app.add(main)
app.add(main3)
sh.show(app)
}
The methods that you are using to show your UI are specifically for Spreadsheet containers. You've probably read this, to get where you are, but re-read Creating User Interface Elements in UI Service, especially the examples of doGet().
function doGet() { // A script with a user interface that is published as a web app
// must contain a doGet(e) function.
...
return myapp;
}
At the end of the function, you simply need to return your UI App instance. No need to call show, or reference the spreadsheet at all.

nested listbox google apps script

Im trying to workaround the impossibility of creating nested listboxes in GAS. I created some arrays to populate the listboxes and used the for looping to connect them to their respective listboxes.
The Arrays
var TicketTypeArray=["TICKETTYPE1","TICKETTYPE2","TICKETTYPE3","TICKETTYPE4","TICKETTYPE5","TICKETTYPE6","TICKETTYPE7","TICKETTYPE8","TICKETTYPE9","TICKETTYPE10","TICKETTYPE11","TICKETTYPE12","TICKETTYPE13","TICKETTYPE14"];
var DemandedByArray=["DEMANDEDBY1","DEMANDEDBY1"];
var AnalystArray=["ANALYST1","ANALYST2","ANALYST3","ANALYST4","ANALYST5","ANALYST6","ANALYST7","ANALYST8","ANALYST9"];
var StatusType1Array=["STATUS1","STATUS2","STATUS3"];
var StatusType2Array=["STATUS1","STATUS2","STATUS3"];
var StatusType3Array=["STATUS1","STATUS2","STATUS3"];
Im trying to use a if else loop to nest the next listbox to another one:
if (TicketTypeListBox="TICKETTYPE1")
{
for(var i=0; i<StatusType1Array.length; i++)
{
StatusListBox.addItem(appRegistro.createLabel(StatusType1Array[i])).setItemText(i, StatusType1Array[i]);
}
}
else if (TicketTypeListBox="TICKETTYPE2")
{
for(var i=0; i<StatusType2Array.length; i++)
{
StatusListBox.addItem(appRegistro.createLabel(StatusType2Array[i])).setItemText(i, StatusType2Array[i]);
}
}
else
{
StatusListBox.addItem("Teste");
}
The TicketTypeListBox is:
var TicketTypeListBox = appRegistro.createListBox().setId('TicketType').setName('TicketType');
for(var i=0; i<TicketTypeArray.length; i++)
{
TicketTypeListBox.addItem(appRegistro.createLabel(TicketTypeArray[i])).setItemText(i, TicketTypeArray[i]);
}
To show the panel, im using the code:
panel.add(DataLabel);
panel.add(DataTextBox);
panel.add(TicketIDLabel);
panel.add(TicketIDTextBox);
panel.add(TicketTypeLabel);
panel.add(TicketTypeListBox);
panel.add(DemandedByLabel);
panel.add(DemandedByListBox);
panel.add(AnalystLabel);
panel.add(AnalystListBox);
panel.add(StatusLabel);
panel.add(StatusListBox);
appRegistro.add(panel);
return appRegistro
Now, when i run the script in a Google Sites, i get the error message "Cannot find method add(string)". When debuging, it locates the error just in the line of the TicketTypeListBox.
panel.add(TicketTypeListBox);
What can i do?
This error is coming because in the if block, instead of equality comparator, you have used assignment operator which assigns a string to the list box.
Istead of
if (TicketTypeListBox = "ACCESS CONTROL")
use
if (TicketTypeListBox == "ACCESS CONTROL")
Similarly modify other places also.
I am pretty much sure that still it will not work because, a listbox can not be compared with a string. To create nested listboxes, you will have to use change handler on the lisboxes. Here is a quick example for the same.
var lbArray1 = ['item1', 'item2', 'item3'];
var item1Array = ['item11', 'item12', 'item13'];
var item2Array = ['item21', 'item22', 'item23'];
var item3Array = ['item31', 'item32', 'item33', 'item34'];
function doGet(e){
var app = UiApp.createApplication();
var vPanel = app.createVerticalPanel();
app.add(vPanel);
var lb1 = app.createListBox().setName('lb1').setId('lb1');
for(var i in lbArray1){lb1.addItem(lbArray1[i])};
var lb2 = app.createListBox().setName('lb2').setId('lb2');
//load the 2nd lb with items dependent on first one
for(var i in item1Array){lb2.addItem(item1Array[i])};
vPanel.add(lb1).add(lb2);
//use change handler to dynamically change the 2nd listbox based on the selection of first listbox
var changeHandler1 = app.createServerHandler('updateListBox_').addCallbackElement(vPanel);
lb1.addChangeHandler(changeHandler1);
return app;
}
function updateListBox_(e){
var app = UiApp.getActiveApplication();
//first listbox item selected
var lb1Item = e.parameter.lb1;
var lb2 = app.getElementById('lb2');
lb2.clear();//remove all items from lb2
if(lb1Item == 'item1'){
for(var i in item1Array){lb2.addItem(item1Array[i])};
}
else if(lb1Item == 'item2'){
for(var i in item2Array){lb2.addItem(item2Array[i])};
}
else if(lb1Item == 'item3'){
for(var i in item3Array){lb2.addItem(item3Array[i])};
}
return app;
}

Access widgets in a VerticalPanel in Google Apps Scripts?

Is there a way to access the widgets within a panel in GAS?
Something like:
function clickHandler_(e) {
var app = UiApp.getActiveApplication();
var panel = app.getElementById(e.parameter.whatever); // short-cutting here
for (var i=0; i<panel.widgets.length; i++) {
var widget = panel.widgets[i];
// do something with them
}
return app;
}
There isn't something simple like this.
You have to give all widgets an id when adding them and save them somewhere so you can retrieve it later. For example, as a tag on the panel:
function doGet(e) {
var app = UiApp.createApplication();
...
var panel = app.createXYZPanel().setId('mypanel');
var IDs = ''; //list of child widgets on this panel
panel.add(widget.setId('id1'));
IDs += ',id1';
panel.add(widget2.setId('id2'));
IDs += ',id2';
//and so on...
panel.setTag(IDs); //save the IDs list as a tag on the panel
...
return app;
}
//...later on a handler
function handler(e) {
var app = UiApp.getActiveApplication();
//the panel or a parent must be added as callback of the handler
var IDs = e.parameter.mypanel_tag.split(',');
for( var i = 1; i < IDs.length; ++i ) { //skipping the 1st empty element
var widget = app.getElementById(IDs[i]);
//do something
}
return app;
}
By the way, you probably know that, but your code had a mistake:
It's not UiApp.getElementById, but app.getElementById.