ReactJS: How to send multiple files in server (Laravel) - javascript

I'm new in ReactJS and my backend is laravel and I have problem regarding inserting multiple files to the database, however if i use only the single upload (inserting one file to the database it's working for me.).
PROBLEM: regarding inserting multiple files in the database.
GOAL: To insert multiple files to the database
I have here
FORMDATA:
const formData = new FormData();
formData.append('myFile', this.state.image);
RESPONSE:
axios.post('/api/save_gallery_album_image', formData).then(response => {
console.log(response);
}).catch(error => (error.response));
OnChange:
handleChangeImage(e){
this.setState({
image:e.target.files[0]
})
// console.log(e.target.files);
}
JSX:
<label>Image</label>
<div className="custom-file">
<input type="file"
name="image"
multiple
onChange={this.handleChangeImage}
className="custom-file-input form-control"
accept="image/x-png,image/gif,image/jpeg"
id="file_selected"/>
<label className="custom-file-label" htmlFor="validatedCustomFile">Choose file...</label>
</div>
Server side Controller:
public function save_gallery_album_image(Request $request)
{
$multiple_gallery_file_upload = $request->file('myFile');
$titleValue = $request->get('titleValue');
$pageValue = $request->get('pageValue');
$now = new DateTime();
if($request->hasFile('myFile'))
{
foreach($multiple_gallery_file_upload as $myfiles)
{
$uniqueid=uniqid();
$original_name=$request->file('myFile')->getClientOriginalName();
$size=$request->file('myFile')->getSize();
$extension=$request->file('myFile')->getClientOriginalExtension();
$name=$uniqueid.'.'.$extension;
$path=$request->file('myFile')->storeAs('public',$name);
DB::insert('INSERT INTO album_category (cid,image,created_at) VALUES (?,?,?) ',[
$titleValue,
$name,
$now
]);
}
return response()->json('Input Saved');
}
}

I am facing the same problem I have fixed like this I hope it's helpful for you.
put this code on the right place and check it's working to upload multiple images Thanks.
<form>
<input name="product_image" type="file" multiple onChange={e => this.HandleProductImage(e)}/>
<button type="submit" onClick={e =>this.submitForm(e)}>
</form>
HandleProductImage = e => {
this.setState({
product_image: e.target.files
});
};
submitForm = e => {
const product = new FormData();
if (this.state.product_image) {
for (const file of this.state.product_image) {
product.append("image", file);
}
}
//then use your API to send form data
}

I guess you should request multiple time as files existed using loop.
When you request array of files in multipart form, the multipart form don't include all of your files. So you should send upload requests separately.
After check the comment, I added some sample code.
OnChange:
handleChangeImage(e) {
// Set file list
let files = e.target.files;
files.map((file) => {
// make diffent formData per each file and request.
let formData = new FormData();
formData.append('myFile', file);
axios.post('/api/save_gallery_album_image', formData)
.then(response => {
console.log(response);
}).catch(error => (error.response));
});
}
If you want to save multiple files in one request, I think you should also change your server-side codes.
For now, your server might save just one file per one request.

Related

Laravel 9 and Javascript: how to download a file returned from Storage::download()

