JS spinner only shows after action completes - javascript

This is a pretty consistent issue we've been struggling with for quite some time.
The page uses the fgnass spinner. The page is built on angular. When performing any kind of lengthy dom manipulation, I try and call the spinner, then perform said dom manipulation (for instance, calling pagination or search on rather large data sets). The actions can take a few seconds, thus the reason for the spinner: so the user knows stuff is happening, and to wait.
What I try to do is.
function callSpinner()
{
j$("div[class^='ui-dialog']").addClass('dialog_spinner');
j$("div[class^='ui-dialog-buttonpane ui-widget-content']").css("display", "none");
j$('#progressSpinner').dialog('open');
}
function closeSpinner()
{
j$('#progressSpinner').dialog('close');
j$("div[class^='ui-dialog']").removeClass('dialog_spinner');
}
This is using the angular pagination lib by Michael Bromley found here. This is happening inside the scope.setCurrent function of his library.
scope.setCurrent = function(num) {
callSpinner();
paginationService.setCurrentPage(paginationId, num);
closeSpinner();
}
That said, it occurs with a standard Angular filter as well.
This is the createSpinner method called on page creation (and I also launch the spinner once on page load)
function CreateSpinner()
{
// initialize spinner.
var opts = {
lines: 13, // The number of lines to draw
length: 15, // The length of each line
width: 5, // The line thickness
radius: 15, // The radius of the inner circle
corners: 1, // Corner roundness (0..1)
rotate: 0, // The rotation offset
direction: 1, // 1: clockwise, -1: counterclockwise
color: '#E68A2E', // #rgb or #rrggbb or array of colors
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: '50', // Top position relative to parent in px
left: '30' // Left position relative to parent in px
};
var target = document.getElementById('progressSpinner');
$spinner = new Spinner(opts).spin(target);
j$("div[class='ui-widget-overlay ui-front']").addClass('overlay_style1');
}
And here's the HTML for the spinner tag I'm targeting.
<div id="progressSpinner" title="Progress" style="display: none; background-color: #000000; z-index: 1200; opacity: 0.8; padding: 0;"></div>
What actually happens though, is it fires off the pagination action, and waits to call the spinner until after the pagination process has finished. So the page just does nothing, the pagination finishes and the form is updated, then the spinner launches, then it closes.
As I said, I create the spinner on page load, then call the callSpinner function while the page is prepared. That works fine.
I can call callSpinner at any time, and it works fine as well.
I can even open the spinner in the console, then trigger the pagination click, and the spinner keeps spinning.

After various attempts at doing different things, I've finally got it working. There was never, at any point, anything specifically wrong with my code. If I put debug breaks at each code line for
callSpinner();
paginationService.setCurrentPage(paginationId, num);
closeSpinner();
everything works how it should.
I finally worked out that what's happening is the browser takes all 3 lines of code at once. And it decides it's more important to run the setCurrentPage function above everything else. So it paginates the entire thing, then it'll run callSpinner(); Then it immediately runs close spinner.
My solution:
callSpinner();
setTimeout(function () {
scope.$evalAsync(
function( scope ) {
paginationService.setCurrentPage(paginationId, num);
closeSpinner();
}
);
}. 200);
Reasoning: As near as I can tell, by putting the pagination call into a timeout with a short delay, it makes sure the callSpinner(); executes before doing anything else.
Originally, I just had the timeout, but as soon as the code in the timeout was called, it would trigger the closeSpinner(); line first, then do the pagination call. So I used Angular's $evalAsync on it, and that got it going right.

Related

JavaScript Velocity do not wait until animation finishes

I am using JavaScript with the VelocityJS library to animate a pointer on my screen. The thing is that I get my input for how many degrees I have to rotate the pointer way more often than it can process.
The code that I have right now is:
function changePointer(msg){
kmh = parseInt(msg.payloadString);
degreesToTurnTo = Math.round(kmh * stapgrote);
$("#pointer").velocity({
rotateZ: degreesToTurnTo+"deg"
});
console.log(degreesToTurnTo);
}
The function is getting called about 5 times a second, but 1 animation already takes about half a second.
So my question is, how can I make an if statements that checks if the previous animation is ready (so that I can update it again once its finished). Or how can I cancel the current animation and start the next one (this method would be preferred since I am making a speedometer).
Thanks in advance!
With kind regards,
Mats de Waard
From: http://velocityjs.org/#stop
/* Prior Velocity call. */
$element.velocity({ opacity: 0 });
/* Later, midway through the call... */
$element
/* Stop animating opacity. */
.velocity("stop")
/* Animate opacity back to 1. */
.velocity("reverse");
So in your case something like:
$("#pointer")
.velocity("stop")
.velocity({
rotateZ: degreesToTurnTo+"deg"
});

