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

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.

Related

Google Script Run Improve Performance with frontend calls in Apps Script

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

How to loop with setTimeout even after a href redirect?

I try to run a loop method on a web page (Facebook) like this:
function loopRefresh () {
console.log("loop refresh method call");
if (shallRefresh){
setTimeout(function(){
loopRefresh()
//Do something here
}, 5000);
} else {
setTimeout(function(){
loopRefresh()
//Do something different here
}, 5000);
}
}
Now so far so good, everything works and the method is called every 5th second. The problem is that when the user clicks the home button:
the page gets reloaded because of the anchor tag and the href, even though it does not refer to a new page.
This breaks the loop.
I already added this function as onclick event to the Home Button:
function homeRefresh() {
shallRefresh = true;
//setTimeout(function(){
// Do the same thing here as in the true case in the loopRefresh method
// }, 2000);
}
I originally just wanted to make the setTimeout call in here, so that the callback function gets executed after the user clicked the button, without the loopRefresh method. But I thought I could solve the problem by passing the variable, which also doesn't work.
Use event.preventDefault() on function starting. like,
function homeRefresh() {
event.preventDefault(); // here..
shallRefresh = true;
//setTimeout(function(){
// Do the same thing here as in the true case in the loopRefresh method
// }, 2000);
}
event.preventDefault() will prevent the default action of the hyperlink.
NOTE: You cannot continue a javascript function after a page reload.

Qualtrics, Javascript: how to implement a pause and show previously hidden choices?

I design a new survey and in one of my multiple choice questions, the alternatives are supposed to be hidden for 1 sec and so that the user is inclined to spend more attention to the question before answering.
So far my code is only able to hide the choices but waiting and showing is still missing. (code below)
Thanks for any help or suggestions on how to solve this issue.
Qualtrics.SurveyEngine.addOnload(function () {
this.hideNextButton();
this.hideChoices();
//HERE I WOULD LIKE THE CODE TO WAIT FOR 1 sec
//HERE I WOULD LIKE THE CHOICES TO REAPPEAR ON THE SCREEN
this.questionclick = function (event, element) {
if (this.getChoiceValue(4) == true) {
this.clickNextButton();
} else if (this.getChoiceValue(5) == true) {
this.clickNextButton();
}
}
});
There are two parts of the problem here.
How to wait one second? That's done with setTimeout() and a callback function.
How to make sure the callback function works on the right object? - That's done by storing the object we want to work on in a variable.
So, without knowing anything about Qualtrics SurveyEngine, it is clear from your code that this inside the onLoad callback refers to your survey object. We will need that object later in the setTimeout callback, so let's store it. The rest is easy:
Qualtrics.SurveyEngine.addOnload(function () {
var survey = this;
// attach click event handler
self.questionclick = function (event, element) {
// using `survey` instead of `this` would work here, too
if (this.getChoiceValue(4) || this.getChoiceValue(5)) {
this.clickNextButton();
}
};
// hide buttons
survey.hideNextButton();
survey.hideChoices();
// after 1000ms, show buttons again
setTimeout(function () {
survey.showNextButton();
survey.showChoices();
}, 1000);
});

Removing setTimeout on click

