How to pass additional parameters to javascript event object? - javascript

I am using HTML 5 FileReader to read more that one file asynchronously.
I would like to keep track of loading of individual files as more that one files can be added at once.
Now i created div with background 0% for each image, but i am not clear about how to pass this division's id or reference in the onprogress event so that i can track progress and update the div content dynamically.
In simple terms let me know about how to ensure that i am updating the correct progress control associated with the file, when multiple files are uploaded simultaneously? I am not getting my JS right.
var up_file = document.getElementById('multiple_file');
if(up_file.files)
{
for(var x=0; x <up_file.files.length; x++)
{
//console.log(up_file.files.length);
var file = up_file.files[x];
var loadingDiv = document.createElement("div");
var container = document.getElementById('loading_container');
loadingDiv.className = "loading";
loadingDiv.id ="loading_" + x;
loadingDiv.innerHTML = '0%';
container.appendChild(loadingDiv);
var reader = new FileReader();
reader.onprogress = function (evt) {
if (evt.lengthComputable) {
console.dir(evt);
// evt.loaded and evt.total are ProgressEvent properties
var loaded = (evt.loaded / evt.total);
if (loaded < 1) {
console.log(loaded);
}
}
}
var img = document.createElement("img");
img.className = "hide";
reader.onload = (function(aimg) {
return function(e) {
LIB.addEvent(aimg, "load", function(){
var scale = 1;
scale = aimg.width / 200;
aimg.width = aimg.width / scale;
aimg.className = "show";
}, false);
aimg.src = e.target.result;
var li = document.createElement("li");
li.appendChild(aimg);
document.getElementById('img_packet').appendChild(li);
};
})(img);
reader.readAsDataURL(file);
}
}

loadingDiv is still visible inside the onprogress function as a closure is formed. The problem is that it's in a loop, so by the time onprogress is called, loadingDiv will probably have been assigned a new value.
To get around this you can use an extra closure to take a copy of the current value of loadingDiv:
reader.onprogress= function(myloadingdiv) {
return function(evt) {
if (evt.lengthComputable)
myloadingdiv.innerHTML= evt.loaded/evt.total*100+'%';
};
}(loadingDiv);
In ECMAScript Fifth Edition, the bind() method will does this for you more cleanly:
reader.onprogress= function(myloadingdiv, evt) {
if (evt.lengthComputable)
myloadingdiv.innerHTML= evt.loaded/evt.total*100+'%';
}.bind(loadingDiv);
For browsers that don't support bind() yet, you can patch in an implementation thus:
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
if (arguments.length<=1) {
return function() {
return that.apply(owner, arguments);
};
} else {
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
};
}
};
}

Related

JavaScript onload and onerror not being called

