How to work with FileReader 'synchronously' in react/redux - javascript

I'm having a little trouble designing a solution to work with the FileReader API in a react/redux app due to it's asynchronous nature. I've done some looking around but nothing has worked for me.
I have a react component that makes use of react-dropzone to get contents of files and work on it's contents later on. Whenever a file get's dropped with dropzone, a callback fires:
onGuestListDrop = (fileData) => {
const headers =
FileParser.read(fileData)
.getHeaders();
};
The purpose of my internal API design is to get headers of .csv or .xlsx files in order to map the column names to existing system field names. I dont have any trouble parsing the content of the files, I got that working, the problem is with the FileReader.onload event.
My top leve file parser is:
class FileParser {
constructor(file) {
this.reader = new FileReader();
this.init(file[0]);
return this;
}
static read(file) {
return new FileParser(file);
}
init(file) {
switch (file.type) {
case 'text/csv':
this.parser = new CsvParser(this.reader, file);
break;
default:
this.parser = new XlsParser(this.reader, file);
break;
}
}
getHeaders() {
return this.parser.getHeaders();
}
}
The CSV class is:
class CsvParser {
constructor(reader, file) {
this.reader = reader;
this.file = file;
this.parse();
return this;
}
parse() {
this.reader.readAsText(this.file, "UTF-8");
this.reader.onload = function (evt) {
const { result } = evt.target;
this.parsedContent = Papa.parse(result);
console.log(this.parsedContent);
};
}
getHeaders() {
return this.parsedContent.data[0];
}
}
The problem right now is that when I try to access the headers via the getHeaders method I'm getting undefined because FileReader works async. Is there a way to make this work with some refactoring, or is this just not possible ?
I was thinking about using redux actions, but I'm not sure how to connect the parser classes with the store. I thought about passing an action directly to the parser class so I can fire the action creator within the FileReader.onload event. I think this could work, but I'm not sure this is the best approach to work with redux given the circumstances.

