Upload o carga de archivos en Laravel Livewire

Video thumbnail

La carga de archivos no es más que el termino empleado para transferir o subir un archivo desde un dispositivo local como una PC o teléfono inteligente a un servidor; en el caso de Laravel, sería el proyecto en Laravel; esta siempre es una características muy necesaria para realizar todo tipo de operaciones como subir un avatar o imagen del usuario, para subir archivos personales como excels, pdfs u otros…

Anteriormente, vimos como podemos implementar un Datatable con Livewire.

Ya que este es un proceso rutinario, vamos a hablar de cómo podemos cargar archivos o imágenes, específicamente el avatar del usuario empleando Laravel Livewire; para esto tenemos que tener presentes algunos puntos importantes antes de entrar en detalle sobre el desarrollo que vamos a llevar a cabo.

Configuración del disco para almacenamiento

Vamos a implementar el upload opcional; en este caso, vamos a querer usar el disco local de Laravel, queremos que registre las imágenes en la carpeta public; así que, vamos a crear un nuevo disco:

config/filesystems.php

'public_upload' => [
    'driver' => 'local',
    'root' => public_path()
],
  • driver es local.
  • root es public_path(), que hace referencia a la carpeta public, accesible mediante el navegador.

Proceso de carga de la imagen (upload)

En un componente de Livewire, vamos a realizar el upload o carga de archivos como imágenes o específicamente el avatar, tenemos que emplear la “herencia múltiple” en otras palabras, empleando un trait que nos provee Livewire en nuestro componente:

app/Livewire/Dashboard/Category/Save.php

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

Con ella, creamos la propiedad para la imagen e indicamos las validaciones para la imagen; lo típico, que sea de tipo imagen, un tamaño máximo y que es opcional:

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",
];
***

En el método de submit(), verificamos si el usuario seleccione una imagen (para eso el condicional):

  1. Damos un nombre a la imagen.
  2. Movemos la imagen al disco que creamos anteriormente.
  3. Actualizamos en la base de datos.

Reglas de validación

Ahora, cómo vamos a trabajar con formularios, tenemos que indicar los datos que vamos emplear, y con esto sus reglas de validación:

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

Nada raro en este punto, validaciones normales sobre campos de textos y lo interesante sería el llamado avatar, que como puedes suponer y analizar las validaciones vamos a emplear las mismas para validar la carga de imágenes, es decir el avatar del usuario; en este campo, definimos lo típico, que no sea nulo, que sea una imagen y un tamaño máximo.

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
        ]);
    }
}

Para poder referenciar fácilmente la imagen, vamos a crear un método que nos devuelve la ruta absoluta a la imagen:

App/Models

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

Vista (cliente)

En la vista, usamos un campo de tipo file:

<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

Si ocurre algún problema con la imagen, se mostrará el error.

Al principio, puede aparecer un error porque falta el trait. Lo importamos y usamos en la clase:

use Livewire\WithFileUploads;

Recargamos la página y seleccionamos la imagen. Ahora funciona correctamente.

Si se escribe mal la validación (por ejemplo, images en plural), Livewire arroja un error. Debe ser image en singular. Tras corregirlo, la carga se realiza correctamente y podemos ver la imagen en la carpeta correspondiente (public/images/category) con el nombre generado automáticamente.

Al principio, puede aparecer un error porque falta el trait. Lo importamos y usamos en la clase:

Y en la vista, colocamos al final de todo, el campo para la imagen:

***
<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>

Y un condicional que pregunta si tenemos una imagen registrada para la categoría, entonces la mostramos:

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 con Laravel básico o Livewire

Video thumbnail

Una de las grandes características modernas —que, aunque ya no es tan nueva, sigue siendo muy útil— es el Drag and Drop. Esta funcionalidad, aunque sencilla de usar para el usuario, suele dar dolores de cabeza a la hora de implementarla, ya que generalmente no es tan simple de programar.

El Drag and Drop, o arrastrar y soltar en español, es una técnica de interfaz de usuario que permite a los usuarios seleccionar un elemento —como una imagen, documento, video, etc.— y moverlo a un área destinada en la pantalla mediante el cursor, para luego soltarlo.

Este mecanismo es muy común para subir documentos, en lugar del clásico campo de tipo archivo. Sin embargo, también puede aplicarse a muchas otras implementaciones, como organizar contenido, resolver captchas, o en otros tipos de desarrollos interactivos.

Implementar el Drag and Drop en Laravel (Livewire)

Como mencionamos antes, esta no es una característica que sea muy sencilla de implementar, ya que, requiere desarrollar e implementar múltiples funciones, pero, por suerte en Laravel tenemos un paquete que usar, su uso es extremadamente sencillo y al ser código PHP y con Blade lo podemos usar ya sea en Lavarel base, es decir sin ningún agregado o en Livewire.

Por suerte, hay un paquete muy interesante que nos permite realizar esta funcionalidad de una manera muy sencilla tanto en Laravel básico como en Laravel livewire y viene siendo el siguiente paquete:

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

Su instalación es lo típico, un comando de composer:

composer require asantibanez/laravel-blade-sortable

Y ya con esto estamos listos para usarla.

Definir los elementos arrastrables (los items):

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

Y la del contenedor:

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

Tenemos dos etiquetas que podemos emplear, la del contenedor, y la de nuestros elementos arrastrables, para este paquete la del contenedor contiene de una nuestros elementos arrastrables.

Para que el Drag and Drop sirva de algo, le tenemos que asignar un identificador para cuando ocurra el Drag poder disparar alguna función o callback:

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

Y luego, la función de callback:

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

Drag and Drop con Laravel basico o Livewire

Te muestro un ejemplo un poco mas completo, en la cual podemos ver un caso del mundo real:

<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>

Y la función callback:

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();
       }
   }

El callback recibe algunos parámetros, como el nombre que le coloques al contenedor arrastrable, y los ids o keys que definas para tus elementos arrastrables; te recomiendo que uses la función de debug de Laravel (dd) para que conozcas los valores que vas recibiendo.

El siguiente paso es aprender el JavaScript de Livewire.

Acepto recibir anuncios de interes sobre este Blog.

Veremos las implementaciones para realizar la carga de archivos a nivel de los componentes en Laravel Livewire y en blade y también mediante el Drag and drop.

| 👤 Andrés Cruz

🇺🇸 In english