How do I tell javascript to immediately update the DOM? - javascript

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.

Related

How to change instanly a DOM element?

I know that this might be a stupid question but it drives me crazy. I'm trying to change the innerHTML of a DOM element but it doesn't change until the end of the function's execution. For example:
function test(){
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; i++)
{
testEl.innerHTML = 'Count: ' + i;
alert(i);
}
}
Even if I have put an alert in the loop, the text of the element will not change until the end of the function's execution. How can the change be applied instantly (for example I mean during the loop)?
You can update the number every period of time using setInterval:
function test(){
let testEl = document.getElementById('testEl');
let i = 0;
const interval = setInterval(function(){
testEl.innerHTML = `Count: ${i++}`;
if(i === 5)
clearInterval(interval);
}, 1000);
}
test();
<p id="testEl"></p>
JavaScript runs in a single-threaded environment. This means that only one execution context can ever be running at any single point in time. Asynchronous code executes outside of the JavaScript runtime environment (in this case by the browser's native processing) and only when the JavaScript thread is idle can the results of an asynchronous request be executed (i.e. callbacks).
Below is an example that updates a DOM element approximately every second, creating a clock. However, if you click the button, it will ask the browser to render an alert, which is handled outside of the JavaScript runtime and is a blocking UI element, so the clock will stop. Once you clear the alert, you will see the time jump to be roughly current.
As you'll see, the asynchronous API call to window.setInterval() allows for the function to run repeatedly, every so often, and therefore not continuously. This replaces the need for a loop that runs in its entirety every time its accessed. Because of this, you can see updates to the webpage instead of the last value of your loop.
See the comments for more details:
const clock = document.querySelector("span");
// setInterval is not JavaScript. It's a call to a browser
// API asking the JS runtime to run the supplied function every
// 900 milliseconds, but that's just a request. After 900
// milliseconds, the browser will place the function on the
// JavaScript event queue and only when the JavaScript thread
// is idle will anything on the queue be executed. This is why
// the 900 milliseconds is not a guarantee - - it's just the
// minimum amount of time you'll have to wait for the function
// to run, but it could be longer if what's already running
// on the JavaScript thread takes longer than 900 milliseconds
// to complete.
window.setInterval(function(){
// Update the DOM
clock.textContent = new Date().toLocaleTimeString();
}, 900);
document.querySelector("button").addEventListener("click", function(){
// An alert is also not JavaScript, but another browser API that is executed
// by the browser, not JavaScript. However, it is a blocking (modal) UI element.
// The rest of the browser interface (including the web page) cannot update
// while the alert is present. As soon as the alert is cleared, the UI will update.
window.alert("I'm a UI blocking construct rendered by the browser, not JavaScript");
});
<div>Current time is: <span></span></div>
<button>Click for alert</button>
Another way to achieve it is by using async - Promise like this
async function test() {
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; ++i) {
testEl.innerHTML = 'Count: ' + i;
await new Promise((resolve, _) => {
setTimeout(function() {
resolve("");
}, 1000 /* your preferred delay here */);
});
}
}

Using setTimeout to ask user if they want to wait or quit, but callback never fires

