Delaying javascript DOM change when running background script - javascript

I am working on a script that sometimes involves many time-consuming calculations to occur before rendering the result, sometimes just a few. To inform the user that the delay is occurring, a "wristwatch" symbol is shown during the calculation. So far so good. But I don't want the wristwatch to be shown if the delay is very short, as the repeated brief flashing of the symbol can be annoying to view. I can't use setTimeout() to delay the display of the wristwatch since that function can't conclude until the sequential calculation operations are completed.
Reading up here on SO, I found that a Web Worker was advisable to use to get asynchronous behaviour of the calculation operations. But even when relegating the calculations to a Web Worker, the flashing behaviour of the wristwatch persisted. Again, setTimeout() couldn't be used to delay the rendering of the watch, as the quick operations were completed before the delay was over. As a last resort, I used fadeIn() as a delay method, which worked. But it seems less than elegant to use fadeIn(). Is there a more convenient way to achieve this goal?
Below are the html page with script and WebWorker of a demo that show the behaviour during long and short operations, respectively, of the various methods, of which the third gives the desired result in my hands. You can switch between them by commenting out the others.
main.html
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
jQuery(function($){
const worker = new Worker("asyncWorker.js");
$("#shortbtn,#longbtn").click(function(){
$("#done").hide();
/* METHOD 1 */
setTimeout(function(){
$("#wait").show();
},100);
/* METHOD 2 */
//$("#wait").show();
/* METHOD 3 */
//$("#wait").fadeIn(100);
let btnId = $(this).attr("id");
worker.postMessage(btnId);
worker.addEventListener("message",function(message){
$("#wait").hide();
$("#done").show();
});
});
})
</script>
<style>
#wait,#done{display:none;}
#wait{font-size: 4em;}
</style>
</head>
<body>
<button id="shortbtn">Short delay</button>
<button id="longbtn">Long delay</button>
<div id="div">
<span id="wait">⌚</span>
<span id="done">Done!</span>
</div>
</body>
</html>
asyncWorker.js
addEventListener("message",function(message){
switch(message.data){
case "shortbtn":
for(let i=0;i<1000;i++){let a = 0;}// simulate quick process
break;
case "longbtn":
for(let i=0;i<3*1e9;i++){let a = 0;} // simulate slow process
break;
}
postMessage("done");
});

As suggested by #A_A, the solution is to use setTimeout() and clear the timeout when the calculation is finished. Maybe that is rather obvious, but I'll keep the question as long as the moderators don't mind.
From main.html:
/* METHOD 4 */
let tmo = setTimeout(function(){
$("#wait").show();
},250);
let btnId = $(this).attr("id");
worker.postMessage(btnId);
worker.addEventListener("message",function(message){
clearTimeout(tmo); // prevents overdue timeout
$("#wait").hide();
$("#done").show();
});

Related

How do I show/hide divs accurately using jQuery?

