Experto en desarrollo de aplicaciones web con JavaEE y Javascript

Framework Grails

Sesión 4: Patrón MVC: Vistas y controladores.

Índice

  • Vistas
  • Controladores

Vistas

  • Archivos GSP
  • Ubicadas en grails-app/views
  • Directorio layouts
  • main.gsp
  • SiteMesh

main.gsp

				
<!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&hellip;"/></div>
    </body>
</html>
				
			

main.gsp

  • Etiqueta layoutTitle
  • Etiqueta layoutHead
  • Etiqueta layoutBody

error.gsp

				
<!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 pinta la página de error?

  • Se detecta que layout debe pintar la página
  • Realiza la sustituciones necesarias de layoutTitle, layoutHead y layoutBody

Organización de vistas

  • Plantillas
  • Vistas

Plantillas

  • Empiezan por un subrayado bajo _
  • Nos permite reutilizar código HTML
  • Cualquier plantilla relacionada con una clase de dominio en cuestión, se debe incluir en un directorio llamado como la clase de dominio
  • Lo que sea común a varias clases, al directorio common

Plantilla de pie de página

_footer.gsp

				
<div class="footer" role="contentinfo">
    &copy; 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>
				
			

Plantilla de pie de página

main.gsp

				
<g:layoutBody/>
<g:render template="/common/footer"/>
<div id="spinner"...
				
			

Plantilla de pie de página

main.css

				
.footer {
    background: #abbf78;
    color: #000;
    clear: both;
    font-size: 0.8em;
    margin-top: 1.5em;
    padding: 1em;
    min-height: 1em;
    text-align:center;
}
				
			

Plantilla de encabezado

main.gsp

				
<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"/>
...
				
			

Plantilla de encabezado

_header.gsp

				
<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>
				
			

Plantilla de encabezado

main.css

				
#header {
    text-align:left;
    margin: 0px;
    padding: 0;
}

#header #menu{
    float: right;
    width: 240px;
    text-align:right;
    font-size:12px;
    padding:4px;
}
				
			

Páginas

  • Se pintan a partir de una llamada desde un controlador
  • No necesitan el subrayado bajo _

Variables y alcance de las mismas en páginas

				
<% now = new Date() %>
				
			
				
<%= now %>
				
			

Variables predefinidas

  • application
  • applicationContext
  • flash
  • grailsApplication
  • out
  • params
  • request
  • response
  • session
  • webRequest

Variables y alcance de las mismas en páginas

				
<html>
   <body>
      <% [1,2,3,4].each { num -> %>
         <p><%="Hello ${num}!" %></p>
      <%}%>
   </body>
</html>
				
			

Variables y alcance de las mismas en páginas

				
<html>
   <body>
      <% if (params.hello == 'true')%>
      <%="Hello!"%>
      <% else %>
      <%="Goodbye!"%>
   </body>
</html>
				
			

Directivas de páginas

				
<%@ page import="java.awt.*" %>
				
			
				
<%@ page contentType="text/json" %>
				
			

Expresiones

				
<html>
  <body>
    Hello ${params.name}
  </body>
</html>
				
			

Etiquetas

  • Etiquetas lógicas
  • Etiquetas de iteración
  • Etiquetas de asignación
  • Etiquetas de enlaces
  • Etiquetas de formularios
  • Etiquetas de renderizado
  • Etiquetas de validación

Etiquetas lógicas

  • <g:if>
  • <g:else>
  • <g:elseif>

Etiquetas lógicas

				
<g:if test="${userInstance?.type == 'admin'}">
   <%-- mostrar funciones de administrador --%>
</g:if>
<g:else>
   <%-- mostrar funciones básicas --%>
</g:else>
				
			

Etiquetas de iteración

  • <g:while>
  • <g:each>
  • <g:collect>
  • <g:findAll>

Etiquetas de iteración

				
<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>
				
			

Etiquetas de asignación

  • <g:while>

Etiquetas de asignación

				
<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" />
				
			

Etiquetas de enlaces

  • <g:link>
  • <g:createLink>

Etiquetas de enlaces

				
<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>
				
			

Etiquetas de formularios

  • <g:actionSubmit>
  • <g:actionSubmitImage>
  • <g:checkBox>
  • <g:currencySelect>
  • <g:datePicker>
  • <g:form>
  • <g:hiddenField>

