My goal is to get a way to upload multiple images and also set in which order they should be displayed later on (it's for an ecommerce website). I have an idea which seems to work. But I would like to know if there is a better way of doing this. Here is my code. Let's assume the number of images is limited to 3.
- One input for multiple images.
- Preview images and make them sortable.
- Store the order in hidden inputs.
- Store the images in the filesystem.
- Add the path and order index of every image to the database.
HTML
<!-- One input for all images -->
<input type="file" name="images[]" onchange="previewImages(this)" multiple>
<!-- This <div> will be made sortable with SortableJS -->
<div id="preview-parent">
<div class="preview">
<!--
Hidden input to save the order of images.
The idea is to have images_order[name_of_image] = index.
name_of_image and index will be set with JavaScript
-->
<input class="order-input" type="hidden" name="images_order[]" value="0">
<!-- <img> is for preview -->
<img class="preview-image" src="" alt="Image1">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="1">
<img class="preview-image" src="" alt="Image2">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="2">
<img class="preview-image" src="" alt="Image3">
</div>
</div>
JavaScript
function previewImages(input) {
// First I take the images
var file = input.files
// Then I go through each of them. This code is based on the explanation
// from https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
for (var i = 0; i < file.length; i++) {
let preview = document.querySelectorAll('.preview-image')[i]
let reader = new FileReader()
reader.onloadend = function () {
preview.src = reader.result
}
if (file[i]) {
reader.readAsDataURL(file[i])
// In addition to previewing images I take their names
// and put them into my hidden inputs
let order_inputs = document.querySelectorAll('.order-input')
order_inputs[i].name = 'images_order[' + file[i].name +']'
} else {
preview.src = ""
}
}
}
// I make the images sortable by means of SortableJS
var el = document.getElementById('preview-parent')
new Sortable(el, {
animation: 150,
// This function updates the values of my hidden inputs
// every time a change is made
onEnd: function (event) {
let order_inputs = document.querySelectorAll('.order-input')
for (var i = 0; i < order_inputs.length; i++) {
order_inputs[i].value = [i]
}
}
})
function previewImages(input) {
var file = input.files
for (var i = 0; i < file.length; i++) {
let preview = document.querySelectorAll('.preview-image')[i]
let reader = new FileReader()
reader.onloadend = function () {
preview.src = reader.result
}
if (file[i]) {
reader.readAsDataURL(file[i])
let order_inputs = document.querySelectorAll('.order-input')
order_inputs[i].name = 'images_order[' + file[i].name +']'
} else {
preview.src = ""
}
}
}
var el = document.getElementById('preview-parent')
new Sortable(el, {
animation: 150,
onEnd: function (event) {
let order_inputs = document.querySelectorAll('.order-input')
for (var i = 0; i < order_inputs.length; i++) {
order_inputs[i].value = [i]
}
}
})
#preview-parent {
display: flex;
gap: 1rem;
margin: 1rem;
}
.preview {
height: 100px;
width: 100px;
padding: 1rem;
border: 1px solid grey;
}
.preview img {
max-height: 100%;
max-width: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<input type="file" name="images[]" onchange="previewImages(this)" multiple>
<div id="preview-parent">
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="0">
<img class="preview-image" src="" alt="Image1">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="1">
<img class="preview-image" src="" alt="Image2">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="2">
<img class="preview-image" src="" alt="Image3">
</div>
</div>
I haven't figured out yet how to limit the number of submitted images. But I saw a number of discussions about that, so I hope it will not be a problem.
PHP. The next step is done by means of PHP (CodeIgniter 4). I take the images, store them in my filesystem. Also I add the path to every image and its order index (taken from the hidden input) to the database. Later when a user will open certain product, the data will be taken from the database and ordered by order index. So basically my controller has this:
// I inserted the product before and can get its id
$product_id = $this->products->insertID();
// This line is just for reference, I actually create $input earlier
$input = $this->request->getPost();
// Here I take all the images as suggested in CodeIgniter docs
if($imagefile = $this->request->getFiles())
{
foreach($imagefile['images'] as $img)
{
if ($img->isValid() && ! $img->hasMoved())
{
// Store each image
$name = $img->getName();
$img->move('assets/images/'.$product_id, $name);
// Add info about the image to the database
$data = [
'product_id' => $product_id,
'path' => base_url('assets/images/'.$product_id.'/'.$name),
'order_index' => $input['images_order'][$name],
];
$this->imagesModel->insert($data);
}
}
}
It would be perfect if I could upload multiple images at once and then not only reorder them but also be able to replace one of the images. If someone could share any ideas, I will appreciate it very much!