Laravel Livewire | Multiple Files Upload with Validation

In this tutorial, You’ll learn to Laravel Livewire uploading single and multiple Files Upload with Validation for managing dynamically changing user interfaces.

Under this tutorial, you’ll especially learn below of livewire features:

  • Interaction and Updation of DOM changes with component properties
  • Emitting events on user actions
  • Previewing Image Files before upload
  • Validation of files before uploading or saving into the database
  • Using Javascript methods of livewire such as window.livewire.on
  • Decoding base64encoded image file and uploading it using Laravel Methods in Component

Creating FileUpload Component

Below command will create a component at app/Http/Livewire/Files/FileUpload.php.

php artisan make:livewire files.file-upload --inline

Note

The command-line argument –inline will only create a component class and not view for the component.

However, if you want to get view file then remove –inline argument from the above command.

Create a blade file for FileUpload component at resources/views/livewire/files/file-upload.blade.php and add the view path into component render() method.

public function render()
{
    return view('livewire.files.file-upload');
}

By the way, no need to pass parameters has argument to view() method.

Create view file base.blade.php for display

If you have ever used livewire package than in addition to that a template file must be created were livewire components can be included.
In brief, create file at path resources/views/livewire/files/base.blade.php.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laravel LiveWire Multiple File Validation and Uploads</title>
    @livewireStyles
    @livewireScripts

    <link rel="stylesheet" href="{{ url('assets/css/bootstrap.min.css') }}">

    <style>
        .error{
            color: red;
        }
    </style>
