Saltar al contenido principal

Observers and Route Parameters

Abrir en ChatGPT

La aplicación de Routing and Composites solo puede agregar nuevos clientes a la base de datos. Utilizando los siguientes conceptos, les darás a los usuarios la capacidad de editar los datos de los clientes existentes:

  • Patrones de ruta
  • Pasar valores de parámetros a través de una URL
  • Observadores de ciclo de vida

Completar este paso crea una versión de 4-observers-and-route-parameters.

Ejecutando la aplicación

Mientras desarrollas tu aplicación, puedes usar 4-observers-and-route-parameters como comparación. Para ver la aplicación en acción:

  1. Navega al directorio de nivel superior que contiene el archivo pom.xml, que es 4-observers-and-route-parameters si estás siguiendo la versión en GitHub.

  2. Usa el siguiente comando de Maven para ejecutar la aplicación Spring Boot localmente:

    mvn

Ejecutar la aplicación abre automáticamente un nuevo navegador en http://localhost:8080.

Usando el id del cliente

Para usar FormView para editar clientes existentes, necesitarás una forma de indicarle qué cliente editar. Puedes hacerlo proporcionando un parámetro inicial a FormView que representa el ID del cliente. En Trabajando con Datos, creaste una entidad Customer que asigna un valor numérico Long como un id único a los clientes cuando se agregan a la base de datos.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

En este paso, realizarás cambios en FormView para que use un id como parámetro inicial antes de que se cargue cualquier cosa. Luego, harás que FormView evalúe el id para determinar si el formulario es para agregar un nuevo cliente o actualizar uno existente. Finalmente, modificarás MainView para que envíe un valor de id al navegar a FormView.

Agregando un patrón de ruta a FormView

En el paso anterior, al establecer la ruta en FormView a @Route(customer), mapeas la clase localmente a http://localhost:8080/customer. Agregar un patrón de ruta te permite añadir un id como un parámetro inicial a FormView.

Un Patrón de Ruta te permite agregar un parámetro en la URL, hacerlo opcional y establecer restricciones sobre patrones válidos. Usando la anotación @Route, aquí está lo que hace que id sea un parámetro de ruta opcional para FormView:

  • /:id le da a la ruta un parámetro nombrado de id, así que ir a http://localhost:8080/customer/6 carga FormView con un parámetro id de 6.

  • ? hace que el parámetro id sea opcional. Por defecto, los parámetros son requeridos, pero al hacer que el id sea opcional, puedes usar FormView para agregar nuevos clientes que aún no tengan un id.

  • <[0-9]+> restringe id a ser un número positivo. En corchetes angulares <>, puedes agregar una restricción como una expresión regular al parámetro. Si el id no coincide con la restricción, por ejemplo, http://localhost:8080/customer/john-smith, envía al usuario a una página 404.

Para agregar el parámetro de ruta opcional a FormView, cambia la anotación @Route a esto:

@Route("customer/:id?<[0-9]+>")

Enrutamiento a FormView

FormView ahora acepta un parámetro id opcional y solo se carga si el id es un número positivo entero.

Sin embargo, FormView aún puede cargarse cuando un usuario ingresa manualmente una URL para un cliente que no existe, como http://localhost:8080/customer/5000. Agregar un observador de ciclo de vida antes de entrar en FormView permite que tu aplicación determine cómo manejar el valor id entrante.

Enrutamiento condicional

Los observadores de ciclo de vida permiten que los componentes reaccionen a eventos del ciclo de vida en etapas específicas. El artículo Observadores de Ciclo de Vida enumera los observadores disponibles, pero este paso solo utiliza el WillEnterObserver.

El tiempo de WillEnterObserver ocurre antes de que se complete el enrutamiento del componente. Usar este observador te permite evaluar el id entrante. Si el id no coincide con un cliente existente, puedes redirigir al usuario de vuelta a MainView para encontrar un cliente válido para editar.

Antes de discutir el código para el WillEnterObserver, el siguiente diagrama de flujo describe cuáles deberían ser los posibles resultados al enrutarse a FormView:

Usando el WillEnterObserver

Usando el observador del ciclo de vida que se activa antes de que el componente se cargue completamente, el WillEnterObserver, puedes agregar condiciones para determinar si la aplicación debe continuar a FormView, o si necesita redirigir a los usuarios a MainView.

Cada observador de ciclo de vida es una interfaz, así que implementa WillEnterObserver como parte de la declaración para FormView:

