Add progress bar to lightbox2 - javascript

I was searching a way to add a progress indicator to lightbox2 script. My JS is pretty poor and I need a hint on where to start.
I assume I need to rewrite Image class prototype, to add methods like onprogress. This is well described here
But when I add those methods at the start of the script, they don't operate at all. I tried inserting console.log() to one of them, nothing logged, they just don't execute.
See comments in code below.
What exactly am I doing wrong, please?
//start of the original lightbox.js
//this is the code I've inserted, you can see I've added
//multiple console.log()s here just to check if methods called
Image.prototype.load = function(url){
console.log('1');
var thisImg = this;
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url,true);
xmlHTTP.responseType = 'arraybuffer';
xmlHTTP.onload = function(e) {
var blob = new Blob([this.response]);
thisImg.src = window.URL.createObjectURL(blob);
console.log('2');
};
xmlHTTP.onprogress = function(e) {
thisImg.completedPercentage = parseInt((e.loaded / e.total) * 100);
console.log('3');
};
xmlHTTP.onloadstart = function() {
thisImg.completedPercentage = 0;
console.log('4');
};
xmlHTTP.send();
console.log('5');
};
Image.prototype.completedPercentage = 0;
//original script continues from here
....
//here imgPreloader declared, I assume it inherits methods from
//rewritten Image's prototype above
var imgPreloader = new Image();
imgPreloader.onload = (function(){
this.lightboxImage.src = this.imageArray[this.activeImage][0];
this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
}).bind(this);
//preloader's src changes and his methods should execute here
//but they don't
imgPreloader.src = this.imageArray[this.activeImage][0];

Related

JavaScript image.onload generates infinite loop

I've a situation where I need to work on a user-provided image with two different functions.
Get user input
Process the image and put it back processed
note: code is incomplete and shortened for brevity. Please don't point out the irrelevant.
1. Get user input
var fReader = new FileReader();
fReader.onload = function(e){
image = new Image();
image.onload = function(){
//BEGIN OF RELEVANT SECTION
processOnCanvasAndBack(image, myCallbackToProceed);
};
image.src = e.target.result;
};
fReader.readAsDataURL(src);
2. Process the image and put it back processed
function processOnCanvasAndBack(image) {
var canvas = $('<canvas></canvas>');
canvas.draw(image);
canvas.doSomeStuffLikeRotatingAndColorBalance();
//BEGIN OF RELEVANT SECTION
image.onload = function() {
myCallbackToProceed();
};
image.src = canvas.toDataURL();
}
Problem
The image.onload from 1. calls the function as expected but when I call the second image.src from 2 the first image.onload gets called again, which in turns calls 2 again and.... booooom, infinite loop (console spits too many recursions)
I tried to reset the first call with image.onload = function(){}; in various points, but it doesn't fix the issue (no more recursion, but the functions just stop being called). Right now I'm out of ideas :-(
I would suggest creating two image objects. One for the source image, and a second for the transformed / target image. You avoid mutating existing state and causing infinite loops by repeatedly setting the .src of the same image object in the onload events. Avoid mutating state whenever possible. I would also suggest using the var keyword to define the variables locally instead of in the global scope.
var fReader = new FileReader();
fReader.onload = function(e){
var sourceImage = new Image();
var targetImage = new Image();
sourceImage.onload = function() {
//BEGIN OF RELEVANT SECTION
processOnCanvasAndBack(sourceImage, targetImage, myCallbackToProceed);
}
sourceImage.src = e.target.result;
};
fReader.readAsDataURL(src);
function processOnCanvasAndBack(sourceImage, targetImage, callback) {
var canvas = $('<canvas></canvas>');
canvas.draw(sourceImage);
canvas.doSomeStuffLikeRotatingAndColorBalance();
// BEGIN OF RELEVANT SECTION
targetImage.onload = function() {
callback();
};
targetImage.src = canvas.toDataURL();
}

XMLHttpRequest on load in saveback to calling object

I'm trying to load in many json files for a HTML5 game that will serve as sprite sheets. Previously I've did this synchronously but my new goal is to do this asynchronously.
I have run into a problem though where I'm trying to saving back to the calling object. This is so the information loaded can be used later and so a flag (loaded) can be set so the system knows when a resource has been loaded. Below is my XMLHttpRequest code. I have substituted "spritesheet" for what ever the call should be to save back to the parent.
function SpriteSheet(filename)
{
var tmpFileName = "json/" + filename;
this.loaded = false;
var xhr = new XMLHttpRequest();
xhr.open("GET",tmpFileName,true);
xhr.onload = function(event){
var parsed = JSON.parse(this.responseText);
"spritesheet".img=new Image();
"spritesheet".img.src = "imgs/" + parsed["imgLoc"];
"spritesheet".animations = parsed["animations"];
"spritesheet".sprites = parsed["sprites"];
"spritesheet".loaded = true;
};
xhr.send();
}
Can somebody inform me how I can save back to the the parent or if this is completely the wrong approach can they point me in the direction of a solution.
I found that by creating a var in the 'class' that is a reference to the object and using it in the onload function works, for example:
function SpriteSheet(filename)
{
var tmpFileName = "json/" + filename;
this.loaded = false;
var caller = this;
var xhr = new XMLHttpRequest();
xhr.open("GET",tmpFileName,true);
xhr.onload = function(event){
var parsed = JSON.parse(this.responseText);
caller.img=new Image();
caller.img.src = "imgs/" + parsed["imgLoc"];
caller.animations = parsed["animations"];
caller.sprites = parsed["sprites"];
caller.loaded = true;
};
xhr.send();
}

How to I refer to the `postMessage()` function from my XHR callbacks?

The file uploads work perfectly, I can't get the progess and load events to callback
I have the following in a JavaScript file as my WebWorker code:
UploadFileWorker.js
function uploadFile(url, m) {
var f = m.file;
var fd = new FormData();
fd.append('file', f, f.name);
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function (e) {
m.set('status', (e.loaded / e.total) * 100 );
postMessage(m); // <-- never makes the call, doesn't throw and error either
});
xhr.upload.addEventListener('load', function (e) {
m.set('status',100);
postMessage(m); // <-- never makes the call, doesn't throw and error either
});
xhr.open('POST', url, true);
xhr.send(fd);
}
function getUploadURL(m) {
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function () {
var url = this.responseText;
uploadFile(url,m);
});
xhr.open('GET', '/upload', false);
xhr.send();
}
var q = [];
onmessage = function(e) {
postMessage(e.data);
q.push(e.data);
while (q.length > 0) {
var m = q.pop();
getUploadURL(m);
}
};
The postMessage(e.data); in the onmessage function work perfectly.
The postMessage(m); in the xhr.upload.addEventListeners() callbacks never happen. They worked fine before I tried and move these functions into WebWorker code.
Is this a scope issue? If so, how do I prefix the calls to get them to work.?
For completeness here is how I am defining my instance of Worker and kicking it off.
onButtonClick: function(button, e, eOpts) {
var ugv = this.getUploadGridView();
var fs = this.getFileUploadStore();
var worker = new Worker('/js/UploadFileWorker.js');
worker.onmessage = function(e) {
console.log(e);
};
var selected = ugv.getSelectionModel().getSelection();
Ext.each(selected, function(m) {
var o = {};
o.name = m.get('name');
o.size = m.get('size');
o.status = m.get('status');
o.file = m.file;
worker.postMessage(o);
});
},
Again the actual uploading of the files works great, I can't figure out how to call postMessage(); from inside the xhr callbacks.
This is apparently a bug that has been fixed just recently.
Issue 71433002: Enable XHR upload progress events for Workers. (Closed)
Workaround until Chrome gets updated
xhr.addEventListener(
instead of
xhr.upload.addEventListener(
This has the drawback that progress only gets called for every 1MB, files smaller than 1MB never get a progress event fired.

preload image with ajax

Found this technique of using ajax to preload things at: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/
window.onload = function() {
setTimeout(function() {
// XHR to request a JS and a CSS
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.js');
xhr.send('');
xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.css');
xhr.send('');
// preload image
new Image().src = "http://domain.tld/preload.png";
}, 1000);
};
I noticed that the 'ajax' preloading for this image isn't really ajax at all. It is the same as what I have been using for years already, just setting the url in a new image object's source and letting the browser load it into the cache.
Now imagine that there was an application where, I needed to actually cancel the preloading of the image if it took over a certain amount of time. There really is no good way to do this with just setting the image to src, unlike the xhr.abort() method which stops the loading of an actual xhr request.
Is there any reason that doing some thing like the below wouldn't preload the image just as well and allow the cancellation of the preload request?
function preload(url, timeout){
this.canceltimeout = function(){
clearTimeout(this.timeout);
this.loaded = true;
return false;
}
this.abort = function(){
this.xhr.abort();
this.aborted = true;
}
//creates a closure to bind the functions to the right execution scope
this.$_bind = function(method){
var obj = this;
return function (e){ obj[method](e);};
}
//set a default of 10 second timeout
if(timeout == null){
timeout = 10000;
}
this.aborted = false;
this.loaded = false;
this.xhr = new XMLHttpRequest();
this.xhr.onreadystatechange = this.$_bind('canceltimeout');
this.xhr.open('GET', url);
this.xhr.send('');
this.timeout = setTimeout(this.$_bind('abort'), timeout);
}
var llama = new preload('/image.gif');
show_image();
function show_image(){
if(llama.loaded){
var l = new Image();
l.src = '/image.gif';
application.appendChild(l);
}else if(llama.aborted){
var l = document.createElement('p');
l.innerHTML = 'image.gif got cancelled';
application.appendChild(l);
}else{
setTimeout(show_image, 10);
}
return false;
}
The main drawback is that unless you have configured your webserver to provide future freshness info(an Expires, or Cache-control: max-age http header that is in the future), the web browser may make a second http request to the server when you set the image.src, or just plain actually use the image in the document. If your web server had sent freshness validation headers(last-modified, or e-tag) then the image won't be redownloaded, but the request asking the server for freshness validation will stil be made, which is wasteful and adds latency to the process.
I don't know why, but browsers really like to cache the images when you hold a reference to an Image() object.
If you watch the net panel in a web browsers debug tools, you'll most browsers make the first 2 requests, but not the 3rd. Commenting out the code in the ajax callback, ands you'll see the request made for #3
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"></script>
<script>
var f = "0.png?" + (new Date); // 0.png should not have future freshness info headers
$.get(f, function() {
var i = new Image();
i.src = f;
});
setTimeout(function(){
//#3
var i = new Image();
i.src = f;
}, 5000);
</script>

How do I make an image load synchronously?

I want to create an object that has an image property, but I want the contstructor to finish running only once the image is loaded. Or to describe this with code:
GraphicObject = Class.extend({
//This is the constructor
init: function(){
this.graphic = new Image();
this.graphic.src = 'path/to/file.png';
while(true)
{
this.graphic.onload = function(){break;};
//I know this won't work since the 'break' is on a different context
//but you got what I try to do.
}
}
})
For those who are unfamiliar with the Class notation I'm using in my script, it's based on this
Any ideas?
It is possible, but only with the ancient art of Base64 and Data-URL.
GIF image converted to Base64.
rune.b64
R0lGODlhIwAjAIAAAP///wAAACwAAAAAIwAjAAACf4SPqcsb3R40ocpJK7YaA35FnPdZGxg647kyqId2SQzHqdlCdgdmqcvbHXKi4AthYiGPvp9KVuoNocWLMOpUtHaS5CS54mZntiWNRWymn14tU7c2t6ukOJlKR5OiNTzQ7wb41LdnJ1coeNg3pojGqFZniPU4lTi0d4mpucmpUAAAOw==
JavaScript which loads the converted image form the same server via blocking AJAX.
loader.js
var request = new XMLHttpRequest();
var image = document.createElement('img');
request.open('GET', 'rune.b64', false);
request.send(null);
if (request.status === 200) {
image.src= 'data:image/gif;base64,' + request.responseText.trim();
document.getElementsByTagName("body")[0].appendChild(image);
}
Problems
Some older browsers don't like (big) Data-URLs
Base64 encoding makes images about 37% bigger
The whole UI is blocked till the image is loaded
This is a very-evil way
There is a non-evil way to load images in Javascript synchronously.
loadImage = async img => {
return new Promise((resolve, reject) => {
img.onload = async () => {
console.log("Image Loaded");
resolve(true);
};
});
};
Call it with await anywhere. like this
for(let i=0;i<photos.length;i++){
await loadImage(photos[i]);
}
It will load all images one by one.
Note: Calling function must be async to use await
Put the dependent code in the callback. There is no other non-evil way.
GraphicObject = Class.extend({
//This is the constructor
init: function(){
this.graphic = new Image();
this.graphic.onload = function ()
{
// the rest of the ctor code here
};
this.graphic.src = 'path/to/file.png';
}
});
var timeOut = 5*1000; //ms - waiting for max 5s to laoad
var start = new Date().getTime();
while(1)
if(img.complete || img.naturalWidth || new Date().getTime()-start>timeOut)
break;
Based on this answer.
I wrapped it into function, and it worked!
for (var i = 0, i < asset.length; i++) {
var img = new Image();
img.src = "file:///" + folder + "/" + asset[i].name;
getWdrp(img);
function getWdrp (img) {
img.onload = function(){
// work with the image file
}
}
}
This is an example that worked for me, because before, when I was processing the image without wrapping in the function, it would work async, now it is async.

Categories

Resources