I'm working on a mini project where I interview people on short GIF animations. I need to let people see the GIF for 10 seconds and with my code, I've realised that the timer is inaccurate.
I've searched around and found the fix in the Sitepoint article by James Edwards. However its in Javascript and I've been trying to combine the JS and Jquery together but with no success at all.
Here
$(function() {
//when showing image
setTimeout(function() {
$("#div2").fadeOut(0);
}, 10000) //edit timer
$('#btnclick').click(function() {
$('#div2').show();
setTimeout(function() {
$("#div2").fadeOut(0);
}, 10000)
$('#div3').delay(10000).fadeIn(0); //show interview
})
})
The code works fine but I want to implement the self-adjusting mechanism described on Sitepoint. Has anyone done this before or know how I can do this?
The HTML is here:
<div id="div1" class="div1">
<!--Show image button-->
<center>
<input type="button" id="btnclick" value="Show me Image" />
</center>
</div>
<div id="div2" class="main" style="display:none;">
<!--Image-->
<img src="timer.gif"/>
</div>
<div id="div3" class="main" style="display:none;">
<!--Form-->
<form> >
</form>
Thanks in advance.
In jQuery, you can simply use:
$('#myDiv').show();
$('#myDiv').hide();
I believe your fadeIn(0) should work too but fadeIn and fadeOut are doing an animation, and the parameters you're giving it is to do it over 0 millisecond, so it does it instantly but still has to do an animation (which isn't good for your program speed). The best way if you want optimisation is to create a css class like this:
.is-hidden{
display: none;
}
And then do this in jQuery
$('#myDiv').toggleClass('is-hidden');
There's a number of questions in this post, so I'll try to address them one at a time.
jQuery is JavaScript
As a note, jQuery is a library built with JavaScript, so it just provides at convenience methods.
Use .hide() and .show()
What you're probably noticing the most for inaccuracies is probably not related to the fact that the timer is off. Does a few milliseconds make a difference to you? When you set your timeout, you try to fade out the element instantly, but that doesn't happen. After you 10 second timeout, it calls the fade, which takes time. It is likely faster to use .hide() and .show() to hide and show the element.
.delay(10000) is the same as setTimeout( function (){}, 10000)
These two sets are MOSTLY equivalent:
$('#div2').delay(10000).show();
setTimeout( function () {
$('#div2').show();
}, 10000);
The only different is that you're using the jQuery wrapper of the timer. There may be small differences (would have to read the source code), but they should act mostly the same.
Self-adjusting Timer Situation Different?
Your situation is a little different than the self-adjusting timer example you're looking at. In the example, it's an animation that is constantly running shorter tasks on an interval with more granularity, so there's a larger set of time sets to adjust the timeout based on trends. If you're only measuring every 10 seconds, the difference is probably negligible if a user is not creating a lot of samples for your to look at. In my opinion, you'd be better offer just making your code as fast as possible. One would to do that is straight JavaScript to show and hide the div.
var div2 = document.getElementById(id);
// hide
div2.style.display = 'none';
// show
div2
.style.display = 'block';

Why I need to use `setTimeout()` to make `jquery.animate` work in a backbone view?

