Apps script get element by id - google-apps-script

I've got the following in an apps script client side html template:
<body>
<form id="myForm" onsubmit="handleFormSubmit(this)">
<div>
<label for="optionList">Click me</label>
<select id="optionList" name="email" onchange="optionChange()">
<option>Loading...</option>
</select>
</div>
I want to capture the change using :
function optionChange() {
mySelect = document.getElementById("optionList");
console.log(mySelect)
};
But I do not see any elements in the console.
Following some other boilerplate code I got from some examples I tried:
mySelect = $(document).getElementById("optionList").value;
Now in the console I get:
TypeError: $(...).getElementById is not a function
At least I see something in the console. What is '$' and How can I get this working?
I've made the suggested change but still just see spreadsheet related post and get statements in the console.

You should specify the value property of the element, so
//based on your coding style
function optionChange() {
mySelect = document.getElementById("optionList");
mySelectValue = mySelect.value;
console.log(mySelectValue)
};
or
//based on your coding style
function optionChange() {
mySelect = document.getElementById("optionList").value;
console.log(mySelect)
};

Related

Unable to pass HTML data into Code.gs function

When I click on the run button (See my HTML code), I am unable to paste the data into sheet.
When I run the makeRequest function from the editor, I get an error - TypeError: Cannot read property 'startDate' of undefined.
HTML
<div class="block form-group">
<label for="select">Date Range</label>
<select id="select">
<option selected id="default" value="default">None</option>
<option selected id="today" value="today">Today</option>
<option selected id="yesterday" value="yesterday">Yesterday</option>
</select>
<div class="form-group">
<button id="btn">Run it</button>
</div>
</div>
Javascript. HTML
<script>
document.getElementById('btn').addEventListener("click",retrieveFacebookData);
function retrieveFacebookData () {
var facebookAccountData = {};
//I will be adding more key value pairs in this object later
facebookAccountData.startDate = document.getElementById('select').value;
google.script.run.makeRequest(facebookAccountData);
}
</script>
Code.gs
function makeRequest(facebookAccountData) {
console.log(facebookAccountData.startDate);
const row = [facebookAccountData.startDate,newDate()];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
Modification points:
From When I run the makeRequest function from the editor, I get an error - TypeError: Cannot read property 'startDate' of undefined., in this case, if you directly run makeRequest with the script editor, the argument of facebookAccountData is not declared. By this, such error occurs. In your script, it is required to run the Javascript at the sidebar, dialog and Web Apps.
About your HTML & Javasript side, I think that there is a modification point. In your Javascript, document.getElementById('btn').addEventListener("click",retrieveFacebookData); is used. But <button class="btn">Run it</button> has no ID.
When above points are reflected to your script, it becomes as follows.
Modified script 1:
When you want to directly run the function of makeRequest with the script editor, how about the following modification?
function makeRequest(facebookAccountData) {
facebookAccountData = {startDate: "sample"};
console.log(facebookAccountData.startDate);
const row = [facebookAccountData.startDate, "newDate()"];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
Modified script 2:
When you want to run the function of makeRequest with HTML & Javascript side, how about the following modification?
Google Apps Script side:
function openDialog() {
const html = HtmlService.createHtmlOutputFromFile("index");
SpreadsheetApp.getUi().showModalDialog(html, "sample");
}
function makeRequest(facebookAccountData) {
console.log(facebookAccountData.startDate);
const row = [facebookAccountData.startDate, "newDate()"];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
HTML & Javascript side: index.html
<div class="block form-group"> <label for="select">Date Range</label> <select id="select">
<option selected id="default" value="default">None</option>
<option selected id="today" value="today">Today</option>
<option selected id="yesterday" value="yesterday">Yesterday</option>
</select>
<div class="form-group"> <button id="btn">Run it</button> </div>
</div>
</div>
<script>
document.getElementById('btn').addEventListener("click",retrieveFacebookData);
function retrieveFacebookData () {
var facebookAccountData = {};
//I will be adding more key value pairs in this object later
facebookAccountData.startDate = document.getElementById('select').value;
google.script.run.makeRequest(facebookAccountData);
}
</script>
In this case, please run openDialog() with the script editor. By this, a dialog is opened and HTML can be seen. When you select it and click the button, makeRequest is run with facebookAccountData. By this, the value is appended to the Spreadsheet.
Reference:
Dialogs and Sidebars in Google Workspace Documents

Google Apps Script with HTML Forms

I am working on a google add-on which uses an HTML sidebar. The sidebar has an HTML form with checkboxes. I want some of the checkboxes to be checked when the user opens the sidebar, based on some User Properties that will already have been initialized. (When the form submits, the Properties are updated. They all start as on). Here is the code for the sidebar:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<title>Settings</title>
<script>
function onSettingsOpen()
{
Logger.log("In the script");
console.log("In the script");
document.getElementById(propsList[0]).checked = (allPreferences.getProperty(propsList[0]) === "true");
document.getElementById(propsList[1]).checked = (allPreferences.getProperty(propsList[1]) === "true");
document.getElementById(propsList[2]).checked = (allPreferences.getProperty(propsList[2]) === "true");
document.getElementById(propsList[3]).checked = (allPreferences.getProperty(propsList[3]) === "true");
document.getElementById(propsList[4]).checked = (allPreferences.getProperty(propsList[4]) === "true");
document.getElementById(propsList[5]).checked = (allPreferences.getProperty(propsList[5]) === "true");
}
</script>
</head>
<body onload="onSettingsOpen()">
<form id="baseSettings" onsubmit="event.preventDefault(); google.script.run.processForm(this)">
<h2>What settings would you like to be on? Please select all that apply.</h2>
<br>
<input type="checkbox" name="checks" value="spaces" id="spaces">Double spaces
<br>
<input type="checkbox" name="checks" value="punctuation" id="punctuation">Punctuation outside of quotes
<br>
<input type="checkbox" name="checks" value="caps" id="caps">Capitilazation at beginning of sentences
<br>
<input type="checkbox" name="checks" value="contractions" id="contractions">Contractions
<br>
<input type="checkbox" name="checks" value="numbers" id="numbers">Small numbers
<br>
<input type="checkbox" name="checks" value="repWords" id="repWords">Repeated words
<br>
<br>
<input type="submit" value="Submit">
</form>
</body>
So as far as I can tell, the Logger.logs and Console.logs aren't running, which implies that the function just isn't running. However, I could not find documentation on running Console/Logger Log functions in an HTML script file; I'm not sure if that is actually the telling factor. What I can't figure out is where to run the function so that it can actually effect the checkboxes. I fear that running it onload of the body won't actually do anything to the checkboxes- it would have to run within the form itself. Where should I call the function?
Here is my create settings pane function:
function openSettings ()
{
populateData ();
initializePreferences();
Logger.log("Data is initialized; pref 1 = " +
allPreferences.getProperty(propsList[0]));
var htmlOutput = HtmlService
.createHtmlOutputFromFile('Settings.html')
.setWidth(300)
.setTitle("Settings");
DocumentApp.getUi().showSidebar(htmlOutput);
}
Any help appreciated!
You could use a function like this when you load the sidebar
<script>
window.onload=function(){
google.script.run
.withSuccessHandler(function(data){
//initialize your checkboxes here
})
.getPropertData();//go to server side to get access to PropertiesService data
};
Client to Server Communication
Your onSettingsOpen references propsList, but this is undefined. You should pass the data in the function by giving the function an argument, e.g. onSettingsOpen(preferences).
Assuming you are storing these preferences in some PropertiesService, when you call Properties.getProperties() you get back an object with key, value pairs. If you make these match your HTML input "id" attributes, you can just lookup the values in the object by passing the id as a key.
Inside the sidebar:
<script>
google.script.run.withSuccessHandler(onSettingsOpen).getAllPreferences();
// #param {Object} preferences - key, value pairs from Properties.getProperties()
function onSettingsOpen(preferences)
{
console.log("In the script");
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
for (let i = 0; i < checkboxes.length; ++i) {
checkboxes[i].checked = preferences[checkboxes[i].id];
}
}
</script>
Server-side code would need the appropriate getAllPreferences function:
function getAllPreferences() {
return PropertiesService.getUserProperties().getProperties();
}

"The name 'Request' does not exist in the current context"

EDIT: I have now changed Request to Context.Request and am receiving a different error to the title of this post now: Cannot apply indexing with [] to an expression of type 'HttpRequest'
So I am trying to introduce myself to ASP.Net and am following an online tutorial available here: https://www.w3schools.com/asp/webpages_forms.asp (the 'Displaying Images' part). I am attempting to implement this in an MVC style layout. The starting template for the structure is a modified version of the template that is produced when you run dotnet new -t web.
In my Pictures.cshtml file, I have the following:
#{
ViewData["Title"] = "Pictures";
var imagePath="";
if (Request["Choice"] != null)
{
imagePath="images/" + Request["Choice"];
}
}
<h2>Pictures</h2>
<form method="post" action="">
I want to see:
<select name="Choice">
<option value="Photo1.jpg">Photo 1</option>
<option value="Photo2.jpg">Photo 2</option>
<option value="Photo3.jpg">Photo 3</option>
</select>
<input type="submit" value="Submit" />
#if (imagePath != "")
{
<p>
<img src="#imagePath" alt="Sample" />
</p>
}
</form>
This is called from a MainController.cs as so:
using Microsoft.AspNetCore.Mvc;
namespace WebApp.Controllers
{
public class MainController : Controller
{
public IActionResult Pictures()
{
return View();
}
}
}
There is also a _ViewStart.cshtml which references a _Layout.cshtml.
When running this, I am redirected to my error page, and the terminal gives the error The name 'Request' does not exist in the current context
Can somebody help point me in the right direction as to what I am missing or what I have done wrong? Why does this tutorial example not work in the context of my project?
Cheers :)
I had the same error in Core 2.1. I've found if you put it in the ViewData or Styles section, it's hard to debug (or something just wonky about it). Otherwise, it should work like this for a specific parameter:
#Context.Request.Query["Choice"]
For your code, I would just request the value once and assign it to a variable, then see if it's empty or not. Then, create the path from your variable:
var choice = #Context.Request.Query["Choice"];
var imagePath="";
if (!String.IsNullOrEmpty(choice)){
imagePath="images/" + choice;
}
As a bonus, you can also get the entire query string with all the names and values (in case you want to parse it or whatever) by going:
#Context.Request.QueryString
I use this in ASP Core 5.0 :
ViewContext.HttpContext.Request.Query["Choice"]
example in JS:
if (getParameterByName("error") != null) {
window.location.href = "#Url.Action("Login", "Home" , new { error = ViewContext.HttpContext.Request.Query["Choice"] })";
}

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>

html5 form checkValidity() method not found

I am trying to use the form method checkValidity().
http://html5test.com/ tells me that my browser (Chrome) support the form-level checkValidity method.
However, using jsfiddle http://jsfiddle.net/LcgnQ/2/ I have tried the following html and javascript snippets:
<form id="profileform" name="profileform">
<input type="text" id="firstname" required>
<input type="button" id="testbutton" value="Test">
</form>
$('#testbutton').bind('click',function(){
try{
alert($('#profileform').checkValidity());
}
catch(err){alert('err='+err)};
});
I'm getting an error: object has no method checkValidity()
What am I doing wrong?
Try:
$('#profileform')[0].checkValidity()
When you select $('#profileform') you get a jQuery object array. To access actual DOM properties you must select the first item in the array, which is the raw DOM element.
#robertc 's Answer is perfect. Anyway I'd just add another way to do it using jQuery's .get([index]) function. It also retrieves the DOM element for the given index, or all of the matched DOM elements if there's no index declared.
In the end it is exactly the same, only written in a bit more verbose way:
$('#profileform').get(0).checkValidity()
Leaving you the docs right here: https://api.jquery.com/get/
Just another way:
// Fetch all the forms we want to apply custom Bootstrap validation styles to
var forms = document.getElementsByName('profileform');
// Loop over them and prevent submission
var validation = Array.prototype.filter.call(forms, function (form) {
form.addEventListener('submit', function (event) {
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
}, false);
});