Tipos de géneros de libros - Interfaz de usuario
- Obtener enlace
- X
- Correo electrónico
- Otras aplicaciones
Hola a todo@s, continuando con la serie de entradas en el blog La Librería, en esta entrada se busca tener el primer contacto con el interfaz de usuarios de nuestro componente SPfx.
El objetivo de esta entrada es la de diseñar un interfaz de usuario lo más parecido a los que ofrece SharePoint Online en sus listas, pero aportando nuestra personalidad.
Según se avance en la creación del interfaz de usuario, se pretende aportar conceptos que nos ayude a entender mejor como se trabajar en un componente SPfx. Lo conceptos que se pretende aportar son:
Como usar componentes de Office UI Fabric.
Saber conceptos de React.js.
Saber conceptos de hojas de estilos.
Saber conceptos de multiidiomas.
Saber conceptos de emular servicios REST de SharePoint Online.
Interfaz de usuario
La representación del interfaz de usuarios en un componente SPfx, se realiza en la función publica Render. En este método conviven las etiquetas HTML de toda la vida, el código JavaScript para establecer nuestra lógica de negocio, y componentes React nuestros personalizados como de terceras personas que se han importado en nuestro proyecto.
En este ejercicio el interfaz grafico se va a realizar con componentes de terceros, más concretamente los que ofrece Office UI Fabric. En este ejercicio se van a utilizar componentes de terceros como los botones de acción, ventanas emergentes, tabla de datos y paneles para los formularios.
Cuando se crea y define un competente SPfx, viene preinstalado los componentes Office UI Fabric, por lo que solo hay que importar en el componente React, los componentes que se quieren utilizar.
// Import components of Office UI Fabric
import { CommandBarButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { DetailsList, DetailsListLayoutMode, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
Teniendo en cuenta el funcionamiento y comportamiento de los componentes que ofrece Office UI Fabric, el interfaz de usuario del componente SPfx que se pretende implementar quedaría de la siguiente forma:
public render(): React.ReactElement<IManagerTypesOfBookGenresProps>{
console.warn("render - ManagerTypesOfBookGenres");
return (
<div className={styles.managerTypesOfBookGenres}>
<div className={styles.listButton}>
<CommandBarButton iconProps={{ iconName: 'Add' }}
text={strings.Components.Actions.AddGenre}
onClick={this.handelAddGenre} />
</div>
<div>
<DetailsList items={this.state.listItemsGenres.value}
columns={_columns}
setKey="set"
layoutMode={DetailsListLayoutMode.justified}
onItemInvoked={this.handelItemInvoked} />
</div>
<div>
<Panel isOpen={this.state.showPanel}
type={PanelType.custom}
customWidth="350px"
headerText={this.state.panelNew ? strings.Components.Panel.PanelTitleNew : strings.Components.Panel.PanelTitleUpdate}
closeButtonAriaLabel={strings.Components.Actions.Close}
onRenderFooterContent={this.handelRenderFooterContent}>
<TextField label={strings.Components.Fields.Title}
required={true}
name={Constants.ControlId.FieldTitle}
onChanged={value => this.handelChangeInputText(value, Constants.ControlId.FieldTitle)}
value={this.state.updating}
/>
</Panel>
<Dialog hidden={this.state.hideDialog}
dialogContentProps={{
type: DialogType.normal,
title: strings.Components.Dialog.DialogTitle,
subText: strings.Components.Dialog.DialogDescription
}}>
<DialogFooter>
<div className={styles.listButton}>
<DefaultButton text={strings.Components.Actions.Accept}
onClick={this.handelCloseDialog} />
<DefaultButton text={strings.Components.Actions.Cancel}
onClick={this.handelCloseDialog} />
</div>
</DialogFooter>
</Dialog>
</div>
</div>
);
}
Como es incuestionable, con el código fuente introducido en la función render, el editor del componente SPfx, que se esté utilizando, nos avisará de que no es capaz de identificar objetos, propiedades y funciones.
No hay que preocuparse, a continuación, se explican los demás movimientos que se necesitan realizar para generar el interfaz de usuario.
Ficheros de recursos
Si se analiza el código fuente establecido en la función de renderización, se observa que los literales de los controles utilizados, como botones de acción, cajas de textos y títulos en los paneles emergentes, se otorgan a través del objeto strings. Este objeto esta declarado en la importación del módulo ManagerTypesOfBookGenresWebPartStrings que es el encargado de contener la estructura y definición de los literales que se van a usar en el componente SPfx.
Fichero mystrings.d.ts
El encargado de que se pueda usar los literales como su fuera un objeto de una clase, a través de propiedades, es el fichero mystrings.d.ts. En su contenido, está definido un interfaz (IManagerTypesOfBookGenresWebPartStrings), el cual especifica como debe ser definidos los ficheros de recursos y como deben ser utilizados en los controles del componente SPfx.
Con el fin de que los literales no se convierta en un cajón desastre, se han definido una estructura en el que se agrupan los textos por sus funcionalidades o conceptos. De esta forma se facilitará su manejo, evitando duplicidad de literales.
declare interface IManagerTypesOfBookGenresWebPartStrings {
PropertyPaneDescription: string;
Components: {
Actions: {
AddGenre: string;
Close: string;
Save: string;
Delete: string;
Cancel: string;
Accept: string;
}
Fields: {
Title: string;
}
Panel: {
PanelTitleNew: string;
PanelTitleUpdate: string;
}
Dialog: {
DialogTitle: string;
DialogDescription: string;
}
}
}
Ficheros de recursos en-us.js y es-es.js
Una vez definido la estructura con la distribución de los literales, hay que trasladar esa estructura en forma de Json a los ficheros de recursos en-us.js y es-es.js, con los textos en el idioma correspondiente.
define([], function () {
return {
"PropertyPaneDescription": "In this pop-up panel of the web part SPfx 'Manager of types of genres of books', its characteristics are configured for its correct functioning.",
"Components": {
"Actions": {
"AddGenre": "Add gender",
"Close": "Close",
"Save": "Save",
"Delete": "Delete",
"Cancel": "Cancel",
"Accept": "Accept"
},
"Fields": {
"Title": "Title"
},
"Panel": {
"PanelTitleNew": "New genre",
"PanelTitleUpdate": "Update gender"
},
"Dialog": {
"DialogTitle": "Delete genre",
"DialogDescription": "Are you sure you want to delete the type of book genre?"
}
}
}
});
define([], function () {
return {
"PropertyPaneDescription": "En este panel emergente del web part SPfx ‘Gestor de tipos de géneros de libros’, se configuran sus características para su correcto funcionamiento.",
"Components": {
"Actions": {
"AddGenre": "Crear género",
"Close": "Cerrar",
"Save": "Guardar",
"Delete": "Eliminar",
"Cancel": "Cancelar",
"Accept": "Aceptar"
},
"Fields": {
"Title": "Título"
},
"Panel": {
"PanelTitleNew": "Nuevo género",
"PanelTitleUpdate": "Actualizar género"
},
"Dialog": {
"DialogTitle": "Eliminar género",
"DialogDescription": "¿Estás seguro de que quieres eliminar el tipo de genero de libro?"
}
}
}
});
Eventos
Para un correcto entendimiento por parte de todos los controles implicados en un interfaz de usuario, es necesario la ejecución de acciones o eventos. Aunque hay multitud de eventos debido a la diversidad de controles que se pueden utilizar, hay que decir que la mayoría de los eventos se configuran y definen de la misma manera para un componente SPfx.
Sobre los eventos recomendaría, no definir los eventos en el propio control si no es necesario, ya que de esta forma se puede reutilizar si hay más controles que tienen que realizar la misma funcionalidad.
Si las definiciones de los eventos no se han colocado directamente sobre el control, las definiciones se definen dentro del constructor del componente React. Recomendaría, ubicarlas después del componente state.
constructor(props: any) {
super(props);
this.state = {
...
...
...
};
this.handelRenderFooterContent = this.handelRenderFooterContent.bind(this);
this.handelClosePanel = this.handelClosePanel.bind(this);
this.handelCloseDialog = this.handelCloseDialog.bind(this);
this.handelShowDialog = this.handelShowDialog.bind(this);
this.handelItemInvoked = this.handelItemInvoked.bind(this);
this.handelAddGenre = this.handelAddGenre.bind(this);
this.handelChangeInputText = this.handelChangeInputText.bind(this);
}
La función bind que utiliza el objeto this como parámetros de entrada, que hay en todas las definiciones de evento, se utiliza para indicar a la implementación del evento, el contexto en el que trabaja el evento. Esto se realiza normalmente, por la necesidad de tener que usar el componente state en la lógica de negocio del evento.
Una vez definido los eventos que intervienen en el componente React, se implementaran su lógica de negocio. Al estar en la fase del diseño del interfaz de usuario, la mayoría de los eventos solo muestran u ocultan paneles de información. A continuación, se destacan los siguientes eventos definidos:
El evento handelRenderFooterContent que se encarga de renderizar los botones del panel que continen el formulario donde se hacen las acciones de los tipos de géneros de libros.
El evento handelChangeInputText que se encarga registrar los cambios que se producen en la caja de texto del formulario. Mas adelante os explicare el funcionamiento de este evento y el paso de parámetros.
private handelRenderFooterContent() {
console.warn("handelRenderFooterContent - ManagerTypesOfBookGenres");
return (
<div className={styles.listButton}>
<DefaultButton text={strings.Components.Actions.Save}
onClick={this.handelClosePanel} />
{!this.state.panelNew && (
<DefaultButton text={strings.Components.Actions.Delete}
onClick={this.handelShowDialog}
className={styles.delete} />
)}
<DefaultButton text={strings.Components.Actions.Cancel}
onClick={this.handelClosePanel} />
</div>
);
}
private handelClosePanel() {
this.setState({ showPanel: false });
}
private handelCloseDialog() {
this.setState({ hideDialog: true });
}
private handelShowDialog() {
this.setState({ hideDialog: false });
}
private handelItemInvoked() {
this.setState({
showPanel: true,
panelNew: false
});
}
private handelAddGenre() {
this.setState({
showPanel: true,
panelNew: true
});
}
private handelChangeInputText(value: any, key: React.ReactText) {
console.warn("Textbox value: " + value);
let auxFormUpdating = this.state.formUpdating;
auxFormUpdating[key] = value;
this.setState({ formUpdating: auxFormUpdating });
}
Hoja de estilos
El diseño del interfaz de usuario del componente SPfx, no ha requerido de aplicar muchos elementos de estilos CSS, gracias a la utilización de los componentes Office UI Fabric y la importación de sus estilos en la primera línea del fichero de estilos con extensión scss.
Los elementos de estilos aplicados al interfaz de usuario son para establecer un diseño lo más parecido a lo que ofrece SharePoint Online, pero aportando nuestra personalidad.
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.managerTypesOfBookGenres{
.listButton{
background-color: rgb(244, 244, 244);
text-align: left;
}
}
.listButton{
margin: 10px 0px;
padding: 0px;
text-align: center;
button{
margin-left: 10px;
padding: 10px 5px;
}
button.delete{
background-color: rgba(236, 100, 75, 1);
color: white;
}
button:hover.delete{
background-color:rgba(217, 30, 24, 1);
}
}
Emular de contenido
Como se muestra en las imágenes del diseño del interfaz de usuario que se desea realizar, se lista valores de tipos de géneros de libros. Estos valores no pertenecen a la lista de géneros que hemos definido en otra entrada del blog. Sino que se ha realizado a través de la emulación de datos para poder realizar el diseño más preciso al resultado final.
A esta técnica desarrollo, se la denomina Mock y se utiliza cuando el acceso a los servicios que necesita el componente SPfx, no están posibles. De esta forma el interfaz de usuario puede seguir su desarrollo aun que no se disponga de todos los servicios requeridos por el componente SPfx, para su finalidad.
Es necesario que la información que se emula sea lo más fiel a la información aportada por los servicios oficiales que consumirá el componente SPfx. A continuación, se explica lo necesario para respetar esa fidelidad en la información y como emular la información.
Estructura de la información
Para poder emular los datos que va a devolver un servicio real, es muy importante disponer de una estructura de datos idéntica para ambos procesos. Esto se realiza a través de interfaces de clases que se definen a continuación:
-
Interfaz IItemGenre: representa la información de un elemento de la lista de géneros. De toda la información que se obtiene de un elemento de lista en SharePoint online, solo se va a necesitar las propiedades identificador y título.
El nombre del fichero que contendrá el interfaz es IItemGenre.tsx.
export interface IItemGenre { Id: number; Title: string; }
-
Interfaz IListItemsGenre: representa una clase que contiene la propiedad value, que contiene el listado de elementos con el interfaz IItemGenre.
El nombre del fichero que alberga el interfaz es IListItemsGenre.tsx.
import { IItemGenre } from './IItemGenre'; export interface IListItemsGenre { value: IItemGenre[]; }
Emulador MockWebApiCalls.tsx
Como el objeto de un emulador, es el de generar una serie de elementos lo más realistas a los resultados que devuelve el servicio REST de SharePoint Online, se ha creado un fichero con el nombre MockWebApiCalls.tsx con esta finalidad.
Por ahora solo hay implementado un método público GetRepositoryGenres que se encarga de devolver un listado de tipo de géneros de libros. A su vez se han implementado unos métodos privados para hacer que los resultados sean diferentes cada vez que se ejecute el método principal y se devuelva la información ordenada alfabéticamente.
import { IItemGenre } from './../Entities/IItemGenre';
import { IListItemsGenre } from './../Entities/IListItemsGenre';
export class MockWebApiCalls {
private static getRandomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min)) + min;
}
private static sortElements(list: { sort: (arg0: (a: any, b: any) => any) => IItemGenre[]; }): IListItemsGenre {
let result: IListItemsGenre = { value: list.sort((a, b) => a.Title.localeCompare(b.Title)) };
return result;
}
public static GetRepositoryGenres(): IListItemsGenre {
const ListGenres = ["Terror", "Romantics", "Thriller", "Fantastic", "Youth", "Historical", "Adventure", "Futurists"];
let randomItem = this.getRandomInt(3, ListGenres.length);
let list = null;
let i = 0;
list = [];
while (i < randomItem) {
let valueGenre = ListGenres[this.getRandomInt(0, ListGenres.length - 1)];
if (list.findIndex((g: { Title: string; }) => g.Title === valueGenre) === -1) {
i++;
let newItem: IItemGenre = { Id: i, Title: valueGenre };
list.push(newItem);
}
}
return this.sortElements(list);
}
}
export default MockWebApiCalls;
Ejecutar un Mock
Como se esta en la fase de diseño del interfaz de usuario, no vamos a adentrarnos como diferenciar si la ejecución de un componente SPfx, se realiza dentro del contexto de SharePoint Online o en un emulador de desarrollo de SharePoint Online.
Como es habitual en este tipo de ejercicios, es necesario disponer de la información antes de renderizar el contenido final al usuario. En este ejercicio se va a usar el método componentWillMount que ofrece React, para obtener del emulador la información y guardarla en el componente state del componente React antes de pasar por el método render.
public componentWillMount() {
console.warn("componentWillMount - ManagerTypesOfBookGenres");
// Load Genre
this.setState({
listItemsGenres: MockWebApiCalls.GetRepositoryGenres()
});
}
Recordad que hay que importar el módulo MockWebApiCalls en el componente React donde se quiera realizar la emulación de los resultados que se va a mostrar al usuario.
import { MockWebApiCalls } from "./../WebApi/MockWebApiCalls";
Control DetailsList de Office UI Fabric
El control que representa el listado de los tipos de géneros de libros en el interfaz de usuario es el control DetailsList que ofrece Office UI Fabric en su versión básica.
Este control, requiere de una configuración a nivel de definición de columnas, para indicar como se desea mapea la información que se le aporta a través de la propiedad items.
La configuración a nivel de definición de columnas se debe realizar fuera de la definición componente React. Para este ejercicio, se ha optado por colocar la definición antes de empezar la definición del componente React, pero también se puede definir las columnas en otro fichero tsx y posteriormente importar su definición.
...
...
const _columns: IColumn[] = [
{
key: "".concat("col", Constants.ControlId.FieldTitle),
name: strings.Components.Fields.Title,
fieldName: Constants.ControlId.FieldTitle,
minWidth: 100
}
];
export default class ManagerTypesOfBookGenres extends React.Component {
...
...
}
Hay que informar de que se ha creado un fichero de constantes para el desarrollo, y se ha utilizado para configurar las columnas del control DetailsList, por lo que hay que importar su definición.
import { Constants } from "../Constants";
El fichero de constantes que se maneja en el componente SPfx, es el siguiente:
export const Constants = {
ControlId: {
FieldTitle: "Title"
}
};
Componente state
Los componentes React, disponen de un componente u objeto llamado state, que es el encargado de almacenar información relevante, en forma de variables, para el negocio de nuestro componente SPfx.
Hay que recordar que los valores del componente state, solo puede ser modificados por el método setState y que la ejecución de este método implica el renderizado del componente React al que representa el componente state y todos los componentes react hijos que se estén utilizando.
Para este ejercicio, las variables que se han definido en el componente state, solo representan los estados para la visualización y comportamiento de los controles utilizados, a parte de almacenar el listado de tipos de géneros de libros obtenidos a través de nuestro emulador de servicios REST de SharePoint Online.
constructor(props: any) {
super(props);
this.state = {
listItemsGenres: null,
showPanel: false,
hideDialog: true,
panelNew: true,
formUpdating: {
Title: ""
}
};
...
...
}
¿Es nuestro diseño eficiente?
El porqué de esta pregunta a estas alturas de la entrada, os preguntares. Pues bien, todos cuando visitamos una página web con un navegador o móvil, nos ponemos enfermos, si la página tarda más de tres segundos en darnos el feedback deseado.
Los factores puedes ser muchos, desde una lenta conexión a internet, procesos muy tediosos desde el lado del servidor o un JavaScript que se come los recursos de mi navegador.
Yo no soy un experto en las optimizaciones y en rendimientos de sistemas, pero sí que tengo dos dedos de frente para saber si mi código esta más o menos optimizado, o por lo contrario he cometido un error dejando un bucle infinito por ahí perdido.
No sé si os habéis fijado en el código fuente que he dejado unas líneas de código que escriben en la consola del navegador como warnings. Lo he realizado para evaluar las veces que se ejecutan ciertos métodos.
Pensad que, si una página tarda cinco segundos en responder a la petición del usuario, debido a que un método que se ejecuta solo una vez, pero tarda cuatro segundos en ejecutarse, se puedo asumir su bajo rendimiento. Pero si el mismo método se ejecuta diez veces y siempre devuelve la misma información, no puedo asumir que la respuesta al usuario este por encima de los treinta segundos.
Comento esto, ya que, en el apartado anterior, se ha explicado que cualquier cambio sobre el componente state, hace que se vuelva a renderizar el componente React al que pertenece y los componentes React hijos.
Si os fijáis en las trazas que se han dejado en el navegador, cada vez que se escribo algo en la caja de texto, se lanza los eventos render y handelRenderFooterContent. Aunque estos métodos no tardan en su ejecución y para el usuario final no hay efecto de que la pagina vaya lenta, es conveniente diseñar un interfaz de usuario que sea óptimo.
Por ello en la siguiente entrada del blog, se centrará en optimizar los procesos del interfaz de usuario.
- Obtener enlace
- X
- Correo electrónico
- Otras aplicaciones