DISCLAIMER: Before creating this question, I've checked here, here and here, and also checked Laravel docs.
Context
Laravel 9 full-stack
No JS framework on front-end, which means I'm using vanilla JS
The folders on Storage are setted like this:
storage
app
public
folder1
folder1A
folder1B
folder1C
etc
The files stored in each folder1X are .pdf format and I don't know its names.
No folders are empty, nor with invalid/corrupted files.
The problem
I have a FileController.php to download files that are inside a folder1X/ directory. The method to download it is as follows:
public function downloadFileFromStorage(Request $request): mixed
{
$dirpath = $request->dirpath; // dirpath = public/folder1/folder1X.
$files = Storage::allFiles($dirpath);
return response()->download(storage_path('app\\' . $files[0]));
}
(Note: dirpath is sent in a axios request by client and is also fetched from database on a previous request)
My Javascript CLI needs to enable the download of this file. The download is enabled by clicking on a button. The button calls downloadPDF(dirpath) which works as follows:
function downloadPDF(dirpath) {
axios.post('/download-pdf-file', { dirpath })
.then(
success => {
const url = success.data
const a = document.createElement('a')
a.download = 'file.pdf'
a.href = url
a.click()
},
error => {
console.log(error.response)
}
)
}
But, when I run this function, I get a about:blank#blocked error.
Attempts
Changed the a HTML DOM approach to a window.open(url) on client;
Changed response() to Storage::download($files[0], 'file-name.pdf'), and with this I also tried using Blob on client as follows:
success => {
const blob = new Blob([success.data], { type: 'application/pdf' })
const fileURL = URL.createObjectURL(blob)
window.openURL(fileURL)
},
Also mixed Blob with the a HTML DOM approach;
Changed storage_path argument to /app/public/ before concatenating to $files[0].
UPDATE
Following tips from #BenGooding and #cengsemihsahin, I changed files to the following:
JS
// FileDownload is imported on a require() at the code beginning
function downloadPDF(dirpath) {
axios({
url: '/download-pdf-file',
method: 'GET',
responseType: 'blob',
options: {
body: { dirpath }
}
}).then(
success => {
FileDownload(success.data, 'nota-fiscal.pdf')
}
)
}
PHP:
public function downloadFileFromStorage(Request $request): mixed
{
$dirpath = $request->dirpath; // dirpath = public/folder1/folder1X.
$files = Storage::allFiles($dirpath);
return Storage::download($files[0], 'filename.pdf');
}
and now it downloads a corrupted PDF that can't be opened.
Finally found the issue, and it was here:
axios({
url: '/download-pdf-file',
method: 'GET',
responseType: 'blob',
options: { // here
body: { dirpath } // here
}
})
Laravel's Request arrow operator -> can't fetch a GET body sent through options (At least, not on $request->key fashion; see more about it here) thus making me download a corrupted file - it wasn't fetching any file on Laravel as it didn't get any path at all.
Here is the solution I came with:
As I want to get a file in a route that doesn't change except for the 1X at folder1X, I'm processing the path obtained and sending the 1X as a GET query param:
let folderNumber = dirpath.split('/')
folderNumber = folderNumber[folderNumber.length].replaceAll('/', '')
axios({
url: '/download-pdf-file?folder=',
method: 'GET',
responseType: 'blob'
})
This way I don't pass the whole path to back-end and it's possible to get folderNumber by using $request->query():
public function downloadFileFromStorage(Request $request): mixed
{
$folderNumber = $request->query('folderNumber');
$folderPath = '/public/folder1/folder' . $folderNumber . '/';
$files = Storage::allFiles($folderPath);
return Storage::download($files[0], 'file-name.pdf');
}
In a nutshell:
To download files, use GET requests;
To send arguments within GET requests, use query parameters and fetch them with $request->query('keyname') (or find out another way. Good luck!);

How do you get the file path of an <input> file?

