Visualizar datos en una cuadrícula

Introducción

En este tutorial se mostrarán los pasos necesarios para cambiar la visualización de los empleados, para pasar de formato de tabla a formato de cuadrícula, y modificar su formulario detalle para que se muestre en un diálogo flotante.

tutorial_o_web_39.png

Modificar el listado de empleados

employees-home.component.html

<o-grid #employeesGrid attr="employeesGrid" title="EMPLOYEES" service="employees" entity="employee" keys="EMPLOYEEID"
    columns="EMPLOYEEID;EMPLOYEENAME;EMPLOYEESURNAME;EMPLOYEEPHOTO;EMPLOYEEADDRESS;EMPLOYEEPHONE;EMPLOYEESTARTDATE;EMPLOYEEEMAIL;OFFICEID;NAME;EMPLOYEETYPEID;EMPLOYEETYPENAME"
    query-rows="8" page-size-options="8;10;12" orderable="true"
    sortable-columns="EMPLOYEENAME;EMPLOYEESURNAME:asc;EMPLOYEESURNAME:desc;EMPLOYEEEMAIL" insert-button="true"
    pagination-controls="true" gutter-size="8px" fixed-header="yes" grid-item-height="300px" detail-mode="none"
    insert-button-floatable="no">
    <o-grid-item *ngFor="let data of employeesGrid.dataArray">
        <div (click)="openDetail(data)" fxLayout="column" fxLayoutAlign="space-evenly center"
            class="mat-elevation-z1 employeeCard">
            <img [src]="getImageSrc(data.EMPLOYEEPHOTO)" width="144px" height="200px">
            <span><strong>{{ data.EMPLOYEENAME }} {{data.EMPLOYEESURNAME}}</strong></span>
            <span><em>{{ data.EMPLOYEETYPENAME }}</em></span>
            <span class="office">{{ data.NAME }}</span>
        </div>
    </o-grid-item>
</o-grid>
o-grid (atributos de o-grid)
Atributo Valor Significado
query-rows 8 Número de registros por página en la consulta inicial
page-size-options 8;10;12 Opciones del número de elementos por página a mostrar
orderable true Muestra u oculta el botón de ordenación de la cabecera
sortable-columns EMPLOYEENAME;EMPLOYEESURNAME:asc;EMPLOYEESURNAME:desc;EMPLOYEEEMAIL Lista de columnas por la que puenden ordenarse los elementos. Si se escribe :asc o :desc después del nombre de la columna, se puede especificar el tipo de ordenación (ascendente por defecto)
insert-button true Indica si se muestra el botón de inserción
pagination-controls true Muestra los controles de paginación del componente
gutter-size 8px El espacio minimo que hay entre los diferentes elementos del componente
fixed-header yes Indica si la cabecera y el pie de página deben ser fijos cuando el contenido es mayor que su propia altura.
grid-item-height 300px Establece la representación interna de la altura de la fila a partir del valor proporcionado por el usuario
detail-mode none Accción que desencadena el abrir el formulario de detalle
insert-button-floatable no Indica si el botón de inserción es o no flotante

Dentro de la etiqueta de la cuadrícula tenemos que definir la plantilla que se seguirá para cada elemento de la cuadrícula. Esto se consigue mediante el uso de la etiqueta <o-grid-item> y la directiva de Angular *ngFor="let list of employeesGrid.dataArray" aplicará este elemento para todos los registros que se recuperen en la consulta. Dentro de la etiqueta <o-grid-item>, se diseña el aspecto de cada uno de los elementos de la cuadrícula.

Lo envolveremos todo con un elemento <div> que permitirá ejecutar la acción de abrir el formulario detalle (acción que hemos invalidado con el atributo detail-mode del elemento <o-grid>) por lo que será necesario indicar otra acción para mostrar el desplegable de detalle. Para cargar imágenes, tendremos que indicarle que dicha foto está contenida en la petición, por lo que invocaremos un método que permita mostrar la imagen correctamente.

El componente detalle que será el diálogo flotante tiene que recibir los datos y una altura y anchura determinadas.

employees-home.component.ts

import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { DomSanitizer } from '@angular/platform-browser';
import { EmployeesDetailComponent } from '../employees-detail/employees-detail.component';

@Component({
  selector: 'app-employees-home',
  templateUrl: './employees-home.component.html',
  styleUrls: ['./employees-home.component.css']
})
export class EmployeesHomeComponent implements OnInit {

  constructor(
    protected dialog: MatDialog,
    protected sanitizer: DomSanitizer
  ) { }