var images;
function preloadTrial(actor, event) {
return new Promise(function(res) {
var i = 0;
images = [];
var handler = function(resolve, reject) {
var img = new Image;
var source = '/static/videos/' + actor + '/' + event + '/' + i + '.png';
img.onload = function() {
i++;
resolve(img);
}
img.onerror = function() {
reject()
}
img.src = source;
}
var _catch = function() { res(images) }
var operate = function(value) {
if (value) images.push(value);
new Promise(handler).then(operate).catch(_catch);
}
operate();
})
}
function playSequence(time){
var delta = (time - currentTime) / 1000;
currentFrame += (delta * FPS);
var frameNum = Math.floor(currentFrame);
if (frameNum >= numFramesPlay) {
currentFrame = frameNum = 0;
return;
}else{
requestAnimationFrame(playSequence);
currentImage.src = images[frameNum];
currentTime = time;
console.log("display"+currentImage.src);
}
};
function rightNow() {
if (window['performance'] && window['performance']['now']) {
return window['performance']['now']();
} else {
return +(new Date());
}
};
currentImage = document.getElementById("instructionImage");
// Then use like this
preloadTrial('examples', 'ex1').then(function(value) {
playSequence(currentTime=rightNow());
});
I wrote a Javascript function that is suppose to load a directory full of numbered .png files. However, I do not know the number of items inside the directory beforehand. So I made a function that continues to store images until the source gives me an error. But when I run the code the program does not even enter the .onload and .onerror functions, resulting in an infinite loop.
Edit: This is my current code. It appears that images are correctly assigned and pushed into the array images. But when I attempt to load it onto a img tag (currentImage.src) and run playSequence, it does not display.
You could use promises to handle the pre-loading of the images.
Chain the resolves on the onload event and reject onerror to end the cycle.
function preloadImages(baseurl, extension, starter) {
return new Promise(function(res) {
var i = starter;
var images = [];
// Inner promise handler
var handler = function(resolve, reject) {
var img = new Image;
var source = baseurl + i + '.' + extension;
img.onload = function() {
i++;
resolve(img);
}
img.onerror = function() {
reject('Rejected after '+ i + 'frames.');
}
img.src = source;
}
// Once you catch the inner promise you resolve the outer one.
var _catch = function() { res(images) }
var operate = function(value) {
if (value) images.push(value);
// Inner recursive promises chain.
// Stop with the catch resolving the outer promise.
new Promise(handler).then(operate).catch(_catch);
}
operate();
})
}
To simulate a video player, you can draw on a HTML5 canvas.
function play(canvas, imagelist, refreshRate, frameWidth, frameHeight) {
// Since we're using promises, let's promisify the animation too.
return new Promise(function(resolve) {
// May need to adjust the framerate
// requestAnimationFrame is about 60/120 fps depending on the browser
// and the refresh rate of the display devices.
var ctx = canvas.getContext('2d');
var ts, i = 0, delay = 1000 / refreshRate;
var roll = function(timestamp) {
if (!ts || timestamp - ts >= delay) {
// Since the image was prefetched you need to specify the rect.
ctx.drawImage(imagelist[i], 0, 0, frameWidth, frameHeight);
i++;
ts = timestamp;
}
if (i < imagelist.length)
requestAnimationFrame(roll);
else
resolve(i);
}
roll();
})
}
To test I used ffmpeg to cut a video with the following command:
ffmpeg -i input.mp4 -ss 00:00:14.435 -vframes 100 %d.png
And I used devd.io to quickly create a static folder containing the script and images and a basic index.html.
imageroller.js - with the above code.
var preload = preloadImages('/static/videos/examples/testvid/', 'png', 1);
preload.then(function(value) {
console.log('starting play');
var canvas = document.getElementById("canvas");
play(canvas, value, 24, 720, 400) // ~480p 24fps
.then(function(frame){
console.log('roll finished after ' + frame + ' frames.')
})
});
While the preloading of the images was pretty slow, if you keep the number of frames to an acceptable level you can make some nice loops.
I haven't tested the snippet below (and there are probably cleaner solutions) but the idea should be correct. Basically we have a recursive function loadImages(), and we pass in the images array and a callback function. We wait for our current image to load; if it loads, we push it into images and call loadImages() again. If it throws an error, we know we are finished loading, so we return our callback function. Let me know if you have any questions.
function preloadTrial(actor, event) {
let images = [];
loadImages(images, actor, event, function () {
// code to run when done loading
});
};
function loadImages (images, actor, event, callback) {
let img = new Image();
let i = images.length;
let source ='/static/videos/'+actor+'/'+event+'/'+i+'.png';
img.onload = function() {
images.push(img);
return loadImages(images, actor, event, callback);
}
img.onerror = function() {
return callback(images);
}
img.src = source;
}
The optimal solution would be to provide a server-side API that tells you beforehand, how many Images there are in the directories.
If that is not possible, you should load the images one after the other to prevent excess requests to the server. In this case i would put the image loading code in a separate function and call it if the previous image was loaded successfully, like so:
function loadImage(actor, event, i, loadCallback, errorCallback) {
var image = new Image();
var source ='/static/videos/'+actor+'/'+event+'/'+i+'.png';
image.onload = loadCallback;
image.onerror = errorCallback;
image.src = source;
return image;
}
and then call this function in your while loop and in the loadCallback.

Is FileReader take more time to load?

