Tutoriales

En este tutorial, presentaré un enfoque para lograr la paginación en AdonisJs de una manera fácil e intuitiva usando Lucid ORM, además de View Components y View Presenters del motor de plantillas Edge.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

¿Qué vamos a construir?

Crearemos una simple aplicación de una lista de empleados, en el cual podremos movernos entre páginas y realizar búsquedas por el nombre de los empleados.

Usaremos AdonisJS 4.1 en este tutorial y a continuación se muestra la tabla de contenido de cómo se desarrollará la aplicación final:

Tabla de contenido

Requerimientos

Este tutorial asume que tienes lo siguiente instalado en tu computadora:

node >= 8.0 o mayor

$ node --version

npm >= 5.0 o mayor

$ npm --version
Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Instalación de Adonis CLI

Primero necesitamos instalar la Adonis CLI que nos ayudará a crear nuevas aplicaciones de AdonisJS:

$ npm i -g adonisjs-cli

Crear nuevo proyecto

Comenzaremos creando una nueva aplicación de AdonisJS. Haremos uso de Adonis CLI.

$ adonis new adonisjs-pagination #Creamos la aplicación

El comando anterior creará una nueva aplicación de AdonisJS con el nombre adonisjs-pagination utilizando la plantilla de la aplicación fullstack. Para asegurarnos de que todo funcione como se esperaba, ejecutemos la aplicación recién creada. Primero, ingresamos a la carpeta adonisjs-pagination y ejecutamos el siguiente comando:

$ cd adonisjs-pagination #Ingresar al proyecto
$ adonis serve --dev #Ejecutamos la aplicación

#info: serving app on http://127.0.0.1:3333

Abrimos http://localhost:3333 en el navegador para ver la página de bienvenida.

¡Bien! Ahora comencemos a desarrollar la aplicación.

Wheater Dasboard: Angular + OpenWeather
Publicidad
Wheater Dasboard: Angular + OpenWeather by Victor Valencia Rico

Base de datos y migración

Comenzaremos estructurando la base de datos de la aplicación, utilizaremos el esquema de base de datos MySQL para nuestra aplicación. Entonces, necesitamos instalar el controlador Node.js para MySQL.

$ npm install mysql --save #Instalamos MySQL

A continuación, configuramos las variables de entorno ingresando a nuestra configuración en el archivo .env. Entonces, abra el archivo .env y agregue las siguientes líneas:

//.env
...
DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=adonisjs-pagination
DB_USER=root
DB_PASSWORD=
...
  

Recuerde actualizar el nombre de la base de datos, el nombre de usuario y la contraseña de acuerdo con su propia configuración de la base de datos. Para este caso llamaremos adonisjs-pagination a nuestra base de datos.

Por simplicidad, nuestra aplicación tendrá una sola tabla en la base de datos a la que llamaremos Employees. La tabla Employees contendrá 5 campos: id, name, email, created_at y updated_at. Utilizaremos el comando adonis make:migration para crear el archivo de migración:

$ adonis make:migration employees

Cuando se le solicite, elija la opción Create table y presione Enter. Esto creará un nuevo archivo dentro del directorio database/migrations. El nombre del archivo será una marca de tiempo con el nombre del esquema (en mi caso 1576606188175_employees_schema.js). Abra este archivo y actualice el método up() como se muestra a continuación:


//database/migrations/1576606188175_employees_schema.js
...
up () {
  this.create('employees', (table) => {
    table.increments()
    table.string('name', 254).notNullable()
    table.string('email', 254).notNullable()
    table.timestamps()
  })
}
...
  

El método increments() creará el campo id con Auto Increment y se establecerá como Primary Key. El método timestamps() creará los campos created_at y updated_at respectivamente. Una vez hecho esto, podemos ejecutar la migración:

$ adonis migration:run

Con nuestra base de datos y tabla configuradas, ahora creemos un modelo. Lo llamaremos Employee. Aunque no haremos un uso extenso del modelo en este tutorial, usaremos modelos para las consultas en la bases de datos porque brindan facilidad de uso, proporcionan una API expresiva para manejar el flujo de datos y también nos permite usar Lucid ORM. Para hacer un modelo, utilizamos el comando adonis make:model Employee de Adonis CLI:

$ adonis make:model Employee

