Google Apps Script Persistence - google-apps-script

Background: I have a google site and I have been pulling information from a google spreadsheet containing the marks of my students, however I'd like to make it more dynamic so that they can request a report of all of their current marks whenever they'd like. In the script that I've written, students will enter a password, click a button and then their marks will be generated.
Issue: From what I've read, when they click the button, the handler for the button causes the script to be re-run. The current spreadsheet cannot be stored and when I try to access the spreadsheet, it tells me that it is null. How can I get access to the spreadsheet again? I've tried using ScriptProperties, but I got the same result. By the way, it works if I do not try to run it as a webapp.
Here's the doGet() function and part of the getPassword() function that is called once the button on the UI is pressed.
function doGet() {
var app = UiApp.createApplication();
app.add(app.loadComponent("MyGui"));
var panel = app.getElementById("VerticalPanel1");
var text = app.createPasswordTextBox().setName("text");
var handler = app.createServerHandler("getResults").addCallbackElement(text);
panel.add(text);
panel.add(app.createButton("Get Record", handler));
SpreadsheetApp.getActiveSpreadsheet().show(app);
}
function getResults(eventInfo) {
var app = UiApp.createApplication();
var password = eventInfo.parameter.text;
var panel = app.getElementById("VerticalPanel1");
var textArea = app.createRichTextArea();
panel.add(textArea);
var pointsSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var passwordCheckRange = pointsSheet.getRange("B70:C94").getValues();
...

The problem probably is that when the script is run as a webapp theres no "activeSpreadSheet"
so
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
fails. An alternate aproach will be to pass the SpreadSheet id in hidden field to the call back.
In your doGet function:
var hidden = app.createHidden('ssId', 'YOUR_SS_ID_HERE');
panel.add(hidden);
var handler = app.createServerHandler("getResults").addCallbackElement(text);
handler.addCallbackElement(hidden)`
In your callback function
var ssID = eventInfo.parameter.ssId;
var pointsSheet = SpreadsheetApp.openById(ssID).getSheetByName('SHEET_NAME');

Related

Apps Script Add-on Card Service Refresh Issue

I created add-ons with the card service.
All I wanted is to run the function refresh() to clear data in Spreadsheet and add-on should refresh once I press Logout Button.
I use the below code to try running the function refresh() and my Add-ons should reload to display some details. Redirect URL (cardFooter1Button1OpenLink1) is working fine, but it's not calling the function refresh
function refresh(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var clear = sheet.getSheetByName('OfficeUsers');
var range = clear.getRange('D3:I').clearContent()
}
function newdemo(){
var cardFooter1Button1Action1 = CardService.newAction()
.setFunctionName('refresh');
var cardFooter1Button1OpenLink1 = CardService.newOpenLink()
.setUrl('www.office.com/logout')
.setOnClose(CardService.OnClose.RELOAD_ADD_ON);
var cardFooter1Button1 = CardService.newTextButton()
.setText('Logout')
.setOnClickAction(cardFooter1Button1Action1)
.setOpenLink(cardFooter1Button1OpenLink1);
var cardFooter1 = CardService.newFixedFooter()
.setPrimaryButton(cardFooter1Button1);
var cardSection1 = CardService.newCardSection();
var card = CardService.newCardBuilder()
.setFixedFooter(cardFooter1)
.addSection(cardSection1)
.build();
return card;
}
Please help me, When I press Logout, It's redirecting to URL and when I close the pop up, it's refreshing the add-on also as I expected but function refresh() is not calling.

Creating a view history log every time someone accesses a google doc and/or copies it using apps script

I wrote a script to log who and when opens a google doc. It works fine but the doc is a template for users to fill in so want to ensure whoever needs it will make a copy of the template through apps script.
I know users need edit access for the script to work but in terms of workarounds:
Is there a way for the script to still work if I give them a copy link or a template link?
Does it make more sense to use the google sheet as the base and pull from the google doc ID?
If that doesn't work than:
2) Is there a way to prompt them with a menu window to copy the file but delay it by x seconds on open?
//setups a count for the file
function setup() {
var propertyService = PropertiesService.getDocumentProperties();
propertyService.setProperty('viewCount', '0');
}
//logs the email and date of the user accessing the file
function onOpen(e) {
var count = parseInt(PropertiesService.getDocumentProperties().getProperty('viewCount'))+1;
PropertiesService.getDocumentProperties().setProperty('viewCount', count);
Logger.log(count);
var sheet = SpreadsheetApp.openById([spreadsheet ID]);
var user = Session.getActiveUser().getEmail();
var date = new Date();
sheet.getSheetByName('View Count').appendRow([user,date]);
}
You can add a menu item that creates a copy of the template for the user, logs the copy to the same sheet where you track who opens the file, and prompts the user to open their copy of the file to start working.
Steps
First: add these 3 functions.
The first, copyFile, creates a copy of the active document and names it the same plus the user's email. Then it logs the activity to the same change log, adding the new file's ID to the row. Finally, thanks to this nifty function it pops up a window with a link to the new file so the user can easily open it and start working.
The second, showAnchor, is the function that generates the HTML dialog box which presents the user a link to their new file.
The third, createMenu, adds a new menu item to the Google Docs nav bar, which prompts the user to copy the document. This, of course, triggers the copyFile function.
function copyFile() {
var thisDoc = DocumentApp.getActiveDocument()
var sheet = SpreadsheetApp.openById([spreadsheet ID]);
var user = Session.getActiveUser().getEmail();
var date = new Date();
var newCopy = DriveApp.getFileById(thisDoc.getId()).makeCopy(thisDoc.getName() + " " + user)
sheet.getSheetByName('View Count').appendRow([user,date,newCopy.getId()]);
var url = newCopy.getUrl()
showAnchor('Open Your File',url);
}
function showAnchor(name,url) {
var html = '<html><body>'+name+'</body></html>';
var ui = HtmlService.createHtmlOutput(html)
DocumentApp.getUi().showModelessDialog(ui," ");
}
function createMenu() {
const ui = DocumentApp.getUi();
const menu = ui.createMenu("Copy This Template");
menu.addItem("Copy", "copyFile");
menu.addToUi();
}
Then: add a call to createMenu() at the end of your onOpen script:
function onOpen(e) {
var count = parseInt(PropertiesService.getDocumentProperties().getProperty('viewCount'))+1;
PropertiesService.getDocumentProperties().setProperty('viewCount', count);
Logger.log(count);
var sheet = SpreadsheetApp.openById([spreadsheet ID]);
var user = Session.getActiveUser().getEmail();
var date = new Date();
sheet.getSheetByName('View Count').appendRow([user,date]);
createMenu();
}

Google Script Pass Variable To Click Handler

I have created a Google Apps script and I have a question about passing a variable to the click handler. The script is published as a Web App and is accessed by an HTML link in an email (its an approve/deny form for a business request). When someone submits a form, an email is sent to the approver who when clicks on the approve link. This link contains the following variables:
?status=enteredInCC&rowNum=9
So when they click on the link the script is launched, and it enters the function doGet(e) block and where relevant code is:
var app = UiApp.createApplication();
var grid = app.createGrid(1, 2);
var label = app.createLabel('Please enter the item\'s PLU:')
var input = app.createTextBox().setId("input").setName("input");
grid.setWidget(0, 0, label);
grid.setWidget(0, 1, input);
var handler = app.createServerHandler("retrieveInput").addCallbackElement(input);
var button = app.createButton("OK", handler);
var panel = app.createVerticalPanel().setId("panel")
panel.add(grid);
panel.add(button);
app.add(panel);
return app;
Which leads to the click handler:
function retrieveInput(e){
var input = e.parameter.input;
var app = UiApp.getActiveApplication();
var panel = app.getElementById('panel');
panel.clear();
app.add(app.createHTML('<h2>The request has been updated. You can close this window.</h2>'))
app.close()
return app;
}
And all of that runs great. However, earlier in the doGet(e) function some information was collected from the URL and stored in variables. Now in retrieveInput(e) I'm needing to access those variables to make some updates to the Spreadsheet (just after panel.clear()). Any ideas?
Storing in variables will not work because the retrieveInput function is called in a new/different "instance" of your script, therefore not being able to access variables set in the doGet. In your case, I suggest you save the desired parameters as hidden inputs in the panel and use it as callbackElement instead of only the input. Like this:
function doGet(e) {
var app = UiApp.createApplication();
var panel = app.createVerticalPanel().setId("panel")
panel.add(app.createHidden('status', e.parameter.status));
panel.add(app.createHidden('rowNum', e.parameter.rowNum));
var grid = app.createGrid(1, 2);
var label = app.createLabel('Please enter the item\'s PLU:')
var input = app.createTextBox().setId("input").setName("input");
grid.setWidget(0, 0, label);
grid.setWidget(0, 1, input);
var handler = app.createServerHandler("retrieveInput")
.addCallbackElement(panel); //changed to panel instead of input
var button = app.createButton("OK", handler);
panel.add(grid).add(button);
return app.add(panel);
}
Then you can use e.parameter.status and others normally in retrieveInput(e).

doPost Twice in Google Apps Script?

I have a web-deployed form written in Google Apps Script with doGet and doPost. In the doPost, the code checks if the user has filled in the form correctly (e.g. not leaving certain things blank). If not, it highlights the things that need to be fixed and adds a warning label. If everything is all right, it writes the form data to a spreadsheet.
The problem is that it doesn't seem like doPost can be called again if the user fixes the problems.
Any thoughts? Thanks!
EDIT: I am using UiService
EDIT: Here is a very simplified version of the app:
function doGet(e) {
var app = UiApp.createApplication();
var mainForm = app.createFormPanel().setId('mainForm');
var formContent = app.createVerticalPanel().setId('formContent');
var userName = app.createTextBox().setId('userName').setName('userName');
var passport = app.createFileUpload().setName('passport');
var submitButton = app.createSubmitButton('submit here');
var submitButtonWarning = app.createLabel('Something is wrong.').setId('submitButtonWarning')
.setVisible(false);
formContent
.add(userName)
.add(passport)
.add(submitButton)
.add(submitButtonWarning);
mainForm.add(formContent);
app.add(mainForm);
return app;
}
function doPost(e) {
var app = UiApp.getActiveApplication();
var userName = e.parameter.userName;
var passport = e.parameter.passport;
if (userName == 'no') {
app.getElementById('submitButtonWarning').setVisible(true);
app.add(app.getElementById('formContent'));
return app;
} else {
app.getElementById('submitButtonWarning').setVisible(false);
app.add(app.getElementById('formContent'));
return app;
}
return app;
}
I think you are just adding the wrong UI element to the app in your doPost.
Instead of
app.add(app.getElementById('formContent'));
use
app.add(app.getElementById('mainForm'));

Listbox not opening handler

I am trying to create a menu in a listbox format where user chooses an option and then another uiapp is shown with the info they selected. I am having an issue here that when I opened google gives me an error that says Error encountered. An expected error occurred. I think it has to do with the setId part, if I remove one of the setId's the error doesnt happen. is this even possible?
function doGet(e) {
var app = UiApp.createApplication().setTitle("Services");
var dropDownList = app.createListBox().setName('list').setId('list');
var infoLabel = app.createLabel('Scroll around to select the service desired').setId('infoLabel');
var panel = app.createVerticalPanel();
//addItem fills the list
dropDownList.addItem("Option 1").setId("add");
dropDownList.addItem("Option 2");
panel.add(dropDownList);
panel.add(infoLabel);
app.add(panel);
var info = app.getElementById("add");
var handler2 = app.createServerHandler('display2');
info.addClickHandler(handler2);
app.add(dropDownList);
app.add(infoLabel);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function display2(e) {
var app = UiApp.createApplication();
var html = app.add(app.createHTML("<p><p><b>You have selected this option</b> </p>")).setHeight(220).setWidth(220);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
return app;
}
Are you deploying this as a web app or a script within a Spreadsheet?
If you are deploying this as a web app, then SpreadsheetApp.getActiveSpreadsheet() will not work - replace it with SpreadsheetApp.openById(id) where id is your spreadsheet ID which you will find in the URL when you open the file in the browser.
If you want to deploy this w/in a spreadsheet through a menu item or a simple button, then that works as is.
I was able to just copy paste your code and get the listbox part working fine -
https://docs.google.com/spreadsheet/ccc?key=0AkJNj_IM2wiPdHRYQThlaGVVSk04R052ZGNqclhEZWc#gid=0
Update -
I now understand what you are trying to do. Couple of things - you want to make sure you are adding a callback element via handler.addCallbackElement(myWidget) otherwise, you will not be able to read the value of the element. Second thing is that you don't need a server handler on each option in a dropdown list. Just having one handler will fire it for every change and you'll be able to get the option you selected.
I've cleaned up the code here below and also updated the spreadsheet to use this code.
function showUI() {
var app = UiApp.createApplication().setTitle("GeekSquad Services");
var infoLabel = app.createLabel('Scroll around to select the service desired');
var dropDownList = app.createListBox().setName('list').setId('list');
dropDownList.addItem("Option 1");
dropDownList.addItem("Option 2");
//you can add as many options here manually or dynamically
var handler = app.createServerHandler('dropDownCallback')
handler.addCallbackElement(dropDownList);
dropDownList.addClickHandler(handler);
var panel = app.createVerticalPanel();
panel.add(dropDownList);
panel.add(infoLabel);
app.add(panel);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function dropDownCallback(e) {
var app = UiApp.createApplication();
var html = app.add(app.createHTML("<b>You have selected this option</b> " + e.parameter.list));
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
Update #2 -
If you want to fork off and create different app instances thats easy (though its unclear why wouldn't just change panels).
function dropDownCallback(e) {
if(e.parameter.list === 'Option 1'){
var app = UiApp.createApplication();
var html = app.add(app.createHTML("Here for option!"));
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
else if (e.parameter.list ==== 'Option 2'){
//create and show other App here or whatever else
}
//refactor this better to not repeat code.
}