Below is my code
var uploadIDImage = {
IDPos: {},
IDNeg: {}
};
var FR = new FileReader();
FR.onload = function(e) {
console.log("123");
console.log(e);
$("#UploadIDPos img").remove();
$("#UploadIDPos i").hide();
$('#UploadIDPos').prepend('<img id="IDPosImg" style="width: 140px; height: 80px;"/>');
var img = document.getElementById('IDPosImg');
img.src = FR.result;
uploadIDImage.IDPos.Files = FR.result.split(",")[1];
console.log("11111");
};
if(e.target.files[0]) {
FR.readAsDataURL(e.target.files[0]);
if(originalIDPosSize === undefined) {
var size = Math.round(e.target.files[0].size / 1024 / 1024);
originalIDPosSize = size;
}
else {
totalSize = totalSize + originalIDPosSize;
var size = Math.round(e.target.files[0].size / 1024 / 1024);
}
var remainSize = totalSize - size;
console.log("Remain size : " + remainSize);
$("#remain-size").text(totalSize - size);
totalSize = remainSize;
}
console.log("22222");
console.log(uploadIDImage.IDPos.Files);
What I got from my console.log is first print "22222" and undefined and then "111111".
Why "11111" not print first?
When you do
FR.onload = function(e) {... }
you are setting a callback on the FileReader which is called when the reading operation has successfully completed.
Now you script proceeds and runs console.log("22222");
After a while the callback is invoked and you see the 11111.
Your section of code FR.onload = function(e) { ... } is just defining a handler for the FR object. The FileReader methods like readAsDataURL() are asynchronous -- your program continues after it executes the FR.readAsDataURL(...) statement.
Then later, when the file reading is done, then the function you specified for FR.onload runs. It is indeterminate whether this happens before or after your console.log("22222"); statement.

socket.io image upload via javascript

So I'm doing a simple multiple image upload script using javascript, but socket.io has to be used in order to get the image into the database. In order to run previews I have been taking event.target.result and putting it as the image src on a div. Is there any way I can store the this in an array for each image so that I can transfer it over the socket, and have it load on the other side? When I try to load it into an array, it's always undefined.
for (var i = 0; file = files[i]; i++) {
name[i] = files[i].name;
// if the file is not an image, continue
if (!file.type.match('image.*')) {
continue;
}
reader = new FileReader();
reader.onload = (function (tFile) {
return function (evt) {
var div = document.createElement('div');
var miniDiv = document.createElement('div');
div.id = "photoDiv";
div.innerHTML = '<img style="width: 120px; height: auto;" src="' + evt.target.result + '" />';
div.className = "photos";
var data = evt.target.result;
picture[i] = data;
document.getElementById('filesInfo').appendChild(div);
document.getElementById('previewDiv').appendChild(document.getElementById('filesInfo'));
};
}(file));
reader.readAsDataURL(file);
}
uploadFiles();
}
Don't make functions within a loop like that, it can lead to unexpected things.
I would suggest using JSHint, it's very helpful.
You made two mistakes:
1) You should pass i variable to your closure together with file.
2) The most important: reader.onload is a function that will be called not immediately, but in some delay, and as a result it will be called after uploadFiles() call. That's why you get an empty picture.
Try to rewrite your code as follows:
var done = 0;
var picture = [];
for (var i = 0; file = files[i]; i++) {
name[i] = files[i].name;
// if the file is not an image, continue
if (!file.type.match('image.*')) {
if (++done === files.length) {
uploadFiles();
}
continue;
}
reader = new FileReader();
reader.onload = (function (tFile, index) {
return function (evt) {
//[...]
picture[index] = data;
//[...]
if (++done === files.length) {
//the last image has been loaded
uploadFiles();
}
};
}(file, i));
reader.readAsDataURL(file);
}

JavaScript loading progress of an image

