Google Script Run Improve Performance with frontend calls in Apps Script - javascript

I'm struggling with Apps Script's google.script.run response time. There is a function that returns user permission as boolean: initFunction() { // function code } returns true.
Some divs in my frontend are showing based on the initFunction boolean, and I don't know why the callback time is soo slow (like 4-5 seconds)
INDEX.HTML
<script>
function check_permission () {
google.script.run.withSuccessHandler(function(data){
if (data === true) {
document.getElementById('hidden_div').style.display = 'block';
} else {
document.getElementById('hidden_div').style.display = 'none';
}
}).initFunction();
}
document.addEventListener('DOMContentLoaded', () => { check_permission() });
</script>
I've tried calling initFunction just after sidebar load function just to check the function time and it returns true in 0.5 seconds, so it's not about the function, i suppose it's about google.script.run
function sidebarLoad() {
let route = HtmlService.createTemplateFromFile('index').evaluate();
SpreadsheetApp.getUi().showSidebar(route);
let permission = initFunction(); SpreadsheetApp.getUi().alert(permission)
}
How could I solve this and reduce response time?
Edits: after reading your comments I still don't know where is the problem but i've been doing tests and:
When calling the function from onclick event, the time response is very fast, so it's not about the function itself.
Answering #TheMaster, my start criteria for time response is when pressing the menu ui button that opens my GAS sidebar. The DOMContentLoaded function triggers immediately, I know because I changed the google.script.run in check_permission function with any other javascript code and it's loaded quicky. So I suppose it's not about DOMContent loading slowly.
If I click a button in the loaded html page that calls the function check_permission() I also get the results immediately. I only get the slow response time when google.script.run is triggered by DOMContentLoaded listenerEvent.

Try using templated html and load user permission as a global into the template before it's rendered.
function myFunction() {
let temp = HtmlService.createTemplateFromFile('filename');
temp.protection = initFunction();
let html = temp.evaluate();
}
Then in the html protection is a global variable and you can distribute it with scriptlets
Pushing variables into templates

Related

How to use async javascript to wait for page load before executing a function on that page

From my index.html page, when a user clicks on a link, I want to execute a function, with following signature: updateFlightPage() on another html page called flight.html
However, the updateFlightPage() function is not executing properly, because I suspect it is trying to access an element on the flight.html page before the page has finished loading. I am very new to JS, but I suspect I need to use async JS to circumvent this problem.
How would I use promises or ASYNC AWAIT to fix this?
Please see my code below:
This function is called on my index.html page:
const updateFlightNum = function(e) {
if (e.target.nodeName === 'A') {
UIController.updateFlightPage(e.target.innerText);
}
}
Here is the udateFlightPage(){} function that is called in previous function:
updateFlightPage: function(flightNum) {
// UISelectors.flightPageFltNum.innerHTML = flightNum;
// CONTINUE HERE....
// THE PROBLEM:
// this function is trying to execute before flight.html is loaded
// therefore the element i am trying to console.log() is returning null
// i need to find a way to use async js to wait for the page to be loaded
// before i can execute below code.
console.log(UISelectors.flightPageFltNum.nodeName);
}
I would appreciate any help.

NW.js have a "loading" icon while processing request

I have an app created with NW?js that works fine.
However, it does a lot of processing (loading, save file, reload it and processing, ...) All of that is done by functions.
While I see that it is doing something (because when I click on the "button" that execute the function, the button remains "clicked" until it is finished), it seems impossible to get a spinner instead.
I have 2 functions that are used to respectively show and hide a spinner, but when I use them at start and end of the function, they are executed almost immediately (normal for show, but not foe hide) and I can't find the reason why
Any idea how I could have a spinner while my function are still processing and hide it when done ?
Thank you
In JavaScript, functions can't "sleep", all the code is executed without any stop, they return immediately. If you want to delay the execution of a function, you need a callback :
var button = document.getElementById("button");
var spinner = document.getElementById("spinner");
button.onclick = function () {
startSpinning();
button.setAttribute("disabled", "disabled");
doSomethingTimeConsumingThen(function () {
button.removeAttribute("disabled");
stopSpinning();
});
}
function doSomethingTimeConsumingThen (onDone) {
setTimeout(onDone, 1000);
}
function startSpinning () {
spinner.innerHTML = "Spinning...";
}
function stopSpinning () {
spinner.innerHTML = "Not spinning.";
}
<button id="button">Do something time consuming</button>
<div id="spinner">Not spinning.</div>
A callback is called when something happens, for example when a time consuming task is done. In the above snippet the callback is :
function () {
button.removeAttribute("disabled");
stopSpinning();
}
There must be something similar in your API.

Unload JS loaded via load() to avoid duplicates?

I'm building a dynamic website that loads all pages inside a "body" div via jquery's load(). The problem is I have a script looped with setInterval inside the loaded PHP page, the reason being I want the script loaded only when that page is displayed. Now I discovered that the scripts keep running even after "leaving" the page (loading something else inside the div without refresh) and if I keep leaving / returning the loops stack up flooding my server with GET requests (from the javascript).
What's a good way to unload all JS once you leave the page? I could do a simple dummy var to not load scripts twice, but I would like to stop the loop after leaving the page because it's causing useless traffic and spouting console errors as elements it's supposed to fill are no longer there.
Sorry if this has already been asked, but it's pretty hard to come up with keywords for this.
1) why don't you try with clearInterval?
2) if you have a general (main) function a( ) { ... } doing something you can just override it with function a() { }; doing nothing
3) if you null the references to something it will be garbage collected
no code provided, so no more I can do to help you
This really sounds like you need to reevaluate your design. Either you need to drop ajax, or you need to not have collisions in you method names.
You can review this link: http://www.javascriptkit.com/javatutors/loadjavascriptcss2.shtml
Which gives information on how to remove the javascript from the DOM. However, modern browsers will leave the code in memory on the browser.
Since you are not dealing with real page loads/unloads I would build a system that simulates an unload event.
var myUnload = (function () {
var queue = [],
myUnload = function () {
queue.forEach(function (unloadFunc) {
undloadFunc();
});
queue = [];
};
myUnload.add = function (unloadFunc) {
queue.push(unloadFunc);
};
return myUnload;
}());
The code that loads the new pages should just run myUnload() before it loads the new page in.
function loadPage(url) {
myUnload();
$('#page').load(url);
}
Any code that is loaded by a page can call myUnload.add() to register a cleanup function that should be run when a new page is loaded.
// some .js file that is loaded by a page
(function () {
var doSomething = function () {
// do something here
},
timer = setInterval(doSomething, 1000);
// register our cleanup callback with unload event system
myUnload.add(function () {
// since all of this code is isolated in an IIFE,
// clearing the timer will remove the last reference to
// doSomething and it will automatically be GCed
// This callback, the timer var and the enclosing IIFE
// will be GCed too when myUnload sets queue back to an empty array.
clearInterval(timer);
});
}());

