En "web tradicional" las abstracciones están orientadas más al protocolo que usamos y no a los componentes que manejamos. ||Wicket busca hacer menos tedioso y más transparente la parte de presentación, combinando las ideas que vimos de las unidades anteriores:
El proyecto que vamos a generar se puede descargar de los ejemplos: es el contador-ui-wicket, para ver el avance de cada iteración hay un tag específico.
Creamos un proyecto wicket desde cero siguiendo el instructivo de la cátedra.
¿Qué cosas intersantes aparecen?
class CalculadoraPage extends WebPage { Calculadora calculadora
Esto funciona como uno espera: cada vez que un usuario accede a la página de la calculadora, esa página tiene estado: un objeto Calculadora. Claro, eso se mapea como un session.getAttribute("calculadora"). Pero eso se mantiene transparente para nosotros.
new(final PageParameters parameters) { ...
Dentro del constructor definimos qué componentes va a tener la vista (sí, componentes que se programan en Java, donde valen todas las herramientas de Objetos nuevamente). Esto se parece más a lo que hemos visto en la Unidad 2, sólo que con los template methods que teníamos la construcción de la vista se reducía a redefinir ciertos métodos.
Una WebPage extiende de Page (que a su vez extiende de Component). La page define un set de components, como los Label, TextField (se forma así un Composite Pattern).
<<TODO: Hacer un diagrama.>>
Wicket es un Framework Orientado a componentes que (como todos) dice ser MVC.
Cada web page tiene asociado un html
La configuración del web-inf.xml es en la mayoría de los casos redundante: ¿para qué forzarlo a definir si en el 95% de los casos no cambia? Por otra parte, si definimos un contrato y no permitimos hacerlo configurable eso nos impide poder cambiar el default. La solución es tener una convención que evite tener que definir un montón de archivos xml de cosas que dicen una y otra vez lo mismo, y usar el xml para cuando necesitemos cambiar la configuración default. ¿Qué convenciones tiene Wicket?
Avanzamos sobre un ejemplo partiendo del "hola mundo" del archetype de maven:
<html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" > ... <body> ... <span wicket:id="contador">contador</span> </body> </html>
Unexpected RuntimeException
WicketMessage: Unable to find component with id 'contador' in [Page class = com.uqbar_project.edu.progui.claseInicial_ui_wicket.HomePage, id = 0, version = 0]. This means that you declared wicket:id=contador in your markup, but that you either did not add the component to your page at all, or that the hierarchy does not match.
Leemos el mensaje... es claro y se entiende: definimos un wicket:id pero no agregamos el componente en el controller.
<html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" > <body> <span wicket:id="contador">contador</span> <br/> <a href="#" wicket:id="sumar">Sumar</a> <a href="#" wicket:id="restar">Restar</a> </body> </html>
ContadorPage.html
class ContadorPage extends WebPage { extension WicketExtensionFactoryMethods = new WicketExtensionFactoryMethods int contador new() { this.addChild(new Label("contador", new PropertyModel(this, "contador"))) this.addChild(new XLink<Object>("sumar") => [ onClick = [| contador = contador + 1 ] ]) this.addChild(new XLink<Object>("restar") => [ onClick = [ contador = contador - 1 ] ]) } }
ContadorPage.xtend
add(new Link<Object>("restar") { @Override public void onClick() { contador--; } });
this.addChild(new XLink<Object>("restar") => [ onClick = [ contador = contador - 1 ] ])
¿Por qué es esto?
Si reemplazamos el Label por:
this.addChild(new Label("contador", "" + contador))
no se refresca el valor del contador. Si queremos que al actualizar la página se vea reflejado el cambio, tenemos que usar un model de wicket
Para aprender más sobre models de Wicket, pueden ver esta página.
Para aprender más sobre controllers de Wicket, pueden ver esta página.
class ContadorPage extends WebPage { extension WicketExtensionFactoryMethods = new WicketExtensionFactoryMethods int contador new() { val labelContador = new Label("contador", new PropertyModel(this, "contador")) this.addChild(labelContador) this.addChild(new XLink<Object>("sumar") => [ onClick = [| contador = contador + 1 ] ]) this.addChild(new XAjaxLink<Object>("restar") => [ onClick = [ target | contador = contador - 1 target.add(labelContador) ] ]) } }
Unexpected RuntimeExceptionRoot
cause: java.lang.IllegalArgumentException: cannot update component that does not have setOutputMarkupId property set to true. Component: [Component id = contador]
¿Cómo funcionaba Ajax?
new() { val labelContador = new Label("contador", new PropertyModel(this, "contador")) this.addChild(labelContador) labelContador.outputMarkupId = true ...
Entonces ahora sí Wicket sabe que el target va a sobreescribir el label (lo reconoce por el id de wicket).
Observaciones:
Observaciones:
Paso 1: páginas nuevas sin estado compartido
Observaciones:
Paso 2: ir a página nueva, pasando estado
Paso 3: volver a la misma instancia de la página original
Para aprender más sobre navegación de una página a otra, podés ver esta página.