Add a deferred pattern in your CsvParser like this.
class CsvParser {
constructor(reader, file) {
this.reader = reader;
this.file = file;
this.deferred = {};
let promise = new Promise((resolve, reject) => {
this.deferred.resolve = resolve;
this.deferred.reject = reject;
});
this.deferred.promise = promise;
this.parse();
return this;
}
parse() {
this.reader.readAsText(this.file, "UTF-8");
this.reader.onload = function (evt) {
const { result } = evt.target;
this.parsedContent = Papa.parse(result);
console.log(this.parsedContent);
this.deferred.resolve(this.parsedContent.data[0])
};
}
getHeaders() {
return this.deferred.promise;
}
Now change your onGuestListDrop method like this
onGuestListDrop = (fileData) => {
let headers = {};
FileParser.read(fileData).getHeaders().then(function (headers) {
headers = headers;
});
};

Related

Display a downloaded blob image

I'm trying to be able to display images that can be edited in the back end, but these have to be available while offline too. To acomplish this goal I'm downloading these images with the following method (which is the only one that worked for me)
download_save_picture(picture_url) {
let splited_url = picture_url.split('/');
var filename = splited_url[splited_url.length - 1];
var config = { responseType: 'blob' as 'blob' };
this.httpClient.get(picture_url, config).subscribe(
data => {
this.file.writeFile(this.file.dataDirectory, filename, data, { replace: true });
},
err => console.error(err),
() => { }
);
}
And then I can verify that these files exist and that their weight is also different from 0, so I guess everyting is correct until this point.
this.file.checkFile(this.file.dataDirectory, filename)
.then(() => {
alert("File Exist!!!"); // I always enter here
})
.catch((err) => {
alert("ERR : " + err);
});
this.file.resolveLocalFilesystemUrl(this.file.dataDirectory + filename).then((entry: any)=>{
entry.getMetadata((metadata) => {
alert("SIZE: " + metadata.size);
})
}).catch((error)=>{
alert(error);
});
So the next step is to display the image which is in the path this.file.dataDirectory + filename, how can I do this?
After searching for a solution and reading I understand that I have to:
Load the file binary content.
Convert this binary content to base 64.
Then display it with src="data:image/jpeg;base64,"+{{image_in_base64}};
But until now I've not been able to do steps 1 (load the file content) and 2 (convert it to base 64), how can I do that?
At the end it was easier and faster to use LocalStorage instead of files
First I made the function download_save_picture(picture_url) which save the content of the image in picture_url in Base64 in localStorage(key), the key will be everything after the last /, so if we use the URL https://www.w3schools.com/w3css/img_lights.jpg the content will be saved in icon.img_lights.jpg.
download_save_picture(picture_url) {
let splited_url = picture_url.split('/');
var name = splited_url[splited_url.length - 1];
if (localStorage.getItem('icon.' + name) === null) {
var config = { responseType: 'blob' as 'blob' };
this.httpClient.get(picture_url, config).subscribe(
data => {
var reader = new FileReader();
reader.readAsDataURL(data);
reader.onload = function() {
window.localStorage.setItem('icon.' + name, reader.result);
}
},
err => console.error(err),
() => { }
);
}
}
Then at the view I display the image with <img src={{useLocalImage(item.image)}}></p>, where useLocalImage simply returns the content saved in localStorage.
useLocalImage(picture_url) {
let splited_url = picture_url.split('/');
var name = splited_url[splited_url.length - 1];
var icon_name = window.localStorage.getItem('icon.' + name);
return icon_name;
}
Following is the code that worked for me.
<input #fileinput type="file" [(ngModel)]="file_obj" (click)="resetFileInput()" (change)="onUploadChange()"/>
Then in your typescript code.
#ViewChild('fileinput') file_input;
file_obj:any;
onUploadChange() {
const file = this.file_input.nativeElement.files[0];
const fReader = new FileReader();
fReader.readAsDataURL(file);
fReader.onloadend = function(event) {
//do whatever you want with the result here.
console.log(event.target['result']);
};
}
resetFileInput() {
this.file_input.nativeElement.value = '';
}
UPDATE - If you are using Ionic Native File or Cordova File Plugin
Ionic Native file is different from the browser File object.
There seems to be a method called getFile() , which returns FileEntry object
This has something called method .file() which returns a Native file object .
And then use the FileReader to read the file as dataURL using readAsDataURL method.

How to convert a image src from a blob string to data URI

I have a page where the user can paste an image into a content editable div. When I get the image the src returns a string. When I look in debug tools this is what I see:
<img src="blob:http://www.example.com/3955202440-AeFf-4a9e-b82c-cae3822d96d4"/>
How do I convert that to a base 64 string?
Here is the test script, http://jsfiddle.net/bt7BU/824/.
// We start by checking if the browser supports the
// Clipboard object. If not, we need to create a
// contenteditable element that catches all pasted data
if (!window.Clipboard) {
var pasteCatcher = document.createElement("div");
// Firefox allows images to be pasted into contenteditable elements
pasteCatcher.setAttribute("contenteditable", "");
// We can hide the element and append it to the body,
pasteCatcher.style.opacity = 0.5;
document.body.appendChild(pasteCatcher);
// as long as we make sure it is always in focus
pasteCatcher.focus();
document.addEventListener("click", function() { pasteCatcher.focus(); });
}
// Add the paste event listener
window.addEventListener("paste", pasteHandler);
/* Handle paste events */
function pasteHandler(e) {
// We need to check if event.clipboardData is supported (Chrome)
if (e.clipboardData) {
// Get the items from the clipboard
var items = e.clipboardData.items || e.clipboardData.files;
var itemcount = items ? items.length : 0;
pasteArea.value = "items found:"+itemcount;
if (itemcount) {
// Loop through all items, looking for any kind of image
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
// We need to represent the image as a file,
var blob = items[i].getAsFile();
// and use a URL or webkitURL (whichever is available to the browser)
// to create a temporary URL to the object
var URLObj = window.URL || window.webkitURL;
var source = URLObj.createObjectURL(blob);
// The URL can then be used as the source of an image
createImage(source);
}
}
} else {
console.log("no items found. checking input");
// This is a cheap trick to make sure we read the data
// AFTER it has been inserted.
setTimeout(checkInput, 1);
}
// If we can't handle clipboard data directly (Firefox),
// we need to read what was pasted from the contenteditable element
} else {
console.log("checking input");
// This is a cheap trick to make sure we read the data
// AFTER it has been inserted.
setTimeout(checkInput, 1);
}
}
/* Parse the input in the paste catcher element */
function checkInput() {
console.log("check input");
// Store the pasted content in a variable
var child = pasteCatcher.childNodes[0];
// Clear the inner html to make sure we're always
// getting the latest inserted content
//pasteCatcher.innerHTML = "";
//console.log( "clearing catcher");
console.log(child);
if (child) {
// If the user pastes an image, the src attribute
// will represent the image as a base64 encoded string.
if (child.tagName === "IMG") {
createImage(child.src);
reader = new FileReader();
reader.readAsDataURL(child.src);
reader.loadend = function(e) {
console.log(e.target.result);
}
}
}
}
/* Creates a new image from a given source */
function createImage(source) {
var pastedImage = new Image();
pastedImage.onload = function(e) {
//pasteArea.text = pastedImage.src;
console.log(1);
console.log(e);
loadImage.src = e.target.src;
console.log(loadImage.src);
}
pastedImage.src = source;
}
<textarea id="pasteArea" placeholder="Paste Image Here"></textarea>
<img id="loadImage" />
I'm testing this in Safari on Mac.
Since the blobURI is generated automatically by the browser, you can use this, which will download the produced image as a new Blob:
const toDataURL = url => fetch(url)
.then(response => response.blob())
.then(blob => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
}))
And then on your function createImage(source) { you can call it:
toDataURL(source)
.then(dataUrl => {
console.log('RESULT:', dataUrl)
})
This answer is complimentary to #BrunoLM's answer for when you don't have ES6 or you want to read in a different image type.
ES6:
const toDataURL = url => fetch(url)
.then(response => response.blob())
.then(blob => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
}))
Not ES6 (seems to work the same):
const toDataURL = function(url) {
return fetch(url).then(function(response) {
return response.blob();
}).then(function (blob) {
var type = blob.type;
var size = blob.size;
return new Promise(function(resolve, reject) {
const reader = new FileReader();
reader.onerror = reject;
reader.readAsDataURL(blob);
reader.onloadend = function() {
return resolve(reader.result);
}
}
)}
)}
Based on my understanding of ES6 (ES6 to not ES6):
var a = url => fetch(url)
var a = function(url) { return fetch(url) }
var a = function(parameter) { return statement }
var b = (parameter, parameter) => { fetch(param, param) }
var b = function(foo1, foo2) => { return fetch(param, param) }
var c = url = () => resolve(reader.result)
var c = url = function() { return resolve() }
Making a call:
toDataURL(url).then(function(dataUrl) {
console.log("RESULT:" + dataUrl);
});
Note:
The value returned by the above method is of type "image/tiff" when run in Safari on OSX. If you want to specify another type, such as PNG, there more info on that here.

