Wicket - Clase 1

22/10/2013 - UNSAM

Apache Wicket: un framework web.

Si tengo que contarle a otro lo que hice en Grails ¿cuál es la metáfora que está detrás? ¿por dónde arranco? Hay que definir vistas, dominio, controllers. MVC, aunque Arena y Grails son MVC y no se parecen en nada:

  • en Arena teníamos controllers muy chicos
  • en Grails el controller es más grande: tiene muchos métodos, donde cada uno representa una acción del usuario.
  • Un GSP no es un objeto, no está vivo, es un template que genera HTML, terminan siendo objetos que viven en la VM del navegador.
  • la vista de Arena sí son objetos. 
Esta idea de que generamos HTML del lado de la vista nos está mostrando la metáfora de la app: está orientado a acciones de usuario. Cuando se dispara una acción se ejecuta un pedido que por lo general no tiene estado. Esto resulta contradictorio con la idea de objetos, que puede inducir a que el controller tenga más responsabilidades de las que les corresponda.

Entonces... en lugar de utilizar Frameworks orientados a acciones como Stripes, Struts2, Tapestry, Play vamos a ver un framework orientado a componentes: Apache Wicket. Otro similar: Echo3. ¿Componentes como objetos? En Wicket sí.

Bienvenidos a 

Unidad 4: Desarrollo web con componentes.

Archetypes de Maven: al correrlo me genera un proyecto con configuración propia. Lo corrió Javi desde el shell Linux y luego tiró Import > Existing Maven project desde Eclipse (otra opciòn es correr el goal mvn eclipse:eclipse).

Las app java tienen como convención el directorio
src/main/webapp, todo lo que puedo ver por URL, salvo el directorio web-inf.
En web-xml se guarda información sobre la app web, afortunadamente no tenemos que tocar demasiado.

Sobre el IDE, conviene
  • no usar el de Grails, sino 
  • un Eclipse J2EE (JEE, not el Classic) que viene con WTP + m2e + m2e-wtp (para que puedan utilizar WTP y maven) + plugin Xtend + Tomcat.
Levantamos el web server.
http://localhost:8080/holamundowicket/
No anda, porque hay que buildear a mano: hay que entrar a una clase xtend, espacio y grabar y recompila los .xtend a .java.

¿Cómo es una app Wicket?
1) Toda app tiene un WicketApplication.xtend que hereda de WebApplication y define la página inicial en el método getHomePage(). Si tuvieran componentes de persistencia es aquí donde deberían inicializarlos. Es la única clase que está referenciada desde el web.xml

2) HomePage extiende de WebPage, que a su vez contiene otros componentes: Label, etc. Todos los componentes de Wicket tienen un id, con su par html...

3) El html por convención tiene el mismo nombre que la página. La filosofía de Wicket dice que uno no puede poner código en el html (no quieren ensuciar la vista con código). Esto permite que los diseñadores gráficos maqueten sin que tengamos que agregarle después el código necesario para que el contenido sea dinámico. De esto se va a encargar el motor Wicket, matcheando Page con html y produciendo un nuevo html dinámico.

Page 
  |---> Label: "version"

<html>
      version <span wicket:id="version">1.5-SNAPSHOT</span>

Wicket reemplaza en cada pedido al server el valor 
<html>
      version <span>1.5.10</span>

1.5.10 es el valor del objeto Label.getVersion().
Ir de una página a otra no representa mayores problemas porque la página es un objeto, la puedo instanciar con parámetros y le paso la información que necesito.

IMPORTANTE: Para trabajar con Xtend hay que poner
extension WicketExtensionFactoryMethods = new WicketExtensionFactoryMethods() 
en todas las pages.

Generamos un contador:
private int contador = 0

ponemos un Label con el String.valueOf(contador). Y creamos un XLink. 
Por qué necesitamos utilizar un XLink: la estrategia de Wicket es heredar un Botón/Link para decirle qué hacer. Como en Xtend no necesitamos hacer eso pero Wicket está pensado para Java, Javi le creó una pequeña capa de abstracción para definir comportamiento en forma más cómoda:

this.addChild(
   new XLink("incrementar") => [
      setOnClick [  |  this.contador = this.contador + 1 ] 
   ])

NUNCA USAR this.add(...) porque se mambea. 

En el html agregamos.
<a wicket:id="incrementar" href="#">Incrementar</a> 

No refresca, porque cuando construimos el Label, apunta a un string fijo que no cambia, no es dinámico. Wicket tiene una idea de binding, para eso cada control tiene que tener un modelo: (un objeto de una clase que implementa un IModel), con dos métodos: getObject/setObject. El Label no necesita definir el setObject, el TextField sí.

Cambiamos entonces el modelo del label a una propiedad contador de la HomePage:
val label = new Label("contador", new PropertyModel(this, "contador")))

y cambiamos el private int contador por un @Property int contador...
en realidad deberíamos tener un modelo, no poner el modelo en la misma ventana.

Recargar toda la pantalla es molesto, cambiamos el pedido para que se haga por Ajax:
val link = new XAjaxLink("incrementar")
link.setOnClick [ target | this.contador = this.contador + 1
                           target.add(label) ] 
this.addChild(link) 

target es de AjaxRequestTarget.

¡¡Pincha!!
Le falta un setOutputMarkupId = true en el label:
label.outputMarkupId = true

Entonces sí funciona: como el modelo de objetos de Wicket es muy completo no tenemos que tocar html, ni javascript: refrescá este componente. Y listo.
Por último vemos que en el Browser se puede activar el Wicket Ajax Debug para ver lo que recibimos en el lado del cliente + una consola para evaluar pedidos.