Post Excel file/Blob to Rest API using Angular 4 - file

I am using input type with file to upload a excel file like below :
<input type="file" style="display: inline-block;" (change)="readFile($event)" placeholder="Upload file" accept=".xlsx">
<button type="button" class="btn btn-info" (click)="uploadExcel()" [disabled]="!enableUpload">Upload</button>
For reading file contents :
public readFile(event) {
try {
this.enableUpload = false;
const reader = new FileReader();
reader.onload = (e: any) => {
console.log(e.target.result);
this.fileContent = e.target.result;
let binary = "";
const bytes = new Uint8Array(e.target.result);
const length = bytes.byteLength;
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
const workbook = XLSX.read(binary, { type: 'binary' });
console.log(workbook);
};
reader.readAsArrayBuffer(file);
console.log(reader);
this.enableUpload = true;
} catch (error) {
console.log('Error reading uploaded file. See below msg for details : \n');
console.log(error);
}
}
on click of Upload below code is used to upload the file content.
public uploadExcel() {
let formData = new FormData();
formData.append('file', this.fileContent, 'filename');
this._commonService
.commonHttpPostRequest("http://urlforfileupload",
{ file: this.fileContent }, {}
)
.subscribe(response => {
try {
console.log(response);
} catch (error) {
console.log("Error" + error);
}
});
}
But I am unable to upload file contents and getting the below response :
400 Bad Request
{
"status": "bad_input",
"message": "file not found in request payload."
}
I can see fileContent before I post the request.

Finally found the solution
Template :
<input type="file" [multiple]="multiple" #fileInput class="browse-btn" (change)="readFile()" accept=".xlsx">
<button type="button" class="btn btn-info btn-lg" (click)="upload()" >Upload</button>
Component :
public upload() {
const inputEl: HTMLInputElement = this.inputEl.nativeElement;
const fileCount: number = inputEl.files.length;
const formData = new FormData();
const headers = new Headers();
headers.set('Accept', 'application/json');
headers.delete('Content-Type'); // mandate for accepting binary content
if (fileCount > 0) {
for (let i = 0; i < fileCount; i++) {
formData.append('file', inputEl.files.item(i));
}
try {
this.loaderForFileUpload = true;
this.http
.post('http://urlForFileUpload', formData, { headers: headers })
.subscribe(response => {
if (response.status === 200) {
this._toastr.success('File uploaded successfully', 'Success!');
}
}, error => {
this._toastr.error('File contents mismatch', error.statusText);
});
} catch (e) {
console.log('Error occured while posting uploaded file. See below message for details : \n');
console.log(e);
}
}
}

Related

How can I check if the data_URL is returning an image of video ? - Firebase & Next.js/React

The image is uploaded to firebase and returned as a data_URL that looks like this:
https://firebasestorage.googleapis.com/v0/b/app_name/o/posts%2postId?alt=media&token=token
I am trying to check if the file type is a video or an image, then return a div depending on the "mediaType". Because firebase storage doesn't include the file extension in the url, it is difficult to determine the file type.
First attempt:
const [mediaType, setMediaType] = useState(null);
useEffect(() => {
if (postImage) {
const storageRef = firebase.storage().ref();
storageRef.child(postImage).getDownloadURL().then(url => {
fetch(url)
.then(res => res.blob())
.then(blob => {
let type = blob.type;
if (type.startsWith("image")) {
setMediaType("image");
} else if (type.startsWith("video")) {
setMediaType("video");
} else {
setMediaType("other");
console.log("Unknown file format: " + type);
}
});
});
}
}, [postImage]);
Second attempt:
const handleFile = async (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = async (e) => {
const dataURL = e.target.result;
if (dataURL.startsWith('data:image/')) {
setMediaType('image');
setDataUrl(dataURL);
console.log("Image: " + dataURL);
} else if (dataURL.startsWith('data:video/')) {
setMediaType('video');
setDataUrl(dataURL);
console.log("Video: " + dataURL);
} else {
let response = await fetch(dataURL);
let type = response.headers.get("Content-Type");
if (type.startsWith("image")) {
setMediaType("image");
setDataUrl(dataURL);
} else if (type.startsWith("video")) {
setMediaType("video");
setDataUrl(dataURL);
} else {
setMediaType("other");
console.log("Unknown file format: " + type);
}
}
}
reader.readAsDataURL(file);
}
The div inside my return statement:
<div className="w-full px-3">
{mediaType === 'image' ? <img className="shadow-md w-full" src={postImage || 'default-image.jpg'} alt="" /> : null}
{mediaType === 'video' ? <ReactPlayer layout="fill" url={postImage} config={{file:{attributes:{controlsList:'nodownload'}}}} controls onContextMenu={e => e.preventDefault()}/> : null}
{mediaType === 'other' ? <p>File is not an image or video</p> : null}
</div>
What I would do is add metadata while uploading the file to firebase. You can check the documentation to see how:
//This is a metadata, you can customize these
//as you can see the content type is set to be image/jpeg
var newMetadata = {
cacheControl: 'public,max-age=300',
contentType: 'image/jpeg'
.........
.........
};
You will use this metadata while uploading the file using:
storageRef.updateMetadata(newMetadata).......
And when reading the file read out the metadata that you set to the file to for example detect its type:
storageRef.getMetadata().then((metadata) => {
//use this metadata to know the type here.......
})
Hope this gives you an idea of what to do.