public class FormView extends Composite<Div> implements WillEnterObserver {

El observador WillEnterObserver tiene el método onWillEnter() que webforJ llama antes de enrutarse al componente. Este método tiene dos parámetros: el WillEnterEvent y el ParametersBag.

El WillEnterEvent determina si continuar el enrutamiento al componente con el método accept(), o detener el enrutamiento utilizando el método reject(). Después de rechazar la ruta actual, necesitas redirigir al usuario a otro lugar.

El ParametersBag contiene los parámetros del enrutador de la URL. Utilizarás el ParametersBag en la siguiente sección para crear la lógica condicional para onWillEnter() usando el parámetro id.

El siguiente onWillEnter() es un ejemplo con solo dos resultados:

@Override
public void onWillEnter(WillEnterEvent event, ParametersBag parameters) {

//Agregar lógica condicional
if (<condition>) {

//Permitir que el enrutamiento a FormView proceda
event.accept();

} else {

//Detener el enrutamiento a FormView
event.reject();

//Enviar al usuario a MainView
navigateToMain();
}
}

Usando el ParametersBag

Como se mencionó brevemente en la sección anterior, el ParametersBag contiene el parámetro del enrutador de la URL. Cada observador del ciclo de vida tiene acceso a este objeto, y usarlo en tu aplicación te permite obtener el valor id.

El objeto ParametersBag proporciona varios métodos de consulta para recuperar un parámetro como un tipo de objeto específico. Por ejemplo, getInt() puede obtenerte un parámetro como un Integer.

Sin embargo, dado que algunos parámetros son opcionales, lo que realmente devuelve getInt() es Optional<Integer>. Usar el método ifPresentOrElse() sobre el Optional<Integer> te permite establecer una variable usando el Integer.

Cuando no hay un id presente, el usuario puede continuar dirigiéndose a FormView para agregar un nuevo cliente.

@Override
public void onWillEnter(WillEnterEvent event, ParametersBag parameters) {

//Determinar qué parámetro obtener y verificar si está presente o no
parameters.getInt("id").ifPresentOrElse(id -> {

//Usar el id como una variable
customerId = Long.valueOf(id);

//Cuando no hay un id presente, continuar a FormView para un nuevo cliente
}, () -> event.accept());

}

¿Es válido el id?

En este momento, el WillEnterObserver de la última sección solo acepta el enrutamiento cuando no hay un id presente. El observador necesita realizar una verificación más antes de continuar a FormView: verificar que el id coincide con un cliente existente.

Ahora FormView puede usar CustomerService para confirmar la existencia de un cliente usando el método doesCustomerExist(). Si no hay coincidencia, la aplicación puede rechazar el enrutamiento actual y redirigir al usuario a MainView usando navigateToMain().

Cuando se proporciona un id válido, la aplicación puede usar accept() para continuar el enrutamiento a FormView. Crea un método fillForm() para asignar la variable customer al cliente con el id correspondiente en la base de datos y establecer los valores de los campos:

public void fillForm(Long customerId) {
customer = customerService.getCustomerByKey(customerId);
firstName.setValue(customer.getFirstName());
lastName.setValue(customer.getLastName());
company.setValue(customer.getCompany());
country.selectKey(customer.getCountry());
}

Al igual que al agregar un nuevo cliente, usar la copia de trabajo permite a los usuarios editar los datos del cliente en la interfaz de usuario sin editar directamente el repositorio.

onWillEnter() completado

Las dos últimas secciones entraron en detalle sobre cómo manejar cada resultado para el enrutamiento a FormView usando el ParametersBag y el CustomerService.

El siguiente es el onWillEnter() completado para FormView que usa el ParametersBag para rechazar o aceptar la ruta entrante y llama a otros métodos para llenar el formulario o enviar al usuario a MainView:

@Override
public void onWillEnter(WillEnterEvent event, ParametersBag parameters) {

//Determinar qué parámetro obtener y verificar si está presente o no
parameters.getInt("id").ifPresentOrElse(id -> {
customerId = Long.valueOf(id);
//Verificar si hay un cliente con este id
if (customerService.doesCustomerExist(customerId)) {
//Este cliente existe, así que continuar a FormView e inicializar los campos usando el id
event.accept();
fillForm(customerId);
} else {
//Este cliente no existe, redirigir a MainView
event.reject();
navigateToMain();
}

//No se presentó id, así que continuar a FormView para un nuevo cliente
}, () -> event.accept());

}

Agregando o editando un cliente

La versión anterior de esta aplicación solo añadía nuevos clientes cuando el usuario enviaba el formulario. Ahora que los usuarios pueden editar clientes existentes, el método submitCustomer() debe verificar si el cliente ya existe antes de actualizar la base de datos.

Inicialmente, no era necesario asignar una variable para el id del cliente en FormView, porque los nuevos clientes reciben un id único cuando se envían a la base de datos. Sin embargo, si declaras customerId como una variable inicial en FormView con un valor de id que no esté en uso, permanecerá intacto para nuevos clientes y será sobrescrito en onWillEnter() para los existentes.

Esto te permite usar doesCustomerExist() para verificar si se debe agregar un nuevo cliente o actualizar uno existente.

private Long customerId = 0L;

//...

private void submitCustomer() {
if (customerService.doesCustomerExist(customerId)) {
customerService.updateCustomer(customer);
} else {
customerService.createCustomer(customer);
}
navigateToMain();
}

FormView completado

Aquí está cómo debería lucir FormView, ahora que puede manejar la edición de clientes existentes:

FormView.java
@Route("customer/:id?<[0-9]+>")
@FrameTitle("Formulario de Cliente")
public class FormView extends Composite<Div> implements WillEnterObserver {
private final CustomerService customerService;
private Customer customer = new Customer();
private Long customerId = 0L;
private Div self = getBoundComponent();
private TextField firstName = new TextField("Nombre", e -> customer.setFirstName(e.getValue()));
private TextField lastName = new TextField("Apellido", e -> customer.setLastName(e.getValue()));
private TextField company = new TextField("Empresa", e -> customer.setCompany(e.getValue()));
private ChoiceBox country = new ChoiceBox("País",
e -> customer.setCountry((Customer.Country) e.getSelectedItem().getKey()));
private Button submit = new Button("Enviar", ButtonTheme.PRIMARY, e -> submitCustomer());
private Button cancel = new Button("Cancelar", ButtonTheme.OUTLINED_PRIMARY, e -> navigateToMain());
private ColumnsLayout layout = new ColumnsLayout(

Antes en este paso, usaste un ParametersBag existente para determinar el valor de un id. Crear un nuevo ParametersBag te permite navegar entre clases directamente con los parámetros de tu elección. Usar los datos en la Table es una opción viable para enviar usuarios a FormView con un id de cliente.

Similar al botón, vincular la navegación a una acción elegida por el usuario les permite decidir cuándo ir a FormView. Agregar un listener de eventos a la Table te permite enviar al usuario a FormView con un ParametersBag:

table.addItemClickListener(this::editCustomer);

private void editCustomer(TableItemClickEvent<Customer> e) {
Router.getCurrent().navigate(FormView.class,
ParametersBag.of("id=" + e.getItemKey()));
}

Sin embargo, la clave de los elementos de la Table se genera automáticamente por defecto. Puedes hacer explícito que cada clave se correlacione con el id de un cliente usando el método setKeyProvider():

table.setKeyProvider(Customer::getId);

En MainView, agrega los métodos addItemClickListener() y setKeyProvider() a buildTable(), luego agrega el método que envía al usuario a FormView con un valor para el id en el ParametersBag basado en dónde en la tabla hizo clic el usuario:

MainView.java
@Route("/")
@FrameTitle("Tabla de Clientes")
public class MainView extends Composite<Div> {
private final CustomerService customerService;
private Div self = getBoundComponent();
private Table<Customer> table = new Table<>();
private Button addCustomer = new Button("Agregar Cliente", ButtonTheme.PRIMARY,
e -> Router.getCurrent().navigate(FormView.class));

public MainView(CustomerService customerService) {
this.customerService = customerService;
addCustomer.setWidth(200);
buildTable();
self.setWidth("fit-content")
.addClassName("card")
.add(table, addCustomer);
}

private void buildTable() {
table.setSize("1000px", "294px");
table.setMaxWidth("90vw");
table.addColumn("firstName", Customer::getFirstName).setLabel("Nombre");
table.addColumn("lastName", Customer::getLastName).setLabel("Apellido");
table.addColumn("company", Customer::getCompany).setLabel("Empresa");
table.addColumn("country", Customer::getCountry).setLabel("País");
table.setColumnsToAutoFit();
table.setColumnsToResizable(false);
table.getColumns().forEach(column -> column.setSortable(true));
table.setRepository(customerService.getRepositoryAdapter());
table.setKeyProvider(Customer::getId);
table.addItemClickListener(this::editCustomer);
}

private void editCustomer(TableItemClickEvent<Customer> e) {
Router.getCurrent().navigate(FormView.class,
ParametersBag.of("id=" + e.getItemKey()));
}
}

Próximo paso

Ahora que los usuarios pueden editar los datos del cliente directamente, tu aplicación debe validar los cambios antes de comprometerlos al repositorio. En Validando y Vinculando Datos, crearás reglas de validación y asociarás directamente el modelo de datos con la interfaz de usuario, permitiendo que los componentes muestren mensajes de error cuando los datos sean inválidos.