call backs / que loading - call function after complete - javascript

What would be the best way to call render() once all the imageData vars have been populated?
would I just call them all in sequence using callbacks, and call render, or is there a better method?
function loadImageData()
{
//Get Background Image Data
var backgroundImg = loadImage(background, function(){
backgroundData = getDataFromImage(backgroundImg);
});
//Get Overlay Image Data
var overlayImg = loadImage(overlay, function(){
overlayData = getDataFromImage(overlayImg);
});
//Get more Image Data
//Get more Image Data
//Get more Image Data
}
function render()
{
ctx.putImageData(backgroundData, 0,0);
}

From inside the callbacks, "render" the individual image elements onto the canvas.
var backgroundImg = loadImage(background, function(){
backgroundData = getDataFromImage(backgroundImg);
ctx.putImageData(backgroundData, 0,0);
});

My approach would include creating a "counter" of the number of callbacks you need, something like this:
function loadImageData()
{
var counter = 5;
function imageReceived() {
if (--counter == 0) render();
}
//Get Background Image Data
var backgroundImg = loadImage(background, function(){
backgroundData = getDataFromImage(backgroundImg);
imageReceived();
});
//Get Overlay Image Data
var overlayImg = loadImage(overlay, function(){
overlayData = getDataFromImage(overlayImg);
imageReceived();
});
//Get more Image Data
//Get more Image Data
//Get more Image Data
Of course, I would probably rewrite it in such a way that the number of waiting requests was increased in the loadImage() function, and have a global callback that gets fired whenever the number of requests waiting gets back to 0.

Related

JavaScript appended child (div) only shows up after ALL code has been executed

Long story short: got an upload file element and a button with an onclick function called "start". So, all of these happens way after all DOM content is loaded.
createLoader: function(){
var outerDiv = document.createElement('div');
var innerDiv = document.createElement('div');
innerDiv.className = '_gisplayloader';
var mapDiv = this.getContainer();
/*outerDiv.style = ' opacity: 0.5; background-color: grey; justify-content: center; display: flex;';
outerDiv.style.position = 'absolute';
outerDiv.style.zIndex = '999999999';*/
outerDiv.className = '_gisplayLoaderOuterDiv';
outerDiv.style.height = mapDiv.offsetHeight;
outerDiv.style.width = mapDiv.offsetWidth;
outerDiv.appendChild(innerDiv);
this.loaderDiv = outerDiv;
mapDiv.parentElement.insertBefore(outerDiv, mapDiv);
}
This is the loader/spinner create and append code. It works instantly if I call it through the browser console.
Inside start(), it reads the uploaded file and onloadend calls another function that calls createLoader().
function start(){
//var data = new Array();
var time = Date.now();
console.log("starting...");
var reader = new FileReader();
reader.onloadend = function(){
var data = JSON.parse(reader.result);
var datareadtimestamp = Date.now();
makeChoropleth(map, data ,options,null);
}
reader.readAsText(document.getElementById("file").files[0]);
}
The simplified version of makeChoropleth function:
makeChoropleth: function(bgmap, geometry, options,defaultid){
var gismap = new Choropleth(bgmap, geometry, options); //inside here it calls createLoader()
//the next 3 functions take about 5-10s to execute all together
gismap.processData(geometry);
gismap.draw();
gismap.buildLegend();
if(options.loader != false){
// gismap.loader(); that would hide the loader. disabled it so i could check if the loader was appearing at all
}
}
Unless I put a breakpoint somewhere inside makeChoropleth, the loader only shows up upon all code completion. The following code takes almost 10 seconds to finish, which is more than enough to create the loader (assuming it is asynchronous). Why does that happen? How could one fix it?
if you want the loader to show up before the file finishes reading you need to call it before the onloadend event, in the start() function.
function start(){
//var data = new Array();
var time = Date.now();
console.log("starting...");
var reader = new FileReader();
reader.onloadend = function(){
//stuff here only runs after the file is read completely!
var data = JSON.parse(reader.result);
var datareadtimestamp = Date.now();
makeChoropleth(map, data ,options,null);
}
reader.readAsText(document.getElementById("file").files[0]);
createLoader();
}
if you want the loader to show up after the file read finishes but before your Choropleth code runs, the easiest way is to put a 1ms timeout on your 5-10s operation to give the browser time to do a reflow. (otherwise the blocking code will run first). Try:
makeChoropleth: function(bgmap, geometry, options,defaultid){
var gismap = new Choropleth(bgmap, geometry, options); //inside here it calls createLoader()
//the next 3 functions take about 5-10s to execute all together
setTimeout(function(){
gismap.processData(geometry);
gismap.draw();
gismap.buildLegend();
},1);
if(options.loader != false){
// gismap.loader(); that would hide the loader. disabled it so i could check if the loader was appearing at all
}
}

Sharing one physical image between two img elements in HTML

There is an image element , how do I use this same img element without sending another request to the server. Its important to note, I don't want image1.jpg downloaded twice from the webserver. Any ideas?
function loadCarousels(carouselLoc, carouselId) {
$("li").find(carouselLoc).each(function (index) {
var img = this;
var outer = 0;
$(carouselId).find("ul").each(function (innerIndex) {
var liX = document.createElement("li");
$(this).append(liX);
var imgInner = document.createElement("img");
imgInner.src = img.src;
$(imgInner).appendTo(liX);
console.log($(this));
});
});
}
Is how I currently try but it doesn't work. it creates a separate image.
Browsers should be already pretty aggressive on caching images: Chrome often shows multiple requests, but if you check from the second on usually they're all satisfied using the cache.
In case you want to cache internally in your JS code, try to cache images by URL like the following:
// use this as JS cache
var images = {};
function loadCarousels(carouselLoc, carouselId) {
$("li").find(carouselLoc).each(function (index) {
var img = this;
var outer = 0;
// cache it
if(!images[img.src]){
images[img.src] = document.createElement("img");
// if the user disable the cache, this should prevent another request
images[img.src].src = img.src;
}
$(carouselId).find("ul").each(function (innerIndex) {
var liX = document.createElement("li");
$(this).append(liX);
// retrieve from the cache
var imgInner = images[img.src];
$(imgInner).appendTo(liX);
console.log($(this));
});
});
}

How to stop a javascript function from within another function?

I'm developing a simple slideshow system.
I've got the slideshow wrapped in a hidden div, which is shown when a thumbnail from the gallery is clicked.
The slideshow works through a function called commence(), which is executed when the play button is clicked.
At the moment I've got it set to hide to whole div again when stop is clicked, but I would like to keep the div shown, simply stop the slideshow, in other words, stop the commence() function.
Can anyone tell me how to do this?
Here is my JS:
function commence() {
hidden = document.getElementById("hidden");
hidden.style.display = 'block';
pause = document.getElementById("pause");
pause.style.display = 'block';
play = document.getElementById("play");
play.style.display = 'none';
pic = document.getElementById("picbox"); // Assign var pic to the html element.
imgs = []; // Assign images as values and indexes to imgs array.
/* --------------------------- IMAGE URLS FOR IMGS ARRAY -------------------------*/
imgs[0] = "/snakelane/assets/images/thumb/_1.jpg"; imgs[10] = "/snakelane/assets/images/thumb/_19.jpg";
imgs[1] = "/snakelane/assets/images/thumb/_2.jpg"; imgs[11] = "/snakelane/assets/images/thumb/_20.jpg";
imgs[2] = "/snakelane/assets/images/thumb/_3.jpg"; imgs[12] = "/snakelane/assets/images/thumb/_21.jpg";
imgs[3] = "/snakelane/assets/images/thumb/_4.jpg"; imgs[13] = "/snakelane/assets/images/thumb/_22.jpg";
imgs[4] = "/snakelane/assets/images/thumb/_5.jpg"; imgs[14] = "/snakelane/assets/images/thumb/_23.jpg";
imgs[5] = "/snakelane/assets/images/thumb/_6.jpg"; imgs[15] = "/snakelane/assets/images/thumb/_24.jpg";
imgs[6] = "/snakelane/assets/images/thumb/_7.jpg"; imgs[16] = "/snakelane/assets/images/thumb/_25.jpg";
imgs[7] = "/snakelane/assets/images/thumb/_8.jpg"; imgs[17] = "/snakelane/assets/images/thumb/_26.jpg";
imgs[8] = "/snakelane/assets/images/thumb/_9.jpg"; imgs[18] = "/snakelane/assets/images/thumb/_27.jpg";
imgs[9] = "/snakelane/assets/images/thumb/_10.jpg"; imgs[19] = "/snakelane/assets/images/thumb/_28.jpg";
/* -----------------------------------------------------------------------------------------------------*/
var preload = []; // New array to hold the 'new' images.
for(i = 0 ; i < imgs.length; i++) // Loop through imgs array
{
preload[i] = new Image(); // Loop preload array and declare current index as a new image object.
preload[i].src = imgs[i]; // Fill preload array with the images being looped from ims array.
}
i = 0; // Reset counter to 0.
rotate(); // Execute rotate function to create slideshow effect.
}
// Function to perform change between pictures.
function rotate() {
pic.src = imgs[i]; // Change html element source to looping images
(i === (imgs.length -1))?(i=0) : (i++); // counter equals imgs array length -1.
setTimeout( rotate, 4000); // Sets the time between picture changes. (5000 milliseconds).
}
function init() {
[].forEach.call(document.querySelectorAll('.pic'), function(el) {
el.addEventListener('click', changeSource);
});
function changeSource() {
hidden = document.getElementById("hidden");
hidden.style.display = 'block';
newpic = this.src;
var pic = document.getElementById("picbox");
pic.src = newpic;
}
}
document.addEventListener("DOMContentLoaded", init, false);
function stopSlide() {
var hidden = document.getElementById("hidden");
hidden.style.visibility = 'hidden';
pause.style.display = 'none';
var play = document.getElementById("play");
play.style.display = 'block';
}
The pause and play statements are not relevant to my question, they simply hide the play button and show the pause button if the slideshow is running, and vice versa.
It looks to me like you actually want to stop the rotate() function, rather then commence, since rotate is what is actually changing the images (ie running the slideshow).
There are two ways. The first, like thriqon posted, is to use the clearTimeout function. However I'd recommend doing it like this:
// somewhere in global space
var rotateTimeout;
// in commence
rotateTimeout = window.setInterval(rotate,4000); // this will tell the browser to call rotate every 4 seconds, saving the ID of the interval into rotateTimeout
// in stopSlide
window.clearInterval(rotateTimeout); // this will tell the browser to clear, or stop running, the interval.
The second, which is a bit messier, is to use a sentinel.
// somewhere in global space
var running;
// in commence
running = true;
rotate();
// in stopSlide
running = false;
// in rotate
if (running) {
setTimeout(rotate,4000);
}
You want to use window.clearTimeout using the id you get by call window.setTimeout.
But you should also consider switching to window.setInterval, which gives you an function call every 4 seconds (and not 4 seconds after the last call), the not-needing of repeated calls to re-set the timeout and a better handling of missed events.
See this jsFiddle: http://jsfiddle.net/D3kMG/ for an example on how to use these functions for a simple counter.
As mentioned by #thriqon you will want to use window.clearTimeout. To do this you need to save a "global" reference to the id returned by window.setTimeout, as follows:
var timeoutID;
//... function declarations
function rotate() {
//...
timeoutID = setTimeout( rotate, 4000);
}
Then in your stopSlide function you'll simply call clearTimout( timeoutID ) to stop the rotation.

javascript preloader/progress/percentage

I'm having trouble finding any good information on how to make a javascript(or jquery) progress bar WITH text that tells you the percentage.
I don't want a plug in, I just want to know how it works so that I can adapt it to what I need. How do you preload images and get a variable for the number of images that are preloaded. Also, how do you change html/css and-or call a function, based on the number of images that are loaded already?
<img> elements have an onload event that fires once the image has fully loaded. Therefore, in js you can keep track of the number of images that have loaded vs the number remaining using this event.
Images also have corresponding onerror and onabort events that fire when the image fails to load or the download have been aborted (by the user pressing the 'x' button). You also need to keep track of them along with the onload event to keep track of image loading properly.
Additional answer:
A simple example in pure js:
var img_to_load = [ '/img/1.jpg', '/img/2.jpg' ];
var loaded_images = 0;
for (var i=0; i<img_to_load.length; i++) {
var img = document.createElement('img');
img.src = img_to_load[i];
img.style.display = 'hidden'; // don't display preloaded images
img.onload = function () {
loaded_images ++;
if (loaded_images == img_to_load.length) {
alert('done loading images');
}
else {
alert((100*loaded_images/img_to_load.length) + '% loaded');
}
}
document.body.appendChild(img);
}
The example above doesn't handle onerror or onabort for clarity but real world code should take care of them as well.
What about using something below:
$('#btnUpload').click(function() {
var bar = document.getElementById('progBar'),
fallback = document.getElementById('downloadProgress'),
loaded = 0;
var load = function() {
loaded += 1;
bar.value = loaded;
/* The below will be visible if the progress tag is not supported */
$(fallback).empty().append("HTML5 progress tag not supported: ");
$('#progUpdate').empty().append(loaded + "% loaded");
if (loaded == 100) {
clearInterval(beginLoad);
$('#progUpdate').empty().append("Upload Complete");
console.log('Load was performed.');
}
};
var beginLoad = setInterval(function() {
load();
}, 50);
});
JSFIDDLE
You might also want to try HTML5 progress element:
<section>
<p>Progress: <progress id="p" max=100><span>0</span>%</progress></p>
<script>
var progressBar = document.getElementById('p');
function updateProgress(newValue) {
progressBar.value = newValue;
progressBar.getElementsByTagName('span')[0].textContent = newValue;
} </script>
</section>
http://www.html5tutorial.info/html5-progress.php

Precache a varying number of images in jQuery and call back if all are loaded

I would like to do an animation with the images, but only when they are all loaded.
When I start the animation with images still loading, it looks bad.
The images are dynamically added to the dom by parsing a json requested by javascript on $(document).ready.
After the images are loaded I would like to get a callback.
my core code is like this:
$('.gallery-block').each(function () {
$.ajax({
url: $(this).data("url"),
dataType: 'json',
outerthis: this,
success: function (json) {
$(this.outerthis).data("json", json);
var i = json.length;
while (i--) {
var preload = new Image();
preload.src = json[i];
}
$(this.outerthis).data("loaded", "true");
}
});
});
As you can see I'm trying to preload the images, but I don't know how fickle they are this way. They are not attached to the dom or anything.
The other problem is that I wnat the line
$(this.outerthis).data("loaded", "true");
only to be executed when the images are really preloaded.
Should I iterate a variable on every image's callback?
And probably this.outerthis is a bad design pattern, but I'm new to javascript.
The images don't need to be connected to the DOM to be loaded. What you can do is hook up an onload event to each image (before you set the URL), which will acts as your callback. Just count up the number of responses to onload and you can figure out when they're all loaded.
Something like this (untested):
var i, image,
preload = [], loaded = 0,
images = ["a", "b"], count = images.length;
for(i = 0; i < count; i++) {
image = new Image();
image.onload = onImageLoaded;
image.src = images[i] + ".jpg";
preload[i] = image;
}
function onImageLoaded() {
loaded++;
if(loaded === count) {
alert("done");
}
}

Categories