Diferencia entre revisiones de «PHP Livewire»
| (No se muestran 61 ediciones intermedias del mismo usuario) | |||
| Línea 17: | Línea 17: | ||
* No directorio raíz do proxecto Laravel escribe: '''composer require livewire/livewire''' | * No directorio raíz do proxecto Laravel escribe: '''composer require livewire/livewire''' | ||
| + | : '''ATENCIÓN:''' A día de hoxe (2026) saiu a versión 4.0 de Livewire. No exame imos empregar a 4.0. | ||
| + | : : Polo tanto empregar esta orden: <u>composer require livewire/livewire "^4.0"</u> para descargar a versión 4.0 (non poñer sudo) | ||
| − | : Unha vez instalado en Laravel podedes consultar a versión instalada buscando a entrada livewire/livewire no arquivo ''' | + | : Unha vez instalado en Laravel podedes consultar a versión instalada buscando a entrada livewire/livewire no arquivo '''composer.json''' no raíz do proxecto. |
| − | : Polo de agora | + | : Polo de agora coa versión 13 de Laravel imos empregar a versión 4.0 de Livewire. |
| Línea 26: | Línea 28: | ||
==Validación de campos== | ==Validación de campos== | ||
| + | |||
| + | * Creamos en mysql (se non o tema xa de exercicios anteriores) unha base de datos Mysql de nome '''perfumes'''. | ||
| + | : Lembrar que o usuario configurado en Laravel para acceder á base de datos debería ter os permisos mínimos para que a aplicación funcione. | ||
* Imos crear a táboa marcas e imos crear unha relación entre perfumes e marcas. | * Imos crear a táboa marcas e imos crear unha relación entre perfumes e marcas. | ||
| Línea 33: | Línea 38: | ||
CREATE TABLE perfumes.marcas ( | CREATE TABLE perfumes.marcas ( | ||
| − | + | `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, | |
| − | `id` int NOT NULL, | ||
| − | |||
`descripcion` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL | `descripcion` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL | ||
| + | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; | ||
| − | |||
| − | INSERT INTO | + | INSERT INTO perfumes.marcas (`id`, `descripcion`) VALUES |
(1, 'Descripción actualizada da marca con id=1'), | (1, 'Descripción actualizada da marca con id=1'), | ||
(2, 'ADIDAS'), | (2, 'ADIDAS'), | ||
| Línea 52: | Línea 55: | ||
: '''Táboa perfumes''' | : '''Táboa perfumes''' | ||
::<syntaxhighlight lang="java" enclose="div" highlight="" > | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| − | CREATE TABLE perfumes.perfumes ( | + | CREATE TABLE `perfumes.perfumes` ( |
| − | `id` int NOT NULL, | + | `id` int NOT NULL AUTO_INCREMENT, |
`descripcion` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL, | `descripcion` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL, | ||
`prezo` decimal(8,2) NOT NULL, | `prezo` decimal(8,2) NOT NULL, | ||
`data_compra` date DEFAULT NULL, | `data_compra` date DEFAULT NULL, | ||
| − | `marca_id` int DEFAULT NULL | + | `marca_id` int DEFAULT NULL, |
| − | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; | + | PRIMARY KEY (`id`), |
| + | KEY `fk_PERFUMES_1_idx` (`id`), | ||
| + | KEY `fk_PERFUMES_MARCA_idx` (`marca_id`), | ||
| + | CONSTRAINT `fk_perfumes_1` FOREIGN KEY (`marca_id`) REFERENCES `marcas` (`id`) ON UPDATE CASCADE | ||
| + | ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; | ||
| − | |||
| − | |||
| − | |||
| − | |||
INSERT INTO perfumes.perfumes (`id`, `descripcion`, `prezo`, `data_compra`, `marca_id`) VALUES | INSERT INTO perfumes.perfumes (`id`, `descripcion`, `prezo`, `data_compra`, `marca_id`) VALUES | ||
| Línea 191: | Línea 194: | ||
@endif | @endif | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| Línea 259: | Línea 253: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | : <u | + | :<u>Nota:</u> Non separamos os campos nun arquivo diferente xa que en livewire as operacións de alta e modificación vanse facer sobre a mesma vista. |
| Línea 279: | Línea 273: | ||
................ | ................ | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | |||
| + | * <u>Nota:</u> Outra forma de añadir o livewire en la vista es: <b>@livewire(Marca:class)</b> o también <b>@livewire('marca')</b> | ||
| + | |||
| + | |||
| Línea 317: | Línea 315: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | :* wire:model="descripcion": O que estamos a dicir con isto é que este input está vinculado á propiedade $descripcion definida no compoñente Livewire. Calquera modificación realizada polo usuario no cliente (na páxina web) actualizará automaticamente o valor da propiedade no servidor grazas á reactividade proporcionada por Livewire. Ademais, se a propiedade no servidor cambia, ese cambio tamén se reflectirá no cliente. | + | :* wire:model="descripcion": O que estamos a dicir con isto é que este input está vinculado á propiedade $descripcion definida no compoñente Livewire. Calquera modificación realizada polo usuario no cliente (na páxina web), <u>cando se faga unha chamada ao controlador livewire</u>, actualizará automaticamente o valor da propiedade no servidor grazas á reactividade proporcionada por Livewire. Ademais, se a propiedade no servidor cambia, ese cambio tamén se reflectirá no cliente. |
:: Se vos dades conta non temos a propiedade value como tiñamos en perfumes. | :: Se vos dades conta non temos a propiedade value como tiñamos en perfumes. | ||
:: Se agora cargades a páxina veredes como o valor inicial se carga no input. | :: Se agora cargades a páxina veredes como o valor inicial se carga no input. | ||
| Línea 382: | Línea 380: | ||
: Con isto <b>wire:submit.prevent="gardarMarca"</b> estamos a decir que cando faga o submit chame de forma asíncrona ao controlador do compoñente livewire ao método gardarMarca. | : Con isto <b>wire:submit.prevent="gardarMarca"</b> estamos a decir que cando faga o submit chame de forma asíncrona ao controlador do compoñente livewire ao método gardarMarca. | ||
| + | : No caso de manexar formularios en livewire, empregar sempre prevent. Poderíamos facelo con <u>wire:submit</u> pero nese caso a páxina enteira se carga como se fora un submit 'tradicional'. | ||
| + | |||
| + | |||
| + | |||
: <u>Nota:</u> Podemos asociar calquera click de botón directamente ao un método no controlador do livewire, da forma: <button wire:click="gardarMarca">+</button> | : <u>Nota:</u> Podemos asociar calquera click de botón directamente ao un método no controlador do livewire, da forma: <button wire:click="gardarMarca">+</button> | ||
| Línea 418: | Línea 420: | ||
* Fixerase que neste caso a validación faise cando o usuario preme o botón de submit. | * Fixerase que neste caso a validación faise cando o usuario preme o botón de submit. | ||
: Se queremos que se faga a validación cando o usuario cambio o contido da caixa de texto temos que: | : Se queremos que se faga a validación cando o usuario cambio o contido da caixa de texto temos que: | ||
| − | :* Empregar o método update($propiedade) que será chamado de forma automáticamente cando o campo teña unha modificación. Dito método fará a validación da propiedade. Temos que chamar ao método '''validatyOnly'''. | + | :* Empregar o método update($propiedade,$valor) que será chamado de forma automáticamente cando o campo teña unha modificación. Dito método fará a validación da propiedade. Temos que chamar ao método '''validatyOnly'''. |
:* Cambiar a propiedade wire:model da caixa de texto por '''wire:model.live''' | :* Cambiar a propiedade wire:model da caixa de texto por '''wire:model.live''' | ||
: Lembrar que estas chamadas fanse ao servidor polo que pode ser que non nos interese para aforrar recursos e comunicacións entre cliente e o servidor. | : Lembrar que estas chamadas fanse ao servidor polo que pode ser que non nos interese para aforrar recursos e comunicacións entre cliente e o servidor. | ||
| Línea 437: | Línea 439: | ||
.............. | .............. | ||
| − | public function updated($propiedade){ | + | public function updated($propiedade,$valor){ |
$this->validateOnly($propiedade); | $this->validateOnly($propiedade); | ||
} | } | ||
| Línea 444: | Línea 446: | ||
| − | : Ao facer isto, cada vez que se preme no | + | : Ao facer isto, cada vez que se preme no input fará a validación. |
: Lembrar que poñer isto non quita que teñades a chamada $this->validate() cando se vaia a dar de alta/modificar os datos | : Lembrar que poñer isto non quita que teñades a chamada $this->validate() cando se vaia a dar de alta/modificar os datos | ||
| Línea 453: | Línea 455: | ||
<br /> | <br /> | ||
| − | ==Operacións de alta-baixa-modificación== | + | ==Operacións de alta-baixa-modificación-listado== |
===Alta=== | ===Alta=== | ||
| − | + | * A alta faise como sempre pero agora está no controlador do compoñente livewire. | |
| − | + | :Hai un pequeno detalle xa que como chamamos Alta ao compoñente Livewire, se empregamos o modelo Marca teríamos o mesmo nome. Por iso empregaremos un alias para o modelo. | |
| − | : '''Arquivo: app/Livewire/Marca.php''' | + | :'''Arquivo: app/Livewire/Marca.php''' |
::<syntaxhighlight lang="java" enclose="div" highlight="1,9-15" > | ::<syntaxhighlight lang="java" enclose="div" highlight="1,9-15" > | ||
use App\Models\Marca as MarcaModel; | use App\Models\Marca as MarcaModel; | ||
| Línea 484: | Línea 486: | ||
| − | : Fixarse que para enviar unha información á vista empregamos session()->flash que vai gardar nunha variable de sesión (message) o texto a amosar. | + | :Fixarse que para enviar unha información á vista empregamos session()->flash que vai gardar nunha variable de sesión (message) o texto a amosar. |
| − | : Dita variable non pode ser empregada pola páxina, soamente polo compoñente livewire xa que non se recarga a páxina enteira. | + | :Dita variable non pode ser empregada pola páxina, soamente polo compoñente livewire xa que non se recarga a páxina enteira. |
| − | : A chamada a reset vai poñer en branco os valor da variable $descripcion do compoñente. Pero como temos un valor por defecto na definición poñerá dito valor. Lembrar que non é obrigatorio e podemos quitalo. | + | :A chamada a reset vai poñer en branco os valor da variable $descripcion do compoñente. Pero como temos un valor por defecto na definición poñerá dito valor. Lembrar que non é obrigatorio e podemos quitalo. |
| − | : Tamén indicar que podemos facer o create da forma: '''MarcaModel::create($this->all())''' | + | :Tamén indicar que podemos facer o create da forma: '''MarcaModel::create($this->all())''' |
| Línea 497: | Línea 499: | ||
* Para amosar as mensaxes de que todo foi correcto, imos crear un arquivo na vista. | * Para amosar as mensaxes de que todo foi correcto, imos crear un arquivo na vista. | ||
| − | : Tamén imos poñer nese arquivo os posibles erros que enviemos dende o controlador. | + | :Tamén imos poñer nese arquivo os posibles erros que enviemos dende o controlador. |
| − | : '''Arquivo app/views/mensaxes/mensaxes.blade.php'''. Lembrar crear o cartafol mensaxes. | + | :'''Arquivo app/views/mensaxes/mensaxes.blade.php'''. Lembrar crear o cartafol mensaxes. |
::<syntaxhighlight lang="java" enclose="div" highlight="" > | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| Línea 511: | Línea 513: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | : Simplemente amosamos o contido da variable de sesión message. | + | :Simplemente amosamos o contido da variable de sesión message. |
* Incluimos dito arquivo na vista do compoñente livewire: | * Incluimos dito arquivo na vista do compoñente livewire: | ||
| − | : '''Arquivo app/views/livewire/marca.blade.php'''. | + | :'''Arquivo app/views/livewire/marca.blade.php'''. |
::<syntaxhighlight lang="java" enclose="div" highlight="" > | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
<div> | <div> | ||
| Línea 537: | Línea 539: | ||
@include('mensaxes.mensaxes') | @include('mensaxes.mensaxes') | ||
</div> | </div> | ||
| + | </syntaxhighlight> | ||
| − | : '''IMPORTANTE:''' O compoñente livewire soamente pode ter un elemento raíz, no exemplo posto <div>. Non podedes poñer o include fora do div. | + | :'''IMPORTANTE:''' O compoñente livewire soamente pode ter un elemento raíz, no exemplo posto <div>. Non podedes poñer o include fora do div. |
| − | |||
| − | |||
* E que pasa se queremos ir a outra pantalla ou que recargue toda a páxina e non soamente o compoñente livewire ? Debemos empregar o método redirectRoute() poñendo o nome da ruta á que queremos ir. | * E que pasa se queremos ir a outra pantalla ou que recargue toda a páxina e non soamente o compoñente livewire ? Debemos empregar o método redirectRoute() poñendo o nome da ruta á que queremos ir. | ||
| − | + | :'''Arquivo: app/Livewire/Marca.php''' | |
::<syntaxhighlight lang="java" enclose="div" highlight="15" > | ::<syntaxhighlight lang="java" enclose="div" highlight="15" > | ||
| Línea 589: | Línea 590: | ||
<livewire:marca modo="actualizar" :id="$marca->id"/> <!-- Se queres enviar unha cadea ou numero poñemos clave="valor". Os dous puntos (:id) é para enviar unha variable de Laravel --> | <livewire:marca modo="actualizar" :id="$marca->id"/> <!-- Se queres enviar unha cadea ou numero poñemos clave="valor". Os dous puntos (:id) é para enviar unha variable de Laravel --> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
@include('mensaxes.alertas_javascript') | @include('mensaxes.alertas_javascript') | ||
| Línea 701: | Línea 691: | ||
} | } | ||
| − | public function updated($propiedade){ | + | public function updated($propiedade,$valor){ |
$this->validateOnly($propiedade); | $this->validateOnly($propiedade); | ||
} | } | ||
| Línea 740: | Línea 730: | ||
| + | |||
| + | * Podedes facer a proba de que funciona poñendo na url: http://localhost:8080/marcas/1/edit (sendo 1 o id dunha marca que teñades engadida) | ||
| + | |||
| + | <br /> | ||
| + | |||
| + | ===Listado=== | ||
| + | |||
| + | * O listado o imos implementar empregando un compoñente livewire. | ||
| + | : Ademáis imos poder empregar un campo de filtro e paxinación sen necesidade de recargar toda a páxina ao empregar o compoñente. | ||
| + | |||
| + | : Executamos a orde dende terminal: '''php artisan make:livewire marca-listado''' | ||
| + | : Creará: | ||
| + | ::Class: app/Livewire/MarcaListado.php | ||
| + | ::View: resources/views/livewire/marca-listado.blade.php | ||
| + | |||
| + | |||
| + | * '''Arquivo app/Livewire/MarcaListado.php''' | ||
| + | : Polo de agora imos amosar o listado de marcas. | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <?php | ||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use App\Models\Marca; | ||
| + | |||
| + | use Illuminate\Support\Facades\Log; | ||
| + | |||
| + | class MarcaListado extends Component | ||
| + | { | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $marcas = Marca::orderBy('descripcion', 'asc')->get(); | ||
| + | |||
| + | return view('livewire.marca-listado', compact('marcas')); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | * '''Arquivo resources/views/livewire/marca-listado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <div> | ||
| + | |||
| + | <table class="table-auto w-full border-collapse border border-gray-300"> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th class="border border-gray-300 p-2">ID</th> | ||
| + | <th class="border border-gray-300 p-2">Descripción</th> | ||
| + | <th class="border border-gray-300 p-2">Acciones</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | <tbody> | ||
| + | @forelse ($marcas as $marca) | ||
| + | <tr wire:key="marca-{{ $marca->id }}"> | ||
| + | <td class="border border-gray-300 p-2">{{ $marca->id }}</td> | ||
| + | <td class="border border-gray-300 p-2">{{ $marca->descripcion }}</td> | ||
| + | <td class="border border-gray-300 p-2"> | ||
| + | <a href="{{ route('marcas.edit',['marca' => $marca->id]) }}" class="bg-blue-500 text-white px-4 py-1 rounded"> | ||
| + | Editar | ||
| + | </a> | ||
| + | <button class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> | ||
| + | </td> | ||
| + | </tr> | ||
| + | @empty | ||
| + | <tr> | ||
| + | <td colspan="3" class="border border-gray-300 p-2 text-center">No se encontraron marcas</td> | ||
| + | </tr> | ||
| + | @endforelse | ||
| + | </tbody> | ||
| + | </table> | ||
| + | |||
| + | @include('mensaxes.mensaxes') | ||
| + | |||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | '''NOTAS:''' | ||
| + | :* Fixarse que cambiamos o botón de modificar por un enlace. Se podería empregar un botón cun formulario ou outra forma de que cargue unha url. | ||
| + | :* Fixarse que no bucle o tr ten un wire:key. Isto é necesario sempre que teñades un bucle e empregedes: | ||
| + | ::* @foreach de elementos dinámicos | ||
| + | ::* Botóns wire:click dentro do loop | ||
| + | ::* Eliminación ou actualización de filas | ||
| + | ::* Inputs editables en lista | ||
| + | ::* Ordenaión ou filtros | ||
| + | |||
| + | |||
| + | |||
| + | * Indicamos no controlador da marca que cargue unha vista cando chamemos a index | ||
| + | : '''Arquivo app/Http/Controllers/MarcaController.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | ............... | ||
| + | public function index() | ||
| + | { | ||
| + | return view('marcas.marcas'); | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | * '''Arquivo resources/views/marcas/marcas.blade.php''' | ||
| + | : Cargamos o livewire que amosa o listado. En principio non necesitamos enviarlle ningún dato xa que é o propio compoñente livewire quen obtén o listado de marcas. | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="9" > | ||
| + | @extends('plantilla'); | ||
| + | |||
| + | @section('pageTitle', 'Listado de marcas') | ||
| + | |||
| + | @section('content') | ||
| + | <h2>Listado de Marcas</h2> | ||
| + | |||
| + | |||
| + | @livewire("marca-listado") | ||
| + | |||
| + | @include('mensaxes.alertas_javascript') | ||
| + | |||
| + | |||
| + | @endsection | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | :Se agora probades a poñer '''http://localhost:8000/marcas''' vos amosara o listado de marcas. | ||
| + | |||
| + | |||
| + | |||
| + | ====Paxinación==== | ||
| + | |||
| + | * Máis información en: [https://laravel.com/docs/10.x/pagination https://laravel.com/docs/10.x/pagination] | ||
| + | * Faise de forma parecido a como se fai en Laravel cando temos unha vista. | ||
| + | : No controlador do compoñente livewire imos indicar que empregaremos paxinación e cando obtemos a lista de marcas faremos o paxinate. | ||
| + | |||
| + | * '''Arquivo app/Livewire/MarcaListado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="5,13,17" > | ||
| + | <?php | ||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use Livewire\WithPagination; | ||
| + | use App\Models\Marca; | ||
| + | |||
| + | use Illuminate\Support\Facades\Log; | ||
| + | |||
| + | |||
| + | class MarcaListado extends Component | ||
| + | { | ||
| + | use WithPagination; | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $marcas = Marca::orderBy('descripcion', 'asc')->simplePaginate(3); | ||
| + | |||
| + | return view('livewire.marca-listado', compact('marcas')); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | :<u>Nota:</u> Poño simplePaginate en vez de Paginate xa que por defecto engade dúas iconas para desprazarse á dereita/esqueda polas páxinas, pero ditas iconas se ven moi grandes, supoño a que por defecto emprega Tailwind CSS. Se consultades o enlace superior indica como empregar Bootstrap. | ||
| + | |||
| + | |||
| + | |||
| + | * Agora imos indicarlle á vista que visualice os enlaces de seguinte e anterior: | ||
| + | * '''Arquivo resources/views/livewire/marca-listado.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="30-32" > | ||
| + | <div> | ||
| + | |||
| + | <table class="table-auto w-full border-collapse border border-gray-300"> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th class="border border-gray-300 p-2">ID</th> | ||
| + | <th class="border border-gray-300 p-2">Descripción</th> | ||
| + | <th class="border border-gray-300 p-2">Acciones</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | <tbody> | ||
| + | @forelse ($marcas as $marca) | ||
| + | <tr wire:key="marca-{{ $marca->id }}"> | ||
| + | <td class="border border-gray-300 p-2">{{ $marca->id }}</td> | ||
| + | <td class="border border-gray-300 p-2">{{ $marca->descripcion }}</td> | ||
| + | <td class="border border-gray-300 p-2"> | ||
| + | <a href="{{ route('marcas.edit',['marca' => $marca->id]) }}" class="bg-blue-500 text-white px-4 py-1 rounded"> | ||
| + | Editar | ||
| + | </a> | ||
| + | <button class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> | ||
| + | </td> | ||
| + | </tr> | ||
| + | @empty | ||
| + | <tr> | ||
| + | <td colspan="3" class="border border-gray-300 p-2 text-center">No se encontraron marcas</td> | ||
| + | </tr> | ||
| + | @endforelse | ||
| + | </tbody> | ||
| + | </table> | ||
| + | <div class="mt-4"> | ||
| + | {{ $marcas->links() }} | ||
| + | </div> | ||
| + | |||
| + | @include('mensaxes.mensaxes') | ||
| + | |||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | : Cada 3 marcas teredes unha páxina. | ||
| + | |||
| + | |||
| + | |||
| + | <br /> | ||
| + | |||
| + | ====Filtro==== | ||
| + | |||
| + | * Para filtrar imos necesitar na vista un campo input no que imos poder poñer unha palabra e amosará as marcas que teñan dita palabra. | ||
| + | : Iso o fará se necesidade de cargar novamente a páxina enteira. | ||
| + | * '''Arquivo resources/views/livewire/marca-listado.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <div> | ||
| + | <input type="text" wire:model.live="filtro" placeholder="Buscar marcas..." class="mb-4 p-2 border rounded"> | ||
| + | |||
| + | <table class="table-auto w-full border-collapse border border-gray-300"> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th class="border border-gray-300 p-2">ID</th> | ||
| + | <th class="border border-gray-300 p-2">Descripción</th> | ||
| + | <th class="border border-gray-300 p-2">Acciones</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | |||
| + | ................ | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : Fixarse que o campo input vai estar ligado a unha propiedade de nome '''filtro''' que se atopará no controlador do livewire. Ao poñer wire:model.'''live''' facemos que cando premamos unha tecla vai actualizar dita propiedade. Se non queremos facelo ao premer na tecla teríamos que ter un botón que ao premer fixera dita o proceso de actualizar a vista. | ||
| + | |||
| + | |||
| + | * '''Arquivo app/Livewire/MarcaListado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="14,15,21" > | ||
| + | <?php | ||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use Livewire\WithPagination; | ||
| + | use App\Models\Marca; | ||
| + | |||
| + | use Illuminate\Support\Facades\Log; | ||
| + | |||
| + | |||
| + | class MarcaListado extends Component | ||
| + | { | ||
| + | use WithPagination; | ||
| + | public $filtro; | ||
| + | protected $queryString = ['filtro']; // Sincronizar búsqueda con la URL | ||
| + | |||
| + | |||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $marcas = Marca::where('descripcion', 'like', '%' . $this->filtro . '%')->orderBy('descripcion', 'asc') | ||
| + | ->simplePaginate(5); | ||
| + | |||
| + | |||
| + | return view('livewire.marca-listado', compact('marcas')); | ||
| + | } | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : <u>Nota:</u> A liña 15 non é obrigatoria. O que fai a propiedade $queryString é actualizar a URL có contido da campo de busca no parámetro filtro. Sería necesario se queremos empregar a url para algún proceso posterior. | ||
| + | |||
| + | <br /> | ||
| + | |||
| + | ====Filtro: Variante con botón==== | ||
| + | |||
| + | * Imos facer unha variante do anterior para que soamente busque cando premamos un botón. | ||
| + | : Para isto temos que cambiar wire.model.live por wire.model e engadir un botón. | ||
| + | * '''Arquivo resources/views/livewire/marca-listado.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="2,3" > | ||
| + | <div> | ||
| + | <input type="text" wire:model="filtro" placeholder="Buscar marcas..." class="mb-4 p-2 border rounded"> | ||
| + | <input type="button" wire:click="$refresh" value="Buscar" /> | ||
| + | ............. | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | :Fixarse que o botón o que fai é chamar a un método 'refresh' que xa ten definido internamento o controlador do livewire. | ||
| + | :O que fai dito método e chamar ao método render do controlador. | ||
| + | : Por outra banda, ao empregar wire:model, o campo non se actualiza no controlador ata que alguén o chame (como fai refresh). | ||
| + | |||
| + | : <u>NOTA:</u> Se queremos chamar a un método do controlador o podemos facer poñendo: wire:click="nomeMetodo" e definindo dito método no controlador. | ||
| + | |||
| + | ===Baixa=== | ||
| + | |||
| + | * A baixa vai consistir en chamar a un método do controlador ao que lle mandaremos o id da marca a dar de baixa. | ||
| + | :Polo tanto definimos o método no controlador. | ||
| + | |||
| + | |||
| + | '''Arquivo app/Livewire/MarcaListado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | ................ | ||
| + | public function eliminarMarca($id){ | ||
| + | $marca = Marca::find($id); // Se poñemos findOrFail a aplicación pararía se non o atopa. | ||
| + | |||
| + | if ($marca) { | ||
| + | $marca->delete(); | ||
| + | session()->flash('message', 'Marca eliminada correctamente.'); | ||
| + | } else { | ||
| + | $this->addError('error', 'Marca non atopada.'); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | :Como vedes é moi sinxelo. Buscamos a marca baseada nese id e se a atopa a borra. Despois xa chama a render automaticamente. Se non atopa envía unha mensaxe de erro. | ||
| + | : Empregamos $this->addError para gardar o erro na variable 'error'. | ||
| + | |||
| + | |||
| + | * '''IMPORTANTE:''' Ao facer que o botón de Baixa se gestione con wire:click, se queremos pedir confirmación do borrado non podemos empregar confirm de javascripr, xa que vai realizar a llamada igual. Debemos empregar '''wire:confirm''' como está indicado no exemplo seguinte. | ||
| + | |||
| + | |||
| + | '''Arquivo resources/views/livewire/marca-listado.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="39-43" > | ||
| + | <div> | ||
| + | <input type="text" wire:model="filtro" placeholder="Buscar marcas..." class="mb-4 p-2 border rounded"> | ||
| + | <input type="button" wire:click="$refresh" value="Buscar" /> | ||
| + | <table class="table-auto w-full border-collapse border border-gray-300"> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th class="border border-gray-300 p-2">ID</th> | ||
| + | <th class="border border-gray-300 p-2">Descripción</th> | ||
| + | <th class="border border-gray-300 p-2">Acciones</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | <tbody> | ||
| + | @forelse ($marcas as $marca) | ||
| + | <tr> | ||
| + | <td class="border border-gray-300 p-2">{{ $marca->id }}</td> | ||
| + | <td class="border border-gray-300 p-2">{{ $marca->descripcion }}</td> | ||
| + | <td class="border border-gray-300 p-2"> | ||
| + | <a href="{{ route('marcas.edit',['marca' => $marca->id]) }}" class="bg-blue-500 text-white px-4 py-1 rounded"> | ||
| + | Editar | ||
| + | </a> | ||
| + | <button wire:click="eliminarMarca({{ $marca->id }})" | ||
| + | wire:confirm="Estás seguro de borrar la marca {{ $marca->descripcion }}? " | ||
| + | class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> | ||
| + | </td> | ||
| + | </tr> | ||
| + | @empty | ||
| + | <tr> | ||
| + | <td colspan="3" class="border border-gray-300 p-2 text-center">No se encontraron marcas</td> | ||
| + | </tr> | ||
| + | @endforelse | ||
| + | </tbody> | ||
| + | </table> | ||
| + | <div class="mt-4"> | ||
| + | {{ $marcas->links() }} | ||
| + | </div> | ||
| + | |||
| + | @include('mensaxes.mensaxes') | ||
| + | |||
| + | @error('error') | ||
| + | <div class="alert alert-danger"> | ||
| + | {{ $message }} | ||
| + | </div> | ||
| + | @enderror | ||
| + | |||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | : Comprobamos se temos un erro na variable 'error' de livewire. | ||
| + | |||
| + | |||
| + | <br /> | ||
| + | |||
| + | ===Axustes finais=== | ||
| + | |||
| + | * Imos modificar a modificación e alta para que vain á pantalla de listado. | ||
| + | :'''Arquivo app/Livewire/Marca.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="58,72" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Illuminate\Support\Facades\Log; | ||
| + | |||
| + | use Livewire\Attributes\Validate; | ||
| + | use App\Models\Marca as MarcaModel; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | |||
| + | class Marca extends Component | ||
| + | { | ||
| + | public $descripcion = "Valor inicial"; | ||
| + | public $id; // A empregamos na operación de modificación/baixa para atopar a marca a modificar ou borrar | ||
| + | public $modo = "engadir"; | ||
| + | |||
| + | protected $rules = [ | ||
| + | 'descripcion' => 'required|string|min:5|max:45', | ||
| + | ]; | ||
| + | |||
| + | protected $messages = [ | ||
| + | 'descripcion.required' => 'La descripción es obligatoria.', | ||
| + | 'descripcion.string' => 'La descripción debe ser una cadena de texto.', | ||
| + | 'descripcion.min' => 'La descripción debe tener al menos 5 caracteres.', | ||
| + | 'descripcion.max' => 'La descripción no puede tener más de 45 caracteres.', | ||
| + | ]; | ||
| + | |||
| + | public function mount($id = null) | ||
| + | { | ||
| + | $this->id = $id; | ||
| + | if($id) { | ||
| + | $marca = MarcaModel::findOrFail($id); | ||
| + | $this->descripcion = $marca->descripcion; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | return view('livewire.marca'); | ||
| + | } | ||
| + | |||
| + | public function gardarMarca() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | |||
| + | Log::info('Pasa a validación'); | ||
| + | |||
| + | $marcaCreada = MarcaModel::create([ | ||
| + | 'descripcion' => $this->descripcion | ||
| + | ]); | ||
| + | |||
| + | $this->id = $marcaCreada->id; | ||
| + | |||
| + | session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); | ||
| + | $this->reset(['descripcion']); | ||
| + | |||
| + | return $this->redirectRoute('marcas.index'); | ||
| + | } | ||
| + | |||
| + | public function actualizarMarca() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | |||
| + | $marca = MarcaModel::findOrFail($this->id); | ||
| + | $marca->update([ | ||
| + | 'descripcion' => $this->descripcion, | ||
| + | ]); | ||
| + | |||
| + | session()->flash('message', 'Marca actualizada correctamente.'); | ||
| + | |||
| + | return $this->redirectRoute('marcas.index'); | ||
| + | |||
| + | } | ||
| + | |||
| + | public function updated($propiedade,$valor){ | ||
| + | $this->validateOnly($propiedade); | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | * Engado a entrada de listar perfumes. | ||
| + | :'''Arquivo resources/plantilla.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="25" > | ||
| + | <!DOCTYPE html> | ||
| + | <html> | ||
| + | <head> | ||
| + | <title>@yield('pageTitle')</title> | ||
| + | |||
| + | <style> | ||
| + | .menu{ | ||
| + | float: left; | ||
| + | width: 30%; | ||
| + | padding-left: 5px; | ||
| + | } | ||
| + | .menu a{ | ||
| + | display: block; | ||
| + | } | ||
| + | .contido{ | ||
| + | float: left; | ||
| + | width: 65%; | ||
| + | } | ||
| + | |||
| + | </style> | ||
| + | </head> | ||
| + | <body> | ||
| + | <div class="menu"> | ||
| + | <a href='{{route('perfumes.index')}}'>Operacións Perfumes</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> | ||
| + | <a href='{{route('marcas.index')}}'>Operacións Marcas</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> | ||
| + | </div> | ||
| + | <div class="contido"> | ||
| + | @yield('content') | ||
| + | </div> | ||
| + | </body> | ||
| + | </html> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | <br /> | ||
| + | ===Aclaracións varias=== | ||
| + | * Neste exercicio empregamos o controlador de Laravel para cargar o mesmo compoñente livewire na alta e modificación empregando un parámetro para informar ao compoñnente en que 'modo' se atopaba. | ||
| + | |||
| + | : <u>Normalmente isto non é necesario.</u> | ||
| + | : Pódese facer unha única vista no controlador de Laravel que cargue o compoñente livewire e xestionar dende dentro do compoñente o listado, alta, baixa e modificación. | ||
| + | : Dentro do compoñente podes definir unha variable pública estado que determine cando a vista ten que visualizar o listado, a alta-modificación. | ||
| + | |||
| + | |||
| + | : Vos queda como exercicio para facer. | ||
| + | : Tedes un exemplo de cómo facelo nos vídeos da plataforma a distancia. | ||
| + | |||
| + | |||
| + | |||
| + | <br /> | ||
| + | |||
| + | ==Exercicio proposto== | ||
| + | |||
| + | * Imos implementar a <u>xestión da perfumes</u> empregando compoñentes Livewire. | ||
| + | : Dexiaremos [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Modelo_-_Vista_-_Controlador#Exercicio_1:_Xesti.C3.B3n_dunha_.C3.BAnica_t.C3.A1boa o feito na UD6] e faremos unha nova versión de xestión dos perfumes (listado, alta, baixa e modificación) empregando compoñentes Livewire. | ||
| + | |||
| + | |||
| + | |||
| + | * Para cargar os compoñentes dentro da páxina master de Laravel crearemos o cartafol resources/views/'''perfumes_b'''/ e o controlador de Laravel chamarase '''PerfumeBController'''. | ||
| + | : As rutas as teremos que definir nos (non podemos empregar resource xa que perfumes están definidas para empregar o controlador de Laravel). | ||
| + | : Empregaremos: | ||
| + | :* perfumesB/ => PerfumeBController@index | ||
| + | |||
| + | |||
| + | * No listado (index) imos a poder: | ||
| + | :* Crear un novo perfume | ||
| + | :* Modificar un perfume | ||
| + | :* Borrar un perfume | ||
| + | |||
| + | : Na creación/modificación do perfume imos engadir un campo novo, que vai a ser un select coa marca á que pertence o perfume. | ||
| + | |||
| + | * Imos empregar dous compoñentes Livewire: | ||
| + | :* O encargado de listar os perfumes. Nome '''perfume-listar''' | ||
| + | :* O encargado de engadir/modificar un perfume. Nome '''perfume''' | ||
| + | |||
| + | : No seguinte punto imos ver como podemos enviar 'mensaxes' de un compoñente a outro. Polo de agora ides amosar os dous compoñentes na mesma páxina e desta forma aparecerá o listado e os campos para dar de alta un novo perfume ao mesmo tempo. Despois solucionaremos o problema de como facer que non apareza ata que se pulse o botón de Novo Perfume e que cambie o contido/acción cando se prema o de modificar perfume. Polo de agora <u>non fagades o código de modificar perfume</u>. Soamente amosar o botón pero que non faga nada. | ||
| + | |||
| + | : É dicir, a páxina '''resources/views/perfumes_b/perfumes.blade.php''' terá este código: | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="9,11" > | ||
| + | @extends('plantilla'); | ||
| + | |||
| + | @section('pageTitle', 'Listado de perfumes con Livewire') | ||
| + | |||
| + | @section('content') | ||
| + | <h2>Listado de Perfumes con Livewire</h2> | ||
| + | |||
| + | |||
| + | @livewire("perfume-listado") | ||
| + | |||
| + | @livewire("perfume") | ||
| + | |||
| + | @include('mensaxes.alertas_javascript') | ||
| + | |||
| + | |||
| + | @endsection | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | : E o compoñente livewire perfume amosará os campos para dar de alta un novo perfume. | ||
| + | [[Image:Ud_livewire_1.png|center|1000px]] | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | * Lembrar, se non o fixéchedes antes, comprobar que a táboa perfumes ten un campo marca_id relacionada coa táboa marcas. | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | CREATE TABLE `perfumes` ( | ||
| + | `id` int NOT NULL AUTO_INCREMENT, | ||
| + | `descripcion` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL, | ||
| + | `prezo` decimal(8,2) NOT NULL, | ||
| + | `data_compra` date DEFAULT NULL, | ||
| + | `marca_id` int DEFAULT NULL, | ||
| + | PRIMARY KEY (`id`), | ||
| + | KEY `fk_PERFUMES_1_idx` (`id`), | ||
| + | KEY `fk_PERFUMES_MARCA_idx` (`marca_id`), | ||
| + | CONSTRAINT `fk_perfumes_1` FOREIGN KEY (`marca_id`) REFERENCES `marcas` (`id`) ON UPDATE CASCADE | ||
| + | ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : E crear a relación en Laravel entre os dous modelos. | ||
| + | <br /> | ||
| + | |||
| + | |||
| + | |||
| + | * Se seguides a solución proposta para Marca, podedes intentar empregar [https://laravel.com/docs/10.x/eloquent#upserts o método Modelo::updateOrCreate] de Laravel que permite crear ou modificar unha fila da táboa. Podedes intentar crear un único método para as dúas operacións e non facer como en Marca, de ter un para a actualización e outro para a inserción. | ||
| + | ===Solución Exercicio proposto=== | ||
| + | |||
| + | '''Arquivo app/Models/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="24,45-47" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Models; | ||
| + | |||
| + | use Illuminate\Database\Eloquent\Model; | ||
| + | use Illuminate\Database\Eloquent\Casts\Attribute; | ||
| + | use Carbon\Carbon; | ||
| + | |||
| + | class Perfume extends Model | ||
| + | { | ||
| + | protected $table = 'perfumes'; // Indicamos a táboa relacionada con este modelo. | ||
| + | protected $primaryKey = 'id'; // Indicamos a clave primaria da táboa | ||
| + | |||
| + | public $timestamps = false; // Por defecto laravel inclúe dous campos (created_at e updated_at) de tipo datetime cando se fai unha operación de inserción e actualización sobre unha fila. Con isto indicamos que a táboa non ten eses campos. | ||
| + | |||
| + | protected $casts = [ // Cando accedamos a unha fila empregando este modelo, se poñemos $fila->data_compra teremos un string. Se poñemos o cast, obteremos unha instancia da [https://carbon.nesbot.com/docs/ clase Carbon] de Laravel que nos permite facer operacións e formatear a data. | ||
| + | 'data_compra' => 'date', // Tamén se pode empregar datetime. Devolve un Carbon | ||
| + | ]; | ||
| + | |||
| + | protected $fillable = [ // Isto xa o veremos. O que indica é que cando facemos unha operación masiva de inserción ou un insert da forma create(datos) soamente deixa enviar en datos (que é un array) as columnas indicadas nesta propiedade (as columnas indicadas son descripcion, prezo e data_compra). Faise por motivos de seguridade. | ||
| + | 'descripcion', | ||
| + | 'prezo', | ||
| + | 'data_compra', | ||
| + | 'marca_id' | ||
| + | ]; | ||
| + | |||
| + | protected function DataCompra(): Attribute | ||
| + | { | ||
| + | return Attribute::make( | ||
| + | get: fn($value) => $value ? Carbon::parse($value)->format('d/m/Y') : null, // Cando empregamos a data da forma echo($perfume->data_compra); convirte o obxecto Carbon a unha cadea có formato correcto | ||
| + | set: fn($value) => $value ? Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d') : null // Cando empregamos a data da forma: $perfume->data_compra = "10/01/2025" o gardará | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | protected function Prezo(): Attribute | ||
| + | { | ||
| + | return Attribute::make( | ||
| + | // Cuando accedemos al valor del campo, convertimos el punto en coma | ||
| + | get: fn ($value) => number_format($value, 2, ',', '.'), | ||
| + | // Cuando se asigna el valor, reemplazamos la coma por un punto | ||
| + | set: fn ($value) => str_replace(',', '.', $value), | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | public function marca() { | ||
| + | return $this->belongsTo(Marca::class); | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | '''Arquivo routes.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="11" > | ||
| + | <?php | ||
| + | |||
| + | use Illuminate\Support\Facades\Route; | ||
| + | |||
| + | use App\Http\Controllers\PerfumeController; | ||
| + | use App\Http\Controllers\MarcaController; | ||
| + | use App\Http\Controllers\PerfumeBController; | ||
| + | |||
| + | Route::resource('perfumes', PerfumeController::class); | ||
| + | Route::resource('marcas', MarcaController::class); | ||
| + | Route::get('perfumesB', [PerfumeBController::class, 'index'])->name('perfumesB.index'); | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo app/Htpp/Controllers/PerfumeBController.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Http\Controllers; | ||
| + | |||
| + | use Illuminate\Http\Request; | ||
| + | |||
| + | class PerfumeBController extends Controller | ||
| + | { | ||
| + | public function index() | ||
| + | { | ||
| + | return view('perfumes_b.perfumes'); | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | '''Arquivo resources/views/perfumes_b/perfumes.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | @extends('plantilla'); | ||
| + | |||
| + | @section('pageTitle', 'Listado de perfumes con Livewire') | ||
| + | |||
| + | @section('content') | ||
| + | <h2>Listado de Perfumes con Livewire</h2> | ||
| + | |||
| + | |||
| + | @livewire("perfume-listado") | ||
| + | |||
| + | @livewire("perfume") | ||
| + | |||
| + | @include('mensaxes.alertas_javascript') | ||
| + | |||
| + | |||
| + | @endsection | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo app/Livewire/PerfumeListado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use App\Models\Perfume; | ||
| + | |||
| + | class PerfumeListado extends Component | ||
| + | { | ||
| + | public $perfumes; | ||
| + | |||
| + | public function cargarPerfumes() | ||
| + | { | ||
| + | $this->perfumes = Perfume::with('marca')->get(); | ||
| + | } | ||
| + | |||
| + | public function mount() | ||
| + | { | ||
| + | $this->cargarPerfumes(); | ||
| + | } | ||
| + | |||
| + | public function eliminarPerfume($id) | ||
| + | { | ||
| + | $perfume = Perfume::find($id); | ||
| + | |||
| + | if (!$perfume) { | ||
| + | $this->addError('error', 'Perfume non atopado.'); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | $perfume->delete(); | ||
| + | session()->flash('message', 'Perfume eliminado correctamente.'); | ||
| + | |||
| + | $this->cargarPerfumes(); | ||
| + | } | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | return view('livewire.perfume-listado'); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo resources/views/livewire/perfume-listado.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <div> | ||
| + | <button class="bg-green-500 text-white px-2 py-1 rounded mb-2">Novo perfume</button> | ||
| + | |||
| + | <table class="table-auto w-full border"> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th>Descrición</th> | ||
| + | <th>Prezo</th> | ||
| + | <th>Data Compra</th> | ||
| + | <th>Marca</th> | ||
| + | <th>Accións</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | <tbody> | ||
| + | @foreach($perfumes as $perfume) | ||
| + | <tr> | ||
| + | <td>{{ $perfume->descripcion }}</td> | ||
| + | <td>{{ $perfume->prezo }}</td> | ||
| + | <td>{{ $perfume->data_compra }}</td> | ||
| + | <td>{{ $perfume->marca->descripcion ?? 'Sen marca' }}</td> | ||
| + | <td> | ||
| + | <button class="bg-blue-500 text-white px-2 py-1 rounded">Editar</button> | ||
| + | <button wire:click="eliminarPerfume({{ $perfume->id }})" | ||
| + | wire:confirm="Estás seguro de borrar la marca {{ $perfume->descripcion }}? " | ||
| + | class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> | ||
| + | </td> | ||
| + | </tr> | ||
| + | @endforeach | ||
| + | </tbody> | ||
| + | </table> | ||
| + | |||
| + | @include('mensaxes.mensaxes') | ||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | '''Arquivo app/Livewire/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use App\Models\Perfume as PerfumeModel; | ||
| + | use App\Models\Marca; | ||
| + | |||
| + | class Perfume extends Component | ||
| + | { | ||
| + | public $id, $descripcion, $prezo, $data_compra, $marca_id; | ||
| + | public $modo = 'engadir'; | ||
| + | |||
| + | protected $rules = [ | ||
| + | 'descripcion' => 'required|string|min:5|max:45', | ||
| + | 'prezo' => 'required|numeric|min:0|max:999999.99', | ||
| + | 'data_compra' => 'nullable|date|after_or_equal:today', | ||
| + | 'marca_id' => 'nullable|exists:marcas,id', | ||
| + | ]; | ||
| + | |||
| + | |||
| + | public function mount($id = null) | ||
| + | { | ||
| + | $this->id = $id; | ||
| + | |||
| + | if ($id) { | ||
| + | $perfume = PerfumeModel::findOrFail($id); | ||
| + | $this->descripcion = $perfume->descripcion; | ||
| + | $this->prezo = $perfume->prezo; | ||
| + | $this->data_compra = $perfume->data_compra; | ||
| + | $this->marca_id = $perfume->marca_id; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public function gardarPerfume() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | |||
| + | if($this->modo=='engadir') { | ||
| + | $this->id = -1; | ||
| + | } | ||
| + | |||
| + | // Na versión anterior de xestión de perfumes, temos posto no modelo un set para pasar automaticamente da data en formato español a data en formato inglés. | ||
| + | // Revisade o código e podedes ver que temos un cast con Date e un set para pasar a data que ven en formato español da caixa de texto a inglés que se vai gardar na base de datos mysql. | ||
| + | // Nesta versión a data vaise recoller cun input de tipo 'date' que xa ven en formato inglés polo que daría un erro. Por iso a pasamos antes a formato español | ||
| + | $this->data_compra = \Carbon\Carbon::createFromFormat('Y-m-d', $this->data_compra)->format('d/m/Y'); | ||
| + | // Crear o actualizar el perfume | ||
| + | $perfumeCreado = PerfumeModel::updateOrCreate( | ||
| + | ['id' => $this->id], // Si existe un ID, lo actualiza. Si no, lo crea. | ||
| + | [ | ||
| + | 'descripcion' => $this->descripcion, | ||
| + | 'prezo' => $this->prezo, | ||
| + | 'data_compra' => $this->data_compra, | ||
| + | 'marca_id' => $this->marca_id | ||
| + | ] | ||
| + | ); | ||
| + | |||
| + | // Si se ha creado un nuevo perfume, asignar el ID | ||
| + | if (!$this->id) { | ||
| + | $this->id = $perfumeCreado->id; | ||
| + | } | ||
| + | |||
| + | // Mensaje de éxito | ||
| + | session()->flash('message', "Perfume guardado correctamente con id $perfumeCreado->id"); | ||
| + | |||
| + | // Resetear los campos | ||
| + | $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); | ||
| + | |||
| + | // Redirigir a la lista de perfumes | ||
| + | return redirect()->route('perfumesB.index'); | ||
| + | } | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $marcas = Marca::orderBy('descripcion')->get(); | ||
| + | return view('livewire.perfume',compact('marcas')); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | '''Arquivo resources/views/livewire/perfume.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <div class="p-4 border rounded bg-gray-100"> | ||
| + | <form wire:submit.prevent="gardarPerfume"> | ||
| + | |||
| + | <h2 class="text-lg font-bold mb-2">{{ $modo == 'engadir' ? 'Novo Perfume' : 'Editar Perfume' }}</h2> | ||
| + | |||
| + | <input type="text" wire:model="descripcion" placeholder="Descrición" class="block mb-2 w-full"> | ||
| + | @error('descripcion') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <input type="number" step="0.01" wire:model="prezo" placeholder="Prezo" class="block mb-2 w-full"> | ||
| + | @error('prezo') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <input type="date" wire:model="data_compra" class="block mb-2 w-full"> | ||
| + | @error('data_compra') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <select wire:model="marca_id" class="block mb-2 w-full"> | ||
| + | <option value="">-- Selecciona unha marca --</option> | ||
| + | @foreach ($marcas as $marca) | ||
| + | <option value="{{ $marca->id }}">{{ $marca->descripcion }}</option> | ||
| + | @endforeach | ||
| + | </select> | ||
| + | @error('marca_id') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | |||
| + | <input type="submit" name="btnAlta" value="{{ $modo == 'engadir' ? 'Alta' : 'Modificar' }}" | ||
| + | onClick="return confirm('{{ $modo == 'engadir' ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');" /> | ||
| + | <input type="reset" name="btnBorrar" value="Limpar" /> | ||
| + | |||
| + | </form> | ||
| + | @include('mensaxes.mensaxes') | ||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo resources/views/plantilla.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="25" > | ||
| + | <!DOCTYPE html> | ||
| + | <html> | ||
| + | <head> | ||
| + | <title>@yield('pageTitle')</title> | ||
| + | |||
| + | <style> | ||
| + | .menu{ | ||
| + | float: left; | ||
| + | width: 30%; | ||
| + | padding-left: 5px; | ||
| + | } | ||
| + | .menu a{ | ||
| + | display: block; | ||
| + | } | ||
| + | .contido{ | ||
| + | float: left; | ||
| + | width: 65%; | ||
| + | } | ||
| + | |||
| + | </style> | ||
| + | </head> | ||
| + | <body> | ||
| + | <div class="menu"> | ||
| + | <a href='{{route('perfumes.index')}}'>Operacións Perfumes</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> | ||
| + | <a href='{{route('perfumesB.index')}}'>Operacións Perfumes Livewire</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> | ||
| + | <a href='{{route('marcas.index')}}'>Operacións Marcas</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> | ||
| + | </div> | ||
| + | <div class="contido"> | ||
| + | @yield('content') | ||
| + | </div> | ||
| + | </body> | ||
| + | </html> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | '''Arquivo app/Models/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | </syntaxhighlight> | ||
<br /> | <br /> | ||
| − | |||
| + | ==Eventos entre compoñentes== | ||
| + | |||
| + | * Máis información en [https://livewire.laravel.com/docs/events https://livewire.laravel.com/docs/events] | ||
| + | : Imos seguir co exemplo anterior de perfumes e imos solucionar as funcionalidades que nos quedan. | ||
| + | |||
| + | |||
| + | * Os eventos poden ir dende un compoñente livewire a outro ou dende un compoñente livewire a un script dunha plantilla blade. | ||
| + | |||
| + | |||
| + | * Imos aplicar isto á nosa pantalla de Perfumes. | ||
| + | : Se vos fixades, cando creamos un novo perfume no compoñente livewire Perfume, recargamos a páxina: | ||
| + | '''Arquivo app/Livewire/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | ............ | ||
| + | public function gardarPerfume() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | ............ | ||
| + | |||
| + | return redirect()->route('perfumesB.index'); | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | : O que imos facer é enviar un evento a todos os compoñentes livewire que estean cargados na páxina e que estén escoitando dito evento. | ||
| + | : No noso caso: | ||
| + | '''Arquivo app/Livewire/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="7" > | ||
| + | ............ | ||
| + | public function gardarPerfume() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | ............ | ||
| + | |||
| + | $this->dispatch('refrescar-listado-perfumes'); | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | : O nome pode ser calquera. Agora temos que facer que o compoñente livewire de listado escoite dito evento e asocialo a un método. | ||
| + | '''Arquivo app/Livewire/PerfumeListado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="6,13-16" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use Livewire\Attributes\On; | ||
| + | |||
| + | use App\Models\Perfume; | ||
| + | |||
| + | class PerfumeListado extends Component | ||
| + | { | ||
| + | ................. | ||
| + | #[On('refrescar-listado-perfumes')] | ||
| + | public function refrescarListado(){ | ||
| + | $this->cargarPerfumes(); | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | : O nome da liña On debe ser o mesmo que empregamos cando fixemos o dispatch. | ||
| + | : Agora se probades a dar de alta un novo perfume se actualizará o listado pero no se carga a páxina novamente. | ||
| + | |||
| + | |||
| + | |||
| + | * Agora imos a facer que cando premamos no botón de alta fagamos unha alta no compoñente livewire de perfume e o mesmo na modificación. | ||
| + | : Para iso necesitamos crear un método no controlador de PerfumeListado que sexa chamado polos botóns de alta e modificar enviando no caso de modificar o id do perfume. | ||
| + | : Despois o que fará este método é lanzar un evento para que sexa capturado polo compoñente livewire Perfume e modificar a propiedade modo e cargar os datos do perfume en caso de modificación. | ||
| + | |||
| + | '''Arquivo resources/views/livewire/perfume-listado.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="2,22" > | ||
| + | <div> | ||
| + | <button wire:click="operacionPerfume" class="bg-green-500 text-white px-2 py-1 rounded mb-2">Novo perfume</button> | ||
| + | |||
| + | <table class="table-auto w-full border"> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th>Descrición</th> | ||
| + | <th>Prezo</th> | ||
| + | <th>Data Compra</th> | ||
| + | <th>Marca</th> | ||
| + | <th>Accións</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | <tbody> | ||
| + | @foreach($perfumes as $perfume) | ||
| + | <tr> | ||
| + | <td>{{ $perfume->descripcion }}</td> | ||
| + | <td>{{ $perfume->prezo }}</td> | ||
| + | <td>{{ $perfume->data_compra }}</td> | ||
| + | <td>{{ $perfume->marca->descripcion ?? 'Sen marca' }}</td> | ||
| + | <td> | ||
| + | <button wire:click="operacionPerfume({{$perfume->id}})" class="bg-blue-500 text-white px-2 py-1 rounded">Editar</button> | ||
| + | <button wire:click="eliminarPerfume({{ $perfume->id }})" | ||
| + | wire:confirm="Estás seguro de borrar la marca {{ $perfume->descripcion }}? " | ||
| + | class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> | ||
| + | </td> | ||
| + | </tr> | ||
| + | @endforeach | ||
| + | </tbody> | ||
| + | </table> | ||
| + | |||
| + | @include('mensaxes.mensaxes') | ||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | '''Arquivo app/Livewire/PerfumeListado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="6,25-27" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Component; | ||
| + | use Livewire\Attributes\On; | ||
| + | use Illuminate\Support\Facades\Log; | ||
| + | |||
| + | use App\Models\Perfume; | ||
| + | |||
| + | class PerfumeListado extends Component | ||
| + | { | ||
| + | public $perfumes; | ||
| + | |||
| + | public function cargarPerfumes() | ||
| + | { | ||
| + | $this->perfumes = Perfume::with('marca')->get(); | ||
| + | } | ||
| + | |||
| + | public function mount() | ||
| + | { | ||
| + | $this->cargarPerfumes(); | ||
| + | } | ||
| + | |||
| + | public function operacionPerfume($id=null){ | ||
| + | $this->dispatch('operar-perfume',id:$id); // Chamamos ao método e enviamos o id do perfume. null => alta, numero => modificar | ||
| + | } | ||
| + | |||
| + | #[On('refrescar-listado-perfumes')] | ||
| + | public function refrescarListado(){ | ||
| + | $this->cargarPerfumes(); | ||
| + | } | ||
| + | |||
| + | public function eliminarPerfume($id) | ||
| + | { | ||
| + | $perfume = Perfume::find($id); | ||
| + | |||
| + | if (!$perfume) { | ||
| + | $this->addError('error', 'Perfume non atopado.'); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | $perfume->delete(); | ||
| + | session()->flash('message', 'Perfume eliminado correctamente.'); | ||
| + | |||
| + | $this->cargarPerfumes(); | ||
| + | } | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | return view('livewire.perfume-listado'); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo app/Livewire/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="5,36-55" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Attributes\On; | ||
| + | use Livewire\Component; | ||
| + | |||
| + | use App\Models\Perfume as PerfumeModel; | ||
| + | use App\Models\Marca; | ||
| + | |||
| + | class Perfume extends Component | ||
| + | { | ||
| + | public $id, $descripcion, $prezo, $data_compra, $marca_id; | ||
| + | public $modo = 'engadir'; | ||
| + | |||
| + | protected $rules = [ | ||
| + | 'descripcion' => 'required|string|min:5|max:45', | ||
| + | 'prezo' => 'required|numeric|min:0|max:999999.99', | ||
| + | 'data_compra' => 'nullable|date|after_or_equal:today', | ||
| + | 'marca_id' => 'nullable|exists:marcas,id', | ||
| + | ]; | ||
| + | |||
| + | |||
| + | public function mount($id = null) | ||
| + | { | ||
| + | $this->id = $id; | ||
| + | |||
| + | if ($id) { | ||
| + | $perfume = PerfumeModel::findOrFail($id); | ||
| + | $this->descripcion = $perfume->descripcion; | ||
| + | $this->prezo = $perfume->prezo; | ||
| + | $this->data_compra = $perfume->data_compra; | ||
| + | $this->marca_id = $perfume->marca_id; | ||
| + | } | ||
| + | } | ||
| + | #[On('operar-perfume')] | ||
| + | public function operarPerfume($id){ | ||
| + | |||
| + | if($id==null){ | ||
| + | $this->modo = "engadir"; | ||
| + | $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); | ||
| + | $this->id = -1; | ||
| + | } | ||
| + | else{ | ||
| + | $this->id = $id; | ||
| + | $perfume = PerfumeModel::findOrFail($id); | ||
| + | $this->descripcion = $perfume->descripcion; | ||
| + | $this->prezo = str_replace(',','.',$perfume->prezo); // Prezo ten un mutator para poñer coma en vez de punto | ||
| + | if($this->data_compra){ | ||
| + | $this->data_compra = \Carbon\Carbon::createFromFormat('d/m/Y', $perfume->data_compra)->format('Y-m-d'); | ||
| + | } | ||
| + | $this->marca_id = $perfume->marca_id; | ||
| + | $this->modo ="actualizar"; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public function gardarPerfume() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | |||
| + | // Na versión anterior de xestión de perfumes, temos posto no modelo un set para pasar automaticamente da data en formato español a data en formato inglés. | ||
| + | // Nesta versión a data xa ven en formato inglés polo que daría un erro. Por iso a pasamos antes a formato español | ||
| + | if($this->data_compra) { | ||
| + | $this->data_compra = \Carbon\Carbon::createFromFormat('Y-m-d', $this->data_compra)->format('d/m/Y'); | ||
| + | } | ||
| + | |||
| + | // Crear o actualizar el perfume | ||
| + | $perfumeCreado = PerfumeModel::updateOrCreate( | ||
| + | ['id' => $this->id], // Si existe un ID, lo actualiza. Si no, lo crea. | ||
| + | [ | ||
| + | 'descripcion' => $this->descripcion, | ||
| + | 'prezo' => $this->prezo, | ||
| + | 'data_compra' => $this->data_compra, | ||
| + | 'marca_id' => $this->marca_id | ||
| + | ] | ||
| + | ); | ||
| + | |||
| + | // Si se ha creado un nuevo perfume, asignar el ID | ||
| + | if (!$this->id) { | ||
| + | $this->id = $perfumeCreado->id; | ||
| + | } | ||
| + | |||
| + | // Mensaje de éxito | ||
| + | session()->flash('message', "Perfume guardado correctamente con id $perfumeCreado->id"); | ||
| + | |||
| + | // Resetear los campos | ||
| + | $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); | ||
| + | |||
| + | // Redirigir a la lista de perfumes | ||
| + | //return redirect()->route('perfumesB.index'); | ||
| + | $this->dispatch('refrescar-listado-perfumes'); | ||
| + | } | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $marcas = Marca::orderBy('descripcion')->get(); | ||
| + | return view('livewire.perfume',compact('marcas')); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo resources/views/livewire/perfume.blade.php''' | ||
::<syntaxhighlight lang="java" enclose="div" highlight="" > | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <div class="p-4 border rounded bg-gray-100"> | ||
| + | <form wire:submit.prevent="gardarPerfume"> | ||
| + | |||
| + | <h2 class="text-lg font-bold mb-2">{{ $modo == 'engadir' ? 'Novo Perfume' : 'Editar Perfume' }}</h2> | ||
| + | |||
| + | <input type="text" wire:model="descripcion" placeholder="Descrición" class="block mb-2 w-full"> | ||
| + | @error('descripcion') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <input type="number" step="0.01" wire:model="prezo" placeholder="Prezo" class="block mb-2 w-full"> | ||
| + | @error('prezo') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <input type="date" wire:model="data_compra" class="block mb-2 w-full"> | ||
| + | @error('data_compra') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <select wire:model="marca_id" class="block mb-2 w-full"> | ||
| + | <option value="">-- Selecciona unha marca --</option> | ||
| + | @foreach ($marcas as $marca) | ||
| + | <option value="{{ $marca->id }}">{{ $marca->descripcion }}</option> | ||
| + | @endforeach | ||
| + | </select> | ||
| + | @error('marca_id') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | |||
| + | <input type="submit" name="btnAlta" value="{{ $modo == 'engadir' ? 'Alta' : 'Modificar' }}" | ||
| + | onClick="return confirm('{{ $modo == 'engadir' ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');" /> | ||
| + | <input type="reset" name="btnBorrar" value="Limpar" /> | ||
| + | |||
| + | </form> | ||
| + | @include('mensaxes.mensaxes') | ||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | * Agora soamente queda por facer que a alta/modificación sexa invisible ata que se pulse o botón correspondente. | ||
| + | : Iso o podemos facer na vista mirando que o modo sexa engadir/actualizar en en caso de que non o sexa, non amosar nada (algo hai que amosar, polo que envolvemos todo con un div) | ||
| + | '''Arquivo resources/views/livewire/perfume.blade.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="2" > | ||
| + | <div> | ||
| + | @if($modo=='engadir' || $modo=='actualizar') | ||
| + | <div class="p-4 border rounded bg-gray-100"> | ||
| + | <form wire:submit.prevent="gardarPerfume"> | ||
| + | |||
| + | <h2 class="text-lg font-bold mb-2">{{ $modo == 'engadir' ? 'Novo Perfume' : 'Editar Perfume' }}</h2> | ||
| + | <input type="text" wire:model="descripcion" placeholder="Descrición" class="block mb-2 w-full"> | ||
| + | @error('descripcion') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <input type="number" step="0.01" wire:model="prezo" placeholder="Prezo" class="block mb-2 w-full"> | ||
| + | @error('prezo') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <input type="date" wire:model="data_compra" class="block mb-2 w-full"> | ||
| + | @error('data_compra') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | <select wire:model="marca_id" class="block mb-2 w-full"> | ||
| + | <option value="">-- Selecciona unha marca --</option> | ||
| + | @foreach ($marcas as $marca) | ||
| + | <option value="{{ $marca->id }}">{{ $marca->descripcion }}</option> | ||
| + | @endforeach | ||
| + | </select> | ||
| + | @error('marca_id') <span class="error">{{ $message }}</span><br /> @enderror | ||
| + | |||
| + | <input type="submit" name="btnAlta" value="{{ $modo == 'engadir' ? 'Alta' : 'Modificar' }}" | ||
| + | onClick="return confirm('{{ $modo == 'engadir' ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');" /> | ||
| + | <input type="reset" name="btnBorrar" value="Limpar" /> | ||
| + | |||
| + | </form> | ||
| + | @include('mensaxes.mensaxes') | ||
| + | </div> | ||
| + | @endif | ||
| + | </div> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | * Agora no controlador inicializamos o modo a "" e o cambiamos en función da operación a realizar. | ||
| + | '''Arquivo app/Livewire/Perfume.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="14,40,53,88" > | ||
| + | <?php | ||
| + | |||
| + | namespace App\Livewire; | ||
| + | |||
| + | use Livewire\Attributes\On; | ||
| + | use Livewire\Component; | ||
| + | |||
| + | use App\Models\Perfume as PerfumeModel; | ||
| + | use App\Models\Marca; | ||
| + | |||
| + | class Perfume extends Component | ||
| + | { | ||
| + | public $id, $descripcion, $prezo, $data_compra, $marca_id; | ||
| + | public $modo=""; | ||
| + | |||
| + | protected $rules = [ | ||
| + | 'descripcion' => 'required|string|min:5|max:45', | ||
| + | 'prezo' => 'required|numeric|min:0|max:999999.99', | ||
| + | 'data_compra' => 'nullable|date|after_or_equal:today', | ||
| + | 'marca_id' => 'nullable|exists:marcas,id', | ||
| + | ]; | ||
| + | |||
| + | |||
| + | public function mount($id = null) | ||
| + | { | ||
| + | $this->id = $id; | ||
| + | |||
| + | if ($id) { | ||
| + | $perfume = PerfumeModel::findOrFail($id); | ||
| + | $this->descripcion = $perfume->descripcion; | ||
| + | $this->prezo = $perfume->prezo; | ||
| + | $this->data_compra = $perfume->data_compra; | ||
| + | $this->marca_id = $perfume->marca_id; | ||
| + | } | ||
| + | } | ||
| + | #[On('operar-perfume')] | ||
| + | public function operarPerfume($id){ | ||
| + | |||
| + | if($id==null){ | ||
| + | $this->modo = "engadir"; | ||
| + | $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); | ||
| + | $this->id = -1; | ||
| + | } | ||
| + | else{ | ||
| + | $this->id = $id; | ||
| + | $perfume = PerfumeModel::findOrFail($id); | ||
| + | $this->descripcion = $perfume->descripcion; | ||
| + | $this->prezo = str_replace(',','.',$perfume->prezo); // Prezo ten un mutator para poñer coma en vez de punto | ||
| + | if($this->data_compra){ | ||
| + | $this->data_compra = \Carbon\Carbon::createFromFormat('d/m/Y', $perfume->data_compra)->format('Y-m-d'); | ||
| + | } | ||
| + | $this->marca_id = $perfume->marca_id; | ||
| + | $this->modo ="actualizar"; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public function gardarPerfume() | ||
| + | { | ||
| + | $this->validate(); | ||
| + | |||
| + | // Na versión anterior de xestión de perfumes, temos posto no modelo un set para pasar automaticamente da data en formato español a data en formato inglés. | ||
| + | // Nesta versión a data xa ven en formato inglés polo que daría un erro. Por iso a pasamos antes a formato español | ||
| + | if($this->data_compra) { | ||
| + | $this->data_compra = \Carbon\Carbon::createFromFormat('Y-m-d', $this->data_compra)->format('d/m/Y'); | ||
| + | } | ||
| + | // Crear o actualizar el perfume | ||
| + | $perfumeCreado = PerfumeModel::updateOrCreate( | ||
| + | ['id' => $this->id], // Si existe un ID, lo actualiza. Si no, lo crea. | ||
| + | [ | ||
| + | 'descripcion' => $this->descripcion, | ||
| + | 'prezo' => $this->prezo, | ||
| + | 'data_compra' => $this->data_compra, | ||
| + | 'marca_id' => $this->marca_id | ||
| + | ] | ||
| + | ); | ||
| + | |||
| + | // Si se ha creado un nuevo perfume, asignar el ID | ||
| + | if (!$this->id) { | ||
| + | $this->id = $perfumeCreado->id; | ||
| + | } | ||
| + | |||
| + | // Mensaje de éxito | ||
| + | session()->flash('message', "Perfume guardado correctamente con id $perfumeCreado->id"); | ||
| + | |||
| + | // Resetear los campos | ||
| + | $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); | ||
| + | |||
| + | $this->modo=""; | ||
| + | // Redirigir a la lista de perfumes | ||
| + | //return redirect()->route('perfumesB.index'); | ||
| + | $this->dispatch('refrescar-listado-perfumes'); | ||
| + | } | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $marcas = Marca::orderBy('descripcion')->get(); | ||
| + | return view('livewire.perfume',compact('marcas')); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | <br /> | ||
| + | * Neste caso non fixo falta, pero cando queremos refrescar o compoñente perfumes-listado, pode ser que en vez de gardar os datos nunha propiedade local ($perfumes), podemos envialos localmente no render, da forma: | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | |||
| + | public function render() | ||
| + | { | ||
| + | $perfumes = Perfume::with('marca')->get(); | ||
| + | return view('livewire.perfume-listado',compact('perfumes'); | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : '''<u>NOTA IMPORTANTE:</u>''' Para que funcione desta forma tedes que eliminar a propiedade $perfumes do compoñente e xa non necesitades o mount nin o método cargarPerfumes. | ||
| + | |||
| + | : Nese caso, se queremos 'refrescar' o compoñente para que volva a facer o render, teríamos que poñer: <b>this->dispatch('render')</b> no método 'refrescar-perfumes'. | ||
| + | |||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="3" > | ||
| + | #[On('refrescar-listado-perfumes')] | ||
| + | public function refrescarListado(){ | ||
| + | $this->dispatch('refrescar'); | ||
| + | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | |||
Revisión actual del 17:27 7 sep 2026
Sumario
Introducción
- Livewire permite o uso de compoñentes integrados en Laravel nos que se establece unha comunicación bidireccional (podemos enviar información desde o cliente (HTML) ao servidor (controlador) e viceversa sen necesidade de recargar completamente a páxina).
- É o mesmo concepto que se empregaba en AJAX e que agora se usa con FETCH, pero integrado no framework de Laravel.
- IMPORTANTE: Cando vexades a documentación mirade que version estades a consultar xa que dende a versión 2 á versión 3 houbo cambios.
- Unha vez instalado en Laravel podedes consultar a versión instalada buscando a entrada livewire/livewire no arquivo composor.json no raíz do proxecto.
- Polo de agora (versión 10-12) estamos a traballar coa versión 3.x de Livewire.
Instalación
- No directorio raíz do proxecto Laravel escribe: composer require livewire/livewire
- ATENCIÓN: A día de hoxe (2026) saiu a versión 4.0 de Livewire. No exame imos empregar a 4.0.
- : Polo tanto empregar esta orden: composer require livewire/livewire "^4.0" para descargar a versión 4.0 (non poñer sudo)
- Unha vez instalado en Laravel podedes consultar a versión instalada buscando a entrada livewire/livewire no arquivo composer.json no raíz do proxecto.
- Polo de agora coa versión 13 de Laravel imos empregar a versión 4.0 de Livewire.
Validación de campos
- Creamos en mysql (se non o tema xa de exercicios anteriores) unha base de datos Mysql de nome perfumes.
- Lembrar que o usuario configurado en Laravel para acceder á base de datos debería ter os permisos mínimos para que a aplicación funcione.
- Imos crear a táboa marcas e imos crear unha relación entre perfumes e marcas.
- A nivel de mysql:
CREATE TABLE perfumes.marcas ( `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, `descripcion` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; INSERT INTO perfumes.marcas (`id`, `descripcion`) VALUES (1, 'Descripción actualizada da marca con id=1'), (2, 'ADIDAS'), (3, 'ARAMIS');
- Táboa perfumes
CREATE TABLE `perfumes.perfumes` ( `id` int NOT NULL AUTO_INCREMENT, `descripcion` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL, `prezo` decimal(8,2) NOT NULL, `data_compra` date DEFAULT NULL, `marca_id` int DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_PERFUMES_1_idx` (`id`), KEY `fk_PERFUMES_MARCA_idx` (`marca_id`), CONSTRAINT `fk_perfumes_1` FOREIGN KEY (`marca_id`) REFERENCES `marcas` (`id`) ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; INSERT INTO perfumes.perfumes (`id`, `descripcion`, `prezo`, `data_compra`, `marca_id`) VALUES (18, 'Best Aramis', '54.32', '2024-01-18', 2), (19, 'Novo perfume da marca con id 1', '100.22', '2024-01-22', 1);
- Creamos o modelo, ruta, controlador e polo de agora imos facer a vista para crear unha nova marca.
- Lembrar que esta parte ven ser a continuación do feito na UD6 - Modelo Vista Controlador de Laralve.
- Dende o terminal escribimos: php artisan make:model Marca --resource
- Arquivo: app/models/Marca.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use App\Models\Perfume; class Marca extends Model { public $timestamps = false; protected $fillable = [ 'descripcion', ]; public function perfumes(){ return $this->hasMany(Perfume::class, 'marca_id','id'); // Non necesitamos poñer xa que 'marca_id','id' cumpren as normas de nomeamento } }
- Arquivo: app/models/Perfume.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; use Carbon\Carbon; class Perfume extends Model { protected $table = 'perfumes'; // Indicamos a táboa relacionada con este modelo. protected $primaryKey = 'id'; // Indicamos a clave primaria da táboa public $timestamps = false; // Por defecto laravel inclúe dous campos (created_at e updated_at) de tipo datetime cando se fai unha operación de inserción e actualización sobre unha fila. Con isto indicamos que a táboa non ten eses campos. protected $casts = [ // Cando accedamos a unha fila empregando este modelo, se poñemos $fila->data_compra teremos un string. Se poñemos o cast, obteremos unha instancia da [https://carbon.nesbot.com/docs/ clase Carbon] de Laravel que nos permite facer operacións e formatear a data. 'data_compra' => 'date', // Tamén se pode empregar datetime. Devolve un Carbon ]; protected $fillable = [ // Isto xa o veremos. O que indica é que cando facemos unha operación masiva de inserción ou un insert da forma create(datos) soamente deixa enviar en datos (que é un array) as columnas indicadas nesta propiedade (as columnas indicadas son descripcion, prezo e data_compra). Faise por motivos de seguridade. 'descripcion', 'prezo', 'data_compra' ]; protected function DataCompra(): Attribute { return Attribute::make( get: fn($value) => $value ? Carbon::parse($value)->format('d/m/Y') : null, // Cando empregamos a data da forma echo($perfume->data_compra); convirte o obxecto Carbon a unha cadea có formato correcto set: fn($value) => $value ? Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d') : null // Cando empregamos a data da forma: $perfume->data_compra = "10/01/2025" o gardará ); } protected function Prezo(): Attribute { return Attribute::make( // Cuando accedemos al valor del campo, convertimos el punto en coma get: fn ($value) => number_format($value, 2, ',', '.'), // Cuando se asigna el valor, reemplazamos la coma por un punto set: fn ($value) => str_replace(',', '.', $value), ); } }
- Arquivo: routes/web.php
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\PerfumeController; use App\Http\Controllers\MarcaController; Route::resource('perfumes', PerfumeController::class); Route::resource('marcas', MarcaController::class);
- O arquivo de MarcaController xa se crearia con todo o código necesario para a xestión.
- Arquivo resources/views/marcas/form_alta_marcas.blade.php (creado o cartafol marcas dentro de view)
@extends('plantilla'); @section('pageTitle', 'Alta de marcas') @section('content') <h2>Alta de Marcas</h2> @if (session()->has('idPerfume')) <h4>Creado un novo rexistro co id: {{session('idPerfume')}}</h4> @endif @include('mensaxes.alertas_javascript') @endsection
- Creamos o compoñente Livewire con esta orde: php artisan make:livewire marca
- Isto creará dous arquivos en Laravel:
- CLASS: app/Livewire/Marca.php => Ven a ser o controlador que vimos ata o de agora en Laravel.
- VIEW: resources/views/livewire/marca.blade.php => Ven a ser a vista que vai visualizar Laravel no cliente.
- Arquivo: app/Livewire/Marca.php
<?php namespace App\Livewire; use Livewire\Component; class Marca extends Component { public function render() { return view('livewire.marca'); } }
- Básicamente indicamos en render a vista que vai enviar ao cliente.
- Arquivo resource/views/Livewire/marca.blade.php
<form method="post" action="{{route('marcas.store')}}" name="formulario_marcas"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required name="descripcion" /> </div> <div> <input type="submit" name="btnAlta" value="Alta" onClick="return confirm('Estás seguro de dar de alta a nova marca ');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form>
- Nota: Non separamos os campos nun arquivo diferente xa que en livewire as operacións de alta e modificación vanse facer sobre a mesma vista.
- Agora para incluir o compoñente Livewire nunha view de Laravel temos que poñer:
- Arquivo resouces/views/marcas/form_alta_marca.blade.php
@extends('plantilla'); @section('pageTitle', 'Alta de Marcas') @section('content') <h2>Alta de novos perfumes</h2> <livewire:marca /> ................
- Nota: Outra forma de añadir o livewire en la vista es: @livewire(Marca:class) o también @livewire('marca')
- Se agora poñemosm na url: http://localhost:8080/marcas/create veremos que aparecerá unha vista tal cual aparecía cando fixemos o de perfume, pero a diferenza de perfumes, esta vista (os campos e form) se xestionan cun compoñente Livewire.
- Agora imos ver os cambios que temos que facer para xestionar os campos do formulario.
- Primero imos definir no controlador do compoñente livewire os campos que imos xestionar. No noso caso está a descripcion.
<?php namespace App\Livewire; use Livewire\Component; class Marca extends Component { public $descripcion = "Valor inicial"; // O valor inicial non é necesasrio. Soamente para que vexades como dito valor se carga na vista. public function render() { return view('livewire.marca'); } }
- Modificamos o campo descripcion.
- Arquivo: resources/views/livewire/marca.blade.php
<div> Descripción:<input type="text" maxlength="45" required size="45" wire:model="descripcion" name="descripcion" /> </div>
- wire:model="descripcion": O que estamos a dicir con isto é que este input está vinculado á propiedade $descripcion definida no compoñente Livewire. Calquera modificación realizada polo usuario no cliente (na páxina web), cando se faga unha chamada ao controlador livewire, actualizará automaticamente o valor da propiedade no servidor grazas á reactividade proporcionada por Livewire. Ademais, se a propiedade no servidor cambia, ese cambio tamén se reflectirá no cliente.
- Se vos dades conta non temos a propiedade value como tiñamos en perfumes.
- Se agora cargades a páxina veredes como o valor inicial se carga no input.
- Agora imos a facer a validación no servidor. Para iso temos que definir unha propiedade $rules coas regras de validación que xa empregamos en Laravel.
- Ademáis imos crear un método ao que vai chamar o formulario cando faga submit e que fará a validación dos campos. No exemplo imos chamar ao método gardarMarca.
- Arquivo: app/Livewire/Marca.php
<?php namespace App\Livewire; use Illuminate\Support\Facades\Log; use Livewire\Component; class Marca extends Component { public $descripcion = "Valor inicial"; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', ]; public function render() { return view('livewire.marca'); } public function gardarMarca(){ $this->validate(); Log::info('Pasa a validación'); } }
- Para depurar o funcionamento dos compoñente livewire podemos empregar o facade Log::info e ver as mensaxes no arquivo /storage/logs/laravel.log (podedes borrar o contido antes de executar para ter todo limpo ao principio).
- Para chamar a ese método temos que modificar a vista do compoñente livewire.
- Arquivo: resources/views/livewire/marca.blade.php
<form wire:submit.prevent="gardarMarca"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required wire:model="descripcion" name="descripcion" /> </div> <div> <input type="submit" name="btnAlta" value="Alta" onClick="return confirm('Estás seguro de dar de alta a nova marca ');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form>
- Con isto wire:submit.prevent="gardarMarca" estamos a decir que cando faga o submit chame de forma asíncrona ao controlador do compoñente livewire ao método gardarMarca.
- No caso de manexar formularios en livewire, empregar sempre prevent. Poderíamos facelo con wire:submit pero nese caso a páxina enteira se carga como se fora un submit 'tradicional'.
- Nota: Podemos asociar calquera click de botón directamente ao un método no controlador do livewire, da forma: <button wire:click="gardarMarca">+</button>
- Tamén imos visualizar os erros de validación que poida enviar o controlador Livewire se non pasa a validación. Para iso temos que empregar a directiva @error sobre cada un dos campos que teñen validación. No exemplo se coloca ao lado do input, pero poderían ir en calquera parte.
- Arquivo: resources/views/livewire/marca.blade.php
<div> Descripción:<input type="text" maxlength="45" required size="45" wire:model="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div>
- Se queremos personalizar as mensaxes de erro:
- Arquivo: app/Livewire/Marca.php
<?php ........... protected $messages = [ 'descripcion.required' => 'La descripción es obligatoria.', 'descripcion.string' => 'La descripción debe ser una cadena de texto.', 'descripcion.min' => 'La descripción debe tener al menos 5 caracteres.', 'descripcion.max' => 'La descripción no puede tener más de 45 caracteres.', ]; ..............
- Fixerase que neste caso a validación faise cando o usuario preme o botón de submit.
- Se queremos que se faga a validación cando o usuario cambio o contido da caixa de texto temos que:
- Empregar o método update($propiedade,$valor) que será chamado de forma automáticamente cando o campo teña unha modificación. Dito método fará a validación da propiedade. Temos que chamar ao método validatyOnly.
- Cambiar a propiedade wire:model da caixa de texto por wire:model.live
- Lembrar que estas chamadas fanse ao servidor polo que pode ser que non nos interese para aforrar recursos e comunicacións entre cliente e o servidor.
- Arquivo: resources/views/livewire/marca.blade.php
- MOI IMPORTANTE: Tedes que cambiar a propiedade wire:model por wire:model.live
<div> Descripción:<input type="text" maxlength="45" size="45" required wire:model.live="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div>
- Arquivo: app/Livewire/Marca.php
.............. public function updated($propiedade,$valor){ $this->validateOnly($propiedade); } .............
- Ao facer isto, cada vez que se preme no input fará a validación.
- Lembrar que poñer isto non quita que teñades a chamada $this->validate() cando se vaia a dar de alta/modificar os datos
- Mirade a documentación xa que a validación tamén se pode definir en cada un dos campos en vez de empregar a propiedade $rules: https://livewire.laravel.com/docs/validation (escoller versión 3 na parte esquerda da páxina)
Operacións de alta-baixa-modificación-listado
Alta
- A alta faise como sempre pero agora está no controlador do compoñente livewire.
- Hai un pequeno detalle xa que como chamamos Alta ao compoñente Livewire, se empregamos o modelo Marca teríamos o mesmo nome. Por iso empregaremos un alias para o modelo.
- Arquivo: app/Livewire/Marca.php
use App\Models\Marca as MarcaModel; ............. public function gardarMarca(){ $this->validate(); Log::info('Pasa a validación'); $marcaCreada = MarcaModel::create([ 'descripcion' => $this->descripcion ]); session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); $this->reset(['descripcion']); // Borra os datos da caixa de texto. }
- Fixarse que para enviar unha información á vista empregamos session()->flash que vai gardar nunha variable de sesión (message) o texto a amosar.
- Dita variable non pode ser empregada pola páxina, soamente polo compoñente livewire xa que non se recarga a páxina enteira.
- A chamada a reset vai poñer en branco os valor da variable $descripcion do compoñente. Pero como temos un valor por defecto na definición poñerá dito valor. Lembrar que non é obrigatorio e podemos quitalo.
- Tamén indicar que podemos facer o create da forma: MarcaModel::create($this->all())
- Neste caso quedaríamos na pantalla de alta pero dita pantalla non se refresca. Soamente refresca o compoñente livewire.
- Para amosar as mensaxes de que todo foi correcto, imos crear un arquivo na vista.
- Tamén imos poñer nese arquivo os posibles erros que enviemos dende o controlador.
- Arquivo app/views/mensaxes/mensaxes.blade.php. Lembrar crear o cartafol mensaxes.
@if (session()->has('message')) <div class="alert alert-success"> {{ session('message') }} </div> @endif
- Simplemente amosamos o contido da variable de sesión message.
- Incluimos dito arquivo na vista do compoñente livewire:
- Arquivo app/views/livewire/marca.blade.php.
<div> <form wire:submit.prevent="gardarMarca"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required wire:model.live="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div> <div> <input type="submit" name="btnAlta" value="Alta" onClick="return confirm('Estás seguro de dar de alta a nova marca ');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form> @include('mensaxes.mensaxes') </div>
- IMPORTANTE: O compoñente livewire soamente pode ter un elemento raíz, no exemplo posto . Non podedes poñer o include fora do div.
- E que pasa se queremos ir a outra pantalla ou que recargue toda a páxina e non soamente o compoñente livewire ? Debemos empregar o método redirectRoute() poñendo o nome da ruta á que queremos ir.
- Arquivo: app/Livewire/Marca.php
................... public function gardarMarca() { $this->validate(); Log::info('Pasa a validación'); $marcaCreada = MarcaModel::create([ 'descripcion' => $this->descripcion ]); session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); $this->reset(['descripcion']); return $this->redirectRoute('marcas.create'); }
Modificar
- Agora, ao engadir unha nova operación temos un problema.
- Se non empregáramos livewire teríamos dúas vistas diferentes, unha para alta e outra para modificación, pero ao estar nun compoñente livewire temos que engadir a lóxica para que a vista faga unha cousa ou outra.
- Creamos a vista en Laravel para actualizar a marca:
- Arquivo resources/views/marcas/form_modificar_marcas.blade.php
@extends('plantilla'); @section('pageTitle', 'Modificar Marcas') @section('content') <h2>Modificar Marcas</h2> <livewire:marca modo="actualizar" :id="$marca->id"/> <!-- Se queres enviar unha cadea ou numero poñemos clave="valor". Os dous puntos (:id) é para enviar unha variable de Laravel --> @include('mensaxes.alertas_javascript') @endsection
- Fixarse como estamos a pasar o id da marca que queremos modificar ao compoñente Livewire. Dita marca ven do controlador de Laravel (posto a continuación)
- Cargamos dita vista no controlador de Laravel no método edit.
- Arquivo app/http/Controllers/MarcaController.php
public function edit(Marca $marca) { return view('marcas.form_modificar_marcas', compact('marca')); }
- Agora temos que modificar o controlador do compoñente livewire
- Polo tanto imos engadir ao controlador:
- Unha variable que vai indicar se estamos en modo edición ou en modo alta. En función desa variable a vista amosará un texto diferente no botón e fará un actión diferente.
- Unha variable $id que vai gardar o id da marca que imos querer modificar. Dita id será o que enviemos dende a vista de Laravel cando premamos na modificación.
- Tamén teremos un método actualizarMarca() que actualizará os campos necesarios (no noso exemplo descripcion) cando o usuario preme no botón modificar da vista do compoñente livewire.
- Arquivo app/http/livewire/Marcas.php
<?php namespace App\Livewire; use Illuminate\Support\Facades\Log; use Livewire\Attributes\Validate; use App\Models\Marca as MarcaModel; use Livewire\Component; class Marca extends Component { public $descripcion = "Valor inicial"; public $id; // A empregamos na operación de modificación/baixa para atopar a marca a modificar ou borrar public $modo = "engadir"; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', ]; protected $messages = [ 'descripcion.required' => 'La descripción es obligatoria.', 'descripcion.string' => 'La descripción debe ser una cadena de texto.', 'descripcion.min' => 'La descripción debe tener al menos 5 caracteres.', 'descripcion.max' => 'La descripción no puede tener más de 45 caracteres.', ]; public function mount($id = null) { $this->id = $id; if($id) { $marca = MarcaModel::findOrFail($id); $this->descripcion = $marca->descripcion; } } public function render() { return view('livewire.marca'); } public function gardarMarca() { $this->validate(); Log::info('Pasa a validación'); $marcaCreada = MarcaModel::create([ 'descripcion' => $this->descripcion ]); $this->id = $marcaCreada->id; session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); $this->reset(['descripcion']); return $this->redirectRoute('marcas.create'); } public function actualizarMarca() { $this->validate(); $marca = MarcaModel::findOrFail($this->id); $marca->update([ 'descripcion' => $this->descripcion, ]); session()->flash('message', 'Marca actualizada correctamente.'); } public function updated($propiedade,$valor){ $this->validateOnly($propiedade); } }
- Fixarse no método mount. En principio non é necesario poñelo xa que automaticamente vai gardar o id pasado dende a vista de Laravel á propiedade id do compoñente livewire, pero como queremos atopar a descripción, o poñemos para atopar o modelo asociado a dita id e obter a descripción.
- Agora na vista do compoñente temos que cambiar a chamada á función dependendo do modo no que estemos (e os textos do botón de alta e mensaxe confirm).
Arquivo resources/views/livewire/marca.blade.php
<div> <form wire:submit.prevent="{{$modo=="engadir" ? 'gardarMarca' : 'actualizarMarca' }}"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required wire:model.live="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div> <div> <input type="submit" name="btnAlta" value="{{$modo=='engadir' ? "Alta" : "Modificar"}}" onClick="return confirm('{{$modo=="engadir" ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form> @include('mensaxes.mensaxes') </div>
- Podedes facer a proba de que funciona poñendo na url: http://localhost:8080/marcas/1/edit (sendo 1 o id dunha marca que teñades engadida)
Listado
- O listado o imos implementar empregando un compoñente livewire.
- Ademáis imos poder empregar un campo de filtro e paxinación sen necesidade de recargar toda a páxina ao empregar o compoñente.
- Executamos a orde dende terminal: php artisan make:livewire marca-listado
- Creará:
- Class: app/Livewire/MarcaListado.php
- View: resources/views/livewire/marca-listado.blade.php
- Arquivo app/Livewire/MarcaListado.php
- Polo de agora imos amosar o listado de marcas.
<?php namespace App\Livewire; use Livewire\Component; use App\Models\Marca; use Illuminate\Support\Facades\Log; class MarcaListado extends Component { public function render() { $marcas = Marca::orderBy('descripcion', 'asc')->get(); return view('livewire.marca-listado', compact('marcas')); } }
- Arquivo resources/views/livewire/marca-listado.php
<div> <table class="table-auto w-full border-collapse border border-gray-300"> <thead> <tr> <th class="border border-gray-300 p-2">ID</th> <th class="border border-gray-300 p-2">Descripción</th> <th class="border border-gray-300 p-2">Acciones</th> </tr> </thead> <tbody> @forelse ($marcas as $marca) <tr wire:key="marca-{{ $marca->id }}"> <td class="border border-gray-300 p-2">{{ $marca->id }}</td> <td class="border border-gray-300 p-2">{{ $marca->descripcion }}</td> <td class="border border-gray-300 p-2"> <a href="{{ route('marcas.edit',['marca' => $marca->id]) }}" class="bg-blue-500 text-white px-4 py-1 rounded"> Editar </a> <button class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> </td> </tr> @empty <tr> <td colspan="3" class="border border-gray-300 p-2 text-center">No se encontraron marcas</td> </tr> @endforelse </tbody> </table> @include('mensaxes.mensaxes') </div>
NOTAS:
- Fixarse que cambiamos o botón de modificar por un enlace. Se podería empregar un botón cun formulario ou outra forma de que cargue unha url.
- Fixarse que no bucle o tr ten un wire:key. Isto é necesario sempre que teñades un bucle e empregedes:
- @foreach de elementos dinámicos
- Botóns wire:click dentro do loop
- Eliminación ou actualización de filas
- Inputs editables en lista
- Ordenaión ou filtros
- Indicamos no controlador da marca que cargue unha vista cando chamemos a index
- Arquivo app/Http/Controllers/MarcaController.php
............... public function index() { return view('marcas.marcas'); }
- Arquivo resources/views/marcas/marcas.blade.php
- Cargamos o livewire que amosa o listado. En principio non necesitamos enviarlle ningún dato xa que é o propio compoñente livewire quen obtén o listado de marcas.
@extends('plantilla'); @section('pageTitle', 'Listado de marcas') @section('content') <h2>Listado de Marcas</h2> @livewire("marca-listado") @include('mensaxes.alertas_javascript') @endsection
- Se agora probades a poñer http://localhost:8000/marcas vos amosara o listado de marcas.
Paxinación
- Máis información en: https://laravel.com/docs/10.x/pagination
- Faise de forma parecido a como se fai en Laravel cando temos unha vista.
- No controlador do compoñente livewire imos indicar que empregaremos paxinación e cando obtemos a lista de marcas faremos o paxinate.
- Arquivo app/Livewire/MarcaListado.php
<?php namespace App\Livewire; use Livewire\Component; use Livewire\WithPagination; use App\Models\Marca; use Illuminate\Support\Facades\Log; class MarcaListado extends Component { use WithPagination; public function render() { $marcas = Marca::orderBy('descripcion', 'asc')->simplePaginate(3); return view('livewire.marca-listado', compact('marcas')); } }
- Nota: Poño simplePaginate en vez de Paginate xa que por defecto engade dúas iconas para desprazarse á dereita/esqueda polas páxinas, pero ditas iconas se ven moi grandes, supoño a que por defecto emprega Tailwind CSS. Se consultades o enlace superior indica como empregar Bootstrap.
- Agora imos indicarlle á vista que visualice os enlaces de seguinte e anterior:
- Arquivo resources/views/livewire/marca-listado.blade.php
<div> <table class="table-auto w-full border-collapse border border-gray-300"> <thead> <tr> <th class="border border-gray-300 p-2">ID</th> <th class="border border-gray-300 p-2">Descripción</th> <th class="border border-gray-300 p-2">Acciones</th> </tr> </thead> <tbody> @forelse ($marcas as $marca) <tr wire:key="marca-{{ $marca->id }}"> <td class="border border-gray-300 p-2">{{ $marca->id }}</td> <td class="border border-gray-300 p-2">{{ $marca->descripcion }}</td> <td class="border border-gray-300 p-2"> <a href="{{ route('marcas.edit',['marca' => $marca->id]) }}" class="bg-blue-500 text-white px-4 py-1 rounded"> Editar </a> <button class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> </td> </tr> @empty <tr> <td colspan="3" class="border border-gray-300 p-2 text-center">No se encontraron marcas</td> </tr> @endforelse </tbody> </table> <div class="mt-4"> {{ $marcas->links() }} </div> @include('mensaxes.mensaxes') </div>
- Cada 3 marcas teredes unha páxina.
Filtro
- Para filtrar imos necesitar na vista un campo input no que imos poder poñer unha palabra e amosará as marcas que teñan dita palabra.
- Iso o fará se necesidade de cargar novamente a páxina enteira.
- Arquivo resources/views/livewire/marca-listado.blade.php
<div> <input type="text" wire:model.live="filtro" placeholder="Buscar marcas..." class="mb-4 p-2 border rounded"> <table class="table-auto w-full border-collapse border border-gray-300"> <thead> <tr> <th class="border border-gray-300 p-2">ID</th> <th class="border border-gray-300 p-2">Descripción</th> <th class="border border-gray-300 p-2">Acciones</th> </tr> </thead> ................
- Fixarse que o campo input vai estar ligado a unha propiedade de nome filtro que se atopará no controlador do livewire. Ao poñer wire:model.live facemos que cando premamos unha tecla vai actualizar dita propiedade. Se non queremos facelo ao premer na tecla teríamos que ter un botón que ao premer fixera dita o proceso de actualizar a vista.
- Arquivo app/Livewire/MarcaListado.php
<?php namespace App\Livewire; use Livewire\Component; use Livewire\WithPagination; use App\Models\Marca; use Illuminate\Support\Facades\Log; class MarcaListado extends Component { use WithPagination; public $filtro; protected $queryString = ['filtro']; // Sincronizar búsqueda con la URL public function render() { $marcas = Marca::where('descripcion', 'like', '%' . $this->filtro . '%')->orderBy('descripcion', 'asc') ->simplePaginate(5); return view('livewire.marca-listado', compact('marcas')); } }
- Nota: A liña 15 non é obrigatoria. O que fai a propiedade $queryString é actualizar a URL có contido da campo de busca no parámetro filtro. Sería necesario se queremos empregar a url para algún proceso posterior.
Filtro: Variante con botón
- Imos facer unha variante do anterior para que soamente busque cando premamos un botón.
- Para isto temos que cambiar wire.model.live por wire.model e engadir un botón.
- Arquivo resources/views/livewire/marca-listado.blade.php
<div> <input type="text" wire:model="filtro" placeholder="Buscar marcas..." class="mb-4 p-2 border rounded"> <input type="button" wire:click="$refresh" value="Buscar" /> .............
- Fixarse que o botón o que fai é chamar a un método 'refresh' que xa ten definido internamento o controlador do livewire.
- O que fai dito método e chamar ao método render do controlador.
- Por outra banda, ao empregar wire:model, o campo non se actualiza no controlador ata que alguén o chame (como fai refresh).
- NOTA: Se queremos chamar a un método do controlador o podemos facer poñendo: wire:click="nomeMetodo" e definindo dito método no controlador.
Baixa
- A baixa vai consistir en chamar a un método do controlador ao que lle mandaremos o id da marca a dar de baixa.
- Polo tanto definimos o método no controlador.
Arquivo app/Livewire/MarcaListado.php
................ public function eliminarMarca($id){ $marca = Marca::find($id); // Se poñemos findOrFail a aplicación pararía se non o atopa. if ($marca) { $marca->delete(); session()->flash('message', 'Marca eliminada correctamente.'); } else { $this->addError('error', 'Marca non atopada.'); } }
- Como vedes é moi sinxelo. Buscamos a marca baseada nese id e se a atopa a borra. Despois xa chama a render automaticamente. Se non atopa envía unha mensaxe de erro.
- Empregamos $this->addError para gardar o erro na variable 'error'.
- IMPORTANTE: Ao facer que o botón de Baixa se gestione con wire:click, se queremos pedir confirmación do borrado non podemos empregar confirm de javascripr, xa que vai realizar a llamada igual. Debemos empregar wire:confirm como está indicado no exemplo seguinte.
Arquivo resources/views/livewire/marca-listado.blade.php
<div> <input type="text" wire:model="filtro" placeholder="Buscar marcas..." class="mb-4 p-2 border rounded"> <input type="button" wire:click="$refresh" value="Buscar" /> <table class="table-auto w-full border-collapse border border-gray-300"> <thead> <tr> <th class="border border-gray-300 p-2">ID</th> <th class="border border-gray-300 p-2">Descripción</th> <th class="border border-gray-300 p-2">Acciones</th> </tr> </thead> <tbody> @forelse ($marcas as $marca) <tr> <td class="border border-gray-300 p-2">{{ $marca->id }}</td> <td class="border border-gray-300 p-2">{{ $marca->descripcion }}</td> <td class="border border-gray-300 p-2"> <a href="{{ route('marcas.edit',['marca' => $marca->id]) }}" class="bg-blue-500 text-white px-4 py-1 rounded"> Editar </a> <button wire:click="eliminarMarca({{ $marca->id }})" wire:confirm="Estás seguro de borrar la marca {{ $marca->descripcion }}? " class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> </td> </tr> @empty <tr> <td colspan="3" class="border border-gray-300 p-2 text-center">No se encontraron marcas</td> </tr> @endforelse </tbody> </table> <div class="mt-4"> {{ $marcas->links() }} </div> @include('mensaxes.mensaxes') @error('error') <div class="alert alert-danger"> {{ $message }} </div> @enderror </div>
- Comprobamos se temos un erro na variable 'error' de livewire.
Axustes finais
- Imos modificar a modificación e alta para que vain á pantalla de listado.
- Arquivo app/Livewire/Marca.php
<?php namespace App\Livewire; use Illuminate\Support\Facades\Log; use Livewire\Attributes\Validate; use App\Models\Marca as MarcaModel; use Livewire\Component; class Marca extends Component { public $descripcion = "Valor inicial"; public $id; // A empregamos na operación de modificación/baixa para atopar a marca a modificar ou borrar public $modo = "engadir"; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', ]; protected $messages = [ 'descripcion.required' => 'La descripción es obligatoria.', 'descripcion.string' => 'La descripción debe ser una cadena de texto.', 'descripcion.min' => 'La descripción debe tener al menos 5 caracteres.', 'descripcion.max' => 'La descripción no puede tener más de 45 caracteres.', ]; public function mount($id = null) { $this->id = $id; if($id) { $marca = MarcaModel::findOrFail($id); $this->descripcion = $marca->descripcion; } } public function render() { return view('livewire.marca'); } public function gardarMarca() { $this->validate(); Log::info('Pasa a validación'); $marcaCreada = MarcaModel::create([ 'descripcion' => $this->descripcion ]); $this->id = $marcaCreada->id; session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); $this->reset(['descripcion']); return $this->redirectRoute('marcas.index'); } public function actualizarMarca() { $this->validate(); $marca = MarcaModel::findOrFail($this->id); $marca->update([ 'descripcion' => $this->descripcion, ]); session()->flash('message', 'Marca actualizada correctamente.'); return $this->redirectRoute('marcas.index'); } public function updated($propiedade,$valor){ $this->validateOnly($propiedade); } }
- Engado a entrada de listar perfumes.
- Arquivo resources/plantilla.blade.php
<!DOCTYPE html> <html> <head> <title>@yield('pageTitle')</title> <style> .menu{ float: left; width: 30%; padding-left: 5px; } .menu a{ display: block; } .contido{ float: left; width: 65%; } </style> </head> <body> <div class="menu"> <a href='{{route('perfumes.index')}}'>Operacións Perfumes</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> <a href='{{route('marcas.index')}}'>Operacións Marcas</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> </div> <div class="contido"> @yield('content') </div> </body> </html>
Aclaracións varias
- Neste exercicio empregamos o controlador de Laravel para cargar o mesmo compoñente livewire na alta e modificación empregando un parámetro para informar ao compoñnente en que 'modo' se atopaba.
- Normalmente isto non é necesario.
- Pódese facer unha única vista no controlador de Laravel que cargue o compoñente livewire e xestionar dende dentro do compoñente o listado, alta, baixa e modificación.
- Dentro do compoñente podes definir unha variable pública estado que determine cando a vista ten que visualizar o listado, a alta-modificación.
- Vos queda como exercicio para facer.
- Tedes un exemplo de cómo facelo nos vídeos da plataforma a distancia.
Exercicio proposto
- Imos implementar a xestión da perfumes empregando compoñentes Livewire.
- Dexiaremos o feito na UD6 e faremos unha nova versión de xestión dos perfumes (listado, alta, baixa e modificación) empregando compoñentes Livewire.
- Para cargar os compoñentes dentro da páxina master de Laravel crearemos o cartafol resources/views/perfumes_b/ e o controlador de Laravel chamarase PerfumeBController.
- As rutas as teremos que definir nos (non podemos empregar resource xa que perfumes están definidas para empregar o controlador de Laravel).
- Empregaremos:
- perfumesB/ => PerfumeBController@index
- No listado (index) imos a poder:
- Crear un novo perfume
- Modificar un perfume
- Borrar un perfume
- Na creación/modificación do perfume imos engadir un campo novo, que vai a ser un select coa marca á que pertence o perfume.
- Imos empregar dous compoñentes Livewire:
- O encargado de listar os perfumes. Nome perfume-listar
- O encargado de engadir/modificar un perfume. Nome perfume
- No seguinte punto imos ver como podemos enviar 'mensaxes' de un compoñente a outro. Polo de agora ides amosar os dous compoñentes na mesma páxina e desta forma aparecerá o listado e os campos para dar de alta un novo perfume ao mesmo tempo. Despois solucionaremos o problema de como facer que non apareza ata que se pulse o botón de Novo Perfume e que cambie o contido/acción cando se prema o de modificar perfume. Polo de agora non fagades o código de modificar perfume. Soamente amosar o botón pero que non faga nada.
- É dicir, a páxina resources/views/perfumes_b/perfumes.blade.php terá este código:
@extends('plantilla'); @section('pageTitle', 'Listado de perfumes con Livewire') @section('content') <h2>Listado de Perfumes con Livewire</h2> @livewire("perfume-listado") @livewire("perfume") @include('mensaxes.alertas_javascript') @endsection
- E o compoñente livewire perfume amosará os campos para dar de alta un novo perfume.
- Lembrar, se non o fixéchedes antes, comprobar que a táboa perfumes ten un campo marca_id relacionada coa táboa marcas.
CREATE TABLE `perfumes` ( `id` int NOT NULL AUTO_INCREMENT, `descripcion` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL, `prezo` decimal(8,2) NOT NULL, `data_compra` date DEFAULT NULL, `marca_id` int DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_PERFUMES_1_idx` (`id`), KEY `fk_PERFUMES_MARCA_idx` (`marca_id`), CONSTRAINT `fk_perfumes_1` FOREIGN KEY (`marca_id`) REFERENCES `marcas` (`id`) ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci;
- E crear a relación en Laravel entre os dous modelos.
- Se seguides a solución proposta para Marca, podedes intentar empregar o método Modelo::updateOrCreate de Laravel que permite crear ou modificar unha fila da táboa. Podedes intentar crear un único método para as dúas operacións e non facer como en Marca, de ter un para a actualización e outro para a inserción.
Solución Exercicio proposto
Arquivo app/Models/Perfume.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; use Carbon\Carbon; class Perfume extends Model { protected $table = 'perfumes'; // Indicamos a táboa relacionada con este modelo. protected $primaryKey = 'id'; // Indicamos a clave primaria da táboa public $timestamps = false; // Por defecto laravel inclúe dous campos (created_at e updated_at) de tipo datetime cando se fai unha operación de inserción e actualización sobre unha fila. Con isto indicamos que a táboa non ten eses campos. protected $casts = [ // Cando accedamos a unha fila empregando este modelo, se poñemos $fila->data_compra teremos un string. Se poñemos o cast, obteremos unha instancia da [https://carbon.nesbot.com/docs/ clase Carbon] de Laravel que nos permite facer operacións e formatear a data. 'data_compra' => 'date', // Tamén se pode empregar datetime. Devolve un Carbon ]; protected $fillable = [ // Isto xa o veremos. O que indica é que cando facemos unha operación masiva de inserción ou un insert da forma create(datos) soamente deixa enviar en datos (que é un array) as columnas indicadas nesta propiedade (as columnas indicadas son descripcion, prezo e data_compra). Faise por motivos de seguridade. 'descripcion', 'prezo', 'data_compra', 'marca_id' ]; protected function DataCompra(): Attribute { return Attribute::make( get: fn($value) => $value ? Carbon::parse($value)->format('d/m/Y') : null, // Cando empregamos a data da forma echo($perfume->data_compra); convirte o obxecto Carbon a unha cadea có formato correcto set: fn($value) => $value ? Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d') : null // Cando empregamos a data da forma: $perfume->data_compra = "10/01/2025" o gardará ); } protected function Prezo(): Attribute { return Attribute::make( // Cuando accedemos al valor del campo, convertimos el punto en coma get: fn ($value) => number_format($value, 2, ',', '.'), // Cuando se asigna el valor, reemplazamos la coma por un punto set: fn ($value) => str_replace(',', '.', $value), ); } public function marca() { return $this->belongsTo(Marca::class); } }
Arquivo routes.php
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\PerfumeController; use App\Http\Controllers\MarcaController; use App\Http\Controllers\PerfumeBController; Route::resource('perfumes', PerfumeController::class); Route::resource('marcas', MarcaController::class); Route::get('perfumesB', [PerfumeBController::class, 'index'])->name('perfumesB.index');
Arquivo app/Htpp/Controllers/PerfumeBController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class PerfumeBController extends Controller { public function index() { return view('perfumes_b.perfumes'); } }
Arquivo resources/views/perfumes_b/perfumes.blade.php
@extends('plantilla'); @section('pageTitle', 'Listado de perfumes con Livewire') @section('content') <h2>Listado de Perfumes con Livewire</h2> @livewire("perfume-listado") @livewire("perfume") @include('mensaxes.alertas_javascript') @endsection
Arquivo app/Livewire/PerfumeListado.php
<?php namespace App\Livewire; use Livewire\Component; use App\Models\Perfume; class PerfumeListado extends Component { public $perfumes; public function cargarPerfumes() { $this->perfumes = Perfume::with('marca')->get(); } public function mount() { $this->cargarPerfumes(); } public function eliminarPerfume($id) { $perfume = Perfume::find($id); if (!$perfume) { $this->addError('error', 'Perfume non atopado.'); return; } $perfume->delete(); session()->flash('message', 'Perfume eliminado correctamente.'); $this->cargarPerfumes(); } public function render() { return view('livewire.perfume-listado'); } }
Arquivo resources/views/livewire/perfume-listado.blade.php
<div> <button class="bg-green-500 text-white px-2 py-1 rounded mb-2">Novo perfume</button> <table class="table-auto w-full border"> <thead> <tr> <th>Descrición</th> <th>Prezo</th> <th>Data Compra</th> <th>Marca</th> <th>Accións</th> </tr> </thead> <tbody> @foreach($perfumes as $perfume) <tr> <td>{{ $perfume->descripcion }}</td> <td>{{ $perfume->prezo }}</td> <td>{{ $perfume->data_compra }}</td> <td>{{ $perfume->marca->descripcion ?? 'Sen marca' }}</td> <td> <button class="bg-blue-500 text-white px-2 py-1 rounded">Editar</button> <button wire:click="eliminarPerfume({{ $perfume->id }})" wire:confirm="Estás seguro de borrar la marca {{ $perfume->descripcion }}? " class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> </td> </tr> @endforeach </tbody> </table> @include('mensaxes.mensaxes') </div>
Arquivo app/Livewire/Perfume.php
<?php namespace App\Livewire; use Livewire\Component; use App\Models\Perfume as PerfumeModel; use App\Models\Marca; class Perfume extends Component { public $id, $descripcion, $prezo, $data_compra, $marca_id; public $modo = 'engadir'; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', 'prezo' => 'required|numeric|min:0|max:999999.99', 'data_compra' => 'nullable|date|after_or_equal:today', 'marca_id' => 'nullable|exists:marcas,id', ]; public function mount($id = null) { $this->id = $id; if ($id) { $perfume = PerfumeModel::findOrFail($id); $this->descripcion = $perfume->descripcion; $this->prezo = $perfume->prezo; $this->data_compra = $perfume->data_compra; $this->marca_id = $perfume->marca_id; } } public function gardarPerfume() { $this->validate(); if($this->modo=='engadir') { $this->id = -1; } // Na versión anterior de xestión de perfumes, temos posto no modelo un set para pasar automaticamente da data en formato español a data en formato inglés. // Revisade o código e podedes ver que temos un cast con Date e un set para pasar a data que ven en formato español da caixa de texto a inglés que se vai gardar na base de datos mysql. // Nesta versión a data vaise recoller cun input de tipo 'date' que xa ven en formato inglés polo que daría un erro. Por iso a pasamos antes a formato español $this->data_compra = \Carbon\Carbon::createFromFormat('Y-m-d', $this->data_compra)->format('d/m/Y'); // Crear o actualizar el perfume $perfumeCreado = PerfumeModel::updateOrCreate( ['id' => $this->id], // Si existe un ID, lo actualiza. Si no, lo crea. [ 'descripcion' => $this->descripcion, 'prezo' => $this->prezo, 'data_compra' => $this->data_compra, 'marca_id' => $this->marca_id ] ); // Si se ha creado un nuevo perfume, asignar el ID if (!$this->id) { $this->id = $perfumeCreado->id; } // Mensaje de éxito session()->flash('message', "Perfume guardado correctamente con id $perfumeCreado->id"); // Resetear los campos $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); // Redirigir a la lista de perfumes return redirect()->route('perfumesB.index'); } public function render() { $marcas = Marca::orderBy('descripcion')->get(); return view('livewire.perfume',compact('marcas')); } }
Arquivo resources/views/livewire/perfume.blade.php
<div class="p-4 border rounded bg-gray-100"> <form wire:submit.prevent="gardarPerfume"> <h2 class="text-lg font-bold mb-2">{{ $modo == 'engadir' ? 'Novo Perfume' : 'Editar Perfume' }}</h2> <input type="text" wire:model="descripcion" placeholder="Descrición" class="block mb-2 w-full"> @error('descripcion') <span class="error">{{ $message }}</span><br /> @enderror <input type="number" step="0.01" wire:model="prezo" placeholder="Prezo" class="block mb-2 w-full"> @error('prezo') <span class="error">{{ $message }}</span><br /> @enderror <input type="date" wire:model="data_compra" class="block mb-2 w-full"> @error('data_compra') <span class="error">{{ $message }}</span><br /> @enderror <select wire:model="marca_id" class="block mb-2 w-full"> <option value="">-- Selecciona unha marca --</option> @foreach ($marcas as $marca) <option value="{{ $marca->id }}">{{ $marca->descripcion }}</option> @endforeach </select> @error('marca_id') <span class="error">{{ $message }}</span><br /> @enderror <input type="submit" name="btnAlta" value="{{ $modo == 'engadir' ? 'Alta' : 'Modificar' }}" onClick="return confirm('{{ $modo == 'engadir' ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');" /> <input type="reset" name="btnBorrar" value="Limpar" /> </form> @include('mensaxes.mensaxes') </div>
Arquivo resources/views/plantilla.blade.php
<!DOCTYPE html> <html> <head> <title>@yield('pageTitle')</title> <style> .menu{ float: left; width: 30%; padding-left: 5px; } .menu a{ display: block; } .contido{ float: left; width: 65%; } </style> </head> <body> <div class="menu"> <a href='{{route('perfumes.index')}}'>Operacións Perfumes</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> <a href='{{route('perfumesB.index')}}'>Operacións Perfumes Livewire</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> <a href='{{route('marcas.index')}}'>Operacións Marcas</a> <!-- Se podería poñer /perfumes/listar pero mellor empregar os nomes definidos no arquivo de routes.php--> </div> <div class="contido"> @yield('content') </div> </body> </html>
Arquivo app/Models/Perfume.php
Eventos entre compoñentes
- Máis información en https://livewire.laravel.com/docs/events
- Imos seguir co exemplo anterior de perfumes e imos solucionar as funcionalidades que nos quedan.
- Os eventos poden ir dende un compoñente livewire a outro ou dende un compoñente livewire a un script dunha plantilla blade.
- Imos aplicar isto á nosa pantalla de Perfumes.
- Se vos fixades, cando creamos un novo perfume no compoñente livewire Perfume, recargamos a páxina:
Arquivo app/Livewire/Perfume.php
............ public function gardarPerfume() { $this->validate(); ............ return redirect()->route('perfumesB.index'); }
- O que imos facer é enviar un evento a todos os compoñentes livewire que estean cargados na páxina e que estén escoitando dito evento.
- No noso caso:
Arquivo app/Livewire/Perfume.php
............ public function gardarPerfume() { $this->validate(); ............ $this->dispatch('refrescar-listado-perfumes'); }
- O nome pode ser calquera. Agora temos que facer que o compoñente livewire de listado escoite dito evento e asocialo a un método.
Arquivo app/Livewire/PerfumeListado.php
<?php namespace App\Livewire; use Livewire\Component; use Livewire\Attributes\On; use App\Models\Perfume; class PerfumeListado extends Component { ................. #[On('refrescar-listado-perfumes')] public function refrescarListado(){ $this->cargarPerfumes(); }
- O nome da liña On debe ser o mesmo que empregamos cando fixemos o dispatch.
- Agora se probades a dar de alta un novo perfume se actualizará o listado pero no se carga a páxina novamente.
- Agora imos a facer que cando premamos no botón de alta fagamos unha alta no compoñente livewire de perfume e o mesmo na modificación.
- Para iso necesitamos crear un método no controlador de PerfumeListado que sexa chamado polos botóns de alta e modificar enviando no caso de modificar o id do perfume.
- Despois o que fará este método é lanzar un evento para que sexa capturado polo compoñente livewire Perfume e modificar a propiedade modo e cargar os datos do perfume en caso de modificación.
Arquivo resources/views/livewire/perfume-listado.blade.php
<div> <button wire:click="operacionPerfume" class="bg-green-500 text-white px-2 py-1 rounded mb-2">Novo perfume</button> <table class="table-auto w-full border"> <thead> <tr> <th>Descrición</th> <th>Prezo</th> <th>Data Compra</th> <th>Marca</th> <th>Accións</th> </tr> </thead> <tbody> @foreach($perfumes as $perfume) <tr> <td>{{ $perfume->descripcion }}</td> <td>{{ $perfume->prezo }}</td> <td>{{ $perfume->data_compra }}</td> <td>{{ $perfume->marca->descripcion ?? 'Sen marca' }}</td> <td> <button wire:click="operacionPerfume({{$perfume->id}})" class="bg-blue-500 text-white px-2 py-1 rounded">Editar</button> <button wire:click="eliminarPerfume({{ $perfume->id }})" wire:confirm="Estás seguro de borrar la marca {{ $perfume->descripcion }}? " class="bg-red-500 text-white px-4 py-1 rounded">Eliminar</button> </td> </tr> @endforeach </tbody> </table> @include('mensaxes.mensaxes') </div>
Arquivo app/Livewire/PerfumeListado.php
<?php namespace App\Livewire; use Livewire\Component; use Livewire\Attributes\On; use Illuminate\Support\Facades\Log; use App\Models\Perfume; class PerfumeListado extends Component { public $perfumes; public function cargarPerfumes() { $this->perfumes = Perfume::with('marca')->get(); } public function mount() { $this->cargarPerfumes(); } public function operacionPerfume($id=null){ $this->dispatch('operar-perfume',id:$id); // Chamamos ao método e enviamos o id do perfume. null => alta, numero => modificar } #[On('refrescar-listado-perfumes')] public function refrescarListado(){ $this->cargarPerfumes(); } public function eliminarPerfume($id) { $perfume = Perfume::find($id); if (!$perfume) { $this->addError('error', 'Perfume non atopado.'); return; } $perfume->delete(); session()->flash('message', 'Perfume eliminado correctamente.'); $this->cargarPerfumes(); } public function render() { return view('livewire.perfume-listado'); } }
Arquivo app/Livewire/Perfume.php
<?php namespace App\Livewire; use Livewire\Attributes\On; use Livewire\Component; use App\Models\Perfume as PerfumeModel; use App\Models\Marca; class Perfume extends Component { public $id, $descripcion, $prezo, $data_compra, $marca_id; public $modo = 'engadir'; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', 'prezo' => 'required|numeric|min:0|max:999999.99', 'data_compra' => 'nullable|date|after_or_equal:today', 'marca_id' => 'nullable|exists:marcas,id', ]; public function mount($id = null) { $this->id = $id; if ($id) { $perfume = PerfumeModel::findOrFail($id); $this->descripcion = $perfume->descripcion; $this->prezo = $perfume->prezo; $this->data_compra = $perfume->data_compra; $this->marca_id = $perfume->marca_id; } } #[On('operar-perfume')] public function operarPerfume($id){ if($id==null){ $this->modo = "engadir"; $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); $this->id = -1; } else{ $this->id = $id; $perfume = PerfumeModel::findOrFail($id); $this->descripcion = $perfume->descripcion; $this->prezo = str_replace(',','.',$perfume->prezo); // Prezo ten un mutator para poñer coma en vez de punto if($this->data_compra){ $this->data_compra = \Carbon\Carbon::createFromFormat('d/m/Y', $perfume->data_compra)->format('Y-m-d'); } $this->marca_id = $perfume->marca_id; $this->modo ="actualizar"; } } public function gardarPerfume() { $this->validate(); // Na versión anterior de xestión de perfumes, temos posto no modelo un set para pasar automaticamente da data en formato español a data en formato inglés. // Nesta versión a data xa ven en formato inglés polo que daría un erro. Por iso a pasamos antes a formato español if($this->data_compra) { $this->data_compra = \Carbon\Carbon::createFromFormat('Y-m-d', $this->data_compra)->format('d/m/Y'); } // Crear o actualizar el perfume $perfumeCreado = PerfumeModel::updateOrCreate( ['id' => $this->id], // Si existe un ID, lo actualiza. Si no, lo crea. [ 'descripcion' => $this->descripcion, 'prezo' => $this->prezo, 'data_compra' => $this->data_compra, 'marca_id' => $this->marca_id ] ); // Si se ha creado un nuevo perfume, asignar el ID if (!$this->id) { $this->id = $perfumeCreado->id; } // Mensaje de éxito session()->flash('message', "Perfume guardado correctamente con id $perfumeCreado->id"); // Resetear los campos $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); // Redirigir a la lista de perfumes //return redirect()->route('perfumesB.index'); $this->dispatch('refrescar-listado-perfumes'); } public function render() { $marcas = Marca::orderBy('descripcion')->get(); return view('livewire.perfume',compact('marcas')); } }
Arquivo resources/views/livewire/perfume.blade.php
<div class="p-4 border rounded bg-gray-100"> <form wire:submit.prevent="gardarPerfume"> <h2 class="text-lg font-bold mb-2">{{ $modo == 'engadir' ? 'Novo Perfume' : 'Editar Perfume' }}</h2> <input type="text" wire:model="descripcion" placeholder="Descrición" class="block mb-2 w-full"> @error('descripcion') <span class="error">{{ $message }}</span><br /> @enderror <input type="number" step="0.01" wire:model="prezo" placeholder="Prezo" class="block mb-2 w-full"> @error('prezo') <span class="error">{{ $message }}</span><br /> @enderror <input type="date" wire:model="data_compra" class="block mb-2 w-full"> @error('data_compra') <span class="error">{{ $message }}</span><br /> @enderror <select wire:model="marca_id" class="block mb-2 w-full"> <option value="">-- Selecciona unha marca --</option> @foreach ($marcas as $marca) <option value="{{ $marca->id }}">{{ $marca->descripcion }}</option> @endforeach </select> @error('marca_id') <span class="error">{{ $message }}</span><br /> @enderror <input type="submit" name="btnAlta" value="{{ $modo == 'engadir' ? 'Alta' : 'Modificar' }}" onClick="return confirm('{{ $modo == 'engadir' ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');" /> <input type="reset" name="btnBorrar" value="Limpar" /> </form> @include('mensaxes.mensaxes') </div>
- Agora soamente queda por facer que a alta/modificación sexa invisible ata que se pulse o botón correspondente.
- Iso o podemos facer na vista mirando que o modo sexa engadir/actualizar en en caso de que non o sexa, non amosar nada (algo hai que amosar, polo que envolvemos todo con un div)
Arquivo resources/views/livewire/perfume.blade.php
<div> @if($modo=='engadir' || $modo=='actualizar') <div class="p-4 border rounded bg-gray-100"> <form wire:submit.prevent="gardarPerfume"> <h2 class="text-lg font-bold mb-2">{{ $modo == 'engadir' ? 'Novo Perfume' : 'Editar Perfume' }}</h2> <input type="text" wire:model="descripcion" placeholder="Descrición" class="block mb-2 w-full"> @error('descripcion') <span class="error">{{ $message }}</span><br /> @enderror <input type="number" step="0.01" wire:model="prezo" placeholder="Prezo" class="block mb-2 w-full"> @error('prezo') <span class="error">{{ $message }}</span><br /> @enderror <input type="date" wire:model="data_compra" class="block mb-2 w-full"> @error('data_compra') <span class="error">{{ $message }}</span><br /> @enderror <select wire:model="marca_id" class="block mb-2 w-full"> <option value="">-- Selecciona unha marca --</option> @foreach ($marcas as $marca) <option value="{{ $marca->id }}">{{ $marca->descripcion }}</option> @endforeach </select> @error('marca_id') <span class="error">{{ $message }}</span><br /> @enderror <input type="submit" name="btnAlta" value="{{ $modo == 'engadir' ? 'Alta' : 'Modificar' }}" onClick="return confirm('{{ $modo == 'engadir' ? 'Estás seguro de dar de alta a nova marca' : "Estás seguro de dar modificar a marca $descripcion ?" }}');" /> <input type="reset" name="btnBorrar" value="Limpar" /> </form> @include('mensaxes.mensaxes') </div> @endif </div>
- Agora no controlador inicializamos o modo a "" e o cambiamos en función da operación a realizar.
Arquivo app/Livewire/Perfume.php
<?php namespace App\Livewire; use Livewire\Attributes\On; use Livewire\Component; use App\Models\Perfume as PerfumeModel; use App\Models\Marca; class Perfume extends Component { public $id, $descripcion, $prezo, $data_compra, $marca_id; public $modo=""; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', 'prezo' => 'required|numeric|min:0|max:999999.99', 'data_compra' => 'nullable|date|after_or_equal:today', 'marca_id' => 'nullable|exists:marcas,id', ]; public function mount($id = null) { $this->id = $id; if ($id) { $perfume = PerfumeModel::findOrFail($id); $this->descripcion = $perfume->descripcion; $this->prezo = $perfume->prezo; $this->data_compra = $perfume->data_compra; $this->marca_id = $perfume->marca_id; } } #[On('operar-perfume')] public function operarPerfume($id){ if($id==null){ $this->modo = "engadir"; $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); $this->id = -1; } else{ $this->id = $id; $perfume = PerfumeModel::findOrFail($id); $this->descripcion = $perfume->descripcion; $this->prezo = str_replace(',','.',$perfume->prezo); // Prezo ten un mutator para poñer coma en vez de punto if($this->data_compra){ $this->data_compra = \Carbon\Carbon::createFromFormat('d/m/Y', $perfume->data_compra)->format('Y-m-d'); } $this->marca_id = $perfume->marca_id; $this->modo ="actualizar"; } } public function gardarPerfume() { $this->validate(); // Na versión anterior de xestión de perfumes, temos posto no modelo un set para pasar automaticamente da data en formato español a data en formato inglés. // Nesta versión a data xa ven en formato inglés polo que daría un erro. Por iso a pasamos antes a formato español if($this->data_compra) { $this->data_compra = \Carbon\Carbon::createFromFormat('Y-m-d', $this->data_compra)->format('d/m/Y'); } // Crear o actualizar el perfume $perfumeCreado = PerfumeModel::updateOrCreate( ['id' => $this->id], // Si existe un ID, lo actualiza. Si no, lo crea. [ 'descripcion' => $this->descripcion, 'prezo' => $this->prezo, 'data_compra' => $this->data_compra, 'marca_id' => $this->marca_id ] ); // Si se ha creado un nuevo perfume, asignar el ID if (!$this->id) { $this->id = $perfumeCreado->id; } // Mensaje de éxito session()->flash('message', "Perfume guardado correctamente con id $perfumeCreado->id"); // Resetear los campos $this->reset(['descripcion', 'prezo', 'data_compra', 'marca_id']); $this->modo=""; // Redirigir a la lista de perfumes //return redirect()->route('perfumesB.index'); $this->dispatch('refrescar-listado-perfumes'); } public function render() { $marcas = Marca::orderBy('descripcion')->get(); return view('livewire.perfume',compact('marcas')); } }
- Neste caso non fixo falta, pero cando queremos refrescar o compoñente perfumes-listado, pode ser que en vez de gardar os datos nunha propiedade local ($perfumes), podemos envialos localmente no render, da forma:
public function render() { $perfumes = Perfume::with('marca')->get(); return view('livewire.perfume-listado',compact('perfumes'); }
- NOTA IMPORTANTE: Para que funcione desta forma tedes que eliminar a propiedade $perfumes do compoñente e xa non necesitades o mount nin o método cargarPerfumes.
- Nese caso, se queremos 'refrescar' o compoñente para que volva a facer o render, teríamos que poñer: this->dispatch('render') no método 'refrescar-perfumes'.
#[On('refrescar-listado-perfumes')] public function refrescarListado(){ $this->dispatch('refrescar'); }
-- Ángel D. Fernández González -- (2025).