Etiquetas de formularios

  • <g:localeSelect>
  • <g:radio>
  • <g:radioGroup>
  • <g:select>
  • <g:textField>
  • <g:passwordField>
  • <g:textArea>
  • <g:timeZoneSelect>

Etiquetas de formularios

				
<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" />
				
			

Etiquetas de renderizado

  • <g:applyLayout>
  • <g:formatDate>
  • <g:formatNumber>
  • <g:layoutHead>
  • <g:layoutBody>
  • <g:layoutTitle>

Etiquetas de renderizado

  • <g:meta>
  • <g:render>
  • <g:renderErrors>
  • <g:pageProperty>
  • <g:paginate>
  • <g:sortableColumn>

Etiquetas de formularios

				
<g:render template="bookTemplate" model="[book: myBook]" />

/*************************************/
<g:render template="bookTemplate" var="book" collection="${bookList}" />

/*************************************/
<img src="<g:createLinkTo dir="images" file="logo.jpg" />" />
				
			

Etiquetas de validación

  • <g:eachError>
  • <g:hasErrors>
  • <g:message>

Etiquetas de validación

				
<g:eachError bean="${book}">
    <li>${it}</li>
</g:eachError>

/*************************************/
<g:message code="my.message.code" />
				
			

Librerías de etiquetas

  • Crear nuestras propias etiquetas
  • Tareas repetitivas
  • No requiere configuración
  • No necesitan reiniciar aplicación

Crear librería de etiquetas

				
grails create-tag-lib es.ua.expertojava.todo.Todo
				
			

Crear librería de etiquetas

TodoTagLib

				
package es.ua.expertojava.todo

class TodoTagLib {
    static defaultEncodeAs = [taglib:'html']
    //static encodeAsForTags = [tagName: [taglib:'html'], otherTagName: [taglib:'none']]
}
				
			

Crear librería de etiquetas

  • defaultEncodeAs nos permite indicar como renderizar las etiquetas de forma global
  • Con encodeAsForTags podemos especificar como renderizar las etiquetas de forma individual

Etiquetas simples

				
def includeJs = {attrs ->
    out << ""
}
				
			
				
<g:includeJs script="miscript"/>
				
			

Espacio de nombres

				
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'/>"
    }
}			
			

Espacio de nombres

				
					<me:includeJs script="miscript"/>
				
			

Referenciar otras etiquetas

				
def renderImage = { attrs ->
    println "Rendering the image ${attrs.image}"
    asset.image(src:attrs.image)
}				
			

Etiquetas lógicas

				
def esAdmin = { attrs, body ->
    def usuario = attrs['usuario']
    if(usuario != null && usuario.tipo=="administrador") {
        out << body()
    }
}				
				
			

Etiquetas lógicas

				
<me:esAdmin usuario="${session.usuario}">
    Dar de baja a usuario
</me:esAdmin>		
				
			

Generador de código HTML

  • Markup Builder
  • Etiqueta para generar autolinks

Generador de código HTML

				
def printLink = { attrs, body ->
    def mkp = new groovy.xml.MarkupBuilder(out)
    mkp.a(href:body(),body())
}	
				
			
				
<me:printLink>http://www.google.com[]</me:printLink>	
				
			

Controladores

  • Acciones en los controladores
  • Ámbitos de los controladores
  • Relación entre vistas y controladores

Acciones en los controladores

  • Controladores ubicados en grails-app/controllers
  • Se encargan de dirigir las llamadas a la aplicación y se ponen en contacto con clases de dominio, vistas y servicios.

Ejemplo de controlador

				
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()]
    }
    ....
}	
				
			

A tener en cuenta

  • Anotación @Transactional
  • allowedMethods, nos permite especificar el tipo de petición aceptada
  • Parámetros pasados: http://localhost:8080/todo/todo/index?max=20
  • No es necesario especificar lo que van a devolver los métodos
  • Método respond

Método respond

Salida en formato json

				
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X GET  http://localhost:8080/todo/todo/index	
				
			

Método respond

Salida en formato xml

				
curl -v -H "Accept: text/xml" -H "Content-type: text/xml" -X GET  http://localhost:8080/todo/todo/index	
				
			

