4. Patrón MVC: Vistas y controladores.
Tras una primera sesión introductoria a Grails en la que ya hemos visto como crear una sencilla aplicación utilizando el scaffolding que nos ofrece Grails. Aunque en esta primera sesión no hemos comentado nada sobre las vistas, ya podemos suponer como Grails las organiza siguiendo el principio de convención sobre configuración.
En esta sesión profundizaremos en más aspectos relativos a las vistas, tales como creación de plantillas o etiquetas. También aprovecharemos para hablar más detalladamente sobre los controladores en Grails.
4.1. Estructura de las vistas en Grails
Lo primero que hay que comentar es que en Grails los archivos de las vistas de nuestras aplicaciones tienen extensión GSP. Además, y siguiendo con el paradigma de convención sobre configuración, Grails aconseja que las vistas utilizadas en nuestra aplicación se alojen en el directorio grails-app/views. Dentro de este directorio, al crear nuestro proyecto se ha creado también el subdirectorio layouts. Este directorio viene por defecto con un archivo main.gsp, que podríamos decir que es el encargado de pintar la apariencia de toda la aplicación en Grails.
Como comentábamos en la sesión de introducción a Grails, éste utiliza el framework Sitemesh para renderizar una aplicación en Grails y una de las características de Sitemesh es que utiliza lo que podríamos llamar un archivo padre (layout) y dentro de este archivo padre se pintarán todas las vistas de la aplicación. Veamos ahora el contenido del único archivo (/grails-app/views/layouts/main.gsp) que aparece por el momento en el directorio layouts.
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!-->
<html lang="en" class="no-js"><!--<![endif]-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title><g:layoutTitle default="Grails"/></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="${assetPath(src: 'favicon.ico')}" type="image/x-icon">
<link rel="apple-touch-icon" href="${assetPath(src: 'apple-touch-icon.png')}">
<link rel="apple-touch-icon" sizes="114x114" href="${assetPath(src: 'apple-touch-icon-retina.png')}">
<asset:stylesheet src="application.css"/>
<asset:javascript src="application.js"/>
<g:layoutHead/>
</head>
<body>
<div id="grailsLogo" role="banner"><a href="http://grails.org"><asset:image src="grails_logo.png" alt="Grails"/></a></div>
<g:layoutBody/>
<div class="footer" role="contentinfo"></div>
<div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading…"/></div>
</body>
</html>
Si analizamos un poco el código fuente de este archivo main.gsp, comprobaremos la existencia de una serie de etiqueta que veremos posteriormente cuyo espacio de nombres es g, como por ejemplo <g:layoutTitle default="Grails"/>, <g:layoutHead/> o <g:layoutBody/>. Estas tres etiquetas están indicando a Grails que deben ser sustituidas por el título, la cabecera y el contenido de las páginas que estemos renderizando.
Por ejemplo, otro archivo que ha creado Grails automáticamente es el archivo que se encuentra en la raiz del directorio views llamado error.gsp. El contenido de este archivo es el siguiente
<!DOCTYPE html>
<html>
<head>
<title>
<g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else>
</title>
<meta name="layout" content="main">
<g:if env="development"><asset:stylesheet src="errors.css"/></g:if>
</head>
<body>
<g:if env="development">
<g:renderException exception="${exception}" />
</g:if>
<g:else>
<ul class="errors">
<li>An error has occurred</li>
</ul>
</g:else>
</body>
</html>
¿Cómo actuaría Grails cuando quiera pintar este archivo error.gsp? En primer lugar, detecta que este archivo se tiene que pintar envuelto en el layout main puesto que tenemos la etiqueta <meta name="layout" content="main">. Una vez Grails ya sabe que esa página debe ser pintada utilizando un envoltorio (layout), realizará las sustituciones correspondientes en las etiquetas de dicho envoltorio <g:layoutTitle default="Grails"/> (sustituida por el texto Grails Runtime Exception), <g:layoutHead/> (sustituido por todo lo que esté dentro de la cabecera de error.gsp, que en este caso es únicamente la referencia a los estilos <g:layoutHead/>) y <g:layoutBody/>, que será sustituido por la salida que produzca la etiqueta <g:renderException exception="${exception}" /> en caso de que estemos en el entorno de desarrollo o bien por un texto indicando "An error has ocurred".
Una vez tenemos claro como se organizan las vistas en un proyecto desarrollado en Grails, ¿cómo podemos organizar estas vistas de forma lógica? En Grails vamos a distinguir dos tipos de vistas: las plantillas y las vistas. Básicamente las primeras serán fragmentos de código que podremos utilizar en varias partes de nuestra aplicación mientras que las vistas se corresponderán con páginas renderizadas gracias a una llamada de un controlador.
4.2. Plantillas
En Grails las plantillas se diferencian de las vistas porque en las primeras su nombre siempre empieza por un subrayado bajo. Otra diferencia notable entre las vistas y las plantillas, es su utilidad y es que las plantillas normalmente las utilizaremos insertadas en vistas y nos facilitará la labor en aquellas partes de las interfaz de usuario que aparezca en varias páginas. Un buen ejemplo de una plantilla podría ser el menú con todas las opciones de nuestra aplicación.
Las buenas prácticas de Grails indican que cualquier plantilla relacionada con una clase de dominio deben estar alojadas todas en un directorio, como por ejemplo grails-app/views/todo/_mytemplate.gsp. De igual forma, aquellas plantillas que vayan a ser utilizadas de una forma más genérica en varias clases de dominio, deben alojarse en un lugar común, por ejemplo en grails-app/views/common. Las plantillas tendrán extensión GSP que son las siglas de Groovy Server Pages.
4.2.1. Plantilla de pie de página
Para comprobar el funcionamiento de las plantillas en Grails, vamos a crear una que nos permita mostrar un pie de página en toda nuestra aplicación que sustituirá al que Grails ha creado por nosotros. Como es una plantilla común a toda la aplicación, la almacenaremos en el directorio grails-app/views/common y la vamos a llamar _footer.gsp y su contenido será algo parecido a esto:
<div class="footer" role="contentinfo">
© 2015 Experto en Desarrollo de Aplicaciones Web con JavaEE y Javascript<br/>
Aplicación Todo creada por Francisco José García Rico (21.542.334F)
</div>
Una vez creada la plantilla, necesitamos poder incrustarla en nuestro proyecto. Para ello, vamos a modificar el layout main.gsp para incluir la llamada correspondiente a esta plantilla. Nosotros vamos a añadir esta plantilla justo después de la etiqueta <g:layoutBody/>, quedando de esta forma:
...
<g:layoutBody/>
<g:render template="/common/footer"/>
<div id="spinner"...
Para pintar la plantilla correspondiente hemos utilizado la etiqueta de Grails g:render y como habrás podido comprobar, no es necesario poner ni el subrayado bajo ni la extensión gsp en el parámetro template, puesto que Grails ya lo hace por nosotros.
Nuestra aplicación ya dispone de un pie de página con la información sobre los creadores de la aplicación. Pero, ¿qué pasa si queremos modificar la apariencia de como se muestra este pie de página para por ejemplo centrar el texto? Esto lo conseguiremos editando la información relativa a los estilos de la página (CSS). Las hojas de estilos de las aplicaciones en Grails se encuentran en el directorio grails-app/assets/stylesheets/, y concretamente tenemos un archivo llamado main.css. Lo que vamos a hacer a continuación es modificar la clase correspondiente al pie de página (.footer). Abrimos la hoja de estilos main.css y localizamos la clase .footer para dejarla de la siguiente forma.
.footer {
background: #abbf78;
color: #000;
clear: both;
font-size: 0.8em;
margin-top: 1.5em;
padding: 1em;
min-height: 1em;
text-align:center;
}
4.2.2. Plantilla de encabezado
La primera plantilla que hemos creado no nos ha supuesto mucha complicación y era muy sencilla de implementar, pero al menos nos ha servido como introducción al sistema de plantillas de Grails y a comprender como se renderizan estas plantillas en el sistema. La segunda plantilla que vamos a implementar nos va a complicar algo más la vida.
Aunque nuestra aplicación todavía no tiene el concepto de usuario (es algo que añadiremos en la sesión 7), esta plantilla será la encargada de mostrar un enlace indicando la palabra login para que cuando los futuros usuarios hagan clic sobre ella, aparezca un formulario donde el usuario podrá identificarse en el sistema. Ahora bien, ¿qué pasa si el usuario ya se ha identificado? Pues lo que se hace habitualmente es modificar esa primera línea para que en lugar de mostrar un enlace con la palabra login, se muestre una línea con el nombre completo del usuario en cuestión y un enlace para que abandone el sistema de forma segura. Eso es lo que vamos a hacer con esta segunda plantilla.
En primer lugar, vamos a modificar el archivo grails-app/views/layout/main.gsp para que en la parte superior de la aplicación, aparezca un encabezado que definiremos posteriormente. El archivo main.gsp quedaría así:
...
<body>
<g:render template="/common/header"/>
<div id="grailsLogo" role="banner">
<a href="http://grails.org">
<img src="${resource(dir: 'images', file: 'grails_logo.png')}" alt="Grails"/>
</a>
</div>
<g:layoutBody/>
<g:render template="/common/footer"/>
...
Si intentas actualizar ahora la aplicación, verás como Grails nos devuelve un error y esto es debido a que todavía no hemos creado la correspondiente plantilla _header.gsp, así que antes de continuar vamos a crearla en el directorio grails-app/views/common/, puesto que es una plantilla que se utilizará a lo largo de toda la aplicación independientemente de la clase que estemos utilizando en cada momento. Vamos a ver el contenido de esta nueva plantilla y luego lo comentamos más ampliamente.
<div id="menu">
<nobr>
<g:if test="${isUserLoggedIn}">
<b>${userInstance?.name} ${userInstance?.surnames}</b> |
<g:link controller="user" action="logout">Logout</g:link>
</g:if>
<g:else>
<g:link controller="user" action="login">Login</g:link>
</g:else>
</nobr>
</div>
Si actualizamos ahora la aplicación en el navegador, veremos como el enlace con la palabra Login queda a la izquierda, lo que no es lo habitual en las aplicaciones web, donde suele aparecer casi siempre en la parte superior derecha de la pantalla. Para solucionar esto, volvemos a editar los estilos de nuestra aplicación y le añadimos las siguientes entradas:
#header {
text-align:left;
margin: 0px;
padding: 0;
}
#header #menu{
float: right;
width: 240px;
text-align:right;
font-size:12px;
padding:4px;
}
Ya tenemos alineado a la derecha el texto con la palabra Login, que además, aparecerá en cualquier página de la aplicación. Antes de continuar, vamos a explicar el contenido del archivo grails-app/views/common/_header.gsp. Podemos comprobar como se hace una comprobación para saber si el usuario actual se ha identificado o no en nuestro sistema. Para realizar esta comprobación utilizamos la etiqueta <g:if>, a la que se le pasa el parámetro test con la variable ${isUserLoggedIn} (variable que todavía no existe). En caso de que esta variable esté ya definida (el usuario ya se ha identificado en el sistema), se mostrará su nombre y apellidos seguido de un enlace para que abandone la aplicación de forma segura. En caso contrario, se le mostrará un enlace con la palabra Login para que se identifique. En ambos casos, se utiliza la etiqueta <g:link> que permite crear enlaces que apunten a una determinada acción de un controlador, tal y como veremos más adelante.
Si ahora hacemos clic sobre Login, veremos como Grails nos devuelve un error del tipo 404, puesto que no hemos definido nada para que la aplicación actúe en consecuencia. Lo dejaremos así hasta que nuestra aplicación soporte usuarios que veremos en la sesión de Spring Security.
4.3. Páginas
Las páginas en Grails podemos definirlas como aquellos archivos de la interfaz de usuario que se pintarán a partir de una llamada realizada desde un controlador. Por ejemplo, si nuestra aplicación necesita una sección con las preguntas más frecuentes de la aplicación, podríamos crear una vista llamada faqs.gsp que se encargue de esto. Además, como esta vista no va a pertenecer a ninguna clase de dominio en cuestión sino que será común a toda la aplicación, la ubicaremos en el directorio /grails-app/views/common. Más adelante crearemos un controlador que se encargará de pintar las preguntas más frecuentes de nuestra aplicación.
Aunque ya te habrás dado cuenta, a diferencia de las plantillas, las vistas no deben utilizar un subrayado bajo al inicio del nombre pero también deben tener extensión GSP.
4.3.1. Variables y alcance de las mismas en páginas
En GSP podemos utilizar podemos utilizar los símbolos <% %> de la siguiente forma
<% now = new Date() %>
Y de forma similar podemos acceder a esa variables con
<%= now %>
Dentro del alcance de una página GSP, tendremos a nuestra disposición una serie de variables predefinidas, que son:
-
application - Instancia de javax.servlet.ServletContext
-
applicationContext - Instancia de Spring ApplicationContext
-
flash - El objecto flash que veremos más adelante en la sección de los controladores
-
grailsApplication - Instancia de GrailsApplication
-
out - Un response writer para escribir la salida
-
params - Los parámetros pasados en la petición
-
request - Instnaica de HttpServletRequest
-
response - Instancia de HttpServletResponse
-
session - Instancia de HttpSession
-
webRequest - Instancia de GrailsWebRequest
Utilizando los símbolos <% y %> vamos a poder realizar bucles:
<html>
<body>
<% [1,2,3,4].each { num -> %>
<p><%="Hello ${num}!" %></p>
<%}%>
</body>
</html>
así como comprobaciónes lógicas:
<html>
<body>
<% if (params.hello == 'true')%>
<%="Hello!"%>
<% else %>
<%="Goodbye!"%>
</body>
</html>
4.3.2. Directivas de páginas
Las páginas GSP soportan además algunas directivas típicas de las JSP tales como importar clases:
<%@ page import="java.awt.*" %>
o incluso las directivas para especificar el tipo de contenido de la página visualizada:
<%@ page contentType="text/json" %>
4.3.3. Expresiones
La sintaxis vista en el punto anterior que utiliza los símbolos <% y %> sin embargo no es tan utilizada debido a la existencia de las expresiones GSP, que son similares a las expresiones JSP EL y tienen la forma ${expresion}:
<html>
<body>
Hello ${params.name}
</body>
</html>
4.4. Etiquetas
En los ejemplos que hemos visto hasta el momento, han aparecido algunas de las etiquetas más utilizadas en Grails. Sin embargo, son muchas más las disponibles y a continuación vamos a ver algunas de ellas. El número de etiquetas de Grails crece continuamente gracias al aporte de la cada vez mayor comunidad de usuarios con sus plugins. A continuación vamos a ver aquellas etiquetas que vienen con el core de Grails.
4.4.1. Etiquetas lógicas
Estas etiquetas nos permitirán realizar comprobaciones del tipo si-entonces-sino.
Etiqueta | Descripción | API |
---|---|---|
<g:if> |
Evalúa una expresión y actúa en consecuencia |
|
<g:else> |
La parte else del bloque if |
|
<g:elseif> |
La parte elseif del bloque if |
<g:if test="${userInstance?.type == 'admin'}">
<%-- mostrar funciones de administrador --%>
</g:if>
<g:else>
<%-- mostrar funciones básicas --%>
</g:else>
4.4.2. Etiquetas de iteración
Son utilizadas para iterar a través de colecciones de datos o hasta que una determinada condición se evalúe a falso.
Etiqueta | Descripción | API |
---|---|---|
<g:while> |
Ejecuta un bloque de código mientras una condición se evalúe a cierto |
|
<g:each> |
Itera sobre una colección de objetos |
|
<g:collect> |
Itera sobre una colección y transforma los resultados tal y como se defina en el parámetro expr |
|
<g:findAll> |
Itera sobre una colección donde los elementos se corresponden con la expresión GPath definida en el parámetro expr |
<g:set var="num" value="${1}" />
<g:while test="${num < 5 }">
<p>Number ${num++}
</g:while>
/*************************************/
<g:each in="${[1,2,3]}" var="num">
<p>Number ${num}</p>
</g:each>
/*************************************/
Stephen King's Books:
<g:findAll in="${books}" expr="it.author == 'Stephen King'">
<p>Title: ${it.title}
</g:findAll>
4.4.3. Etiquetas de asignación
Nos servirán para asignar valores a variables.
Etiqueta | Descripción | API |
---|---|---|
<g:set> |
Define y establece el valor de una variable utilizada en una página GSP |
<g:set var="now" value="${new Date()}" />
/*************************************/
<g:set var="myHTML">
Some re-usable code on: ${new Date()}
</g:set>
/*************************************/
<g:set var="now" value="${new Date()}" scope="request" />
4.4.4. Etiquetas de enlaces
Crean enlaces a partir de los parámetros pasados.
Etiqueta | Descripción | API |
---|---|---|
<g:link> |
Crea un enlace HTML utilizando los parámetros pasados |
|
<g:createLink> |
Crea un enlace HTML que puede ser utilizado dentro de otras etiquetas |
<g:link action="show" id="1">Book 1</g:link>
<g:link action="show" id="${currentBook.id}">${currentBook.name}</g:link>
<g:link controller="book">Book Home</g:link>
<g:link controller="book" action="list">Book List</g:link>
<g:link url="[action: 'list', controller: 'book']">Book List</g:link>
<g:link params="[sort: 'title', order: 'asc', author: currentBook.author]" action="list">
Book List
</g:link>
4.4.5. Etiquetas de formularios
Las utilizaremos para crear nuestros propios formularios HTML.
Etiqueta | Descripción | API |
---|---|---|
<g:actionSubmit> |
Crea un botón de tipo submit |
|
<g:actionSubmitImage> |
Crea un botón de tipo submit con una imagen |
http://www.grails.org/doc/latest/ref/Tags/actionSubmitImage.html |
<g:checkBox> |
Crea un elemento de formulario de tipo checkbox |
|
<g:currencySelect> |
Crea un campo de tipo select con un listado de monedas |
http://www.grails.org/doc/latest/ref/Tags/currencySelect.html |
<g:datePicker> |
Crea un elemento de formulario para seleccionar una fecha con día, mes, año, hora, minutos y segundos |
|
<g:form> |
Crea un formulario |
|
<g:hiddenField> |
Crea un campo oculto |
|
<g:localeSelect> |
Crea un elemento de formulario de tipo select con un listado de posibles localizaciones |
|
<g:radio> |
Crea un elemento de formulario de tipo radio |
|
<g:radioGroup> |
Crea un grupo de elementos de formulario de tipo radio |
|
<g:select> |
Crea un elemento de formulario de tipo select combo box |
|
<g:textField> |
Crea un elemento de formulario de tipo text |
|
<g:passwordField> |
Crea un elemento de formulario de tipo password |
http://www.grails.org/doc/latest/ref/Tags/passwordField.html |
<g:textArea> |
Crea un elemento de formulario de tipo textarea |
|
<g:timeZoneSelect> |
Crea un elemento de formulario de tipo select con un listado de zonas horarias |
http://www.grails.org/doc/latest/ref/Tags/timeZoneSelect.html |
<g:form name="myForm" url="[controller:'book',action:'list']">...</g:form>
/*************************************/
<g:textField name="myField" value="${myValue}" />
/*************************************/
<g:actionSubmit value="Some update label" action="update" />
4.4.6. Etiquetas de renderizado
Permite renderizar las páginas web de nuestras aplicaciones utilizando las plantillas.
Etiqueta | Descripción | API |
---|---|---|
<g:applyLayout> |
Aplica un determinado diseño a una página o una plantilla |
|
<g:formatDate> |
Aplica el formato SimpleDateFormat a una fecha |
|
<g:formatNumber> |
Aplica el formato DecimalFormat a un número |
|
<g:layoutHead> |
Muestra una determinada cabecera para una página |
|
<g:layoutBody> |
Muestra un determinado contenido para una página |
|
<g:layoutTitle> |
Muestra un determinado título para una página |
|
<g:meta> |
Muestra las propiedades del archivo application.properties |
|
<g:render> |
Muestra un modelo utilizando una plantilla |
|
<g:renderErrors> |
Muestra los errores producidos en una página |
|
<g:pageProperty> |
Muestra una propiedad de una página |
|
<g:paginate> |
Muestra los típicos botones Anterior y Siguiente y las migas de pan cuando se devuelven muchos resultados |
|
<g:sortableColumn> |
Muestra una columna de una tabla con la posibilidad de ordenarla |
http://www.grails.org/doc/latest/ref/Tags/sortableColumn.html |
<g:render template="bookTemplate" model="[book: myBook]" />
/*************************************/
<g:render template="bookTemplate" var="book" collection="${bookList}" />
/*************************************/
<img src="<g:createLinkTo dir="images" file="logo.jpg" />" />
4.4.7. Etiquetas de validación
Se utilizan para mostrar errores y mensajes de advertencia.
Etiqueta | Descripción | API |
---|---|---|
<g:eachError> |
Itera a través de los errores producidos en un bean o un modelo |
|
<g:hasErrors> |
Comprueba si se ha producido algún error en un bean o un modelo |
|
<g:message> |
Muestra un mensaje a partir de la propiedad pasada por parámetro |
<g:eachError bean="${book}">
<li>${it}</li>
</g:eachError>
/*************************************/
<g:message code="my.message.code" />
4.5. Librería de etiquetas
Como acabamos de ver, Grails nos ofrece la posibilidad de utilizar un amplio rango de etiquetas tanto JSP como GSP, pero en ocasiones, es probable que necesitemos crear nuestras propias etiquetas. Estas etiquetas nos van a permitir realizar tareas repetitivas de forma rápida y sencilla.
Las librerías de etiquetas no requieren ninguna tarea de configuración y, como casi siempre, se recargan automáticamente sin reiniciar el servidor.
En Grails tenemos dos métodos para crear etiquetas. Por un lado mediante el comando grails create-tag-lib y por otro creando una nueva clase en el directorio grails-app/taglib cuyo nombre termine en TagLib. Nosotros utilizaremos como hasta ahora el comando grails create-tag-lib es.ua.expertojava.todo.Todo.
Este será el resultado de la nueva librería de etiquetas creada:
package es.ua.expertojava.todo
class TodoTagLib {
static defaultEncodeAs = [taglib:'html']
//static encodeAsForTags = [tagName: [taglib:'html'], otherTagName: [taglib:'none']]
}
Las dos propiedades estáticas creadas nos servirán para indicar de forma global o por etiqueta como queremos renderizarlas. Por ejemplo, con static defaultEncodeAs = [taglib:'html'] estamos indicando que cuando la etiqueta imprima el símbolo < o > lo hará por su correspondiente entidad, ésto es, < o >.
Además de definirlo de forma global, también vamos a poder especificarlo por etiqueta utilizando la variable encodeAsForTags y por supuesto modificando las claves utilizadas en el ejemplo por los nombres de nuestras etiquetas.
En nuestros ejemplos, vamos a tener que modificar la variable defaultEncodeAs por el siguiente valor: [taglib:'html'].
4.5.1. Etiquetas simples
El primer ejemplo de etiqueta que vamos a crear será una etiqueta que permita la inclusión de archivos de funciones javascript en el código de nuestras páginas GSPs. Para ello definimos un método en la nueva clase TodoTagLib con el siguiente contenido:
def includeJs = {attrs ->
out << "<script src='scripts/${attrs['script']}.js' ></script>"
}
La creación de la etiqueta necesita como parámetro los atributos de la misma. En este primer ejemplo, sólo vamos a utilizar un atributo que será el nombre del archivo javascript que queremos invocar en nuestra página GSP.
Una vez creada la etiqueta, para realizar la invocación de la misma en las páginas GSP utilizaremos el siguiente código.
<g:includeJs script="miscript"/>
Para invocar la nueva librería creada se utiliza el namespace genérico <g>. Sin embargo, Grails nos permite crear nuestro propio espacio de nombres para que el código generado sea sencillo de leer. El espacio de nombres que vamos a utilizar será me, acrónimo de mis etiquetas. Para ello debemos definir una variable estática al inicio de la clase TodoTagLib llamada namespace e indicándole el valor del nuevo espacio de nombres, tal y como aparece en el siguiente fragmento de código.
package es.ua.expertojava.todo
class TodoTagLib {
static defaultEncodeAs = [taglib:'html']
//static encodeAsForTags = [tagName: [taglib:'html'], otherTagName: [taglib:'none']]
static namespace = 'me'
def includeJs = {attrs ->
out << "<script src='scripts/${attrs['script']}.js'/>"
}
}
De esta forma, en nuestras páginas GSP ya no tendríamos que utilizar <g:includeJs script="miscript"/> sino que podríamos emplear <me:includeJs script="miscript"/>, con lo que la persona que lea el código podrá detectar que esa etiqueta es una etiqueta propia de la aplicación.
En ocasiones, es posible que sea necesario referenciar a nuestras etiquetas desde controladores o desde otras etiquetas y no nos va a ser posible referenciar a estas etiqueta de la forma habitual. En este caso deberíamos llamar a la etiqueta de la siguiente forma:
def renderImage = { attrs ->
println "Rendering the image ${attrs.image}"
asset.image(src:attrs.image)
}
En este ejemplo, en primer lugar imprimimos un log para saber que estamos renderizando una image y después utilizamos una librería de etiquetas disponible en el plugin asset-pipeline que nos permite renderizar imágenes de forma optimizada en todos los entornos.
4.5.2. Etiquetas lógicas
Con Grails también es posible crear etiquetas lógicas que evalúen una cierta condición y actúen en consecuencia en función de dicha evaluación. El siguiente ejemplo es una etiqueta que comprueba si el usuario autenticado es un administrador. El siguiente método realiza esta comprobación y en caso de ser cierto, se mostraría el contenido encerrado entre la etiqueta.
def esAdmin = { attrs, body ->
def usuario = attrs['usuario']
if(usuario != null && usuario.tipo=="administrador") {
out << body()
}
}
En esta ocasión, la nueva etiqueta no sólo recibe el atributo attrs sino que también necesita del atributo body, que se refiere a aquello que esté encerrado entre la apertura y el cierre de la etiqueta. El método comprueba si el usuario pasado por parámetro es un administrador. En la página GSP correspondiente deberíamos indicar el siguiente código.
<me:esAdmin usuario="${session.usuario}">
Dar de baja a usuario
</me:esAdmin>
4.5.3. Generador de código HMTL
Grails ofrece la posibilidad de generar código HTML de forma muy sencilla gracias a un Builder conocido como MarkupBuilder. Para comprobar el funcionamiento de este Builder, vamos a crear una nueva etiqueta que imprima un enlace cuyo atributo href coincida con el título. Por ejemplo, para imprimir un enlace a la página de Google, normalmente debemos incluir el código <a href="http://www.google.com">http://www.google.com</a>. Con esta etiqueta que vamos a crear nos ahorraremos escribir la dirección url dos veces.
En primer lugar creamos un nuevo método en el archivo TodoTagLib.groovy y lo llamaremos printLink(). Este método utilizará el MarkupBuilder e imprimirá un enlace con el valor del atributo href igual al contenido.
def printLink = { attrs, body ->
def mkp = new groovy.xml.MarkupBuilder(out)
mkp.a(href:body(),body())
}
Ahora en la vista podemos añadir una etiqueta como <me:printLink>http://www.google.com</me:printLink>, la cual será traducida al código HTML <a href="http://www.google.com">http://www.google.com</a>.
4.6. Controladores
Una vez visto a fondo todo lo relativo a las vistas en las aplicaciones en Grails, vamos a analizar los controladores. En la sesión anterior generábamos el scaffolding de forma estática de tal forma que ahora vamos a analizar con más detalle todos los aspectos relativos a los controladores.
4.6.1. Acciones en los controladores
Como veíamos anteriormente, los controladores en Grails se ubican en el directorio grails-app/controllers. Si recordamos la imagen donde se explicaba el proceso típico de una aplicación que utiliza el patrón Módelo Vista Controlador, los controladores se encargan básicamente de dirigir las llamadas a nuestra aplicación y de ponerse en contacto tanto con las clases de dominio, las vistas y los servicios para ponerlos de acuerdo.
Para analizar el formato de los controladores en Grails, veamos uno cualquiera de los generados en la sesión anterior, como por ejemplo TodoController.groovy:
package es.ua.expertojava.todo
import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional
@Transactional(readOnly = true)
class TodoController {
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Todo.list(params), model:[todoInstanceCount: Todo.count()]
}
....
}
En primer lugar vemos que se especifica mediante la anotación @Transactional la transaccionalidad de todos los métodos del controlador. En la siguiente sesión aprovecharemos para hablar sobre los diferentes niveles de transaccionalidad cuando veamos los servicios en Grails.
La variable estática allowedMethods nos permite especificar para determinados métodos de nuestros controladores, el tipo de petición aceptada. En el caso del controlador generado vemos como el método save() sólo puede ser procesado por una petición de tipo POST, las actualizaciones por peticiones de tipo PUT y por último los borrados sólo serán válidos cuando se hagan mediante una petición de tipo DELETE.
Los parámetros pasados a los métodos serán mapeados directamente por Grails a partir de los parámetros pasados en la petición. Por ejemplo, si efectuamos la petición http://localhost:8080/todo/todo/index?max=20, el parámetro max de la petición con valor 20 será pasado como parámetro al método index.
Como has podido comprobar en el ejemplo anterior, no es necesario especificar lo que van a devolver los método de los controladores con lo que podemos dejar que sea Grails quien se encargue de definirlo especificando la palabra reservada def.
Comentar también que en la últimas versiones de Grails se ha añadido el método respond que provocará una salida diferente en función de la petición HTTP efectuada. Por ejemplo, si abrimos un terminal y ejecutamos:
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X GET http://localhost:8080/todo/todo/index
veremos que la salida del método será en formato json, mientras que si ejecutamos
curl -v -H "Accept: text/xml" -H "Content-type: text/xml" -X GET http://localhost:8080/todo/todo/index
veremos que la salida del método será en formato xml, mientras que si desde un navegador vamos a la dirección http://localhost:8080/todo/todo/index, Grails renderizará una página html siguiendo una serie de convenios que veremos a continuación.
Además, cada controlador en Grails tiene un método por defecto que sigue las siguientes reglas:
-
Si sólo hay un método, por supuesto ese será el método por defecto
-
Si hay un método con el nombre index(), ese será el método por defecto
-
Si la clase define la propiedad defaultAction, el método especificado será el escogido por defecto
static defaultAction = "list"
4.6.2. Ámbitos de los controladores
Los ámbitos (scopes) son una serie de objetos que nos permitirán almacenar variables en una aplicación en Grails. Tenemos los siguientes ámbitos:
-
servletContext: también conocido con el ámbito aplicación y que nos servirá para compartir variables a través de cualquier artefacto de la aplicación. Hablaremos de él cuando veamos como configurar una aplicación en Grails.
-
session: es utilizada para controlar el estado de un determinado usuario de nuestra aplicación. Habitualmente, se utilizan cookies para asociar la session con el usuario.
-
request: contendrá toda la información de la petición.
-
params: es un mapa con la información pasada a la petición en forma de parámetro GET o POST.
-
flash: es una variable efímera de tal forma que sólo dura una petición y la siguiente, con lo que no es necesario destruirla a mano. Un ejemplo de su uso es el paso de mensajes de error a las vistas.
4.6.3. Relación entre vistas y controladores
Las vistas serán las encargadas de mostrar al usuario aquello que el controlador quiera mostrar. Si echamos un vistazo a los métodos del controlador TodoController nos costará entender esa relación pues apenas se hace mención a ninguna vista en este controlador. Esto es debido a que cuando no se especifica de forma explícita que vista renderizar, Grails utiliza lo que comentábamos en la sesión anterior al respecto de "convención sobre configuración" y directamente trata de renderizar una vista que coincida con el patrón nombre-del-controlador/nombre-del-método.gsp, con lo que por ejemplo, en el método
def show(Todo todoInstance) {
respond todoInstance
}
al no haber indicado ninguna vista en el método respond(), Grails renderizará la vista ubicada en el directorio grails-app/todo/show.gsp. Como comentábamos anteriormente, el método respond() se encarga de decidir como renderizar la salida pero si tiene que renderizar una vista en HTML, bien buscará una siguiendo el patrón mencionado o bien le podemos pasar una de forma de implícita, como por ejemplo vemos en el método save() del controlador TodoController
...
if (todoInstance.hasErrors()) {
respond todoInstance.errors, view:'create'
return
}
...
Sigamos con la relación entre vistas y controladores. Si vemos las vistas y los métodos del controlador TodoController generados automáticamente por el scaffolding estático nos daremos cuenta rápidamente que cuatro de esos métodos tienen vistas asociadas (index, create, edit y show) mientras que otros tres no tienen ninguna vista asociada (save, update y delete). Esto es debido a que estos tres últimos métodos serán los encargados únicamente de crear, actualizar o eliminar una tarea y serán los otros métodos los encargados de renderizar las vistas correspondientes.
Si abrimos la vista todo/create.gsp veremos que el formulario se debe procesar con la acción save()
<g:form url="[resource:todoInstance, action:'save']" >
<fieldset class="form">
<g:render template="form"/>
</fieldset>
<fieldset class="buttons">
<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
</fieldset>
</g:form>
con esto conseguimos que al enviar el formulario, el encargado de procesarlo sea el método save().
Veamos ahora todo el proceso de creación de una tarea. Si arrancamos nuestra aplicación y accedemos en el navegador a la dirección http://localhost:8080/todo/todo/create, el método que sale al rescate es TodoController.create(). Este método con una sóla línea de código es capaz de renderizar una vista con una tarea asociada, que en principio tendrá todos sus campos vacios.
def create() {
respond new Todo(params)
}
Este método renderizará la vista todo/create.gsp y le pasará una variable llamada todoInstance que contendrá una tarea con sus campos vacios.
Si abrimos ahora la vista todo/create.gsp vemos como esa vista únicamente llama a la plantilla todo/_form.gsp que es la que contiene los elementos de formulario necesarios para crear una tarea.
<g:form url="[resource:todoInstance, action:'save']" >
<fieldset class="form">
<g:render template="form"/>
</fieldset>
<fieldset class="buttons">
<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
</fieldset>
</g:form>
Una vez el usuario envía la información, el método encargado de procesar esa petición será TodoController.save()
@Transactional
def save(Todo todoInstance) {
if (todoInstance == null) {
notFound()
return
}
if (todoInstance.hasErrors()) {
respond todoInstance.errors, view:'create'
return
}
todoInstance.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'todo.label', default: 'Todo'), todoInstance.id])
redirect todoInstance
}
'*' { respond todoInstance, [status: CREATED] }
}
}
Pongámonos en el caso en que no hayamos completado correctamente todos los campos requeridos. Si ésto sucede, el método volverá a renderizar la vista todo/create.gsp con los errores que se hayan producido.
En caso de que todo vaya bien, se almacena la nueva tarea
todoInstance.save flush:true
y se trata de renderizar el resultado en función del tipo de petición realizada. En caso de que hayamos envíado un formulario se redirige la aplicación para mostrar esa tarea recien creada con una variable de tipo flash para mostrar un mensaje indicando que la tarea se ha creado correctamente.
flash.message = message(code: 'default.created.message', args: [message(code: 'todo.label', default: 'Todo'), todoInstance.id])
redirect todoInstance
Cuando hacemos esta redirección Grails buscará el método show() y lo renderizará. Este método pintará en el navegador la vista todo/show.gsp. Esta vista, como prácticamente cualquier vista, tiene un fragmento de código que comprueba la presencia de una variable de tipo flash para actuar en consecuencia. En este caso simplemente mostrará el mensaje que hemos completado en el método save() del TodoController.
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
Si en lugar de haber envíado un formulario a través de una página HTML, la petición es de cualquier otro tipo, se trata de renderizar una salida acorde al tipo de petición.
'*' { respond todoInstance, [status: CREATED] }
Si por ejemplo lanzamos desde la terminal el siguiente comando
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d "{'title':'Hacer los ejercicios de Groovy','date_day':25,'date_month':3,'date_year':2015,'date':'date.struct'}" http://localhost:8080/todo/todo/save
la aplicación nos creará una nueva tarea y nos devolverá el resultado en formato json. Si por el contrario ejecutamos
curl -v -H "Accept: text/xml" -H "Content-type: application/json" -X POST -d "{'title':'Hacer los ejercicios de Grails','date_day':26,'date_month':3,'date_year':2015,'date':'date.struct'}" http://localhost:8080/todo/todo/save
Con esto hemos podido comprobar como Grails con muy pocas líneas de código es capaz de generar una aplicación que será capaz de responder al cliente de varias formas con lo que podemos tener una aplicación multiplataforma todo integrado en el mismo código fuente.
Renderizando vistas
En ocasiones, sobre todo cuando realicemos aplicaciones web, tendremos que simplemente renderizar vistas o fragmentos de texto. Para ello Grails dispone del método render() que puede renderizar desde un trozo de texto hasta una plantilla.
render "Hello World!"
render {
10.times {
div(id: it, "Div ${it}")
}
}
render(view: 'show')
render(template: 'book_template', collection: Book.list())
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")
Redirecciones y encadenamientos
Tal y como veíamos anteriormente, en ocasiones necesitaremos redireccionar nuestra aplicación a otros métodos. Para ello, disponemos del método redirect() que acepta varios parámetros, como en los siguientes ejemplos:
//Redirección que se hace dentro del mismo controlador
redirect(action: login)
//Redirección que se hace a otro controlador
redirect(controller: 'home', action: 'index')
//Redirección a una uri explícitamente
redirect(uri: "/login.html")
//Redirección a una url absoluta
redirect(url: "http://grails.org")
//Redirección pasando parámetros
redirect(action: 'myaction', params: [myparam: "myvalue"])
Además, Grails también ofrece la posibilidad de encadenar acciones. El beneficio de encadenar acciones en lugar de redirigir es que el modelo se pasa de una acción a la siguiente:
class ExampleChainController {
def first() {
chain(action: second, model: [one: 1])
}
def second () {
chain(action: third, model: [two: 2])
}
def third() {
[three: 3])
}
}
Esta cadena de acciones terminaría con el método third() recibiendo los modelos
[one: 1, two: 2, three: 3]
Si necesitamos acceder a los modelos dentro de una cadena, podemos utilizar la variable dinámica chainModel que es un mapa con todos los modelos generados en la cadena.
class ChainController {
def nextInChain() {
def model = chainModel.myModel
…
}
}
4.7. Filtros
Los filtros en una aplicación Grails permiten ejecutar acciones antes y después de que los métodos de los controladores sean invocados. El típico ejemplo de uso de los filtros es el de asegurar las peticiones a nuestra aplicación para comprobar si el usuario tiene acceso a un determinado recurso.
Otro ejemplo util de los filtros puede ser el de mantener un log cada vez que se ejecuta una petición en nuestra aplicación, es decir, que cada vez que llamemos a un método de un controlador, imprimimos algo por pantalla.
Por supuesto que podríamos hacerlo directamente en cada método de los controladores, pero sin duda es algo poco mantenible y no es una buena práctica, con lo que vamos a utilizar los filtros.
En Grails, los filtros se generan creando una nueva clase que termine con la palabra Filters o bien mediante el comando grails create-filters para que sea Grails quien lo haga por nosotros y nos cree además un esqueleto de la clase en cuestión. Estas nuevas clases se deben crear en el directorio grails-app/conf. Cada filtro implementa la lógica a ejecutar antes y después de las acciones así como sobre que acciones se debe hacer. En nuestro caso, debemos crear un filtro ejecutando el comando grails create-filters log que se ubicará en el directorio grails-app/conf/todo y se llamará LogFilters.groovy. El esqueleto que Grails ha creado para este filtro es el siguiente:
package todo
class LogFilters {
def filters = {
all(controller:'*', action:'*') {
before = {
}
after = { Map model ->
}
afterView = { Exception e ->
}
}
}
}
Tal y como vemos en el código del esqueleto generado por Grails, podemos ejecutar el filtro antes (before) o después (after) de que se ejecute el método correspondiente del controlador o bien hacerlo después de que se genere la vista. Además, también podemos especificar sobre que controladores queremos aplicar estos filtros.
Nuestro filtro se aplicará a todos los controladores de nuestra aplicación pero sólo a aquellas acciones que traten de renderizar una vista, que serán index, create, edit y show con lo que podríamos tener algo así:
package todo
class LogFilters {
def filters = {
all(controller:'todo|category|tag', action:'create|edit|index|show') {
before = {
}
after = { Map model ->
println "Controlador ${controllerName} - Accion ${actionName} - Modelo ${model}"
}
afterView = { Exception e ->
}
}
}
}
La clase LogFilters únicamente dispone de un filtro, el denominado all(), pero una misma clase puede tener tantos filtros como queramos.
4.8. Ejercicios
4.8.1. Modificando las vistas (0.25 puntos)
Si hacemos un rápido test de usabilidad en nuestra aplicación veremos que cada vez que necesitamos editar o eliminar una tarea, tenemos que hacer dos clicks sobre la misma, uno para ver la tarea y otro para editarla. Vamos a solucionar este problema de usabilidad modificando el listado de las tareas para añadir una nueva columna a la izquierda de cada tarea para mostrar dos opciones, una para editar y otra para eliminar una tarea.
Aprovecharemos también para eliminar las columnas Description y Reminder Date del listado de las tareas.
4.8.2. Etiqueta para valores booleanos (0.50 puntos)
Vamos a crear una etiqueta que nos permita, a partir de un valor booleano, imprimir un icono u otro. La etiqueta se llamara printIconFromBoolean y únicamente recibirá como parámetro el atributo value. Modifica también el namespace de la librería creada para que podamos a ella a través del namespace todo con lo que para llamar a nuestra etiqueta tendríamos que ejecutar algo como:
<todo:printIconFromBoolean value=${todoInstance.done}/>
Y deberíamos imprimir algo parecido a esto:

Sería aconsejable que utilizaras el método image() de la librería de etiquetas del plugin asset pipeline. Aquí tienes un ejemplo:
asset.image(src:"icon.png")
4.8.3. Página de confirmación de eliminación de etiquetas (0.50 puntos)
Cuando deseamos eliminar una etiqueta, al hacer click sobre el botón eliminar se nos muestra un mensaje en Javascript para confirmar si realmente queremos eliminar esta etiqueta. Vamos a modificar este sistema para que en lugar de mostrar un mensaje en Javascript, nos muestre una nueva página para pedir confirmación. Esto nos permitirá añadir algo más de seguridad en lo que el usuario está realizando ya que podemos mostrar un mensaje de alerta más evidente, como por ejemplo éste:
