FileReader and Callbacks - javascript

I am struggling trying to re-work the idea/code found here (basic premise being working out a file type by simply looking at the first few bytes of data).
Ive got the bare bones of doing what i want - see JSFIDDLE.
Heres my code:
function readTheFiles(file){
var fileReader = new FileReader();
fileReader.onloadend = function(e) {
var fileHeader = new Uint8Array(e.target.result).subarray(0, 4);
var header = "";
for (var q = 0; q < fileHeader.length; q++) {
header += fileHeader[q].toString(16);
}
alert(header);
return header;
};
fileReader.readAsArrayBuffer(file);
}
$("#input-id").on('change', function(event) {
var files = event.target.files;
var i = 0;
for (var i = 0, file; file = files[i]; i++) {
var headString = readTheFiles(file);
alert(headString);
}
});
From what ive read (example 1, example 2) i am sure that the issue lies with calling a callback in the readTheFiles function - presumably the code is calling the alert(headString) line before the files have been loaded (hence why the alert within the readTheFiles function gives the expected result).
Im keen to understand the principal, rather than just get a solution, so any pointers/advice/assistance would be gratefully received.
Many Thanks

I am replying to your question about why your alert(headstring) call tells you "undefined". There may be better ways of discovering what type of file you are dealing with.
You are using an asynchronous process. I've modified and commented your code so that you can see what order things are happening in. You'll see that I have created a treat function in the same scope as your on("change", ...) function. Then I've passed this function as an argument to the readTheFiles() function, so that it can be called back later when the file has been read in.
function readTheFiles(file, callback, index){
var fileReader = new FileReader();
// 3. This function will be called when readAsArrayBuffer() has
// finished reading the file
fileReader.onloadend = function(e) {
var fileHeader = new Uint8Array(e.target.result).subarray(0, 4);
var header = "";
for (var q = 0; q < fileHeader.length; q++) {
header += fileHeader[q].toString(16);
}
callback(header, index);
};
// 2. This gets called almost as soon as fileReader has been created
fileReader.readAsArrayBuffer(file);
}
$("#input-id").on('change', function(event) {
var files = event.target.files;
var i = 0;
for (var i = 0, file; file = files[i]; i++) {
// 1. This is executed for each file that you selected, immediately
// after selection. The treat() function is sent as a callback.
// It will be called later, when the file has been read in.
// Passing `i` as an argument allows you to see which order the
// files are treated in.
var headString = readTheFiles(file, treat, i);
}
// 4. This is called by the callback(header) command inside the
// readTheFiles() function. The `treat` function was sent to
// readTheFiles as the `callback` argument. Putting brackets()
// after the function name executes it. Any value put inside
// the brackets is sent as an argument to this function.
function treat(header, index) {
alert("Header for file " + index + ":" + header);
}
});

Related

Call variable value in another .js file