I'm getting some odd behavior in a part of my js code.
I have some notifications which appear in a bar on top of the page and then disappear after a certain amount of time. I have used a simple setTimeout() to acheive this.
Sometimes, a notification will appear as a result of a particular url query string when the page loads but then a new one would need to be displayed when the user clicks on a button. I want the old one to disappear and the new one to appear. I'm using a variable to keep a reference to the setTimeout() in order to cancel it. However, when I try to do this I manage to create a loop that eventually crashes my chrome tab.
I have put together a jsfiddle illustrating my problem - http://jsfiddle.net/5Nm4c/
Clicking on show notification while another is visible will crash the browser tab. If you click on it when nothing is shown, it is fine.
Here is my js:
var Notification = {
// close main notification bar
close: function (callback) {
$('#notification-bar').fadeOut(250, function () {
// reset its position and fade it back in so it is ready to go again
$(this).css('top', -100).fadeIn(1);
// check if a callback function has been passed in
if (typeof callback === 'function') {
callback();
}
});
},
// open notification bar with the appropriate css class and message
open: function (message) {
// if the notification bar is already visisble
if (verge.inViewport($('#notification-bar'))) {
// hide and then show it with the new message
window.clearTimeout(Notification.timeout);
Notification.close(Notification.open(message));
return false;
}
$('#notification-bar').html(message);
$('#notification-bar').animate({
'top': 0
}, 250, function () {
Notification.timeout = window.setTimeout(function () { Notification.close() }, 1500);
});
},
timeout: null
}
Notification.open('hello');
$('#button').click(function(e){
e.preventDefault();
Notification.open('link clicked');
});
I'm using https://github.com/ryanve/verge/ as it has some nice methods to check if elements are visible in the viewport.
Could someone please tell me where my error is?
I think the error Uncaught RangeError: Maximum call stack size exceededcomes from jsfiddle itself, so I am not able to test it.
I see what you did there:
var Notification = {
open: function (message) {
Notification.close(Notification.open(message)); //Here you create the loop!!
}
}
Another problem I see in your code is, that when Notification.open is called while a animation is running Notification.timeout is not actuell. Try a $('#notification-bar').stop(true, true); to stop the actuell animation befor you call window.clearTimeout(Notification.timeout);. Maybe it would be even better to use $('#notification-bar').stop(true, false);, so the "old" setTimeout will not even be called.

Prevent and queue action (but only once globally) if previously called within X seconds

I always run into this problem and seem to implement a nasty looking solution.
It seems like a common design pattern to fire an action immediately, but not let that action queue up if clicked rapidly / delay firing if previously called within a timeframe. In my real world example, I have an AJAX call being made, so if I don't prevent repetitive actions the browser queues requests.
How would you implement this differently? What other options are there?
function myFunction() {
console.log("fired");
}
var timeout = null;
$("#foo").click(function() {
// if not previously clicked within 1 second, fire immediately
if (!timeout) {
myFunction();
timeout = setTimeout(function() {
timeout = null;
}, 1000);
} else {
// clicked again within 1s
clearTimeout(timeout); // clear it - we can't have multiple timeouts
timeout = setTimeout(function() {
myFunction();
timeout = null;
}, 1000);
};
});
With your current code, if you repeatedly click "#foo" at an interval slightly less than one second, say every 800ms, on first click it will fire the function immediately (obviously), but then it will fire the function exactly once more one second after the last click. That is, if you click ten times at 800ms intervals the function will fire once immediately and a second time approximately 8 seconds (800ms * 9 + 1000ms) after the first click.
I think you're better off removing the else case altogether, so that on click it will fire the function if it has not been called within the last second, otherwise it will do nothing with no attempt to queue another call up for later. Not only does that seem to me like a more logical way to operate, it halves the size of your function...
On the other hand, since you mentioned Ajax, rather than disabling the function based on a timer you may like to disable the function until the last Ajax request returns, i.e., use a flag similar to your timerid and reset it within an Ajax complete callback (noting that Ajax complete callbacks get called after success or failure of the request).
In the case of an auto-complete or auto-search function, where you want to send an Ajax request as the user types, you might want to remove the if case from your existing code and keep the else case, because for auto-complete you likely want to wait until after the user stops typing before sending the request - for that purpose I'd probably go with a shorter delay though, say 400 or 500ms.
Regarding general structure of the code, if I wanted a function to be fired a maximum of once per second I'd likely put that control into the function itself rather than in a click handler:
var myFunction = function() {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid=null; }, 1000);
// actual work of the function to be done here
console.log("myFunction fired");
};
}();
$("#foo").click(function() {
myFunction();
});
The immediately invoked anonymous function that I've added makes it uglier, but it keeps the timerid variable out of the global scope. If you don't like that obviously you could simply declare timerid in the same scope as myFunction() as you currently do.
This answer is getting kind of long, but if you have a lot of different functions that all need some kind of repeat control in them you could implement a single function to handle that part of it:
function limitRepeats(fn, delay) {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid = null; }, delay);
fn();
};
}
// myFunction1 can only be called once every 1000ms
var myFunction1 = limitRepeats(function() {
console.log("fired myFunction1()");
}, 1000);
// myFunction2 can only be called once every 3000ms
var myFunction2 = limitRepeats(function() {
console.log("fired myFunction2()");
}, 3000);
$("#foo").click(function() {
myFunction1();
myFunction2();
});

Categories