Esto creará el archivo Employee.js dentro del directorio app/Models.

Creando el controlador

Utilizaremos un solo controlador principal llamado EmployeeController. Usaremos el comando adonis make:controller Employee de Adonis CLI para crealo:

$ adonis make:controller Employee

Cuando se le solicite, elija la opción For HTTP requests y presione Enter. Ahora tenemos un archivo llamado EmployeeController.js dentro del directorio app/Controllers/Http.

El controlador EmployeeController tendrá solo 1 método: index(). Abra el archivo EmployeeController.js y agregue el siguiente código:


//app/Controllers/Http/EmployeeController.js
...
// Recuerde referenciar el modelo Employee en la parte de arriba
const Employee = use('App/Models/Employee')
...
async index ({ view }) {
  const employees = await Employee.all()
  return view.render('employees.index', { employees: employees.toJSON() })
}
...
  

El método index() simplemente obtendrá todos los empleados existentes en la base de datos y desplegará una vista. Los empleados recuperados son pasados a una vista llamada employees.index (que crearemos en breve).

Angular App: Todo List
Publicidad
Angular App: Todo List by Victor Valencia Rico

Creando la ruta de la aplicación

Abra el archivo start/routes.js y lo actualizamos como a continuación:


//start/routes.js
...
Route.get('/', 'EmployeeController.index')
...
  

La única ruta definida servirá como nuestra página de inicio de la aplicación. Estará vinculada al método index() del controlador EmployeeController (creado anteriormente).

Creando el layout Master

AdonisJS utiliza Edge como su motor de plantillas el cual tiene soporte para layouts. Vamos a crear un layout master para nuestra aplicación. Todos los archivos de las vistas deben estar dentro del directorio resources/views. Entonces, dentro del directorio, crearemos una nueva vista y le asignamos el nombre master.edge. Los archivos Edge tienen la extensión .edge. Abra el archivo recién creado y pegue el siguiente código:

  
  

Usaremos el framework CSS llamado Bootstrap y la librería de iconos FontAwesome, además de la función global style() de AdonisJS para hacer referencia a nuestros estilos .css en CDN. El layout es simple y contiene solo una sección llamada view que representará el contenido.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Creando la vista

Para simplificar nuestra aplicación tendremos solamente una vista, esta a su vez cargará el layout master. Crearemos un nuevo directorio llamado employees dentro del directorio resources/views, luego dentro del directorio employees, cree una nueva vista y asígnele el nombre de index.edge. Ahora, abra el archivo index.edge y pegue el siguiente código:


  
  

Simplemente mostramos los empleados en una tabla. Si no hay empleados, mostramos un mensaje apropiado. Para la columna # de los empleados, estamos utilizando la variable $loop.index de Edge. La propiedad $loop.index contiene el índice de iteración, que comienza desde 0.

Si visitamos la aplicación en el navegador, ya que todavía no tenemos ningún empleado en nuestra base de datos, deberíamos obtener algo similar a la siguiente imagen:

Seeds & Factories

Una vez que haya preparado su esquema de base de datos con migraciones, el siguiente paso es agregar algunos datos. Aquí es donde los Seeds y los Factories de bases de datos entran en escena.

Los Seeds son clases de JavaScript que contienen un método de ejecución. Dentro de este método se puede escribir cualquier operación relacionada con la base de datos.

Al igual que las migraciones, se crea un archivo Seed utilizando el comando adonis make:seed.

$ adonis make:seed Employee

Esto creará el archivo EmployeeSeeder.js dentro del directorio database/seeds. Abra el archivo EmployeeSeeder.js y lo actualizamos como a continuación:


//database/seeds/EmployeeSeeder.js
...
// Recuerde referenciar el modelo Factory y Employee en la parte de arriba
const Factory = use('Factory')
const Employee = use('App/Models/Employee')
...
async run () {
  //Truncar la tabla 'employees' en la base de datos
  await Employee.truncate()
  //Crear todos los empleados (95 registros en total)
  for(var i = 1; i <= 95; i++){
    await Factory.model('App/Models/Employee').create({
      id: i,        
    })
  }
}
...
  

Sin embargo, el poder real de los Seeds se puede aprovechar mejor cuando se combina con los Factories.