UPDATE
Solved, sort of. The problem is the single-threaded nature of Javascript and my misunderstanding of its event model. The loop doesn't get interrupted by the timeout.
So, the solution is that instead of using a callback, I just grab a timestamp before starting the loop, and then each time through the loop compare against it. If more than X seconds have passed, I fire the confirm dialog:
let start = new Date();
...
while ( keepGoing && matchNotFound )
{
...
let end = new Date();
if ( end - start > 10000 )
{
keepGoing = confirm( "This run seems to be taking a while - keep going?" );
start = end;
}
}
Probably not the most efficient or idiomatic solution, but it works for my purposes.
ORIGINAL
I'm trying to call a function through setTimeout, but the callback never fires no matter what time interval I use. I don't know if the problem is in how I've set up the call, or if my understanding of how to use it is completely wrong.
I'm in the early stages of teaching myself Javascript, and have written a version of Dawkins' Weasel program as an exercise. Depending on the parameters the algorithm can take a long time to run, so my thought was to use setTimeout to call a function asking the user if they want to continue and give them the option to bail, sort of like so:
var keepGoing = true; // flag to continue main loop
var timeoutId; // stores result of setTimeout
...
/**
* Asks the user if they want to continue. If they do, reschedule this function to
* execute again after 5 seconds.
*/
function alertFunc()
{
keepGoing = confirm( "This is taking a while - do you want to continue?" );
if ( keepGoing )
{
timeoutId = setTimeout( alertFunc, 5000 );
}
}
/**
* Simulation loop
*/
function doWeasel()
{
/** simulation setup */
timeoutId = setTimeout( alertFunc, 5000 );
/** Loop until we find a match or the user gets bored */
while ( keepGoing && matchNotFound )
{
/** do the thing */
}
clearTimeout( timeoutId );
/** some cleanup */
}
That's the basic idea, anyway. The doWeasel function is tied to a button click in a form in the HTML document:
<html>
<head>
<!-- header stuff -->
</head>
<body>
<!-- long-winded explanation -->
<div class="formarea">
<form class="inputForm">
<!-- form stuff -->
<input type="button" value="OK" onclick="doWeasel()"/>
<input type="reset"/>
</form>
</div>
<div id="outputarea">
<h2>Output from the thing</h2>
<script type="text/javascript" src="weasel.js"></script> <!-- source for doWeasel() -->
</div>
</body>
</html>
The problem I'm having is that alertFunc never appears to fire - I've added console logging to write a message when the function is entered, and it never appears. I've run through the debugger in Chrome, with a breakpoint set in the callback, and it's never reached.
Obviously I'm doing something wrong either with the setTimeout call itself or how I'm trying to use it, but based on the documentation I've read so far I don't see what it could be. Is my concept of how to use it just plain wrong?
I'm an old C and C++ programmer who's only dabbled very lightly in HTML and scripting for the Web, so it's possible I just don't understand the right way to do things here.
EDIT
To address some of the comments, this is a working page - when I click OK the simulation runs and dynamically builds a table for output. It’s just that there are combinations of input parameters that cause it to run for an excessively long time (until the browser throws up a "this page is unresponsive" or I just close the tab).
I can trace the execution of the script in the debugger in Chrome, and it calls setTimeout and assigns a value to timerId, but the callback never actually executes. For the default parameters, the simulation runs to completion and builds a table on the page with the results.
Maybe this isn’t the right way to address the issue - maybe I need to add another button that calls a different function setting keepGoing to false.
EDIT2
I wrote up a quick and dirty prototype to just test the timeout functionality, and I think I know what my problem is. If I'm not in the middle of a loop, the callback executes as expected, but if I have a loop spinning it won't. So, I need to do some reading up on how to deal with asynchronous event handling.
If you're sure the while loop isn't preventing the next line from running, it's possible the script hasn't loaded yet and the function hasn't been defined by the time you assign the onclick attribute to your <input> element, try setting the defer tag to your script element to ensure the script is executed after the document has been parsed. Calling the function externally works fine otherwise.

jquery setTimeout too much recursion

I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.

jquery ready function not executing

I have some javascript placed inside of the jquery $(document).ready function. It searches for an anchor in the url, and then runs a separate function to display matching content.
The code executes if I place an alert inside of the if statement somewhere, but wont' run otherwise. I've stored all the anchor names in an array called 'anchorNameList', and am checking to see if the anchor in the URL exists.
I only want the function to run on the initial pageload, so I set the default value of 'currentAnchor' to 1000 and change it on each iteration.
if (currentAnchor == 1000 && document.location.hash.substring(1)) {
var checkForThisAnchor = document.location.hash.substring(1);
for (var j=0; j < anchorNameList.length; j++) {
if (anchorNameList[j] == checkForThisAnchor) {
expandMe(j);
}
}
}
In my experience, when a JavaScript problem magically fixes itself by adding an innocuous alert() somewhere, the source of the problem is typically an asynchronous request.
Under non-alert circumstances the async request hasn't finished yet. But by adding the alert, it has a chance to finish, and therefore allowing your code to travel a different code-path than it would have hit without the response of the complete asynchronous call.
I switched the onload event from jQuery's document.ready to window.ready. It worked properly about 30% of the time, so it was definitely a timing issue. It seemed the main JavaScript function on the page, which is retrieving list items, was running slowly. I just moved this entire expanding function to the end of that list retrieval function, so it just runs linearly.

Avoiding a Javascript race condition

