Apartado 4.2: Tutorial de Tapestry

Anuncio
4.2 Tutorial de Tapestry
Índice



Introducción
Páginas
Ejemplo pojo-tapestrytutorial



Estructura del subsistema
Capa modelo
Plantillas

Componentes







Validación de campos de entrada
Clases


Sintaxis de expansiones y valores de parámetros
Catálogos de mensajes
Formularios


PageLink, Form, TextField, Label, If
Propiedades
Inyección de páginas
Manejadores de eventos
Ciclo de vida de las páginas
Comunicación de datos entre páginas
Objetivo

Aprender los fundamentos básicos de Tapestry 5
para diseñar e implementar aplicaciones Web
utilizando este framework
Introducción (1)


Proyecto Apache
Framework Java open source para la construcción de
aplicaciones Web



Construido sobre la API estándar de Servlets


La primera versión fue concebida por Howard Lewis Ship en
el año 1999
La versión actual, que será la que nosotros estudiaremos, es
la 5
Puede utilizarse dentro de cualquier servidor de aplicaciones
Java EE “ligero” (solamente necesita soporte para Servlets)
Orientado a componentes

Enfoque: Una aplicación se compone de un conjunto de
páginas Web, cada una de las cuales se construye a partir
de componentes que pueden generar diversos eventos ante
los que la página puede reaccionar
Introducción (2)

Algunas características

Sigue el enfoque POJO








Además de los componentes proporcionados por el
framework permite que el desarrollador construya fácilmente
sus propios componentes a medida
Contiene componentes para interacciones AJAX
Soporte para internacionalización
Soporte para validación de datos de entrada
Soporte para Inyección de Dependencias
Integración con Hibernate


Convenciones de nombrado
Anotaciones
Soporte para implementar aplicaciones Web CRUD
directamente con Hibernate (sin Spring)
Integración con Spring

Útil cuando la lógica de negocio es más compleja y está
implementada con Spring (como es nuestro caso)
Introducción (y 3)

Desde un punto de vista de alto nivel, una aplicación Tapestry
puede ser vista como un conjunto de páginas interactivas que
son gestionadas por el framework



Estás páginas “recuerdan” los valores introducidos por el usuario
en los campos de entrada
Cuando el usuario realiza alguna acción, como por ejemplo un clic
sobre un enlace o un botón, pueden reaccionar a esa acción
ejecutando un método que actúa como manejador del evento
producido por ese componente
Desde un punto de vista de más bajo nivel, cuando la aplicación
Web recibe una petición, utiliza la página apropiada para
generar la respuesta (normalmente una página HTML) y se la
envía al navegador


Cuando el usuario realiza alguna acción sobre la página HTML
recibida (como por ejemplo rellenar un campo de texto y pulsar un
botón), entonces esa información es enviada de vuelta a la
aplicación Tapestry, a través de otra petición
Dependiendo de la lógica de la aplicación y de las acciones del
usuario, la información es pasada a la misma página o a otra
página para procesar la petición y generar la respuesta adecuada
Páginas (1)

Cada página tiene asociada una plantilla y una clase Java



Ambas deben tener el mismo nombre
El objetivo de la clase Java es recibir los eventos relativos a la
página y el objetivo de la plantilla es generar el markup de la
página
Una plantilla es un documento XML que permite generar
markup




En el caso de una aplicación Web, el markup es normalmente
HTML o XHTML, pero también podría ser, por ejemplo, XML (y en
este último caso, la aplicación Web actuaría como un servicio Web
REST)
Tiene extensión .tml (Tapestry Markup Language)
El XML incluirá referencias a componentes (proporcionados por el
framework o desarrollados por el usuario) y también puede incluir
expansiones (expresiones que permiten insertar valores de
propiedades de la clase Java o textos internacionalizados en la
plantilla)
Cuando Tapestry genera el markup para una página, le pide a cada
componente que esté presente en la plantilla de la página que
genere el código correspondiente
Páginas (y 2)

Las clases son POJOs



No extienden de ninguna clase ni implementan ninguna
interfaz del framework
Tienen propiedades para almacenar el estado de la página
Pueden contener métodos para implementar la funcionalidad
de la página



Son invocados cuando se producen determinados eventos en
algún componente de la página
Se utilizan convenciones de nombrado o anotaciones para
asociar métodos a eventos generados por componentes
Cuando se genera el WAR de la aplicación Web, la
clase (fichero .class) y la plantilla (fichero .tml)
deben colocarse en el mismo directorio (que vendrá
determinado por el paquete al que pertenece la
página)
pojo-tapestrytutorial