Método respond

Salida en formato html si desde un navegador accedemos a:

				
http://localhost:8080/todo/todo/index
				
			

Método por defecto en los controladores

  • 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"
				
			

Ámbitos de los controladores

  • servletContext, ámbito aplicación y permite compartir variables
  • session, controla el estado de un usuario de nuestra aplicación
  • request, contiene la información de la aplicación
  • params, mapa con la información pasada a la petición en forma de parámetro GET y POST
  • flash, variable efímera que sólo dura una petición

Relación entre vistas y controladores

  • Las vistas muestran al usuario lo que el controlador quiere mostrar
  • Si en Grails no se especifica ninguna vista de forma explícita, se renderiza una vista de forma implícita.
  • Se busca una vista en el directorio que coincida con el nombre de la clase de dominio y se llame como el método.
  • nombre-del-controlador/metodo.gsp

Renderizando vista explícitamente

				
def show(Todo todoInstance) {
    respond todoInstance
}
				
			

Grails renderiza la vista grails-app/views/todo/show.gsp

Renderizando vista implícitamente

				
...
if (todoInstance.hasErrors()) {
    respond todoInstance.errors, view:'create'
    return
}
...
				
			

Grails renderiza la vista grails-app/views/todo/create.gsp

Procesando formularios

				
<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>
				
			

Formulario procesado por el método save()

Creación de tareas

				
def create() {
    respond new Todo(params)
}
				
			
  • Renderiza todo/create.gsp
  • Pasa la variable todoInstance

Creación de tareas

				
<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>
				
			
  • Se llama a la plantilla todo/_form.gsp
  • El método save() es el encargado de procesar la petición

Creación de tareas

				
@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] }
    }
}
				
			

Creación de tareas

  • Si se produce algún error, se vuelve a renderizar la vista todo/create.gsp
  • Si todo va bien se almacena la nueva tarea
  • Y se procesa en función del tipo de petición realizada
				
todoInstance.save flush:true
				
			

Creación de tareas

  • Si hemos envíado un formulario, se rellena una variable de tipo flash
				
flash.message = message(code: 'default.created.message', args: [message(code: 'todo.label', default: 'Todo'), todoInstance.id])
redirect todoInstance
				
			

Creación de tareas

  • El contenido de esta variable se mostrará en la vista todo/show.gsp
				
<g:if test="${flash.message}">
    <div class="message" role="status">${flash.message}</div>
</g:if>
				
			

Creación de tareas

  • Si no hemos envíado un formulario, se renderiza una salida acorde a la petición
				
'*' { respond todoInstance, [status: CREATED] }
				
			

Creación de tareas

				
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
				
			
  • Se produce una salida en formato json

Creación de tareas

				
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
				
			
  • Se produce una salida en formato xml

Renderizando vistas

				
render "Hello World!"

render {
   10.times {
      div(id: it, "Div ${it}")
   }
}

render(view: 'show')

render(template: 'book_template', collection: Book.list())

render(text: "some xml", contentType: "text/xml", encoding: "UTF-8")
				
			

Redirecciones

				
//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"])
				
			

Encadenamientos

				
class ExampleChainController {
    def first() {
        chain(action: second, model: [one: 1])
    }

    def second () {
        chain(action: third, model: [two: 2])
    }

    def third() {
        [three: 3])
    }
}
				
			

Encadenamientos

Se termina el encadenamiento con el método third()

				
[one: 1, two: 2, three: 3]
				
			

Encadenamientos

Podemos acceder a los modelos dentro de una cadena con chainModel

				
class ChainController {
    def nextInChain() {
        def model = chainModel.myModel
        …
    }
}
				
			

Filtros

  • Permiten ejecutar acciones antes y después de los métodos de los controladores
  • Por ejemplo, para asegurar nuestra aplicación
  • O generar logs
  • grails create-filters log
  • grails-app/conf/todo/LogFilters.groovy

Filtros

				
package todo

class LogFilters {

    def filters = {
        all(controller:'*', action:'*') {
            before = {

            }
            after = { Map model ->

            }
            afterView = { Exception e ->

            }
        }
    }
}
				
			

Filtros

				
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 ->

            }
        }
    }
}
				
			

¿Preguntas...?

Ejercicios