I am doing something similar to /
What I'm doing is Im reading the contents of the selected files one at a time to validate that their lines pass some regex test. After done validating all files, I need to update (enable / disable) some buttons accordingly hence the call back function
Is it possible to have a call back function which will do something after everything is read?
HTML:
<input type="file" id="files" name="files[]" multiple />
Javascipt:
<script>
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var validArray = [];
for (var i = 0, f; f = files[i]; i++) {
//Create new file reader
var r = new FileReader();
//On load call
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
var lines = contents.split('\n');
for(var i=0; i<lines.length; i++){
//Validate regex of line here
//If line does not pass, append file name to validArray and break
}
};
})(f);
r.readAsText(f);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
I am doing something similar to http://www.html5rocks./en/tutorials/file/dndfiles/
What I'm doing is Im reading the contents of the selected files one at a time to validate that their lines pass some regex test. After done validating all files, I need to update (enable / disable) some buttons accordingly hence the call back function
Is it possible to have a call back function which will do something after everything is read?
HTML:
<input type="file" id="files" name="files[]" multiple />
Javascipt:
<script>
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var validArray = [];
for (var i = 0, f; f = files[i]; i++) {
//Create new file reader
var r = new FileReader();
//On load call
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
var lines = contents.split('\n');
for(var i=0; i<lines.length; i++){
//Validate regex of line here
//If line does not pass, append file name to validArray and break
}
};
})(f);
r.readAsText(f);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
Share
Improve this question
edited Jun 20, 2020 at 9:12
CommunityBot
11 silver badge
asked Dec 6, 2012 at 16:52
Omar WagihOmar Wagih
8,7447 gold badges64 silver badges75 bronze badges
2
-
1
The code does not to what you think it does. Every time you select a file, the
ul
is cleared and rewritten with the full list of files, which is kept in theoutput
array (which is exactly the array you are looking for maybe). What is that you are trying to achieve exactly? – Tallmaris Commented Dec 6, 2012 at 17:01 - Hmm. maybe I simplified my example too much. What I'm doing is Im reading the contents of the selected files one at a time to validate that their lines pass some regex test. After done validating all files, I need to update (enable / disable) some buttons accordingly hence the call back function I will update the question with the details – Omar Wagih Commented Dec 6, 2012 at 17:23
4 Answers
Reset to default 5Came here looking for a similar answer. I wanted to call a function after all files were loaded and processed. The solution provided by @Snuffleupagus did not work for me because the function was called after all the files were read, but before they had finished being processed in the onload function. I found a solution around this as follows (not sure if it is the 'cleanest' but it works for me).
var processedCount=0; // global variable
var totalFiles = 0; // global variable
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
totalFiles = files.length; // important
// files is a FileList of File objects. List some properties.
for (var i = 0, f; f = files[i]; i++) {
//Create new file reader
var r = new FileReader();
//On load call
r.onload = (function(theFile){
return function(){
onLoadHandler(this,theFile);
onLoadEndHandler();
};
})(f);
r.readAsText(f);
}
}
function onLoadEndHandler(){
processedCount++;
if(processedCount == totalFiles){
// do whatever - this code will run after everything has been loaded and processed
}
}
I tried to use r.onloadend but it was called too soon. I believe because my function 'onLoadHandler' takes a few seconds to process each file and onloadend is called when the file is done being loaded but before the code within 'onload' has finished running.
Absolutely. Callbacks are just passed as any other normal argument would be, so we'll end up adding another argument to handleFileSelect
and changing the event listener to an anonymous function that calls handleFileSelect
with the extra argument.
I set up a fiddle to give you a quick working demo.
function handleFileSelect(evt, cb) {
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var output = [];
for (var i = 0, f; f = files[i]; i++) {
output.push('<li><strong>'+ escape(f.name) + '</strong>');
}
document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
if(cb) cb();
}
document.getElementById('files').addEventListener('change', function(e){handleFileSelect(e, function(){alert('all done');})}, false);
Breaking it down - added an extra argument to handleFileSelect
and at the end added if(cb) cb();
. That just checks to see if cb exists, if it does, run it as a function.
Then when we go to bind the event handler instead of passing a reference to handleFileSelect
we use an anonymous function - this lets us pass our extra argument.
The anonymous function inside of the anonymous function is just our callback, it could be a reference to a function if you'd rather.
A really clean way to do this is to use async.js reduce method. Async.js gives many nice ways to deal with multiple callbacks. You could use reduce to iterate through the array of file names, and build a reduced value which is an array of the valid lines:
<input type="file" id="files" name="files[]" multiple />
<script type='text/javascript' src='https://github./caolan/async/raw/master/lib/async.js'/>
<script>
var isValidLine = function(text){
// todo: implement
}
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// reduce by starting with empty array in second argument -
// this gets built up with the valid array lines
async.reduce(files, [], function(validLinesSoFar, file, callback){
var r = new FileReader();
// Read file here:
r.onload = function (f) {
var contents = f.target.result;
var lines = contents.split('\n');
for(var i=0; i<lines.length; i++){
if isValidLine(lines[i])
validLinesSoFar.push(lines[i]);
}
callback(null, validLinesSoFar);
};
r.readAsText(file);
}, function(err, validLines){
// gets called after every file iterated through
// result is entire valid array
// do something here with valid array
});
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
I would take a look at jQuery's deferred object
Also a very relevant question that might be applicable to you.
How to fire a callback function after a for-loop is done in Jquery?