Developing a Drag-and-Drop Multiple Image Uploader in Joomla

Select your language

This is modern uploader with ajax power. And it allows to add remove images or allowed files.

Following the steps we will implement this in our component:

Step 1: Add PHP code in template view file(in our case components/com_lmstarbiats/tmpl/course/edit.php)

Here is code snippet:

<?php
/*
<!----------------
MULTIPLE FILE UPLOADER
------------------------------>
*/
if(isset($this->item->downloadable) && $this->item->downloadable!=""):

  $imgArr=explode("#",$this->item->downloadable);
?>
<div class="control-group">
	<div class="control-label"><label id="jform_downloadable-lbl" for="jform_downloadable">&nbsp;</label></div>
	<div class="controls">
	<?php
	$countfiles = count($imgArr);

	for($i=0;$i<$countfiles;$i++) : 
		
		$src="../images/courses/".$imgArr[$i];
	?>
	<div style="border:1px solid; margin-right: 15px; display:inline-flex">

		<img src="/<?php echo $src?>" id="img_<?=$i?>" alt="image not found" style="max-width:250px; height:auto;" />
		<input type="hidden" name="downloadable_file[]" id="jform_downloadable<?=$i?>" value="<?=$imgArr[$i]?>" />
		<a href="javascript:void(0)" class="remove" id="link_<?=$i?>" onclick="removeImage('link_<?=$i?>', 'img_<?=$i?>', 'jform_downloadable<?=$i?>')">Remove</a>

	</div>
	<?php
	endfor;
	?>

	<script>
	// Ajax call to remove image and file data
	function removeImage(link_, img, txtfield)
	{

		var text = 'Do you want to remove this Image Entry?';
		if (confirm(text) == true) {

		var txtfield_=document.getElementById(txtfield);
		var filename=txtfield_.value;
		jQuery.ajax({
			type: "POST",
			url: "index.php?option=com_lmstarbiats&task=course.removeimage&filename="+filename,
			data:"filename="+filename,
			beforeSend: function(){
			//	jQuery("#txtKeyword").css("background","#FFF url(LoaderIcon.gif) no-repeat 165px");
			},
			success: function(data){
			document.getElementById(img).setAttribute('style', 'display:none;');
			txtfield_.remove();//.setAttribute('value','');

			// remove : remove link
			document.getElementById(link_).remove();
			}
		});	

		} else {
			text = "You canceled!";
		}
	
	}
	</script>
	</div>
</div>
<?php
endif;
?>

Code explained : In very first lines, it fetched database record and checked if this row item has downloadable files or not. If yes show them in grid view along with remove function as a link.

This remove link used JavaScript function removeimage and made an ajax called to controller function(coming next) to remove specific image.

Step 2: Add drag & drop HTML

<div class="control-group">
<div class="control-label"><label id="jform_cell-lbl" for="jform_cell"><?=Text::_("COM_LMST_COURSE_DOWNLOADABLE")?></label>
</div>
<div class="controls has-success">

<div class="box">
    <label>
        <strong>Choose files</strong>
        <span>or drag them here.</span>
        <input class="box__file" type="file" name="downloadable[]" id="jform_downloadable" multiple />
    </label>
    <div class="file-list"></div>
</div>

</div>
</div>
<!-- Script --> <script> const box = document.querySelector('.box'); const fileInput = document.querySelector('[name="downloadable[]"'); const selectButton = document.querySelector('label strong'); const fileList = document.querySelector('.file-list'); let droppedFiles = []; [ 'drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop' ].forEach( event => box.addEventListener(event, function(e) { e.preventDefault(); e.stopPropagation(); }), false ); [ 'dragover', 'dragenter' ].forEach( event => box.addEventListener(event, function(e) { box.classList.add('is-dragover'); }), false ); [ 'dragleave', 'dragend', 'drop' ].forEach( event => box.addEventListener(event, function(e) { box.classList.remove('is-dragover'); }), false ); box.addEventListener('drop', function(e) { droppedFiles = e.dataTransfer.files; fileInput.files = droppedFiles; updateFileList(); }, false ); fileInput.addEventListener( 'change', updateFileList ); function updateFileList() { const filesArray = Array.from(fileInput.files); if (filesArray.length > 1) { fileList.innerHTML = '<p>Selected files:</p><ul><li>' + filesArray.map(f => f.name).join('</li><li>') + '</li></ul>'; } else if (filesArray.length == 1) { fileList.innerHTML = `<p>Selected file: ${filesArray[0].name}</p>`; } else { fileList.innerHTML = ''; } } </script> <style> .box { background-color: white; outline: 2px dashed black; height: 400px; } .box.is-dragover { background-color: grey; } .box { display:flex; flex-direction: column; align-items: center; justify-content: center; } .box label strong { text-decoration: underline; color: blue; cursor: pointer; } .box label strong:hover { color: blueviolet } .box input { display: none; } </style>