Using XLSX in Angular2 with IE 11

I wanted to share this as it took me a few hours to sort out details by going over the various posts.
The Task:
Using XLSX as part of my Angular2 project to import xls, xlsx, or csv type files to generate reports.
The Problem:
IE does not support the readAsBinaryString and produces an error where other web browsers do not generate errors
Solution:
Manually create prototype function for readAsBinaryString if one does not exist.
The Code:
Add One of the following functions to the component that is trying to read the file and use "readAsBinaryString". I suggest #2 but both seem to work.
Option 1:
IECheck(): void {
if (FileReader.prototype.readAsBinaryString === undefined) {
FileReader.prototype.readAsBinaryString = function (fileData) {
let binary = '';
const pt = this;
const reader = new FileReader();
reader.onload = function (e) {
const bytes = new Uint8Array(reader.result);
const length = bytes.byteLength;
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
const f = {target: {result: binary}};
pt.onload(f);
}
reader.readAsArrayBuffer(fileData);
}
}
}
Option 2:
IECheck(): void {
if (FileReader.prototype.readAsBinaryString === undefined) {
FileReader.prototype.readAsBinaryString = function (fileData) {
const pt = this;
const reader = new FileReader();
reader.onload = function (e) {
const blobURL = URL.createObjectURL(fileData);
const xhr = new XMLHttpRequest;
xhr.open('get', blobURL);
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onload = function () {
const g = {target: {result: xhr.response}};
pt.onload(g)
}
xhr.send();
}
reader.readAsArrayBuffer(fileData);
}
}
}
In the constructor or OnInit(If using lifecycle hooks) call the IECheck function
this.IECheck();
Result:
XLXS will upload documents in IE
Let me know your thoughts on this, Happy codding!

