My Google Scripts function keeps running even after returning the next function.
To summarise, I have a function (nightTargetSelection ) that calls another function (dayStart). And as soon as the second function gets called, I want the first one to stop running.
I have stripped down the function and removed everything that should not be relevant for this post. But please let me know if you need more information.
Firstly, I call a user-input form via the following code in GS:
function roleWakeUp (roleName, j){
var roleStrAppend = "<div id='id_role_name' style='display:none;'>" + roleName + "</div>";
var jAppend = "<div id='id_j' style='display:none;'>" + j + "</div>";
var actionInputDlg = HtmlService.createHtmlOutputFromFile('night')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(600)
.setHeight(425);
var wakeUpText = roleName + " wakes up."
actionInputDlg.append(roleStrAppend).append(jAppend);
SpreadsheetApp.getUi().showModalDialog(actionInputDlg, wakeUpText);
}
Then I have the form submitted by a user via HTML/JS:
<script>
selected = select.options[select.selectedIndex].value
google.script.run.withSuccessHandler(selected).nightTargetSelection(selected);
google.script.host.close();
</script>
Back in GS, I have the nightTargetSelection (the problematic function that I need helped getting fixed)
function nightTargetSelection (selected){
return dayStart();
}
Finally I have the dayStart function that is called within nightTargetSelection.
function dayStart() {
Browser.msgBox("DAY START");
//If I do not close the msgBox within 6 minutes (GS timeout period), then nightTargetSelection times out.
roleWakeUp (roleName, j);
}
I am expecting that the nightTargetSelection function stops running as soon as dayStart is called.
Unfortunately that is not happening and the nightTargetSelection function only completes when the next nightTargetSelection gets called.
As you can see, dayStart function calls roleWakeUp , making it a loop between functions. I have omitted including the loop details into this question because I dont believe it is relevant. Let me know if it is relevant and I will provide more details.
I don't know if this is your problem but in this:
<script>
var selected=select.options[select.selectedIndex].value;
google.script.run
.withSuccessHandler(selected)//select is supposed to be a function name
.nightTargetSelection(selected);
google.script.host.close();
</script>
.withSuccessHandler(functionname or anonymous function)
Yes, because nightTargetSelection() will not terminate until all of the processes it called have finished or it times out.
Looks like the end of the cycle is Browser.msgBox("DAY START");. So allow your server code to finish execution and then use .withSuccessHandler() to call roleWakeUp() via the client-side script.
(I don't really know what your code is doing. nightTargetSelection() seems to be unnecessary given the information you provided. So the example below is an abstraction to show you the flow.)
Client-Side Script
<script>
function yourOriginal() {
selected = select.options[select.selectedIndex].value;
google.script.run
.withSuccessHandler(restartCycle) // Calls restartCycle() after nightTargetSelection() finishes
.nightTargetSelection(selected);
google.script.host.close();
}
function restartCycle(values) {
google.script.run.roleWakeUp(values.roleName, values.j);
}
</script>
Server-Side Scripts
function roleWakeUp(roleName, j) {
// opens the dialog
}
function nightTargetSelection(selected) {
return dayStart();
}
function dayStart() {
Browser.msgBox("DAY START");
return {"roleName": roleName, "j": j}; // Pass these values to client-side script
}
Related
My script takes CSV input and from that finds a user's name. It then creates a URL given the user's name.
From there, the script opens the user's URL, collects some data about the user, and puts that info into an array for later output.
My problem is with the window.document.addEventListener. the specific code line is as follows:
element.document.addEventListener("DOMContentLoaded",getSomething(),false);
The strange behavior is as follows:
With the statement above, the listener fires and getSomething() code begins execution. However, the page is not loaded. in the console I can see that the page contents are simply nothing more than an empty body.
Changing "getSomething()" to "getSomething" (in the addEventListener code line) causes the pages to eventually load, however, the getSomething function is never executed (apparently addEventListener did not fire.)
some introduction to the code that follows:
variable testURLs is an array containing a user's URL.
function controlOpenWindows() is not fully set up but its intent is to determine when a window is ready to close, and when data from as many as four opened windows is collected, all four will close and four more will open. four is arbitrary. there are over 900 user URLs so just limiting number open at any one time.
The function that closes the previously opened windows makes the call to open more windows.
Please note that you would need a login id and password to open specific user pages. so passing the URL to you in this post would not be helpful. I'm hoping you can help without that specific info.
function closeOpenedWindow(index){
switch (index) {
case 0:
blnZero=true;
break;
case 1:
blnOne=true;
break;
case 2:
blnTwo=true;
break;
case 3:
blnThree=true;
}
if (blnZero===true && blnOne===true && blnTwo===true && blnThree===true) {
for (p=0; p<4; p++) {
openedWindow[p].close();
count +=1;
controlOpenWindows();
}
}
}
function controlOpenWindows() {
debugger;
testURLs=[];
blnZero=false;
blnOne=true;
blnTwo=true;
blnThree=true;
if (editorProfileURL.length>=4) {
testURLs[0]= editorProfileURL.shift();
testURLs[1] =editorProfileURL.shift();
testURLs[2]=editorProfileURL.shift();
testURLs[3]=editorProfileURL.shift();
} else {
for (n=0; n<editorProfileURL.length; n++) {
testURLs[n]=editorProfileURL[n];
}
}
testURLs.forEach(openWindow);
}
controlOpenWindows();
function openWindow(element1, index1, array1) {
openedWindow[index1]= window.open(element1);
}
function loaded(element, index, array) {
element.document.addEventListener("DOMContentLoaded", getSomething(), false);
}
openedWindow.forEach(loaded);
function getSomething() {
debugger;
var whichPage=this.document.URL;
function whichIndex(element, index, array) {
if (element.document.URL==whichPage) {
return element.document.URL;
}
}
var foundIndex=openedWindow.findIndex(whichIndex);
var reg=/The page you were looking for doesn*/g;
if (openedWindow[0].document.getElementsByClassName("container not-found").length>0) {
if(openedWindow[foundIndex].document.getElementsByClassName("container not-found")[foundIndex].innerHTML.match(reg)) {
closeOpenedWindow(foundIndex);
}
} else {
var firstEdit=openedWindow[foundIndex].document.getElementsByClassName("user-last-edit")[0].innerHTML;
var lastEditDaysAgo=openedWindow[foundIndex].document.getElementsByClassName("transaction-header-time")[0].innerHTML;
var rank=openedWindow[foundIndex].doucment.getElementsByClassName("user-rank")[0].innerHTML;
var editCount=openedWindow[foundIndex].document.getElementsByClassName("user-stats-value")[1].innerHTML;
updatedEditorInfo.push();
updatedEditorInfo.push(firstEdit + ",");
updatedEditorInfo.push(lastEditDaysAgo+ ",");
updatedEdtiorInfo.push(rank + ",");
updatedEditorInfo.push(editoCount + ",");
updatedEditorInfo.push("\n");
console.log(updatedEditorInfo);
alert(updatedEditorInfo);
//closeOpenedWindow();
//controlOpenWindows();
}
closeOpenedWindow(foundIndex);
controlOpenWindows();
}
Thanks for taking a look.
You need to pass a reference to the function instead of calling it directly:
element.document.addEventListener("DOMContentLoaded",getSomething,false);
Situation:
Example Spreadsheet
Sheet: Support
Column: H has the following function "=IF(D:D>0;IF($B$1>=$G:G;"Call";"In Time");" ")" that changes the value depending on the result.
Problem:
I need to:
Play a sound when a cell in column H changes to "Call" on the sheet "Support".
This function will need to run every 5min.
Does the sound need to be uploaded to Drive or can I use a sound from a URL?
I will appreciate to anyone can help on it... I see a lot of code but I didn't understand very well.
This is a pretty tough problem, but it can be done with a sidebar that periodically polls the H column for changes.
Code.gs
// creates a custom menu when the spreadsheet is opened
function onOpen() {
var ui = SpreadsheetApp.getUi()
.createMenu('Call App')
.addItem('Open Call Notifier', 'openCallNotifier')
.addToUi();
// you could also open the call notifier sidebar when the spreadsheet opens
// if you find that more convenient
// openCallNotifier();
}
// opens the sidebar app
function openCallNotifier() {
// get the html from the file called "Page.html"
var html = HtmlService.createHtmlOutputFromFile('Page')
.setTitle("Call Notifier");
// open the sidebar
SpreadsheetApp.getUi()
.showSidebar(html);
}
// returns a list of values in column H
function getColumnH() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Support");
// get the values in column H and turn the rows into a single values
return sheet.getRange(1, 8, sheet.getLastRow(), 1).getValues().map(function (row) { return row[0]; });
}
Page.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<p id="message">Checking for calls...</p>
<audio id="call">
<source src="||a URL is best here||" type="audio/mp3">
Your browser does not support the audio element.
</audio>
<script>
var lastTime = []; // store the last result to track changes
function checkCalls() {
// This calls the "getColumnH" function on the server
// Then it waits for the results
// When it gets the results back from the server,
// it calls the callback function passed into withSuccessHandler
google.script.run.withSuccessHandler(function (columnH) {
for (var i = 0; i < columnH.length; i++) {
// if there's a difference and it's a call, notify the user
if (lastTime[i] !== columnH[i] && columnH[i] === "Call") {
notify();
}
}
// store results for next time
lastTime = columnH;
console.log(lastTime);
// poll again in x miliseconds
var x = 1000; // 1 second
window.setTimeout(checkCalls, x);
}).getColumnH();
}
function notify() {
document.getElementById("call").play();
}
window.onload = function () {
checkCalls();
}
</script>
</body>
</html>
Some sources to help:
Sidebars and Dialogs
Custom Menus
Simple Trigger - onOpen
`google.script.run.withSuccessHandler(callback).customFunction()
Array.prototype.map
Recursively calling checkCalls() eventually led to errors, when I implemented the main answer given (which is mostly correct and really useful, so thank you!).
// Note: But the original implementation would work fine for a while - say 90 minutes - then crash. The call that would normally take 1 second would take 300 seconds, and Execution would Halt. It looks like it blew the stack by keeping on recursively calling itself. When moved to a single call of check() with proper exiting of the function, it then worked.
The console log in Chrome on running the JavaScript, said this:
ERR_QUIC_PROTOCOL_ERROR.QUIC_TOO_MANY_RTOS 200
After much investigation, I worked out a better way of doing it... Which doesn't require recursion (and therefore won't blow the stack).
Remove this line:
// window.setTimeout(checkCalls, 500);
And use something like this - at the end of your script:
// This function returns a Promise that resolves after "ms" Milliseconds
// The current best practice is to create a Promise...
function timer(ms) {
return new Promise(res => setTimeout(res, ms));
}
async function loopthis () { // We need to wrap the loop into an async function for the await call (to the Promise) to work. [From web: "An async function is a function declared with the async keyword. Async functions are instances of the AsyncFunction constructor, and the await keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains."]
for (var i = 0; i >= 0; i++) {
console.log('Number of times function has been run: ' + i);
checkCalls();
await timer(3000);
}
}
window.onload = function () {
loopthis();
}
</script>
I am just getting started with coding for FirefoxOS and am trying to get a list of files in a directory.
The idea is to find the name of each file and add it to the array (which works), but I want to return the populated array and this is where I come unstuck. It seems that the array gets populated during the function (as I can get it to spit out file names from it) but when I want to return it to another function it appears to be empty?
Here is the function in question:
function getImageFromDevice (){
var imageHolder = new Array();
var pics = navigator.getDeviceStorage('pictures');
// Let's browse all the images available
var cursor = pics.enumerate();
var imageList = new Array();
var count = 0;
cursor.onsuccess = function () {
var file = this.result;
console.log("File found: " + file.name);
count = count +1;
// Once we found a file we check if there are other results
if (!this.done) {
imageHolder[count] = file.name;
// Then we move to the next result, which call the cursor
// success with the next file as result.
this.continue();
}
console.log("file in array: "+ imageHolder[count]);
// this shows the filename
}
cursor.onerror = function () {
console.warn("No file found: " + this.error);
}
return imageHolder;
}
Thanks for your help!
Enumerating over pictures is an asynchronous call. Essentially what is happening in your code is this:
You are initiating an empty array
You are are telling firefox os to look for pictures on the device
Then in cursor.onsuccess you are telling firefox os to append to the array you have created WHEN it gets back the file. The important thing here is that this does not happen right away, it happens at some point in the future.
Then you are returning the empty array you have created. It's empty because the onsuccess function hasn't actually happened.
After some point in time the onsuccess function will be called. One way to wait until the array is full populated would be to add in a check after:
if (!this.done) {
imageHolder[count] = file.name;
this.continue();
}
else {
//do something with the fully populated array
}
But then of course your code has to go inside the getImageFromDevice function. You can also pass a callback function into the getImageFromDevice function.
See Getting a better understanding of callback functions in JavaScript
The problem is with the aSynchronous nature of the calls you are using.
You are returning (and probably using) the value of imageHolder when it's still empty - as calls to the "onsuccess" function are deferred calls, they happen later in time, whereas your function returns immediately, with the (yet empty) imageHolder value.
You should be doing in this case something along those lines:
function getImageFromDevice (callback){
...
cursor.onsuccess = function () {
...
if (!this.done) {
// next picture
imageHolder[count] = file.name;
this.continue();
} else {
// no more pictures, return with the results
console.log("operation finished:");
callback(imageHolder);
}
}
}
Or use Promises in your code to accomplish the same.
Use the above by e.g.:
getImageFromDevice(function(result) {
console.log(result.length+" pictures found!");
});
Has anyone found that their javascript doesnt work, but when they step through the code it works fine ?
var cookie = getCookie('lusr');
var username = cookie;
if(!cookie){
$("#UserNav").load("loginform.html");
$("#loginbtn").click( function(){
var username = $("#usernametxt").val();
var password = $("#passwordtxt").val();
login(username,password);
});
}else{
$("#UserNav").load("user.htm");
$("#WelcomeUser").text("Welcome "+ username);
}
My issue occurs on this line :
$("#WelcomeUser").text("Welcome "+ username);
That's because load() is asynchronous: it returns right away, performs its work in the background, then calls a user-provided function when its task is complete. The stepping delay gives you the illusion that the function is synchronous and performs all its work before returning.
Therefore, you should pass a callback function to load() and perform your subsequent work inside that callback:
var cookie = getCookie("lusr");
if(!cookie) {
$("#UserNav").load("loginform.html", function() {
$("#loginbtn").click(function() {
var username = $("#usernametxt").val();
var password = $("#passwordtxt").val();
login(username, password);
});
});
} else {
$("#UserNav").load("user.htm", function() {
$("#WelcomeUser").text("Welcome " + cookie);
});
}
You are using the load() function which asynchronously fetches from the server. This means your form has not loaded by the time you go searching for its fields.
The reason it works when you step through is because it gives it time to load the form while you step.
You can use another version of load which has an asynchonous callback function, allowing you to provide functionality only to be called once the load is complete.
Check the jQuery docs for more info.
Okay, so I have an javascript function that retrieves some HTML...
function updateQuestions(i){
var url = 'getQuestions.php?sys=' + i;
if (receiveReq.readyState == 4 || receiveReq.readyState == 0) {
receiveReq.open("GET", url, true);
receiveReq.onreadystatechange = handleQuestionsUpdate;
receiveReq.send(null);
}
}
function handleQuestionsUpdate() {
if (receiveReq.readyState == 4) {
var a=receiveReq.responseText;
document.getElementById('questions').innerHTML=a;
checkSpeakers(); //Error Occurs Here, even though checkSpeakers() is a function in the returned HTML chunk.
}
}
This HTML is not just HTML, but it is more specifically a form and a chunk of javascript. The javascript is hard-coded into the HTML and not referenced by <script src="..">
Is it normal that this retrieved JS code isn't recognized upon call-time? If so, what is my alternative if I need the JS to change every time the div is update?
This is the text being returned to the javascript function.
function checkPillowSpeakers()
{
var pillowSpeakers = document.getElementById('Q9').value + document.getElementById('Q10').value;
document.getElementById('PS1').style.display = ((pillowSpeakers > 0)? '' : 'none');
document.getElementById('PS2').style.display = ((pillowSpeakers > 0)? '' : 'none');
}~ARRAYSEPERATOR~<html>....</html>
The JS Code is seperated from the HTML code by an ~ARRAYSEPERATOR~ tag. The issue is that I don't want to EXECUTE the code at this time, I just want it queued so I can call it on command.
You should first try to get the JavaScript part from the HTML content.
Then you can easily execute it using eval() function from JavaScript;
My answer from How To Call Javascript In Ajax Response? IE: Close a form div upon success
// response is the data returned from the server
var response = "html\<script type=\"text/javascript\">alert(\"foo\");<\/script>html";
var reScript = /\<script.*?>(.*)<\/script>/mg;
response = response.replace(reScript, function(m,m1) {
var fn = new Function(m1); // this will make the eval run in the global scope
fn(); //will run alert("foo");
return "";
});
alert(response); // will alert "htmlhtml"
After doing a whole lot of research, it seems Eval() has some memory issues... so I actually came across this solution:
//Firstly, create a div called codeHolder
var javascriptCode="function test(){.....}";
var JSONCode=document.createElement("script");
JSONCode.setAttribute("type","text/javascript");
JSONCode.text=javascriptCode;
var cell=document.getElementById("codeHolder");
if ( cell.hasChildNodes() )
while ( cell.childNodes.length >= 1 )
cell.removeChild( cell.firstChild );
cell.appendChild(JSONCode);
you should realy think of using an js-lib for ajax for browser-compatibilty and less memory leaks - but if you want to do this by yourself, you have to eval() the passed back javascript before you can use it.
There is also responseXML:
receiveReq.responseXML.getElementsByTagName('input')
etc., etc.