Para el tutorial de Tapestry utilizaremos el módulo
pojo-tapestrytutorial de los ejemplos
Implementa las capas modelo y Web de la misma
mini-aplicación bancaria implementada para el
tutorial de Servlets y JSPs que, recordemos, tiene los
siguientes casos de uso


Crear una cuenta
Buscar una cuenta a partir de su identificador
Página principal del tutorial

http://localhost:9090/pojo-tapestrytutorial
Tapestry Tutorial Main Page
Clic en Create Account
Create Account Form
Clic en Find Account
Find Account Form
Demo: Creación de una Cuenta

http://localhost:9090/pojo-tapestrytutorial/createaccount
Create Account Form
Create Account Form
Created Account Data
Demo: Control de Errores
Create Account Form

El identificador de
usuario y el balance
son obligatorios
Create Account Form

El identificador de
cuenta debe ser un
número entero y
>=0
Create Account Form

El balance debe ser
un número real y
>=0
Demo: Búsqueda de una cuenta

http://localhost:9090/pojo-tapestrytutorial/findaccount
Find Account Form
Find Account Form
Account Data
Demo: Control de Errores
Find Account Form

El identificador de
cuenta es obligatorio
Find Account Form

El identificador de
cuenta debe ser un
número entero y >=0
Account Data

Debe existir alguna
cuenta con el
identificador introducido
Estructura del Subsistema (1)
pojo-tapestrytutorial
src/main
java
es.udc.pojo.tapestrytutorial
model
account
accountservice
web.pages
*.java
resources
es/udc/pojo/tapestrytutorial/web/pages
webapp/WEB-INF
web.xml
app.properties
*.tml
*.properties
Estructura del Subsistema (y 2)

Cada página



Tiene su clase asociada en el paquete
es.udc.pojo.tapestrytutorial.web.pages dentro
del directorio de código fuente src/main/java
Tiene su plantilla asociada en el directorio
es/udc/pojo/tapestrytutorial/web/pages dentro
del directorio de recursos src/main/resources
De esta forma el fichero .tml de la plantilla
correspondiente a cada página se copiará (durante la
fase de procesamiento de recursos de Maven) al
mismo directorio en el que se genera el fichero
.class resultado de compilar la clase de la página
(dentro de target/classes)
Capa Modelo (1)


La capa modelo de la aplicación es exactamente igual
que la del subsistema pojo-servjsptutorial
Recuérdese que para simplificar su implementación


Se ha optado por la realización de la persistencia en
memoria, en lugar de utilizar una BD
Se ha definido la implementación del servicio directamente,
sin crear una interfaz
Capa Modelo (y 2)

Modelado de entidades



Account
Representa una cuenta
bancaria, con la misma
información que la
comentada para pojominibank
Definición API modelo



Account
- accountId : Long
- userId : Long
- balance : double
+ Constructores
+ métodos get/set
AccountServiceImpl
Define los métodos
createAccount y
findAccount
No utiliza DAOs porque
mantiene las cuentas en
memoria en lugar de
hacerlas persistentes
AccountServiceImpl
- lastAccountId : long
- accounts: Map<Long, Account>
+ createAccount(account : Account) : Account
+ findAccount(accountId : long) : Account
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<!-<!-<!--
====================================================================== -->
Tapestry 5 web application configuration
-->
====================================================================== -->
<web-app>
<display-name>POJO-Examples Tapestry Tutorial</display-name>
<context-param>
<param-name>tapestry.app-package</param-name>
<param-value>es.udc.pojo.tapestrytutorial.web</param-value>
</context-param>
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry5.TapestryFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Filtros


Disponibles desde la versión 2.3 de la API de Servlets
Proporcionan un mecanismo estándar para interceptar
peticiones y respuestas dentro de un contenedor de Servlets,
para transformar o utilizar la información contenida en ellas



Se definen con la etiqueta filter



Permiten capturar una petición y cambiar el flujo de ésta (decidir a
qué Servlet dirigirla, rechazarla, procesarla, etc.)
También permiten realizar transformaciones sobre las respuestas
generadas
filter-name: Indica el nombre del filtro
filter-class: Indica la clase Java que lo implementa
La etiqueta filter-mapping indica cuando debe invocarse un
filtro


filter-name: Nombre del filtro a aplicar
url-pattern: Patrón que indica para qué peticiones debe
aplicarse
Integración en un Contenedor de Servlets


Tapestry se integra dentro de un contenedor de Servlets a
través de un filtro
En el fichero web.xml debe declararse el filtro
org.apache.tapestry5.TapestryFilter