Is it possible to read xlsx in react if office is not installed on windows machine?

Currently I am using below code to read xlsx file in react when my system having office (excel) installed. But the same is throwing error on other machine where office(excel) is not installed.
Error is at this point console.log(this.state.file.type)
Here is console.log :
lastModified: 1595606065652
lastModifiedDate: Fri Jul 24 2020 15:54:25 GMT+0000 (Greenwich Mean Time) {}
name: "ExamAnswer.xlsx"
size: 14266
type: "" // here type is null
webkitRelativePath: ""
__proto__: File
So, could you please tell how to read file type
handleExamInputFile() {
const reader = new FileReader();
const rABS = !!reader.readAsBinaryString;
console.log("file", this.state.file);
console.log(this.state.file.type);
let fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
if (this.state.file.size > 0) {
console.log(this.state.file.size);
if (this.state.file.type === fileType) {
if (rABS) {
reader.readAsBinaryString(this.state.file);
} else {
reader.readAsArrayBuffer(this.state.file);
}
reader.onload = (e) => {
const bstr = e.target.result;
const wb = XLSX.read(bstr, {
type: rABS ? "binary" : "array",
bookVBA: true,
});
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_json(ws, {
raw: true,
});
const header = [];
const columnCount = XLSX.utils.decode_range(ws["!ref"]).e.c + 1;
for (let i = 0; i < columnCount; ++i) {
if (ws[`${XLSX.utils.encode_col(i)}1`] != undefined) {
header[i] = ws[`${XLSX.utils.encode_col(i)}1`].v;
}
}
console.log(header);
let headerMustHave = ["Exam Name", "EId", "Name"];
// let ifFound = header.every(function (val) {
// console.log(val);
// return headerMustHave.indexOf(val) !== -1;
// });
let checker = (arr, target) => target.every((v) => arr.includes(v));
let ifFound = checker(header, headerMustHave);
console.log(ifFound);
if (ifFound === true) {
for (var i = 0; i < data.length; i++) {
let excel_date = this.ExcelDateToJSDate(data[i]["Report Date"]);
data[i]["Report Date"] = excel_date;
}
this.setState({
data: data,
result: JSON.stringify(data),
cols: make_cols(ws["!ref"]),
});
const exceldata = JSON.parse(this.state.result);
const excelList = exceldata.map((data) => data);
const excel = { data: excelList };
const url = "http://localhost:8000/upload";
const config = {
headers: {
Accept: "*",
"Content-Type": "application/json",
"Allow-Control-Allow-Origin": "*",
// Accept: "application/json",
},
};
return post(url, excel, config).then((response) => {
toast.success(response.data, {
position: toast.POSITION.TOP_CENTER,
});
document.getElementById("file1").value = "";
this.setState({ file: "" });
});
} else {
toast.error("Some columns are not presented in uploaded file.", {
position: toast.POSITION.TOP_CENTER,
autoClose: 3000,
});
this.setState({ file: "" });
}
};
} else {
toast.error(
"This file format is not supported. Please select an .xlsx file.",
{
position: toast.POSITION.TOP_CENTER,
autoClose: 3000,
}
);
this.setState({ file: "" });
}
} else {
toast.error("Please select .xlsx file to upload.", {
position: toast.POSITION.TOP_CENTER,
autoClose: 3000,
});
this.setState({ file: "" });
}
}
Alternatively, is it possible to read the same xlsx file through python on above type machine?
TIA
Since this is React.js code, it's running client-side, in a browser.
Your problem with type being empty is that not all machines and browsers know how to map the file type .xlsx to the MIME type 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; those with Excel, or maybe LibreOffice installed, certainly might, but even that's not a given. Whether or not Excel is actually installed has no bearing on whether the XLSX.js library you're using can actually parse the file (since the browser doesn't know about Excel).
As I mentioned in the comments, you can simply optionally look at the extension of the file uploaded – and even that's not necessary; you could just attempt to pass the file to XLSX; it would fail to parse files that are not XLSX documents anyway.
Here's a refactoring of your code to move to promise-based asynchronicity, moving the actual processing code out of the React component, so it's more easily testable.
In addition, you seemed to be doing some strange to-JSON-from-JSON coercion before posting the data; I got rid of that (and all of the Toast error handling for a more minimal example)...
function ExcelDateToJSDate(x) {
return x; // TODO: implement
}
function processWorkbook(wb) {
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_json(ws, {
raw: true,
});
const header = [];
const columnCount = XLSX.utils.decode_range(ws["!ref"]).e.c + 1;
for (let i = 0; i < columnCount; ++i) {
const col = `${XLSX.utils.encode_col(i)}1`;
if (ws[col] != undefined) {
header[i] = ws[col].v;
}
}
console.log(header);
const headerMustHave = ["Exam Name", "EId", "Name"];
const checker = (arr, target) => target.every((v) => arr.includes(v));
let ifFound = checker(header, headerMustHave);
if (ifFound === true) {
for (var i = 0; i < data.length; i++) {
let excel_date = ExcelDateToJSDate(data[i]["Report Date"]);
data[i]["Report Date"] = excel_date;
}
}
return { ws, data };
}
/**
* Read a DOM File into a binary string or array buffer; returns a Promise.
*/
function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
let type;
if (reader.readAsBinaryString) {
reader.readAsBinaryString(file);
type = "binaryString";
} else {
reader.readAsArrayBuffer(file);
type = "arrayBuffer";
}
reader.onload = (event) => {
resolve({ type, data: event.target.result });
};
reader.onerror = (event) => reject(event);
});
}
async function processExamInputFile(file) {
if (!file) {
throw new Error("No file selected.");
}
if (
!(
file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
file.name.endsWith(".xlsx")
)
) {
throw new Error("Invalid file type or extension.");
}
const { data, type } = await readFile(file);
const wb = XLSX.read(data, {
type: type === "binaryString" ? "binary" : "array",
bookVBA: true,
});
return processWorkbook(wb);
}
class X {
async handleExamInputFile() {
try {
const { ws, data } = await processExamInputFile(this.state.file);
this.setState({
data: data,
result: JSON.stringify(data),
cols: make_cols(ws["!ref"]),
});
await post(
"http://localhost:8000/upload",
{ data },
{
headers: {
Accept: "*",
"Content-Type": "application/json",
"Allow-Control-Allow-Origin": "*",
},
},
);
} catch (err) {
console.log(err); // TODO: improve
}
}
}

