I'm trying to create a relatively simple app (or at least it seemed that way two weeks ago) to catalog periodical intake in a library. I need a form with two boxes: text input for periodical titles and date input for publication date. I then need this form to submit to a Google Spreadsheet. I could just use a generic form, but I need it to autocomplete the periodical title as the information is being entered.
I'm using Google Apps, Spreadsheets, and Sites because they're free and do most of what I want. Here's the sticking point. I can create an HTMLService that autocompletes perfectly from a Google sheet. I basically followed this post exactly.
I can create a script that is a UiApp for submitting to a sheet. I just can't do both with one project. The code follows:
function doGet() {
var doc = SpreadsheetApp.openById(SPREADSHEET_ID);
var app = UiApp.createApplication().setTitle('Periodical Intake');
// Create a grid with 3 text boxes and corresponding labels
var grid = app.createGrid(3, 2);
grid.setWidget(0, 0, app.createLabel('Title:'));
// Text entered in the text box is passed in to pTitle
// The setName method will make those widgets available by
// the given name to the server handlers later
grid.setWidget(0, 1, app.createTextBox().setName('pTitle').setId('pTitle'));
grid.setWidget(1, 0, app.createLabel('Date:'));
grid.setWidget(1, 1, app.createDateBox().setName('date').setId('date'));
// Text entered in the text box is passed in to city.
// Create a vertical panel..
var panel = app.createVerticalPanel();
// ...and add the grid to the panel
panel.add(grid);
// Here's where this script diverges from the previous script.
// We create a horizontal panel called buttonPanel to hold two buttons, one for submitting the contents of the form
// to the Spreadsheet, the other to close the form.
var buttonPanel = app.createHorizontalPanel();
// Two buttons get added to buttonPanel: button (for submits) and closeButton (for closing the form)
// For the submit button we create a server click handler submitHandler and pass submitHandler to the button as a click handler.
// the function submit gets called when the submit button is clicked.
var button = app.createButton('submit');
var submitHandler = app.createServerClickHandler('submit');
submitHandler.addCallbackElement(grid);
button.addClickHandler(submitHandler);
buttonPanel.add(button);
// For the close button, we create a server click handler closeHandler and pass closeHandler to the close button as a click handler.
// The function close is called when the close button is clicked.
var closeButton = app.createButton('close');
var closeHandler = app.createServerClickHandler('close');
closeButton.addClickHandler(closeHandler);
buttonPanel.add(closeButton);
// Create label called statusLabel and make it invisible; add buttonPanel and statusLabel to the main display panel.
var statusLabel = app.createLabel().setId('status').setVisible(false);
panel.add(statusLabel);
panel.add(buttonPanel);
app.add(panel);
return app;
}
// Close everything return when the close button is clicked
function close() {
var app = UiApp.getActiveApplication();
app.close();
// The following line is REQUIRED for the widget to actually close.
return app;
}
// function called when submit button is clicked
function submit(e) {
// Write the data in the text boxes back to the Spreadsheet
var doc = SpreadsheetApp.openById(SPREADSHEET_ID);
var lastRow = doc.getLastRow();
var cell = doc.getRange('a1').offset(lastRow, 0);
cell.offset(0, 1).setValue(e.parameter.pTitle);
cell.offset(1, 2).setValue(e.parameter.date);
// Clear the values from the text boxes so that new values can be entered
var app = UiApp.getActiveApplication();
app.getElementById('pTitle').setValue('');
app.getElementById('date').setValue('');
// Make the status line visible and tell the user the possible actions
app.getElementById('status').setVisible(true).setText('The periodical ' + e.parameter.pTitle + ' was entered.' +
'To add another, type in the information and click submit. To exit, click close.');
return app;
}
I could use my getAvailableTags function if it's possible to use jQuery in a UiApp (I don't think it is). The other suggestion is to use suggestBox but it seems that has been deprecated. I'm at my wit's end. It's been two weeks and I feel I haven't gotten anywhere. Can anyone please help a librarian out?
Here is an example of a form built with HTMLService that keeps data in a spreadsheet. I has also a file upload that you don't need but I thought it could interest someone else...
The autocomplete feature should be easy to integrate using you existing code borrowed from Mogsdad.
code.gs:
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function serverFunc(theForm) {
var fileBlob = theForm.theFile; // This is a Blob.
var name = theForm.name;
var number = theForm.number;
var adoc = DocsList.createFile(fileBlob);
var sh = SpreadsheetApp.openById('1dx0mr4Dy64jqmliTjFyHF7rZW4TZ_PPkWyA_H3WTRcA').getSheetByName('Sheet1');
sh.getRange(sh.getLastRow()+1,1,1,4).setValues([[adoc.getName(),adoc.getUrl(),name,number]]);
return adoc.getUrl();
}
index.html :
<style>
p {
margin:40px;
font-family:arial,sans-serif;
font-size:10pt;
color:#AAA
}
</style>
<p>
Test this file upload form :
<br><br>
<form>
Enter you name <input type="text" name="name" ><br>
Enter your number <input type="text" name="number"><br>
select your file <input type="file" name="theFile"><br>
<input type="button" value="UpLoad" id="button" onclick="google.script.run.withSuccessHandler(cliHandler).serverFunc(this.parentNode)">
</form>
</div><br>
<div id="url" >
</div><br>
<div id="urlText" >
</p>
<script>
function cliHandler(e){
document.getElementById('button').value = "Your file has been uploaded" ;
var divUrl = document.getElementById('url');
divUrl.innerHTML = 'Open the file';
document.getElementById('urlText').innerHTML = "url : "+e;
}
</script>
EDIT : Here is a version based on your code with a working form and autocomplete:
code.gs
function doGet() {
return HtmlService.createTemplateFromFile('Autocomplete').evaluate().setTitle('Periodical Intake');
}
function getAvailableTags() {
var ss = SpreadsheetApp.openById("0Agz7u97dbdECdGh6QVcyb2NVeFByTnlYeU5KcDdSRWc");
var s = ss.getSheetByName("Cataloging Key");
var data = s.getDataRange().getValues();
var headers = 0; // number of header rows to skip at top
var tagColumn = 0; // column # (0-based) containing tag
var availableTags = [];
for (var row=headers; row < data.length; row++) {
availableTags.push(data[row][tagColumn]);
}
return( availableTags );
}
function doPost(e) {
var title = e.title;
var date = e.date;
Logger.log(title+' '+date);
}
Autocomplete.html
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<style>
p {
margin:40px;
font-family:arial,sans-serif;
font-size:10pt;
color:#AAA
}
</style>
<p>
Test this file upload form :
<br><br>
<div>
<form class="ui-widget" >
<input id="title" type=text placeholder="Periodical Title:" name="title" /><br />
<input id="date" type=date placeholder="Publication Date: " name="date" /><br>
<input type="button" value="Submit"
onclick="google.script.run.doPost(this.parentNode)" />
</form>
</div><br>
<div id="url" >
</div><br>
<div id="urlText" >
</p>
<script>
// This code in this function runs when the page is loaded.
$(function() {
google.script.run.withSuccessHandler(buildTagList).getAvailableTags();
});
function buildTagList(availableTags) {
$( "#title" ).autocomplete({
source: availableTags
});
}
</script>
Related
I am working on a Google Sheets macro that displays some text to the user, then presents some buttons for the user to interact with. The buttons run another function and I am struggling with how to have the button display the text to the user.
I can't find the method or object I am supposed to use to grab the currently open window and edit the html to add more text. Or if that isn't possible, how can I close the open window and then display a new window that also has the old text?
function example(text,title,height=90,width=350) {
const dialogbutton = '<br><input type="button" value="Do Stuff" onClick="google.script.run.doStuff();" />'
var html=HtmlService.createHtmlOutput(text+dialogbutton).setHeight(height).setWidth(width)
SpreadsheetApp.getUi().showModelessDialog(html, title);
}
function doStuff() {
const dialogWindow = ???//I am hoping to retrieve the open window as an object
const text = getText() //run some other function and get the new text to insert
dialogWindow.displayedText += text //modify the displayed window to add the new text
}
Here is a very simple example of how to communicate with the server (i.e. Spreadsheet or Doc). In this case a spreadsheet with Sheet1!A1 = hello
Here is a simple dialog
Server side code Code.gs bound to a spreadsheet
function showTest() {
var html = HtmlService.createTemplateFromFile("HTML_Simple");
html = html.evaluate();
SpreadsheetApp.getUi().showModalDialog(html,"Test");
}
function doStuff() {
try {
// let get a value from spreadsheet
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet1");
return sheet.getRange("A1").getValue();
}
catch(err) {
Logger.log("Error in doStuff() "+err);
}
}
HTML Page HTML_Simple.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input type="button" value="Do Stuff" onClick="doStuffOnClick()">
<input type="text" id="whatStuff">
<script>
function doStuffOnClick() {
try {
google.script.run.withSuccessHandler(
function(response) {
document.getElementById("whatStuff").value = response;
}
).doStuff();
}
catch(err) {
alert(err);
}
}
</script>
</body>
</html>
Reference
HTML Service Best Practices
google.script.run()
i have created a google web app,
when i am typing a number in the orders textbox,
i'm getting a match result in the disabled amount textbox,
from the google sheet column,
the web app working good
now,what i want to do is to be able to create a textbox in app inventor
and when put an orders value and clicking a button
i will get the amount result on a label on the app invenotr interface
its not working on app inventor
here is the code i made in app inventor
[![enter image description here][1]][1]
this the Code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('page');
}
function getCost(oneCode){
var url = "https://script.google.com/macros/s/AKfycbyX";
va
return "not found";
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("one").addEventListener("change", doThis);
});
function doThis() {
var oneCode = document.getElementById("one").value;
google.script.run.withSuccessHandler(updateAmount).getCost(oneCode);
}
function updateAmount(cost) {
document.getElementById("two").value = cost;
}
</script>
<body>
<div>
<input id="one" type="text">
<label for="one">orders</label>
</div>
<div>
<input disabled id="two" type="text">
<label for="two">amount</label>
</div>
</body>
</html>
Try this:
function doThis() {
var oneCode = document.getElementById("one").value;
console.log(oneCode);
google.script.run.withSuccessHandler(updateAmount).getCost(oneCode);
}
and this:
function getCost(oneCode){
console.log('onecode');
var url = "https://script.google.com/macros/s/AKfycbyXX6axO9G9ANDW0LaQVgezT6hSzSld8CRt2VbsZV6v8XXwNus/exec";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Sheet1");
var data = ws.getRange(1,1,ws.getLastRow(),2).getValues();
var ordersList = data.map(function(r){ return r[0].toString(); });
var amountList = data.map(function(r){ return r[1].toString(); });
var position = ordersList.indexOf(oneCode);
if(position > -1){
return amountList[position];
}
return "not found";
}
And then run the code and goto View/Executions and see where you losing the data.
I have a send function as follows from here:
function send(){
var ui = SpreadsheetApp.getUi();
var bccSend = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('main_gen').getRange(2,2).getValue();
var bccSendReplace = bccSend.toString().replace(/,/g,"<br>");
const str = 'You are about to send this to the following email address(es): \n\n' + bccSendReplace + '\n\n Click OK to send, otherwise, close this box or click Cancel to abort.';
const html = `
<b>${str}</b><br>
<input type="button" value="ok" onClick="google.script.run.withSuccessHandler(() => google.script.host.close()).clickOk()">
<input type="button" value="cancel" onClick="google.script.run.withSuccessHandler(() => google.script.host.close()).clickCancel()">
`;
ui.showModalDialog(HtmlService.createHtmlOutput(html), 'sample');
}
I then have a function for when OK is clicked. If I just run this function, it runs as expected and there are no errors. However, When the OK button is clicked, it will not run this function.
function clickOK() {
var ui = SpreadsheetApp.getUi();
copyDataFromForm();
var sendDetails = getMeta(); Logger.log(sendDetails);
//var officeSelection = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('main_gen').getRange(2, 1).getValue();
var subject = gen.setSubjectLine(sendDetails); Logger.log(subject);
var BCC = '';
var bccSend = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('main_gen').getRange(2,2).getValue();
if(bccSend == ''){bccSend = sendDetails[6];}
var toSend = main(sendDetails);//CreateHTML();
var image = toSend[2];
var attachment = [toSend[4].getAs(MimeType.PDF)];
for(var i = 0; i<image.length; i++)
{
attachment.push(image[i]);
}
MailApp.sendEmail(sendDetails[7], subject, null, {
name: 'Test ' + sendDetails[0].split(",", 1),
htmlBody:toSend[3],
inlineImages: toSend[1],
attachments: attachment,
cc:bccSend,
replyTo:sendDetails[7]
});
ui.alert('Message has been sent.');
}
And then here is the function for when Cancel is clicked. Again, if I run this function on its own, it runs just fine. But it is not called when the Cancel button is clicked.
function clickCancel() {SpreadsheetApp.getUi().alert('Send Aborted.');}
Any ideas what I am doing wrong? Thanks!
....UPDATE....
I have corrected the spelling error (it was correct in the script, just copied over an old portion of code by accident. This is the error I get within the browser once the Cancel button is clicked.
I believe it's just a spelling error
<input type="button" value="ok" onClick="google.script.run.withSuccessHandler(() => google.script.host.close()).clickOk()"> spelled clickOk
function clickOK() { spelled clickOK()
two caps on the end as opposed to one cap and lowercase
This is similar to your dialog and the buttons work. Sorry I'm not comfortable with arrow notation yet.
function runTwo(){
var ui = SpreadsheetApp.getUi();
var bccSend = SpreadsheetApp.getActive().getSheetByName('Sheet13').getRange(2,2).getValue();
var bccSendReplace = bccSend.toString().replace(/,/g,"<br>");
const str = 'You are about to send this to the following email address(es): \n\n' + bccSendReplace + '\n\n Click OK to send, otherwise, close this box or click Cancel to abort.';
let html = `<b>${str}</b><br>`;
html+='<input type="button" value="ok" onClick="google.script.run.withSuccessHandler(function(){google.script.host.close();}).clickOk()" />';
html+='<input type="button" value="cancel" onClick="google.script.run.withSuccessHandler(function(){google.script.host.close();}).clickCancel()"/>';
ui.showModalDialog(HtmlService.createHtmlOutput(html), 'sample');
}
function clickCancel() {
SpreadsheetApp.getUi().alert('Clicked cancel');
}
function clickOk() {
SpreadsheetApp.getUi().alert('Click Ok')
}
Background
I'm fairly new to coding in general and as I've gone through building this little UI for Google Sheets using GAS, this is one of the concepts that has just has me stumped. I've tried and tried reading, understand conceptually and apply withSuccessHandler(function) to all sorts of examples but I just can't seem to get it to work.
Here's what I understand so far:
When you run the following on the client side (e.g. Index.html):
google.script.run.withFailureHandler(CallbackFunction).ServerSideFunction(aVariable)
a. ServerSideFunction(aVariable): This function from your Code.gs is first called and returns a value, "OutputA," back to Index.html.
b. CallbackFunction: Then, this function is called and uses "OutputA" as its input and returns another value which you can use for whatever purpose.
Goal
Open Dialog Box asking for "Name" and "E-mail." DONE
Once user hits "Submit" the input is populated in the spreadsheet. DONE
The form is then cleared so they can potentially add another person or exit if they want.
Question
For that last piece, I can't seem to get withSuccessHandler to return a value back from my script, confirming that the inputs were entered properly, and I really don't know how to proceed.
I've included a working version of the code below without withSuccessHandler for reference. Any help at all in better understanding this and how I can incorporate it into the code below would be greatly appreciated!
Google Script
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Menu')
.addItem('Add Member', 'createDialog')
.addToUi();
}
function createDialog() {
var htmlOutput = HtmlService
.createHtmlOutputFromFile('Index')
.setWidth(300)
.setHeight(300);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, " ");
}
function addAName(bName, bEmail) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var ControlSheet = sheet.getSheetByName('Control Sheet');
var lRow = ControlSheet.getRange(1, 1, ControlSheet.getLastRow(), 1).getValues().filter(String).length;
var Name = ControlSheet.getRange(lRow + 1, 1);
var Email = ControlSheet.getRange(lRow + 1, 2);
var ValidName = /(^[A-Za-z]+)\s([A-Za-z]{1}[.]{1}\s)?([A-Za-z]+$)/g;
var ValidEmail = /(^[A-Za-z]+)([.]{1})([A-Za-z]{1}[.]{1})?([A-Za-z]+)(#google.com|#yahoo.com)$/g;
if (bName.match(ValidName) && bEmail.match(ValidEmail)) {
Name.setValue(bName);
Email.setValue(bEmail);
}
}
HTML
<body>
<h2>Add New Member</h2>
<div class="mdc-layout-grid">
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
<div class="mdc-text-field mdc-text-field--upgraded" data-mdc-auto-init="MDCTextField">
<input class="mdc-text-field__input" id="Name" type="text" aria-controls="name-validation-message" pattern="(^[A-Za-z]+)\s([A-Za-z]{1}[.]{1}\s)?([A-Za-z]+$)">
<label for="Name" class="mdc-floating-label">Name</label>
<div class="mdc-line-ripple"></div>
</div>
<p class="mdc-text-field-helper-text mdc-text-field-helper-text--validation-msg" id="name-validation-message" aria-hidden="true">
Please enter your full name.
</p>
</div>
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
<div class="mdc-text-field" data-mdc-auto-init="MDCTextField">
<input class="mdc-text-field__input" id="Email" type="text" aria-controls="name-validation-message" pattern="(^[A-Za-z]+)([.]{1})([A-Za-z]{1}[.]{1})?([A-Za-z]+)(#google.com|#yahoo.com)$">
<label for="Email" class="mdc-floating-label">Email Address</label>
<div class="mdc-line-ripple"></div>
</div>
<p class="mdc-text-field-helper-text mdc-text-field-helper-text--validation-msg" id="name-validation-message" aria-hidden="true">
Please enter a valid Google or Yahoo email address.
</p>
</div>
</div>
<div class="Right_Side">
<button class="mdc-button mdc-button--unelevated secondary-filled-button" OnClick="google.script.host.close()">Cancel
</button>
<button class="mdc-button mdc-button--unelevated primary-filled-button" OnClick="sendInputToGS()">Submit
</button>
</div>
<script>
window.sendInputToGS = function() {
var aName = document.getElementById("Name").value;
var aEmail = document.getElementById("Email").value;
google.script.run.addAName(aName, aEmail);
}
</script>
<script type="text/javascript">
window.mdc.autoInit();
</script>
</body>
Only one parameter can be added to the server function being called. But you can use an array as the one parameter and put multiple values into the array.
var data_Array = [aName, aEmail];
google.script.run
.withSuccessHandler(myClientSideFunction)
.addAName(data_Array);
A value is not automatically returned, you must add a return myValue statement.
function addAName(data) {
var bName, bEmail;
bName = data[0];//Arrays in JavaScript are zero indexed
bEmail = data[1];
//code
return "something";
}
Client Code
<script>
window.sendInputToGS = function() {
var aName = document.getElementById("Name").value;
var aEmail = document.getElementById("Email").value;
var data_Array = [aName, aEmail];
google.script.run
.withSuccessHandler(confirmationBack)
.addAName(data_Array);
}
window.confirmationBack = function(rtrn) {
if (rtrn === 'success') {
}
}
</script>
The success and failure handlers for the async communication between the Apps Script instance and the client-side HTML code are functions in the client-side code, not written in Apps Script (.gs).
When calling your server-side function foo(input), it can be used to call other server-side functions (bar(), baz()) as desired, and use their outputs to form the value that is sent to the client-side success handler.
Example:
.gs
function foo(valFromClient) {
var firstPart = bar(valFromClient);
var secondPart = baz(valFromClient);
return {input: valFromClient, first: firstPart, second: secondPart};
}
function bar(input) {
return "'bar' called, input '" + String(input) + "'.";
}
function baz(val) {
return "'baz' called, input '" + String(val) + "'.";
}
.html
...
<script>
function getServerStuff() {
google.script.run
.withFailureHandler(serverThrewException)
.withSuccessHandler(serverFunctionCalledReturn)
.foo("Hi");
}
function serverThrewException(err) {
console.log(err);
}
function serverFunctionCalledReturn(value) {
console.log(value);
}
</script>
In this example, because our code does not throw any exceptions, the failure handler will never be called, and only the success handler will be called. The browser console would log the object {input: "Hi", first: "'bar' called, input 'Hi'.", second: "'baz' called, input 'Hi'."}.
I have a library and have imported into my spreadsheet.
Library file contains these 2 files (sona)
customHtml.gs // UI
customScript.gs // Code
Spreadsheet file
code.gs
In customHtml.gs code (my library)
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> </script>
<input id="txt1" type="text" />
<input id="txt2" type="text" />
<input id="Button1" class="btnSave" type="button" value="Save" />
<script>
$("#Button1").on('click',function(){
var param1=$("#txt1").val();
var param2=$("#txt2").val();
google.script.run.getCars(param1, param2);
});
</script>
customScript.gs
function sonatask_Assign(){
var html = HtmlService.createHtmlOutputFromFile('customHtml')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(900)
.setHeight(500);
return html;
}
function getCars(param1, param2){
// code logic
Logger.log(param1,param2);
}
Spreadsheet (code.gs)
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('► BOT')
.addSeparator()
.addItem('Task Assign ', 'fn_task_Assign')
.addItem('☎ Help ', 'helpMethod')
.addToUi();
}
// display dialog box with custom HTML (which is stored in library)
function fn_task_Assign(){
// get html from library (reason behind using HTML from library, is to acces only specific user. As spreadsheet is availabe to all any one can make a copy of it, but functions will not work unless i acces them to this library )
var showHTML = sona.sonatask_Assign();
Logger.log(abc);
SpreadsheetApp.getUi().showModalDialog(showHTML, " ");
}
On fn_task_Assign it display modelbox. i.e. 2 txtbox and a button.
Now on button click I try to call getCars function, but its not working.
Create getCars inside your code.gs and call sona.getCars();
function getCars(param1, param2) {
sona.getCars(param1, param2);
}
Also in oder this all to work, your library needs tobe a Stand alone script. Otherwise it wont work.