Waiting for a file to load (onload JavaScript) - javascript

I want to read a text from a file and return it in a function. So here's the important part of my code:
function getFileRequest(id, contentType, callback) {
var val = "x";
if (window.File && window.FileReader && window.FileList && window.Blob) {
var element = document.getElementById(id);
var file = element.files[0];
if(file != null) {
if(file.type.match("text/xml")){
var r;
r = new FileReader();
r.onload = function (e) {
val = e.target.result;
}
r.readAsText(file);
}
else
alert("Wrong file format");
}
} else {
alert('The File APIs are not fully supported by your browser.');
}
alert(val);
if(val == null)
return "";
else
return getRequestBody(id,contentType,val);
}
I want to pass the text to a variable called "val". But, as it seems to me at least, alert(val) is always showing default "x" because probably it's not waiting for onload function to be executed. Am I right at all?
How can I get an access to that text then? Is there a way to wait for an excecution?

Of course the alert isn't in the onload function, so it's called immediately.
You may do that :
var val = "x";
//... code to load a file variable
var r;
r = new FileReader();
r.onload = function (e) {
val = e.target.result;
r.readAsText(file);
alert(val);
};
You cannot wait and stop the execution of your code, so the general idea is to defer it using a callback.
Supposing the code you show is really to be done in two parts, one doing file manipulation and the other one using it, it could be like this :
function fetchVal(callback) {
var val = "x";
//... code to load a file variable
var r;
r = new FileReader();
r.onload = function (e) {
val = e.target.result;
r.readAsText(file);
callback(val);
};
}
fetchVal(function(val){
alert(val);
// use val
});

Related

Iterating over Swagger YAML File to dynamically generate a list of properties

I am trying to create a script that can accept a swagger yml file (like the example petstore.yaml), and generate a list of used "attributes" from the file.
This involves the parsing of yaml, then iterating over all elements in the json object to return the required data. I intend to traverse all the paths to identify valid responses, but for now I just want to filter out all definitions specified in the yaml file, then for each def, output the list of properties.
a sample yaml file can be downloaded here
at the end of the day, i would like to generate a string for each attribute which shows something like
<filename>~Pet~id~integer~int64
<filename>~Pet~name~string~
<filename>~Pet~tag~string~
for this, i need to locate the 'definitions' node, and iterate over all sub-nodes to read the info.
I am struggling to get the logic right for a yaml styled file.. Below is my working code.
It just feels to me like I have over-complicated the iterative looping (maybe a better solution would be to use regex?
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<title>Read YAML</title>
</script><script src='https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.13.1/js-yaml.min.js'>
</script>
<body>
<input type="file" id="file-input" />
<h3>Contents of the file:</h3>
<pre id="file-content"></pre>
<div id='output'></div>
</body>
<script>
var yaml = window.jsyaml;
</script>
<script src="app.js"></script>
</html>
my javascript file
var body = '';
var mystring = ''
function readSingleFile(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
displayContents(contents);
};
reader.readAsText(file);
}
function displayContents(contents) {
var element = document.getElementById('file-content');
var doc = yaml.load(contents);
// Process the file.
// Process only the definitions section of the file
var definition = doc.definitions
console.log (definition) ;
for(var def in definition) {
if(definition.hasOwnProperty(def)) {
var node = definition[def]
if (typeof(node) === 'object') {
// loop through the definitions
processContents(node)
}
}
}
function processContents(obj) {
for (var item in obj) {
var definitions = obj[item]
if (typeof definitions === 'object'){
for (var attribute in definitions) {
// HELP -- if there is an attribute called "properties" then iterate over all properties and create the concatenated string
// Broken from here !!!!
if (attribute.properties) {
for (var param in attribute.properties) {
mystring = param.name + '~' + param.type + '~' + param.description + '~' + param.format
}
}
}
}
}
}
document.getElementById('output').innerHTML = body;
}
document.getElementById('file-input')
.addEventListener('change', readSingleFile, false);
I am running out of time so I will leave it here. It's not as refined as it is but I hope it helps you.
So I did a recursive function to iterate through the objects. The function takes in an object and a prefix. The prefix will be used for printing the details. The exclude prefixes is used to not show certain field names and the exclude types is to not print certain types. Loop through the fields of an object an catch format, type, description and whatever you want to catch. When done looping the fields of an object check if the type field is populated. If it is then log the parameter details.
var body = '';
var mystring = ''
function readSingleFile(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
displayContents(contents);
};
reader.readAsText(file);
}
function displayContents(contents) {
console.log('---contents---')
console.log(contents)
var element = document.getElementById('file-content');
console.log('---element----')
console.log(element)
var doc = yaml.load(contents);
console.log('---doc')
console.log(doc)
// Process the file.
// Process only the definitions section of the file
var definition = doc.definitions
console.log('---definition---')
console.log (definition) ;
processContents2(doc.definitions)
function processContents2(obj, prefix) {
const excludePrefixes = ['properties']
const excludeTypes = ['object', 'array']
let format = ''
let type = ''
let description = ''
let count = 0;
for (var field in obj) {
if (typeof obj[field] === 'object') {
processContents2(obj[field], (prefix || '') + (excludePrefixes.indexOf(field) === -1 ? '~' + field : ''))
} else {
if (field === 'format') {
format = obj[field]
} else if (field === 'type') {
type = obj[field]
} else if (field === 'description') {
description = obj[field]
}
}
}
if (type && (excludeTypes.indexOf(type) === -1)) {
console.log((prefix || '') + '~' + type + (description ? '~' + description : '') + (format ? '~' + format : ''))
}
}
document.getElementById('output').innerHTML = body;
}
document.getElementById('file-input')
.addEventListener('change', readSingleFile, false);