Showing a loading message until the blob file is downloaded

I am printing a bill as a pdf document after submitting the billing form. The service that is downloading the blob file is as follows.
export default [createLogic({
type: reportTypes.REPORT,
latest: true,
debounce: 2000,
process({ MockHTTPClient, getState, action }, dispatch, done) {
dispatch(reportActions.queryStart())
let HTTPClient;
if (MockHTTPClient) {
HTTPClient = MockHTTPClient;
} else {
HTTPClient = API;
}
HTTPClient.Post(endPoints.REPORTS_BILL, action.payload.reportOptions)
.then(
(resp) => {
return resp.data
})
.then((data) => {
dispatch(reportActions.getReportSuccess(data));
var link = document.createElement('a');
link.href = `data:application/octet-stream;base64,${data}`;
var today = new Date();
var date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds() + ":" + today.getMilliseconds();
var dateTime = date + ' ' + time;
var fileName = dateTime + "billing.pdf";
link.download = fileName;
link.click();
var base64str = data;
var binary = atob(base64str.replace(/\s/g, ''));
var len = binary.length;
var buffer = new ArrayBuffer(len);
var view = new Uint8Array(buffer);
for (var i = 0; i < len; i++) {
view[i] = binary.charCodeAt(i);
}
var blob = new Blob([view], { type: "application/pdf" });
var url = URL.createObjectURL(blob);
window.open(url);
dispatch(reportActions.getReportSuccess(data))
})
.then(() => dispatch(reportActions.queryEnd()))
.catch(err => {
dispatch(reportActions.getReportFailed());
var errorMessage = "Failed to get prductivity data.";
if (err && err.code == "ECONNABORTED") {
errorMessage = "Please check your internet connection.";
}
dispatch(
reportActions.getReportFailed({
title: "Error!",
message: errorMessage
})
);
})
.then(() => done());
}
})
]
In the Billing Component following is the method that I am calling the service.
printBill = () => {
debugger;
var reportType = 1;
var reportFiltersSet = {};
if (this.billingOptions.length > 0) {
this.billingOptions.map(v => {
reportFiltersSet[v.name] = v.value;
});
}
this.props.reportActions.queryStart();
var reportType = 1; //Thiis need to be dynamic. use this => //this.state.reportType
var reportFilters = this.state.billingOptions;
if (
reportFiltersSet.organization == undefined ||
reportFiltersSet.department == undefined ||
reportFiltersSet.division == undefined ||
reportFiltersSet.billYear == undefined ||
reportFiltersSet.billMonth == undefined
) {
this.setState({
reportContentNull: true,
warning: "Error!",
errorMessage:
"Either department or division reqiured with organization , year and month",
negative: true,
hidden: false
});
this.props.reportActions.getReport({ reportOptions: reportFiltersSet });
var reportDownloadType = this.state.reportType;
};
I can download the blob file , when I submit the form without any problem. But in user's perspective it is important to show that the loading state in order to have a better user experince. Therefore can anyone help me out to show a loading message during the submit and loading the blob file?
So the Idea is to start loading as soon as HTTP calls happens and remove it when response is recieved.
Simple way would be add a loading variable in state and in render check that variable and show spinner.
onClick = () => {
this.setState({ loading: true }, () => {
Axios.get('/sampelCall')
.then(result => this.setState({
loading: false,
data: [...result.data],
}));
});
}
render() {
const { data, loading } = this.state;
return (
<div>
<button onClick={this.onClick}>
Load Data
</button>
{loading ? <LoadingSpinner /> : <ResultsTable results={data} />} // if loading in true will show Spinner
</div>
);
}
Note:
You need to handle loading variable in all cases (Success/Error) else Spinner will keep on loading.

how to reduce bundle.js file size with vfs_fonts.js?

I'm developing pdf down load function with react/typescript.
(using pdfmake)
My bundle.js file size is too large.(8MB over)
So, I want to reduce this.
The main cause is the bundle.js contains vfs_fonts.js.
this file size is over 17MB.
I've tried fetching font file dynamically instead of bundling the file, referring to this page.
https://github.com/bpampuch/pdfmake/issues/1374
But it didn't work.
this is a part of my code
import * as pdfMake from 'pdfmake/build/pdfmake';
function fetchFont (fontURL: string) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', fontURL, true);
request.responseType = 'arraybuffer';
request.onload = function (e: any) {
resolve(request.response);
};
request.onerror = reject;
request.send();
});
}
interface Dictionary {
[index: string]: string;
}
class PdfFontLoader {
fontDefs: Dictionary[];
vfs: {};
loaded: boolean;
constructor () {
this.fontDefs = [];
this.vfs = {};
}
addFont (fontDef: Dictionary) {
this.fontDefs.push(fontDef);
}
load() {
return new Promise((resolve, reject) => {
if (this.loaded) {
resolve();
} else {
const fetches = this.fontDefs.map(fontDef => {
return fetchFont(fontDef.URL).then((data) => {
console.log('fetched ' + JSON.stringify(data));
this.vfs[fontDef.name] = data;
}).catch(e => {
console.log('error ' + e);
});
});
Promise.all(fetches).then(() => {
this.loaded = true;
resolve();
}).catch(reject);
}
});
}
}
const pdf = pdfMake;
pdf.vfs = fontLoader.vfs;
fontLoader.addFont({URL: 'GenShinGothic-Normal.ttf', name: 'GenShinGothic-Normal.ttf'});
fontLoader.addFont({URL: 'GenShinGothic-Bold.ttf', name: 'GenShinGothic-Bold.ttf'});
fontLoader.load().then(res => {
console.log('load finished');
pdf.fonts = {
GenShinGothic: {
normal: 'GenShinGothic-Normal.ttf',
bold: 'GenShinGothic-Bold.ttf'
}
};
console.log("vfs is " + JSON.stringify(pdf.vfs));
console.log
load finished
fetched {}
vfs is {"GenShinGothic-Normal.ttf":{},"GenShinGothic-Bold.ttf":{}}
if pdf generating...
Error: unknown font format
Can you help me?
====additional info=====
the font file(ttf) are deployed in same directory with bundle.js...

File upload progress using react-dropzone

Using react-dropzone to upload the file, I want to achieve the file progress like in percentage of file transfer or mbs data transfer.
Here is the link of: https://react-dropzone.netlify.com/
onDrop(acceptedFiles, uploadApi) {
const filesToBeSent = this.state.filesToBeSent;
if (acceptedFiles.length) {
if (acceptedFiles[0].type === FileTypeList.TYPE) {
filesToBeSent.push(acceptedFiles);
const formData = new FormData();
formData.append("file", acceptedFiles[0]);
uploadApi(formData).then((response) => {
this.setState({
filesPreview: [],
filesToBeSent: [{}],
showNotification: true,
uploadResponse: response,
});
this.props.fetchHistory();
});
} else {
this.setState({
fileType: true,
});
}
} else {
this.setState({
fileSize: true,
});
}
}
<Dropzone maxSize={this.props.maxSize} onDrop={(files) => this.onDrop(files, this.props.uploadApi)}>
{({ getRootProps, getInputProps }) => {
return (
<div {...getRootProps()} className={"dropzone"}>
<UploadPanel id="uploadFileContainerId">
<p>
<img id="uploadImage" src={UploadImage} />
</p>
<input {...getInputProps()} />
<div>{t("assets:UPLOAD_FILE")}</div>
<Note>
{this.props.maxSizeTitle ? t("workers:UPLOAD_WORKER_FILE_SIZE") : t("assets:UPLOAD_FILE_SIZE")}
</Note>
</UploadPanel>
</div>
);
}}
</Dropzone>
In case you wanna detect file upload process you can use XMLHttpRequest
onDrop(acceptedFiles) {
const formData = new FormData();
formData.append('file', acceptedFiles[0])
const xhr = new XMLHttpRequest();
xhr.open(/*params*/);
xhr.send(formData)
xhr.upload.onprogress = event => {
const percentages = +((event.loaded / event.total) * 100).toFixed(2);
this.setState({percentages})
};
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
if (xhr.status !== 200) {
/*handle error*/
}
/*handle success*/
};
}
You can use React Dropzone Uploader, which gives you file previews with upload progress out of the box, and also handles uploads for you.
import 'react-dropzone-uploader/dist/styles.css'
import Dropzone from 'react-dropzone-uploader'
const Uploader = () => {
return (
<Dropzone
getUploadParams={() => ({ url: 'https://httpbin.org/post' })} // specify upload params and url for your files
onChangeStatus={({ meta, file }, status) => { console.log(status, meta, file) }}
onSubmit={(files) => { console.log(files.map(f => f.meta)) }}
accept="image/*,audio/*,video/*"
/>
)
}
Uploads can be cancelled or restarted. The UI is fully customizable.
Full disclosure: I wrote this library to address some of the shortcomings and excessive boilerplate required by React Dropzone.
Here is another example based on turchak's answer for handling any number of files:
onDrop(acceptedFiles) {
const formData = new FormData();
for (const file of acceptedFiles) formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = event => {
const percentage = parseInt((event.loaded / event.total) * 100);
console.log(percentage); // Update progress here
};
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
if (xhr.status !== 200) {
console.log('error'); // Handle error here
}
console.log('success'); // Handle success here
};
xhr.open('POST', 'https://httpbin.org/post', true);
xhr.send(formData);
}

Resources