I have 2 soy.js and lib-dialogs.js files.
I need to make lib-dialogs pass the value of the lineCount variable to soy.js.
I was able to do this with localStorage but because it saves in a cookie it does not update the values correctly.
In lib-dialogs there is a function called BlocklyDialogs.congratulations that calls the necessary data.
FIle:lib-dialogs.js
BlocklyDialogs.congratulations = function() {
// Add the user's code.
if (BlocklyGames.workspace) {
var linesText = document.getElementById('dialogLinesText');
linesText.textContent = '';
// Line produces warning when compiling Puzzle since there is no JavaScript
// generator. But this function is never called in Puzzle, so no matter.
var code = Blockly.JavaScript.workspaceToCode(BlocklyGames.workspace);
code = BlocklyInterface.stripCode(code);
var noComments = code.replace(/\/\/[^\n]*/g, ''); // Inline comments.
noComments = noComments.replace(/\/\*.*\*\//g, ''); /* Block comments. */
noComments = noComments.replace(/[ \t]+\n/g, '\n'); // Trailing spaces.
noComments = noComments.replace(/\n+/g, '\n'); // Blank lines.
noComments = noComments.trim();
var lineCount = noComments.split('\n').length;
var pre = document.getElementById('containerCode');
pre.textContent = code;
if (typeof prettyPrintOne == 'function') {
code = pre.innerHTML;
code = prettyPrintOne(code, 'js');
pre.innerHTML = code;
}
if (lineCount == 1) {
var text = BlocklyGames.getMsg('Games_linesOfCode1');
} else {
var text = BlocklyGames.getMsg('Games_linesOfCode2')
.replace('%1', String(lineCount));
}
linesText.appendChild(document.createTextNode(text));
}
FIle:soy.js
example "var count = BlocklyDialogs.congratulations(lineCount);"
In soy.js I need to receive the values of lineCount. I've already managed to do this using localStorage but I needed to do something more direct.
In testing I verified that the problem is in the lineCount variable because it is not passing a value to any variable even within the file itself.
I created a variable outside the blocklyDialogs.congratulations function and entered a value of 5.
I called the variable in the soy.js file and got it normally.
I need to make the lineCount pass its value.
You can use event driven programming, pub-sub model.
class EventManager {
constructor(...listeners) {
this.listeners = listeners;
}
register(fn) {
const id = this.listeners.push(fn);
return () => {
this.listeners.splice(id - 1, 1);
};
}
emit(data) {
this.listeners.forEach(fn => fn(data));
}
}
const pushEvents = new EventManager();
// JS 1
const unsubscribe1 = pushEvents.register(x => {
console.log("event:", x);
});
pushEvents.register(x => {
console.log("event:", x);
});
// JS 2
pushEvents.emit("Tets data");
//Output
// event: Tets data
// event: Tets data
unsubscribe1();
pushEvents.emit("Tets data2");
//Output
// event: Tets data2
.as-console-row {color: red!important}

Parse multiple files with javascript: the same file in for-loop

I have such code:
function processFiles(e) {
var filesInput = $('#files').prop('files');
var i, f;
for (i = 0, f = filesInput[i]; i != filesInput.length; ++i) {
var name = f.name;
console.log(name); //why here is the same file, even if i select in file input 2 different?
var reader = new FileReader();
reader.onload = function(e) {
var myFile = e.target.result;
console.log(myFile); //why here is the same file, even if i select in file input 2 different?
};
reader.readAsBinaryString(f);
}
}
$('#sbmt').click(function(e) {
processFiles();
});
but when i try to parse multiple files, i got the same file in for loop & .onload callback
what i do wrong?
why here is the same file, even if i select in file input 2 different?
Because nothing updates f in the for loop. You've set f in the initialization expression, but you don't update it in the update expression.
If you want to use the for to control f, be sure you update f:
for (i = 0, f = filesInput[i]; i != filesInput.length; ++i, f = filesInput[i]) {
// -------------------------------------------------------^^^^^^^^^^^^^^^^^^^
...but at that point, you're duplicating code; instead, I'd just move the sssignment into the loop body:
for (i = 0; i != filesInput.length; ++i) {
f = filesInput[i]
...or more likely I'd probably use forEach:
function processFiles(e) {
Array.from($('#files').prop('files')).forEach(function(f) {
var name = f.name;
var reader = new FileReader();
reader.onload = function(e) {
var myFile = e.target.result;
console.log(myFile);
};
reader.readAsBinaryString(f);
});
}
(Note that that uses Array.from from ES2015; you'll need a polyfill to use that in the wild for now...)
My 2 cents:
Took a crack at using Response (part of the new fetch api) to get the buffer with help of es6 and Promises
function processFiles(e) {
let files = $('#files')[0].files
let buffers = [...files].map(file => new Response(file).arraybuffer())
Promise.all(buffers).then(buffers => {
// concatinate(buffers)
})
}

Nested for loop with inner functions

In javascript For loop it's works fast and inner functions are not called. I am trying to store images in database using cordova. In my code for loop works fast and finished but I didn't get any base64 images.
for(var i = 0; i < pages.length; i++)
{
var cat_img = res.Catalogue[0].Catalogue_img;
var catalogue_image_id = cat_img[i].catalogue_image_id;
var catalogue_image = cat_img[i].catalogue_image;
getBase64FromImage(catalogue_image,function (baseData64) {
console.log("baseData64===="+baseData64);
insertPageData (catalogue_image, catalogue_image_id);
},function (error) {
console.log("error====="+error);
});
}
Thanks in advance!!
Your callback "function (baseData64)" will be executed only when the image is downloaded and converted to base64. At that point, the value of "calalogue_image" and "catalogue_image_id" will contain the value of the last element in the loop - with "i = pages.length"
That is, your for loop finishes probably before even the first image is downloaded.
Actually I think the valid behaviour is that you should see only the last image, with "i = pages.length" in the database.
Try this
var processImage = function (cImg,cImgId){
var catalogueImage = cImg;
var catalogueImageId = cImgId;
return function (baseData64) {
console.log("baseData64===="+baseData64);
insertPageData (catalogueImage, catalogueImageId);
}
}
for(var i = 0; i < pages.length; i++)
{
var cat_img = res.Catalogue[0].Catalogue_img;
var catalogue_image_id = cat_img[i].catalogue_image_id;
var catalogue_image = cat_img[i].catalogue_image;
getBase64FromImage(catalogue_image,
processImage(catalogue_image, catalogue_image_id) ,
function (error) {
console.log("error====="+error);
}
);
}

How does jQuery do async:false in its $.ajax method?

I have a similar question here, but I thought I'd ask it a different way to cast a wider net. I haven't come across a workable solution yet (that I know of).
I'd like for XCode to issue a JavaScript command and get a return value back from an executeSql callback.
From the research that I've been reading, I can't issue a synchronous executeSql command. The closest I came was trying to Spin Lock until I got the callback. But that hasn't worked yet either. Maybe my spinning isn't giving the callback chance to come back (See code below).
Q: How can jQuery have an async=false argument when it comes to Ajax? Is there something different about XHR than there is about the executeSql command?
Here is my proof-of-concept so far: (Please don't laugh)
// First define any dom elements that are referenced more than once.
var dom = {};
dom.TestID = $('#TestID'); // <input id="TestID">
dom.msg = $('#msg'); // <div id="msg"></div>
window.dbo = openDatabase('POC','1.0','Proof-Of-Concept', 1024*1024); // 1MB
!function($, window, undefined) {
var Variables = {}; // Variables that are to be passed from one function to another.
Variables.Ready = new $.Deferred();
Variables.DropTableDeferred = new $.Deferred();
Variables.CreateTableDeferred = new $.Deferred();
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'drop table Test;',
[],
Variables.DropTableDeferred.resolve()
// ,WebSqlError
);
});
$.when(Variables.DropTableDeferred).done(function() {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'CREATE TABLE IF NOT EXISTS Test'
+ '(TestID Integer NOT NULL PRIMARY KEY'
+ ',TestSort Int'
+ ');',
[],
Variables.CreateTableDeferred.resolve(),
WebSqlError
);
});
});
$.when(Variables.CreateTableDeferred).done(function() {
for (var i=0;i < 10;i++) {
myFunction(i);
};
Variables.Ready.resolve();
function myFunction(i) {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'INSERT INTO Test(TestID,TestSort) VALUES(?,?)',
[
i
,i+100000
]
,function() {}
,WebSqlError
)
});
};
});
$.when(Variables.Ready).done(function() {
$('#Save').removeAttr('disabled');
});
}(jQuery, window);
!function($, window, undefined) {
var Variables = {};
$(document).on('click','#Save',function() {
var local = {};
local.result = barcode.Scan(dom.TestID.val());
console.log(local.result);
});
var mySuccess = function(transaction, argument) {
var local = {};
for (local.i=0; local.i < argument.rows.length; local.i++) {
local.qry = argument.rows.item(local.i);
Variables.result = local.qry.TestSort;
}
Variables.Return = true;
};
var myError = function(transaction, argument) {
dom.msg.text(argument.message);
Variables.result = '';
Variables.Return = true;
}
var barcode = {};
barcode.Scan = function(argument) {
var local = {};
Variables.result = '';
Variables.Return = false;
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'SELECT * FROM Test WHERE TestID=?'
,[argument]
,mySuccess
,myError
)
});
for (local.I = 0;local.I < 3; local.I++) { // Try a bunch of times.
if (Variables.Return) break; // Gets set in mySuccess and myError
SpinLock(250);
}
return Variables.result;
}
var SpinLock = function(milliseconds) {
var local = {};
local.StartTime = Date.now();
do {
} while (Date.now() < local.StartTime + milliseconds);
}
function WebSqlError(tx,result) {
if (dom.msg.text()) {
dom.msg.append('<br>');
}
dom.msg.append(result.message);
}
}(jQuery, window);
Is there something different about XHR than there is about the executeSql command?
Kind of.
How can jQuery have an async=false argument when it comes to Ajax?
Ajax, or rather XMLHttpRequest, isn't strictly limited to being asynchronous -- though, as the original acronym suggested, it is preferred.
jQuery.ajax()'s async option is tied to the boolean async argument of xhr.open():
void open(
DOMString method,
DOMString url,
optional boolean async, // <---
optional DOMString user,
optional DOMString password
);
The Web SQL Database spec does also define a Synchronous database API. However, it's only available to implementations of the WorkerUtils interface, defined primarily for Web Workers:
window.dbo = openDatabaseSync('POC','1.0','Proof-Of-Concept', 1024*1024);
var results;
window.dbo.transaction(function (trans) {
results = trans.executeSql('...');
});
If the environment running the script hasn't implemented this interface, then you're stuck with the asynchronous API and returning the result will not be feasible. You can't force blocking/waiting of asynchronous tasks for the reason you suspected:
Maybe my spinning isn't giving the callback chance to come back (See code below).

