Google sheets sidebar form - set default values - google-apps-script

I am trying to make a spreadsheet sidebar that allows a user to input data to create records, as well as edit them. So far I understand how to create the sidebar and display it. I've got a working form that can submit values.
What I am struggling with is how to pre-populate the forms. Form instance some records are associated with others, and I'd like to have a hidden field to store and eventually submit the associated id. Eventually users should also be able to edit records and I'd like to use the same form and just populate the fields and reuse the same submission flow.
I've tried a few different things found on here and other places, but nothing seems to work.
Here is the HTML for the sidebar template
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js"></script>
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<style>
</style>
<script>
// Prevent forms from submitting.
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
$('#accountId').val(<? data.accountId ?>);
function handleFormSubmit(formObject) {
google.script.run.withSuccessHandler(alertSuccess).createContact(formObject);
}
function alertSuccess(message) {
var div = document.getElementById('alert');
div.innerHTML = "<p>" + message + "</p>";
google.script.host.close();
}
</script>
</head>
<body>
<p>Enter Contact Info</p>
<form id="contact" onsubmit="handleFormSubmit(this)">
Account Id: <br>
<input type="number" name="accountId" value="0" id="accountId" /><br>
Name:<br>
<input type="text" name="name"/><br>
Phone Number:<br>
<input type="text" name="phone"/><br>
Email:<br>
<input type="email" name="email"/><br>
Contact Type:<br>
<input type="radio" name="type" value="emergency" checked> Emergency<br>
<input type="radio" name="type" value="guardian" checked> Guardian<br>
<input type="radio" name="type" value="other" checked> Other<br>
<input type="submit" value="Submit" />
</form>
<div id="alert"></div>
</body>
</html>
And the accompanying .gs file:
var AlternativeContact = ObjectModel("AlternativeContacts");
function newContact() {
var htmlOutput = HtmlService.createTemplateFromFile("new_contact");
var id = ACCOUNT_MANAGER.getRange("M4").getValue();
htmlOutput.data = {accountId: id};
UI.showSidebar(htmlOutput.evaluate());
}
function createContact(contactJSON) {
var newContact = new AlternativeContact(contactJSON);
newContact.save();
return "Success!";
}
The first line that uses ObjectModel is creating and ORM around the data sheet.
Thanks for the help!

Couple changes and it seems to be working in a basic way.
First, in the scriptlet, i needed to us the printing tag. so use in stead of . This was causing the value to not be used in the rendered template.
Second, I changed my jQuery to:
$(document).ready(function () {
$("input#accountId").val(<?= data.accountId ?>);
});
If anyone is able to answer, I'd be curious why using the $(document).ready is needed. Doesn't everything in the get run? is it an order of operation thing?

Related

Stop chrome auto filling / suggesting form fields by id

