Arena - Binding avanzado de propiedades

Requerimiento

Tenemos una aplicación que modela encuestas de votación para una zona determinada:



El usuario nos pide ahora que no se pueda votar a los candidatos del PRO.
(Este es un ejemplo didáctico, claro está que no queremos ofender ninguna postura política).
Esto implica inhabilitar el botón "Sumar voto", pero ¿cómo? ¿debemos interceptar la selección de un candidato?

Recordemos que según la visión de Arena podemos establecer un nuevo binding entre la propiedad enabled del botón y una propiedad del modelo que nos diga si se habilita la posibilidad de votar. Esto se debe definir en nuestro modelo de la vista, es decir la Encuesta:

    def getHabilitaVotar() {
        !candidato?.partido.equals(PARTIDO_PROSCRIPTO)
    }

Agregamos el binding en EncuestaWindow:

        new Button(mainPanel) => [
            caption = "Sumar voto"
            // nuevo binding...
            enabled <=> "habilitaVotar"
            //


Pero... ¡no funciona!
Efectivamente, no importa qué candidato seleccionemos... el botón sigue habilitado.
Lo que pasa es que la propiedad "getHabilitaVotar()" es de solo lectura, se calcula en base al candidato... pero cuando el candidato cambia no se vuelve a calcular. 

1. Una primera opción podría ser redefinir el setter de candidato y disparar una falsa notificación de que la propiedad "habilitaVotar" cambió. Así funcionaba en las primeras dos versiones de Arena.

2. Pero una forma más declarativa (sin buscar tanto control del algoritmo) es definir esa dependencia que habilitaVotar tiene respecto del candidato:

    @Dependencies("candidato")
    def getHabilitaVotar() {
        !candidato?.partido.equals(PARTIDO_PROSCRIPTO)
    }

Entonces, la propiedad habiitaVotar se registra como nuevo interesado (observer) en los cambios de la propiedad candidato:

Agregar otra restricción

Ahora nuestro cliente nos pide que por el momento no habilitemos la posibilidad de votar para la zona Capital. Esto agrega una nueva dependencia: no permitimos que el botón se dispare
  • si el partido es x o y 
  • si la zona es z
La propiedad @Dependencies admite más de una propiedad:

    @Dependencies("candidato", "zonaVotacion")
    def getHabilitaVotar() {
        habilitaCandidato && habilitaZona
    }
    
    def habilitaZona() {
        zonaVotacion !== null && !zonaVotacion.descripcion.equalsIgnoreCase(ZONA_INHABILITADA)
    }
    
    def habilitaCandidato() {
        candidato !== null && !candidato.partido.equals(PARTIDO_PROSCRIPTO) 
    }

Las constantes están definidas al comienzo de la clase Encuesta:

class Encuesta {
    public static String PARTIDO_PROSCRIPTO = "PRO"
    public static String ZONA_INHABILITADA = "CABA"

Otro ejemplo de dependencia

Dejamos otro ejemplo de una propiedad calculada en base a otras 3 propiedades:

@Dependencies("profesor", "nombreMateria", "anioCursada")
def getNombreCompleto(){
    profesor + " -> " + nombreMateria + "  " + anioCursada 
}

¿Qué me aporta diseñar el binding?

El binding se define en forma declarativa: no nos importa desde qué evento se está disparando, nos basta con entender cuál es la relación entre elemento gráfico y su modelo correspondiente. Esta decisión de diseño nos simplifica enormemente la construcción de la UI.