I am new to JavaScript. I have already understood how to create an object from a JSON-file with JSON.Parse() and now I need to load multiple local JSONs into an array. I've been googling my problem for a while, but everything that I found was related to single JSON files.
Is there any way to do this in pure JavaScript without any libraries like jQuery and etc.?
P.S.: There is no need to work with web-server or else, the code is running locally.
To do this, you need to first get the actual files. Then, you should parse them.
// we need a function to load files
// done is a "callback" function
// so you call it once you're finished and pass whatever you want
// in this case, we're passing the `responseText` of the XML request
var loadFile = function (filePath, done) {
var xhr = new XMLHTTPRequest();
xhr.onload = function () { return done(this.responseText) }
xhr.open("GET", filePath, true);
xhr.send();
}
// paths to all of your files
var myFiles = [ "file1", "file2", "file3" ];
// where you want to store the data
var jsonData = [];
// loop through each file
myFiles.forEach(function (file, i) {
// and call loadFile
// note how a function is passed as the second parameter
// that's the callback function
loadFile(file, function (responseText) {
// we set jsonData[i] to the parse data since the requests
// will not necessarily come in order
// so we can't use JSONdata.push(JSON.parse(responseText));
// if the order doesn't matter, you can use push
jsonData[i] = JSON.parse(responseText);
// or you could choose not to store it in an array.
// whatever you decide to do with it, it is available as
// responseText within this scope (unparsed!)
}
})
If you can't make an XML Request, you can also use a file reader object:
var loadLocalFile = function (filePath, done) {
var fr = new FileReader();
fr.onload = function () { return done(this.result); }
fr.readAsText(filePath);
}
You can do something like this:
var file1 = JSON.parse(file1);
var file2 = JSON.parse(file2);
var file3 = JSON.parse(file3);
var myFileArray = [file1, file2, file3];
// Do other stuff
// ....
// Add another file to the array
var file4 = JSON.parse(file4);
myFileArray.push(file4);
If you already have an array of un-parsed files you could do this:
var myFileArray = [];
for(var i=0; i<unparsedFileArray.length; i++){
myFileArray.push(JON.parse(unparsedFileArray[i]));
}
Related
I am using sheetJS in order to manipulate excel sheets. My goal is to extract the value of a cell and store it in an array as raw data for later statistical analysis and graphing.
Here is what the function looks like:
function getSheetData()
{
let rawData = [];
/* set up XMLHttpRequest */
var url = "test.xlsx";
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.send();
oReq.onload = function (e) {
var arraybuffer = oReq.response;
/* convert data to binary string */
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
/* Call XLSX */
var workbook = XLSX.read(bstr, {
type: "binary"
});
/* DO SOMETHING WITH workbook HERE */
var sheet_name_list = workbook.SheetNames;
// var worksheet;
sheet_name_list.forEach(function(y) { /* iterate through sheets */
var worksheet = workbook.Sheets[y];
for (z in worksheet) {
/* all keys that do not begin with "!" correspond to cell addresses */
if(z[0] === '!') continue;
// console.log(z + " = " + JSON.stringify(worksheet[z].v));
rawData.push(worksheet[z].v);
}
});
/* Get worksheet */
// console.log(XLSX.utils.sheet_to_json(worksheet, {
// raw: true
// }));
console.log("raw data = " + rawData);
}
// console.log(rawData);
return rawData;
}
The console.log defined as 'raw data' shows all the numbers in one array just how I need it. However, the array named "rawData" returns as undefined by the end of the function.
I am calling the function here:
window.onload = function()
{
const data = getSheetData();
const BenfordTable = calculateBenford(data);
printAsTable(BenfordTable);
printAsGraph(BenfordTable);
}
I get data as an empty array
I have included a picture of the browser window
screenshot of console results in google chrome
data is an empty array because getSheetData() is an asynchronous function - that is to say, you are making an XMLHttpRequest call from within it. If you put console logs within your onload handler and right before your return statement, you will see that the latter runs first. The issue is that when your function returns, the call to the server will not have yet returned.
There are a few different ways of writing asynchronous code, but I think you should start off by passing a callback function to getSheetData() which will be called from within your onload handler. This callback function will be what handles rawData.
Here's roughly how you might do this. I have omitted some of the existing code for brevity, but obviously you will need it.
function getSheetData(callback)
{
let rawData = [];
// ...other code
oReq.onload = function (e) {
var arraybuffer = oReq.response;
// ...other code
callback(rawData); // <-- add this
}
// no need to return anything!
// return rawData;
}
window.onload = function()
{
getSheetData(function (data) {
const BenfordTable = calculateBenford(data);
printAsTable(BenfordTable);
printAsGraph(BenfordTable);
});
}
There are other things you could use to write such code, such as Promises, but that's probably something else to look into. We're also not doing any error handling here, which is also an important concept. The main takeaway here is that you are only handling the rawData once the call to the server has completed.
I am very new to coding and javascript; just a few days in. I was wondering if there was a way to import objects from a text file(separated by lines) to use in my array: replyText. Here is what I'm working with:
// Variables
var theButton = document.getElementById("theButton");
var mainText = document.getElementById("mainText");
var replyText = [...,...,...,...,];
var i = 0;
// Functions
function nextText() {
mainText.innerHTML = replyText[i++ % replyText.length];
}
// MAIN SCRIPT
theButton.onclick = function() {
nextText();
};
You can use XMLHttpRequest to get the .txt file just pass the path of it.
var file = new XMLHttpRequest();
file.open("GET", "file:/../file.txt", false);
file.onreadystatechange = function () {
if (file.readyState === 4) {
if (file.status === 200 || file.status == 0) {
var text = file.responseText;
alert(text);
}
}
}
EDIT: you must pass the absolute path file:///C:/your/path/to/file.txt
For client/browser-side file reading:
You cannot easily read a file on the client-side as you are not allowed direct access to the client's file system. However, you can place a input element of file type in your HTML markup via which the client can load a file for your program to process. For example:
<input type="file" id="file" onchange="readFile()" />
Now when the client selects a file for use, the readFile() function will be called which will read and process the file. Here's an example:
function readFile() {
var file = document.getElementById('file').files[0]; // select the input element from the DOM
var fileReader = new FileReader(); // initialize a new File Reader object
fileReader.onload(function() { // call this function when file is loaded
console.log(this.result); // <--- You can access the file data from this variable
// Do necessary processing on the file
});
fileReader.readAsText(file); // Read the file as text
}
For more information on File Reader, check out the docs.
To add on to Paulo's solution, read below for splitting string by line breaks (new line character)
var replyText = text.split("\n"); // "\n" is new line character
Am using file upload controller to browse images and the selected images should be previewed in the page as image thumbnails.
<input type="file" id="imageSelector" multiple="multiple" />
var uploadImageCtrl = document.querySelector('#imageSelector');
uploadImageCtrl.addEventListener('change', function () {
var files = this.files;
for(var i=0; i<files.length; i++){
preview(this.files[i]);
}
}, false);
After selecting few images, go to next page and do some action. And when going back from that page, all the image previews should be there. I thought of saving these images to IndexedDB, before going to next page. But am not sure how to code for IndexedDB in this case.
Can anyone help me?
File objects are cloneable and can be saved to Indexed DB, either as records on their own or as part of other records.
This example just saves an array of files as a single record (key is "key") to an object store named "images":
// Call with array of images; callback will be called when done.
function save(array_of_files, callback) {
openDB(function(db) {
var tx = db.transaction('images', 'readwrite');
tx.objectStore('images').put(array_of_files, 'key');
tx.oncomplete = function() { callback(); };
tx.onabort = function() { console.log(tx.error); };
});
}
// Callback will be called with array of images, or undefined
// if not previously saved.
function load(callback) {
openDB(function(db) {
var tx = db.transaction('images', 'readonly');
var req = tx.objectStore('images').get('key');
req.onsuccess = function() {
callback(req.result);
};
});
}
function openDB(callback) {
var open = indexedDB.open('my_db');
open.onupgradeneeded = function() {
var db = open.result;
db.createObjectStore('images');
};
open.onsuccess = function() {
var db = open.result;
callback(db);
};
open.onerror = function() { console.log(open.error); };
}
One possible gotcha: HTMLInputElement's files is not an Array itself but an array-like type called FileList. You can convert it to an array with e.g. Array.from(e.files), but a FileList can be cloned (and therefore stored in IDB) so this should "just work".
I have a C++ function which once called consumes input from stdin. Exporting this function to javascript using emscripten causes calls to window.prompt.
Interacting with browser prompt is really tedious task. First of all you can paste only one line at time. Secondly the only way to indicate EOF is by pressing 'cancel'. Last but not least the only way (in case of my function) to make it stop asking user for input by window.prompt is by checking the checkbox preventing more prompts to pop up.
For me the best input method would be reading some blob. I know I can hack library.js but I see some problems:
Reading blob is asynchronous.
To read a blob, first you have to open a file user has to select first.
I don't really know how to prevent my function from reading this blob forever - there is no checkbox like with window.prompt and I'm not sure if spotting EOF will stop it if it didn't in window.prompt case (only checking a checkbox helped).
The best solution would be some kind of callback but I would like to see sime hints from more experienced users.
A way would be to use the Emscripten Filesystem API, for example by calling FS.init in the Module preRun function, passing a custom function as the standard input.
var Module = {
preRun: function() {
function stdin() {
// Return ASCII code of character, or null if no input
}
var stdout = null; // Keep as default
var stderr = null; // Keep as default
FS.init(stdin, stdout, stderr);
}
};
The function is quite low-level: is must deal with one character at a time. To read some data from a blob, you could do something like:
var data = new Int8Array([1,2,3,4,5]);
var blob = new Blob([array], {type: 'application/octet-binary'});
var reader = new FileReader();
var result;
reader.addEventListener("loadend", function() {
result = new Int8Array(reader.result);
});
var i = 0;
var Module = {
preRun: function() {
function stdin() {
if (if < result.byteLength {
var code = result[i];
++i;
return code;
} else {
return null;
}
}
var stdout = null; // Keep as default
var stderr = null; // Keep as default
FS.init(stdin, stdout, stderr);
}
};
Note (as you have hinted), due to the asynchronous nature of the reader, there could be a race condition: the reader must have loaded before you can expect the data at the standard input. You might need to implement some mechanism to avoid this in a real case. Depending on your exact requirements, you could make it so the Emscripten program doesn't actually call main() until you have the data:
var fileRead = false;
var initialised = false;
var result;
var array = new Int8Array([1,2,3,4,5]);
var blob = new Blob([array], {type: 'application/octet-binary'});
var reader = new FileReader();
reader.addEventListener("loadend", function() {
result = new Int8Array(reader.result);
fileRead = true;
runIfCan();
});
reader.readAsArrayBuffer(blob);
var i = 0;
var Module = {
preRun: function() {
function stdin() {
if (i < result.byteLength)
{
var code = result[i];
++i;
return code;
} else{
return null;
}
}
var stdout = null;
var stderr = null;
FS.init(stdin, stdout, stderr);
initialised = true;
runIfCan();
},
noInitialRun: true
};
function runIfCan() {
if (fileRead && initialised) {
// Module.run() doesn't seem to work here
Module.callMain();
}
}
Note: this is a version of my answer at Providing stdin to an emscripten HTML program? , but with focus on the standard input, and adding parts about passing data from a Blob.
From what I understand you could try the following:
Implement selecting a file in Javascript and access it via Javascript Blob interface.
Allocate some memory in Emscripten
var buf = Module._malloc( blob.size );
Write the content of your Blob into the returned memory location from Javascript.
Module.HEAPU8.set( new Uint8Array(blob), buf );
Pass that memory location to a second Emscripten compiled function, which then processes the file content and
Deallocate allocated memory.
Module._free( buf );
Best to read the wiki first.
I am doing something similar to http://www.html5rocks.com/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>
Came 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.com/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?