Puede dársele cualquier nombre, aunque suele utilizarse app
Este filtro procesa todas las peticiones relativas a Tapestry y pasa
el resto de peticiones al contenedor de Servlets
Cuando se accede al directorio raíz de una aplicación Tapestry
entonces envía la petición a la página Index
Además, para cada aplicación debe configurarse el paquete raíz
de la aplicación a través de un parámetro con nombre
tapestry.app-package

Tapestry utiliza este paquete para localizar las clases relativas a las
páginas y los componentes creados por el usuario utilizados en la
aplicación


Las clases de los componentes deben ir en el sub-paquete
components
Las clases de las páginas deben ir en el sub-paquete pages (en
nuestro ejemplo es.udc.pojo.tapestrytutorial.web.pages)
Index.tml (1)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xml:space="preserve">
<head>
<title>${message:title}</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<div align="center">
<p><font color="#000099" size="+2" face="Arial, Helvetica, sans-serif">
<b>${message:title}</b></font><br/>
</p>
</div>
Index.tml (y 2)
<div align="center">
<a href="#" t:type="PageLink"
t:page="CreateAccount">${message:createAccount}</a>
<br/>
<a href="#" t:type="PageLink"
t:page="FindAccount">${message:findAccount}</a>
</div>
</body>
</html>
Plantillas (1)

A través de la declaración DOCTYPE se hace referencia
a la DTD de XHTML 1.0, y se indica además que el
elemento raíz es html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

El elemento raíz incluye la declaración de un espacio de
nombres
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xml:space="preserve">


Cualquier elemento o atributo que tenga prefijo t pertenece al
espacio de nombres de Tapestry
En el markup generado, no se incluye la importación del
espacio de nombres de Tapestry
Plantillas (y 2)

Tapestry elimina todos los espacios y retornos de carro de la
plantilla que sean innecesarios para la respuesta generada. Por
ejemplo:




Dentro de un bloque de texto las secuencias de espacios se sustituyen
por un único espacio
Los bloques de texto entre dos tags HTML, compuestos únicamente
por espacios y retornos de línea, son eliminados
Si se examina la respuesta generada (por ejemplo con la opción
“Ver código fuente” de un navegador) puede verse que está todo
en pocas líneas
Para variar este comportamiento debe usarse el atributo XML
estándar xml:space, para indicar a Tapestry que conserve los
espacios y retornos de carro (xml:space="preserve")


El markup generado no incluye este atributo
Útil para depuración mientras se está desarrollando la aplicación
Componentes

Definición de componentes

Es posible incluir componentes dentro de una plantilla utilizando el
elemento correspondiente (dentro del espacio de nombres de
Tapestry)
<t:PageLink
page="CreateAccount">${message:createAccount}</t:PageLink>


Tiene el inconveniente potencial de que se pierde la capacidad de
visualizar la plantilla, como una página HTML, en un navegador
Otra alternativa consiste en incluir la definición del componente
dentro de un elemento HTML estándar utilizando el atributo
t:type para indicar el tipo del componente
<a href="#" t:type="PageLink"
t:page="CreateAccount">${message:createAccount}</a>


En los ejemplos de la asignatura se ha seguido esta aproximación
Los nombres de los componentes no son sensibles a
mayúsculas/minúsculas
Convenciones en el uso del espacio de nombres de Tapestry con atributos

Para favorecer la legibilidad, en los ejemplos de la asignatura se
han seguido las siguientes convenciones en el uso del espacio
de nombres de Tapestry con atributos

Cuando se usa un componente de Tapestry dentro de un
elemento HTML => se ha usado el prefijo del espacio de
nombres de Tapestry en los atributos propios del componente
<a href="#" t:type="PageLink" t:page="CreateAccount”>...</a>

Cuando se usa un componente de Tapestry directamente
=> no se ha utilizado el prefijo del espacio de nombres de
Tapestry en los atributos del componente
<t:if test="account">...</t:if>

En cualquier caso => siempre se ha usado el prefijo del espacio
de nombres de Tapestry para el atributo id en los componentes
que lo necesitan

Para el atributo especial id, el prefijo siempre es necesario
<t:select t:id="language" model="languages"/>
Componente PageLink




Su propósito es visualizar un enlace que apunte hacia otra
página de la aplicación
Se define dentro de un elemento HTML de tipo a
Debe declararse el atributo t:type con valor PageLink
El atributo t:page sirve para indicar el nombre la página a la
que se debe navegar cuando se hace clic en el enlace



Se refiere al nombre lógico de la página
En el markup generado sustituye el valor del atributo href por
un valor apropiado (en función del valor del atributo t:page) y
al igual que cualquier otro componente, elimina el atributo
t:type
Desde la plantilla de la página Index se ha utilizado este
componente para generar dos enlaces hacia las páginas
CreateAccount y FindAccount
Sintaxis de expansiones y valores de parámetros (1)

