Google Apps Script function executing twice for one onclick event - javascript

I am trying to add a UI feature to a google sheet that allows users to update info. The script functions properly but for whatever reason is executing twice. Here's the code:
HTML
<html>
  <head>
    <base target="_top">
    <link rel="stylesheet" href="https://www.google.com/css/maia.css">
  </head>
  <body>
    <br />
    Username: 
    <input id="username" value="">
    <br />
    Select the Country: 
    <select id="country">
      <option value="USA" selected="selected">USA</option>
      <option value="CAN">Canada</option>
      <option value="MEX">Mexico</option>
    </select>
  </body>
<hr/>
<button id="runBtn">Update Country</button>
<button onclick="google.script.host.close()">Close</button> 
<script>
  document.getElementById("runBtn").addEventListener("click",function r(event){
    // document elements that we will pass to updateCountry function
    var username = document.getElementById("username").value;
    var e = document.getElementById("country");
    var country = e.options[e.selectedIndex].value;
    // run updateCountry function
    google.script.run.updateCountry(username, country);
    google.script.host.close();
    event.currentTarget.removeEventListener(event.type, r)
    event.stopPropagation();
  })
</script> 
</html>
Google script (javascript)
function openDialog() {
  var html = HtmlService.createHtmlOutputFromFile('Index').setHeight(100);
  SpreadsheetApp.getUi()
      .showModalDialog(html, 'Update Country');
}
function updateCountry(username, country){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Sheet1"); 
  // appends row to sheet with updated User country
  updateUserInfo(username, country);
}
Could someone please advise how to get the the function to run only once? Is this the result of a bubbling issue? I've tried event.stopPropagation() but that does not seem to help.
UPDATE:
The updateUserInfo() function also had variables referencing
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("AnotherTab");
I've sinced move that function into updateCountry() and the double execution (appendRow) has stopped.

I couldn't get it to run at first. I had to remove some curly quotation marks in it that the script editor didn't like. Anyway I modified a little and now it works and doesn't run twice.
html:
<html>
<head>
<base target="_top">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
</head>
<body>
<br>
Username:
<input type="text" id="username" value="ldldldldldl" /><br />
Select the Country:<select id="country">
<option value="USA" selected="selected">USA</option>
<option value="CAN">Canada</option>
<option value="MEX">Mexico</option>
</select>
<hr/>
<button id="runBtn">Update Country</button>
<button onclick="google.script.host.close()">Close</button>
<script>
$(function(){
document.getElementById("runBtn").addEventListener("click",function r(event){
console.log('flag1');
var username=$('#username').val();
console.log(username);
console.log('flag2');
var country=$('#country').val();
console.log('flag3');
console.log(country);
google.script.run.updateCountry(username, country);
google.script.host.close();
event.currentTarget.removeEventListener(event.type, r)
event.stopPropagation();
console.log('exit');
})
});
</script>
</body>
</html>
GS:
function runOne() {
var html = HtmlService.createHtmlOutputFromFile('aq7');
SpreadsheetApp.getUi().showModelessDialog(html,'Update Country');
}
function updateCountry(username, country){
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
sh.appendRow([username,country]);
Logger.log(username);
Logger.log(country);
}

Related

I cannot get input to work under google app script

