i was hoping somebody could help me understand where i am going wrong, i have a basic bit of JavaScript; when a user clicks a button the inner HTML of a div is replaced to loading Gif while the rest of the functions calculate a final value and then the loading gif should be replaced with the final value. However what actually happens is the entire process is completed before any changes are made, so the loading gif is set and replaced with the final value instantly at the end. It works fine in firefox but not in chrome or IE. I need the loading giff to be set immediately so the user can see the value is being calculated as it can take some time.
const loadingGif = "path to giff";
$('#btn1').click(function() {
buttonClick()
});
function buttonClick(){
setLoadingGiff("buttonValue")
};
async function setLoadingGiff(range){
$('#divID').html(loadingGif);
var result = await calcFunction(range);
}
function calcFunction(){
//Do calculations ....
//finally
const finalCalulcation = "value";
$('#divID').html(finalCalulcation);
}
Related
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
Let's say the standard progression on the page kickstarts with the window load event like this,
window.addEventListener("load",function(){ doTheseFirst(); }, { once: true });
function doTheseFirst()
{
setTimeout(function(){ andThenDoThese(); }, 5000);
// Maybe show some pictures and play some sounds for 5 seconds
}
function andThenDoThese()
{
// Show other pictures and play other sounds until user clicks OK
elementOnPageForOK.addEventListener("click", andThenDoTheseTooWhenUserClicksOK);
}
function andThenDoTheseTooWhenUserClicksOK()
{
// etc etc
// You get the idea
}
Let's say this piece of code is used over and over again.
But on certain occasions the user has to see a notification before the execution chain gets going. An easy solution for that is using an alert box since it blocks/pauses the code execution. The following code makes the alert box pop up as soon as fetch() gets the content from the txt file. Note that fetch() and window load are working independently.
fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ alert(contentOfTheTxtFile); });
This works because when the alert box is closed, the main code goes back to its normal flow just as needed.
But how do I do the same thing without using a crude alert box that I cannot assign any styles to?
Can I make it so that the code execution is paused as soon as fetch() gets the file and then it is unpaused immediately once the user clicks something like an OK or NEXT button?
Is this possible with async/await?
EDIT1: Further clarification
Please remember that it is uncertain whether fetch() or window load will fire the completion signal first. Because download speed is totally unpredictable.
So if we set them both like this, we don't know just how many milliseconds would pass for the other to catch up.
if(thisTimeItIsASpecialCase){
fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ /*What to do when the file is ready*/ });
}
window.addEventListener("load",function(){ doTheseFirst(); }, { once: true });
A simplified way to achieve the desired result: Put doTheseFirst() function on hold and make it wait for fetch() and let it finish its task of getting the file if fetch() is indeed trying to get a file. Also let doTheseFirst() go ahead and run in case there is no fetching to do.
You can do something like the following
You define some html where you want to display your content and hide it until your content is really available
You define a button to hide this element again
The next eventhandler is just there to call a Promise resolve callback which will be defined when needed
When your text is available to add it to your display and create await a new Promise which is resolved upon the click of the next button
The next handler in the promise chain hide the display again.
This code is of course very rudimentary and schematic. For real use you would encapsulate this in a own class or module. If you are using some framework like React, Angular, ... there are tons of components ready to use, which provide exactly this functionalty. For instance as modal dialog or whatsoever.
var resolver = undefined;
function next() {
if (resolver) resolver.call();
}
function getText() {
fetch("https://baconipsum.com/api/?type=meat-and-filler¶s=3&format=text")
.then(response => response.text())
.then(text => {
document.getElementById("thetext").innerText = text;
document.getElementById("display").hidden = false;
return new Promise(resolve => {
resolver = resolve;
});
})
.then(() => {
document.getElementById("display").hidden = true;
resolver = undefined;
})
.catch(e => console.log(e));
}
<input type="button" onclick="getText()" value="Get Text">
<div id="display" hidden>
<div id="thetext"></div>
<input type="button" onclick="next()" value="OK">
</div>
You can show the content of the text file, which you received by fetch, in a nice <div> or in any element in the fetch and you exit the function.
You add an EventListener to a button upon which when user clicks you can move to next action.
This is my demo code:
while (true) {
// Part one: Execute this code
input = prompt("Enter input"); // Now wait for user input
// Part two: Now execute this after getting user input
}
But, it is not working like I shown in the comments. It first asks the user for input, then loads part one and part two on the screen.
While executing JavaScript, browsers accumulate all changes to the DOM before doing a re-render. The prompt dialog delays completion of the JavaScript thread and delays the re-render operation.
To allow the browser to render accumulated changes, put the prompt and any subsequent operations in a setTimeout function.
document.write("I Want to Display this before Prompt");
$("div").addClass("blue")
setTimeout(function() {
input = prompt("test");
//PUT more code here
});
The setTimeout will hold its code block for the next render tick of the browser.
The problem
I am having is the following code will not update the tow select boxes "select1" and "select2", unless I have an alert immediately preceding the code.
Background info -
I am posting two variables to this page, "sel1val" and "sel2val", and I am assigning them to hidden input boxes with id's "sel1valtry" and "sel2valtry" respectively. I am assigning the values of these input boxes to the variables in the function and have named them "sel1val" and "sel2val". I can post more code if need be, but it is what I call "franken code"...haha! because I am a novice, it is assembled from many different styles of code.
Objective - what I am trying to achieve is to set two select boxes based upon the value of "sel1val" and "sel2val". The correct functionality is only obtained when I have an alert prior to the code.
Methods I have tried - I have left in some commented out code, to illustrate my attempts. I have been through many forums and that is where I got these ideas from. I suspect that the alert "reloads" the javascript part of the page, but I have no real basis for this. I have tried "document ready", variations of "window load", and even tried slowing things down with a delay, in case it was a timing issue. Each of the methods I have tried have resulted in the same outcome, whereby it only works with an alert.
I have achieved much success with my web based projects, and this would not have been possible without the invaluable resource in forums such as this one. I would like to thank anyone that has ever provided input/solutions, as without this I would never have been able to progress.
$(document).ready(function(){
// $(document).ajaxSuccess(function(){
// alert("AJAX request successfully completed");
// });
//$(window).load(function()
//window.onload = function()
//$(function ()
//Code goes here
//alert("got here");
//{
var sel1val = $("#sel1valtry").val()
var sel2val = $("#sel2valtry").val()
if (sel2val)
{
//alert("will not work without this alert"+sel1val);
//$("#select1").delay(2000).val(sel1val);
//$("#select1").val(sel1val);
$("#select1").prop('value',sel1val);
// var element = document.getElementById('select1');
// element.value = sel1val;
dochange('selection2', sel1val)
//var element = document.getElementById('select2');
// element.value = sel2val;
alert("will not work without this alert"+sel2val);
$("#select2").val(sel2val);
}
//}
});
//}
It seems like the dochange function is using some asynchronous API (like an AJAX call for example) which is setting value to the sel2val variable in its success callback. But since AJAX is asynchronous, this function returns immediately, not waiting for the server to return a response. By putting an alert you are blocking the execution of the next line of code and leaving enough time for the AJAX call to complete and assign a value to the sel2val variable.
The proper way to fix your code is to provide a callback to this function where you will perform the necessary actions:
dochange('selection2', sel1val, function(result) {
$("#select2").val(result);
});
And in your dochange function you will invoke this callback in the success event of your AJAX call.
I am trying to load a 'loading' message to the user before a time-intensive function is called in javascript.
HTML:
<p id='foo'>Foo</p>
Javascript:
var foo = document.getElementById('foo');
function tellViewerLoading() {
// Tell the user that loading is occuring.
foo.innerHTML = 'loading...';
}
function someActionThatTakesALongTime() {
// Do some action that takes a long time.
var i = 0;
while(i < 100000) {i++; console.log(i);};
}
function domUpdateDelayExperiment() {
tellViewerLoading();
someActionThatTakesALongTime();
}
domUpdateDelayExperiment();
JSFiddle: http://jsfiddle.net/johnhoffman/xDRVF/
What I want to happen is for the DOM to be updated immediately after tellViewerLoading() is called.
Instead, what happens is that the DOM seems to be updated after someActionThatTakesALongTime() finishes running. At that point, it is useless to display a loading message.
How do I tell javascript to immediately update the DOM after tellViewerLoading() is called?
Spawn the long-time running function with setTimeout:
function domUpdateDelayExperiment() {
tellViewerLoading();
setTimeout(someActionThatTakesALongTime, 50);
}
Explanation: the tellViewerLoading() function updates the DOM but the browser won't reflect changes on screen until domUpdateDelayExperiment() returns. By delaying someActionThatTakesALongTime by means of setTimeout() we let the browser to reflect DOM changes. The timeout is arbitrary, but its minimum value may be important in some browsers. A value of 50 ms is fair enough.
Actually, if you step through your code using a debugger, you will see that the loading text is changed before the next function is called.
Your browser is just hanging at the long function call, so it can't change the displayed text.
Using a short timeout can help if you want your browser to have enough time to change the display before going to the next function.