Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Biblioteca Técnica: plantilla de solución PL010 HGrid (Galerías de imágenes) Última actualización: 2008-02-08 Requiere framework v2.1.4 en adelante Contenido Sumario ............................................................................................................................ 2 Demo online permanente.................................................................................................. 2 Screenshots....................................................................................................................... 3 Arquitectura de la solución................................................................................................... 4 Diagrama de Actions ........................................................................................................ 4 Diagramas de clases ........................................................................................................... 5 Estructura de directorios ..................................................................................................... 6 Cómo instalar la demo ........................................................................................................ 7 Cómo funciona el sistema .................................................................................................... 7 Aspectos relevantes ............................................................................................................ 8 Mecanismo para crear el mosaico de imágenes.................................................................... 8 template.htm .................................................................................................................. 9 row.htm ....................................................................................................................... 10 col.htm para imágenes (BLOBs) ...................................................................................... 10 Reducción dinámica de imágenes..................................................................................... 11 Cómo reutilizar y adaptar la plantilla................................................................................... 12 Notas importantes ......................................................................................................... 12 Procedimiento paso a paso.............................................................................................. 12 Biblioteca técnica Página 1 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Sumario Esta plantilla contiene un grid paginado con orientación horizontal, tipo "thumbnail" o mosaico, ideal para galerías de imágenes o catálogos. Esta solución es 100% libre de código, se apoya en las clases genéricas del framework y su reutilización es muy simple. La funcionalidad de esta plantilla es extraer imágenes de la base de datos y mostrarlas en un grid paginado, usando funcionalidades de la plantilla PL009, por lo tanto esta orientada a aplicaciones de negocios. Para entender esta plantilla es necesario que esté familiarizado con la plantilla PL009 (BLOBs). Este documento se apoya en las facilidades de esta plantilla, y no repite lo que ya se explicó en el documento respectivo. Para reutilizar esta plantilla requiere de conocimientos elementales de programación en Dinámica, además de conocimientos básicos de SQL, DHTML y JavaScript. Demo online permanente Si desea probar como funciona esta plantilla, puede visitarla en este URL: http://209.85.104.97/action/demo/hgrid/form Corre con Tomcat 6, Java 6 y PostgreSQL 8.2.4. Biblioteca técnica Página 2 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Screenshots Biblioteca técnica Página 3 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Arquitectura de la solución Diagrama de Actions Biblioteca técnica Página 4 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Diagramas de clases Todas las clases requeridas por esta plantilla son genéricas, provistas por el framework. Biblioteca técnica Página 5 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Estructura de directorios Biblioteca técnica Página 6 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Cómo instalar la demo 1) Para correr la demo tal como está, requiere de la base de datos de demo de Dinámica, la aplicación donde instale esta demo debe estar configurada para conectarse a esta base de datos. 2) Se recomienda haber montado una aplicación Dinámica como un proyecto en Eclipse, para que todo esté en su lugar y se compile automáticamente a medida que hace cambios o añade clases. 3) Copiar el directorio “/templates/hgrid” a “/WEB-INF/action”, también lo puede copiar dentro de un subdirectorio de “/action”, como “/action/demo” por ejemplo. 4) Navegar a: http://localhost/MiContexto/action/hgrid/form donde “MiContexto” es el nombre de su aplicación o contexto. Cómo funciona el sistema 1) El mecanismo arranca con la acción “form”, esta sirve la página que inicializa todo el mecanismo, contiene los DIVs donde irán apareciendo los resultados, así como todo el javascript requerido por esta plantilla. 2) Cuando la página se carga invoca a la acción “search” la cual hace una llamada ajax y muestra nuevamente el indicador de progreso. Cuando el resultado llega, la función que procesa la respuesta evalúa si se trata de una respuesta JavaScript o un bloque de HTML, en este último caso se esconde el DIV de progreso y se muestra el DIV de resultado, sino se ejecuta el javascript recibido, que invocará funciones ya residentes en la página para mostrar el resultado apropiadamente, por ejemplo, hacer visible el panel de navegación y solicitar la primera página. Si la búsqueda no arroja resultados, entonces se muestra un mensaje de “Datos no encontrados” servido por la acción “notfound”. 3) Cuando retorna la repuesta de la acción “view”, se ejecuta un INCLUDE de la acción “hgrid”, la cual ejecuta la clase genérica “HGridPagedOutput”. Esta clase leerá el query almacenado en sesión e inyectara donde se encuentre marcadores especiales los registros encontrados, todo previamente configurado en un archivo “confi.xml”. Biblioteca técnica Página 7 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Aspectos relevantes Mecanismo para crear el mosaico de imágenes La acción “view” ejecuta un INCLUDE hacia la acción “Hgrid”, la cual genera dinámicamente el código para crear el efecto deseado. <center> ${inc:${def:actionroot}/hgrid} </center> En la acción “hgrid” específicamente el archivo “config.xml”. La salida ejecuta la clase “HGridPagedOutput”, la cual se alimenta de los elementos de configuración del hgrid y del query.sql almacenado en sesión. <?xml version='1.0' encoding='ISO-8859-1'?> <config> <summary> Crea HGrid de los documentos cargados </summary> <log>false</log> <output> <classname>dinamica.HGridPagedOutput</classname> <template>template.htm</template> <set-http-headers>true</set-http-headers> <content-type>text/plain; charset=iso-8859-1</content-type> </output> <!--hgrid config--> <row-template>row.htm</row-template> <col-template>col.htm</col-template> <cols>3</cols> <hgrid-recordset>query.sql</hgrid-recordset> <!--end hgrid config--> </config> public void print(TemplateEngine te, GenericTransaction data) throws Throwable { //read config values String rsName = getConfig().getConfigValue("hgrid-recordset"); Biblioteca técnica Página 8 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com int cols = Integer.parseInt(getConfig().getConfigValue("cols")); Recordset rs = (Recordset)getRequest().getAttribute(rsName); String tRow = getResource(getConfig().getConfigValue("row-template")); String tCol = getResource(getConfig().getConfigValue("col-template")); //navigate recordset and create String containing //hgrid body StringBuffer hgrid = new StringBuffer(); rs.top(); for (int k=0;k<rs.getRecordCount();k=k+cols) { StringBuffer colsBuf = new StringBuffer(); for (int i=0;i<cols;i++) { if (k+i==rs.getRecordCount()) { for (int j=i;j<cols;j++) { colsBuf.append("<td></td>"); } break; } rs.setRecordNumber(k+i); TemplateEngine t = new TemplateEngine(getContext(),getRequest(),tCol); t.replace(rs, "&nbsp;"); colsBuf.append(t.toString()); } hgrid.append(StringUtil.replace(tRow,"${cols}", colsBuf.toString())); } //replace hgrid body into main template te.replace("${hgrid}", hgrid.toString()); System.out.println(te.toString()); // let parent class execute normal processing of config.xml super.print(te, data); } template.htm Contiene un INCLUDE para poder añadirle controles de paginación al grid de imágenes. A su vez contiene un marcador “${hgrid}“ en el cual se inyectará el bloque HTML que contendrá el grid de imágenes. <!--panel de control de navegacion--> <div style="width:400px"> <span id="grid-progress" style="float:right;margin-right:20px;display:none"> Biblioteca técnica Página 9 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com <img src="${def:context}/images/progress.gif"> </span> ${inc:/action/pagecontrols} </div> <table width="450px"> ${hgrid} </table> row.htm Contiene un marcado ${cols} en el cual se inyectará las distintas columnas a partir de la cantidad de registro encontrados e impresos en el “col.htm”. <tr> ${cols} </tr> col.htm para imágenes (BLOBs) Se imprime tantas columnas como registro de imágenes existan en la base de datos. Se hace llamada a funciones en javascript residente en la acción ”form” que abrirán la imagen en un popup o mostrar las barras de progresos mientras se cargan las imágenes. <td valign="top" align="center" style="width:150px;backgroundcolor:#d8d8d8;padding:5px;font-size:8pt;font-family:Arial"> <div style="background-color:white; height:120px; padding-top:5px;"> <!--indicador de progreso de carga del chart--> <div id="image_${fld:id}_wait" class="alert" style="margintop:5px;color:gray;width:120px;font-weight:normal;font-family:Arial"> <img src="${def:context}/images/progress.gif"> Cargando... </div> <img id="image_${fld:id}" style="display:none;width:100px;height:80px" class="tool" src="${def:context}/action/demo/blob/getblob?id=${fld:id}" border="0" onload="displayImage('image_${fld:id}')" onclick="openImage('${fld:id}','${def:context}${def:actionroot}/getimage? id=${fld:id}')" title="Ver imagen"> <br>${fld:description} </div> Biblioteca técnica Página 10 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com </td> //mostrar imagen y esconder su indicador de progreso de carga function displayImage(imageID) { document.getElementById(imageID + "_wait").style.display='none'; document.getElementById(imageID).style.display=''; } //invoca un popup para mostrar los documentos function openImage(id,url) { var features = "height=400,width=700,status=no,toolbar=no,menubar=no,location=no,scrollbars=yes,re sizable=yes"; var w = window.open(url, id, features);} Reducción dinámica de imágenes Al atributo “src” del tag “img” que mostrarála imagen (en el template col.htm) se le puede añadir un parámetro al URL para que el servidor envíe una copia reducida de la imagen, esto hace mucho más eficiente al HGrid, ya que imágenes de 102K pueden resultar en 2K, por ejemplo, y es aun más dramático para fotos JPEG con tamaños originales de 2MB o más, con la reducción pueden llegar a 45K-50K. El parámetro que debe añadirse se llama “scale” y representa el porcentaje al cual debe reducirse la imagen en ancho y largo. Ejemplo: <img src="${def:context}/action/demo/blob/getblob?id=${fld:id}&scale=0.25"…> Retorna imágenes reducidas a un 25%, esto es recomendable para los Thumbnails o galerías de imágenes. De ser posible las imágenes se retornan como JPEG, pero si originalmente estaban en formato PNG, se retornarán como PNG reducidas, para evitar problemas de corrupción en la transformación de formato que puede ocurrir con PNGs de muy alta resolución, según hemos observado en nuestras pruebas. Los GIF son retornados como JPEGs. Las imágenes menores a 20K no son reducidas, no se justifica, además de que puede producir una pérdida de calidad muy severa en la versión reducida. Solo se modificó la clase “dinamica.BlobOutputPGSQL”, y esta clase solo intentará reducir la imagen si: • • • Se trata en efecto de una imagen. El parámetro “scale” está presente (debe ser un double entre 0 y 1) en el request. La imagen pesa más de 20K. Biblioteca técnica Página 11 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com Para lograr la reducción dinámica de la imagen en el servidor, se utilizó el API estándar de manejo de imágenes de Java, la clase BufferedImage, la clase ImageIO y las clases para transformaciones de imágenes, principalmente. El pequeño bloque que se añadió a la clase afectada es este: //scale image if requested if (blob.length > 20000 && scaleFactor > 0 && info.getString("format").startsWith("image/")) { BufferedImage bufimg = ImageIO.read(new ByteArrayInputStream(blob)); AffineTransform tx = new AffineTransform(); tx.scale(scaleFactor, scaleFactor); AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); BufferedImage newImg = op.filter(bufimg, null); ByteArrayOutputStream bout = new ByteArrayOutputStream(); if (info.getString("format").endsWith("png")) ImageIO.write(newImg, "png", bout); else ImageIO.write(newImg, "jpg", bout); blob = bout.toByteArray(); } Cómo reutilizar y adaptar la plantilla Notas importantes • Sugerimos ejecutar estos pasos con Eclipse, simplifica mucho todas las tareas que tendrá que ejecutar. • Es conveniente que antes de reutilizar la plantilla, la haya corrido contra la base de datos de demo. Así tendrá una comprensión cabal desde la óptica del usuario de lo que hace la plantilla. • Recuerde que la plantilla tiene un propósito específico, pueden ser necesarias algunas modificaciones para lograr un efecto particular. Procedimiento paso a paso 1. Crear un directorio debajo de “/WEB-INF/action” o debajo de una subcarpeta dentro de esta ubicación. Escoja un nombre apropiado para su modulo, que también sirva como nombre para el paquete de clases Java que requerirá mas adelante. Biblioteca técnica Página 12 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com 2. Copiar todo el contenido de “/hgrid” a su nueva carpeta. El contenido solamente, no copie la carpeta “hgrid”. 3. Cambiar la acción “search”, modificar el query.sql para que se adapte a la tabla de archivo (imagenes) que se usará. Tener en cuenta la condición para solo retorne data donde el content_type sea de tipo “image”. select id, filename, content_type, (image_size / 1024) as image_size, description from demo.imagebank where upper (content_type) like upper('%image%') order by id 4. Copiar la acción “getblob” de la plantilla “PL009” para manejo de BLOBs y pegarla dentro del directorio en el que se está trabajando. Esta acción contiene un “config.xml” el cual deberá ser modificado para el caso que se use una base de datos distinta de Postgres SQL. <?xml version='1.0' encoding='ISO-8859-1'?> <config> <summary> Muestra un documento </summary> <log>false</log> <transaction> <classname>dinamica.GetBlob</classname> <validator>true</validator> <transaction>false</transaction> <jdbc-log>false</jdbc-log> </transaction> <output> <!--for databases other than PostgreSQL use dinamica.BlobOutput--> <classname>dinamica.BlobOutputPGSQL</classname> </output> <!--custom element for THIS Action only--> <!-set to true if you want to force a "Save as" dialog when the browser downloads the BLOB - your query-info.sql template MUST contain a "filename" column. Biblioteca técnica Página 13 de 14 Dinámica El framework RADical …J2EE sin complicaciones Producido por: Martín Córdova y Asociados C.A. www.martincordova.com martin.cordova@gmail.com --> <attach>false</attach> </config> 4.1 Modificar el “query-blob.sql” para que se adapte a la tabla que se usará. select image_data from demo.imagebank where id = ${fld:id} 4.2 Modificar el “query-info.sql” para que se adapte a la tabla que se usará. select content_type, filename from demo.imagebank where id = ${fld:id} 5. Cambiar la acción “hgrid”, modifique “col.htm” para que apunte a la acción “getblob” que generará la imagen almacenada en la base de datos. 6. Cambiar la acción “getimage”, modificar “template.htm” para que apunte a la acción “getblob” que generará la imagen. Recuerde que para probar su nuevo modulo debe navegar a la acción de entrada del mecanismo, en este caso “/form”. La ruta en el browser dependerá de cómo haya nombrado su carpeta raíz (en vez de “hgrid”) y donde la ubicó dentro de “/WEB-INF/action/….”. Biblioteca técnica Página 14 de 14