My users are presented a basically a stripped down version of a spreadsheet. There are textboxes in each row in the grid. When they change a value in a textbox, I'm performing validation on their input, updating the collection that's driving the grid, and redrawing the subtotals on the page. This is all handled by the OnChange event of each textbox.
When they click the Save button, I'm using the button's OnClick event to perform some final validation on the amounts, and then send their entire input to a web service, saving it.
At least, that's what happens if they tab through the form to the Submit button.
The problem is, if they enter a value, then immediately click the save button, SaveForm() starts executing before UserInputChanged() completes -- a race condition. My code does not use setTimeout, but I'm using it to simulate the sluggish UserInputChanged validation code:
<script>
var amount = null;
var currentControl = null;
function UserInputChanged(control) {
currentControl = control;
// use setTimeout to simulate slow validation code
setTimeout(ValidateAmount, 100);
}
function SaveForm() {
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount;
}
</script>
Amount: <input type="text" onchange="UserInputChanged(this)">
Subtotal: <span id="Subtotal"></span>
<button onclick="SaveForm()">Save</button>
Saved amount: <span id="SavedAmount"></span>
I don't think I can speed up the validation code -- it's pretty lightweight, but apparently, slow enough that code tries to call the web service before the validation is complete.
On my machine, ~95ms is the magic number between whether the validation code executes before the save code begins. This may be higher or lower depending on the users' computer speed.
Does anyone have any ideas how to handle this condition? A coworker suggested using a semaphore while the validation code is running and a busy loop in the save code to wait until the semaphore unlocks - but I'd like to avoid using any sort of busy loop in my code.
Use the semaphore (let's call it StillNeedsValidating). if the SaveForm function sees the StillNeedsValidating semaphore is up, have it activate a second semaphore of its own (which I'll call FormNeedsSaving here) and return. When the validation function finishes, if the FormNeedsSaving semaphore is up, it calls the SaveForm function on its own.
In jankcode;
function UserInputChanged(control) {
StillNeedsValidating = true;
// do validation
StillNeedsValidating = false;
if (FormNeedsSaving) saveForm();
}
function SaveForm() {
if (StillNeedsValidating) { FormNeedsSaving=true; return; }
// call web service to save value
FormNeedsSaving = false;
}
Disable the save button during validation.
Set it to disabled as the first thing validation does, and re-enable it as it finishes.
e.g.
function UserInputChanged(control) {
// --> disable button here --<
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
and
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
// --> enable button here if validation passes --<
}
You'll have to adjust when you remove the setTimeout and make the validation one function, but unless your users have superhuman reflexes, you should be good to go.
I think the timeout is causing your problem... if that's going to be plain code (no asynchronous AJAX calls, timeouts etc) then I don't think that SaveForm will be executed before UserInputChanged completes.
A semaphore or mutex is probably the best way to go, but instead of a busy loop, just use a setTimeout() to simulate a thread sleep. Like this:
busy = false;
function UserInputChanged(control) {
busy = true;
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
if(busy)
{
setTimeout("SaveForm()", 10);
return;
}
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
busy = false;
}
You could set up a recurring function that monitors the state of the entire grid and raises an event that indicates whether the entire grid is valid or not.
Your 'submit form' button would then enable or disable itself based on that status.
Oh I see a similar response now - that works too, of course.
When working with async data sources you can certainly have race conditions because the JavaScript process thread continues to execute directives that may depend on the data which has not yet returned from the remote data source. That's why we have callback functions.
In your example, the call to the validation code needs to have a callback function that can do something when validation returns.
However, when making something with complicated logic or trying to troubleshoot or enhance an existing series of callbacks, you can go nuts.
That's the reason I created the proto-q library: http://code.google.com/p/proto-q/
Check it out if you do a lot of this type of work.
You don't have a race condition, race conditions can not happen in javascript since javascript is single threaded, so 2 threads can not be interfering with each other.
The example that you give is not a very good example. The setTimeout call will put the called function in a queue in the javascript engine, and run it later. If at that point you click the save button, the setTimeout function will not be called until AFTER the save is completely finished.
What is probably happening in your javascript is that the onClick event is called by the javascript engine before the onChange event is called.
As a hint, keep in mind that javascript is single threaded, unless you use a javascript debugger (firebug, microsoft screipt debugger). Those programs intercept the thread and pause it. From that point on other threads (either via events, setTimeout calls or XMLHttp handlers) can then run, making it seem that javascript can run multiple threads at the same time.

Categories