This is my first question in Stack Overflow. Any suggestions woule be really appreciated.
I'm working on creating a website and the main function of it is to allow user to upload images as many as they want. It can come from different folders and users are able to browse for images as many times as they want. In addition, users can preview image they selected. In case users do not want one of photos selected at first. User can delete it by clicking the button near by the previewed image.
At View Part of Laravel, you will see the button to browse multiple files with the text to tell users how many photos selected right now. When I'm working with View Part, it seems to work perfectly. However, the result after store the form. It shows that it stored only the photos selected come from the last time browsing only.
To see more clear picture, here is the example.
- User choose 4 photos which are A, B, C, and D
- Those 4 photos are previewed and each of them have the deleted button next to it
- User do not want D, so user delete it by clicking the button near the preview section
- The left photos are A, B, and C
- User want to upload more photos
- User choose 3 more photos which are E, F, and G
- The total number of photo to preview is 6 (A, B, C, E, F, G)
- A, B, and C are from the first time browsing
- E, F, and G are from the second time browsing
- User do not want G, so user delete it by clicking the button near the preview section
- The left photos are A, B, C, E, and F
- Finally, user click submit button
The expected and correct result of storing this from is to have A, B, C, E, and F as the photos selected from user. However, it turn out that the database only store E and F which are the photos from the second time browsing.
I want to fix it so it can save the correct photos; in this case, A, B, C, E, and F.
I use javascript to add or remove the preview image and add the checkbox to store the name of the image selected. I will use this later in the Controller to check whether it is the file user want to store or not. Here is the code :
var totalFiles = [];
function handleFileSelect(evt) {
// FileList object
var files = evt.target.files;
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<input type="checkbox" name="selected[]" value="' , theFile.name, '" style="display: none;" checked /><img width=50% height="auto" class="thumb p-3" src="', e.target.result,
'" title="', theFile.name, '"/>', "<button onclick='deleteImage()'>" + "<i class='fas fa-trash-alt' aria-hidden='true'></i>" + " ลบรูปภาพ</button><br/>"].join('');
document.getElementById('preview_photo').insertBefore(span, null);
};
})(f);
totalFiles.push(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
if(Array.from(totalFiles).length > 0){
document.getElementById('count_selected_photo').innerHTML = "Image Selected: " + Array.from(totalFiles).length + " Photo(s)";
}
else{
document.getElementById('count_selected_photo').innerHTML = "No Image Selected";
}
}
function deleteImage() {
var index = Array.from(document.getElementById('preview_photo').children).indexOf(event.target.parentNode)
document.querySelector("#preview_photo").removeChild( document.querySelectorAll('#preview_photo span')[index]);
totalFiles.splice(index, 1);
if(Array.from(totalFiles).length > 0){
document.getElementById('count_selected_photo').innerHTML = "Image Selected: " + Array.from(totalFiles).length + " Photo(s)";
}
else{
document.getElementById('count_selected_photo').innerHTML = "No Image Selected";
}
}
document.getElementById('before_photo').addEventListener('change', handleFileSelect, false);
In Controller, I use request->file('before_photo') to get the file selected before deleting and get the name of photo selected by using request->input('selected') and compare these two together. If the name of the file in request->file('before_photo') is the same in request->input('selected'), it will store this photo. If not, which means user delete it before clicking submit, it will not store.
Here is some part of my code in Controller :
// Store Photo If Existed
if (request()->hasFile('before_photo')) {
// File Chosen at First Time
$before_photos = $request->file('before_photo');
// File Chosen After Delete Image
$selected_photos = $request->input('selected');
// Collect Path of Each Photo
$paths = [];
// Collect File Name of Each Photo
$filenames = [];
foreach($selected_photos as $selected_photo){
foreach ($before_photos as $before_photo) {
// If File Name of Chosen Photo at First Time = File Name of Photo After Deleting
if($before_photo->getClientOriginalName() == $selected_photo){
// Transform The File Name to be 'before-photo-TIME-name-originalFileName'
$filename = "before-photo-" . time() . "-name-" . $before_photo->getClientOriginalName();
$paths[] = $before_photo->storeAs('', $filename, 'irepair_photo');
array_push($filenames, $filename);
}
}
}
// Transform List of Photo Name into Array
$repair_ticket->before_photo = $filenames;
}
If you have any suggestion or comments on this, please leave your message below. Thank you for your attention.
This might not be what you are looking for but should guide you in the right direction. I see you are using vanilla JavaScript which might make things trickier. But since you are also using Laravel and Laravel ships with Vue support (at least it used to), I'll be showing you my implementation using Vue. If you know JavaScript, it shouldn't be that hard to understand as Vue is very easy.
The trick with multi file uploading is to use another array to store your files as the native
FileListobject that is returned by the file input is immutable. Look at the code snippets below (I have tried to add comments to explain what I'm doing.)You can see a working example in this pen.
In the HTML section, all we have to do is to listen to changes to the file input and setup a place where we can display thumbnails for the selected images. Vue makes this very easy by using the
v-fordirective to iterate over an array. The current element in the iteration is available for theliand its child elements and we can use it to populate attributes such as theimg'ssrc.The JavaScript part is pretty easy. What we are doing here is setting up a
photos"local state" to store our photos. We use this variable to iterate over and display thumbnails in our HTML. When a user selects images, we take the files and generate a thumbnail for them using theFileReaderobject and push it to thephotosvariable along with the actual file.When a user wants to unselect an image, we just
spliceit from thephotosarray so thephotosarray will always contain photos the user wants to upload. When we come to uploading, if we are using AJAX to send the request we can just create aFormDataobject and append each photo to it by iterating over thephotosvariable. In a Laravel backend, these photos can then be accessed usingrequest()->file(). Before sending your request you have to set the proper headers though, mainly theContent-Type: multipart/form-data.I hope this gave you some general idea on how to proceed with this issue. If you have any questions feel free to leave them in the comments section and I'll try my best to explain more.
UPDATE: More info on working with the photos on your Laravel Backend
Once you are successfully sending the request from your front end, you can access the photos/files using Laravel's
request()->file()helper function. This will return all the files in the request in an array.Output will look something like:
As you can see each file is an instance of
Illuminate\Http\UploadedFile. To store this file, you have a couple of options. One is to use thestoreorstoreAsfunction on the file or just use theStoragefacade'sputorputFileAsmethod. Here is an example:Read the Laravel docuemntation on file storage to understand other options.
$filePathswill be an array of paths where the photos were stored.