I want to read an excel file and return the data in json format. When I call 'readExcelSheet' function on button click, it returns 'undefined' as function hasn't been finished reading the excel data. On subsequent click, data does return properly. I want to wait for this function until it read complete data.
constructor(private httpClient: HttpClient) {
}
readExcelSheet() {
let dataJson;
this.httpClient.get(this.filePath, { responseType: 'blob' })
.subscribe((data: any) => {
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
// reader.readAsBinaryString(e.target.files[0]);
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' });
/* grab first sheet */
const wsname1: string = wb.SheetNames[0];
const ws1: XLSX.WorkSheet = wb.Sheets[wsname1];
dataJson = XLSX.utils.sheet_to_json(ws1);
};
reader.readAsBinaryString(data);
});
return dataJson;
}
When you call subscribe, the code executes this line asynchronously (ie: skips over this line and go to the return immediately). Use lastValueFrom in an async-await function instead.
Try doing this:
async readExcelSheet(){
let dataJson;
const data = await lastValueFrom(this.httpClient.get(this.filePath, { responseType: 'blob' }))
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' });
/* grab first sheet */
const wsname1: string = wb.SheetNames[0];
const ws1: XLSX.WorkSheet = wb.Sheets[wsname1];
dataJson = XLSX.utils.sheet_to_json(ws1);
};
reader.readAsBinaryString(data);
return dataJson;
}
Try using ASYNC & AWAIT in your functions
async readExcelSheet()
Try to use toPromise instead of subscribe and then you can return the function.
You can then try to use Promise.resolve() to resolve the dataJson.
constructor(private httpClient: HttpClient) {
}
readExcelSheet() {
return this.httpClient.get(this.filePath, { responseType: 'blob' })
.toPromise().then((data: any) => {
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
// reader.readAsBinaryString(e.target.files[0]);
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' });
/* grab first sheet */
const wsname1: string = wb.SheetNames[0];
const ws1: XLSX.WorkSheet = wb.Sheets[wsname1];
Promise.resolve(XLSX.utils.sheet_to_json(ws1))
};
reader.readAsBinaryString(data);
});
}
Related
I'm taking input as a PDF file and using javascript to add custom metadata, but I'm not getting a satisfactory result.
Below is a sample method code that I used to add custom metadata that is first converted to blob type and then added, but when we convert its blob data to base64 and download the file and check the properties, we cannot find it.
const blobToBase64 = (blob: any) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
const updatePDFMetaData = (file: any, metadata: any) => {
let convertBlobToBase64: any;
const selectedFile = file;
const reader = new FileReader();
reader.readAsArrayBuffer(selectedFile);
reader.onload = async (event:any) => {
const fileBuffer: any = event?.target?.result;
const blob: any = new Blob([fileBuffer], { type: selectedFile.type });
Object.keys(metadata).forEach((key: any) => {
blob[key] = metadata[key];
});
convertBlobToBase64 = await blobToBase64(blob);
console.log("convertBlobToBase64", convertBlobToBase64);
};
};
I'm using loodash cloneDeep to upload files, however I need files to not be duplicated and only be able to upload a file once. How can I do this using cloneDeep?
I don't know how to do it, I googled, but the solution was only for jquery
const [files, setFiles] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
If relying on the filenames is enough, you can try to store them to check if it has been uploaded already :
const [files, setFiles] = useState([]);
//state to store uploaded file's name
const [fileNames, setFileNames] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
//if the file has not been already uploaded
if (!fileNames.includes(fileName)) {
//add the current fileName in state
setFileNames([fileName, ...fileNames]);
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
} else {
alert("File has already been uploaded");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
Note: this will not prevent the case when the user upload a file, then refresh the website and upload the same file again
If you want to prevent that you have to ask your backend if the file has already been upload or not.
I'm trying encode an image to base64, (so I can later send it this way to a backend server). Everything seems to work until I use JSON.stringify() on the object that has the encoded image in it.
I think It gets lost in the JSON.stringify() and I can't seem to find a solution. I've been working for weeks on this issue and I couldn't find an answer anywhere. Please help!
const [baseImage, setBaseImage] = useState('');
const [baseImageCorrect, setBaseImageCorrect] = useState('');
const convertBase64 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.onerror = (error) => {
reject(error);
console.log(error);
};
});
};
const uploadImage = async (e) => {
const file = e.target.files[0];
const base64 = await convertBase64(file);
const base64RemovedType = base64.split(',')[1];
setBaseImage(`${base64RemovedType}`);
};
useEffect(() => {
setBaseImageCorrect(baseImage);
console.log('current:' + baseImageCorrect);
//prints out a long string with the RIGHT information
}, [baseImage, baseImageCorrect]);
const EncodedImage = JSON.stringify({
fileBase64: (baseImageCorrect, { encoding: 'base64' }),
});
console.log(EncodedImage)
//PRINTS THIS: "fileBase64":{"encoding":"base64"}} , without the encoded image string
I am assuming u need the key baseImageCorrect and encoding key at the same level.
Use this instead:
const EncodedImage = JSON.stringify({
fileBase64: {baseImageCorrect, encoding: 'base64' },
});
I have the following code:
public upload(): void {
const fileinput = document.getElementById('file');
fileinput.click();
fileinput.addEventListener('change', this.handleFileInput.bind(this));
}
private handleFileInput(event) {
try {
const input = event.target as HTMLInputElement;
const reader = new FileReader();
const { objectId } = this.object;
const file = input.files[0];
reader.onload = () => {
event.target.value = null;
this.objectDetailsService
.upload(file, objectId)
.subscribe(
() => //,
() => //,
);
};
reader.readAsArrayBuffer(file);
} catch (e) {
console.log(e);
}
}
I should allow to load the same file sometimes. When user select another this code create as new stream. How to avoid it?
You're right in asking how to apply switchMap. One way is to create an observable to handle the async callback onload. Then you could apply operators like switchMap to call other async functions.
Also I'd suggest attaching the event handler directly in the template instead of using document.getElementById('file'); in the controller.
Try the following
*.ts
import { Observable, Observer, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';
public handleFileInput(event) {
const input = event.target as HTMLInputElement;
const file = input.files[0];
this.readFile(file).pipe(
switchMap((file: any) => {
const { objectId } = this.object;
return this.objectDetailsService.upload(file, objectId);
})
).subscribe({
next: () => console.log('File uploaded'),
error: (error: any) => console.log('Error:', error),
});
}
readFile(file): Observable<any> {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
return new Observable((observer: Observer<any>) => {
reader.onload = (ev: ProgressEvent) => {
observer.next(file);
observer.complete();
};
reader.onerror = (error: any) => {
observer.error(error);
};
});
}
*.html
Select file: <input type="file" (change)="handleFileInput($event)" />
Working example: Stackblitz
I want to read the contents of the file directly by using the file path. I can do this by having the file selected. But I don't know how to do it using the direct file path. I could not find any examples or sources for this. Below is how I read the file by selecting it from the input.
import * as XLSX from 'xlsx';
var items = [];
readExcel = (file) => {
const promise = new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = (e) => {
const bufferArray = e.target.result;
const wb = XLSX.read(bufferArray, { type: "buffer" });
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_json(ws);
resolve(data);
};
fileReader.onerror = (error) => {
reject(error);
};
});
promise.then((d) => {
this.items = d;
console.log(this.items)
// fill dictionary
this.dictionary = Object.assign({}, ...this.items.map((x) => ({ [x.PartNumber]: x.Cost })));
console.log(this.dictionary)
});
};
<input
type="file"
onChange={(e) => {
const file = e.target.files[0];
this.readExcel(file);
}}
/>
I beleive it should work:
const req = new XMLHttpRequest();
req.responseType = "arraybuffer";
req.open("GET", "https://.../MyExcelFile.xlsx", true);
req.onload = () => {
const bufferArray = req.response;
const wb = XLSX.read(bufferArray, { type: "buffer" });
...
I couldn't find a direct read operation. I converted the excel file to json format and got my job done.