I want to invoke jquery.animate directly to change the effect of a div, but found it doesn't have any effect.
Instead, I need to put it inside a setTimeout(..., 0) to make it work.
I wonder why do I need to do this, and is it the best approach?
Live demo
http://jsbin.com/docahu/2/edit
Or here:
var FooView = Backbone.View.extend({
id: 'foo',
});
var BarView = Backbone.View.extend({
render: function() {
$("#foo").animate({width: '200px'});
// !!! HERE !!!
setTimeout(function() {
$("#foo").animate({height: '100px'});
}, 0);
return this;
}
});
var fooView = new FooView();
var barView = new BarView();
var combinedView = $(fooView.render().el).append(barView.render().el);
$(document.body).append(combinedView);
#foo {
width: 50px;
height: 50px;
background-color: blue;
color: white;
}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Jquery animate delay problem in backbone render" />
<script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//jashkenas.github.io/underscore/underscore-min.js"></script>
<script src="//jashkenas.github.io/backbone/backbone-min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
You can see height is changed but the width is not.
PS:
Also I found $(document).ready() is also working:
$(document).ready(function() {
$("#foo").animate({height: '100px'});
});
Which one is better to use?
It's because it's trying to animate the width before the element is in the DOM. If you put a selector in that position, you'll probably find it's not getting anything.
Doing a timeout of 0 (so javascript finishes rendering the things THEN tries the animation) or waiting for the document to finish rendering fixes that
Things happen in this order:
You render your view, but it's unattached to the DOM
the width animation runs. Nothing happens because '#foo' isn't on the DOM.
you attach it to the dom.
your height animation runs. It works because '#foo' is in the DOM.
Well seems like it works by chance. The reason the first one is not working is probably because the object are still not loaded on the screen. The second one is working because after the timer was dispatched and ended (this does not really take 0 time) the page was loaded by another thread on the computer. So the overhead of creating the timer and calling back the procedure is apparently enough to finish loading the page.
You should use $(document).ready to make sure it is always called after the document is fully loaded, because like I said, it is now working by chance, and a different browser\machine may not run any of the two (or both).
Background: JavaScript starts getting executed while the page is loaded, and the DOM is build at the same time (just at the time the HTML and JavaScript text is downloaded). So if you reference DOM objects from JavaScript code like you are doing now, you get a race condition where the outcome is not defined. To avoid that there is the $(document).ready callback.
Edit
See this question. Also the Udacity course is really cool to understand what is going on.
Updated
TL;DR
Using $(document).ready is equivalent to placing the JavaScript at the end of the document…
JSBin (which is were the OP posted his sample code) will execute the JavaScript after all the HTML elements render, but before the $(document).ready event. Binding the jQuery.animate in $(document).ready is the same as firing it anytime after the view is appended to the DOM.
The simple and stable solution is to simply invoke
$("#foo").animate({width: '200px'});
on the last line of the OP's code. (Read the end of the Answer to see a more formal way of binding the jQuery.animate function)
To answer the OP's original quesiton: setTimeout() works in your case because of the way async functions are queue in the JavaScript runtime. If the <body> has already been loaded, then using setTimeout(0) the way the OP does, will have the same effect as placing the animation binding in $(document).ready.
Why setTimeout(0) works
The first thing to understand is that JavaScript is not a multi-threaded framework. While you certainly can invoke non-synchronous functions, asynchronous, async functions don't actually run parallel to the synchronous operations. Instead, async functions are queued to run as soon as the runtime is free.
For example, take the following three synchronous functions.
function1();
function2();
function3();
As you'd expect function1will fire first, followed, in order, byfunction1, function2thenfunction3. However, if I place function1` in an asynchronous call
setTimeout(function1,0);
function2();
function3();
then function1 will be placed on a queue, leaving function2 and then function3 to fire. As soon as the event loop is finished function1 is invoked. That is, it fires last! You can see this in action in this fiddle.
In the OP's example, setTimeout(function() { $("#foo").animate({height: '100px'});}, 0); was fired immediately after the runtime executed $(document.body).append(combinedView); and so jQuery was able to find the #foo element, so technically this is a correct way to do what the OP wants. This is true because of the way JSBin works. That is, it loads the JavaScript from its JavaScript Module after the DOM has loaded (but before the $(document).ready event).
Edited:
Do not use the $(document).ready function...in general
I think there's some confusion regarding how $(document).ready function solves the OP's problem. Most of the confusion probably stems from the complexity of how different web page elements (HTML, CSS, JavaScript) affect the rendering of the DOM.
There is a main parsing and rendering thread used by browsers. This is where your HTML is processed, your CSS stylesheets are fetched and parsed, and your JavaScript is fetched/parsed. All of these operations are executed as they are encountered and will be blocking (unless async or defer is specified in your <link>/<script> tags).
The order in which you pace all of these tags is essential. If your script tag is written at the top of your document (say within the <head> tag) it will be executed before any HTML is injected into the DOM.
In essence, using $(document).ready is equivalent to placing the JavaScript at the end of the document… Since JSBin (which is were the OP posted his sample code) will execute the JavaScript after all the HTML elements render, but before the $(document).ready function, binding the jQuery.animate in $(document).ready is the same as firing it anytime after the view is appended to the DOM.
Instead, the simple and stable solution is to simply invoke
$("#foo").animate({width: '200px'});
after both the fooView and the barView have been attached to the DOM. Or more formally:
var BarView = Backbone.View.extend({
render: function() {
// process your html here
return this;
}
bindTransitions: function {
$("#foo").animate({width: '200px', height: '100px'});
}
});
var fooView = new FooView();
var barView = new BarView();
var combinedView = $(fooView.render().el).append(barView.render().el);
$(document.body).append(combinedView);
barView.bindTransitions();
If you've properly scoped your backbone view, you should be able to reference the element that is currently in memory when you are trying to change the width or height (pre-render).
You can do this by doing something similar to: this.$el.find(#foo") to obtain access / manipulate to your markup before it is added to the DOM.

Replay jQuery function every 5 seconds

I want to replay my jquery function ChangeStats() every 5 seconds, it's currently doing sod all.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500);
setTimeout(function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
}, 500);
}
$(document).ready(function(){
setInterval(ChangeStats, 5000);
})();
Yes I have got the right class names.
No I haven't used underscores in my HTML.
I think it's something to do with my use of "find()", once the DOM has loaded and the function is set is it meant to traverse up the DOM tree instead of down?
EDIT:
Updated code, still not working.
HTML:
<span class="admin-stats-big-figures">%productCount%</span>
<span class="admin-stats-big-figures-hidden">hey</span>
Ok, I am going to go out on a limb and make several assumptions here; one is that you wish to cycle between two elements repeatedly, another is that you are using $(this) in the context of the window rather than a containing element. If either of these are incorrect then the following solution may not be suitable. However, let's give this a shot, eh?
1) You need to use setInterval rather than setTimeout to create a repeating call. You can of course "chain" your timeouts (ie: call the succeeding timeout from the code of the current timeout). This has some benefits in certain situations, but for now let's just assume you will use intervals rather than timeouts.
2) You call the find() jQuery method every time, which is a little unnecessary, especially if you will be repeating the actions so one idea would be to cache the lookup. If you are going to do that a custom object would be more suitable than separate global variables.
3) Some flexibility in terms of starting and stopping the animation could be provided. If we use a custom object as mentioned in (2) then that can easily be added.
4) You are using fadeIn and fadeOut, however if you wish the items to cycle then fadeToggle may be your best solution as it will simply allow you to do exactly that, toggle, without needing to check the current opacity state of the element.
5) Finally in my example I have provided a little extra "padding HTML" in order for the example to look good when run. Fading in jQuery will actually set the faded item to a CSS display of "none" which results in the content "jumping about" in this demo, so I have used some div's and a couple of HTML entity spaces to keep the formatting.
Ok, after all that here is the code..
// your custom animation object
var myAnim = {
// these will be cached variables used in the animation
elements : null,
interval : null,
// default values for fading and anim delays are set to allow them to be optional
delay : { fade: 500, anim: 200 },
// call the init() function in order to set the variables and trigger the animation
init : function(classNameOne, classNameTwo, fadeDelay, animDelay) {
this.elements = [$("."+classNameOne),$("."+classNameTwo)];
// if no fade and animation delays are provided (or if they are 0) the default ones are used
if (animDelay) this.delay.anim = animDelay;
if (fadeDelay) this.delay.fade= fadeDelay;
this.elements[0].fadeOut(function(){myAnim.start()});
},
// this is where the actual toggling happens, it uses the fadeToggle callback function to fade in/out one element once the previous fade has completed
update : function() {
this.elements[0].fadeToggle(this.delay.anim,function(el,delay){el.fadeToggle(delay)}(this.elements[1],this.delay.anim));
},
// the start() method allows you to (re)start the animation
start : function() {
if (this.interval) return; // do nothing if the animation is currently running
this.interval = setInterval(function(){myAnim.update()},this.delay.fade);
},
// and as you would expect the stop() stops it.
stop : function () {
if (!this.interval) return; // do nothing if the animation had already stopped
clearInterval(this.interval);
this.interval = null;
}
}
// this is the jQuery hook in order to run the animation the moment the document is ready
$(document).ready(
function(){
// the first two parameters are the two classnames of the elements
// the last two parameters are the delay between the animation repeating and the time taken for each animation (fade) to happen. The first one should always be bigger
myAnim.init("admin-stats-big-figures","admin-stats-big-figures-hidden",500,200);
}
);
OK, so now we need the HTML to compliment this (as I say I have added a little formatting):
<div><span class="admin-stats-big-figures">One</span> </div>
<div><span class="admin-stats-big-figures-hidden">Two</span> </div>
<hr/>
<input type="button" value="Start" onclick="myAnim.start()"/> | <input type="button" value="Stop" onclick="myAnim.stop()"/>
I have also provided buttons to stop/start the animation. You can see a working example at this JSFiddle - although the stop/start buttons are not working (presumably something specific to JSFiddle) they do work when in context though.
Here im gonna just replace your $(this). and maybe it'll work then.. also using callback.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500, function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
});
}
$(document).ready(function(){
setTimeout('ChangeStats()', 5000);
});