I'm writing a web app under google sheets and can't get an input field to work. What am I doing wrong?
everything works but uname is always empty (not undefined).
edit: I'm adding the full code after simplifying it as much as I could.
In the log I get "name" regardless of the input I type in.
In the file code.gs:
function doGet () {
var participant = {};
var templ = HtmlService.createTemplateFromFile('out');
return templ.evaluate();;
}
function formSubmit(name) {
Logger.log("name " + name);
}
In out.html
<!DOCTYPE html>
<html>
<head>
<base target ="_top">
</head>
<body dir="rtl"; background-color: #92a8d1;>
<label> Name 1 </label> <input type="text" id="firstname"><br>
<label> Name 2 </label> <input type="text" id="lastname"> <br><br>
<button type="button" id="send">Send</button>
<script>
document.getElementById("send").addEventListener("click", getData());
function getData(){
var uname = document.getElementById("firstname").value;
google.script.run.formSubmit(uname);
}
</script>
</body>
</html>
You want to retrieve the value of <input type="text" id="firstname"> when the button is clicked.
In your current situation, when you see the log with the script editor, only name is retrieved. This is your current issue.
You want to know the reason of the issue.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
In your script, document.getElementById("send").addEventListener("click", getData()); is used. In this case, when the HTML is loaded, getData() is run by () of getData(). By this, uname becomes "" and "" is sent to formSubmit(uname), then, when you see the log, you see name. And also, in this case, even when the button is clicked, google.script.run.formSubmit(uname); cannot be run. I think that this is the reason of the issue of your script in your question.
In order to avoid this, please modify your script as follows.
Modified script:
From:
document.getElementById("send").addEventListener("click", getData());
To:
document.getElementById("send").addEventListener("click", getData);
By the above modification for your script, when sample is inputted to "Name 1" and click "Send" button, you can see name sample at the log with the script editor.
Reference:
addEventListener()
If I misunderstood your question and this was not the result you want, I apologize.
Here's an example form that you can probably use to accomplish your needs. This form is used as a simple receipt collection system. You can actually take and upload images from a mobile device with it. I also has text and button input types and upload a form node.
Code.gs
var receiptImageFolderId='';
var SSID='';
function onOpen() {
SpreadsheetApp.getUi().createMenu('Receipt Collection')
.addItem('Run as Dialog', 'showAsDialog')
.addItem('Run as Sidebar', 'showAsSidebar')
.addToUi();
var sh=SpreadsheetApp.getActive().getSheetByName("Sheet1");
sh.getRange(sh.getLastRow()+1,1).activate();
}
function uploadTheForm(theForm) {
var rObj={};
rObj['vendor']=theForm.vendor;
rObj['amount']=theForm.amount;
rObj['date']=theForm.date;
rObj['notes']=theForm.notes
var fileBlob=theForm.receipt;
var fldr = DriveApp.getFolderById(receiptImageFolderId);
rObj['file']=fldr.createFile(fileBlob);
rObj['filetype']=fileBlob.getContentType();
Logger.log(JSON.stringify(rObj));
var cObj=formatFileName(rObj);
Logger.log(JSON.stringify(cObj));
var ss=SpreadsheetApp.openById(SSID);
ss.getSheetByName('Sheet1').appendRow([cObj.date,cObj.vendor,cObj.amount,cObj.notes,cObj.file.getUrl()]);
var html=Utilities.formatString('<br />FileName: %s',cObj.file.getName());
return html;
}
function formatFileName(rObj) {
if(rObj) {
Logger.log(JSON.stringify(rObj));
var mA=rObj.date.split('-');
var name=Utilities.formatString('%s_%s_%s.%s',Utilities.formatDate(new Date(mA[0],mA[1]-1,mA[2]),Session.getScriptTimeZone(),"yyyyMMdd"),rObj.vendor,rObj.amount,rObj.filetype.split('/')[1]);
rObj.file.setName(name);
}else{
throw('Invalid or No File in formatFileName() upload.gs');
}
return rObj;
}
function doGet() {
var output=HtmlService.createHtmlOutputFromFile('receipts').setTitle('thehtml');
return output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL).addMetaTag('viewport', 'width=360, initial-scale=1');
}
function showAsDialog() {
var ui=HtmlService.createHtmlOutputFromFile('thehtml');
SpreadsheetApp.getUi().showModelessDialog(ui, 'Receipts')
}
function showAsSidebar() {
var ui=HtmlService.createHtmlOutputFromFile('thehtml');
SpreadsheetApp.getUi().showSidebar(ui);
}
function initForm() {
var datestring=Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "yyyy-MM-dd")
return {date:datestring};
}
The Html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function(){
google.script.run
.withSuccessHandler(function(rObj){
$('#dt').val(rObj.date);
})
.initForm();
});
function fileUploadJs(frmData) {
var amt=$('#amt').val();
var vndr=$('#vndr').val();
var img=$('#img').val();
if(!amt){
window.alert('No amount provided');
$('#amt').focus();
return;
}
if(!vndr) {
window.alert('No vendor provided');
$('#vndr').focus();
return;
}
if(!img) {
window.alert('No image chosen');
$('#img').focus();
}
document.getElementById('status').style.display ='inline';
google.script.run
.withSuccessHandler(function(hl){
document.getElementById('status').innerHTML=hl;
})
.uploadTheForm(frmData)
}
console.log('My Code');
</script>
<style>
input,textarea{margin:5px 5px 5px 0;}
</style>
</head>
<body>
<h3 id="main-heading">Receipt Information</h3>
<div id="formDiv">
<form id="myForm">
<br /><input type="date" name="date" id="dt"/>
<br /><input type="number" name="amount" placeholder="Amount" id="amt" />
<br /><input type="text" name="vendor" placeholder="Vendor" id="vndr"/>
<br /><textarea name="notes" cols="40" rows="2" placeholder="NOTES"></textarea>
<br/>Receipt Image
<br /><input type="file" name="receipt" id="img" />
<br /><input type="button" value="Submit" onclick="fileUploadJs(this.parentNode)" />
</form>
</div>
<div id="status" style="display: none">
<!-- div will be filled with innerHTML after form submission. -->
Uploading. Please wait...
</div>
</body>
</html>
Here's what the dialog looks like:

