Seaside - Continuations

Intro a Continuations

 Creamos ahora la clase CIUEjemplos (que hereda de WAComponent) para agregar un link a un nuevo componente CIUCalculador:

renderContentOn: html
    html text: html class name.
    html break.
    html anchor
        callback: [self call: CIUContador new];
        with: 'Contador'.


    html break.

    html anchor
        callback: [self call: CIUCalculadora new];
        with: 'Calculadora'


Y entonces creamos un nuevo componente CIUCalculadora, pero esta vez heredamos de WATask y definimos el método "go":

go 
   |sumando1 sumando2|
   sumando1 := (self request: 'Ingrese el primer sumando') asNumber.
   sumando2 := (self request: 'Ingrese el segundo sumando') asNumber.
   self inform: (sumando1 + sumando2) asString.

Como vemos, no tiene el canvas, con lo cual no puede "escribir html".

Registramos la aplicación (esta vez en el Workspace):

WAAdmin register: CIUEjemplos asApplicationAt: 'ejemplosBasicos'

Y vemos entonces en el browser algo bastante mágico al hacer click en el link Calculadora !

  • Al usuario se le presenta una pantalla con un input y el mensaje "Ingrese el primer sumando". Al hacer submit ...
  • se le mostró una nueva segunda pantalla con otro input y el mensaje "Ingrese el segundo sumando". Y al hacer submit...
  • se le mostró una tercer pantalla con el resultado de la suma.
Vemos entonces que cada pantalla es exactamente cada una de las lineas del método "go".
Esas líneas de código mágicas, utilizan mensajes especiales como "request" e "inform",  que se podrían pensar como pasos de una secuencia (o wizard). Y esos pasos serían de hecho pedidos al usuario, que requieren su interacción.

Seaside, a través de un feature llamado Continuations, nos permite escribir entonces todo el comportamiento en un único método, pero él se encarga de ir generando diferentes páginas como pasos de un circuito, y mantiene el estado y la navegación.

Lo que va haciendo es pausar la ejecución de cada linea, y cuando el usuario sigue al siguiente paso, continúa la ejecución. Y de ahí viene el nombre.

Componentes que definen un continuation

Modificaremos el componente para que como primer paso muestre nuestro contador. La idea es que ahora el primer sumando no se obtenga mediante el ingreso manual del usuario (request), sino a través de usar nuestro componente "contador"

go 
   |sumando1 sumando2|
   sumando1 := self call: CIUContador new.
   sumando2 := (self request: 'Ingrese el segundo sumando') asNumber.
   self inform: (sumando1 + sumando2) asString.

En el componente Contador el link que antes era "Volver" ahora devuelve el valor actual del contador

renderContentOn: html
    html anchor callback: [self incrementar]; with: '++'.
    html text: self count asString.
    html anchor callback: [self decrementar]; with: '--'.

    html break.
    html anchor
        callback: [self answer: self count]; with: 'Listo'.


Y al ejecutar el ejemplo, vemos que como primer paso nos lleva al contador, donde a través de los links incrementar/decrementar vamos subiendo y bajando su valor, hasta que clickeamos "Listo". Eso nos lleva al siguiente paso donde ingresamos el segundo sumando y finalmente nos aparece la suma de ambos números.

Creación del componente contador

Creamos un nuevo componente CIUAutoContador con una variable de instancia "contadores" y el método initialize

WAComponent subclass: #CIUAutoContador
instanceVariableNames: 'contadores'
classVariableNames: ''
poolDictionaries: ''
category: 'PAIU-Seaside-PrimerosEjemplos'

initialize
    super initialize.
    contadores := OrderedCollection with: CIUContador new with: CIUContador new with: CIUContador new.

OrderedCollection es el equivalente al List de java (la colección de elementos respeta el orden en que fueron creados). El mensaje with:with:with: permite definirle tres elementos sin necesitar enviar tres mensajes add: por separado.

El renderContentOn :
  • le pide a cada componente contador que se renderice en el html enviando un mensaje do: a la colección de contadores. El método do: es equivalente al foreach de Java/.NET
  • y muestra la suma de todos los contadores que hayamos definido

renderContentOn: html
contadores
do: [ :contador | 
html render: contador.
html break ].
html text: 'El total es:' , self suma asString

El método suma se codifica de esta manera:

suma
    ^ self contadores inject: 0 into: [:acum :contador | acum + contador count].

El método inject: into: nos sirve para operar un conjunto de elementos y obtener un valor final. Le pasamos:
  • un valor inicial
  • un bloque que trabaja con dos elementos, el primero es el que va generando el valor final y el segundo es cada uno de los elementos de la colección: dentro del bloque se produce una operación que genera el nuevo valor intermedio que termina siendo en el último paso el valor final.
Acá vemos cómo se pueden combinar fácilmente los componentes Seaside: dentro del componente padre CIUAutoContador tenemos n componentes contador, cada vez que yo sumo o resto algún contador se refresca el total. Lo interesante es ver que la aplicación no se divide entre acciones del lado del cliente y acciones del lado servidor, todo ese manejo es transparente para el programador.