Would like help figuring out the best way to watch for changes to the DOM

I'm trying to use Jquery-Mutation Summary https://code.google.com/p/mutation-summary/
"a JavaScript library that makes observing changes to the DOM fast, easy and safe"
It can be found here: https://github.com/joelpurra/jquery-mutation-summary
Here's an example of it at work:
http://joelpurra.github.io/jquery-mutation-summary/example/demo.html
All I want to do is call a function when there's changes to content within an element such as a div with an id "MyDiv"
Here's my code. What am I doing wrong? There is no alert message as my function that's being called in this example is suppose to display once changes are observed.
<script src="http://joelpurra.github.io/jquery-mutation-summary/lib/mutation-summary /src/mutation-summary.js"></script>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://joelpurra.github.io/jquery-mutation-summary/src/jquery.mutation- summary.js"></script>
<script>
function MyFunction(){
alert('changes have been made');
}
// This code won't be executed until jQuery has been loaded.
$(function() {
var $ChangeThere = $("#MyDiv")
// The callback in this case will only print the result
// Connect mutation-summary
$ChangeThere.mutationSummary("connect", callback, [{
all: true
}]);
// Disconnect when done listening
//$ChangeThere.mutationSummary("disconnect");
function callback(summaries) {
MyFunction();
}
});
</script>
In newer versions of Firefox, a mozAfterPaint event exists, but there's obvious compatibility problems with that, so I'd suggest checking for the various things that cause reflows by adding a check into an abstracted version of each cause. A list of causes and some other information can be found in this SO