Uncaught TypeError: $(...).ready(...) is not a function even though it works on JsFiddle

The JSFiddle in question seems to have no trouble: http://jsfiddle.net/S3LF3/ (if you type a url like google.com it will select the value of ".com" or whatever you put after the dot and it is in the list)
However the jQuery function seems to be faulty?
In chrome I get this error:
Uncaught TypeError: $(...).ready(...) is not a function
Here is my code:
<html>
<head>
<title>Add Site</title>
<script src="js/jquery-3.1.1.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/msdropdown/dd.css" />
<script src="js/msdropdown/jquery.dd.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/msdropdown/flags.css" />
</head>
<body>
<h1>Add new site</h1>
<div class="contentbox">
<form method="post">
<input type="hidden" name="addsite" value="true"/>
<p>
<label for="site_url">Site url:</label>
<input type="text" name="site_url" id="urlText" placeholder="domain.xxx" value=""/>
</p>
<p>
<label for="site_url">Search locale:</label>
<select name="locale" id="locale">
<option value="">
Select locale
</option>
<optgroup label="Popular">
<option value=".dk" data-image="images/msdropdown/icons/blank.gif" data-imagecss="flag dk" data-title="Denmark">Denmark - Danish</option>
<option value=".de" data-image="images/msdropdown/icons/blank.gif" data-imagecss="flag de" data-title="Germany">Germany - German</option>
<option value=".au" data-image="images/msdropdown/icons/blank.gif" data-imagecss="flag au" data-title="Australia">Australia - English</option>
</optgroup>
</select>
</p>
<p>
<label for="site_url"></label>
<input type="submit" name="submit" class="btn" value="Add">
</p>
</form>
</div>
</body>
</html>
<script>
$(document).ready(function() {
$("#locale").msDropdown();
})
(function ($) {
$('#urlText').on('change', function () {
var value = this.value,
parts = this.value.split('.'),
str, $opt;
for (var i = 0; i < parts.length; i++) {
str = '.' + parts.slice(i).join('.');
$opt = $('#locale option[value="' + str + '"]');
if ($opt.length) {
$opt.prop('selected', true);
break;
}
}
})
})(jQuery);
</script>
What I want to do is when a user types a url, like "google.dk" , it should select the value with ".dk" at the end from the dropdown for him.
I played with your code and found out, it's the missing semicolon at the end of $(document).ready();
$(document).ready(function() {
$("#locale").msDropdown();
});
Just try it in the built-in editor:
<html>
<head>
<title>Add Site</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/msdropdown/dd.css" />
<script src="js/msdropdown/jquery.dd.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/msdropdown/flags.css" />
</head>
<body>
<h1>Add new site</h1>
<div class="contentbox">
<form method="post">
<input type="hidden" name="addsite" value="true"/>
<p>
<label for="site_url">Site url:</label>
<input type="text" name="site_url" id="urlText" placeholder="domain.xxx" value=""/>
</p>
<p>
<label for="site_url">Search locale:</label>
<select name="locale" id="locale">
<option value="">
Select locale
</option>
<optgroup label="Popular">
<option value=".dk" data-image="images/msdropdown/icons/blank.gif" data-imagecss="flag dk" data-title="Denmark">Denmark - Danish</option>
<option value=".de" data-image="images/msdropdown/icons/blank.gif" data-imagecss="flag de" data-title="Germany">Germany - German</option>
<option value=".au" data-image="images/msdropdown/icons/blank.gif" data-imagecss="flag au" data-title="Australia">Australia - English</option>
</optgroup>
</select>
</p>
<p>
<label for="site_url"></label>
<input type="submit" name="submit" class="btn" value="Add">
</p>
</form>
</div>
</body>
</html>
<script>
$(document).ready(function() {
//$("#locale").msDropdown();
});
(function ($) {
$('#urlText').on('change', function () {
var value = this.value,
parts = this.value.split('.'),
str, $opt;
for (var i = 0; i < parts.length; i++) {
str = '.' + parts.slice(i).join('.');
$opt = $('#locale option[value="' + str + '"]');
if ($opt.length) {
$opt.prop('selected', true);
break;
}
}
})
})(jQuery);
</script>
The reason the JSFiddle works fine is it doesn't actually use the country flag drop down menu plugin you found. The way the plugin works is different than the way a standard <select> html tag works.
I looked inside the jquery.dd.js file, and it seems there is no way to interact with the dropdown menu using javascript. Therefore, your line $opt.prop('selected', true); is valid for a real drop down menu (a <select> tag), but not for the plugin's drop down menu, which handles the selected state differently. Also, the plugin provides little documentation and has little to no comments in the code, so this makes it diffcult to know how the plugin works.
I can see 2 solutions to your problem:
You can try to understand the plugin's code in the jquery.dd.js file and try to implement a way to tell the plugin which element you want to select. I wouldn't normally recommend editing third-party plugins, but since the site you got the plugin from has not been updated since 2012, there is little chance for the plugin to receive updates in the future, so editing it would be safe in this case. It will require some knowledge in jQuery plugin making, though.
You can try to find another country flag drop down plugin that can be updated using javascript instead of using your mouse only. Such projects can be found on Github (here are some results found on Github: Country flag drop down menu results on Github).
Previous information found:
From the code you provided, you are missing a semi-colon after the call of the $(document).ready() function. It should look like this:
$(document).ready(function() {
$("#locale").msDropdown();
}); // <--- This semi-colon is missing
Also, the code in (function ($) {// code} )(jQuery); may be executed before the document was ready, which means this code could sometimes not work. Try putting this code inside your $(document).ready(); anonymous function. This way, you will be sure all html nodes will be accessible by jQuery.
Finally, you should consider putting your javascript logic somewhere inside the <html> tag (inside the <head>, right after <body> or right before </body>). While putting the <script> tag after the </html> tag may work at first, this is far from valid html and may be the cause of some problems you're having. The best way would be to put your javascript in a .js file and link it in your <head> the same way you linked jQuery and msdropdown:
<head>
<!-- Your other linked files... -->
<script src="js/main-logic.js"></script>
</head>
The JQuery shortcut - $() - seems to have been overridden by another library is my guess.
Try using:
jQuery( document ).ready(function( $ ) {
// Code that uses jQuery's $ can follow here.
});

need help switching pages

i need help with my function. i have all my variables.
var swith = document.getElementById("sub");
var one = document.getElementById("rule");
var two = document.getElementById("cool");
var three = document.getElementById("fool");
Function cool() {
if(swith,one) window.location.replace("app.html");
};
<html>
<head>
<title>question?</title>
<link href="app.css" rel="stylesheet" type="text/css"/>
</head>
<body id="QA">
<select id="much">
<option id="rule">0-10,000</option>
<option id="cool">25,00-50,000</option>
<option id="fool">75,000-100,000</option>
<input type="submit" id="sub">
<script src="app.js"></script>
</select>
</body>
</html>
i think i missed something important. but google stated "Uncaught SyntaxError: Unexpected identifier".
Try lower case f for Function eg:
function cool() {
if(swith,one) window.location.replace("app.html");
};
Function with a capital "F" is a function constructor in javascript.
You should be using the regular function expression.
Instead of adding id attributes to options, I suggest adding a value attribute and on submit of the containing form, you carry out your logic for submission.
I have attached a raw snippet which you could expand and modify to suit your needs. This snippet submission will not work on this site due to sandboxing of form submission.
window.onload = function() {
document.getElementById("TestForm").addEventListener("submit",function(e) {
if (this.value === "rule") {
document.location.replace('app.html');
}
},false);
};
<html>
<head>
<title>question?</title>
</head>
<body id="QA">
<form id="TestForm" name="TestForm" action="#">
<select id="much">
<option value="rule">0-10,000</option>
<option value="cool">25,00-50,000</option>
<option value="fool">75,000-100,000</option>
</select>
<input type=submit value="Submit"/>
</form>
</body>
</html>

javascript on change using while loop

Error:
TypeError: d.options is undefined while(i<=d.options.length){
Hi i have this javascript of mine which has the select option to choose from. and from choosing from the select options it will display to the textbox field im using this onchange and using it while loop. can someone help me how to figure this out?? using while loop code?
Here's my code below
<!DOCTYPE html>
<html>
<head>
<title>Activity 2 while loop</title>
<script type="text/javascript">
function tellMe(d){
var i = 0;
while(i<=d.options.length){
if(d.listbox1.options[i].selected == true){
d.choose.value = d.listbox1.options[i].text;
}
}
}
</script>
</head>
<body>
<form name="form1">
<p>Girl's qualities you want?</p>
<select name="listbox1" size="5" onchange="tellMe(this.form)">
<option>Pretty</option>
<option>Sexy</option>
<option>Hot</option>
<option>Intelligent</option>
<option>Funny</option>
</select>
<br />
<p>
You Choose: <input type="text" name="choose" />
</p>
</form>
</body>
</html>
any help is muchly appreciated! thanks
Fiddle
Fixed it:
<!DOCTYPE html>
<html>
<head>
<title>Activity 2 while loop</title>
<script type="text/javascript">
function tellMe(d){
document.getElementById("choose").value = d.value;
}
</script>
</head>
<body>
<form name="form1">
<p>Girl's qualities you want?</p>
<select name="listbox1" size="5" onchange="tellMe(this)">
<option>Pretty</option>
<option>Sexy</option>
<option>Hot</option>
<option>Intelligent</option>
<option>Funny</option>
</select>
<br />
<p>
You Choose: <input type="text" id="choose" name="choose" />
</p>
</form>
</body>
</html>
You were trying to get this.form but it should have been this, then the value of this (this.value).
Then, all you had to do was set the input type with name='choose', however I gave it an ID of choose to make it easier to select, then gave that value d.value, which was the value of listbox1.
You are passing the form element, which doesn't have options. Change d.options to d.listbox1.options
while(i<=d.listbox1.options.length){
After that is fixed your second problem will become apparant, whch is that you fail to increment i:
while(i<=d.listbox1.options.length){
...
i++;
}
You aren't incrementing i. You need to add i++ at the end of your while loop. You also need to target listbox1 in your while loop.
function tellMe(d){
var i = 0;
while(i<=d.listbox1.options.length){ // <-- use d.listbox1.options.length
if(d.listbox1.options[i].selected == true){
d.choose.value = d.listbox1.options[i].text;
}
i++; // <-- add increment here
}
}

Disable the text boxes

I am beginner to html. I have two text boxes say t1 and t2 If t1 is filled with some data then then other text box t2 should be disable. Please let me know hot to do it. Thanks in advance
Based on your simple scenario description, here's an implementation that works cross-browser and without any third-party javascript library:
<html>
<head>
<script type="text/javascript">
window.onload = function(){
var t1 = document.getElementById("t1");
var t2 = document.getElementById("t2");
t1.onchange = function(){
t2.disabled = t1.value.length > 0;
}
}
</script>
</head>
<body>
<form>
t1:<input type="text" id="t1" name="t1" /><br/>
t2:<input type="text" id="t2" name="t2" />
</form>
</body>
</html>
<head>
<script type="text/javascript">
function verify(){
var t1 = document.getElementById ('first');
var t2 = document.getElementById ('second');
if (t1.value != '') {
t2.setAttribute('disabled','disabled');
return true;
}
if (t2.value != '') {
t1.setAttribute('disabled','disabled');
return true;
}
}
</script>
</head>
<body>
...
<input type="text" id="first" onblur="verify()">
<input type="text" id="second" onblur="verify()">
...
</body>
You can't achieve this with plain HTML.
Following the guidelines of progressive enhancement, you should first implement a server side check in whatever form handler you are using to process the submitted data.
Then you can consider adding JavaScript for a client side check. Something along the lines of:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Disabling form controls</title>
</head>
<body>
<form id="myForm" action="http://example.com/">
<div>
<input name="t1">
<input name="t2">
</div>
</form>
<script type="text/javascript">
(function () {
var t1 = document.forms.myForm.elements.t1;
var t2 = document.forms.myForm.elements.t2;
var handler = function handler() {
t2.disabled = (t1.value !== "");
};
t1.onchange = handler;
}());
</script>
</body>
</html>
(Although I would use a library such as YUI or jQuery to add event handlers in a fashion that is better protected from overwriting in a crossbrowser compatible way).
You might want some tutorials on JavaScript and the DOM so that this makes sense.

Categories