KendoReact upload - passing file Id to React after saving file - kendo-react-ui

I'm a bit stuck with the kendo react upload control.
I need to customise the rendering of the kendo react upload control.
After the user upload the file, I want to save the file in database. Then I need to pass the database Id back to the client because if the user then wants to remove the file I also need to clear the database.
To give you an idea this is what I would like to achieve.
https://stackblitz.com/edit/react-ghna5h
(When you start stackblitz open file app/main.jsx)
Is it possible?
Thanks for your help

I solved this particular issue using a combination of the onStatusChange and onBeforeRemove events. The server's 'save' endpoint returns an id (responseUID) that can be accessed through the onChange event's response object. I set this id on the 'file' object. Then when you're removing the file I pass the responseUID along in the additionalData field which gets put into the POST body.
const onStatusChange = (e) => {
if (e.response) {
const fileId = e.response.response.responseUID;
// This does not deal with multiple or batch uploads.
e.affectedFiles[0].responseUID = fileId;
}
};
const onBeforeRemove = (e) => {
e.additionalData.responseUID = e.files[0].responseUID;
};
return (
<Upload
batch={false}
defaultFiles={[]}
withCredentials={false}
saveUrl="https://localhost/upload/save"
removeUrl="https://localhost/upload/remove"
onStatusChange={onStatusChange}
onBeforeRemove={onBeforeRemove}
/>
);

Related

Why is the S3 Url not able to be added to the object and submitted to MongoDB?

I am working on this nextjs jobboard website app and I have a job object which has several key value pairs that gets filled up over several sibling components and is to be submitted by the current component. The current component's job is to upload the job file attachment to s3, retrieve the url, and store it in the job object and upload it to mongodb. The file was successfully added to s3 and able to grab the url. However after retrieving the file url from s3, it was not able to add the string url to the job object which has a specific string field for the url. When i click submit job from the UI, the joblisting was able to be submitted to mogodb with all the other fields filled with values from other sibling components but failed to add the string URL. I have tried to find a way to solve it but was unable to even though the logic seems so clear.
I need help. Thank you in advance
const submit = async (e) =>{
setsubmitjob(true);
let {url} = await uploadToS3(file);
setFileUrl(url);
setjob({
...jobarray, joblistingattachment: url
})
createjoblist();
}
const createjoblist = async () =>{
try {
const res = await axios.post("http://localhost:3000/api/job", jobarray);
setuploadstatus(true);
sleep(3500).then(() => {
router.push("/");
})
} catch (e) {
console.log("Connection to server failed. Please try again in a minute")
}
}

expressJS/multer multiple file upload, render for each file

I am using expressJS with multer, and want to create a website to upload multiple files.
So I already manage to get this working. Currently I am using XMLHttpRequest for POST request on the client side, and update elements on page also from the client side script. For 5 files selected to upload with one click on submit button, I can do 5 post request from the client-side and update feedback one by one.
// load file
formData.append(file1);
let req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState == XMLHttpRequest.DONE) {
updateView(); // Change layout in HTML when POST DONE
}
}
req.open("POST", "/upload");
req.send(formData);
Reapeat for multiple files [file1, file2, file3,...]
Question:
So now, I would like to use res.render() with parameters instead. I am wondering if it is possible to get one POST request and render the page multiple times. If I POST 5 files at once, I want to render every time one file is processed, and let the user on the client side see the feedback. I don't need a progress bar, I just want to show some basic informations and status of the file. Play a bit around with res.render(), but didn't find anything working as I wished.
So I can avoid adding any HTML code in my JavaScript. And just use the handlebar on the backend.
Front end:
formData.append(file1);
formData.append(file2);
formData.append(file3);
let req = new XMLHttpRequest();
req.open("POST", "/upload");
req.send(formData);
And for backend I want something like this:
router.post('/upload', upload.array('multi_files'), async function (req, res, next) {
const files = req.files;
for (const file of files) {
let result = processFile(file);
res.render('/', result);
}
});
But unfortunately I cannot res.render multiple times.

