Objetivos de esta lección
En el capítulo pasado utilizamos algunos de los componentes que Ionic provee dentro de su librería de componentes. En este capítulo vamos a ver los componentes de Ionic mas a detalle. Para ello vamos a separarlos en las siguientes categorías:
- Output components (Componentes de salida)
- Layout components (Componentes de maquetación)
- Input components (Componentes para formularios)
En este punto tenemos un listado de cursos dentro de Cursos.tsx, para hacerlo mas presentable vamos a complementar IonCard
de manera que agreguemos un IonCardHeader
, IonCardTitle
e IonCardSubtitle
, esto le dará una mejor vista y nos familiarizará con el uso de estos componentes.
<IonCard>
<IonCardHeader>
<IonCardTitle>{curso.titulo}</IonCardTitle>
<IonCardSubtitle>Feb 12 2020</IonCardSubtitle>
</IonCardHeader>
<IonCardContent>
<div className="ion-text-right">
<IonButton
fill="clear"
color="secondary"
routerLink={`/curso/${curso.id}`}
>
Ver Información
</IonButton>
</div>
</IonCardContent>
</IonCard>
Recordar en todos los casos que hay que importar los componentes que se vayan agregando. En este caso estos se encuentran dentro de @ionic/react.
Ahora dentro de Cursos.tsx vamos a complementar la información de CURSOS_DATA
para incluir dentro de cada curso información de los objetivos.
export const CURSOS_DATA = [
{
id: "c1",
titulo: "Titulo 1",
fecha: "02/12/2020",
objetivos: [
{ id: "o1", detalles: "Objetivo 1" },
{ id: "o2", detalles: "Objetivo 2" },
],
},
{
id: "c2",
titulo: "Titulo 2",
fecha: "06/20/2020",
objetivos: [
{ id: "o1", detalles: "Objetivo 1" },
{ id: "o2", detalles: "Objetivo 2" },
],
},
{
id: "c3",
titulo: "Titulo 3",
fecha: "11/03/2020",
objetivos: [
{ id: "o1", detalles: "Objetivo 1" },
{ id: "o2", detalles: "Objetivo 2" },
],
},
];
Actualizamos la página Objetivo.tsx de forma que incorporemos la iteración de los objetivos para el curso seleccionado.
<IonContent>
{curso &&
curso.objetivos &&
curso.objetivos.map((o) => {
return (
<IonList>
<IonItem>{o && o.detalles}</IonItem>
</IonList>
);
})}
</IonContent>
Podemos complementar Objetivo.tsx incluyendo un botón para hacer la función de edición, este asociado a una función que en este momento solo imprime un mensaje en consola.
const onClickEditar = () => {
console.log("Ir a editar objetivo");
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>
{curso ? curso.titulo : "Curso no encontrado"}
</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
{curso &&
curso.objetivos &&
curso.objetivos.map((o) => {
return (
<IonList key={o.id}>
<IonItem>
<IonLabel>{o && o.detalles}</IonLabel>
<IonButton onClick={onClickEditar}>
Editar
</IonButton>
</IonItem>
</IonList>
);
})}
</IonContent>
</IonPage>
);
¿Cómo crear botones deslizables y ocultos en Ionic?
Para crear botones deslizables en Ionic tenemos que hacer uso del componente IonItemSliding
en combinación con IonItemOptions
e IonItemOption
.
En el ejemplo anterior utilizamos una lista de items usando el componente IonList
, ahora vamos a indicarle a Ionic que deseamos que se implemente el comportamiento slide que permite mostrar botones ocultos.
<IonList>
{curso &&
curso.objetivos &&
curso.objetivos.map((o) => {
return (
<IonItemSliding key={o.id}>
<IonItemOptions onClick={onClickEditar} side="start">
<IonItemOption>Editar</IonItemOption>
</IonItemOptions>
<IonItem>
<IonLabel>{o && o.detalles}</IonLabel>
</IonItem>
<IonItemOptions onClick={onClickBorrar}>
<IonItemOption color="danger">Borrar</IonItemOption>
</IonItemOptions>
</IonItemSliding>
);
})}
</IonList>
Los botones Borrar y Editar solo estarán visible cuando se deslice el
ItemSliding
a la derecha y a la izquierda.
¿Cómo crear botones en la barra de herramientas (toolbar) de Ionic?
Para agregar un botón a la toolbar en Ionic utilizamos los componentes IonButtons
y IonButton
dentro de IonToolbar
.
En nuestro archivo Objetivos.tsx vamos a agregar un botón dentro de nuestra toolbar para poder agregar un objetivo mas.
const Objetivos: React.FC = () => {
...
const onClickAgregar = () => {
console.log('Agregar');
}
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/cursos" />
</IonButtons>
<IonTitle>{curso ? curso.titulo : 'Curso no encontrado'}</IonTitle>
<IonButtons slot="end">
<IonButton onClick={onClickAgregar}>Agregar</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
...
</IonPage>
);
}
¿Cómo crear un botón flotable tipo Android en Ionic?
Para crear un botón flotante tipo Android en Ionic se utilizan los componentes IonFab
y IonFabButton
en conjunto.
En nuestro archivo Objetivo.tsx vamos a agregar otro botón, en este caso uno flotante que haga la misma función de enviar un llamado a la función onClickAgregar.
const Objetivos: React.FC = () => {
...
return (
<IonPage>
...
<IonContent>
...
<IonFab horizontal="end" vertical="bottom">
<IonFabButton>+</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
);
}
Al refrescar en la pagina de los objetivos para un curso en específico, podremos ver que un botón circular flotante aparece en escena.
¿Cómo crear una alerta en Ionic?
Las alertas y los modals son algunos de los componentes mas comunes en las aplicaciones móviles y también web.Mediante las alertas podemos incluso realizar un proceso de confirmación de una acción requerida.
En Ionic React para controlar la visibilidad de una alerta, una opción bastante útil es el manejo de estados mediante el hook useState
dentro de React
.
Agregamos el control de estado dentro de Objetivo.tsx.
const Objetivos: React.FC = () => {
const [overlayVisible, setOverlayVisible] = useState<boolean>(false);
...
}
Para crear un overlay de confirmación vamos a utilizar el componente IonAlert
, este requiere de un parámetro isOpen
cuyo valor es boleano y que controla si el overlay esta visible. En este caso asociamos dicho valor a el estado overlayVisible
.
return (
<React.Fragment>
<IonAlert
isOpen={overlayVisible}
header="Estas seguro?"
message="¿Deseas borrar este objetivo?"
buttons={[
{
text: "No",
role: "cancel",
handler: oniClickCancelarBorrado,
},
{
text: "Si",
handler: onClickConfirmarBorrado,
},
]}
/>
...
</React.Fragment>
);
El componente
IonAlert
recibe ademas de controlador de estado, otros tres parámetros. Un título y subtítulo con texto que es desplegable, y un arreglo de botones. En el ejemplo se ingresaron uno para cancelar y otro para confirmar. Cada botón ademas recibe un texto a desplegar, un role que identifica el tipo de botón, y un disparador de eventos o handler.
¿Cómo crear una mensaje tipo toast en Ionic?
Para crear una alerta tipo toast en Ionic utilizamos el component IonToast
. Este componente al igual que el componente IonAlert
tiene un parámetro isOpen
.
Primer requerimos un manejador de estado que controle la visibilidad.
const [mensajeDeAlerta, setMensajeDeAlerta] = useState<string>("");
Vamos a actualizar las funciones que manejan el borrado y la cancelación de este, de forma que se asigne un valor cuando se borra el objetivo, y se vacíe el string cuando se cancele el borrado del mismo.
const onClickConfirmarBorrado = () => {
setMensajeDeAlerta("Se ha borrado el objetivo");
setOverlayVisible(false);
};
const onAlertaCompletada = () => {
setMensajeDeAlerta("");
};
Ahora tenemos que definir importar IonToast
y asignar los parámetros necesarios.
<IonToast
isOpen={!!mensajeDeAlerta}
message={mensajeDeAlerta}
duration={3000}
onDidDismiss={onAlertaCompletada}
/>
Tomar en cuenta que el manejador de estado
mensajeDeAlerta
es un texto. El componenteIonToast
tiene un parámetroisOpen
y uno llamado message (mensaje). Para ambos casos he usado la referencia amensajeDeAlerta
sin embargo comoisOpen
es un boleano, al agregar!!mensajeDeAlerta
este valor se evalúa como verdadero cuando no es un string vacío.
¿Cómo crear una ventana tipo modal en Ionic?
Para crear un modal dentro de Ionic utilizamos el componente IonModal
, este recibe también un parámetro isOpen
que controla la visibilidad de dicho componente.
Creamos un nuevo componente dentro de componentes/EditarObjetivo.txt
.
import React from "react";
import {
IonModal,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonButton,
IonGrid,
IonRow,
IonCol,
IonItem,
IonLabel,
IonInput,
} from "@ionic/react";
const EditarObjetivo: React.FC<{
esVisible: boolean;
onClickCancelar: () => void;
onClickGuardar: () => void;
objetivo: { id: string; detalles: string } | null;
}> = (props) => {
return (
<IonModal isOpen={props.esVisible}>
<IonHeader>
<IonToolbar>
<IonTitle>Editar Objetivo</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonGrid>
<IonRow>
<IonItem>
<IonLabel position="floating">Objetivo</IonLabel>
<IonInput
type="text"
value={props.objetivo?.detalles}
/>
</IonItem>
</IonRow>
<IonRow>
<IonCol>
<IonButton
fill="clear"
expand="block"
onClick={props.onClickCancelar}
>
Cancelar
</IonButton>
</IonCol>
<IonCol>
<IonButton expand="block">Guardar</IonButton>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonModal>
);
};
export default EditarObjetivo;
El componente recibe tres parámetros.
esVisible
permite controlar la visibilidad del componente.onClickCancelar
es una función que se dispara al dar clic en el botónCancelar
.onClickGuardar
es una función que se dispara al dar clic en el botónGuardar
.
Para realizar la integración del componente vamos a importarlo dentro de Objetivo.tsx.
import EditarObjetivo from "./../componentes/EditarObjetivo";
El resto de la implementación se puede ver así.
const Objetivos: React.FC = () => {
...
const [objetivoElegido, setObjetivoElegido] = useState<{ id: string; detalles: string }|null>(null);
...
const onClickEditar = (
objetivo: { id: string; detalles: string },
event: React.MouseEvent
) => {
setModalVisible(true);
setObjetivoElegido(objetivo);
console.log(objetivo);
}
...
const onClickCancelarCambios = () => {
setModalVisible(false);
setObjetivoElegido(null);
}
const onClickGuardarCambios = () => {
setModalVisible(false);
}
return (
<React.Fragment>
<EditarObjetivo
esVisible={modalVisible}
onClickCancelar={onClickCancelarCambios}
onClickGuardar={onClickGuardarCambios}
objetivo={objetivoElegido}
/>
...
<IonPage>
...
<IonContent>
<IonList>
{curso && curso.objetivos && curso.objetivos.map(objetivo => {
return (
<IonItemSliding key={objetivo.id}>
<IonItemOptions onClick={onClickEditar.bind(null, objetivo)} side="start">
<IonItemOption>Editar</IonItemOption>
</IonItemOptions>
...
</IonItemSliding>
);
})}
</IonList>
<IonFab horizontal="end" vertical="bottom">
<IonFabButton onClick={onClickAgregar}>+</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
...
</React.Fragment>
);
}
Para mantener una referencia al objetivo que vamos a editar hemos utilizado el controlador de estado.
const [objetivoElegido, setObjetivoElegido] = useState<{
id: string;
detalles: string;
} | null>(null);
Actualizamos la función de edición de manera que guarde la referencial objetivo que se esta editando.
const onClickEditar = (
objetivo: { id: string; detalles: string },
event: React.MouseEvent
) => {
setModalVisible(true);
setObjetivoElegido(objetivo);
console.log(objetivo);
};
Creamos las funciones para el guardado o cancelación del guardado.
const onClickCancelarCambios = () => {
setModalVisible(false);
setObjetivoElegido(null);
};
const onClickGuardarCambios = () => {
setModalVisible(false);
};
Para invocar la referencia al onClickEditar
con el parámetro objetivo
, en este caso para poder enviar dicho parámetro requerimos utilizar bind
cuyo primer valor es la referencia que se obtiene al utilizar this
y después el primer valor recibido por la función.
<ionlist>
{curso &&
curso.objetivos &&
curso.objetivos.map((objetivo) => {
return (
<ionitemsliding key={objetivo.id}>
<ionitemoptions
onclick={onClickEditar.bind(null, objetivo)}
side="start"
>
<ionitemoption>editar</ionitemoption>
</ionitemoptions>
...
</ionitemsliding>
);
})}
</ionlist>
Implementamos el componente con las referencias a las funciones y el control de estado objetivoElegido
, este provee la información del objetivo en edición.
<EditarObjetivo
esVisible={modalVisible}
onClickCancelar={onClickCancelarCambios}
onClickGuardar={onClickGuardarCambios}
objetivo={objetivoElegido}
/>
En este punto podemos visualizar el objetivo en edición.
Agregando un segundo overlay para agregar un curso
Repetimos el proceso pero en este caso para agregar un curso. Primero creando un nuevo componente componentes/AgregarCurso.tsx
, esto sera incorporado en la pagina Crusos.tsx
.
import React from "react";
import {
IonModal,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonGrid,
IonRow,
IonCol,
IonItem,
IonLabel,
IonInput,
IonButton,
} from "@ionic/react";
const AgregarCurso: React.FC<{
esVisible: boolean;
onClickCancelar: () => void;
}> = (props) => {
return (
<IonModal isOpen={props.esVisible}>
<IonHeader>
<IonToolbar>
<IonTitle>Agregar Curso</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonGrid>
<IonRow>
<IonCol>
<IonItem>
<IonLabel position="floating">
Titulo del curso
</IonLabel>
<IonInput type="text" />
</IonItem>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonButton
fill="clear"
onClick={props.onClickCancelar}
>
Cancelar
</IonButton>
</IonCol>
<IonCol>
<IonButton color="primary">Guardar</IonButton>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonModal>
);
};
export default AgregarCurso;
La lógica es la misma que el ejemplo anterior, pero ahora vamos a montar este sobre Cursos.txt
.
import React, { useState } from 'react';
...
import AgregarCurso from '../componentes/AgregarCurso';
...
const Cursos: React.FC = () => {
const [agregarCursoOverlayVisible, setAgregarCursoOverlayVisible] = useState<boolean>(false);
const onClickAgregarCurso = () => {
setAgregarCursoOverlayVisible(true);
}
const onCancelarAgregarCurso = () => {
setAgregarCursoOverlayVisible(false);
}
return (
<React.Fragment>
<AgregarCurso
esVisible={agregarCursoOverlayVisible}
onClickCancelar={onCancelarAgregarCurso}
/>
<IonPage>
<IonContent>
...
<IonFab horizontal="end" vertical="bottom">
<IonFab horizontal="end" vertical="bottom">
<IonFabButton color="secondary" onClick={onClickAgregarCurso}>
+
</IonFabButton>
</IonFab>
</IonFab>
</IonContent>
</IonPage>
</React.Fragment>
);
}
Para controlar la visibilidad del modal utilizamos el controlador de estado agregarCursorOverlayVisible
.
const [agregarCursoOverlayVisible, setAgregarCursoOverlayVisible] =
useState<boolean>(false);
La función que activa la visibilidad del modal.
const onClickAgregarCurso = () => {
setAgregarCursoOverlayVisible(true);
};
const onCancelarAgregarCurso = () => {
setAgregarCursoOverlayVisible(false);
};
A través de un botón flotante disparamos la función onClickAgregarCurso
.
<IonFab horizontal="end" vertical="bottom">
<IonFab horizontal="end" vertical="bottom">
<IonFabButton color="secondary" onClick={onClickAgregarCurso}>
+
</IonFabButton>
</IonFab>
</IonFab>
El modal se implementa utilizando el controlador de estado agregarCursoOverlayVisible
y la función que cancela la visibilidad del modal.
<IonFab horizontal="end" vertical="bottom">
<IonFab horizontal="end" vertical="bottom">
<IonFabButton color="secondary" onClick={onClickAgregarCurso}>
+
</IonFabButton>
</IonFab>
</IonFab>
¿Cómo crear un campo de fecha y hora en Ionic?
Para crear un campo de fecha/hora en Ionic utilizamos el componente IonDatetime
, este nos permite ingresar la fecha y hora con un formato personalizado.
Abrimos el componente componentes/AgregarCurso.tsx
e importamos el componente IonDatetime
que se encuentra dentro de @ionic/react
. Una vez importado podemos proceder a su implementación.
const AgregarCurso: React.FC<{
esVisible: boolean;
onClickCancelar: () => void;
}> = (props) => {
return (
<IonModal isOpen={props.esVisible}>
...
<IonContent>
<IonGrid>
...
<IonRow>
<IonCol>
<IonItem>
<IonLabel>
<IonDatetime displayFormat="MM DD YY" />
</IonLabel>
</IonItem>
</IonCol>
</IonRow>
...
</IonGrid>
</IonContent>
</IonModal>
);
};
Si refrescamos el proyecto y navegamos hasta este componente podremos ver como se despliega un widget para elegir fecha y hora.
Complementando la lista de todos los objetivos
Aún sigue haciendo falta listar todos los objetivos que se encuentran dentro de todos los cursos. Para ello tomaremos la estructura CURSOS_DATA
y dentro de ella extraeremos todos los objetivos.
Dentro de Cursos.tsx
la estructura es la siguiente…
export const CURSOS_DATA = [
{
id: "c1",
titulo: "Este es el curso número 1",
fecha: "02/12/2020",
objetivos: [
{ id: "c1o1", detalles: "Objetivo 1" },
{ id: "c1o2", detalles: "Objetivo 2" },
],
},
{
id: "c2",
titulo: "Este es el curso número 2",
fecha: "06/20/2020",
objetivos: [
{ id: "c2o1", detalles: "Objetivo 1" },
{ id: "c2o2", detalles: "Objetivo 2" },
],
},
{
id: "c3",
titulo: "Este es el curso número 3",
fecha: "11/03/2020",
objetivos: [
{ id: "c3o1", detalles: "Objetivo 1" },
{ id: "c3o2", detalles: "Objetivo 2" },
],
},
];
Creamos dentro de Objetivos.tsx
un recorrido que nos entregue un arreglo de una sola dimensión con los valores que necesitamos.
const objetivos = CURSOS_DATA.map((curso) => {
return curso.objetivos.map((objetivo) => {
return { ...objetivo, tituloDelCurso: curso.titulo };
});
}).reduce((p, c) => {
return p.concat(c);
}, []);
Finalmente recorremos el arreglo para listar todos los objetivos.
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Objetivos</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonList>
{objetivos.map((objetivo) => {
return (
<IonItem key={objetivo.id}>
<IonLabel>
<h2>{objetivo.tituloDelCurso}</h2>
<h3>{objetivo.detalles}</h3>
</IonLabel>
</IonItem>
);
})}
</IonList>
</IonContent>
</IonPage>
);
¿Cómo crear un botón tipo toggle en Ionic?
Para crear un botón tipo toggle en Ionic utilizamos el componente IonToggle
, este componente nos permite simular un botón deslizable al igual que en las versiones de native.
Vamos a complementar la página Filtrado.tsx
, abrimos el archivo y listamos todos los cursos utilizando un loop sobre la estructura CURSOS_DATA
.
import React from "react";
import {
IonPage,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonButtons,
IonMenuButton,
IonItem,
IonLabel,
IonToggle,
} from "@ionic/react";
import { CURSOS_DATA } from "./Cursos";
const Filter: React.FC = () => {
const onActualizarToggle = (e: CustomEvent) => {
console.log(e);
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Filtrado</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
{CURSOS_DATA.map((curso) => {
return (
<IonItem key={curso.id}>
<IonLabel>{curso.titulo}</IonLabel>
<IonToggle
value={curso.id}
onIonChange={onActualizarToggle}
/>
</IonItem>
);
})}
</IonContent>
</IonPage>
);
};
Para habilitar el botón toggle usamos el componente IonToggle
para cada una de las filas del curso.
<IonToggle value={curso.id} onIonChange={onActualizarToggle} />