Ok so...like many other posts this is driving me nuts. Chrome is continually offering autocomplete suggestions for fields that I would rather it not be on. It, along with the soft keyboard take up the whole page which blocks the view for the user / the form is not intended to fill our the users data but rather a new address that would be previously unknown.
So far I've got these both on
<form autocomplete="off">
and
<input autocomplete="randomstringxxx">
Their effect is noticed and chrome is no longer filling the whole form - but it STILL wants to suggest single field suggestions for each element in my form.
I've finally realised that its now picking up the id/name fields from my form elements.
i.e the below will give me a list of names I have used before.
<input id="contact_name" name="contact_name">
Can anyone suggest a way to stop this without renaming the elements? They are tied to fields in my database and ideally I would not have to manually rename and match up these together.
example -
https://jsfiddle.net/drsx4w1e/
with random strings as autocomplete element attribute - STILL AUTOCOMPLETING
https://jsfiddle.net/drsx4w1e/1/
with "off" as autocomplete attribute. - STILL AUTOCOMPLETING
https://jsfiddle.net/6bgoj23d/1/
example no autocomplete when labels / ids/ name attr are removed - NOT AUTOCOMPLETING
example
I know this isn't ideal because it changes the name of the inputs but it only does it temporarily. Changing the name attribute is the only way I found that completely removes the autocomplete.
This solution is all in JS and HTML but I think it would be better if it was implemented with a server side language such as PHP or Java.
I found autocomplete="none" works best for chrome but it doesn't fully turn off auto complete.
How it works
So, on page load this solution adds a string of random characters to each input name.
eg. 'delivery_contact_name' becomes 'delivery_contact_nameI5NTE'
When the form is submitted it calls a function (submission()) which removes the random character that were added. So the submitted form data will have the original names.
See solution below:
<html>
<body>
<form autocomplete="none" id="account_form" method="post" action="" onsubmit="return submission();">
<div class="my-2">
<label for="delivery_contact_name" class="">*</label>
<input autocomplete="none" class="form-control" id="delivery_contact_name" maxlength="200" minlength="2" name="delivery_contact_name" required="" type="text" value="">
</div>
<div class="my-2">
<label for="delivery_telephone" class="">Telephone*</label>
<input autocomplete="none" class="form-control" id="delivery_telephone" maxlength="200" minlength="8" name="delivery_telephone" required="" type="tel" value="">
</div>
<div class="my-2">
<label for="delivery_address_1" class="">Delivery Address*</label>
<input autocomplete="none" class="form-control" id="delivery_address_1" maxlength="50" minlength="2" name="delivery_address_1" required="" type="text" value="">
</div>
<div class="my-2">
<label for="delivery_address_2" class="">Delivery Address*</label>
<input autocomplete="none" class="form-control" id="delivery_address_2" maxlength="50" minlength="2" name="delivery_address_2" required="" type="text" value="">
</div>
<div class="my-2">
<label for="delivery_address_3" class="">Delivery Address</label>
<input autocomplete="none" class="form-control" id="delivery_address_3" name="delivery_address_3" type="text" value="">
</div>
<div class="my-2">
<label for="delivery_address_4" class="">Delivery Address</label>
<input autocomplete="none" class="form-control" id="delivery_address_4" name="delivery_address_4" type="text" value="">
</div>
<div class="my-2">
<label for="delivery_address_postcode" class="">Delivery Postcode*</label>
<input autocomplete="none" class="form-control" id="delivery_address_postcode" maxlength="10" minlength="6" name="delivery_address_postcode" required="" type="text" value="">
</div>
<input type="submit" name="submit" value="Send">
</form>
</body>
<script>
//generate a random string to append to the names
const autocompleteString = btoa(Math.random().toString()).substr(10, 5);
//get all the inputs in the form
const inputs = document.querySelectorAll("input");
//make sure script calls function after page load
document.addEventListener("DOMContentLoaded", function(){
changeInputNames();
});
//add random characters to input names
function changeInputNames(){
for (var i = 0; i < inputs.length; i++) {
inputs[i].setAttribute("name", inputs[i].getAttribute("name")+autocompleteString);
}
}
//remove the random characters from input names
function changeInputNamesBack(){
for (var i = 0; i < inputs.length; i++) {
inputs[i].setAttribute("name", inputs[i].getAttribute("name").replace(autocompleteString, ''));
}
}
function submission(){
let valid = true;
//do any additional form validation here
if(valid){
changeInputNamesBack();
}
return valid;
}
</script>
</html>
Thanks to #rydog for his help. I've changed it into a function that I've put into a my js file as I didn't want to manually add to each page / fire on every page - I have also added the submit event handler with js rather than adding to the on submit of the form.
GREAT SOLUTION by Rydog
function stop_autofill() {
//generate a random string to append to the names
this.autocompleteString = btoa(Math.random().toString()).substr(10, 5);
this.add_submit_handlers = () => {
document.querySelectorAll("form").forEach(value => {
value.addEventListener("submit", (e) => {
this.form_submit_override(e)
})
})
}
//add random characters to input names
this.changeInputNames = () => {
for (var i = 0; i < this.input_elements_arr.length; i++) {
this.input_elements_arr[i].setAttribute("name", this.input_elements_arr[i].getAttribute("name") + this.autocompleteString);
}
}
//remove the random characters from input names
this.changeInputNamesBack = () => {
for (var i = 0; i < this.input_elements_arr.length; i++) {
this.input_elements_arr[i].setAttribute("name", this.input_elements_arr[i].getAttribute("name").replace(this.autocompleteString, ''));
}
}
this.form_submit_override = (e) => {
e.preventDefault()
this.changeInputNamesBack()
e.currentTarget.submit()
return true
}
this.setup_form = () => {
//get all the inputs in the form
this.input_elements_arr = document.querySelectorAll("input");
this.changeInputNames();
this.add_submit_handlers();
}
//make sure script calls function after page load
this.init = () => {
if (document.readyState === "complete") {
this.setup_form()
} else {
let setup_form = this.setup_form
document.addEventListener("DOMContentLoaded", function (e) {
setup_form()
})
}
}
}
on the page that needs it
<script>
af = new stop_autofill()
af.init()
</script>

