🥇 Estudio de componentes de Ionic

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:

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 componente IonToast tiene un parámetro isOpen y uno llamado message (mensaje). Para ambos casos he usado la referencia a mensajeDeAlerta sin embargo como isOpen 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.

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}
/>
Navegación dentro del proyecto Mano de entrada de datos y estados
comments powered by Disqus