what is the difference between readFileSync and readAsDataURL?

NOTE: this is NOT a production application. It is a small app that I am making to explore the capabilities of selecting, opening, saving and reading an image from a DB.
I do not know what method I should call to get the correct Buffer data to store image in mongodb. ( I am aware that mongoDB has a 16mb limit on documents )
In my app, I want the user to be able to select an image from a file on their pc, give the user a preview of the image, then when the user hits submit (not shown here) save the image into mongodb
I know when the user clicks the "choose file" button I can get the file like this:
const file = event.target.files[0]
if (!file) {
// user has hit the cancel button
return;
}
//console.log(file);
I can get information about the file:
console.log(file.type);
console.log(file.size);
and here is how the image is read:
let fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = (e) =>{
this.productImageAdd(e.target.result);
}
This is how the image is stored in the (react) component state
productImageAdd = (file) => {
let temp = this.state["filesArray"] || [];
temp.push(file);
this.setState({"filesArray": temp});
}
Here is the state of my component
this.state = {
filesArray: [],
};
In another component, I get a listing of all the images and then map them to an item for display:
return filesArray.map((file, index) => (
<FileDropZoneItem key={index} id={index} file={file} />
));
In FileDropZoneItem, this is how images are "previewed":
return (
<li className="flex-item ">
<img src={file} width="200" height="200"/>
</li>
);
I saw this stackoverflow question and I am moving towards that to store the image in MongoDB:
Store an image in MongoDB using Node.js/Express and Mongoose
In that post, their schema is like this:
var schema = new Schema({
img: { data: Buffer, contentType: String }
});
And they store the image like this:
var A = mongoose.model('A', schema);
var a = new A;
a.img.data = fs.readFileSync(imgPath);
a.img.contentType = 'image/png';
but I don't really know the difference between fs.readFileSync and fr.readAsDataURL.
Will storing to mongodb work using fr.readAsDataURL or do I need fs.readFileSync ?
I also saw: Difference between readAsBinaryString() and readAsDataURL() in HTML5 FileReader API
From the MongoDB manual GridFS
GridFS uses two collections to store files. One collection stores the file chunks, and the other stores file metadata. The section GridFS Collections describes each collection in detail.
For reference you can read Storing images in MongoDB
readAsDataURL permits to store the images inside the JSON document encoded as base64 characters. Given the size of the Products pictures 200px x 200px seems the best option for your hello world app.
readFileSync can have blocking issues in the server like Nodejs for image handling behavior and reads the binary file to store it as Binary Data.
IMO the best solution is to not overload mongoDB and Nodejs with the image handling and store only an URL to the image in the mongoDB document leaving the image serving process to a proxy like nginx.
The Gridfs option depends on the driver used. Following is Gridfs use in Nodejs that uses its own reader:
fs.createReadStream('./meistersinger.mp3').
pipe(bucket.openUploadStream('meistersinger.mp3')).

Getting octet-stream instead of image in node js