Pass relative files into function using javascript file constructor

I'm trying to load in a relative file address into a function previously used by a file reader. On the site, I had a button that would let you pick local files to be loaded into a graphics renderer. I want to use a url to access these files relatively instead, but I can't figure out how to bind them to a file object. I have been using this mozilla documentation to try and figure it out.
Here is the code that was used originally:
// function that takes file input and renders the image
function readFiles(){
// Deal with file input
if (window.File && window.FileReader && window.FileList && window.Blob) {
var file1 = document.getElementById('fileinput').files[0];
var file2 = document.getElementById('fileinput').files[1];
// Call the file analyzer
fileAnalyzer( file1, file2 );
} else {
alert('The File APIs are not fully supported by your browser.');
}
}
Here is the code that I want to update:
// load cube button
var loadcube = document.getElementById('loadcube');
loadcube.onclick = function(evt) {
var file1 = new File([], "Object files/cube3.coor" );
var file2 = new File([], "Object files/cube3.poly" );
fileAnalyzer( file1, file2);
}
You can use the Fetch API to asynchronously fetch the resources, convert them to a Blob, and then construct a File like this:
function fetchAsFile(path) {
return fetch(path).then(function (response) {
return response.blob()
}).then(function (blob) {
return new File([blob], path)
})
}
var loadcube = document.getElementById('loadcube')
loadcube.addEventlistener('click', function () {
var p1 = fetchAsFile('Object files/cube3.coor')
var p2 = fetchAsFile('Object files/cube3.poly')
Promise.all([p1, p2]).then(function (files) {
fileAnalyzer(files[0], files[1])
})
})
Using ES2017 async / await, you can simplify the above like this:
async function fetchAsFile(path) {
let response = await fetch(path)
let blob = await response.blob()
return new File([blob], path)
}
var loadcube = document.getElementById('loadcube')
loadcube.addEventlistener('click', async function () {
let p1 = fetchAsFile('Object files/cube3.coor')
let p2 = fetchAsFile('Object files/cube3.poly')
let files = await Promise.all([p1, p2])
fileAnalyzer(files[0], files[1])
})

Capture microphone in IE10

I need to capture microphone audio in IE10. So far I have two semi-working solutions:
getUserMedia from Microsoft's experimental WebRTC plugin:
http://www.html5labs.com/prototypes/media-capture-api-(2nd-updated)/media-capture-api-(2nd-update)/info
The issue with this is that while I can capture and replay the audio in the browser, I cannot send the audio to the server. In particular, it is not clear how to extract the audio data from the "blob" object:
function msStopRecordCallback(blob) {
console.log(blob) // outputs {}
console.dir(blob) // outputs {}
playMediaObject.Play(blob); // This works!
}
jRecorder: http://www.sajithmr.me/jrecorder-jquery The issue with this is that it relies on Flash to capture the audio, which is something I would like to avoid.
Are there any other ways to capture audio in IE10?
I recognize that my answer a bit late, but...
You may upload a blob to a server as following (Javascript):
function saveBlob(blob)
{
var uploader = new CustomXMLHttpRequest();
uploader.onpartreceived = function (response)
{
// TODO: handle the server response here
};
var base = window.location.toString();
var uploadService = base.substr(0, base.lastIndexOf("/")) + "/api/upload";
uploader.open("POST", uploadService, true);
uploader.responseType = "text";
var form = new FormData();
form.append("fname", blob, "audio.wav");
uploader.send(form);
}
On the server side, you may treat this blob as a file attachment, e.g. (C#):
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> PostFile()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data and return an async task.
await Request.Content.ReadAsMultipartAsync(provider);
var fileName = "";
// get the uploaded files.
foreach (var data in provider.FileData)
{
var file = new FileInfo(data.LocalFileName);
// TODO: handle received file here
}
if (string.IsNullOrEmpty(fileName))
{
return Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}
Hope this will help.

Categories

Resources