execute callback after all iframes finish loading

Trying out a small jquery plugin to make the user experience of html input file upload control consistent across browsers. Its basically a form which has an html input control which is nested in a hidden iframe.
The plugin exposes events like beforeUpload, afterUpload etc, and I am able to wire these correctly.
Here is a sample of how I'd invoke the afterUpload event.
$.fn.createUpload = function(config){
//stuff
$(myUploadIframe).load(function(){
var doc = this.contentWindow.document;
var txt = $(doc).text();
//parse txt to get json results
var result = frameWorkParser(txt);
if(result.uploadSuccess){
if(config.afterUpload){
config.afterUpload(sender, result);
}
}
});
//other stuff
};
Here is how I'd consume the plugin:
var config = {
afterUpload: function(el, data){
//set some hidden field
},
//other options
};
$('#btnSelectFile').createUpload(config);
If I've somehow invoked nested form (i.e inside the iframe) submit method, the iframe loads and I am able to successfully call the afterUpload event.
But here is where things get trickier. I am trying to execute some logic after all uploads are completed.
$.doUploads = function (callback) {
$('iframe').each(function () {
if (this.id.indexOf('_uploadContainer') > -1) {
var doc = this.contentWindow.document;
doc.forms[0].submit();
}
});
callback();
};
Here I am iterating all the plugin-created iframes (created for uploading) and invoking the nested form submit method. But this call doesn't wait for the consequent iframe onload event to complete, and the callback executes.
I want the callback to execute post the afterUpload event of my plugin. How to accomplish this?
Set some int variable(say totaliframeloaded=0;) which gets incremented on every iframe load. also give a call to callback function on every iframe load.
In callback check, whether that int variable has length equal to $('iframe').length. This condition will be getting matched when all the iframes gets loaded.
if(`totaliframeloaded==$('iframe').length){//all iframes are loaded
//callback coad
}else{
//do nothing/return
}

The impossible inline Javascript delay/sleep

There is a JavaScript function, of which I have zero control of the code, which calls a function that I wrote. My function uses DOM to generate an iFrame, defines it's src and then appends it to another DOM element. However, before my function returns, and thus allows continued execution of the containing function, it is imperative that the iFrame be fully loaded.
Here are the things that I have tried and why they do not work :
1. The SetTimeout option :
99.999% of the time, this is THE answer. As a matter of fact, in the past decade that I have been mentoring in JavaScript, I have always insisted that code could always be refactored to use this option, and never believed a scenario existed where that was not the case. Well, I finally found one! The problem is that because my function is being called inline, if the very next line is executed before my iFrame finishes loading, it totally neuters my script, and since the moment my script completes, the external script continues. A callback of sorts will not work
2. The "Do nothing" loop :This option you use while(//iFrame is not loaded){//do nothing}. In theory this would not return until the frame is loaded. The problem is that since this hogs all the resources, the iFrame never loads. This trick, although horribly unprofessional, dirty etc. will work when you just need an inline delay, but since I require an external thread to complete, it will not.In FF, after a few seconds, it pauses the script and an alert pops up stating that there is an unresponsive script. While that alert is up, the iFrame is able to load, and then my function is able to return, but having the browser frozen for 10 seconds, and then requiring the user to correctly dismiss an error is a no go.
3. The model dialogue :
I was inspired by the fact that the FF popup allowed the iFrame to load while halting the execution of the function, and thinking about it, I realized that it is because the modal dialogue, is a way of halting execution yet allowing other threads to continue! Brilliant, so I decided to try other modal options. Things like alert() work beautifully! When it pops up, even if only up for 1/10th of a second, the iFrame is able to complete, and all works great. And just in case the 1/10 of a second is not sufficient, I can put the model dialogue in the while loop from solution 2, and it would ensure that the iFrame is loaded in time. Sweet right? Except for the fact that I now have to pop up a very unprofessional dialogue for the user to dismiss in order to run my script. I fought with myself about this cost/benefit of this action, but then I encountered a scenario where my code was called 10 times on a single page! Having to dismiss 10 alerts before acessing a page?! That reminds me of the late 90s script kiddie pages, and is NOT an option.
4. A gazillion other delay script out there:There are about 10 jQuery delay or sleep functions, some of them actually quite cleverly developed, but none worked. A few prototype options, and again, none I found could do it! A dozen or so other libraries and frameworks claimed they had what I needed, but alas they all conspired to give me false hope.
I am convinced that since a built in model dialogue can halt execution, while allowing other threads to continue, there must be some code accessible way to do the same thing with out user input.
The Code is literally thousands upon thousands of lines and is proprietary, so I wrote this little example of the problem for you to work with. It is important to note the ONLY code you are able to change is in the onlyThingYouCanChange function
Test File :
<html>
<head>
</head>
</html>
<body>
<div id='iFrameHolder'></div>
<script type='text/javascript'>
function unChangeableFunction()
{
new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
new_iFrame_body = new_iFrame_doc.body;
if(new_iFrame_body.innerHTML != 'Loaded?')
{
//The world explodes!!!
alert('you just blew up the world! Way to go!');
}
else
{
alert('wow, you did it! Way to go!');
}
}
var iFrameLoaded = false;
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
iFrameLoaded = false;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
while(!iFrameLoaded) //I put the limit on here so you don't
{
//If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
//alert('test'); //This would work if it did not require user interaction!
}
return iframe;
}
unChangeableFunction();
</script>
</body>
blank_frame.html :
<html>
<head>
</head>
<body style='margin:0px'>Loaded?</body>
</html>
HERE IS THE ANSWER I MADE FROM COMBINING IDEAS FROM RESPONDERS! YOU GUYS ROCK!
new source of the function I was allowed to change :
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
iFrameLoaded = false;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
while(!iFrameLoaded) //I put the limit on here so you don't
{
if (window.XMLHttpRequest)
{
AJAX=new XMLHttpRequest();
}
else
{
AJAX=new ActiveXObject("Microsoft.XMLHTTP");
}
if (AJAX)
{
AJAX.open("GET", 'slow_page.php', false);
AJAX.send(null);
}
else
{
alert('something is wrong with AJAX!');
}
//If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
//alert('test'); //This would work if it did not require user interaction!
}
return iframe;
}
slow_page.php :
<?
usleep(100000);//sleep for 1/10th of a second, to allow iFrame time to load without DOSing our own server!
?>
I do want to note that I stated that there was nothing outside of that function that I could change, and adding the php page did violate that "rule" but in may case I was able to do that. If I were not able to do that, I could have called blank_frame.html instead of slow_page.php, and it should have only ever needed to call it once (so 2 times per frame load) assuming that it responded in an identical amount of time as the iFrame load. If for some reason the iFrame load was slower, it might call it 2ce (a total of 3 calls to the server)
Yeah, the fact that javascript is single threaded really bites you here. You can use a synchronous ajax call to a purposefully slow page to emulate a sleep, but you aren't going to get the results you want. Why don't you just make sure that your IFrame is loaded before unchangeable function is called?
NB This is extremely hacky, and I wouldn't use it in any real-world situation. Among other potential issues, given sufficient traffic you could end up DDOSing yourself.
You could create sleep functionality by making non-asynchronous (A)JAX calls. In some older browsers this may freeze everything, but at least it won't require any kind of user response.
while (!iFrameLoaded)
{
if (XMLHTTPRequest) {
var request = new XMLHttpRequest();
} else {
var request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.open('GET', 'anyoldfile.htm', false);
request.send();
// check if the iframe is loaded and set iFrameLoaded
}
What you really need is an event to be fired when the iFrame content has loaded. This is actually really easy because the page inside the iFrame has its own events and it can access scripts on the parent page. You will need to be able to change the contents of the iFrame though.
In your iFrame, you'll need this piece of code
// Use whichever DOMReady function you like, or window.onload would work
window.addEventListener('DOMContentLoaded', function() {
if (parent.window.myFunction) {
parent.window.myFunction();
}
}, false);
Then in your parent page, make a function called "myFunction" and put all the scripts you need to fire in there. This should work every time.
Edit: To get this to work you really need two functions. I'm assuming that's really not an option so we'll hack the one function to contain two functions and call the right part when we need it to.
function onlyThingYouCanChange(stringOrObject) {
function createIFrame(objectToAppendIFrameTo) {
// This comment represents all the code that appends your iFrame
}
function onIFrameReady() {
// This comment represents all the stuff you want to happen when the iFrame is ready
}
// The bones of it
if (stringOrObject === "iFrameLoaded") {
onIFrameReady();
} else {
createIFrame(stringOrObject);
}
}
The script in the iFrame should now be changed to something like this:
// Use whichever DOMReady function you like, or window.onload would work
window.addEventListener('DOMContentLoaded', function() {
if (parent.window.onlyThingYouCanChange) {
parent.window.onlyThingYouCanChange('iFrameLoaded');
}
}, false);
I haven't tested it, but in theory that should do it
A stupefyingly simple ;-} answer using XPCOM:
// Get instance of the XPCOM thread manager.
var threadManager=Components.classes['#mozilla.org/thread-manager;1'].getService(
Components.interfaces.nsIThreadManager);
// Release current thread.
function doThread() {threadManager.currentThread.processNextEvent(false);};
// Event enabled delay, time in ms.
function delay(time) {
var end;
var start=Date.now();
do {
end=Date.now();
doThread();
} while ((end-start) <= time);
}
Works in recent version of Firefox. Sorry no hope for Explorer!
A recursive function might help out in this case. just call the function until a global variable indicates that the frame is loaded
var iFrameStarted = false; //you need two global vars
var iFrameLoaded = false;
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
if (iFrameLoaded=false) // if the frame has loaded then you are done. skip everything and return iframe
{ if (iFrameStarted = false) //otherwise start the frame if it has not been
{
iFrameStarted = true;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
for (i=0;i<10000;i++) {} //slow down execution so you are not recursing yourself to death
onlyThingYouCanChange(objectToAppendIFrameTo); //start the recursion process
}
else //the frame has been started so continue recursion until the frame loaded
{
for (i=0;i<10000;i++) {} //slow down execution so you are not recursing yourself to death
onlyThingYouCanChange(objectToAppendIFrameTo); recursively call your function until the frame is loaded
}
}
return iframe; //you only get here when all the recursions are finished
}
Why can you not modify the base code? For example, it could be fairly simple to change the core function from
function unChangeableFunction()
{
new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
new_iFrame_body = new_iFrame_doc.body;
if(new_iFrame_body.innerHTML != 'Loaded?')
{
//The world explodes!!!
alert('you just blew up the world! Way to go!');
}
else
{
alert('wow, you did it! Way to go!');
}
}
To something like this:
function unChangeableFunction()
{
var new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
new_iFrame.onload = function()
{
new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
new_iFrame_body = new_iFrame_doc.body;
if(new_iFrame_body.innerHTML != 'Loaded?')
{
//The world explodes!!!
alert('you just blew up the world! Way to go!');
}
else
{
alert('wow, you did it! Way to go!');
}
};
}
If that doesn't work for you, how about a transparent modification of the original code? Compile it with Javascript Strands and use the built-in futures support to handle this. Note that Javascript 1.7 also supports continuations, but would require changing the code manually to use them.
Another solution that may not be applicable, depending on how much you have simplified the original code. You could set an onload handler, then throw an error, then call unChangeableFunction in your onload handler:
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
// using global variable func_called
if (!func_called) {
func_called = true;
var iframe=document.createElement('iframe');
iframe.src = 'blank_frame.html';
iframe.id = 'myIframe';
iframe.onload = function() {
unChangeableFunction();
};
objectToAppendIFrameTo.appendChild(iframe);
throw new Error('not an error');
} else {
return document.getElementById('myIframe');
}
}
This function (like unChangeableFunction) will be called twice: once in the first instance, then again when the onload handler is triggered. The two different pathways reflect this.
Again, this is hacky, and a definite abuse of JS's error functionality.
you can use cookie and setTimeout like that:
in blank_frame.html add a script:
<script type="text/javascript">
function deleteCookie(cookie_name)
{
var cookie_date=new Date();
cookie_date.setTime(cookie_date.getTime()-1);
document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
}
function setCookie(name,value,expires,path,domain,secure){
document.cookie=name+"="+escape(value)+((expires)?"; expires="+expires.toGMTString():"")+((path)?"; path="+path:"")+((domain)?"; domain="+domain:"")+((secure)?"; secure":"");
}
window.onload=function(){
setCookie('iframe_loaded','yes',false,'/',false,false);
}
</script>
Basically you're adding a cookie iframe_loaded with value yes.
IMO it's better to remove the cookie as you need to do the same if you'll reload the page.
You can as well set the domain in setCookie function call.
Now in main file we'll use setTimeout with function that will check if the cookie exists, if it does then the function will return iframe like in your code:
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
function get_cookie(cookie_name){
var results = document.cookie.match('(^|;) ?'+cookie_name+'=([^;]*)(;|$)');
return results?unescape(results[2]):null;
}
function deleteCookie(cookie_name){
var cookie_date=new Date();
cookie_date.setTime(cookie_date.getTime()-1);
document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
}
iFrameLoaded = false;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
function checkiframe(){
if(get_cookie('iframe_loaded')=="yes"){
alert('iframe loaded');
deleteCookie('iframe_loaded');
return iframe;
}else{
setTimeout(checkiframe,1000);
}
}
checkiframe();
}
As a failsafe cookie is being deleted in this file as well.
Hopefully that will give you something to work with :)
Cheers
G.

Categories