Los valores de los parámetros de los
componentes se especifican con la sintaxis
prefijo:valor y las expansiones mediante
${prefijo:valor}

Entre otros, existen los siguiente prefijos



literal: indica que valor debe interpretarse como un
literal
prop: indica que valor es el nombre de una propiedad
de la clase de la página (se accederá a ella través del
método get correspondiente)
message: indica que valor es la clave de un mensaje
del catálogo de mensajes
Sintaxis de expansiones y valores de parámetros (2)

En el caso de acceso a propiedades (prefijo prop) es
posible



Referirse a subpropiedades
 Ejemplo: propiedad.subpropiedad invocaría el
método getSubpropiedad sobre el objeto devuelto
por el método getPropiedad de la página
Invocar a un método del objeto que no sea un getter
(utilizando paréntesis a continuación del nombre del
método)
 Ejemplo: propiedad.hashCode() invocaría el método
hashCode sobre el objeto devuelto por el método
getPropiedad de la página
Invocar a un método del objeto pasándole parámetros (se
indican separados por comas dentro de los paréntesis)

Los parámetros son a su vez expresiones que pueden referirse
a propiedades del objeto
Sintaxis de expansiones y valores de parámetros (y 3)

Prefijos por defecto

Un parámetro puede definir un prefijo por defecto, y de
hecho es lo habitual, de manera que en ese caso, el valor
del parámetro se puede especificar sin incluir el prefijo


La documentación de un componente especifica para cada
parámetro su prefijo por defecto
En el caso de las expansiones, el valor por defecto del
prefijo es prop
Catálogos de Mensajes (1)

app.properties
accountId-label=Account Identifier
balance-label=Balance
link-home=Home
userId-label=User Identifier

Index.properties
createAccount=Create Account
findAccount=Find Account
title=Tapestry Tutorial Main Page
Catálogos de Mensajes (2)


Forman parte del soporte de internacionalización que
proporciona Tapestry
Permiten que los textos fijos de las plantillas, en
lugar de escribirlos directamente en ellas, se escriban
en un fichero de mensajes


Ficheros .properties
Existe un catálogo de mensajes general para toda la
aplicación



Mensajes visibles desde todas las páginas
Debe colocarse dentro del directorio WEB-INF
El nombre debe ser el mismo que el que se le dio al filtro de
Tapestry en el fichero web.xml


En el ejemplo debe llamarse, por tanto, app.properties
En este fichero deben colocarse los mensajes que sean
comunes a varias páginas
Catálogos de Mensajes (3)

Es posible definir un fichero de mensajes específicos
para una página




Debe tener el mismo nombre que la página y debe estar en
el mismo directorio que ella (en la estructura de código
fuente están colocados en el mismo directorio que la
plantilla de la página)
Los mensajes definidos en este fichero solo serán visibles
para la página en cuestión
Por ejemplo los mensajes definidos en Index.properties
solamente son visibles para la página Index
Los mensajes se referencian a través de una
expansión utilizando el prefijo message seguido de
la clave del mensaje en el fichero de mensajes


La sintaxis es ${message:clave}
Primero se busca la clave en el fichero de mensajes
específico de la página (si existe) y si no se encuentra,
entonces se busca en el fichero de mensajes general
Catálogos de Mensajes (y 4)

En el apartado 4.3 se explicará cómo pueden
utilizarse los ficheros de mensajes junto con otras
funcionalidades proporcionadas por Tapestry para
visualizar una aplicación en diferentes idiomas
Index.java
package es.udc.pojo.tapestrytutorial.web.pages;
public class Index {}

La clase asociada a la página Index no define
ninguna propiedad ni ningún método
CreateAccount.tml (1)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xml:space="preserve">
<head>
<title>${message:title}</title>
</head>
<body>
<form t:type="Form" t:id="createAccountForm">
<t:errors/>
<table width="100%" border="0" align="center" cellspacing="12">
CreateAccount.tml (2)
<!-- User Identifier -->
<tr>
<th align="right" width="50%">
<t:label for="userId"/>
</th>
<td align="left">
<input t:type="TextField" t:id="userId"
t:validate="required, min=0" size="16" maxlength="16" />
</td>
</tr>
<!-- Balance -->
<tr>
<th align="right" width="50%">
<t:label for="balance"/>
</th>
<td align="left">
<input t:type="TextField" t:id="balance"
t:validate="required,min=0" size="16" maxlength="16"/>
</td>
</tr>
CreateAccount.tml (y 3)
<!-- Create button -->
<tr>
<td width="50%"></td>
<td align="left" width="50%">
<input type="submit" value="${message:create}"/>
</td>
</tr>
</table>
</form>
</body>
</html>
CreateAccount.properties
create=Create
title=Create Account Form
Componente Form