Return true/false from asynchronous function to use in synchronous if()

I'm creating simple upload form (for .zip files) and I want to validate if zip contains all files which will be necessary later.
so I have a function isZipCorrect():
isZipCorrect = function() {
'use strict';
if (this.name.slice(-3) === 'zip') {
var fileReader = new FileReader();
var zip = new JSZip();
var shpCorrect = false;
fileReader.onload = function() {
var zip = new JSZip(this.result);
shpCorrect = zip.file(/.*?/).every(function(file) {
return (file.name.slice(-3) === 'shp' ||
file.name.slice(-3) === 'dbf' ||
file.name.slice(-3) === 'shx');
});
console.log(shpCorrect);
};
fileReader.readAsArrayBuffer(this.file);
return shpCorrect;
} else {
return true;
}
and I use it in if(isZipCorrect()) before XMLHttpRequest.
I assume that the problem is asynchronous function (fileReader.onload) which end operation when whole code is already ended. But I don't want to call send function from fileReader.onload because for me checking is Zip correct must be optional (you should be able to upload other files which will go through without 'parsing')
You could use jQuery.Deferred() to do something like this:
validateZipFile = function(file) {
var
deferred = $.Deferred(),
fileReader = new FileReader();
fileReader.onload = function() {
var zip = new JSZip(this.result);
var isCorrect = zip.file(/.*?/).every(function(file) {
return /\.(shp|dbf|shx)$/i.test(file.name)
});
isCorrect ? deferred.resolve() : deferred.reject();
};
if( !/\.(zip)$/i.test(file.name) ) {
deferred.reject()
} else {
fileReader.readAsArrayBuffer(file)
}
return deferred.promise()
};
and call it like so:
validateZipFile(file).done(submitForm).fail(handleBadZip);
where 'submitForm' and 'handleBadZip' are functions you've previously defined
you can pass a callback function as a parameter to isZipCorrect and in callback function you can use send function:
isZipCorrect = function(callback) {
'use strict';
if (this.name.slice(-3) === 'zip') {
// all the existing code
fileReader.onload = function() {
var zip = new JSZip(this.result);
shpCorrect = zip.file(/.*?/).every(function(file) {
callback((file.name.slice(-3) === 'shp' ||
file.name.slice(-3) === 'dbf' ||
file.name.slice(-3) === 'shx'));
});
};
fileReader.readAsArrayBuffer(this.file);
//return shpCorrect;
} else {
callback(true);
}

How to create image file on google drive having BinaryString or any of FileReader options

I'm passing data to the google application wich published on the web using Post method. Here is the code of the sever side:
function doPost(e) {
var params, result, responce;
if(typeof e !== 'undefined')
params = (e.parameters);
if (params['type'] == 'get') {
//do the get;
}
else {
try {
result = reformat_src(e.parameters);
responce = process_result(result);
}
catch(e) {
return ContentService.createTextOutput(JSON.stringify(e));
}
}
return ContentService.createTextOutput(JSON.stringify(responce));
}
function reformat_src(request) {
var prop, obj;
obj = {};
for (prop in request) {
obj[prop] = request[prop][0];
}
return obj;
}
In process_result function I'm trying to create an image file on google drive
function process_result(result) {
var folder;
folder = DriveApp.getRootFolder();
folder.createFile('test', result['blob'], MimeType.PNG);
}
I know that result['blob'] contains the png file that I've got with
var reader = new FileReader();
reader.readAsBinaryString(f);
But I failed to create a file. Does anybody know how to create an image file having data from FileReader? I can pass to GAS any of type:
readAsBinaryString
readAsText
readAsDataURL
readAsArrayBuffer
Thank you in advance!
As usual I found answer by myself. I need to use readAsDataURL and this function:
function uploadArquivoParaDrive(base64Data, nomeArq, idPasta) {
try{
var splitBase = base64Data.split(','),
type = splitBase[0].split(';')[0].replace('data:','');
var byteCharacters = Utilities.base64Decode(splitBase[1]);
var ss = Utilities.newBlob(byteCharacters, type);
ss.setName(nomeArq);
var file = DriveApp.getFolderById(idPasta).createFile(ss);
return file.getName();
}catch(e){
return 'Erro: ' + e.toString();
}
}
found here

FileReader onload with result and parameter

I can't manage to get both the result of the filereader and some parameters in a onload function. This is my code:
HTML of control:
<input type="file" id="files_input" multiple/>
Javascript function:
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
reader = new FileReader();
reader.onload = function(){
var data = $.csv.toArrays(this.result,{separator:'\t'});
};
reader.readAsText(file);
}
}
Add event:
files_input.addEventListener("change", openFiles, false);
I use the filereader.result, in the onload function. If I use a parameter, like file, for this function, I can't not access to the result anymore. For example I'd like to use file.name in the onload function. How to resolve this issue ?
Try wrapping your onload function in another function. Here the closure gives you access to each file being processed in turn via the variable f:
function openFiles(evt){
var files = evt.target.files;
for (var i = 0, len = files.length; i < len; i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = (function(f) {
return function(e) {
// Here you can use `e.target.result` or `this.result`
// and `f.name`.
};
})(file);
reader.readAsText(file);
}
}
For a discussion of why a closure is required here see these related questions:
JavaScript closure inside loops – simple practical example
Javascript infamous Loop issue?
You should use closure at 'onload' handler.
Example: http://jsfiddle.net/2bjt7Lon/
reader.onload = (function (file) { // here we save variable 'file' in closure
return function (e) { // return handler function for 'onload' event
var data = this.result; // do some thing with data
}
})(file);
Use
var that = this;
to access external variables in the function scope.
function(){
that.externalVariable //now accessible using that.___
}
My scenario - Using Angular 9.
I struggled with this for a long time, I just couldn't seem to get it to work.
I found the following to be a really elegant solution to access external variables inside a function() block.
public _importRawData : any[];
importFile(file){
var reader = new FileReader();
reader.readAsArrayBuffer(file);
var data;
var that = this; //the important bit
reader.onloadend = await function(){
//read data
that._importRawData = data; //external variables are now available in the function
}
One of the important parts in the above code is the var keyword, which scopes variables outside the function block.
However, when I accessed the value of data after the function block, it was still undefined as the function executed after the other code. I tried async and await, but could not get it to work. And I could not access data outside of this function.
The saving grace was the var that = this line.
Using that allows external variables to be accessed inside the function. So I could set that variable inside the function scope and not worry about when the code gets executed. As soon as it has been read, it is available.
For the original question the code would be:
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
var that = this; //the magic happens
reader = new FileReader();
reader.onload = function(){
var data = $.csv.toArrays(this.result,{separator:'\t'});
that.file.name //or whatever you want to access.
};
reader.readAsText(file);
}
}
Event handling is asynchronous and thus they pick up the latest value of all the enclosed local variables(i.e. closure). To bind a particular local variable to the event, you need to follow the code suggested by users above or you can look at this working example:-
http://jsfiddle.net/sahilbatla/hjk3u2ee/
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
reader = new FileReader();
reader.onload = (function(file){
return function() {
console.log(file)
}
})(file);
reader.readAsText(file);
}
}
#Using jQuery document ready
$(function() {
files_input.addEventListener("change", openFiles, false);
});
For Typescript;
for (var i = 0; i < files.length; i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = ((file: any) => {
return (e: Event) => {
//use "e" or "file"
}
})(file);
reader.readAsText(file);
}
As the variable file is within the scope, you may use the file variable without passing it to function.
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
reader = new FileReader();
reader.onload = function(){
alert(file.name);
alert(this.result);
};
reader.readAsText(file);
}
}
files_input.addEventListener("change", openFiles, false);
<input type="file" id="files_input" multiple/>