Is there a way in JS to get the progress of a loading image while the image is being loaded?
I want to use the new Progress tag of HTML5 to show the progress of loading images.
I wish there was something like:
var someImage = new Image()
someImage.onloadprogress = function(e) { progressBar.value = e.loaded / e.total };
someImage.src = "image.jpg";
With this, you add 2 new functions on the Image() object:
Image.prototype.load = function(url){
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);
};
xmlHTTP.onprogress = function(e) {
thisImg.completedPercentage = parseInt((e.loaded / e.total) * 100);
};
xmlHTTP.onloadstart = function() {
thisImg.completedPercentage = 0;
};
xmlHTTP.send();
};
Image.prototype.completedPercentage = 0;
And here you use the load function and append the image on a div.
var img = new Image();
img.load("url");
document.getElementById("myDiv").appendChild(img);
During the loading state you can check the progress percentage using img.completedPercentage.
Sebastian's answer is excellent, the best I've seen to this question. There are, however, a few possible improvements. I use his code modified like this:
Image.prototype.load = function( url, callback ) {
var thisImg = this,
xmlHTTP = new XMLHttpRequest();
thisImg.completedPercentage = 0;
xmlHTTP.open( 'GET', url , true );
xmlHTTP.responseType = 'arraybuffer';
xmlHTTP.onload = function( e ) {
var h = xmlHTTP.getAllResponseHeaders(),
m = h.match( /^Content-Type\:\s*(.*?)$/mi ),
mimeType = m[ 1 ] || 'image/png';
// Remove your progress bar or whatever here. Load is done.
var blob = new Blob( [ this.response ], { type: mimeType } );
thisImg.src = window.URL.createObjectURL( blob );
if ( callback ) callback( this );
};
xmlHTTP.onprogress = function( e ) {
if ( e.lengthComputable )
thisImg.completedPercentage = parseInt( ( e.loaded / e.total ) * 100 );
// Update your progress bar here. Make sure to check if the progress value
// has changed to avoid spamming the DOM.
// Something like:
// if ( prevValue != thisImage completedPercentage ) display_progress();
};
xmlHTTP.onloadstart = function() {
// Display your progress bar here, starting at 0
thisImg.completedPercentage = 0;
};
xmlHTTP.onloadend = function() {
// You can also remove your progress bar here, if you like.
thisImg.completedPercentage = 100;
}
xmlHTTP.send();
};
Mainly I added a mime-type and some minor details. Use as Sebastian describes. Works well.
Just to add to the improvements, I've modified Julian's answer (which in turn modified Sebastian's). I've moved the logic to a function instead of modifying the Image object. This function returns a Promise that resolves with the URL object, which only needs to be inserted as the src attribute of an image tag.
/**
* Loads an image with progress callback.
*
* The `onprogress` callback will be called by XMLHttpRequest's onprogress
* event, and will receive the loading progress ratio as an whole number.
* However, if it's not possible to compute the progress ratio, `onprogress`
* will be called only once passing -1 as progress value. This is useful to,
* for example, change the progress animation to an undefined animation.
*
* #param {string} imageUrl The image to load
* #param {Function} onprogress
* #return {Promise}
*/
function loadImage(imageUrl, onprogress) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
var notifiedNotComputable = false;
xhr.open('GET', imageUrl, true);
xhr.responseType = 'arraybuffer';
xhr.onprogress = function(ev) {
if (ev.lengthComputable) {
onprogress(parseInt((ev.loaded / ev.total) * 100));
} else {
if (!notifiedNotComputable) {
notifiedNotComputable = true;
onprogress(-1);
}
}
}
xhr.onloadend = function() {
if (!xhr.status.toString().match(/^2/)) {
reject(xhr);
} else {
if (!notifiedNotComputable) {
onprogress(100);
}
var options = {}
var headers = xhr.getAllResponseHeaders();
var m = headers.match(/^Content-Type\:\s*(.*?)$/mi);
if (m && m[1]) {
options.type = m[1];
}
var blob = new Blob([this.response], options);
resolve(window.URL.createObjectURL(blob));
}
}
xhr.send();
});
}
/*****************
* Example usage
*/
var imgContainer = document.getElementById('imgcont');
var progressBar = document.getElementById('progress');
var imageUrl = 'https://placekitten.com/g/2000/2000';
loadImage(imageUrl, (ratio) => {
if (ratio == -1) {
// Ratio not computable. Let's make this bar an undefined one.
// Remember that since ratio isn't computable, calling this function
// makes no further sense, so it won't be called again.
progressBar.removeAttribute('value');
} else {
// We have progress ratio; update the bar.
progressBar.value = ratio;
}
})
.then(imgSrc => {
// Loading successfuly complete; set the image and probably do other stuff.
imgContainer.src = imgSrc;
}, xhr => {
// An error occured. We have the XHR object to see what happened.
});
<progress id="progress" value="0" max="100" style="width: 100%;"></progress>
<img id="imgcont" />
Actually, in latest chrome you can use it.
$progress = document.querySelector('#progress');
var url = 'https://placekitten.com/g/2000/2000';
var request = new XMLHttpRequest();
request.onprogress = onProgress;
request.onload = onComplete;
request.onerror = onError;
function onProgress(event) {
if (!event.lengthComputable) {
return;
}
var loaded = event.loaded;
var total = event.total;
var progress = (loaded / total).toFixed(2);
$progress.textContent = 'Loading... ' + parseInt(progress * 100) + ' %';
console.log(progress);
}
function onComplete(event) {
var $img = document.createElement('img');
$img.setAttribute('src', url);
$progress.appendChild($img);
console.log('complete', url);
}
function onError(event) {
console.log('error');
}
$progress.addEventListener('click', function() {
request.open('GET', url, true);
request.overrideMimeType('text/plain; charset=x-user-defined');
request.send(null);
});
<div id="progress">Click me to load</div>
Here is a small update of the code of Julian Jensen in order to be able to draw the image in a Canvas after it is loaded :
xmlHTTP.onload = function( e ) {
var h = xmlHTTP.getAllResponseHeaders(),
m = h.match( /^Content-Type\:\s*(.*?)$/mi ),
mimeType = m[ 1 ] || 'image/png';
// Remove your progress bar or whatever here. Load is done.
var blob = new Blob( [ this.response ], { type: mimeType } );
thisImg.src = window.URL.createObjectURL( blob );
thisImg.onload = function()
{
if ( callback ) callback( this );
};
};
for xmlhttpreq v2 check, use:
var xmlHTTP = new XMLHttpRequest();
if ('onprogress' in xmlHTTP) {
// supported
} else {
// isn't supported
}
If you want to process your loaded image, than you have to add one more function, because
thisImg.src = window.URL.createObjectURL(blob)
just starts to process the image as a thread.
You have to add a new a function to the body of load prototype, like
this.onload = function(e)
{
var canvas = document.createElement('canvas')
canvas.width = this.width
canvas.height = this.height
canvas.getContext('2d').drawImage(this, 0, 0)
}
This make me headache to realize :)