Los Factories definen estructuras de datos (blueprints) utilizados para generar datos ficticios. Los blueprints se establecen dentro del archivo database/factory.js. Abra el archivo factory.js y lo actualizamos como a continuación:


//database/factory.js
...
// Recuerde referenciar el modelo Factory en la parte de arriba
const Factory = use('Factory')
...
Factory.blueprint('App/Models/Employee', (faker, i, data) => {
  return {
    id: data.id || (i+1),
    name: data.name || faker.name() + ' ' + faker.last(),
    email: data.email || faker.word() + '@' + faker.domain(),
  }
})
...
  

El método create() del modelo Employee acepta un objeto de datos personalizado que se pasa directamente a su blueprint. El objeto faker pasado a un blueprint es una referencia a la librería JavaScript Chance, la cual es un generador minimalista de cadenas aleatorias, números, etc.

A continuación, ejecutamos nuestro seed EmployeeSeeder.js en nuestra base de datos, el cual borrará en primera instancia todos los datos de la tabla employees, para después insertar los 95 empleados aleatorios.

$ adonis seed --files=EmployeeSeeder.js

Si visitamos la aplicación en el navegador, ahora que ya tenemos empleados en nuestra base de datos, deberíamos obtener algo similar a la siguiente imagen:

Hasta aquí nuestra aplicación mostrará todos los empleados de la base de datos solamente, continuaremos ahora con la paginación de los datos.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Paginación en AdonisJS

AdonisJS proporciona paginación de base de datos por fuera utilizando tanto Query Builder como Lucid ORM.

Query Builder

Query Builder proporciona dos métodos convenientes para paginar los resultados de la base de datos forPage(page, [limit=20]) y paginate(page, [limit=20]).

forPage(page, [limit=20])


const employees = await Database
  .from('employees')
  .forPage(1, 10)
//Esto funciona como un alias para SQL LIMIT.
//La consulta se traduce así
//SELECT * FROM employees LIMIT 0,10
  

Este método devuelve solo la matriz de resultados, sin metadatos adjuntos.

paginate(page, [limit=20])


const results = await Database
  .from('employees')
  .paginate(1, 10)
//Esto funciona como un alias para SQL LIMIT.
//La consulta se traduce así
//SELECT * FROM employees LIMIT 0,10
  

Este método es diferente, porque devuelve la matriz de resultados en la propiedad data


{
  total: 95, //Define el número total de resultados
  perPage: 10, //Define cuántos resultados hay por página
  lastPage: 10, //Define el número de la última página
  page: 1, //Define el número de la página actual
  data: [{...}] //Define la matriz de resultados
}
  
Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Lucid ORM

Lucid ORM también admite el método paginate() de Query Builder:


const Employee = use('App/Models/Employee')
const employees = await Employee.query().paginate(1, 10)    
const results = employees.toJSON()
  

El resultado es el mismo del método paginate() de Query Builder.

Realizaremos algunas modificaciones a nuestra aplicación para poder paginar nuestra lista de empleados.

Abra el archivo start/routes.js y lo actualizamos como a continuación:


//start/routes.js
...
Route.get('/:page?', 'EmployeeController.index').as('employees.pagination')
...
  

Agregaremos el parámetro opcional page y a nuestra ruta le definiremos el alias employees.pagination.

Modificamos el controlador EmployeeController en el método index() y agregamos el siguiente código:


//app/Controllers/Http/EmployeeController.js
...
async index ({ view, params }) {
  const page = params.page || 1
  const employees = await Employee.query().paginate(page, 10)    
  const pagination = employees.toJSON()  
  pagination.offset = (pagination.page - 1) * pagination.perPage
  pagination.pages = Array(pagination.lastPage).fill(null).map( (x,i) => i + 1 )
  pagination.route = 'employees.pagination'
  return view.render('employees.index', { employees: pagination })
}
...
  

Al método modificado index() le agregaremos el parámetro params del cual leeremos el parámetro page para paginar los datos de acuerdo al número de página que es pasado dinámicamente, si no se envía el parámetro page al controlador, su valor por default será 1. Además, se agregan 3 variables de ayuda a los metadatos de la paginación: offset, pages, route. La variable offset define el número donde inicia el desplazamiento de los registros obtenidos. La variable pages define una colección de todas las páginas disponibles de los registros. Por último, la variable route define el alías de la ruta actual.