How to remove duplicate JavaScript code of DOM event handler?

I'm trying to remove duplicate JavaScript code. I have a page with many <input type="file">. Each loads an image and performs some distinct processing. The problem is that I have many duplicates of the following code:
inputFile1.onchange = function (e) {
var file = e.target.files[0];
if (typeof file == 'undefined' || file == null) {
return;
}
var imageType = /image.*/;
if (!file.type.match(imageType)) {
window.alert('Bad file type!');
return;
}
var reader = new FileReader();
reader.onloadend = function (e) {
var imageLoader = new Image();
imageLoader.onload = function () {
// process image
};
imageLoader.src = e.target.result;
};
reader.readAsDataURL(file);
};
inputFile2.onchange = ... (repeats all but process image)
inputFile3.onchange = ... (repeats all but process image)
Only the code at process image comment varies. How can I remove the surrounding duplicate code?
I know that JavaScript functions are objects. How can I define a function object and create one distinct instance for each event handler, passing a different function for process image to each object?
You can make a generator for such functions with a closure taking the individual callback as an argument:
function getChangeHandler(loadCallback) {
return function (e) {
var file = e.target.files[0];
if (typeof file == 'undefined' || file == null) {
return;
}
var imageType = /image.*/;
if (!file.type.match(imageType)) {
window.alert('Bad file type!');
return;
}
var reader = new FileReader();
reader.onloadend = function (e) {
var imageLoader = new Image();
imageLoader.onload = loadCallback; // <= uses the closure argument
imageLoader.src = e.target.result;
};
reader.readAsDataURL(file);
};
}
inputFile1.onchange = getChangeHandler(function() { /* custom process image */ });
inputFile2.onchange = getChangeHandler(function() { /* custom process image */ });
inputFile3.onchange = getChangeHandler(function() { /* custom process image */ });
An other, eventually superior approach would be to use only one change-event handler for all inputs, that dynamically chooses the custom image processor by the name or id of the input:
var imageProcessors = {
"box1": function() { … },
"anotherbox": function() { … },
…
};
function changeHandler(e) {
var input = this; // === e.target
…
reader.onloadend = function (e) {
…
imageLoader.onload = imageProcessors[input.id];
};
}
// and bind this one function on all inputs (jQuery-style):
$("#box1, #anotherbox, …").click(changeHandler);
You can write a function that returns a function:
function processFile(callback) { //callback is the unique file processing routine
return function(e) {
var file = e.target.files[0];
if (typeof file == 'undefined' || file == null) {
return;
}
var imageType = /image.*/;
if (!file.type.match(imageType)) {
window.alert('Bad file type!');
return;
}
var reader = new FileReader();
reader.onloadend = function (e) {
var imageLoader = new Image();
imageLoader.onload = callback; //Put it here!
imageLoader.src = e.target.result;
};
reader.readAsDataURL(file);
};
}
Then call like this:
inputFile1.onchange = processFile(function() {
//file processing for number 1
});
inputFile2.onchange = processFile(function() {
//file processing for number 2
});
inputFile3.onchange = processFile(function() {
//file processing for number 3
});
Here's an EMCA5 solution, just to throw it into the mix. It binds a dynamic event callback depending on the element.
I've assumed each field has an ID (input1 etc) but with some modification of the code (i.e. identifying the trigger element by some other means) this wouldn't be necessary.
Array.prototype.slice.call(document.querySelectorAll('input[type=file]')).forEach(function(element) {
/* prepare code specific to the element */
var input_specific_code = (function() {
switch (element.id) {
case 'input1': return function() { /* #input1 code here */ };
case 'input2': return function() { /* #input2 code here */ };
case 'input3': return function() { /* #input3 code here */ };
}
})();
element.addEventListener('change', (function(input_specific_code) { return function(evt) {
var id_of_trigger_input = element.id;
/* common code here */
/* element-specific code */
input_specific_code();
/* continuation of common code */
}; })(input_specific_code), false);
});

Categories

Resources