Can one create an Interactive web app with GAS? - google-apps-script

Broken down to its most elemental, what I want to do from a web app context is:
Ask for some data from user.
Display some data to user.
Ask for some more data from user.
Use all that data in a web app.
More specifically, I am trying to build a Google Script web app that does the following:
Presents an html page where the user can input a user ID number.
Takes that number and finds the last line on a spreadsheet belonging
to that user.
Displays that last line number to the user (this is
the first point at which I am stumped—see below for what I have
tried).
Presents a 2nd html input page where the user can either
accept the last line info displayed to them, or enter an alternate
number (and some other info).
All of that info is then used to
create a Google Doc and add info about that Google Doc on a new row
in a Google spreadsheet.
I have tried:
(a) Class PromptResponse [ui.prompt]
(b) alert(Prompt)
(c) showModalDialog
(d) show ModelessDialog
All of these failed as they apparently must be triggered from a bound app.
I considered the concept of having two doGet statements in a single webApp which led me to
Linking to another HTML page in Google Apps Script, but that seems to deal with a two-page SINGLE html rather than two separate html pages (which is what I think I need).
I also considered using the Browser.msgBox in the Class CacheService but that produced the same context error as (a) thru (d) above.
Lastly, I thought about—rather than displaying the user ID number from (1) above—saving the variable and inserting it later in the script (i.e., loading it in (4) above). That led me to the CacheService. But I could not see how to make that work and in any event, it’s not really what I want to do.
GS
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
function getSongId(objArgs){
// Get User's Catalog SS URL from Master Users List
var userId = objArgs.user;
var masterSSId = "ID";//This is the ID to the master users list SS.
var userSS = SpreadsheetApp.openById(masterSSId);//Open
var userSheet = userSS.getActiveSheet();
var nameOfUserRange = "User" + userId; //this constructs the user ID, like "user101"
Logger.log("nameOfUserRange = " + nameOfUserRange);
var userNamedRange = userSS.getRangeByName(nameOfUserRange); //this returns "Range" to pass its value on to future code lines
var cell = userNamedRange.activate(); //activates range and first cell in range
var namedUrlRange = userSS.getRange('SongsSheetUrl'); //this gets the SongSheetUrl named range
var userCol = namedUrlRange.getColumn(); //this gets col # of namedUrlRange
Logger.log("userCol = " + userCol);
var userSsUrl = cell.offset(0, userCol-1, 1, 1). getValue(); //this gets the user's Catalog SS URL
Logger.log("userSsUrl = " + userSsUrl);
var ss = SpreadsheetApp.openByUrl(userSsUrl);
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var songId = lastRow+1;
Logger.log("songId = " + songId);
//some code here that displays songID to user
HTML "Index"
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<center> Enter your User ID below.
<input id="userId" type="text" placeholder="User ID"><br><br>
<button onclick="saveUserInput()">Continue</button>
</center>
<script>
window.saveUserInput = function() {
var user = document.getElementById('userId').value;
console.log('userId: ' + userId)
google.script.run
.withSuccessHandler(openPrompt)
.getSongId({user:user})
}
function openPrompt(results){
window.open(results.url, '_blank').focus();
}
</script>
</body>
</html>
songId Code
function getSongId() {
var masterSSId = "ID";//This is the ID to the master users list SS.
var userSS = SpreadsheetApp.openById(masterSSId);//Open
var userSheet = userSS.getActiveSheet();
var nameOfUserRange = "User" + userId; //this constructs the user ID, like "user101"
var userNamedRange = userSS.getRangeByName(nameOfUserRange); //this returns "Range" to pass its value on to future code lines
var cell = userNamedRange.activate(); //activates range and first cell in range
var namedUrlRange = userSS.getRange('SongsSheetUrl'); //this gets the SongSheetUrl named range
var userCol = namedUrlRange.getColumn(); //this gets col # of namedUrlRange
var userSsUrl = cell.offset(0, userCol-1, 1, 1). getValue(); //this gets the user's Catalog SS URL
var ss = SpreadsheetApp.openByUrl(userSsUrl);
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var songId = lastRow+1;
}
As noted, I got "context" errors with everything I tried. BTW, I also created a web app that had 2 GS pages and 2 Index pages, and that just displayed both html pages on one page, and I still couldn't figure out how to display the User ID.
Finally, I spent a lot of hours, and used a lot of search terms, both at SO and the web in general trying to find someone else that has tackled this problem—and came up goose eggs.
Note: To respect "minimal, and verifiable," I have not included the script that asks for the 2nd set of info, but it is written and works.
Update: The following SO Question/Answer showed up to the right of this question: "Web apps+ remotely using script" after I posted it
It seems to in part solve my problem. At least it does display the user's User ID input, but I need it to display info I pull from a Google sheet based on the User ID (i.e., the songId). Using the doGet(e) approach, I still don't know where to put the getSongIdcode that gets the songId. I have added that code above.
Revised Code
gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function getSongId(uObj) {
var userId = uObj.user;
var masterSSId = "ID";//This is the ID to the master users list SS.
var userSS = SpreadsheetApp.openById(masterSSId);//Open
var userSheet = userSS.getActiveSheet();
var nameOfUserRange = "User" + userId; //this constructs the user ID, like "user101"
Logger.log("nameOfUserRange = " + nameOfUserRange);
var userNamedRange = userSS.getRangeByName(nameOfUserRange); //this returns "Range" to pass its value on to future code lines
var cell = userNamedRange.activate(); //activates range and first cell in range
var namedUrlRange = userSS.getRange('SongsSheetUrl'); //this gets the SongSheetUrl named range
var userCol = namedUrlRange.getColumn(); //this gets col # of namedUrlRange
var userSsUrl = cell.offset(0, userCol-1, 1, 1). getValue(); //this gets the user's Catalog SS URL
var ss = SpreadsheetApp.openByUrl(userSsUrl);
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var songId = lastRow+1;
Logger.log("songId = " + songId);
return songId;
}
html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<center>
Enter your User ID below.
<input id="userId" type="text" placeholder="User ID"><br>
<input type="button" value="Continue" onclick="saveUserInput()">
<div id="results"></div>
</center>
<script>
function saveUserInput() {
var user = document.getElementById('userId').value;
google.script.run
.withSuccessHandler(function(hl){
document.getElementById('results').innerHTML=hl;
})
.getSongId({user:user})
}
</script>
</body>
</html>

Try something simple first. Just to see that you can get the client and the server communicating:
html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Enter your User ID below.
<input id="userId" type="text" placeholder="User ID"><br>
<input type="button" value="Continue" onclick="saveUserInput()">
<div id="results"></div>
<script>
function saveUserInput() {
var user = document.getElementById('userId').value;
google.script.run
.withSuccessHandler(function(hl){
document.getElementById('results').innerHTML=hl;
})
.getSongId({user:user})
}
</script>
</body>
</html>
Then use a simple getSongId() function:
function getSongId(uObj) {
return uObj.user
}
I would use this sort of doGet()
function doGet() {return HtmlService.createHtmlOutputFromFile('Index');
}
You html doesn't have any scriptlets that need to be evaluated. It's not really a template.
Then test the getSongId by itself and once that works you can return it to the div and later if you wish you can create another page.

Related

Script to autofill google doc from google form using checkboxes

I have the following issue. I am trying to create a script that will autofill a template google document using the submission of a google form. I am able to get the script to work for questions that are input with text but am struggling on getting the data from questions in the form that are checkboxes (or multiple choice) to work and fill the google document. Any assistance would be great. For example the variable identified as "offense" is from a question with checkboxes that has about 30 different options, I would like each option that is checked on the form to replace text within my google doc. Thanks.
function autoFillGoogleDocFromForm(e) {
//e.values is an array of form values
var timestamp = e.values[4];
var studentName = e.values[3];
var oe = e.values[16];
var gradelevel = e.values[14];
var program = e.values[15];
var offense = e.values[6];
var action = e.values[18];
var serve = e.values[31];
var makeUp = e.values[32];
var comments = e.values[29];
//file is the template file, and you get it by ID
var file = DriveApp.getFileById('1nPWC0IKc1zUJXYxbGahJsSW4uNWwhxnLM8shcD8kEE4');
//We can make a copy of the template, name it, and optionally tell it what folder to live in
//file.makeCopy will return a Google Drive file object
var folder = DriveApp.getFolderById('1FlpHRKqYrEHttA-3ozU3oUVJlgiqqa-F')
var copy = file.makeCopy(studentName + ', ' + timestamp, folder);
//Once we've got the new file created, we need to open it as a document by using its ID
var doc = DocumentApp.openById(copy.getId());
//Since everything we need to change is in the body, we need to get that
var body = doc.getBody();
//Then we call all of our replaceText methods
body.replaceText('<<Student Name>>', studentName);
body.replaceText('<<Incident Date>>', timestamp);
body.replaceText('<<Student Grade>>', gradelevel);
body.replaceText('<<Open enrolled?>>', oe);
body.replaceText('<<IEP/504?>>', program);
body.replaceText('<<Reason for Referral (Handbook)>>', offense);
body.replaceText('<<Administrative Action>>', action);
body.replaceText('<<Date(s) to be Served>>', serve);
body.replaceText('<<Make up Date(s)>>', makeUp);
body.replaceText('<<Comments>>', comments);
//Lastly we save and close the document to persist our changes
doc.saveAndClose();
}
You need to use the labels assigned to the checkboxes to determine if they have been checked. Same for multiple coice.
You can't use ListItems because you can't set the glyph to a check box so I simply insert text with a checkbox character.
I created a form
I then created an onFormSubmit(e) installed trigger in the spreadsheet to get the form response and put it in the Doc. Here I've simply used an active doc to perform my tests. You will need to adjust the script to handle your template doc.
function onFormSubmit() {
// test data
let e = {"authMode":"FULL","namedValues":{"Timestamp":["8/16/2022 14:40:26"],"Student Grade":["Junior"],"Reason for Referrel":["Bad grades, Disruptive in class, Other"],"Student Name":["Joe Smith"],"Open Enrollment":["Yes"]},"range":{"columnEnd":5,"columnStart":1,"rowEnd":2,"rowStart":2},"source":{},"triggerUid":"12151926","values":["8/16/2022 14:40:26","Joe Smith","Junior","Yes","Bad grades, Disruptive in class, Other"]};
try {
let doc = DocumentApp.getActiveDocument();
let body = doc.getBody();
let referrels = ["Bad grades","Unexcused absence","Disruptive in class","Fighting","Other"];
body.replaceText("<<Student Name>>",e.namedValues["Student Name"]);
body.replaceText("<<Student Grade>>",e.namedValues["Student Grade"]);
body.replaceText("<<Open Enrollment>>",e.namedValues["Open Enrollment"]);
// Notice the regex expression below because findText doesn't seem to handle parenthesis well
let text = body.findText("<<Reason for Referral.*>>");
body.replaceText("<<Reason for Referral.*>>","");
if( text ) {
let index = body.getChildIndex(text.getElement().getParent())+1;
referrels.forEach( item => {
let checked = e.namedValues["Reason for Referrel"][0].indexOf(item);
if( checked >= 0 ) {
let listItem = body.insertListItem(index,item);
index = body.getChildIndex(listItem)+1;
}
}
);
}
}
catch(err) {
Logger.log(err);
}
}

How can I create a web app where I can read text from a sheet based on dropdown selections?

I am trying to create a role description generator which reads pre-written text from a Google Sheet and assembles it in blocks in a web app through selections (team, role, seniority level, etc.) in dropdown menus.
This is an example of what the data in the sheet looks like:
Team name
Team description
A-team
Description
B-team
Description
...
...
So far, for the team selection, I have created the dropdown menu which reads the data from the sheet, and pulls the names of each team into a dropdown list. But my problem is loading the corresponding team description text into the HTML page. I just can't seem to get it to work.
When pressing the generate button, what should happen is that the description for A-team is loaded, but instead I get [object MouseEvent].
Any suggestions? Thanks in advance! :)
Here's my code:
Code.gs
var url = "*spreadsheet URL*";
function doGet(e) {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
//get the data for the dropdown list
function valuesForList(list) {
//define the data
var ss = SpreadsheetApp.openByUrl(url)
var teamsSheet = ss.getSheetByName('Data');
var lastRow = teamsSheet.getLastRow();
var teamsRange = teamsSheet.getRange(1, 3, lastRow, 1);
//create a named range
ss.setNamedRange('teamsList', teamsRange);
//get the values from the range
var listValues = ss.getRangeByName(list).getValues();
return listValues;
}
//the function to show the data on the index.html
function PostInfo (userInfo){
//load the data
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Teams");
var data = ws.getRange(2,1,ws2.getLastRow(),2).getValues();
var teamList = data.map(function(r){ return r[0]});
var teamDesc = data.map(function(r){ return r[1]});
var position = teamList.indexOf(userInfo.teams);
if(position > -1){
return teamDesc[position];
} else {
return "Unavailable";
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
<script>
function onListSuccess(list) {
var listLength = list.length;
for (i=0; i<listLength;i++) {
var dropdown = document.getElementById("teams");
var opt = document.createElement("option");
dropdown.options.add(opt);
opt.text = list[i][0];
opt.value = list[i][0];
}
}
function onListSelect(teamDesc){
var text = teamDesc.toString().split(",");
document.getElementById('est').innerHTML = text;
}
</script>
</head>
<body>
<div id="main">
<h1>Role Description Generator</h1>
<p>
<label for="teams">Team:</label>
</p>
<p>
<select name="teams" id="teams" tabindex="2"></select>
</p>
<button id="btn">Generate</button>
<div>
<label for="est">Team description:</label>
<p id="est" name="est"></p>
</div>
</div>
</body>
<script>
function populateList(){
google.script.run.withSuccessHandler(onListSuccess).valuesForList('teamsList');
}
document.getElementById("teams").addEventListener("change", doStuff);
document.getElementById("btn").addEventListener("click", onListSelect);
function doStuff(){
var userInfo = {};
userInfo.teams = document.getElementById("teams").value;
google.script.run.PostInfo(userInfo);
}
window.addEventListener('load', populateList);
</script>
</html>
Modification points:
In your script, when the dropdown list is changed, doStuff() is run. But in this case, google.script.run.PostInfo(userInfo) runs only the function of PostInfo at Google Apps Script. By this, the returned value is not used.
And, when the button is clicked, onListSelect is run. But in this case, teamDesc of onListSelect(teamDesc) is the event object. By this, such value of [object MouseEvent] is shown. I thought that this might be the reason of your issue.
By the way, when I saw your Google Apps Script, I noticed that PostInfo has a modification point. When var data = ws.getRange(2,1,ws2.getLastRow(),2).getValues(); is run, I think that an error occurs. Because ws2 is not declared. In your case, is that ws? I thought that this might be due to your miscopy.
When you want to show the value from PostInfo when the button is clicked, how about the following modification?
Modified script:
HTML&Javascript side:
From:
document.getElementById("teams").addEventListener("change", doStuff);
document.getElementById("btn").addEventListener("click", onListSelect);
function doStuff(){
var userInfo = {};
userInfo.teams = document.getElementById("teams").value;
google.script.run.PostInfo(userInfo);
}
To:
document.getElementById("btn").addEventListener("click", doStuff);
function doStuff(){
var userInfo = {};
userInfo.teams = document.getElementById("teams").value;
google.script.run.withSuccessHandler(onListSelect).PostInfo(userInfo);
}
Google Apps Script side:
From:
var ws = ss.getSheetByName("Teams");
var data = ws.getRange(2,1,ws2.getLastRow(),2).getValues();
To:
var ws = ss.getSheetByName("Teams");
var data = ws.getRange(2,1,ws.getLastRow(),2).getValues();
Note:
In this modidication, it supposes that the Google Apps Script works fine and returns the correct values. Please be careful this.
Reference:
Class google.script.run

How can I remove or programmatically disable/block an attached script on a copied Google Doc?

I am looking to simulate the behavior of Google Classroom's assignment feature that allows you to make a copy of a document for a list of students. This copy retains ownership by the creator and adds the student as an editor.
I have succeed in making this work with a script attached to a template doc. The teachers can paste a list of email addresses into a custom pull-out, and on submission a loop creates each doc with the permissions.
Attempts:
I can use copy() functionality to copy the doc, but the attached script goes along for the ride and is then accessible by the students. This is not a major security risk, but has the potential to be abused.
I can have moderate success by using the regularly mentioned method of looping through all elements and appending them to the new doc, but so far I have not been able to make everything work in this way. Some images, tables, and other elements that teachers might create do not format properly in the new doc.
Hopeful solutions:
Is there a way to remove the script from the copied doc? or
Is there a way to use permissions to only allow the script to be run by faculty? (We do have an Org Unit for faculty, but my tests with the AdminDirectory module leave me concerned about permissions once all faculty is using the tool.) or
Knowing that our student email addresses are formatted differently from our faculty email addresses, could I programmatically block the script based on email address parsing of the current user?
I've gone in circles and keep ending up at the posts explaining how to copy elements one at a time into a new doc. This does not appear to be sufficient due to formatting so I'm hoping one of the other solutions involving keeping the copy() function is possible.
Sidebar Code sidebar.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
</script>
<style>
textarea{
font-size: .9em;
width: 90%;
height: 300px;
}
</style>
</head>
<body>
<textarea id="addressList">
Paste class email list here with spaces between addresses.
</textarea>
<p>
<input id="submit" type="button" value="Distribute to Students" onclick="distributor();" />
<input id="reset" type="button" value="Reset" onclick="reset();" />
</p>
<script>
function reset(){
document.getElementById('addressList').value = "Paste class email list here with spaces between addresses.";
document.getElementById("submit").disabled = false;
}
var mainOut = "";
function cleanUp(output){
mainOut += output;
document.getElementById('addressList').value = mainOut;
}
function distributor(){
document.getElementById("submit").disabled = true;
var nameList = document.getElementById('addressList').value; // get email list
var list = nameList.split("\n"); // break it up
// loop through names
for(let i = 0; i < list.length; i++){
// create a doc for each student and return success to the UI
google.script.run.withSuccessHandler(cleanUp).distro(list[i],i+1,list.length);
}
}
</script>
</body>
</html>
Current Script Code.gs
// student email addresses end in a 2 digit class year, faculty does not
var event;
var emailParts = Session.getEffectiveUser().getEmail().split('#');
var username = emailParts[0];
var classCheck = username.substring(username.length-2);
var validUser = false;
// using school email address style, determine if student or teacher to hide the UI changes
if(isNaN(classCheck)){
validUser = true;
}
// Custom Menu
function onOpen(e){
if(validUser){
event = e;
var ui = DocumentApp.getUi();
var faMenu = ui.createMenu('FA')
.addItem('Open Distribution Tool', 'openTool').addToUi();
}
}
/**
* Creates Custom Sidebar for emailing teams from spreadsheet
*/
function openTool() {
if(validUser){
var html = HtmlService.createHtmlOutputFromFile('sidebar');
html.setTitle('Share with Students');
html.setWidth(400);
html.setContent(html.getContent());
DocumentApp.getUi().showSidebar(html);
}
}
// create copy, set permissions
function distro(studentEmail,count,total){
var output = "";
if(validUser){
var teacherEmail = 'xxx#xxx.org'; // this will be replaced by email of logged in user
var thisDoc = DocumentApp.getActiveDocument();
let studentName = studentEmail.split('#')[0];
if(studentEmail != "" && studentEmail != null){
let filename = thisDoc.getName() + "- " + studentName;
let newDoc = DriveApp.getFileById(thisDoc.getId()).makeCopy(filename);
newDoc.setOwner(studentEmail);
newDoc.addEditor(teacherEmail);
output += "Created doc " + count + "/" + total + ": " + filename + "\n";
}
}
return output;
}
Issue:
I don't think you can programmatically remove the bound script from a copied document.
In theory, this is possible if you use Apps Script API, by calling projects.updateContent and set and empty content for your Files.
Nevertheless, this requires knowing the scriptId, and you cannot programmatically retrieve the scriptId of a bound script which is not the current one (for the current one, Session.getScriptId() can be used). See this answer, for example, and this related feature request:
Retrieving Project ID of Container-Bound script
Workaround - use libraries:
As a workaround, I'd suggest putting at least some of the script code in a different, standalone script, and make your template call this library. This way, the library source code would not be available to the script bound to the copied file, which could only run the different functions defined in the library.
For example, you could move onOpen and distro to another script:
Library Code.gs:
// student email addresses end in a 2 digit class year, faculty does not
var event;
var emailParts = Session.getEffectiveUser().getEmail().split('#');
var username = emailParts[0];
var classCheck = username.substring(username.length-2);
var validUser = false;
// using school email address style, determine if student or teacher to hide the UI changes
if(isNaN(classCheck)){
validUser = true;
}
// Custom Menu
function onOpen(e){
if(validUser){
event = e;
var ui = DocumentApp.getUi();
var faMenu = ui.createMenu('FA')
.addItem('Open Distribution Tool', 'openTool').addToUi();
}
}
// create copy, set permissions
function distro(studentEmail,count,total){
var output = "";
if(validUser){
var teacherEmail = 'xxx#xxx.org'; // this will be replaced by email of logged in user
var thisDoc = DocumentApp.getActiveDocument();
let studentName = studentEmail.split('#')[0];
if(studentEmail != "" && studentEmail != null){
let filename = thisDoc.getName() + "- " + studentName;
let newDoc = DriveApp.getFileById(thisDoc.getId()).makeCopy(filename);
newDoc.setOwner(studentEmail);
newDoc.addEditor(teacherEmail);
output += "Created doc " + count + "/" + total + ": " + filename + "\n";
}
}
return output;
}
Then, share this script as a library, and use it in your template script, which could be like this (where LIBRARY is the identifier for your previously shared library):
Template Code.gs:
// student email addresses end in a 2 digit class year, faculty does not
var event;
var emailParts = Session.getEffectiveUser().getEmail().split('#');
var username = emailParts[0];
var classCheck = username.substring(username.length-2);
var validUser = false;
// using school email address style, determine if student or teacher to hide the UI changes
if(isNaN(classCheck)){
validUser = true;
}
// Custom Menu
function onOpen(e){
LIBRARY.onOpen(e);
}
/**
* Creates Custom Sidebar for emailing teams from spreadsheet
*/
function openTool() {
if(validUser){
var html = HtmlService.createHtmlOutputFromFile('sidebar');
html.setTitle('Share with Students');
html.setWidth(400);
html.setContent(html.getContent());
DocumentApp.getUi().showSidebar(html);
}
}
// create copy, set permissions
function distro(studentEmail,count,total){
LIBRARY.distro(studentEmail, count, total);
}
In this example, sidebar.html would also be contained in your template script.
Note:
This is a basic example just to show how this could be done, and could probably be improved. For example, it should be possible to also move openTool and the .html file to the library code, even though calling distro via google.script.run could become tricky: see Call Library function from html with google.script.run.
Reference:
Apps Script: Libraries

Populate Google Form using Google App Script on Form open

I have a google form to where I want to populate with some data from a spreadsheet when user is opening the form.
I have a ListItem which I populate it with spreadsheet data. Until now I used the Open(e) function and a trigger, but I just found out that this method is triggered only on form edit not on form open.
Do you have an idea how can I do that?
To have an idea on what I want, I have two files, Code.gs contains the main functions like onOpen and onFormSubmit, and ItemClass where I get my data and create the UI.
I set a console log to Open(e) function, but never triggers.
Code.gs
function onOpen(e) {
console.log({message: 'onOpen', initialData: e});
let items = getItems();
let form = FormApp.openById(PARAMS.formID);
form.setTitle('New Form')
createUI(form, items);
}
ItemsClass.gs
function getItems() {
var email = Session.getActiveUser().getEmail();
var allItems = SpreadsheetApp.openByUrl(PARAMS.sheetURL).getSheetByName("Items Stream").getDataRange().getDisplayValues();
var headers = allItems.shift();
var items = new Array;
for (var i = 0; i < allItems.length; i++) {
var first = allItems[i][1]
var second = allItems[i][2]
items.push(first + "&" + second)
}
return items;
}
The on Open trigger Google Forms works only when opening the form editor, not the actual form that the user fills out
To return to the user the updated data whenever he opens the form and allow him to modify the data, you should create a custom HTML form with Web polling.
Web Polling with setInterval allows to pull fresh data from the spreadsheet and update it in specified intervals
Apps Script WebApps allow you to combine Apps Script and HTML/Javascript which allows you easy interaction between serverside and UI - useful for creation of a custom HTML form
Use google.script.run to communicate between the two sides.
Simple sample pulling updated data from column A in a spreadsheet and allowing the user to modify the values:
code.gs:
var sheet = SpreadsheetApp.openById('XXX').getSheetByName("YYY");
function doGet(){
var html=HtmlService.createTemplateFromFile('index');
return html.evaluate();
}
function getValues() {
//get data from the first column
var data = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
var table = "";
for (var i = 0; i < data.length; i++) {
table +='<tr><td>' + data[i][0] + ' </td><tr>';
}
return table;
}
function writeToSheet(newValues) {
newValues = newValues.split(",");
var range = sheet.getRange(1, 1, newValues.length, 1);
newValues = newValues.map(function(row){return [row]});
range.setValues(newValues);
}
index.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
function onSuccess(values){
document.getElementById("data").innerHTML=values;
}
function polling(){
//modify the interval of 2000 ms to any desired value
setInterval( function(){google.script.run.withSuccessHandler(onSuccess).getValues()},2000);
}
function updateValues(){
var newValues= document.getElementById("newValues").value;
google.script.run.writeToSheet(newValues);
}
</script>
<body onload="polling()">
<div> Values: </div>
<table id="data">
</table>
<div> If you want to modify the values in the spreadsheet, type in new values comma separated: </div>
<input type="text" id="newValues" ><br><br>
<input type="button" value="Confirm" onclick="updateValues()">
</body>
</html>
Deploy this WebApp and described in the documentation and paste the WebApp URL into a browser address bar.

Put document contents in a textBox

I'm looking to get the contents of a Google word document and put it in a textBox. The following code is spitting out an error:
function listBoxClick(e) {
var tapp = UiApp.getActiveApplication();
var docName = DocsList.find(e.parameter.doclist); //find the document with the same name as what the user clicked on
var docContents = docName[0].getContentAsString(); //get contents of document
tapp.getElementById("songboxID").setValue(songfile2); //set the value of the textBox to the contents of the document
return tapp;
}
This returns the following error:
Unsupported conversion requested.
I read somewhere that we can't do this for Google Documents but we can for other non-google documents that we upload. Is that right?
Here's the answer that I can't post for 5 more hours since I'm new and have no reputation:
With Serge's assistance, here's what worked for me:
function listBoxClick(e) {
var tapp = UiApp.getActiveApplication();
var docName = DocsList.find(e.parameter.doclist); //get document name based on what user clicked on in listBox
var docId = docName[0].getId(); //get document ID
var content = DocumentApp.openById(docId).getText(); //get contents of document
tapp.getElementById("songboxID").setValue(content); //set web app's textBox (called songboxID) to match the contents of the document the user clicked on
return tapp;
}
You have to use DocumentApp.getText(); to get the text content of your document.
in you code it would become :
function listBoxClick(e) {
var tapp = UiApp.getActiveApplication();
Logger.log(e.parameter.doclist)
var docName = DocsList.find(e.parameter.doclist); //find the document with the same name as what the user clicked on
var docId = docName[0].getId();
Logger.log(docName[0].getName()+' = '+docId)
var textContent = DocumentApp.openById(docId).getText();
Logger.log(textContent)
tapp.getElementById("songboxID").setValue(textContent); //set the value of the textBox to the contents of the document
return tapp;
}
EDIT : as you noticed with your experiments DocsList.find(e.parameter.doclist) returns results that you didn't expect... that's simply because the find operator searches not only on doc's names but also on docs content.
To solve that , you should add a smal routine that checks the results of the find query against the documents name, something like this :
var docName = DocsList.find(e.parameter.doclist); //find the document with the same name as what the user clicked on
for(var d in docName){{
if(docName[d].getName()==e.parameter.doclist){
var docId = docName[d].getId();
}
}
This will ensure that the document you are looking for is actually the right one.
PS: sorry for not having mentioned that immediately... it just slipped out of my mind ;-)