Bien, ahora el resultado de la paginación enviado a la vista será parecido al siguiente:


{
  total: 95, //Define el número total de resultados
  perPage: 10, //Define cuántos resultados hay por página
  lastPage: 10, //Define el número de la última página
  page: 1, //Define el número de la página actual  
  offset: 0, //Define el desplazamiento inicial
  pages: [1,2,...,9,10], //Define todas las páginas disponibles  
  route: 'employees.pagination' //Define el alías de la ruta actual
  data: [{...}] //Define la matriz de resultados
}
  

Por último, modificamos la vista. Ahora, abra el archivo index.edge y pegue el siguiente código:


  

Le agregamos a la vista en la parte de abajo el código correspondiente para representar la paginación y podernos mover entre páginas, con la ayuda de las 3 variables que se agregaron a los metadatos de la paginación.

Si visitamos la aplicación en el navegador, deberíamos obtener algo similar a la siguiente imagen:

View Components

Los componentes de Edge no son componentes web verdaderos, pero le dan la oportunidad de escribir marcas aisladas con lógica autónoma. Ahora bien, implementaremos 2 componentes en nuestra aplicación (pagination_buttons y pagination_label) que podremos reutilizar en otros controladores.

Realizaremos algunas modificaciones a nuestra aplicación para adaptar nuestros nuevos componentes.

Vamos a crear una nueva carpeta llamada components dentro del directorio resources/views, luego dentro del directorio components, cree un nuevo componente y asígnele el nombre de pagination_buttons.edge. Ahora, abra el archivo pagination_buttons.edge y pegaremos la parte de la navegación de la paginación, donde renombraremos la variable employees por pagination:


  

Ahora, para el segundo componente crearemos dentro del directorio components otro nuevo componente y asígnele el nombre de pagination_label.edge. Ahora, abra el archivo pagination_label.edge y pegaremos el siguiente código:


  

Por último, modificamos la vista. Ahora, abra el archivo index.edge. Reemplazaremos la parte de la paginación por una sola línea donde llamarenos al componente pagination_buttons, ademas se incluirá la llamada al componente pagination_label, donde a la etiqueta @!component se le asignaran 2 parámetros: el nombre del componente y la variabe pagination se inicializará con la variable employees de la vista, esta variable se le enviará a ambos componentes, quedando de la siguiente manera:


  

Si visitamos la aplicación en el navegador, deberíamos obtener algo similar a la siguiente imagen:

Wheater Dasboard: Angular + OpenWeather
Publicidad
Wheater Dasboard: Angular + OpenWeather by Victor Valencia Rico

View Presenters

View Presenters es una forma de encapsular lógica compleja dentro de una clase dedicada, en lugar de escribirla dentro de sus componentes o plantillas. Además, es muy importante comprender la filosofía detrás de un Presenter antes de que realmente pueda potenciarlo.

Los motores de plantillas se utilizan mucho dentro de las aplicaciones web y casi todas las aplicaciones web que se pueden mantener intentan estructurar su código siguiendo algunos de los patrones predefinidos. Uno de ellos es el patrón MVC.

Los Presenters de Edge es un intento de resolver la transformación de datos para su presentación. En lugar de mantener toda la lógica de transformar los datos dentro de su controlador, puede pasar los datos sin procesar a sus vistas y transformarlos dentro de los Presenters.

Realizaremos algunas modificaciones a nuestra aplicación para adaptar nuestros componentes con Presenters. Vamos a crear una nueva carpeta llamada presenters dentro del directorio resources, luego dentro del directorio presenters, cree una nueva clase de nombre PaginationPresenter.js. Ahora, abra el archivo PaginationPresenter.js y pegaremos el siguiente código:


//resources/presenters/PaginationPresenter.js
const { BasePresenter } = require('edge.js')

class PaginationPresenter extends BasePresenter {  

  //Retorna el desplazamiento inicial
  offset(pagination) {
    return (pagination.page - 1) * pagination.perPage
  }

  //Retorna el estatus de la paginación
  label(pagination) {    
    const offset = this.$presenter.offset(pagination)
    const rows = pagination.data.length
    return (rows == 0) ? '' : 'Mostrando ' + rows + ' registros, del ' + (offset + 1) + ' al ' + (offset + rows) + ' de ' + pagination.total + ' - Página ' + pagination.page + ' de ' + pagination.lastPage
  }
  
