HTMLService won't display return value - google-apps-script

I created a sidebar to have a basic UI for searching my Google sheet. I'm following this tutorial exactly to make sure the first step works, except that it doesn't! I even took out the userObject part to make it simpler (honestly, because I don't know what that part does).
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function updateButton(email, button) {
button.value = 'Clicked by ' + email;
}
</script>
</head>
<body>
<input type="button" value="Not Clicked"
onclick="google.script.run
.withSuccessHandler(updateButton)
//.withUserObject(this)
.testMe()" />
<input type="button" value="Not Clicked"
onclick="google.script.run
.withSuccessHandler(updateButton)
//.withUserObject(this)
.testMe()" />
</body>
</html>
It calls this function:
function testMe() {
Logger.log("Test log.");
return ContentService.createTextOutput("Jackpot!");
}
If it matters, the HTML runs in a sidebar via onOpen as follows:
function showGradingSidebar() {
var html = HtmlService.createHtmlOutputFromFile('testSidebar')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle('Testing Module')
.setWidth(300);
SpreadsheetApp.getUi()
.showSidebar(html);
}
When I click the button, it does nothing (that I can see). By changing various aspects, I can get it to Logger.log() a simple message but even that doesn't work reliably if I change the HTML side.
I was reading about the security restrictions that require sanitizing what the function returns, but both HtmlService.createHtmlOutput() and ContentService.createTextOutput() were also unsuccessful. Please advise.
UPDATE: Thanks to #Bryan P, I got it to work. The testMe() is simply:
return "Jackpot";
...and the HTML page looks like this:
[html, head, etc.]<body>
<input type="button" value="Ready"
onclick="google.script.run
.withSuccessHandler(updateButton)
.withUserObject(this)
.testMe()" --->
<br><div id="output">Output goes here: </div>
<br><div id="papa">Papa goes here: </div>
<br><p></p>
<script>
function updateButton(result) {
var div = document.getElementById('output')
div.innerHTML = 'It finally works!' + result;
}
</script>
</body>
</html>
I don't know how much it helped, but I did move the script tag down to the bottom of the body, fwiw, after reading this SO post.

In Chrome, if you right-click in the sidebar area >> Inspect >> in the Console it should show a message that there wasn't a valid return type after clicking on one of the buttons.
.createTextOutput(content) returns a TextOutput type (which isn't the same as just plain text type)
It's only used when you've deployed a the web app URL and some external service calls that URL. It only gets handled with doGet() too.
Did you try just return "Jackpot"; instead?
.withUserObject(this) - this refers to button element and the whole method passes it on to the successHandler(). So you can consider keeping it. Otherwise you'd have to reference the button from within the successHandler another way:
function updateButton(email) {
document.getElementById('myButton').value = 'Clicked by ' + email;
}
...which requires you add an ID attribute into the button.
You can always do:
function updateButton(email, button) {
console.log('Success hit');
button.value = 'Clicked by ' + email;
}
...to check whether the successHandler was even called in that Chrome dev console too.

Related

google.script.run.withSuccessHandler does not work properly

I have a HTML code setup within my Google Apps Script project which basically validates the userid and password provided that pair exists in the gSheet or not and according to the verification I either show an alert or redirect to another web-page. When I deploy the code as a web-app, HTML renders just fine, submit button works and executes the gs code however it does not return the value back to the withSuccessHandler function of JS inside the script tag in the HTML code.
I have read a couple of very identical queries in the forum and followed exactly the same path, however I couldn't be able to succeed. For example like this post
It seems there's something that my eyes and mind cannot see - what is it?
Here is the HTML
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div>
<form id="user_login">
<input type="text" name="userid" id="userid" placeholder="enter username"/>
<input type="text" name="pswrd" id="pswrd" placeholder="enter password"/>
<input type="submit" name="login" id="login" value="LOGIN"/>
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
$('#login').click(function() {
console.log("Form submitted...");
google.script.run
.withSuccessHandler(function(response) {
console.log(response);
if (response == true) {
window.open("http://www.google.com", "_self");
} else {
alert("Some alert message here")
}
})
.checkName({
userid: $('#userid').val(),
pswrd: $('#pswrd').val()
});
});
</script>
</body>
</html>
And here is the gs functions
var html;
var verified;
function doGet() {
html = HtmlService.createTemplateFromFile('myformtest').evaluate().setTitle('Web App').setSandboxMode(HtmlService.SandboxMode.NATIVE);
return html
}
function checkName(form){
Logger.log("Form info > "+JSON.stringify(form));
Logger.log("Userid > "+form.userid);
Logger.log("Pwd > "+form.pswrd);
verified = false;
var ss = SpreadsheetApp.openById("1rard7vdpjmfrN81oi2ARhMlLGdNSxC7MqoIw2soj25E").getSheetByName("names");
var values = ss.getDataRange().getValues();
for(n=1;n<values.length;++n){
var username = values[n][0]; // 1 is the index of the column starting from 0
var password = values[n][1];
Logger.log("username: "+username+" | password: "+password);
if (username == form.userid && password == form.pswrd) {
Logger.log("User found in Row("+(n + 1)+")");
verified = true;
break;
}
}
Logger.log("User verified? "+verified);
return verified
}
I can log that the JS is fired correctly with passing the form parameters to the gs function correctly and the gs function executed correctly as well. However, it does not return the true/false value back to the handler function inside the JS.
I expect that when the checkName function is executed, to return the variable's value back to the JS that is to be processed within the withSuccessHandler function and resulting with either redirecting to a definite URL or displaying an alert message.

Add refresh button to a Google Scripts Web App

I came across the following script last night and it works really nicely to drop files into a Google Drive folder, however I've noticed that there's no clear way to get back to the front page of the app after uploading a file.
https://script.google.com/macros/d/1URDuve8yT1EpDj_WKLHPAuiVt1LWDdUN2kzH-ERUnuxVQqXbi-9I9EfU/edit?usp=drive_web
I realised that this can be achieved by refreshing the page, but my end users are people who are not very computer savvy, and I would like to add a button that refreshes the form to make it a bit easier on them. Unfortunately, I have no idea how to go about doing this.
Can anybody help me out?
Once your file is uploaded successfully this function is called :
function fileUploaded(status) {
document.getElementById('myForm').style.display = 'none';
document.getElementById('output').innerHTML = status;
}
As we can see, this function is hiding the form and putting status in output div, so if we don't hide the form and only update status in output[or maybe you can so a popup/alert on success ?] I think your purpose will be solved.
Something like this should work [Maybe you'll need to style your html a bit]:
function fileUploaded(status) {
document.getElementById('output').innerHTML = status;
}
You can add a button with href to the self page[web app], this is a hacky way to refresh.
The form is has id="myForm" and the status is shown on a div with id="output".
To show the form set is display style property to block. You could do this my using something like
document.getElementById('myForm').style.display = 'block';
To clear the status just add use something like
document.getElementById('output').innerHTML = '';
Example:
The following examples use HTML/CSS and pure JavaScript to show how to "reset a page" on Google Apps Script
//Initializes the html elements as they are shown after a file is uploaded
document.getElementById('myForm').style.display = 'none';
document.getElementById('output').innerHTML = "File uploaded successfully.";
function resetPage() {
document.getElementById('myForm').style.display = 'block';
document.getElementById('output').innerHTML = '';
}
input {
display:block; margin: 20px;
}
<form id="myForm">
<input type="text" placeholder="Input 1">
<input type="text" placeholder="Input 2">
</form>
<div id="output"></div>
<input type="button" onClick="resetPage();" value="Reset">

Prevent HTML Page Refresh

At this stage I'm mostly used to backend Javascript and server side Java, so my HTML is not as savvy as it needs to be.
I've built several applications that require user input with Apps script, but I was using the now deprecated UI service, as I'm not a designer and this provided an easy way to design simple pages to pass data back and forth. With the UI service having been deprecated for some time, I'm begging the arduous task of migrating these services to the HTML service, and I'm noticing some difference in behavior.
For example, when submitting a form, the entire page refreshes to a blank page, and I can't seem to prevent that. The UI service would remain static for information re-entry, and I can't find a working method to get the HTML service to either stop refreshing or reload the form.
Simple code to reproduce my issue:
function doGet() {
return HtmlService.createHtmlOutputFromFile('test')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function logValues(value){
Logger.log('Something worked....');
}
With the index file being:
<form>
<input type="submit" value="Book Meeting" onclick="google.script.run
.logValues()">
</form>
Some things I've tried:
1) Adding a callback to the 'doGet' function, to attempt to get the page to load again.
2) Adding a whole new function to try and call a NEW HTML page.
The issue here is my poor understanding of the HTML service, but is there a simple way for me to just clear the form for re-submission, or alternatively just reload the page? None of the other questions I've found on SO adequately answer this question in a way I can understand.
Since you're technically submitting your form by clicking the submit button, then that creates the page refresh. You need to cancel the submit event with the preventDefault function, which "Cancels the event if it is cancelable, without stopping further propagation of the event."
See the docs here: https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
So maybe you can try something along these lines (straight from the docs):
function stopDefAction(evt) {
evt.preventDefault();
}
document.getElementById('my-checkbox').addEventListener('click', stopDefAction, false);
Another option is to remove the form/input elements and simply use a button element instead, which doesn't trigger a page refresh on click.
It's an interesting ride switching old UI services across, I just did that with one of my applications and it has really improved the readability of the code. I posted a copy of a basic version of what I was doing in another question
Once you get your head around it all it becomes a lot simpler. This is a really basic example of using multiple HTML files similar to your example using the HTMLService when submitting forms (you can pass in parameters instead)
Code.gs
function doGet() {
return HtmlService.createTemplateFromFile('Main')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
function onLogin(form) {
if (form.username == "fuzzyjulz") {
var template = HtmlService.createTemplateFromFile('Response');
//Setup any variables that should be used in the page
template.firstName = "Fuzzy";
template.username = form.username;
return template.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
.getContent();
} else {
throw "You could not be found in the database please try again.";
}
}
function include(filename) {
return HtmlService.createTemplateFromFile(filename)
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.getContent();
}
Main.html
<?!= include('CSS'); ?>
<script>
function loadPage(htmlOut) {
var div = document.getElementById('content');
div.innerHTML = htmlOut;
document.getElementById('errors').innerHTML = "";
}
function onFailure(error) {
var errors = document.getElementById('errors');
errors.innerHTML = error.message;
}
</script>
<div id="errors"></div>
<div id="content">
<?!= include('Login'); ?>
</div>
CSS.html
<style>
p b {
width: 100px;
display: inline-block;
}
</style>
Login.html
<script>
function onLoginFailure(error) {
var loginBtn = document.getElementById('loginBtn');
loginBtn.disabled = false;
loginBtn.value = 'Login';
onFailure(error);
}
</script>
<div class="loginPanel">
<form>
<p>
<b>Username: </b>
<input type="text" name="username"/>
</p>
<input type="button" id="loginBtn" value="Login" onclick="this.disabled = true; this.value = 'Loading...';google.script.run
.withSuccessHandler(loadPage)
.withFailureHandler(onLoginFailure)
.onLogin(this.parentNode)"/>
</form>
</div>
Response.html
<div class="text">
Hi <?= firstName ?>,<br/>
Thanks for logging in as <?= username ?>
</div>

HTML form validation with Google Apps Script's HTML Service

I'm trying to use HTML form validation when using Google Apps Script's HTML Service. As another user asked, according to the documentation example, you must use a button input instead of a submit input. Using a submit button seems to do the validation, but the server function is called anyway. The answer given to that user didn't work for me. Also, I want to call two functions when submitting the form and this can make it more complex.
This is what I'm trying to do: The user fills a form and I generate a Google Doc and give him the URL. When he clicks the submit button, I show him a jQuery UI dialog saying "Your document is being created" with a nice spinner. Then, when the document is generated, I give him the link. I use the success handler to show the result when the Google Doc stuff is finished, but meanwhile I need a function to show the spinner. I don't know if there is a better way to do that than adding another function to the onclick event and maybe it can be damaging the process in some way. Is there a way not to call any of these functions if the form is not valid (using HTML validation)?
This is a simplified version of my code:
Code.gs
function generateDocument(formObject) {
var doc = DocumentApp.create("Document name");
...
return doc.getUrl();
}
Page.html
<main>
<form id="myForm">
...
<input type="button" value="Generate document"
onclick="showProgress();
google.script.run
.withSuccessHandler(openDocument)
.generateDocument(this.parentNode);"/>
</form>
<div id="dialog-confirm" title="Your document">
<div id="dialog-confirm-text"></div>
</div>
Javascript.html
$( "#dialog-confirm" ).dialog({ autoOpen: false, resizable: false, modal: true });
function showProgress() {
$( "#dialog-confirm" ).dialog({ buttons: [ { text: "Cancel", click: function() { $( this ).dialog( "close" ); } } ] });
$( "#dialog-confirm" ).dialog( "open" );
$( "#dialog-confirm-text" ).html( "<br />Wait a second, your document is being generated...<br /><br /><img src='http://i.stack.imgur.com/FhHRx.gif' alt='Spinner'></img>" );
return false;
}
function openDocument(url) {
$( "#dialog-confirm" ).dialog({ autoOpen: false, resizable: false, width: 400, buttons: [ { text: "Ok", click: function() { $( this ).dialog( "close" ); } } ] });
$( "#dialog-confirm-text" ).html( '<br />Click here to open and print your document!' );
return false;
}
All three HTML docs are joined together (and working with its respective tags) with the include function as recommended in the documentation.
The Cancel button in the dialog will close it but won't stop the doc being created. Is it possible to stop this process?
Here's a solution that I found:
"<input type='submit' onclick='if(verifyForm(this.parentNode)===true){google.script.run.withSuccessHandler(YOUROUTPUT).YOURFUNCTION(this.parentNode); return false;}' value='Submit'></form>";
JavaScript side
function verifyForm(){
var elements = document.getElementById("myForm").elements;
for (var i = 0, element; element = elements[i++];) {
if (element.hasAttribute("required") && element.value === ""){
resetInputs();
return false;
}
if (element.hasAttribute("pattern")){
var value = element.value;
if(value.match(element.pattern)){
}else{
resetInputs();
return false;
}
}
}
return true;
}
Calling the window has issues in iOS sometimes, which is why I investigated this further.
Move the function call to the <form> element; remove any function call from the submit input element; and put intermediary JavaScript code into a <script> tag:
<input tabindex="9" type="submit" value="Save Input" id='idInputBtn'>
<form id="myInputForm" name="input" onsubmit="fncWriteInput(this)">
<script>
window.fncWriteInput= function(argTheInfo) {
// Do additional checks here if you want
var everythingIsOk = . . . . . . . ;
if (everythingIsOk) {
google.script.run
.withSuccessHandler(openDocument)
.generateDocument(argTheInfo);
};
};
Notice that this.parentNode gets removed to the arg of the function call, and just use this in the function argument because the function is getting called from the <form> element, which is the parent.
If there are any errors, the form will not be submitted, and the user will get a msg that something was wrong. No code will run.
This is pseudo code, but I do use a set up like this in my application. But use developer tools and you can put a break point right in your browser and step through every line to test it without needing to put in console.log statements.

ajax form submit with mootools

I have a site that I built way back in 2007.. I had done some ajax stuff, and it worked fine.. Today, that site owner contacted me and said he noticed the forms were no longer working.
I went to take a look and the console showed that upon submitting it was getting an error:
Refused to set unsafe header "Connection"
I assumed this had to be a change in browser security or something, since no code had changed and everything worked fine last time I looked at it.... So I did some googling and found some others mention this, and their solution was to update mootools.. I did, and what a headache, the entire API had changed.. So I fixed everything, and it seemed like it should all work, but it didn't.
$('application_form').addEvent('submit', function(e) {
new DOMEvent(e).stop();
updateText("Sending...");
var progress_bar = $('bar');
progress_bar.empty().addClass('ajax-loading');
this.send({
update: progress_bar,
onSuccess: function(e) {
progress_bar.removeClass('ajax-loading');
(function() { mySlide.slideOut() }).delay(1500);
}
});
});
This results in an ajax request going to the url [Object object], rather than the actual .php script specified in the application_form's action attribute... I tried specifying a url option to tell it not to go to [Object object] but no good...
Here's a jsfiddle with a working example based on yours.
It shouldn't be too hard to upgrade yours.
http://jsfiddle.net/K976E/3/
Here's the html code in case the jsfiddle gets gone.
<!DOCTYPE HTML>
<html>
<body>
<form id='application_form' action='http://www.scoobydoo.com/cgi-bin/scoobysnack'>
Count of Scooby Snacks: <input type='text' size='5' /><br />
<input id='submitButton' type='submit' value='Rut Roh Shaggy!'/>
</form>
<div id='bar' style='margin-top:20px'>
Yikes!
</div>
</body>
</html>
And here's the javascript (place in onLoad):
new Form.Request('application_form', 'bar', {
onSend: function(){
updateText("Sending...");
var progress_bar = $('bar');
progress_bar.empty().addClass('ajax-loading');
$('submitButton').set('value', 'Sending the form...').set('disabled', true);
},
onComplete: function(){
var progress_bar = $('bar');
progress_bar.removeClass('ajax-loading');
//(function() { mySlide.slideOut() }).delay(1500);
}
});
function updateText(txt){
// do something here...
}
... and no, I didn't re-enabled the submit button within the onComplete...