</head>
<body>

    @livewire('files.file-upload')

    <!--  {# livewire('Files.FileUpload') #} this does not work -->
    
    <script src="{{ url('assets/js/jquery.min.js') }}"></script>
    <script src="{{ url('assets/js/popper.min.js') }}"></script>
    <script src="{{ url('assets/js/bootstrap.min.js') }}"></script>
</body>
</html>

The directive to include livewire components inside views is @livewire(‘component-name’) and in this case the component is files.file-upload.

Web Routes

Specify at which URL you would like to render the base view. In this case, it is www.example.com/files.

// Livewire File Uploads
Route::get('files', function(){
    return view('livewire.files.base', []);
});

However, you can pass the controller method as callbacks. For instance, if you are working on a large project in that way it is more manageable.

Snippet for FileUpload Component

<?php

namespace App\Http\Livewire\Files;

use Exception;
use Livewire\Component;
use Log;
use File;
use Illuminate\Support\Str;
use Storage;
use Illuminate\Support\Facades\Validator;

class FileUpload extends Component
{

    public $image;

    public $files = [];

    public $listeners = [
        "single_file_uploaded" => 'singleFileUploaded',
        "add_file" => 'addFile',
        "clear_files" => 'clearFiles',
        "clear_file" => 'clearFile',
    ];

    public $validation_errors = [];

    public function singleFileUploaded($file){

        try {
            if($this->getFileInfo($file)["file_type"] == "image"){
                $this->image = $file;
            }else{
                session()->flash("error", "Uploaded file must be an image");
            }
        } catch (Exception $ex) {
            
        }
    }

    public function addFile($file){
        try {
            if($this->getFileInfo($file)["file_type"] == "image"){
                array_push($this->files, $file);
            }else{
                session()->flash("error", "Uploaded file must be an image");
            }
        } catch (Exception $ex) {
            
        }
    }

    public function clearFiles(){
        $this->files = [];
    }

    public function clearFile($index){
        unset($this->files[$index]);
    }

    public function getFileInfo($file){
        $info = [
            "decoded_file" => NULL,
            "file_meta" => NULL,
            "file_mime_type" => NULL,
            "file_type" => NULL,
            "file_extension" => NULL,
        ];
        try{
            $info['decoded_file'] = base64_decode(substr($file, strpos($file, ',') + 1));
            $info['file_meta'] = explode(';', $file)[0];
            $info['file_mime_type'] = explode(':', $info['file_meta'])[1];
            $info['file_type'] = explode('/', $info['file_mime_type'])[0];
            $info['file_extension'] = explode('/', $info['file_mime_type'])[1];
        }catch(Exception $ex){

        }

        return $info;
    }

    public function uploadFiles(){
        try {

            $rules=[
                'image' => 'required',
                'files' => 'required',
            ];
    
            $messages = [
                "image.required" => "Image must be selected.",
                "files.required" => "Choose atleast one file.",
            ];

            $validator = Validator::make([
                "image" => $this->image,
                "files" => $this->files,
            ],$rules, $messages);

            $validator->after(function ($validator) {

                if($this->getFileInfo($validator->getData()["image"])["file_type"] != "image"){
                    $validator->errors()->add('image', 'Must be an image');   
                }

            });
            
            if($validator->fails()){
                return $this->validation_errors = $validator->errors()->toArray();
            }else{

                // Single File Upload
                $file_data = $this->getFileInfo($this->image);
                $file_name = Str::random(10).'.'.$file_data['file_extension'];
                $result = Storage::disk('public')->put($file_name, $file_data['decoded_file']);
                
                //Multiple Files Upload
                foreach($this->files as $k => $v){
                    $file_data = $this->getFileInfo($v);
                    $file_name = Str::random(10).'.'.$file_data['file_extension'];
                    $result = Storage::disk('public')->put($file_name, $file_data['decoded_file']);
                }
                
            }

            session()->flash("success", "Files uploaded successfully.");
            $this->image = NULL;
            $this->files = [];

        } catch (Exception $ex) {
            session()->flash("error", "Error uploading files. Please try again.");
        }
    }
    
    public function render()
    {
        return view('livewire.files.file-upload');
    }
}

In brief, FileUpload component contains properties such as $image, $files, $listeners and $validation_errors and methods such as singleFileUploaded(), addFile(), clearFiles(), clearFile, getFileInfo and uploadFiles.

View File Snippet : file-upload.blade.php

<div class="container" >

    <style>

        body{
            background: #eee;
            margin: 0;
            padding: 0;
        }

        .wrapper{
            width: 60%;
            margin: 15px auto;
            background: #fff;
        }

        .header-container{
            background: #eee;
            border:2px solid #888;
        }

        .row{
            margin-left: 0;
            margin-right: 0;
        }

        .form-container{
            padding: 15px;
        }
    </style>

    <div class="wrapper">

        <form wire:submit.prevent="uploadFiles" method="post" enctype="multipart/form-data" >

            <div class="row header-container">
                <div class="col-md-12">
                    <h2 class="text-center" >Laravel LiveWire Multiple File Validation and Uploads</h2>
                </div>
            </div>

            <div class="form-container">
                @if(session()->has('error'))
                    <div>
                        <div class="alert alert-danger alert-dismissible fade show">
                            <button type="button" class="close" data-dismiss="alert">×</button>
                            {{ session('error') }}
                        </div>
                    </div>
                @endif

                @if(session()->has('success'))
                    <div>
                        <div class="alert alert-success alert-dismissible fade show">
                            <button type="button" class="close" data-dismiss="alert">×</button>
                            {{ session('success') }}
                        </div>
                    </div>
                @endif

                <div class="row">
                    <div class="col-md-6">
                        <label for="">Single Image Upload</label>
                        <input type="file" class="form-control" wire:change="$emit('single_file_choosed')" ><br>
                        <span>Only image files are allowed to upload</span>
                        @if(!empty($validation_errors["image"]))
                            @foreach($validation_errors["image"] as $k => $v)
                                <label for="" class="error" >{{ $v }}</label>
                            @endforeach
                        @endif
                    </div>

                    <div class="col-md-12">
                        @if($image)
                            <br>
                            <img src="{{ $image }}" alt="" style="height: 150px;" class="img-thumbnail" >
                        @endif
                    </div>
                </div>
                <br>
                <div class="row">
                    <div class="col-md-6">
                        <label for="">Multiple Image Uploads</label>
                        <input type="file"  class="form-control"  wire:change="$emit('multiple_file_choosed')" multiple> 
                        @if(!empty($files)) <button type="button" wire:click="$emit('confirm_remove_files')"  class="btn btn-danger" >Clear Files</button> @endif <br>
                        <span>You can upload multiple image files here</span>
                        @if(!empty($validation_errors["files"]))
                            @foreach($validation_errors["files"] as $k => $v)
                                <label for="" class="error" >{{ $v }}</label>
                            @endforeach
                        @endif
                    </div>

                    <div class="col-md-12">
                        @if(!empty($files))
                            <div class="row">
                                @foreach($files as $k => $v)
                                    <div class="col-md-3">
                                        <img src="{{ $v }}" alt="" style="height: 100px;object-fit:contain;" class="img-thumbnail" >
                                        <br>
                                        <button type="button" wire:click="$emit('confirm_remove_file', {{ $k }})"  class="btn btn-danger btn-sm" >Clear Files</button>
                                    </div>
                                @endforeach
                            </div>
                            <br>
                        @endif
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-3">
                        <br>
                        <input type="submit" class="btn btn-success" value="Upload Now">
                    </div>
                </div>
            </div>
        </form>

    </div>


    <script>
        window.livewire.on('single_file_choosed', function(){
            try {
                let file = event.target.files[0];
                if(file){
                    let reader = new FileReader();

                    reader.onloadend = () => {
                        window.livewire.emit('single_file_uploaded', reader.result);
                    }
                    reader.readAsDataURL(file);
                }
            } catch (error) {
                console.log(error);
            }

        });

        window.livewire.on('multiple_file_choosed', function(){
            try {
                let files = event.target.files;
                let read_files = [];
                if(files.length>0){
                    for(let index in files){
                        if(typeof files[index] == "object"){
                            let reader = new FileReader();
                            reader.onloadend = () => {
                                window.livewire.emit('add_file', reader.result);
                            }
                            reader.readAsDataURL(files[index]);
                        }
                    }
                }
            } catch (error) {
                console.log(error);
            }

        });

        window.livewire.on('confirm_remove_files', function(){
            let cfn = confirm("Confirm to remove all files");
            if(cfn){
                return  window.livewire.emit('clear_files');
            }
            return false;
        });


        window.livewire.on('confirm_remove_file', function(index){
            let cfn = confirm("Confirm to remove this file");
            if(cfn){
                return  window.livewire.emit('clear_file', index);
            }
            return false;
        });
    </script>
</div>

In short, this template file contains HTML elements with javascript.

Output Preview

Conclusion

In conclusion, this is the end of Laravel Livewire | Multiple Files Upload with Validation tutorial. For suggestion comment below and motivate us by sharing this post.

For documentation visit livewire docs.

Related Posts

Summary
Review Date
Reviewed Item
Laravel Livewire | Multiple Files Upload with Validation
Author Rating
51star
1star1star1star1star
Software Name
Laravel Livewire Package
Software Name
Linux, Windows, Mac OS
Software Category
Web Development