En la plantilla se ha incluido un componente Form
(t:type="Form"), embebido dentro de una etiqueta
form HTML, que dentro contiene otros componentes


Este componente genera un formulario HTML
En la clase de la página puede haber definidos manejadores
asociados a los eventos que genera este componente



Hay una serie de eventos que se generan cuando se visualiza
el componente y otros cuando se envía el formulario (submit)
El componente contiene un elemento HTML input de tipo
submit, que sirve para enviar el formulario
Se le ha asignado un identificador a través del atributo t:id


Como se verá más adelante, este identificador puede ser
utilizado desde el código de la clase de la página para indicar
cuales son los manejadores de eventos de este formulario
t:id es un atributo especial que tienen todos los componentes
y su valor identifica al componente de manera única dentro de
la página
Componente TextField (1)

Dentro del formulario hay dos componentes de tipo
TextField embebidos dentro de dos elementos
input HTML (t:type="TextField")

Este componente está ligado a una propiedad de la clase de
la página

El atributo t:value sirve para indicar el nombre de la
propiedad a la que está asociado



Si no existe ese atributo entonces t:id es el que indica el
nombre de la propiedad a la que está asociado
La clase debe tener métodos get y set para acceder al valor
de la propiedad y establecerlo
Por ejemplo, el primer TextField tiene el valor userId para
la propiedad t:id, por tanto estará asociado a la propiedad
userId, lo que implica que la clase de la página debe tener los
métodos getUserId y setUserId
Componente TextField (y 2)

Dentro del formulario hay dos componentes de tipo
TextField embebidos dentro de dos elementos
input HTML (cont)

Este componente se visualiza como un campo de texto (un
input de tipo text), conteniendo el valor de la propiedad
a la que está asociado


Accede a la propiedad de la clase para recuperar su valor a
través del método get correspondiente
Cuando se envía el formulario al servidor, se le asigna el
valor que contenga en ese momento el campo de texto a la
propiedad a la que está asociada

Establece el valor de la propiedad en la clase a través del
método set correspondiente
Componente Label

El componente Label genera el tag HTML <label> asociado a
un campo de entrada de un formulario


En CreateAccount.tml se han utilizado dos componentes
Label para visualizar la etiqueta asociada a los dos campos de
texto del formulario
En este caso el componente se ha declarado directamente a través
del elemento t:label


El atributo for indica el identificador del componente cuya etiqueta
debe ser visualizada (en nuestro caso los identificadores de los
componentes TextField)
Para obtener el valor de la etiqueta asociada a un componente
TextField

Primero se mira si tiene un atributo t:label y si existe se usa su
valor


Puede ser constante o leerse de un fichero de mensajes
Si no existe ese atributo se genera en función del identificador del
componente (atributo t:id)


Si existe una clave en el fichero de mensajes id-label (sustituyendo
id por el identificador del componente) se usa el valor que tenga
asignado (por ejemplo userId-label=User identifier)
Si no, se genera en función del valor del identificador dividiéndolo en
palabras (por ejemplo userId se dividiría como “User Id”)
Validación de campos de formularios (1)


Tapestry proporciona un conjunto de validadores que
pueden ser aplicados a los campos de entrada de un
formulario a través del atributo t:validate
Para los campos de texto userId y balance se han
utilizado los validadores



required: Comprueba que el valor asignado al campo no
sea nulo ni la cadena vacía
min: Comprueba que el valor numérico asignado al campo
no sea menor que la cantidad indicada (0 en este caso)
Por defecto, cuando se produce algún error de
validación en el envío de un formulario se vuelve a
mostrar la misma página que lo contiene

Este comportamiento puede variarse proporcionando un
manejador para el evento adecuado
Validación de campos de formularios (y 2)


Los validadores básicos proporcionados por Tapestry realizan las
validaciones en el navegador (JavaScript) y también en el lado
servidor (por seguridad y por si el cliente tiene deshabilitado
JavaScript)
Para visualizar los mensajes de error (lado servidor) asociados a
los validadores utilizados dentro de un formulario se emplea el
componente Errors (elemento t:errors)



Otros validadores proporcionados por Tapestry son




