Laravel Livewire Course - File Upload, Component and Blade

Video thumbnail

File upload is simply the term used for transferring or uploading a file from a local device, such as a PC or smartphone, to a server; in the case of Laravel, this would be the Laravel project. This is always a very necessary feature for performing all kinds of operations, such as uploading an avatar or user image, or for uploading personal files like Excel sheets, PDFs, or others...

Previously, we saw how we can implement a Datatable with Livewire.

Since this is a routine process, we're going to talk about how we can upload files or images, specifically the user's avatar, using Laravel Livewire; for this, we need to keep a few important points in mind before getting into the details of the development we're going to carry out.

Disk Configuration for Storage

We are going to implement the optional upload; in this case, we're going to want to use Laravel's local disk, and we want it to register the images in the public folder; so, let's create a new disk:

config/filesystems.php

'public_upload' => [
    'driver' => 'local',
    'root' => public_path()
],
  • driver is local.
  • root is public_path(), which refers to the public folder, accessible via the browser.

Image Upload Process

In a Livewire component, we are going to perform the upload of files like images or specifically the avatar. We have to use "multiple inheritance," in other words, employing a trait that Livewire provides in our component:

app/Livewire/Dashboard/Category/Save.php

use Livewire\WithFileUploads;
class Save extends Component
{
    use WithFileUploads;
***

With it, we create the property for the image and indicate the image validations; the typical ones: it must be an image type, a maximum size, and that it is optional:

app/Livewire/Dashboard/Category/Save.php

public $title;
public $text;
public $image;
public $category;

protected $rules = [
     'title' => "required|min:2|max:255",
     'text' => "nullable",
     'image' => "nullable|image|max:1024",
];
***

In the submit() method, we check if the user selects an image (that's what the conditional is for):

  1. We give the image a name.
  2. We move the image to the disk we created earlier.
  3. We update the database.

Validation Rules

Now, since we are going to work with forms, we have to indicate the data we are going to use, and with that, their validation rules:

   protected $rules = [
       'title' => 'required|min:6',
       'text' => 'required|min:6'
   ];

Nothing strange at this point, normal validations on text fields, and the interesting part would be the called avatar, which, as you can suppose and analyze the validations, we are going to use the same ones to validate the image upload, that is, the user's avatar; in this field, we define the typical: that it is not null, that it is an image, and a maximum size.

app/Livewire/Dashboard/Category/Save.php

function submit()
{
    $this->validate();

    if ($this->category) {
        $this->category->update(
            [
                'title' => $this->title,
                'text' => $this->text,
                'slug' => str($this->title)->slug(),
            ]
        );
        $this->dispatch('updated');
    } else {
        $this->category = Category::create(
            [
                'title' => $this->title,
                'text' => $this->text,
                'slug' => str($this->title)->slug(),
            ]
        );
        $this->dispatch('created');
    }

    // upload
    if($this->image){
        $imageName = $this->category->slug . '.'.$this->image->getClientOriginalExtension();
        $this->image->storeAs('images/category',$imageName,'public_upload');

        $this->category->update([
            'image' => $imageName
        ]);
    }
}

To easily reference the image, we are going to create a method that returns the absolute path to the image:

App/Models

use Illuminate\Support\Facades\URL;
***
class Category extends Model
{
    ***
    public function getImageUrl()
    {
        return URL::asset("images/category/".$this->image);
    }
}

View (client)

In the view, we use a file type field:

<input
        type="file"
        id="image"
        wire:model="image"
        class="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none"
    />
@error('image') {{ $message }} @enderror

If there is any issue with the image, the error will be shown.

Initially, an error may appear because the trait is missing. We import it and use it in the class:

use Livewire\WithFileUploads;

We reload the page and select the image. Now it works correctly.

If the validation is misspelled (for example, 'images' in plural), Livewire throws an error. It must be 'image' in singular. After correcting it, the upload is carried out correctly, and we can see the image in the corresponding folder (public/images/category) with the automatically generated name.

Initially, an error may appear because the trait is missing. We import it and use it in the class:

And in the view, we place the field for the image at the end of everything:

***
<form wire:submit.prevent="save">
    <label for="image" class="block font-medium text-sm text-gray-700">
        Imagen
    </label>

    <input
        type="file"
        id="image"
        wire:model="image"
        class="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none"
    />

    @error('image')
        <p class="text-sm text-red-600 mt-2">{{ $message }}</p>
    @enderror

    <button type="submit" class="mt-4 inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
        Enviar
    </button>
</form>

And a conditional that asks if we have a registered image for the category, then we display it:

resources/views/livewire/dashboard/category/save.blade.php

@if ($category && $category->image)
    <img class="w-40 my-3" src="{{ $category->getImageUrl() }}" />
@endif

Drag and Drop with basic Laravel or Livewire

Video thumbnail

One of the great modern features — which, although no longer so new, is still very useful — is Drag and Drop. This functionality, although simple for the user to use, often causes headaches when implementing it, as it is generally not so simple to program.

Drag and Drop is a user interface technique that allows users to select an element — such as an image, document, video, etc. — and move it to a designated area on the screen using the cursor, and then release it.

This mechanism is very common for uploading documents, instead of the classic file type field. However, it can also be applied to many other implementations, such as organizing content, solving captchas, or in other types of interactive developments.

Implementing Drag and Drop in Laravel (Livewire)

As we mentioned before, this is not a feature that is very simple to implement, since it requires developing and implementing multiple functions, but luckily in Laravel we have a package to use. Its use is extremely simple and since it is PHP code and with Blade, we can use it either in basic Laravel, that is, without any additions, or in Livewire.

Luckily, there is a very interesting package that allows us to perform this functionality in a very simple way in both basic Laravel and Laravel Livewire, and it is the following package:

https://github.com/asantibanez/laravel-blade-sortable

Its installation is typical, a composer command:

composer require asantibanez/laravel-blade-sortable

And with this, we are ready to use it.

Defining the draggable elements (the items):

 <x-laravel-blade-sortable::sortable-item>

And the container's:

 <x-laravel-blade-sortable::sortable>

We have two tags that we can use, the container one, and the one for our draggable elements. For this package, the container one already includes our draggable elements.

For Drag and Drop to serve a purpose, we have to assign an identifier for when the Drag occurs to be able to trigger a function or callback:

<x-laravel-blade-sortable::sortable-item sort-key="{{ $c->id }}">

And then, the callback function:

<x-laravel-blade-sortable::sortable wire:onSortOrderChange="handleSortOrderChange">

Drag and Drop with basic Laravel or Livewire

I show you a slightly more complete example, in which we can see a real-world case:

<x-laravel-blade-sortable::sortable name="class-{{ $tutorialSection->id }}" wire:onSortOrderChange="handleSortOrderChange">
  @foreach ($tutorialSection->classes()->orderBy('orden')->get() as $k => $c)
     <x-laravel-blade-sortable::sortable-item sort-key="{{ $c->id }}">
        <div class="border m-2 p-4 bg-gray-100">
         <details>
           <summary>{{ $c->title }}</summary>
            @livewire('dashboard.tutorials.tutorial-section-class-save', ['tutorial' => $tutorial, 'tutorialSection' => $tutorialSection, 'tutorialSectionClass' => $c,'nrm'=> $c->orden ], key($c->id))
         </details>
       </div>
     </x-laravel-blade-sortable::sortable-item>
  @endforeach
 </x-laravel-blade-sortable::sortable>

And the callback function:

public function handleSortOrderChange($sortOrder, $previousSortOrder, $name, $from, $to)
   {
       if($name == "sections"){
           
           foreach ($sortOrder as $orden => $id) {
               //dd(TutorialSection::where('id',$id));
               TutorialSection::where('id',$id)->update(['orden' => $orden]);
           }
           $this->sections = $this->tutorial->sections()->orderBy('orden')->get();
       }
   }

The callback receives some parameters, such as the name you give to the draggable container, and the ids or keys you define for your draggable elements; I recommend that you use Laravel's debug function (dd) so that you know the values you are receiving.

The next step is to learn Livewire's JavaScript.

I agree to receive announcements of interest about this Blog.

We will see the implementations for loading files at the component level in Laravel Livewire and Blade, and also using drag and drop.

| 👤 Andrés Cruz

🇪🇸 En español