Para obtener una colección con los modelos que deseamos usaremos
el trait CanReadModels en el componente de livewire.
Debemos definir una variable en la clase llamada model y le asignaremos
como valor el modelo del cual queremos ver registros.
use DevCucei\RocketmanComponents\Traits\CanReadModels;
use Livewire\Component;
use Spatie\Permission\Models\Role;
class Roles extends Component
{
use CanReadModels;
public $model = Role::class;
public function render()
{
return view('livewire.desarrollo.roles');
}
}
Con eso, en nuestra vista de blade tendremos disponible una variable con el nombre en plural, tomando en cuenta el nombre de la tabla del modelo.
En caso de necesitar cambiar el nombre de la colección, tienes que definir
una variable llamada collectionName y asignarle el nombre requerido.
public $collectionName = "myCollection";
En caso de necesitar el nombre de la colección, este se puede obtener con:
$this->collectionName();
Como ya se mencionó, para acceder a la función podemos hacerlo mediante su nombre, pero también es posible con el método:
$this->getCollection();
No será habitual, pero si necesita modificar la colección eso es posible con el metodo:
$this->setCollection($collection);
Cuando necesita hacer una consulta específica de los modelos o quiere cambiar su comportamiento, tiene que declarar la siguiente función dentro de su componente y aplicar todas las consultas que ocupe.
public function applyQueries(Builder $query)
{
$query->with('roles');
}
En ella puede filtrar resultados, ordenar o cargar relaciones en los modelos.
Para usar esta función desde otro trait, será necesario que le añada el nombre del trait de la siguiente manera para evitar conflictos de nombres.
public function applyQueriesMyTraitName(Builder $query)
{
$query->orderBy('name');
}
Por defecto, el trait termina la consulta con un get(), pero puede
cambiar este comportamiento declarando el siguiente método:
public function fetchWith(Builder $query)
{
return $query->paginate(15);
}
En este, puede hacer todo lo que el query builder le permite como añadir más consultas y hacer el fetch de los modelos como usted requiera. Tiene que asegurarse de devolver el resultado.
Cuando requiera representar de forma visual sus modelos en una tabla, puede hacer uso
del trait WithTableView.
Este trait necesita que le suministremos de una colección de modelos. Para ello tenemos
que definir un método llamado getCollection():
public function getCollection()
{
return Role::all();
}
Si ya tenemos en uso el trait CanReadModels no hace falta definir el método, pues
este ya lo hace por nosotros.
Tenemos que indicar las columnas de nuestra tabla e indicar su contenido para cada modelo
que se le pase. Para ello, tenemos que definir un método llamado columns() que devuelva
un array.
use DevCucei\RocketmanComponents\Traits\WithTableView;
use DevCucei\RocketmanComponents\Classes\Tables\Column;
use Livewire\Component;
class MiTabla extends Component
{
use WithTableView;
public function columns()
{
return [
// Columnas
];
}
}
Por cada columna, debemos agregar un objeto de tipo Column al array.
Existen diferentes formas de hacer que muestren el contenido que queramos.
Para generar el tipo de columna más básico, debemos por lo menos indicarle el
nombre a la función name. Esta función nos devolverá una instancia de la
clase Column.
Column::name('Columna vacia')
A una columna le podemos indicar que muestre un cierto atributo para cada modelo
que se le pase. Esto se puede hacer de dos formas. La primera es pasándole una cadena
a content que haga referencia al nombre del atributo del modelo que queremos
mostrar.
Column::name('Nombre')->content('name')
La opción anterior tiene la limitante de que solamente muestra el atributo tal cual
lo devuelve el modelo. Si queremos más libertad entonces lo haremos de la segunda
manera. Tenemos que pasarle a content una Closure, es decir,
una función que devuelva el contenido que queremos mostrar.
El ejemplo siguiente hace exactamente lo mismo que el ejemplo anterior:
Column::name('Nombre')->content( function ($user) {
return $user->name
})
También se pueden hacer uso de las funciones flecha para hacer más limpio el código:
Column::name('Nombre')->content(fn ($user) =>
strtoupper($user->name)
)
Note que la función anterior transforma el nombre del usuario a mayúsculas y lo devuelve, mostrando una de las ventajas de este método.
Puede que necesitemos poner el mismo contenido para todas las filas de una columna. Para ello, y aplicando lo anterior, podemos hacer lo siguiente.
Column::name('Relleno')->content(fn () =>
"Este texto se repetira en todas las filas"
)
También podemos hacer que una columna muestre una vista o componente de blade para cada modelo. Esto se puede lograr de la siguiente manera:
Column::name('Permisos')->content(fn ($rol) =>
view('roles.listado', [
'permisos' => $rol->permisos,
])
)
La columna anterior devuelve una vista del archivo listado.blade.php que se encuentra en la carpeta roles y le pasa como atributos permisos que los extrae del modelo.
Si requiere mostrar / ocultar una cierta columna solo si se cumple una condición,
debe pasarle al método show dicha condición.
Column::name('Eliminar')
->show($this->user->hasPermissionTo('delete article'))
->content(fn ($articulo) =>
view('articles.delete-button', [
'article' => $articulo,
])
)
Esta función es muy util cuando a ciertos usuarios les quieres permitir ver o no ciertas columnas.
Ya que se definieron las columnas, podemos mostrarlas en cualquier parte del componente. Para
ello solo hace falta que llames al método table.
<div>
{{ $this->table() }}
</div>
La tabla es altamente personalizable. Para darle estilos, tendremos que pasarle un array al
método table:
<div>
{{ $this->table([
// Styles
]) }}
</div>
Es muy sencillo cambiar la el estilo de cualquier tag, solo tendremos que poner el nombre del tag como llave y su valor será un array de estilos que se aplicarán.
'table' => [
'table table-hover ',
]
Los posibles tags a cambiar son:
A veces queremos que se apliquen estilos solamente a una columna en especial.
Para indicar estilos a la cabecera de una columna, ponemos 'th.' antes del nombre de una columna.
El siguiente ejemplo aplica el estilo text-center al header de la columna Roles.
'th.Roles' => [
'text-center'
],
Si queremos aplicar estilos a las celdas de solo una columna hacemos algo similar, solo que debemos poner de prefijo 'td.'.
'td.Acciones' => [
'text-center',
'p-5' => $padding
],
Notar como en el ejemplo anterior se indica que se aplica el estilo p-5 si la variable
$padding se evalua en true.
También se puede hacer que a ciertas filas se les apliquen estilos dependiedo si se cumple alguna
condición con el modelo. Para eso usamos tr (el mismo que se usa para el tag), solo que esta vez los estilos se asociarán
a una Closure la cual debe recibir el modelo y devolver un valor booleano.
'tr' => [
'text-danger' => fn($m) => $m->id === 1,
],
El ejemplo anterior aplica el estilo text-danger a aquellas filas que tengan id igual a 1.
Para darle al usuario la posibilidad de que filtre los resultados usamos el trait
WithFilters en nuestro componente.
Tenemos que definir un método llamado filters que devuelva un array de filtros.
use DevCucei\RocketmanComponents\Traits\Views\WithFilters;
use DevCucei\RocketmanComponents\Classes\Filters\SelectFilter;
use Livewire\Component;
class Filtrando extends Component
{
use WithFilters;
public function filters()
{
return [
// Filtros
];
}
}
Puede haber diferentes tipos de filtros y cada uno se puede configurar de diferente manera. Pero todos deben comenzar por especificar el nombre de la siguiente manera:
Filter::name('Mi filtro')
Es aquel que muestra un listado de opciones y el usuario puede seleccionar
una sola. La clase es SelectFilter.
Para obtener un filtro util, al menos
hay que especificarle el listado de opciones con el método options.
Este puede recibir cualquier objeto. Si se le pasa un objeto que no sea de tipo array o Collection entonces tendremos una lista de dos opciones, una para el valor por defecto (el cual es Todos) y otro para el objeto que hemos puesto.
En el siguiente ejemplo el usuario veria un filtro de nombre Mostrar con dos opciones: Todos y Ninguno.
SelectFilter::name('Mostrar')->options('Ninguno')
Por lo general se necesitan mostrar varias opciones, en esos casos es mejor pasarle un array o Collection. La estructura del array debe ser la siguiente:
El siguiente ejemplo muestra un filtro llamado Tamaño y contiene 4 opciones: Grande, Mediano y Pequeño.
SelectFilter::name('Tamaño')->options([
'Grande', 'Mediano', 'Pequeño'
])
Si se pasa un arreglo no asociativo, se generaran identificadores automaticamente. La lista de opciones que genera el filtro anterior sería algo así:
[
0 => 'Todos',
1 => 'Grande',
2 => 'Mediano',
3 => 'Pequeño'
]
Si queremos tener control sobre cuáles son las keys entonces debemos asociarlas con su valor:
SelectFilter::name('Estado civil')->options([
'c' => 'Casado', 's' => 'Soltero'
])
Cuando queremos mostrar opciones que se encuentran en la base de datos, podemos
hacer uso del método pluck que se encuentra disponible en cualquer modelo de Elocuent.
SelectFilter::name('Permiso')
->options(Permission::pluck('name', 'id'))
])
Lo que hace pluck en este caso es consultar en la tabla del modelo solo los datos
name y id para después colocarlos en una Collection donde los ids de los modelos
están en las keys y los nombres estarán en los values.
Ahora, para cambiar los valores de la opción por defecto usamos el método default
El primer parametro value cambia el valor y el segúndo key cambia el identificador.
Se pueden pasar ambos a la vez o solo el que se requiera cambiar.
SelectFilter::name('Color')->default('Cualquiera')
->options(['Amarillo', 'Verde', 'Rosa'])
])
SelectFilter::name('Estado')->default(key: "TD")
->options(['SE' => 'Sin empezar', 'P' => 'Pendiente', 'T' => 'Terminado'])
])
Por último, todos los ejemplos que se han visto no funcionarian como deberían pues
falta definir algo muy importante: cómo filtrar los modelos. Para eso
utilizaremos el método scope y se puede hacer de dos formas.
La primera es pasando el nombre de la scope del modelo al que se le aplicaran los filtros.
Supongamos que tenemos el modelo Alumno que se relaciona con Programa en una relación de muchos a 1 y queremos filtrar los alumnos por programa. El filtro quedaría:
SelectFilter::name('Programa')
->options(Programa::pluck('nombre', 'id'))
->scope('dondeProgramaEs')
Y para que funcione en el modelo Alumno debe existir la siguiente scope:
public function scopeDondeProgramaEs(Builder $query, $programa_id)
{
$query->where('programa_id', $programa_id);
}
La segunda forma de definir la scope es pasando una Closure a scope.
Esta opción es útil cuando no podemos agregar un scope al modelo o porque
se nos hace más fácil de esta manera.
El equivalente del ejemplo anterior utilizando esta forma sería:
SelectFilter::name('Programa')
->options(Programa::pluck('nombre', 'id'))
->scope(function ($query, $programa_id) {
$query->where('programa_id', $programa_id);
})
Y de esta manera no haria falta definir ninguna scope dentro del modelo Alumno.
Para mostrar los filtros en tu vista, solo hace falta que llames al método filter.
<div>
{{ $this->filter() }}
</div>
Para aplicar los filtros no es necesario hacer nada si también estás usando el trait
CanReadModels, solo se encargará de aplicar las queries antes de que haga la consulta
de la colección.
Pero si no lo estás usando, puedes hacer uso del método applyFilters desde cualquier
parte de la clase. Este recibe como argumento la query a la cual le quieras aplicar
los filtros.
public function render()
{
$roles = Role::query();
$this->applyFilters($roles);
return view('livewire.roles.listado', ['roles' => $roles->get()]);
}
Si quisiéramos crear nuestros propios tipos de filtros para tener más posibilidades
es muy simple. Solo tenemos que heredar de la clase AbstractFilter que define
la interfaz que debe tener un filtro y guía define los métodos necesarios a implementar.
Una de las cosas a considerar son:
name que te devolverá una instancia
de tu filtro.parent::__construct($name);public function options(mixed $list): static
{
// Stuff
return $this;
}Puedes basarte en el filtro SelectFilter para crear tus filtros.
Es recurrente necesitar habilitar la búsqueda de registros según la entrada de un usuario, eso también se facilita con este paquete.
Antes de habilitar la búsqueda de modelos en tu componente, es necesario
crear una scope en el modelo que se encargue de filtrar la consulta de
acuerdo al término que se le pase.
Eres libre de implementar dicha scope como mejor convenga, pero de igual forma esta disponible un trait para modelos que te permitirá generar esta scope de manera mas sencilla y rápida.
El trait se llama Searchable y tienes que implementarlo en el modelo
que quieres que sea buscable.
use DevCucei\RocketmanComponents\Traits\Models\Searchable;
use Illuminate\Database\Eloquent\Model;
class Alumno extends Model
{
use HasFactory;
use Searchable;
// stuff
public $search = [
// Columnas y relaciones buscables
];
}
De esta manera, ya estará disponible una scope llamada search.
Para que tu scope filtre los modelos por las columnas y relaciones que quieres,
debes definir una variable llamada search de tipo array en el modelo. Cada elemento del
arreglo indicará por cual columna se debe buscar.
Ejemplo, si nuestro modelo se llama alumno y definimos la siguiente variable, entonces el scope buscaría en la tabla alumnos las columnas: nombres, _apellidopaterno, _apellidomaterno y matricula.
public $search = [
'nombres',
'apellido_paterno',
'apellido_materno',
'matricula'
];
Pero a veces no solo queremos buscar sobre las columnas de la tabla del modelo, sino que también queremos buscar en las columnas de las tablas de las relaciones del modelo. Eso se consigue asociando el nombre de la relación en conjunto con las columnas que queremos buscar dentro de su tabla.
Siguiendo el ejemplo anterior, si quicieramos buscar también por el correo y alumno tuviera una relación con user, se lograria de la siguiente manera:
public $search = [
'nombres',
'apellido_paterno',
'apellido_materno',
'matricula'
'user' => [
'email'
]
];
Cuando especificas las columnas de una relación, funciona igual como si definieras para el modelo mismo, así que puedes incluso especificar relaciones dentro de relaciones.
Este trait permite hacer full-text search, que viene a ser una busqueda que permite dividir una oración en palabras y generar una busqueda por cada una.
Por defecto no se hace una full-text search para evitar que las consutas se hagan pesadas, pero puedes cambiar este comportamiento publicando los archivos de configuración y cambiandolo a tus necesidades.
php artisan vendor:publish --provider="DevCucei\RocketmanComponents\RocketmanComponentsServiceProvider" --tag="config"
De igual forma, siempre puedes sobreescribir este comportamiento para un modelo
declarando una variable llamada fullTextSearch.
use DevCucei\RocketmanComponents\Traits\Models\Searchable;
use Illuminate\Database\Eloquent\Model;
class Materia extends Model
{
use HasFactory;
use Searchable;
protected $fullTextSearch = true;
public $search = [
'clave',
'nombre',
'maestros' => [
'nombre'
]
];
}
También puedes sobre escribir las variables:
searchWordsLimit. Define la cantidad de palabras maximas en
las que se puede dividir una oración (Default: 5).isPartialMatch. Indica si se debe hacer una busqueda parcial de las
palabras. Por default esta en true entonces no es necesario que la
palabra sea exacta.Una vez que tienes la scope puedes implementar en tu vista el trait WithSearch.
use DevCucei\RocketmanComponents\Traits\Views\CanReadModels;
use DevCucei\RocketmanComponents\Traits\Views\WithSearch;
use Livewire\Component;
class ListadoAlumnos extends Component
{
use CanReadModels;
use WithSearch;
public function render()
{
return view('livewire.alumnos.listado-alumnos');
}
}
Como puedes ver, el ejemplo anterior esta haciendo uso del trait CanReadModels y solo se
encargará de filtrar, pero también es posible evitarlo y aplicarle la busqueda manualmente a la query
que queramos con el método applyQueriesWithSearch el cual recibe la query.
use DevCucei\RocketmanComponents\Traits\Views\WithSearch;
use Livewire\Component;
class ListadoAlumnos extends Component
{
use WithSearch;
public function render()
{
$alumnos = Alumno::query();
$alumnos = $this->applyQueriesWithSearch($alumnos)->get();
return view('livewire.alumnos.listado-alumnos', compact('alumnos'));
}
}
Para que el usuario puede ingresar el termino de busqueda, desde la vista llama el
método searchBar el cual generará la barra de busqueda.
<div class="pt-4 pr-4">
{{ $this->searchBar() }}
</div>
Por defecto, el trait busca una scope llamada search en el modelo, pero si
implementaste tu propia scope con otro nombre, puedes sobreescribirlo con la variable
searchScopeName:
public string $searchScopeName = "buscar";
La otra cosa que puedes sobreescribir es el placeholder de la barra de busqueda
public string $searchBarPlaceholder = "Escribe aquí...";
Si te encuentras en la situación de que no es posible agregar una scope a un modelo
que permita buscar, puedes definir un método llamado searchScope en tu
componente que haga las funciones del scope.
Por ejemplo, en el caso de spatie/laravel-permission, no es posible modificar sus modelos de manera sencilla. En este caso puedes definir tu scope de la sigiente manera:
use Livewire\Component;
use Spatie\Permission\Models\Role;
use Illuminate\Database\Eloquent\Builder;
use DevCucei\RocketmanComponents\Traits\Views\CanReadModels;
use DevCucei\RocketmanComponents\Traits\Views\WithSearch;
class Roles extends Component
{
use CanReadModels;
use WithSearch;
public $model = Role::class;
protected function searchScope(Builder $query, $search): void
{
$query->where('name', 'LIKE', "%$search%");
}
}