  ngOnInit() {
  }

  public getImageSrc(base64: any): any {
    return base64 ? this.sanitizer.bypassSecurityTrustResourceUrl('data:image/*;base64,' + base64.bytes) : './assets/images/no-image-transparent.png';
  }

  public openDetail(data: any): void {
    this.dialog.open(EmployeesDetailComponent, {
      height: '330px',
      width: '520px',
      data: data
    });
  }
}

También modificamos el fichero css para pesonalizar algo más el elemento de una cuadrícula

employees-home.component.css

.employeeCard {
  width: 300px;
  background-color: white;
}

.employeeCard img {
  margin-top: 8px;
}

.employeeCard .office {
  font-size: 0.8em;
}

Añadidmos también traducciones para una columna que no tendría traducción en los filtros del quick-filter

en.json

{
  ...
  "EMPLOYEETYPENAME": "Job title"
}

es.json

{
  ...
  "EMPLOYEETYPENAME": "Cargo",
}

Modificar el formulario detalle

Con los datos que recibimos desde el listado de empleados, modificaremos la presentación de elementos del detalle.

employees-detail.component.html

<div fxFill fxLayout="column" fxLayoutAlign="space-evenly center">
    <div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="8px">
        <div fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="4px" class="employeeFirst">
            <img [src]="getImageSrc(data.EMPLOYEEPHOTO)" width="144px" height="200px">
            <span><strong>{{ data.EMPLOYEENAME }} {{ data.EMPLOYEESURNAME }}</strong></span>
            <mat-divider></mat-divider>
            <span><em>{{ data.EMPLOYEETYPENAME }}</em></span>
            <mat-divider></mat-divider>
        </div>
        <div fxLayout="column" class="employeeSecond">
            <span *ngIf="data.EMPLOYEEEMAIL;else no_mail">
                <mat-icon>email</mat-icon><span>{{ data.EMPLOYEEEMAIL }}</span>
            </span>
            <ng-template #no_mail><span><mat-icon>email</mat-icon><span>{{ "NO_DATA_AVAILABLE" |
                        oTranslate}}</span></span></ng-template>
            <span *ngIf="data.EMPLOYEEPHONE;else no_phone">
                <mat-icon>smartphone</mat-icon><span>{{ data.EMPLOYEEPHONE }}</span>
            </span>
            <ng-template #no_phone><span><mat-icon>smartphone</mat-icon><span>{{ "NO_DATA_AVAILABLE" |
                        oTranslate}}</span></span></ng-template>
            <span *ngIf="data.EMPLOYEEADDRESS;else no_address">
                <mat-icon>home</mat-icon><span>{{ data.EMPLOYEEADDRESS }}</span>
            </span>
            <ng-template #no_address><span><mat-icon>home</mat-icon><span>{{ "NO_DATA_AVAILABLE" |
                        oTranslate}}</span></span></ng-template>
        </div>
    </div>
    <span layout-margin-right fxFlexAlign="end"><mat-icon>domain</mat-icon>{{ data.NAME }}</span>
</div>

Utilizamos las plantillas de *ngIf con el else para que muestre un contenido en caso de que contenga la información que se pide. En caso contrario, cargará el contenido del <ng-template> cuyo id que coincida con el nombre que se ha escrito en el *ngIf

Para rellenar los datos es necesario que este componente cargue los datos a través del Injector

employees-detail.component.ts

import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-employees-detail',
  templateUrl: './employees-detail.component.html',
  styleUrls: ['./employees-detail.component.css']
})
export class EmployeesDetailComponent implements OnInit {

  constructor(
    @Inject(MAT_DIALOG_DATA) public data: any,
    protected sanitizer: DomSanitizer
  ) { }

  public getImageSrc(base64: any): any {
    return base64 ? this.sanitizer.bypassSecurityTrustResourceUrl('data:image/*;base64,' + base64.bytes) : './assets/images/no-image-transparent.png';
  }

  ngOnInit() {
  }
}

Añadimos los estilos que tendrá el componente

employees-detail.component.css

.mat-divider {
  border-top-width: 2px;
  width: 100px;
}

.employeeFirst {
  min-width: 200px;
  max-width: 200px;
}

.employeeFirst span {
  text-align: center;
}

span {
  display: flex;
  align-items: center;
}

span mat-icon {
  padding-right: 8px;
}

Y por último las traducciones que necesitamos

en.json

{
  ...
  "NO_DATA_AVAILABLE": "No data available"
}

es.json

{
  ...
  "NO_DATA_AVAILABLE": "No hay información disponible"
}