How do update a page element while inside a long running event?

I've got a long running method -- and I want to indicate to the user that an operation is underway. This is NOT and ajax call, so I can't use the common pattern I've used in the past to display, say a spinner, before the ajax event, then hiding it on success for example.
In my case -- I'm not making an ajax call, I'm instead doing some very heavy DOM manipulation.
I had a test example on jsFiddle.net -- would love to learn how to capture the event. At the moment, my "wait-message" div updates at the same exact time when my operation completes which is much too late :(
Complete sample code is here: http://jsfiddle.net/rsturim/97hrs/6/
Javascript (jQuery)
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
HTML:
<div id="wait-message">
push button please
</div>
<hr />
<button id="link-action">Run Operation</button>
<hr />
<h1>Results:</h1>
<div id="result"> </div>
Here is a solution. I'm not sure if it works in all browsers, you may want to test it out in several, but I think it does:
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
// Stuff to do after a render
setTimeout(function(){
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
}, 0);
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
Basically, the browser won't render any changes until a script finishes executing. That allows you to do things like:
Hide all divs with a certain class
Show one of those divs
In a row and the browser will never render the div that is being shown as hidden, so you won't get weird flickers or things moving around on the page.
Using setTimeout like I did, the anonymous click handler will finish executing, the browser will re-render, the the anonymous function in the setTimeout will run (immediately after the render since there is no actual delay).
Use setTimeout or setInterval instead of your while loop; a sub-second delay like 15ms should be enough to prevent your window freezing / UI locking.

Categories

Resources