jueves, 25 de febrero de 2010

GeneXus: Convirtiendo aplicaciones de escritorio a web

GeneXus

Esta entrada está dedicada a documentar el proceso de migración de una aplicación Genexus 9 desde el escritorio hacia el entorno web. Después de la reciente creación del grupo de desarrollo web (del cual formo parte, por cierto ;o) se han comenzado a migrar parcial o totalmente algunas aplicaciones y módulos de un sistema para escritorio. Aquellos que conocen la filosofía de Genexus 9 deberían pensar que esto es bastante fácil, considerando que la herramienta genera código y posee conceptos abstractos. Sin embargo, utilizando la versión 9.0 en la práctica se ha detectado que este proceso implica varios retos.

Sin más retórica, les presento las diferencias existentes entre ambos ambientes y, en algunos casos, decisiones que pueden ayudar durante la conversión. De más está decir que la principal intención es promover la búsqueda de soluciones eficientes y adecuadas, y documentarlas para que sean del conocimiento de todos.

La función Confirm no funciona

Actualizado 26/02/2010 09:58:20 AM basado en un artículo de Nestor Lesna

Normalmente en las versiones de escritorio está disponible la función Confirm. Es muy útil cuando se necesita una confirmación para realizar una acción que tenga un impacto importante para el usuario (ya sea porque se demora mucho, porque se pierde información que podría ser útil, ...). El código para escritorio seria más o menos así

Confirm("Desea continuar con esta acción ?",N)

if confirmed()
  Call(WAction)
endif

En la web esto no está disponible, por lo que el generador emite errores en tiempo de compilación. Por tanto hay que recurrir a los eventos de JavaScript para lograr un efecto similar. Supongamos que tenemos un botón btn que, al presionarlo, deplegará la confirmación. Lo primero que hay que hacer es sobrescribir el evento OnClick del botón dinámicamente al cargar la página (i.e. evento Start). Esto sería más o menos así

Event Start
  btn.JSEvent("onclick", "confirm('Desea continuar con esta acción ?')")
EndEvent

Event btn_Click
  Call(HAction)
EndEvent

Como se puede observar, el evento del botón se implementa como de costumbre. Lo que sucede en este caso es que se invoca la funcion confirm disponible en JavaScript, por lo tanto nos muestra una ventana con el mensaje Desea continuar con esta acción ? y dos botones Aceptar y Cancelar. El código del evento del botón se ejecuta solamente si se hace click sobre el primero (i.e. Aceptar ;o).

La función cursor no funciona

La función cursor no está en el ambiente web. Por tanto no es posible implementar (tan fácilmente) prompts que dependan del contexto y de las acciones o selección del usuario. Por tanto un ejemplo como el siguiente hay que re-implementarlo para la web.

Event 'Prompt' 
  if cursor(&Cod)
    call('Wselcod', &Cod)
  endif
  If cursor(&Category)
    call('Wselcat', &Cod)
  endif
Endevent

Múltiples puntos de entrada

En el ambiente de escritorio es posible lograr que un usuario utilice los paneles en una secuencia que se establece en el diseño de la aplicación. Por tanto hay algunos chequeos que no es preciso realizar puesto a que se puede asumir que si el usuario ya llegó a una pantalla conocida, entonces ya debió haber pasado por otra. En la web esto no es así, sino que existen un conjunto de URL que pueden ser visitadas en cualquier momento. Por tanto hay que verificarlo todo obligatoriamente por todos lados.

Una solución puede ser implementar uno o varios procedimientos que hagan las verificaciones globales (e.g. usuario autentificado) o locales (e.g. se pasó por el panel X antes de llegar al Y) e incluirlos al principio del evento Start. Si se detectan fallas, mostrar una página de error, autentificación o de otro tipo.

Los paneles web no aceptan estructuras como parámetros

GeneXus Wiki

Para ilustrar esto utilizaré el ejemplo clásico de la estructura que contiene datos globales y, por tanto, se le pasa como parámetro a todos los paneles. En este caso el especificador del Genexus 9 emite un error debido a que los paneles web no aceptan estructuras como parámetros. Sin embargo en el código de la aplicación de escritorio esta estructura debe estar por todos lados.

Para este caso específico, la información se debe almacenar en la sesión web (objeto de tipo WebSession) y debemos recuperarla antes de hace cualquier otra cosa con el panel. Como esto resulta una tarea repetitiva, se puede implementar un procedimiento que llene la estructura a partir de la sesión. El mismo debe ser llamado al principio del método 'Start' de los paneles web para asegurar que en lo sucesivo se cuente con la información preparada de la misma forma que estaba en el código original.

La sesión sólo acepta cadenas de caracteres

El consejo anterior tiene otra limitación: solo se pueden incluir cadenas de caracteres en la sesión. Esto quiere decir que si se pasa un valor numérico o fecha, o de otro tipo, entonces hay que tomar precauciones adicionales y realizar conversiones (quizás dentro del procedimiento). Si se intercambian estructuras o colecciones, entonces el esfuerzo es mayor porque hay que convertir cada dato que contiene cada elemento y después hay que colocarlo en un lugar conocido en la sesión para posteriormente hacer el proceso inverso.

El comando return no devuelve parámetros de salida

En el escritorio se tienen los datos en la memoria y esto implica que se pueden pasar de un procedimiento o web panel hacia el lugar donde se invoca. En el ambiente web esto no ocurre así con los paneles. Cuando se invoca un panel web desde otro, se pierden los valores de origen. En este caso el retorno consiste en volver a invocar el panel de origen (en vez de copiar los valores retornados y devolverlos como en el entorno de escritorio). Es por esto que hay que tomar precauciones adicionales con todos los retornos, y esto implica un esfuerzo adicional cuando un mismo panel es reutilizado muchas veces por muchos otros. También se puede llegar a la conclusión de que algunos valores pueden ser eliminados de la interfaz de usuario al regresar.

Todavía busco una solución satisfactoria para este caso. :o(

Los eventos funcionan de manera diferente

Si bien en las aplicaciones para Windows es posible validar campos individuales utilizando el evento IsValid, en las aplicaciones web este evento no está disponible. Esto implica que es preciso incluir más validaciones en lugares diferentes, y se deduce que hay que escribir más código. También está el caso de los radio buttons. Si se utiliza este componente para seleccionar una entre varias alternativas, hay que tener en cuenta que el evento Click no funciona. La solución que he encontrado para suplir esta última carencia es remplazar estos controles por combobox.

El tipo de datos retornado por Value

Muy frecuentemente, especialmente cuando se cancela una acción, se borra el valor que tiene la variable sustituyéndolo por uno predeterminado. Esto se haría más o menos así &variable = &variable.Value(1) . En aplicaciones de escritorio el tipo del datos que devuelve el método Value se corresponde con el de la variable (e.g. si &variable es de tipo Numeric(2.0) entonces &variable.Value(1) es también de tipo Numeric(2.0)). En la web la diferencia es que el método antes mencionado devuelve siempre un dato de tipo Character. Esto implica que hay que hacer la conversión explícitamente (i.e. más código).