  //Retorna todas las páginas disponibles del listado
  pages(pagination) {    
    return Array(pagination.lastPage).fill(null).map( (x,i) => i + 1 )
  }    

  //Define si es la primera página
  isFirstPage(pagination) {
    return pagination.page == 1
  }

  //Define si es la página actual
  isCurrentPage(pagination, page) {
    return pagination.page == page
  }

  //Define si es la última página
  isLastPage(pagination) {
    return pagination.page == pagination.lastPage
  }

  //Retorna la ruta de la página indicada
  getRoute(pagination, page) {
    return this.$globals.route(pagination.route, {page: page})
  }

}

module.exports = PaginationPresenter
  

Modificamos el componente pagination_buttons adaptándolo a la clase PaginationPresenter con nuevo código de la siguiente manera:


  

Modificamos el componente pagination_label adaptándolo a la clase PaginationPresenter con nuevo código de la siguiente manera:


  

Modificamos el controlador EmployeeController y solo eliminamos la variable pages, ya que se accederá a esta variable mediante la clase PaginationPresenter:


//app/Controllers/Http/EmployeeController.js
...
async index ({ view, params }) {
  const page = params.page || 1
  const employees = await Employee.query().paginate(page, 10)    
  const pagination = employees.toJSON()  
  pagination.offset = (pagination.page - 1) * pagination.perPage  
  pagination.route = 'employees.pagination'
  return view.render('employees.index', { employees: pagination })
}
...
  

Ahora modificamos la vista. Abra el archivo index.edge y solamente agregaremos un tercer parámetro llamado presenter a los componentes, quedando de la siguiente manera:


  

Si visitamos la aplicación en el navegador, deberíamos obtener el mismo resultado al anterior, solo que ahora podremos reusar los componentes ya creados en algunas otras vistas creadas posteriormente:

Agregar búsqueda

Para terminar, agregaremos una búsqueda para agregarle un plus a nuestra aplicación. Haremos una última modificación para agregar esta funcionalidad.

Agregamos un nuevo componente que llamaremos pagination_search_label, entonces crearemos dentro del directorio components otro nuevo componente y lo nombramos pagination_search_label.edge. Ahora, abra el archivo pagination_search_label.edge y pegaremos el siguiente código:

  
  

Ahora modificamos la vista. Abra el archivo index.edge y solamente cambiaremos el componente pagination_label por pagination_search_label de la siguiente manera:


  

Casi para terminar modificamos nuestra clase PaginationPresenter. Solo modificaremos el método getRoute() para agregar el parámetro search a la URL:


//resources/presenters/PaginationPresenter.js
...
getRoute(pagination, page) {
  const search = (pagination.search != '') ? '?search=' + pagination.search : ''
  return this.$globals.route(pagination.route, {page: page}) + search
}
...
  

Por último, modificamos el controlador EmployeeController agregando el siguiente código:


//app/Controllers/Http/EmployeeController.js
...
async index ({ view, params, request, response }) {
  const page = params.page || 1
  const search = request.input('search') || ''
  const employees = await Employee.query()
                                  .where('name', 'LIKE', '%' + search + '%')
                                  .paginate(page, 10)
  const pagination = employees.toJSON()
  pagination.route = 'employees.pagination'
  if(pagination.lastPage < page && page != 1) {
    response.route(pagination.route, { page: 1 }, null, true)      
  }
  else {      
    pagination.offset = (pagination.page - 1) * pagination.perPage
    pagination.search = search
    return view.render('employees.index', { employees: pagination })
  }   
}
...
  

Si visitamos la aplicación en el navegador, deberíamos obtener el siguiente resultado con la búsqueda por la columna nombre:

Angular App: Todo List
Publicidad
Angular App: Todo List by Victor Valencia Rico

Conclusión

Eso es todo, han visto lo fácil que es implementar ciertas funciones utilizando AdonisJS. Esta aplicación es básica e incluso se pueden agregar mas funcionalidades, tales como ordenar los datos o bien buscar por varias columnas del listado, estas actividades se las dejo a su imaginación y criterio para tener una aplicación más completa.