Unable to submit more than one file to google script web project

I have created a Google Script Web project and added a HTML form which is having multiple file input fields and a few text input fields. The form is working and submitting all the text input every time but having an issue with the file input fields.
When my form is having only one file input field then all the text inputs and the file data are submitted to the server and can save the file.
When my form is having two or more file input fields then all the text inputs are submitted to the server side but the file fields are having empty object which is {}
The form looks like below:
<form id="msForm">
.......
<input type="file" class="custom-file-input" name="certificateFile" accept="application/pdf" required >
<input type="file" class="custom-file-input" name="otherDocuments" accept="application/pdf" required >
.....
<input type="button" name="next" class="next action-button" value="Submit" />
</form>
JS has the following to submit the form:
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
$(document).ready(function(){
$(".next").click(function(){
if($(this).val()=="Submit") {
google.script.run
.withSuccessHandler(successFormSubmit)
.withFailureHandler(failedFormSubmit)
.processForm($(this).closest("form")[0]);
}
});
});
The server side function in the Code.gs file:
function processForm(formObject) {
console.log(formObject.certificateFile);
console.log(formObject.otherDocuments);
}
Application is deploying and testing with following project settings:
Post edit:
The issue has been fixed by Google in this bug reported
This issue does seem to be a bug. While Google is looking at it, I suggest you a workaround:
Only logging the bytearray server-side seems to be impacted by the bug, you can still create all files from the blobs passed on form submisison with DriveApp.createFile().
If you need a server-side check to make sure the file blob is not empty, you can create in your form a hidden additional element of a type that is not "file" and use it a placeholder to send information to the serverside.
Sample:
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<form id="msForm">
<input type="file" class="custom-file-input" name="certificateFile" accept="application/pdf" required >
<input type="file" class="custom-file-input" name="otherDocuments" id="otherDocuments" accept="application/pdf" required >
<input type="text" readonly="true" name="otherDocumentsName" id="otherDocumentsName" style="display: none;">
<input type="button" id="submitBtn" name="next" class="next action-button" value="Submit" />
</form>
<script>
document.getElementById('submitBtn').addEventListener('click',function(e) {
console.log(this.parentNode)
document.getElementById("otherDocumentsName").value = document.getElementById("otherDocuments").value;
google.script.run.withSuccessHandler(successFormSubmit).withFailureHandler(failedFormSubmit).processForm(this.parentNode);
})
function successFormSubmit(data){
console.log("success");
}
function failedFormSubmit(data){
console.log("failed");
}
</script>
</body>
</html>
code.gs
function doGet() {
var html = HtmlService.createHtmlOutputFromFile('index');
return html.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function processForm(data){
var blob1 = data.certificateFile;
var file1 = DriveApp.createFile(blob1);
file1.setName("certificateFile");
if (data.otherDocumentsName !== "") {
console.log("not empty")
var file2 = DriveApp.createFile(data.otherDocuments);
file2.setName("otherDocuments");
}
else{
console.log("empty");
}
}

Custom dialog box isn't showing any output

I'm using the following scripts for a custom dialog box to make data entries into a specific range of cells. (source: https://spreadsheet.dev/custom-dialog-in-google-sheets)
Here is the HTML part:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function submitForm() {
google.script.run.appendRowFromFormSubmit(document.getElementById("Stunde eintragen"));
document.getElementById("form").style.display = "none";
document.getElementById("Danke").style.display = "block";
}
</script>
</head>
<body>
<div>
<div id="form">
<form id="Stunde eintragen">
<label for="Datum">Datum</label>
<input type="date" id="Datum" name="Datum"><br><br>
<label for="Inhalt">Inhalt</label>
<input type="text" id="Inhalt" name="Inhalt" size="60"><br><br>
<label for="Lehrkraft">Lehrkraft</label>
<select id="Lehrkraft" name="Lehrkraft">
<option value="Alexander Michaelis">Alexander Michaelis</option>
<option value="Thomas Weider">Thomas Weider</option>
</select>
<div>
</br>
<label for="Ausfall">Stunde ausgefallen?</label><br>
<input type="radio" id="E" name="Ausfall" value="E">
<label for="E">Entschuldigt</label><br>
<input type="radio" id="UE" name="Ausfall" value="UE">
<label for="UE">Unentschuldigt</label><br><br>
<input type="button" value="Eintragen" onclick="submitForm();">
</form>
</div>
</div>
<div id="Danke" style="display: none;">
<p>Die Stunde wurde eingetragen!</p>
</div>
</body>
</html>
And here is the GAS part:
//#OnlyCurrentDoc
function onOpen() {
SpreadsheetApp
.getUi()
.createMenu("Klassenbuch")
.addItem("Stunde eintragen", "showFeedbackDialog")
.addToUi();
}
function showFeedbackDialog() {
var widget = HtmlService.createHtmlOutputFromFile("Form.html");
SpreadsheetApp.getUi().showModalDialog(widget, "Stunde eintragen");
}
function appendRowFromFormSubmit(form) {
var row = [form.name, form.feedback, form.rating];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
By submitting the form I want to have the data copied into a specific range (for example A7:A, B7:B, C7:C, D7:D with row 6 for the labels) and everytime I use the script it should jump in the next row.
But unfortunately the script doesn't show any output.
Can anybody help?
You can't transmit objects, but you can transmit values, even if they are in array like this
html :
function submitForm() {
var form = document.getElementById("Stunde eintragen")
google.script.run.appendRowFromFormSubmit([form.Datum.value , form.Ausfall.value , form.Inhalt.value]);
document.getElementById("form").style.display = "none";
document.getElementById("Danke").style.display = "block";
}
and gs
function appendRowFromFormSubmit(data) {
SpreadsheetApp.getActiveSheet().appendRow(data);
}
Note that in your form has no feedback and no rating !!

Select a random element from a dynamic array in HTML

I'd like to add HTML to a Google Site that allows a user to press a button that displays a random letter of the alphabet. However, it should randomize only the letters that the user selects through checkboxes. Below is an image of what I'd like to achieve, and I'd like the result to display to the right of the checkbox array.
As to what I have tried so far, I have the following code that I modified from an open source online. I hope it is ok for my purpose.
<!DOCTYPE html>
<html>
<body>
<h1>Pick Letters To Randomize</h1>
<form action="/action_page.php">
<input type="checkbox" id="letter1" name="letter1" >
<label for="letter1"> A</label><br>
<input type="checkbox" id="letter2" name="letter2" >
<label for="letter2"> B</label><br>
<input type="checkbox" id="letter3" name="letter3" >
<label for="letter3"> C</label><br><br>
<input type="submit" value="Randomize">
</form>
</body>
</html>
But I am really at a loss for how to solve the rest of my problem.
Here is a working example for you. I have a few suggestions that I've implemented that will make this easier for you:
Add a value to the checkbox input. That way, you don't have to grab a child/sibling label.
I've added comments to show what I'm doing. Hope that helps!
document.addEventListener("DOMContentLoaded", function() {
const form = document.getElementById("randomLetterForm");
const submitBtn = document.getElementById("randomSubmit");
const textResult = document.getElementById("result");
// We check the values on the submit click
submitBtn.addEventListener("click", function(e) {
// Prevent it from *actually* submitting (e.g. refresh)
e.preventDefault();
// Grab *all* selected checkboxed into an array
const items = document.querySelectorAll("#randomLetterForm input:checked");
// Checking if it's not empty
if (items.length > 0) {
// Setting a random index from items[0] to items[items.length]
textResult.innerHTML = items[Math.floor(Math.random() * items.length)].value;
} else {
// If not, we alert
alert("Please choose at least 1 number");
}
});
});
<h1>Pick Letters To Randomize</h1>
<form id="randomLetterForm" action="/action_page.php">
<input type="checkbox" value="A" id="letter1" name="letter1" >
<label for="letter1"> A</label><br>
<input type="checkbox" value="B" id="letter2" name="letter2" >
<label for="letter2"> B</label><br>
<input type="checkbox" value= "C" id="letter3" name="letter3" >
<label for="letter3"> C</label><br><br>
<input id="randomSubmit" type="submit" value="Randomize">
</form>
<div>
<p id="result"></p>
</div>

Dynamic HTML Form Entry

Is it possible to make an HTML form that responds to the number of things the user wants to send over?
That is, what I have now is:
<form ...>
<select ...>
<option>1</option>
<option>2</option>
...
</select>
***
</form>
When the user selects one of the options, *** should have
<input type="text" ...>
appear the number of times the user selected.
That is, if the user selected 5 from the options, then the user should see 5 input options. If he changes his mind selected 2 instead, then the page should update accordingly to show only 2 input options.
=====[EDIT]=====
I've changed the code to have the input just be text. The code I have does not work. It doesn't update the number of input fields.
<script type="text/javascript">
<!--
function updateOptions(nvars)
{
var n = nvars;
while(n>0) {
var newdiv1 = "<div>Var name: <input type=\"text\" name=\"var-name\"><br></div>";
var newdiv2 = "<div>Var type: <input type=\"text\" name=\"var-type\"><br></div>";
newdiv1.appendTo("#bloo");
newdiv2.appendTo("#bloo");
n--;
}
}
//-->
</script>
<h3>Create a table in the test db!<h3>
<form name="f1" method="POST" action="createTable.php">
Name of Table: <input type="text" name="table-name"><br>
No of vars: <input type="text" name="numvars" onChange="updateOptions(this.value)"><br>
<div id="bloo"></div>
</form>
It worked when I had a document.write instead of an appendTo, but I essentially want the page the remain the same save for the extra input fields after the user changes the value in the numvars field.
That's a good idea when you want the user to be able to upload an arbitrary number of files or something like that. You can do it with Javascript:
Have an empty DIV near the SELECT
Bind a function to the "onchange" event on the select element
In the function, read the value of the SELECT element and:
Empty the DIV
Create an equivalent number of <INPUT type="text"> inside the DIV
Do you need code? If you do, is Prototype OK?
OK, sorry for the delay, lots of work to do lately.
The following should be enough for you to get an idea. You'll have to study JS though, I don't even know what you're doing with the appendTo stuff in your question.
<html>
<head>
</head>
<body>
<form>
<select id="num" value="1">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
<div id="container">
<p>
<input type="text" name="var-name" />
<input type="text" name="var-type" />
</p>
</div>
</form>
<script type="text/javascript">
var selectElm = document.getElementById('num');
var containerElm = document.getElementById('container');
var update = function () {
containerElm.innerHTML = '';
for (var i = 0, l = selectElm.value; i < l; ++i) {
containerElm.innerHTML += '<p><input type="text" name="var-name" /><br /><input type="text" name="var-type" /></p>';
} // add a number of couples of <input> equal to selectElm.value
}
//the following stuff says that when <select> changes the function called "update" must fire. Most of the code is for compatibility across browsers.
var listen, evt;
if (document.attachEvent) {
listen = 'attachEvent';
evt = 'onchange' ;
} else {
listen = 'addEventListener';
evt = 'change';
}
try {
selectElm[listen](evt, update);
} catch (e) {
selectElm[listen](evt, update, false);
}
// You do the same in Prototype with a single line:
// selectElm.observe('change', update);
// jQuery also requires only a single line of code.
</script>
</body>
</html>
Yes use onChange event of your dropdown input field and show/hide your input fields.