Mostrando Registros


Obtener los modelos

Para obtener una colección con los modelos que deseamos usaremos el trait CanReadModels en el componente de livewire.

Especificar modelo

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

Manipulando la coleccion

Obtener nombre

En caso de necesitar el nombre de la colección, este se puede obtener con:

$this->collectionName();

Obtener colección

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

Establecer colección

No será habitual, pero si necesita modificar la colección eso es posible con el metodo:

$this->setCollection($collection);

Modificando comportamiento

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

Obtencion de los datos

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.

Vista de tabla

Cuando requiera representar de forma visual sus modelos en una tabla, puede hacer uso del trait WithTableView.

Indicando la coleccion

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.

Especificando las columnas

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.

Columna Vacia

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

Columna con solo atributo

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.

Columna con solo texto

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

Columna con vista

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.

Columna condicional

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.

Mostrando la tabla

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>

Estilos a tags

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:

  • table. A toda la tabla.
  • thead. A toda la cabecera de la tabla.
  • tbody. A todo el contenido de la tabla.
  • tr. A cada fila.
  • th. A cada cabecera de columna.
  • td. A cada celda de una fila.

Estilos a columnas

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.

Estilos a filas

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.

Filtrando los modelos

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

Especificando los 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')


Filtro de Selección

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:

  • Keys. Hacen referencia al identificador que tendran las opciones. Este valor se pasa más adelante a la scope que filtrará los modelos.
  • Values. Serían lo que el usuario vería como opciones.

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.

Mostrando los filtros

Para mostrar los filtros en tu vista, solo hace falta que llames al método filter.

<div>
  {{ $this->filter() }}
</div>

Aplicando los filtros

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

Crear filtros

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:

  • Ya está implementado el método estático name que te devolverá una instancia de tu filtro.
  • Ya está implementado el constructor que solo recibe el nombre del filtro. Si tu filtro requiere que asignes valores por defecto, siempre puedes sobreescribirlo únicamente no te olvides de llamar al constructor del padre.
    parent::__construct($name);
  • Para que alguien cambie el cómo se comporta tu filtro, crea métodos que configuren tu filtro y al final devuelvan la instancia misma.
    public function options(mixed $list): static
    {
        // Stuff
        return $this;
    }
  • Asegúrate de implementar todos los métodos abstractos de la clase.

Puedes basarte en el filtro SelectFilter para crear tus filtros.

Buscando modelos

Es recurrente necesitar habilitar la búsqueda de registros según la entrada de un usuario, eso también se facilita con este paquete.

Definiendo la scope de busqueda

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.

Filtrando por busqueda

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%");
    }
}