jQuery dnd file upload for multiple targets

I'm using jQuery dnd file upload plugin for a project. All example of dnd uploader use id as a selector. For multiple items they used different dropzone declaration.
How can I change the plugin setting for multiple dropzone where the selector will be a class or something else to grab multiple elements with a single dropzone initiation?
Since this is using jQuery, couldn't you use the standard jQuery multiple selector method? It would look like this:
$("#drop1, #drop2, #drop3").dropzone();
Or if you are trying to do a class, you would do this:
$(".drop_zone").dropzone();
This isn't tested, and I have never used this plugin. I'm just assuming this would work since it is based off jQuery.
Since that isn't working, try replacing the code for jquery.dnd-file-upload.js with the following:
(function ($) {
$.fn.dropzone = function (options) {
var opts = {};
var uploadFiles = function (files) {
$.fn.dropzone.newFilesDropped(); for (var i = 0; i < files.length; i++) {
var file = filesi?;
// create a new xhr object var xhr = new XMLHttpRequest(); var upload = xhr.upload; upload.fileIndex = i; upload.fileObj = file; upload.downloadStartTime = new Date().getTime(); upload.currentStart = upload.downloadStartTime; upload.currentProgress = 0; upload.startData = 0;
// add listeners upload.addEventListener("progress", progress, false);
xhr.onload = function (event) {
load(event, xhr);
};
xhr.open(opts.method, opts.url); xhr.setRequestHeader("Cache-Control", "no-cache"); xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhr.setRequestHeader("X-File-Name", file.fileName); xhr.setRequestHeader("X-File-Size", file.fileSize); xhr.setRequestHeader("Content-Type", "multipart/form-data"); xhr.send(file);
$.fn.dropzone.uploadStarted(i, file);
}
};
var drop = function (event) {
var dt = event.dataTransfer; var files = dt.files;
event.preventDefault(); uploadFiles(files);
return false;
};
var log = function (logMsg) {
if (opts.printLogs) {
// console && console.log(logMsg);
}
};
// invoked when the input field has changed and new files have been dropped // or selected var change = function (event) {
event.preventDefault();
// get all files ... var files = this.files;
// ... and upload them uploadFiles(files);
};
var progress = function (event) {
if (event.lengthComputable) {
var percentage = Math.round((event.loaded 100) / event.total); if (this.currentProgress != percentage) {
// log(this.fileIndex + " --> " + percentage + "%");
this.currentProgress = percentage; $.fn.dropzone.fileUploadProgressUpdated(this.fileIndex, this.fileObj, this.currentProgress);
var elapsed = new Date().getTime(); var diffTime = elapsed - this.currentStart; if (diffTime >= opts.uploadRateRefreshTime) {
var diffData = event.loaded - this.startData; var speed = diffData / diffTime; // in KB/sec
$.fn.dropzone.fileUploadSpeedUpdated(this.fileIndex, this.fileObj, speed);
this.startData = event.loaded; this.currentStart = elapsed;
}
}
}
};
var load = function (event, xhr) {
var now = new Date().getTime(); var timeDiff = now - this.downloadStartTime; if (opts.onLoadComplete) {
opts.onLoadComplete(xhr.responseText);
} $.fn.dropzone.uploadFinished(this.fileIndex, this.fileObj, timeDiff); log("finished loading of file " + this.fileIndex);
};
var dragenter = function (event) {
event.stopPropagation(); event.preventDefault(); return false;
};
var dragover = function (event) {
event.stopPropagation(); event.preventDefault(); return false;
};
// Extend our default options with those provided. opts = $.extend({}, $.fn.dropzone.defaults, options);
var id = this.attr("id"); var dropzone = document.getElementById(id);
log("adding dnd-file-upload functionalities to element with id: " + id);
// hack for safari on windows: due to not supported drop/dragenter/dragover events we have to create a invisible <input type="file" /> tag instead if ($.client.browser == "Safari" && $.client.os == "Windows") {
var fileInput = $("<input>"); fileInput.attr({
type: "file"
}); fileInput.bind("change", change); fileInput.css({
'opacity': '0', 'width': '100%', 'height': '100%'
}); fileInput.attr("multiple", "multiple"); fileInput.click(function () {
return false;
}); this.append(fileInput);
} else {
dropzone.addEventListener("drop", drop, true); var jQueryDropzone = $("#" + id); jQueryDropzone.bind("dragenter", dragenter); jQueryDropzone.bind("dragover", dragover);
}
return this;
};
$.fn.dropzone.defaults = {
url: "", method: "POST", numConcurrentUploads: 3, printLogs: false, // update upload speed every second uploadRateRefreshTime: 1000
};
// invoked when new files are dropped $.fn.dropzone.newFilesDropped = function () { };
// invoked when the upload for given file has been started $.fn.dropzone.uploadStarted = function (fileIndex, file) { };
// invoked when the upload for given file has been finished $.fn.dropzone.uploadFinished = function (fileIndex, file, time) { };
// invoked when the progress for given file has changed $.fn.dropzone.fileUploadProgressUpdated = function (fileIndex, file,
newProgress) {
};
// invoked when the upload speed of given file has changed $.fn.dropzone.fileUploadSpeedUpdated = function (fileIndex, file,
KBperSecond) {
};
})(jQuery);
This was suggested by the user rik.leigh#gmail.com here http://code.google.com/p/dnd-file-upload/wiki/howto

Categories