Code explained : In this part of code we added drag & drop HTML along with required JavaScript and CSS code.

Step 3: Add functions to controller file (in our case components/com_lmstarbiats/src/controller/CourseController.php)

// Add this at very top : 
use Joomla\CMS\Filesystem\File;

/* MULTIPLE FILE UPLOADER ***************************/ public function optimizeImage($sourcePath, $destinationPath, $newWidth, $newHeight, $quality = 80) { // Get original dimensions and type of the image list($originalWidth, $originalHeight, $imageType) = getimagesize($sourcePath); // Calculate aspect ratio $aspectRatio = $originalWidth / $originalHeight; if ($newWidth / $newHeight > $aspectRatio) { $newWidth = $newHeight * $aspectRatio; } else { $newHeight = $newWidth / $aspectRatio; } // Create a new true color image with specified dimensions $newImage = imagecreatetruecolor($newWidth, $newHeight); // Load the source image based on its type switch ($imageType) { case IMAGETYPE_JPEG: $sourceImage = imagecreatefromjpeg($sourcePath); break; case IMAGETYPE_PNG: $sourceImage = imagecreatefrompng($sourcePath); break; case IMAGETYPE_GIF: $sourceImage = imagecreatefromgif($sourcePath); break; default: die('Unsupported image type'); } // Resize the original image into the new canvas imagecopyresampled($newImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $originalWidth, $originalHeight); // Save the resized and compressed image to the destination path switch ($imageType) { case IMAGETYPE_JPEG: imagejpeg($newImage, $destinationPath, $quality); // Adjust quality for compression break; case IMAGETYPE_PNG: // For PNG, quality ranges from 0 (no compression) to 9 (maximum compression) $compressionLevel = (int)((100 - $quality) / 10); imagepng($newImage, $destinationPath, $compressionLevel); break; case IMAGETYPE_GIF: imagegif($newImage, $destinationPath); break; } // Free memory imagedestroy($newImage); imagedestroy($sourceImage); } public function resize_image($sourcePath, $destinationPath, $newWidth, $newHeight, $crop = true) { // Get original image dimensions and type list($originalWidth, $originalHeight, $imageType) = getimagesize($sourcePath); // Calculate aspect ratio $aspectRatio = $originalWidth / $originalHeight; if ($crop) { // Adjust dimensions for cropping $targetAspectRatio = $newWidth / $newHeight; if ($aspectRatio > $targetAspectRatio) { $cropWidth = $originalHeight * $targetAspectRatio; $cropHeight = $originalHeight; $srcX = ($originalWidth - $cropWidth) / 2; $srcY = 0; } else { $cropWidth = $originalWidth; $cropHeight = $originalWidth / $targetAspectRatio; $srcX = 0; $srcY = ($originalHeight - $cropHeight) / 2; } } else { // Maintain aspect ratio if ($newWidth / $newHeight > $aspectRatio) { $newWidth = $newHeight * $aspectRatio; } else { $newHeight = $newWidth / $aspectRatio; } $cropWidth = $originalWidth; $cropHeight = $originalHeight; $srcX = $srcY = 0; } // Create a new true color image $newImage = imagecreatetruecolor($newWidth, $newHeight); // Load the source image switch ($imageType) { case IMAGETYPE_JPEG: $sourceImage = imagecreatefromjpeg($sourcePath); break; case IMAGETYPE_PNG: $sourceImage = imagecreatefrompng($sourcePath); break; case IMAGETYPE_GIF: $sourceImage = imagecreatefromgif($sourcePath); break; default: die('Unsupported image type'); } // Resize and crop the image imagecopyresampled($newImage, $sourceImage, 0, 0, $srcX, $srcY, $newWidth, $newHeight, $cropWidth, $cropHeight); // Save the resized image switch ($imageType) { case IMAGETYPE_JPEG: imagejpeg($newImage, $destinationPath, 90); // Quality: 90 break; case IMAGETYPE_PNG: imagepng($newImage, $destinationPath, 9); // Compression: 9 break; case IMAGETYPE_GIF: imagegif($newImage, $destinationPath); break; } // Free memory imagedestroy($newImage); imagedestroy($sourceImage); return true; } public function uploader($fieldname='') { $countfiles = count($_FILES[$fieldname]['name']); $totalFileUploaded = 0; for($i=0;$i<$countfiles;$i++){ $target_dir = JPATH_SITE."/images/courses/"; $name=$_FILES[$fieldname]["name"][$i]; $tmp_name=$_FILES[$fieldname]["tmp_name"][$i]; $target_file = $target_dir . basename($name); $uploadOk = 1; $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); // echo $_FILES[$fieldname]["tmp_name"]." : ". $target_file;exit; // Check if image file is a actual image or fake image // if(isset($_POST["submit"])) { $check = getimagesize($tmp_name); if($check !== false) { echo "File is an image - " . $check["mime"] . "."; $uploadOk = 1; } else { echo "File is not an image."; $uploadOk = 0; } // } // Check if file already exists if (file_exists($target_file)) { // echo "Sorry, file already exists."; // $uploadOk = 0; } // Check file size if ($_FILES[$fieldname]["size"] > 500000) { // echo "Sorry, your file is too large."; // $uploadOk = 0; } // Allow certain file formats if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "gif" ) { echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed."; $uploadOk = 0; } // Check if $uploadOk is set to 0 by an error if ($uploadOk == 0) { echo "Sorry, your file was not uploaded."; // return $uploadOk; // if everything is ok, try to upload file } else { if (move_uploaded_file($tmp_name, $target_file)) { "The file ". htmlspecialchars( basename( $name)). " has been uploaded."; // return true; $uploadOk=1; } else { echo "Sorry, there was an error uploading your file."; // return false; } } if($uploadOk) // Resize { $source= $target_file; $destination= $target_file; // $webp_filename= 'webp_filename.webp'; $width=1000; $height=700; /* PARAMS */ $param_comp='com_lmstarbiats'; $param_filesize=200; $param_quality=70; // 1. Certain component if(isset($_GET['option'])&&$_GET['option']==$param_comp) { //echo '<pre>';print_r(filesize($source));exit; echo '<pre>';print_r(getimagesize($source));exit; } // 2. 200kb or more if( (filesize($source)/1024)>=$param_filesize) { // OPTIMIZE only // Compress at 70% quality $source_width=getimagesize($source)[0]; $source_height=getimagesize($source)[1]; $this->optimizeImage($source, $destination, $source_width, $source_height, $param_quality); } // RESIZE //$this->resize_image($source, $destination, $width, $height, true); // } // Ends : if($uploadOk) // Resize } // Ends : for($i=0;$i<$countfiles;$i++) return $uploadOk; }
// ajax call to remove image file public function removeimage() { $input=Factory::getApplication()->input; $filename=$input->get('filename', "", 'STRING'); $result=File::delete(JPATH_SITE."/images/courses/".$filename); return $result; }

Code explained : In our controller we added three functions for optimzing, resizing, uploading and removing image. If you need to upload other file types, adjust according to your needs, and skip optimizing and resizing functions.

And finally added following lines inside your controller save function:

/* MULTIPLE UPLOADER sTARTs */

// CASE : Get all files name, no matter if there are already files or NOT
$downloadable_file=$input->get('downloadable_file', "", 'STRING');
$downloadable=(is_array($downloadable_file))?implode("#", $downloadable_file):$downloadable_file;
$countfiles= count($_FILES["downloadable"]['name']);

if($countfiles>=1&&$_FILES["downloadable"]['name'][0]!="") {
// echo 'yes';print_r($_FILES["ax_uploaded_files"]['name']);
	$uploadOk=$this->uploader("downloadable");
	if(!$uploadOk) return false;

	$post['downloadable']='';

	for($i=0;$i<$countfiles;$i++){	
		
		$name=$_FILES["downloadable"]["name"][$i];
		$post['downloadable'].="#".$name;
	}
	$post['downloadable']=substr($post['downloadable'],1);

	// CASE embed : already files uploaded, then embed this name at the End
	if($downloadable!="") { 
		$post['downloadable']=$downloadable."#".$post['downloadable'];
	}
} else {
	// CASE New : New entry
	$post['downloadable']=$downloadable;
//	echo 'NO'; exit;
}

/* MULTIPLE UPLOADER Ends */

Hope this helped.


Still need help! no problem, feel free to contact us Today


 Abdul Waheed : (Hire Joomla & PHP Pakistani Freelancer)