Canvas: Detect if it is visible in browser

I have a list of charts. I use Chart.js to create those charts. Since my list can have 1 to 100 or more entries initializing all charts at once would not be smart because that would make the ui freeze for a long time. So instead I thought it would be much better to only initialize those charts which are visible inside the view bounds of the browser so that for example only the first chart is getting initialized and when the user scrolls down and the second canvas becomes visible the second is getting initialized and so on.
I have everything setup but the only problem that I have right now is: how can I create an eventlistener or anything similiar which I can add to each canvas element that gets triggered when a canvas becomes visible inside the view bounds of the browser so that i can perform the chart initialization for that canvas?
I'm the author of OnScreen, a small library that can call a callback function when a HTMLElement enters the viewport or the boundaries of its container.
// Uses ES6 syntax
import OnScreen from 'onscreen';
const os = new OnScreen();
os.on('enter', 'canvas', (element) => {
if (!element.chartInitialized) {
// Initialize the chart
// Update the `chartInitialized` property
// to avoid initializing it over and over
element.chartInitialized = true;
}
});
For more information, take a look at the documentation. Don't forget to check the demos repo for a couple simple examples.
I have used the onScreen jQuery plugin.
It is very easy. You just have to call for each canvas this:
$('elements').onScreen({
container: window,
direction: 'vertical',
doIn: function() {
// initialize canvas
},
doOut: function() {
// Do something to the matched elements as they get off scren
},
tolerance: 0,
throttle: 50,
toggleClass: 'onScreen',
lazyAttr: null,
lazyPlaceholder: 'someImage.jpg',
debug: false
});

toggle KineticJs layer broken after rebuilding stage

I have a canvas created with KineticJs Stage. on this stage I have three layers. One is the background and is always on. The other two are overlays and the visibility is toggled by a checkbox. Any time the parent div of this stage resizes I redraw the entire stage to keep my layout correct. Here are the two situations where my toggling works:
1. before resize and redraw.
2. If I don't toggle it at all before the redraw.
Here is where it does not work:
1. Toggle layer on then off. Resize canvas to trigger redraw. Then try and toggle back on. In this case the visible attribute gets set to true when I call show() but the layer does not actually show up.
Stepping through the code I can not find any difference in the layer during any of the above scenarios. I did however notice that the index of each layer gets incremented each time it is rebuilt even though I have instantiated new instances of the stage and every layer each time I rebuild.
Can anybody tell me why the index increments even though everything has been destroyed and why the layer is not showing up? I was thinking zindex but this never seems to change and should be showing up.
here is what I do before every rebuild:
stage = new $window.Kinetic.Stage({
container: 'canvas',
x: 0,
y: 0,
width: parent.clientWidth,
height: parent.clientHeight
});
var layer = new $window.Kinetic.Layer();
erosionLayer = new $window.Kinetic.Layer({
visible: scope.erosionVisible
});
Here is where I toggle it:
scope.$watch('erosionVisible', function(val) {
if (!erosionLayer) return;
var showErosion = false;
if (!scope.erosionVisible) {
showErosion = false;
} else if (scope.erosionVisible) {
showErosion = true;
}
if (showErosion) {
erosionLayer.show();
} else {
erosionLayer.hide();
}
});
FYI, This is in an angularJs directive.
fixed the issue. for some reason I need to call draw on the layer when toggling if the stage has been rebuilt.

How do I stop my heatmap from flickering?

