Celulares en Wicket

Detalles de la solución presentada

Objetos que no dependen de la tecnología de presentación (comunes para cualquier proyecto)

Los siguientes objetos se importaron del proyecto de Celulares resuelto en Arena (ver página de ejemplos).

Objetos de dominio:

  • Celular: abstracción que representa un cliente de una compañía de celulares
  • ModeloCelular: abstracción que representa un modelo de celular de un cliente

Objetos home:

  • RepositorioModelos: instancia y permite conocer todos los modelos de celular que la aplicación conoce
  • RepositorioCelulares: instancia el conjunto de clientes de la compañía / hace la búsqueda de clientes / valida si un número de celular está asignado a otro cliente

Objetos de modelo de vista/application model:

  • BuscadorCelular: recolecta la información del panel de búsqueda y delega en el home (RepositorioCelulares) la búsqueda by example / también sabe limpiar los campos de búsqueda nombre y número de celular

Objetos definidos para proyecto wicket (dependientes de la tecnología)

Pantalla principal: búsqueda de celulares
La pantalla tiene el formato:


Controles y sus mapeos:
BusquedaCelularesPage.html (Vista)BusquedaCelularesPage.java (Controlador)
Usa un Compound Property Model sobre el Buscador Celular
BuscadorCelular (Modelo)
nombre input type=text TextField<String>("nombre")variable nombre
numero input type=textTextField<String>("numero")     variable numero
buscar input type=submitButton "buscar" que delega a ... search() (método heredado de Search<T>) 
limpiar input type=submit Button "limpiar" que delega a.... clear() 
nuevo celular input type=submitButton("nuevo") llama a actualizar
       con un nuevo example 
 
 grilla con tr="results"mapea con results variable results (hereda de Search<T>)
     - nombre
     - número
     - modeloCelular


     - recibeResumenCuenta, input type=checkbox
     - editar , input type=submit

     - eliminar , input type=submit
      - new Label("nombre")
      - new Label("numero")
      - new Label("modeloCelular", new Model<String>(modeloCelular.getDescripcion())
(mapea la descripción del modelo del celular)
       - new CheckBox("recibeResumenCuenta") (*)
       - new Button("editar") que llama a actualizar
con el celular en cuestión (item.getModelObject())
       - new Button("eliminar") que delega al DAO la eliminación, luego le pide al buscador que vuelva a buscar para actualizar la grilla
 

(*) Hay que tener cuidado, originalmente en el html el control checkbox de la grilla estaba disabled=true. Esto tiene como efecto colateral que al editar un cliente el form de Wicket no refresca ese dato en el model (entonces se pierde el dato de si el cliente quiere recibir el resumen de cuenta en domicilio). La solución es deshabilitar el control desde java, mediante un:

    val checkResumen = new CheckBox("recibeResumenCuenta")
    checkResumen.setEnabled(false)

(**) Al generar el formulario se dispara la búsqueda, entonces al cargar la pantalla se muestran los datos de los clientes existentes,también cuando se vuelve de la pantalla de edición/nuevo cliente. BONUS: Ver cómo cambiar el flujo si lo que se quiere es no hacer la búsqueda al comenzar el formulario.

Decisiones más relevantes:

  • Pasaje de info entre pantallas: el panel de búsqueda le pasa al panel de edición cuál es el cliente que quiere modificar, a través de un constructor explícito (igual que en Arena). Esto se ve en el método actualizar del panel de búsqueda, común para edición y nuevo cliente:
    def editar(Celular celular) {
        responsePage = new EditarCelularPage(celular, this) 
    } 

Pantalla de Edición de datos

El formato es el siguiente:

Controles y sus mapeos:
EditarCelularPage.html (Vista)EditarCelularPage.page (Controlador), usa un Compound Property Model sobre el Celular Celular (Modelo)
titulo del caso de uso  new Label("titulo",... depende de si el celular es nuevo o no...)sabe si es un nuevo celular (isNew()) 
nombre, input type=text  new TextField<String>("numero")variable numero 
numero, input type=text new TextField<String>("nombre")variable nombre
modeloCelular, input type=select new DropDownChoice<ModeloCelular> ... (*)variable modeloCelular que apunta a un ModeloCelular 
recibeResumenCuenta, input type=checkbox new CheckBox("recibeResumenCuenta")variable recibeResumenCuenta 
mensajes de error new FeedbackPanel("feedbackPanel")  

Decisiones más relevantes:

  • Uso del combo: se define un DropDownChoice<ModeloCelular>, cada opción va a ser un ModeloCelular posible. Propiedades del combo:
    • id: modeloCelular
    • modelo asociado: la lista de modelos disponibles, que lo resuelve el home; sólo tenemos que decirle 
      • cómo obtener esa lista en el momento de cargar el combo (LoadableModel)
      • cómo vamos a renderizar cada opción, es decir, qué vamos a mostrar para cada ítem (a través de un ChoiceRenderer)
        parent.addChild(new DropDownChoice<Modelo>("modeloCelular") => [
            choices = loadableModel([| Modelo.home.allInstances ])
            choiceRenderer = choiceRenderer([Modelo m| m.descripcion ])
        ])

El choice renderer define que se va a mostrar en cada opción del combo la descripción de cada modelo de celular, esto podríamos reemplazarlo por cualquier otro mensaje que queramos.

Al cargarse el panel en modo Alta, el modelo de un celular nuevo referencia a null, no obstante el fwk se da cuenta y deja el combo sin selección.
Al cargarse el panel en modo Edición, el fwk se encarga de matchear el modelo existente con la lista de opciones posibles y bindea automáticamente la selección del combo.
  • Botón Aceptar: el submit del botón delega 
    • la validación al objeto Celular, si ocurre una excepción de negocio se maneja enviando el mensaje info al feedbackPanel del formulario. El objeto Celular ya tiene toda la info cargada por el usuario (el binding es automático)
    • el agregado/actualización de los datos se delega al home (también puede ocurrir una excepción de negocio, se trata como los demás)
    • si ocurre un error de programa/sistema no mostramos el error, sino que avisamos al usuario que hubo un error (TODO: guardar el error en un archivo de log que pueda ser leído posteriormente por alguien técnico)
      parent.addChild(new XButton("aceptar") => [
            onClick = [|
                try {
                    celular.validar()
                    if (alta) {
                        Celular.home.create(celular)
                    } else {
                        Celular.home.update(celular)
                    }
                    volver()
                } catch (UserException e) {
                    info(e.getMessage())
                } catch (RuntimeException e) {
                    error("Ocurrió un error al procesar el pedido del celular. Consulte al administrador del sistema")
                }
            ]               
        ])
  • Navegación a la pantalla principal: el método volver se encarga de esto: 
    def volver() {
        mainPage.buscarCelulares()
        responsePage = mainPage
    }

El mainPage lo recibimos al construir la página de edición:

new(Celular celularAEditar, BusquedaCelularesPage mainPage) {
    this.mainPage = mainPage
        ...

De esta manera se conserva la información de la pantalla de búsqueda (por ser stateful).