Este es el resultado final:

tutorial_o_web_40.png

Crear el formulario de inserción

Si ahora probamos a añadir un nuevo empleado, a través del botón con el signo + situado en la parte superior de la cuadrícula, fallará. Esto es debido a que el formulario que estábamos usando, el formulario de detalle, ya no es compatible para poder isertar datos, ya que es el que estamos usando para mostrar el detalle. Por ese mismo motivo, debemos crear un nuevo componente para usar la inserción del componente, por lo que crearemos el componente nuevo usando el siguiente comando:

npx ng generate component --skip-tests employees-new

employees.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OntimizeWebModule } from 'ontimize-web-ngx';
import { EmployeesRoutingModule } from './employees-routing.module';
import { EmployeesHomeComponent } from './employees-home/employees-home.component';
import { EmployeesDetailComponent } from './employees-detail/employees-detail.component';
import { EmployeesNewComponent } from './employees-new/employees-new.component';


@NgModule({
  declarations: [
    EmployeesHomeComponent,
    EmployeesDetailComponent,
    EmployeesNewComponent
  ],
  imports: [
    CommonModule,
    OntimizeWebModule,
    EmployeesRoutingModule
  ]
})
export class EmployeesModule { }

Modificamos la ruta en employees-routing.module.ts para que la ruta de new utilice este nuevo componente en vez del componente detalle.

employees-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EmployeesHomeComponent } from './employees-home/employees-home.component';
import { EmployeesDetailComponent } from './employees-detail/employees-detail.component';
import { EmployeesNewComponent } from './employees-new/employees-new.component';

const routes: Routes = [{
  path: '',
  component: EmployeesHomeComponent
},
{
  path: "new",
  component: EmployeesNewComponent
},
{
  path: ":EMPLOYEEID",
  component: EmployeesDetailComponent
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class EmployeesRoutingModule { }
Solo resta añadir el formulario que se encontraba antes como fomulario detalle, pero esta vez para inserción.
<o-form attr="employeesDetail" service="employees" entity="employee" keys="EMPLOYEEID" header-actions="R;U;D"
    show-header-navigation="yes" confirm-exit="no">
    <o-integer-input attr="EMPLOYEEID" sql-type="INTEGER" enabled="no"></o-integer-input>
    <div fxLayout="row">
        <div>
            <o-image attr="EMPLOYEEPHOTO" empty-image="assets/images/no-image.png" sql-type="OTHER"></o-image>
        </div>
        <o-column fxFlex title="EMPLOYEE_PERSONAL_INFORMATION">
            <div fxLayout="row" fxLayoutGap="8px">
                <o-text-input fxFlex="40" attr="EMPLOYEENAME" required="yes"></o-text-input>
                <o-text-input fxFlex="40" attr="EMPLOYEESURNAME" required="yes"></o-text-input>
                <o-date-input fxFlex="20" attr="EMPLOYEESTARTDATE"></o-date-input>
            </div>
            <div fxLayout="row" fxLayoutGap="8px">
                <o-text-input fxFlex="40" attr="EMPLOYEEPHONE"></o-text-input>
                <o-text-input fxFlex="40" attr="EMPLOYEEEMAIL"></o-text-input>
                <o-list-picker fxFlex="20" attr="EMPLOYEETYPEID" service="employees" entity="employeeType"
                    keys="EMPLOYEETYPEID" columns="EMPLOYEETYPEID;EMPLOYEETYPENAME" visible-columns="EMPLOYEETYPENAME"
                    value-column="EMPLOYEETYPEID"></o-list-picker>
            </div>
            <div fxLayout="row" fxLayoutGap="8px">
                <o-text-input fxFlex="65" attr="EMPLOYEEADDRESS"></o-text-input>
                <o-combo fxFlex="35" attr="OFFICEID" required="yes" service="branches" entity="branch" keys="OFFICEID"
                    columns="OFFICEID;NAME" visible-columns="NAME" value-column="OFFICEID"></o-combo>
            </div>
            <div fxLayout="row" fxLayoutGap="8px">
                <o-real-input fxFlex="50" attr="LONGITUDE" decimal-separator="," max-decimal-digits="10"
                    min-decimal-digits="0"></o-real-input>
                <o-real-input fxFlex="50" attr="LATITUDE" decimal-separator="," max-decimal-digits="10"
                    min-decimal-digits="0"></o-real-input>
            </div>
        </o-column>
    </div>
</o-form>

arrow_back Tutorial anterior Próximo tutorial arrow_forward