multiple asynchronous callbacks javascript how do I resolve this?

I got myself into a tangle which probably involves multiple asynchronous callback situations.
I have a javascript function called populatePageArea()
Inside populatePageArea it traverses through this array-like variable called pages amongst other code.
function populatePagesArea() {
// there was some code before the for loop
for (var i=0, l=pages.length; i<l; i++) {
addToPagesArea(pages[i], "");
}
// some code after...
}
Inside addToPagesArea function, I used the FileAPI from HTML 5 to preview the drag'n dropped files amongst other code.
function addToPages(file, front) {
// there was some code before..
reader = new FileReader();
reader.onload = (function (theDiv) {
return function (evt) {
var backgroundimage = "url(" + evt.target.result + ")";
theDiv.css("background-image", backgroundimage);
var sizeSettings = getSizeSettingsFromPage(file, calculateRatio);
};
}(imageDiv));
// step#3 execute file reader
reader.readAsDataURL(file);
// there was some code after..
}
So every time I previewed the file, I also attempted to do some calculation on the dimensions of the file.
function getSizeSettingsFromPage(file, whenReady) {
reader = new FileReader();
reader.onload = function(evt) {
var image = new Image();
image.onload = function(evt) {
var width = this.width;
var height = this.height;
var filename = file.name;
if (whenReady) {
whenReady(width, height, filename);
}
};
image.src = evt.target.result;
};
reader.readAsDataURL(file);
}
function calculateRatio(width, height, filename) {
var ratio = width/height;
var object = new Object();
object['height'] = width;
object['width'] = height;
object['ratio'] = ratio;
object['size'] = 'Original';
for (var size in SIZES) {
var min = SIZES[size].ratio - 0.01;
var max = SIZES[size].ratio + 0.01;
if (ratio <= max && ratio >= min) {
object['size'] = size;
}
}
pageSizes.add(filename, object);
}
pageSizes as seen in calculateRatio is a global variable which is an array-like type of variable.
It is definitely empty BEFORE populatePagesArea gets called.
Here is the situation:
my code is:
populatePagesArea();
getMajorityPageSize(); // this acts on the supposedly non-empty pageSizes global variable
But because I think the calculateRatio has not been called on ALL the previewed images, the pageSizes is always empty when getMajorityPageSize is called.
how can i make sure that after populatePagesArea is called, getMajorityPageSize gets triggered ONLY after all the pages have undergone calculateRatio function?
I believe this is asynchronous callback. But I am not sure how to do it for an array of objects that will need to undergo an async callback function like calculateRatio.
The simple solution (I have marked my changes with // ***:
// ***
var totalPages;
function populatePagesArea() {
// there was some code before the for loop
// ***
totalPages = pages.length;
for (var i=0, l=pages.length; i<l; i++) {
addToPagesArea(pages[i], "");
}
// some code after...
}
function addToPages(file, front) {
// there was some code before..
reader = new FileReader();
reader.onload = (function (theDiv) {
return function (evt) {
var backgroundimage = "url(" + evt.target.result + ")";
theDiv.css("background-image", backgroundimage);
var sizeSettings = getSizeSettingsFromPage(file, calculateRatio);
// *** Check to see if we're done after every load
checkPagesReady();
};
}(imageDiv));
// step#3 execute file reader
reader.readAsDataURL(file);
// there was some code after..
}
// *** Call getMajorityPageSize() here, only after all pages have loaded.
function checkPagesReady() {
if (pageSizes.length >= totalPages)
getMajorityPageSize();
}
The better solution if you're going to be dealing with more asynchronous things later on would be to refactor your code using promises. Promises is an API designed for dealing with asynchronous programming in a systematic and organized way. It'll make your life a lot easier if you're going to be doing more async work. There's a lot of free libraries that support promises, one of the major players is Q.js.

Categories