In my front-end I use angular6 and I have this form where you can choose an image either by dropping a file in a div or clicking the div to open a file picker.
The form is
<form [formGroup]="imageUpload" (ngSubmit)="imageUploadSubmitted($event.target)" >
<div id="imageDrop" (click)='imageInput.click()' (drop)="drop($event)" (dragover)="allowDrop($event)" #imageDrop></div>
<input type="file" (change)='imageChange($event)' #imageInput id="imageInput" name = 'imageInput' accept=".png, .jpg, .jpeg, .gif" formControlName="imageInput" required >
<button type="submit" >Submit</button>
</form>
This is the typescript
selectedFile:File=null;
allowDrop(e) {
e.preventDefault();
}
drop(e) {
e.preventDefault();
this.imageUpload.controls.imageInput.reset();
this.selectedFile = e.dataTransfer.files[0];
let input = this.imageUpload.controls.imageInput as any;
input.value = e.dataTransfer.files[0];
}
imageChange(e){
this.selectedFile = e.target.files[0];
}
So, when dropping an image, get it from the event and put it in the file input. When the form is submitted, send the form data to a service for posting. The console.log shows a File object (__proto__ : File) whether you picked an image from the file picker, or dropped one in the div.
imageUploadSubmitted(form){
console.log('imageInput value - ', this.imageUpload.controls.imageInput.value);
this.mapcmsService.uploadImage(form).subscribe((data) =>{
if(data.success){ alert('all good'); }
else{ alert('error'); }
})
}
The service grabs the form controls and builds a FormData object to send in node.
uploadImage(data){
let formData = new FormData(data);
let headers = new Headers();
headers.append('Authorization',this.userToken);
return this.http.post('http://localhost:3000/user/upload/image', formData ,{headers:headers} ).pipe(map(res => res.json()));
}
In node I use formidable to get the file and save it. This is for testing.
var form = new formidable.IncomingForm();
form.parse(req);
form.on('file', function (name, file){
console.log('file' , file);
});
The problem is that if I have chose an image via the file picker, I get a file of type image/jpeg , a name a path and a size.
If I chose an image by drag and drop, I get a file of type application/octet-stream. This has no name and no size.
I would like to get image/jpeg in both cases. I am confused, is this a node or angular issue? How can I fix this ?
Thanks
angular 6 , node 8.11.1, formidable 1.2.1
The issue is that the assignment in the drop event does not actually set the value of the input because file inputs are not supported by Angular reactive forms. I am talking about this lines:
let input = this.imageUpload.controls.imageInput as any;
input.value = e.dataTransfer.files[0];
So when you drop in your case you are not actually sending the file to the server at all. That is why the data you get is wrong. Here are also links to two other stack overflow questions about reactive forms and file upload where there is more information regarding this issue.
How to include a file upload control in an Angular2 reactive form?
Using reactive form validation with input type=“file” for an Angular app
There are two possible solutions to workaround this issue. The first is that you get the ElementRef of the file input by using the ViewChild query. And then assign the files to the native html element directly. The good thing with this approach is that you will see the dropped file name also in the file input. The downside is that this might not work in all browsers. The official documentation on MDN says that it works in modern browsers but for me it did work in Chrome and not in Edge.
Here is a sample of the code:
#ViewChild('imageInput') private imageInput: ElementRef;
public drop(e: DragEvent) {
e.preventDefault();
this.imageUpload.controls.imageInput.reset();
this.selectedFile = e.dataTransfer.files[0];
this.imageInput.nativeElement.files = e.dataTransfer.files;
}
The other approach is that you build the FormData object yourself by adding the selected file yourself in code before sending it to the server. This should work anywhere without issues. Here is a sample code:
let formData = new FormData();
formData.append('imageInput', this.selectedFile);
I have created also a StackBlitz example where you can see all the code.

File input meteor cfs

So i see this code on the Docs
Template.myForm.events({
'change .myFileInput': function(event, template) {
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {
//Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
});
});
}
});
But i dont want the file upload inmediatly when i click "myFileInptu" , i want to store that value (from the input), and insert lately with a button, so there is some way to do this?
Also its there a way to upload a FSCollection without a file? just metadata
Sorry for bad english hope you can help me
Achieving what you want to requires a trivial change of the event, i.e switching from change .myFileInput to submit .myForm. In the submit event, you can get the value of the file by selecting the file input, and then storing it as a FS File manually. Something like:
'submit .myForm': function (event, template) {
event.preventDefault();
var file = template.find('#input').files[0];
file = new FS.File(file);
// set metadata
file.metadata = { 'caption': 'wow' };
Images.insert(file, function (error, file) {
if (!error)
// do something with file._id
});
}
If you're using autoform with CollectionFS, you can put that code inside the onSubmit hook. The loop you provided in your question works also.
As for your second question, I don't think FS.Files can be created without a size, so my guess is no, you can't just store metadata without attaching it to a file. Anyways, it seems to me kind of counterintuitive to store just metadata when the metadata is supposed to describe the associated image. You would be better off using a separate collection for that.
Hope that helped :)

Resources