I am making a page that displays data in the form of a heatmap. The script that writes the image to the page runs every second. As I am plotting from live data, it's very important that the heatmap updates every second.
The problem is that my image flickers. How can I get rid of that flicker?
I can't cache it as I need to change it. If the image flickering cannot be stopped, can it be given some smooth transition?
<script type="text/javascript" charset="utf-8">
function toggle (mainImage, backupImage) {
//$("#imagecontainer").load("./php/sarimage.png");
var refreshId = setInterval(function() {
var ele = document.getElementById(mainImage);
var imageEle = document.getElementById(backupImage);
var randomnumber=Math.floor(Math.random()*101);
var source ="./php/sarimage.png?dummy=";
source = source.concat(randomnumber);
imageEle.src = source;
$("#imagecontainer").load(source);
ele.src= imageEle.src;
}, 1000);
$.ajaxSetup({ cache: false });
}
</script>
It looks like you're feeding a SRC URL to the image using JavaScript that runs an AJAX call once a second to get fresh data. I suspect that the image is probably flickering because of network latency -- it takes a moment for the fresh image data to download and then get updated.
You might try introducing a one-second delay into the script. Use two images, one on screen for the currently displayed data, and one offscreen. Load fresh info into the off screen image. Then, swap the positions of the two images (onscreen goes off, offscreen goes on). Because the fresh data would be loaded into an image outside the viewport, the download wouldn't happen visibly. Just moving it into position should take place with no perceptible flicker.
It would go something like this (this is pseudo code that won't necessarily run). First, some HTML -- just a couple of images.
<img src="initial.png" alt="heatmap" class="heatmap onscreen" />
<img src="loading-area.png" alt="heatmap" class="heatmap" />
Then some CSS:
/* By default heatmaps are off screen. */
.heatmap { position: absolute; left: -999em; }
/* But then we override that for ones marked as on screen. */
.heatmap.onscreen { position: static; left: auto; }
Lastly some JavaScript.
var firstRun = true;
function loadNewImage(){
// Download fresh image and load it into an image OFF SCREEN.
var imagePath = '/path/to/image.png?dummy='+Math.floor(Math.random()*101);
$(".heatmap:not(.onscreen)").attr("src", "imagePath");
}
function updateImage(){
if(firstRun){
// The first time this function runs, load new data ...
loadNewImage();
// Now make a note that the function has run already.
firstRun = false;
// And return without doing anything else.
return false;
} else {
// The off screen image has fresh data by this time. Swap it.
$(".heatmap:not(.onscreen)").addClass("next");
// Remove the onscreen class from the current image, so it
// moves off screen.
$(".onscreen").removeClass("onscreen");
// Add onscreen to the next one, moving it into place.
$(".next").addClass("onscreen");
// Remove the "next" class from the newly displayed image.
$(".next").removeClass("next");
// Load more image data.
loadNewImage();
}
// Lastly, call this same function again in a second.
window.setTimeout(updateImage, 1000);
}
$(document).ready(function(){
// Start the swapping.
updateImage();
});
Assuming you have a reasonably speedy and reliable connection, something like that should take care of flickering caused by network latency. It does introduce a one second delay -- the currently displayed image will always be one second behind realtime. But if real time synchronicity is important to you, then HTML/CSS/JavaScript is probably the wrong tool for the job.
If there's some OTHER cause for your flickering, well, good luck.

Javascript - loading/busy indicator or transparent div over page on event click

i got a client side javascript function which is triggered on a button click (basically, its a calculator!!). Sometimes, due to enormous data on the page, the javascript calculator function take to long & makes the page appear inactive to the user. I was planning to display a transparent div over entire page, maybe with a busy indicator (in the center) till the calculator function ends, so that user waits till process ends.
function CalculateAmountOnClick() {
// Display transparent div
// MY time consuming loop!
{
}
// Remove transparent div
}
Any ideas on how to go about this? Should i assign a css class to a div (which surrounds my entire page's content) using javascript when my calculator function starts? I tried that but didnt get desired results. Was facing issues with transparency in IE 6. Also how will i show a loading message + image in such a transparent div?
TIA
Javacript to show a curtain:
function CalculateAmountOnClick () {
var curtain = document.body.appendChild( document.createElement('div') );
curtain.id = "curtain";
curtain.onkeypress = curtain.onclick = function(){ return false; }
try {
// your operations
}
finally {
curtain.parentNode.removeChild( curtain );
}
}
Your CSS:
#curtain {
position: fixed;
_position: absolute;
z-index: 99;
left: 0;
top: 0;
width: 100%;
height: 100%;
_height: expression(document.body.offsetHeight + "px");
background: url(curtain.png);
_background: url(curtain.gif);
}
(Move MSIE 6 underscore hacks to conditionally included files as desired.)
You could set this up as add/remove functions for the curtain, or as a wrapper:
function modalProcess( callback ) {
var ret;
var curtain = document.body.appendChild( document.createElement('div') );
curtain.id = "curtain";
curtain.onkeypress = curtain.onclick = function(){ return false; }
try {
ret = callback();
}
finally {
curtain.parentNode.removeChild( curtain );
}
return ret;
}
Which you could then call like this:
var result = modalProcess(function(){
// your operations here
});
I'm going to make some heavy assumptions here, but it sounds to me what is happening is that because you are directly locking the browser up with intense processing immediately after having set up the curtain element, the browser never has a chance to draw the curtain.
The browser doesn't redraw every time you update the DOM. It may woit to see if you're doing something more, and then draw what is needed (browsers vary their method for this). So in this case it may be refreshing the display only after it has removed the curtain, or you have forced a redraw by scrolling.
A fair waring: This kind of intense processing isn't very nice of you because it not only locks up your page. Because browsers generally implement only a single Javascript thread for ALL tabs, your processing will lock up all open tabs (= the browser). Also, you run the risk of the execution timeout and browser simply stopping your script (this can be as low as 5 seconds).
Here is a way around that.
If you can break your processing up into smaller chunks you could run it with a timeout (to allow the browser breathing space). Something like this should work:
function processLoop( actionFunc, numTimes, doneFunc ) {
var i = 0;
var f = function () {
if (i < numTimes) {
actionFunc( i++ ); // closure on i
setTimeout( f, 10 )
}
else if (doneFunc) {
doneFunc();
}
};
f();
}
// add a curtain here
processLoop(function (i){
// loop code goes in here
console.log('number: ', i);
},
10, // how many times to run loop
function (){
// things that happen after the processing is done go here
console.log('done!');
// remove curtain here
});
This is essentially a while loop but each iteration of the loop is done in an timed interval so the browser has a bit of time to breathe in between. It will slow down the processing though, and any work done afterwards needs to go into a callback as the loop runs independently of whatwever may follow the call to processLoop.
Another variation on this is to set up the curtain, call your processing function with a setTimeout to allow the browser time to draw the curtain, and then remove it once you're done.
// add a curtain
var curtain = document.body.appendChild( document.createElement('div') );
curtain.id = "curtain";
curtain.onkeypress = curtain.onclick = function(){ return false; }
// delay running processing
setTimeout(function(){
try {
// here we go...
myHeavyProcessingFunction();
}
finally {
// remove the curtain
curtain.parentNode.removeChild( curtain );
}
}, 40);
If you are using a js-library, you may want to look at a ready made solution for creating curtains. These should exist for most libraries, here is one for jQuery, and they can help with the CSS.
I would do something like:
unhide a div (display:inline)
make the position:absolute
give it a z-index:99
make the height and width 100%
when the processing is done set display:none
To make it transparent you'll have to set the opacity which is different in Firefox, IE, etc.
To show a loading icon you can always create a second div and position it where you want to on the page. When it's done loading, remove it along with the transparent one.
In addition to all of the above, don't forget to put an invisible iframe behind the shim, so that it shows up above select boxes in IE.
Edit:
This site, although it provides a solution to a more complex problem, does cover creating a modal background.
http://www.codeproject.com/KB/aspnet/ModalDialogV2.aspx
For the loading message, I would use a <div> with position:absolute, position it using left and top, and set the display to none.
When you want to show the loading indicator, you're going to have to use a timeout otherwise the div won't display until your processing is done. So, you should modify your code to this:
function showLoadingIndicator()
{
// Display div by setting display to 'inline'
setTimeout(CalculateAmountOnClick,0);
}
function CalculateAmountOnClick()
{
// MY time consuming loop!
{
}
// Remove transparent div
}
Because you set the timeout, the page will redraw before the time-consuming loop happens.

Categories

Resources