Debe colocarse dentro del componente Form que incluye a los
componentes que tienen los validadores
Es recomendable utilizarlo siempre por si el navegador tiene
deshabilitado JavaScript
minlength/maxlength: Comprueban que la longitud de la
cadena asignada al campo no sea menor/mayor que la cantidad
indicada
max: Comprueba que el valor numérico asignado al campo no sea
mayor que la cantidad indicada
regexp: Comprueban que la cadena asignada al campo concuerda
con un patrón
Es posible añadir nuevos validadores
CreateAccount.java
public class CreateAccount {
@Property
private Long userId;
@Property
private Double balance;
@InjectPage
private AccountCreated accountCreated;
Object onSuccess() {
Account account = new Account(userId, balance);
new AccountServiceImpl().createAccount(account);
accountCreated.setAccountId(account.getAccountId());
return accountCreated;
}
}
Propiedades

La clase CreateAccount contiene dos propiedades
asociadas a los dos campos de texto presentes en la
plantilla de la página: userId y balance


Como se había dicho con anterioridad el acceso a esas
propiedades se hace a través de métodos get/set
La anotación @Property permite anotar campos para los
que deben crearse métodos get/set


De esta forma no es necesario escribir esos métodos
Si se usa la anotación, los métodos get/set no pueden existir
Inyección de páginas

Utilizando el patrón de inyección de dependencias, la
anotación @InjectPage permite inyectar una
página en una propiedad de otra página

Tiene un atributo opcional (value) que debe ser utilizado
cuando el tipo de la página a inyectar no puede ser
determinado (e.g. el tipo de la propiedad en la que se
inyecta es una interfaz que cumple la página a ser
inyectada)
Formularios

El componente Form genera una serie de eventos (tanto a la hora de
visualizarse como a la hora de hacer un submit de él) para los cuales
se puede proporcionar un manejador en la clase de la página que
contiene el formulario


En CreateAccount se proporciona un manejador para el evento
“success”, que es emitido una vez que el formulario se ha enviado al
servidor y sus campos de entrada se han validado correctamente
Para procesar un evento, existen dos posibilidades





Definir un método con el formato onXxx, siendo Xxx el nombre del evento (e.g.
onSuccess)
Usar la anotación @OnEvent sobre el nombre de un método (que puede tener un
nombre arbitrario)
NOTA: en el código de los ejemplos, se ha preferido la primera opción
(convención de nombrado)
El método onSuccess actúa como manejador del evento “success” para
todos los formularios de la página
Si hubiese más de un formulario podría especificarse un manejador
diferente para cada uno en función de su identificador (atributo t:id)