I'm trying to use the Axios POST method to upload a file to Pinata IPFS:
FRONT-END:
<body>
<input type="file" id="file-upload" ></input>
<script> let file = document.getElementById("file-upload").value;</script>
</body>
FILE GETS SENT TO BACKEND THROUGH SOCKET.IO:
pin = (pinataApiKey, pinataSecretApiKey, file) => {
url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
const data = new FormData();
data.append("file", fs.createReadStream(`${file}`));
return axios.post(url, data, {
maxBodyLength: "Infinity",
headers: {
"Content-Type": `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey,
},
});
};
pin() only works when I use a local file path (i.e. C:/Users/anon/Desktop/project/untitled.png). If I try to use file.value (whose path is "C:\fakepath\testImage.jpeg"), the code doesn't work. I need the actual path of the uploaded file.
What do you get when you log file??
Perhaps you need to put this:
let file = document.getElementById("file-upload").value;
inside your js file:
And probably the best thing is to do some validations before the file is uploaded.

React SPFx - Adding files to SharePoint list field using PnPjs

I'm building an application using SPFx Webpart with React. On one of my components I have a form that the user can fill out. I'm using PnPjs to push the user's responses into my list field. Everything works as expected.
I was looking at how to add a file or attachment field type to a list. I saw I can do it in the powerapps. So now in my "Product" list I have a field called attachments. When I attach files to that field from SharePoint's backend and then make a call to the list using PnPjs the attachment field does not return information about the files. But rather a boolean with a true or false.
pnp.sp.web.lists.getByTitle("Products").items.filter("Id eq '" + this.props.match.params.id + "'").top(1).get().then((items: any[]) => {
console.log(items);
}
So this works perfect and returns back the item which should have had the attachments from the code below. Now in my items console I get back Attachments: true or Attachments: false
I'm using react-dropzone to allow users to upload files. Using PnPjs how do I upload the files to that item?
Here is how I'm creating the item:
pnp.sp.web.lists.getByTitle("Requests").items.add({
Title: this.state.SuggestedVendor,
Client_x0020_Email: this.state.getEmail,
Created: this.state.startDate,
Attachments: //need help here
}
Here is my code for the dropdown files:
onDrop = (acceptedFiles) => {
console.log(acceptedFiles);
//Assuming this is where I do the logic
}
<Dropzone
onDrop={this.onDrop}
multiple
>
{({getRootProps, getInputProps, isDragActive}) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
{isDragActive ? "Drop it like it's hot!" : 'Click me or drag a file to upload!'}
</div>
)}
</Dropzone>
And here is the response I get back from console.log(acceptedFiles);:
[File]
0: File {path: "John-Hancock-signature.png", name: "John-Hancock-signature.png", lastModified: 1590783703732, lastModifiedDate: Fri May 29 2020 13:21:43 GMT-0700 (Pacific Daylight Time), webkitRelativePath: "", …}
length: 1
I found this documentation here on how to push the files : https://pnp.github.io/pnpjs/sp/files/
You have to create the item, and then add an attachment to it.
You can add an attachment to a list item using the add method. This method takes either a string, Blob, or ArrayBuffer. pnp documentation
onDrop = (acceptedFiles) => {
acceptedFiles.forEach(file => {
const reader = new FileReader()
reader.onabort = () => console.log('file reading was aborted')
reader.onerror = () => console.log('file reading has failed')
reader.onload = async () => {
// get file content
const binaryStr = reader.result
// assume we have itemId
let itemId = 1;
let request = await sp.web.lists.getByTitle("Requests").items.getById(itemId)();
await request.attachmentFiles.add("file2.txt", "Here is my content");
}
reader.readAsArrayBuffer(file)
})
}

json_decode($request->array) returns empty value on one of the object properties (image file) - array(0) { } {"photo":null}

Dynamic Form
Adding and removing boxes works fine and data is fed well upon entering text on form. Even the photo is being captured very well
<form>
<div class="box" v-for="(data,i) in datas" :key="i">
<input type="text" v-model="data[i]['key1']>
<input type="text" v-model="data[i]['key2']>
<input type="file" #change="selectImage($event,i)">
// I have not included add/delete methods to reduce codes
<button #click.prevent="method_to_add_box">Add</button>
<button #click.prevent="method_to_delete_box">Del</button>
<button #click.prevent="submitData">Submit</button>
</div>
</form>
Vuejs scripts
datas: [
{key1:'value1',key2:'value2',photoToUpload:null}
],
methods: {
submitData() {
let theData = new FormData();
theData.append('datas',JSON.stringify(this.datas));
let config = {
headers: {
'Content-Type':'multipart/form-data',
}
};
axios.post('/url', theData, config)
.then(res => console.log(res))
.catch(err => console.log(err))
},
selectImage(event, i) {
let theImage = event.target.files[0];
this.datas[i].photoToUpload = theImage;
}
}
I receive the data in Laravel backend
$datas = json_decode($request->datas);
if(is_array($datas)) {
foreach($datas as $data) {
$object = new Object();
$object->field1 = $data->key1;
$object->field2 = $data->key2;
// problem starts here - photo seen as an empty object so can't store()
$path = $data->photoToUpload->store('/path');
$path = explode('/',$path); $filename = $path[2]
$object->photo = $filename;
if($object->save()) {
return new ObjectResource($object);
}
}
}
Troubleshooting & Results
Tried to return response immediately after json_decode($request->datas) and here are the results;
$datas = json_decode($request->datas);
return \response([$datas[0]->photoToUpload]);
Developer Tools Network Tab response returns
[{ }], status is OK
have also tried returning extension of the photo
return \response([$datas[0]->photoToUpload->extension()]);
Developer Tools Network Tab preview returns message below
"Call to undefined method stdClass::extension()", Status Code: 500 Internal Server Error
However, if I don't put the fileToUpload inside an array in Vuejs data I can append to formdata, receive in Laravel backend and upload without troubles.
What am I doing wrong here? Please assist

Client side compression with HTML5 and Javascript

Am working on a web application and we allow users to upload files to our server. Am trying to do client side compression before uploading files to the server. What would be the better way to achieve this using HTML5 and JavaScript.
Thanks.
The common mechanism to do what you want is using FileReader and a JavaScript client-side compression library (i.e. compressjs).
In 2022 it's almost too simple, if the browser supports CompressionStream, FormData and Response.
In the example below I use FormData to collect all the fields from the form.
Then I use the readable stream from the file, and pipe it though the compression stream. Then I use Response to read everything from the compressed stream and return it in a blob.
async function compress(file, encoding = 'gzip') {
try {
return {
data: await new Response(file.stream().pipeThrough(new CompressionStream(encoding)), {
headers: {
'Content-Type': file.type
},
}).blob(),
encoding,
};
} catch (error) {
// If error, return the file uncompressed
console.error(error.message);
return {
data: file,
encoding: null
};
}
}
theForm.addEventListener(
'submit',
(event) => event.preventDefault()
)
theForm.addEventListener(
'input',
async function(event) {
// collect all fields
const fd = new FormData(theForm);
// Get 'file handle' from imput elemen
const file = fd.get('theFile');
if (!file) return
const encoding = fd.get('theEncoding');
const compressed = await compress(file, encoding);
theMessage.value = [
'Compressed with', compressed.encoding,
'Source file was', file.size, 'bytes',
'and the compressed file', compressed.data.size,
'saving', ((1 - compressed.data.size / file.size) * 100)
.toFixed(0),
'%.'
].join(' ')
}
)
form>* {
display: block;
width: 100%;
}
<form id="theForm">
<select name="theEncoding">
<option>gzip</option>
<option>deflate</option>
<option>deflate-raw</option>
</select>
<input type="file" name="theFile" id="theFile">
</form>
<output id="theMessage"></output>

Categories