Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PlugIn Tapestry

pico.dev
January 17, 2014

PlugIn Tapestry

Resumen corto sobre varias de las caracterśiticas del framework Apache Tapestry y parte de lo comentado en el libro PlugIn Tapestry (http://goo.gl/Vx8icI)

pico.dev

January 17, 2014
Tweet

Other Decks in Programming

Transcript

  1. PlugIn Tapestry Desarrollo de aplicaciones y páginas web con Apache

    Tapestry @picodotdev http://picodotdev.github.io/blog-bitix/ https://github.com/picodotdev #tapestry5
  2. Libro PlugIn Tapestry Presentación del libro Página de descarga Contiene

    todo lo de esta presentación, más ampliado además de otras muchas cosas (no solo de Tapestry) ¡Más de 250 páginas, en español y al precio de 0,00€!
  3. Definición Apache Tapestry es un framework orientado a componentes para

    crear aplicaciones web rápidas, dinámicas, robustas y altamente escalables en la plataforma Java. ➔ Componentes: pieza de software reutilizable ➔ Rápidas: inicio, ejecución, … ➔ Dinámicas: comportamiento variable ➔ Robustas: no fallos simples ➔ Escalables: aplicación pequeña convertible a grande
  4. Características ➔ Compilador ◆ 0 errores de “compilación”, no malos

    merges con conflicto ◆ 100% código analizado ➔ IDE ◆ Refactors ◆ Asistente código ◆ Feedback instantáneo ➔ Políglota ◆ Groovy, Scala, …, cualquier lenguaje JVM Java
  5. Características ➔ Libertad de elegir ◆ Centrado en hacer bien

    una parte ➔ No encadenados al framework ➔ Se puede conseguir ◆ Inicio rápido con arquetipo maven ◆ Entorno integrado para desarrollo • Servidor embebido que inicie aplicación • Ejecución de teses unitarios y de integración No fullstack
  6. Características ➔ Pieza reutilizable de código / DRY ◆ En

    mismo proyecto, misma página, diferentes proyectos ➔ Caja negra ◆ Encapsulación lógica ◆ Para usar uno solo necesitamos conocer parámetros, no los CSS, JS, imágenes, literales, … necesarios ◆ El componente define sus recursos, Tapestry los incluye ➔ 3 tipos: páginas, componentes y mixins ◆ Mismos conceptos ➔ Pueden lanzar eventos ◆ Mismo evento comportamiento diferente según contenedor Basado en componentes
  7. ➔ Fácil reutilización => Alta productividad ➔ Productividad a base

    de reutilización ◆ No copiar y pegar ➔ Todo componente es reutilizable ◆ Varias veces en una misma o distintas páginas ➔ Librerías de componentes ◆ En diferentes proyectos ◆ Muchos proyectos pequeños ◆ Varios proyectos grandes ◆ Librería de componentes == jar, basta con incluir dependencia Características Alta reutilización y productividad
  8. Características ➔ Las URLs de páginas y eventos las crea

    Tapestry ◆ Refactors ◆ Actualizaciones ➔ URLs de páginas siguen esquema RESTful ◆ Sin fichero de “mapeo” ➔ Los datos de un formulario se dejan en objetos ◆ Propiedades (componente/entidad/POJO) ◆ Parámetros de método ➔ Datos en sesión a través de propiedades (anotación) Objetos y propiedades (no URLs y parámetros)
  9. Características ➔ Solución propia ➔ Unidad básica, servicio (interfaz +

    implementación) ➔ Inyección por tipo (refactors) ➔ Adaptable, flexible, modular ◆ Configuración nueva, redefinir y añadir servicios, decorar, reemplazar, dependencias mutuas ➔ Distribuido ◆ No hay archivo de configuración único ◆ Basta con incluir dependencia (con un módulo) ◆ Java: rápido, compilador, refactor, IDE, más adecuado, … ➔ Módulos para Hibernate, Shiro, ... Contenedor IoC
  10. Características ➔ Sin herencia ◆ Metaprogramación, anotación, convención, inyección ◆

    Testable ◆ Actualizaciones ➔ Componente define, framework llama ➔ Convención sobre configuración o anotaciones POJO
  11. Características ➔ Ajax ◆ Cero javascript ◆ 1 petición actualiza

    múltiples zonas ◆ Actualizaciones parciales, sin “microgestión”* ➔ RequireJS ◆ Dependencias cliente, no polución, carga asíncrona ➔ CoffeeScript ➔ LESS/sass ➔ Minimización y agregación Soporte cliente
  12. Características ➔ 10 años de conocimiento ➔ Suficiente para aprender

    de forma autodidacta ➔ Como consulta ➔ Guía inicio rápido ➔ Tutoriales ➔ Libros ➔ Javadoc ➔ Aplicación con ejemplos (JumpStart) ◆ Código completo ejecutable ◆ Contiene casi todo lo que podemos necesitar Documentado
  13. Características ➔ No dar información clara para resolver un problema

    se considera un bug ➔ Inicio aplicación ➔ Informe de error ◆ No solo la traza de la excepción ◆ También en peticiones Ajax ➔ Página Dashboard ◆ Estado páginas ◆ Estado servicios ◆ Estadísticas Hibernate Informativo
  14. Características ➔ Diferentes opciones ◆ A nivel de aplicación, componente

    o mixto ➔ Assets localizables ➔ Plantillas localizables Soporte internacionalización (i18n) y localización (l10n)
  15. Características ➔ Redirect After Post (todas las URLs son añadibles

    a marcadores) ➔ Instrumentalización invisible ◆ No “destrucción” html, colaboración diseñadores <=> programadores ➔ Componentes lanzan eventos (comportamiento diferente según contenedor) ➔ Plantillas xml bien formado ◆ 0 errores “compilación” con página Dashboard ➔ Parámetros con tipo (conversión transparente y extensible) ➔ Muchos componentes built-in (Ajax, beans, condiciones y bucles, controles de formulario, grid, árboles, enlaces y botones, de salida y mensajes) ◆ Algunos complejos como grid y árboles Otras
  16. Arquitectura aplicaciones Presentación Servicios (lógica de negocio) DAO (persistencia) Base

    de datos Spring Hibernate Servidor de aplicaciones Tapestry RequireJS Bootstrap LESS Sass CoffeeScript Cliente
  17. Modelo Vista Controlador (MVC) Tapestry (basado en componentes) Basados en

    acciones (Grails, Play!, Symfony, Django, .NET MVC, Struts, Spring MVC) ➔ Controlador [con plantilla] = 1 componente ➔ Vista puede acceder al controlador = lógica solo en controlador (código java), no duplicación ➔ Modelo «pull» (vista tira) ➔ Controlador más plantilla(s) = N piezas a unir ➔ Vista no accede al controlador = lógica en vistas (scriptlets, condiciones if, “set”), duplicación ➔ Modelo «push» (controlador empuja) ➔ Controlador debe conocer los datos que necesita la vista => No tan independientes
  18. Ejemplo componente (I) (Sin plantilla) package es.com.blogspot.elblogdepicodev.plugintapestry.components; ... public class

    NumeroProductos { @Parameter(defaultPrefix = BindingConstants.LITERAL, value = "Hay %d productos") String mensaje; @Inject ProductoDAO dao; // @BeginRender boolean beginRender(MarkupWriter writer) { long numero = dao.countAll(); writer.write(String.format(mensaje, numero)); return false; } } <t:numeroProductos/> -> Hay X productos <span>Hay N productos</span> -> <span t:type="numeroProductos">Hay N productos</span> -> Hay X productos <t:numeroProductos mensaje="Hay %d productos que quizá te interesen"/> -> Hay X productos que quizá te interesen
  19. Ejemplo componente (II) (Con plantilla) package es.com.blogspot.elblogdepicodev.plugintapestry.components; … public class

    NumeroProductos { @Parameter(defaultPrefix = BindingConstants.LITERAL, value = "Hay %d productos") String mensaje; @Inject ProductoDAO dao; public String getTextoMensaje() { return String.format(mensaje, dao.countAll()); } } <!DOCTYPE html> <t:container xmlns="http://www.w3.org/1999/xhtml" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4. xsd" xmlns:p="tapestry:parameter"> ${textoMensaje} </t:container>
  20. Ejemplo componente (II) package es.com.blogspot.elblogdepicodev.plugintapestry.components; … public class NumeroProductos {

    @Parameter(defaultPrefix = BindingConstants.LITERAL, value = "Hay %d productos") String mensaje; @Inject ProductoDAO dao; public String getTextoMensaje() { return String.format(mensaje, dao.countAll()); } } <!DOCTYPE html> <t:container xmlns="http://www.w3.org/1999/xhtml" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4. xsd" xmlns:p="tapestry:parameter"> ${textoMensaje} </t:container> Página con muchos datos ¿Push?
  21. Fases de renderizado Anotaciones y convenciones Métodos de ciclo de

    vida del componente http://tapestry.apache.org/component-rendering.html
  22. Peticiones de página es/com/blogspot/elblogdepicodev/plugintapestry/pages/admin/ProductoAdmin.java http://localhost/admin/producto/1 http://tynamo.org/tapestry-routing+guide public class ProductoAdmin {

    @Inject ProductoDAO dao; @Property private Producto producto; void onActivate(Long id) { producto = dao.findById(id)); } Object[] onPassivate() { return new Object[] { producto.getId() }; } }
  23. Peticiones de evento ➔ Convención manejador evento onEvento[FromComponente] o anotaciones.

    ➔ Posibles retornos manejadores: Respuesta nula, cadena, clase, instancia página, error HTTP, stream (fichero), objeto enlace (Link), URL ➔ Redirect After Post (Petición evento => redirect => Petición página) <a t:id="sumarCuenta1" t:type="actionLink">Sumar uno a la cuenta</a> public class Index { @Property @Persist(value = PersistenceConstants.SESSION) private Long cuenta; void onActionFromSumarCuenta1() throws Exception { cuenta += 1; } }
  24. Componentes disponibles Componentes específicos para Ajax (AjaxFormLoop, AddRowLink, RemoveRowLink, ProgressiveDisplay,

    Zone). Mostrado y edición de beans (BeanDisplay, BeanEditForm, BeanEditor, PropertyDisplay, PropertyEditor). Condicionales y de bucle (If, Case, Loop, Unless, Delegate). Controles de formulario (Checkbox, Checklist, DateField, Form, FormFragment, FormInjector, Hidden, Label, KaptchaField, KaptchaImage, Palette, PasswordField, Radio, RadioGroup, Select, SubmitNotifier, TextArea, TextField, Upload). Grid, tablas y árboles (Grid, GridCell, GridColumns, GridPager, GridRows, GridRows). Enlaces y botones (ActionLink, EventLink, LinkSubmit, Submit, PageLink). De salida y mensajes (Alerts, Dynamic, Error, Errors, ExceptionDisplay, Output, OutputRaw, TextOutput). Mixins (Autocomplete, DiscardBody, FormFieldFocus, NotEmpty, OverrideFieldFocus, RenderClientId, RenderDisabled, RenderInformals, RenderNotification, TriggerFragment, ZoneRefresh). Páginas de Tapestry (ExceptionReport, PageCatalog, PropertyDisplayBlocks, PropertyEditBlocks, ServiceStatus, Dashboard). Componentes base (AbstractComponentEventLink, AbstractConditional, AbstractField, AbstractLink, AbstractPropertyOutput, AbstractTextField, BaseMessages) Diversos (Any, Doctype, RenderObject, Trigger). Referencia Más componentes en librerías de terceros.
  25. Inyecciones ➔ Cualquier servicio (propio, de Tapestry, Spring) ➔ De

    cualquier módulo ➔ Los servicios se referencian por tipo (no por nombre) public class ProductoAdmin { @Inject private ProductoDAO dao; @Inject private AjaxResponseRenderer renderer; }
  26. Configuración IoC (Módulo) public class AppModule { public static void

    bind(ServiceBinder binder) { binder.bind(ProductoDAO.class, ProductoDAOImpl.class); } public static void contributeFileServiceDispatcher(MappedConfiguration<String,FileService> configuration) { configuration.add("txt", new TextFileService()); configuration.add("pdf", new PDFFileService()); } public static FileServiceDispatcher buildFileServicerDispatcher(Map<String,FileService> contributions) { return new FileServiceDispatcherImpl(contributions); } public static void contributeApplicationDefaults(MappedConfiguration<String, Object> configuration) { configuration.add(SymbolConstants.PRODUCTION_MODE, false); } }
  27. Conversiones ➔ Grafo ➔ Navegables ➔ Extensible ➔ Automáticas, al

    haber diferencia entre tipo parámetro y tipo esperado ➔ Parámetros componente => Con coercers ➔ De cliente (String) a servidor (Objeto) y de servidor a cliente => Con translators ➔ Soporte para entidades Hibernate => Con encoders
  28. Extendiendo conversiones (Coercer) // Módulo public static void contributeTypeCoercer(Configuration<CoercionTuple> configuration)

    { Coercion<BigDecimal, Money> coercion = new Coercion<BigDecimal, Money>() { public Money coerce(BigDecimal input) { return new Money(input); } }; configuration.add(new CoercionTuple<BigDecimal, Money>(BigDecimal.class, Money.class, coercion)); } ➔ Conversiones en parámetros de componentes
  29. Extendiendo conversiones (Translator) public class DateTranslator extends AbstractTranslator<Date> { private

    String patron; public DateTranslator(String patron) { super("date", Date.class, "date-format-exception"); this.patron = patron; } @Override public String toClient(Date value) { if (value == null) { return null; } return new SimpleDateFormat(patron).format(value); } @Override public Date parseClient(Field field, String clientValue, String message) throws ValidationException { if (clientValue == null) { return null; } try { return new SimpleDateFormat(patron).parse(clientValue); } catch (ParseException e) { throw new ValidationException(MessageFormat.format(message, field.getLabel())); } } @Override public void render(Field field, String message, MarkupWriter writer, FormSupport formSupport) {} } // Módulo public static void contributeTranslatorSource(MappedConfiguration configuration) { configuration.add(Date.class, new DateTranslator("dd/MM/yyyy")); } ➔ Conversiones de cliente a servidor y de servidor a cliente
  30. Assets ➔ Cualquier tipo de contenido estático que puede ser

    descargado a un cliente. ➔ Son internacionalizables (i18n) ➔ Minimizado de assets automático (css, javascript, less/sass/coffeescript, hash) en modo producción ◆ configuration.add(SymbolConstants.PRODUCTION_MODE, true); <img src="${context:image/tapestry.png}" alt="Banner"/> <link href="${context:css/site.css}" rel="stylesheet" type="text/css"/> @Import(stylesheet = "context:css/site.css") public class MiComponente { @Inject @Path("context:images/tapesty.png") private Asset banner; }
  31. Stacks @Import(stack = { "core", "MiStack" }) public class MiPagina

    { ... } public class MiStack implements JavaScriptStack { public String getInitialization() { … } public List<String> getModules() { … } public List<Asset> getJavaScriptLibraries() { … } public List<StylesheetLink> getStylesheets() { … } public List<String> getStacks() { … } } ➔ Agregación de assets
  32. Módulos RequireJS ➔ No javascript incrustado en el html ➔

    Todas las ventajas de RequireJS (gestión dependencias en cliente, no polución, asíncrono) ➔ Soporte para inicialización de módulos RequireJS public class Saludador { // Propiedades selector, mensaje … @Environmental private JavaScriptSupport support; void setupRender() { JSONObject o = new JSONObject(); o.put("selector", selector); o.put("mensaje", mensaje); support.require("app/saludador").invoke ("init").with(o); } } define("app/saludador", ["jquery"], function($) { function Saludador(spec) { this.spec = spec; } Saludador.prototype.render = function(){ $(this.spec.selector).html(this. spec.mensaje); } function init(spec) { new Saludador(spec).render(); } return { init: init } });
  33. Componentes formularios Datos enviados desde el cliente y enviados al

    cliente. Form Hidden TextField Label Checkbox Radio TextArea * FormFragment * FormInjector * KaptchaField * KaptchaImage PasswordField Select DateField Checklist RadioGroup Upload * Palette * SubmitNotifier
  34. Envío de datos ➔ Datos en las propiedades de objetos

    y componentes (no en la request) ➔ Convertidos al tipo de la propiedad ➔ En caso de error (validación y conversión), decoración automática de los elementos de formulario <t:form t:id="form" clientValidation="none"> <t:errors class="literal:alert alert-error"/> <t:label for="usuario" /> <input t:type="TextField" t:id="cantidad" value="producto.cantidad" label="Cantidad"/> </t:form> public class ProductoAdmin { @Property private Producto producto; } @Entity public class Producto implements Serializable { @NotNull @Min(value = 0) @Max(value = 1000) @Column(name = "cantidad") private Long cantidad; }
  35. Eventos formularios Eventos del componente Form: prepareForRender prepare prepareForSubmit validate

    failure o success submit onEvento[FromComponente] public class ProductoAdmin { @Inject private ProductoDAO dao; @Property private Producto producto; void onPrepareForSubmitFromForm(Long id) { if (id != null) { // Si se envía un id se trata de una edición, buscarlo producto = dao.findById(id); } if (producto == null) { producto = new Producto(); } } void onSuccessFromForm() { dao.persist(producto); setModo(Modo.LISTA, null); } } <t:form t:id="form" context="producto.id" validate="producto" clientValidation="none" class="well" role="form">
  36. Validaciones en formularios Declarativa en html <t:textfield value="email" validate="email" />

    <t:textfield value="age" validate="max=120,min=0" /> <t:textfield value="zip" validate="maxlength=7" /> <t:textfield value="age" validate="max=120,min=0" /> <t:textfield value="string" validate="minlength=1" /> <t:textfield value="string" validate="none" /> <t:textfield value="letters" validate="regexp=^[A-Za-z]+$" /> <t:textfield value="name" validate="required" /> <t:textfield t:value="producto.cantidad" t:validate="required, minlength=3" size="30" /> Centralizadas en el modelo Bean Validation API (JSR 303) @Entity public class Producto implements Serializable { @Id @GeneratedValue private Long id; @NotNull @Min(value = 0) @Max(value = 1000) @Column(name = "cantidad") private Long cantidad; } Colaboración Bean Validation API y validaciones Tapestry
  37. Macros de validaciones public static void contributeValidatorMacro(MappedConfiguration<String, String> configuration) {

    configuration.add("password","required,minlength=5,maxlength=15"); } <!-- HTML --> <input t:type="textField" t:id="password" t:validate="password" /> // Clase componente @Validate("password") private String password;
  38. Personalización mensajes error Convenciones formId-fieldId-validatorName-message fieldId-validatorName-message <t:form t:id="form"> <t:textfield t:id="nombre"

    validate="usuario.nombre" validate="required"/> </t:form> form-nombre-required-message=El nombre del usuario es requerido. <t:textfield t:id="snn" validate="required,regexp"/> ssn-regexp=\d{3}-\d{2}-\d{4} ssn-regexp-message=Social security numbers are in the format 12-34-5678.
  39. Aplicación ejemplo ➔ Rápido ◆ Inicio aplicación ➔ Componentes: HolaMundo,

    HolaMundoTemplate, HolaMundoAjax ➔ Recarga en caliente ➔ RequireJS Organización javascript ➔ Informativo ◆ Inicio ◆ Exception report ◆ Dashboard • 0 errores en plantillas por “compilación” • Estado páginas, servicios, Hibernate • Problema SQL N+1 https://github.com/picodotdev/elblogdepicodev/tree/master/PlugInTapestry ➔ Soporte Ajax Actualización múltiples zonas en 1 petición, con 0 javascript Exception report ➔ Aplicación inicial con arquetipo Maven ◆ $MAVEN_HOME/bin/mvn archetype:generate - DarchetypeCatalog=https://repository.apache. org/content/repositories/staging ➔ Pruebas unitarias y de integración ➔ Agregación y minimización (modo producción) ◆ Módulos ◆ Javascript ➔ Mixin N-click ➔ Más ejemplos en JumpStart
  40. Licencia Este obra está bajo una licencia de Creative Commons

    Reconocimiento- CompartirIgual 4.0 Internacional.