A través del nombre del método: onSuccessFromCreateAccountForm
Anotando el método: @OnEvent(value = “success", component =
“createAccountForm")
Manejadores de eventos (1)

Un manejador de un evento puede devolver


Nada (tipo de retorno void o cuando devuelve null): En
este caso la misma página vuelve a ser visualizada
Una nueva página de la aplicación a ser visualizada







String: Debe ser el nombre lógico de la página
Class: La clase de la página
Una instancia de la clase de la página (útil cuando la página
debe ser configurada de alguna manera)
Link: Una implementación de la interfaz Link que será
convertida a una URL a la cual será redirigido el cliente
URL: El cliente será redirigido a esa URL
StreamResponse: Un objeto de tipo Stream que permite
enviar datos binarios al cliente (e.g. fichero PDF)
Cualquier otro objeto devuelto producirá un error
Manejadores de eventos (y 2)

El manejador del evento “success” de la página
CreateAccount (método onSuccess)

Crea un objeto Account a partir de las propiedades
userId y balance de la página



Llama al servicio del modelo para crear la cuenta
Utiliza la instancia de la página de tipo AccountCreated
que ha sido inyectada y establece el identificador de la
cuenta que se acaba de crear a través del método
setAccountId


Serán los valores (ya validados) que se hayan introducido en
los campos de texto del formulario
Esa página lo necesita puesto que muestra el identificador de la
cuenta creada
Devuelve la instancia de la página de tipo
AccountCreated

Por tanto, esa será la siguiente página a ser visualizada
AccountCreated.java
package es.udc.pojo.tapestrytutorial.web.pages;
public class AccountCreated {
private Long accountId;
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
Long onPassivate() {
return accountId;
}
void onActivate(Long accountId) {
this.accountId = accountId;
}
}
NOTA: No se ha utilizado la
anotación @Property sobre el
campo accountId porque desde
la clase CreateAccount se llama
al método setAccountId (y por
tanto el método debe existir en
tiempo de compilación)
Ciclo de vida de las páginas

Para mejorar el rendimiento Tapestry mantiene un pool de
instancias para cada página


Cuando se solicita una página, obtiene una instancia del pool para
atender esa petición
Una vez atendida la petición la página es devuelta al pool



Antes de devolverla al pool las variables de la instancia son inicializadas
para borrar los datos específicos de esa petición
Hay que tener en cuenta que cada vez que se trabaja con una
página puede ser una instancia distinta
Cuando el manejador del evento “success” de la página
CreateAccount devuelve una instancia de la página
AccountCreated (en la que ha establecido el identificador de
la cuenta que se ha creado)



Realmente lo que se hace es indicarle al navegador que haga una
nueva petición sobre la página AccountCreated
Cuando esa petición llega a la aplicación, se coge una instancia de
la página AccountCreated del pool, y la utiliza para generar la
respuesta
PROBLEMA: La instancia que se ha cogido del pool no tiene
establecido el identificador de la cuenta que se ha creado !!!
Comunicación de datos entre páginas

Existen dos mecanismos que permiten pasar datos
entre páginas

Se puede hacer que un campo de la página sea persistente




Los campos persistentes se anotan con @Persist
El valor de estos campos se guarda en la sesión
(HttpSession)
Cada vez que se obtiene del pool una página que tenga un
campo persistente se busca en la sesión el valor del campo y
se establece en la página
Desventajas:



Menor escalabilidad al hacer uso de la sesión para almacenar
datos
No es posible hacer bookmarks a esas páginas
Se puede utilizar el “contexto de activación de páginas”


Requiere más código pero no utiliza la sesión y permite hacer
bookmarks a las páginas
Es la opción elegida en el ejemplo
Contexto de Activación de Páginas (1)


Para hacer una página “activable” basta con añadirle
los métodos onActivate y onPassivate
Cuando una página implementa estos métodos

Antes de ser devuelta al pool se invoca el método
onPassivate


Este método debe devolver los datos que necesitará la página
para visualizar los datos deseados
Estos datos se incluyen en la URL a la que se redirige el cliente
junto con el nombre de la página



No se hace uso de la sesión
Es posible hacer un bookmark a esa URL
Cuando se recibe la petición del cliente, se obtiene una
nueva instancia del pool y se llama al método onActivate
pasándole los datos que vienen incluidos en la petición

Recibe, por tanto, los datos que devuelve onPassivate
Contexto de Activación de Páginas (y 2)

El método onPassivate de AccountCreated devuelve el
identificador de la cuenta creada

Cuando el método onSuccess de la clase CreateAccount
devuelve la instancia de AccountCreated (suponiendo que el
identificador de la cuenta creada es 1) la URL a la que se redirige
al cliente es
http://host:port/pojo-tapestrytutorial/accountcreated/1



El método onActivate recibe el identificador de la cuenta
creada y lo almacena en la variable accountId (invocando al
método setAccountId)
Los datos que devuelve onPassivate y recibe onActivate
tienen que ser tipos simples (o sus contrapartidas objetuales) o
String
Es posible pasar más de un dato entre páginas


onPassivate puede devolver un Object[]
onActivate puede recibir más de un parámetro

Cada parámetro, de izquierda a derecha, corresponde a un elemento
del array Object[], y Tapestry hace automáticamente un cast al tipo
de parámetro
AccountCreated.tml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xml:space="preserve">
<head>
<title>${message:title}</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<div align="center">
<p>
<font color="#000099" face="Arial, Helvetica, sans-serif">
<b>${message:bodyMessage-1} ${accountId} ${message:bodyMessage-2}</b>
</font>
</p>
</div>
<br/>
<a href="#" t:type="PageLink" t:page="Index">${message:link-home}</a>
<br/>
</body>
</html>
AccountCreated.properties
bodyMessage-1=Account number
bodyMessage-2=created sucessfully
title=Created Account Data
Creación de una cuenta (AccountCreated)

En AccountCreated.tml se usa la expansión
${accountId} y por tanto se llama al método
getAccountId de la clase AccountCreated, que
en este caso contendrá el identificador de la cuenta
que se ha creado

Recuérdese que ese valor se le pasó a través del mecanismo
de activación de páginas
FindAccount.tml (1)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xml:space="preserve">
<head>
<title>${message:title}</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<form t:type="Form" t:id="findAccountForm">
<t:errors/>
<table width="100%" border="0" align="center" cellspacing="12">
FindAccount.tml (2)
<!-- Account Identifier -->
<tr>
<th align="right" width="50%">
<t:label for="accountId"/>
</th>
<td align="left">
<input t:type="TextField" t:id="accountId"
t:validate="required, min=0" size="16" maxlength="16"/>
</td>
</tr>
<!-- Search button -->
<tr>
<td width="50%"></td>
<td align="left" width="50%">
<input type="submit" value="${message:find}"/>
</td>
</tr>
</table>
</form>
</body>
</html>
FindAccount.java
public class FindAccount {
@Property
private Long accountId;
@InjectPage
private AccountDetails accountDetails;
Object onSuccess() {
accountDetails.setAccountId(accountId);
return accountDetails;
}
}
Búsqueda de una cuenta (FindAccount)

FindAccount.tml

Contiene un formulario con un campo de texto para
introducir el identificador de la cuenta buscada


Se valida que tenga algún valor y que sea mayor que 0
FindAccount.java



Tiene una propiedad para almacenar el identificador de la
cuenta buscada
Se le inyecta una página de tipo AccountDetails
(utilizada para mostrar los detalles de una cuenta)
Tiene un manejador para el evento “success” que utiliza la
página inyectada para establecerle el identificador de la
cuenta buscada y la devuelve como resultado
AccountDetails.java
public class AccountDetails {
private Long accountId;
private Account account;
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public Account getAccount() {
return account;
}
void onActivate(Long accountId) {
this.accountId = accountId;
try {
account = new AccountServiceImpl().findAccount(accountId);
} catch (InstanceNotFoundException e) {
}
}
Long onPassivate() {
return accountId;
}
}
Búsqueda de una cuenta (AccountDetails.java)

Utiliza el contexto de activación de páginas

En el método onPassivate se devuelve el identificador de
la cuenta buscada

Cuando se envía el formulario de búsqueda de cuenta,
suponiendo que se está buscando la cuenta con identificador 1,
la URL a la que se redirige el cliente será
http://host:port/pojo-tapestrytutorial/accountdetails/1


En el método onActivate




Es posible hacer un bookmark sobre esa URL
Se llama al modelo para recuperar una cuenta con el
identificador especificado por el usuario
Si la cuenta se encuentra, se guarda en la variable account
Si la cuenta no se encuentra, se captura la excepción y no se
hace nada (simplemente se deja a null la variable account)
Define el método getAccount para permitir el acceso a la
propiedad account
AccountDetails.tml (1)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
xmlns:p="tapestry:parameter"
xml:space="preserve">
<head>
<title>${message:title}</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<div align="center">
<p>
<font color="#000099" face="Arial, Helvetica, sans-serif">
<b>${message:header}</b>
</font>
</p>
</div>
<div align="center">
AccountDetails.tml (2)
<t:if test="account">
<table border="1" align="center" width="35%">
<tr>
<th width="60%">${message:accountId-label}</th>
<td width="30%" align="center">${account.accountId}</td>
</tr>
<tr>
<th width="60%">${message:userId-label}</th>
<td width="30%" align="center">${account.userId}</td>
</tr>
<tr>
<th width="60%">${message:balance-label}</th>
<td width="30%" align="center">${account.balance}</td>
</tr>
</table>
<p:else>
<font color="#000099" face="Arial, Helvetica, sans-serif">
<b>${message:accountNotFound}</b>
</font>
</p:else>
</t:if>
AccountDetails.tml (3)
</div>
<br/>
<a href="#" t:type="PageLink" t:page="Index">${message:link-home}</a>
<br/>
</body>
</html>
Componente If


El componente If muestra su contenido si se cumple una condición
El parámetro test recibe un valor de tipo booleano



Si es true se muestra el contenido del componente
El prefijo por defecto para este parámetro es prop
Permite otros tipos de valores no booleanos





Object: true si no
Number: true si no
Collection: true
String: true si no
es null
es 0
si no está vacía
es la cadena vacía y no es el literal “false”
Tiene un parámetro opcional else que permite proporcionar un
contenido alternativo a ser mostrado si la condición no se cumple

El espacio de nombres tapestry:parameter sirve para pasar
parámetros “de bloque” a componentes



En Tapestry, un bloque (block) es un trozo de plantilla
Normalmente se le asigna el prefijo “p”
Con este espacio de nombres definido, los parámetros de bloque se pasan a
través de un elemento que se construye usando el prefijo “p:” seguido del
nombre del parámetro
<p:else>…</p:else>
Búsqueda de una cuenta (AccountDetails.tml)

Utiliza un componente If para determinar si la
propiedad account de la página (accedida a través
del método getAccount) es distinta de null


Si es distinta de null se usan expansiones para
mostrar los valores de los campos de la cuenta


Recuérdese que el prefijo por defecto del parámetro test
del componente If es prop
Por ejemplo ${account.accountId} inserta el resultado
de invocar a getAccount().getAccountId()
Si es null significa que no se ha encontrado
ninguna cuenta con el identificador introducido por el
usuario y se muestra un mensaje indicando que no
se ha podido encontrar dicha cuenta
Descargar