Grails 1.1 Web Application Development

«»

SHARE & COMMENT :

Customize the home page

With tagging in place, we can enhance the application to allow users to create their
own home page. The aim is to allow users to specify the tags they are interested in,
so any content with these tags will be displayed on their home page. This will allow
us to break the home page up into two sections:

  • A Most Recent section, containing the last five file uploads and messages
  • A Your Data section, containing all the files and messages that are tagged
    according to the user’s preferences

Introducing templates

Taking this approach means that files and messages will be displayed in many
different places on the site, instead of just the home page. By the end of this chapter,
messages and files will be rendered in the context of:

  • A Most Recent section
  • A Your Data section

In the future, we will probably render messages and files in the following contexts
as well:

  • Show all files and messages
  • Show files and messages by tags
  • Show files and messages by search results

Ideally we want to encapsulate the rendering of a file and a message so they look the
same all over the site, and we don’t need to duplicate our presentation logic. Grails
provides a mechanism to handle this, through GSP, called templates.

A template is a GSP file, just the same as our view GSP files, but is differentiated
from a view by prefixing the file name with an underscore. We are going to create
two templates—one template for messages, which will be called _message.gsp and
the other for files, which will be called _file.gsp.

The templates will be responsible for rendering a single message and a single file.

Templates can be created anywhere under the views folder. The location that they
are created in affects the way they are executed. To execute a template we use the
grails render tag. Assume that we create our message template under the views/
message folder. To render this template from a view in the same folder, we would
call the following:


	<g:render template="message" />

However, if we need to render a message from another controller view, say the home
page, which exists under views/home, we would need to call it like so:


	<g:render template="/message/message" />

Passing data to a template

The two examples of executing a template above would only be capable of rendering
static information. We have not supplied any data to the template to render. There
are three ways of passing data into a template:

  • Send a map of the data into the template to be rendered
  • Provide an object for the template to render
  • Provide a collection of objects for the template to render

Render a map

This mechanism is the same as when a controller provides a model for a view to
render. The keys of the map will be the variable names that the values of the map are
bound to within the template. Calling the render tag given below:


	<g:render template="message" model="[message: myMessage]" />

would bind the myMessage object into a message variable in the template scope and
the template could perform the following:


	<div class="messagetitle">
		<g:message code="${message.title}" encodeAs="HTML"/>
	</div>

Render an object

A single object can be rendered by using the bean attribute:


	<g:render template="message" bean="${message}" />

The bean is bound into the template scope with the default variable named it:


	<div class="messagetitle">
		<g:message code="${it.title}" encodeAs="HTML"/>
	</div>

Render a collection

A collection of objects can be rendered by using the collection and var attributes:


	<g:render template="message" var="message" collection="${messages}" />

When using a collection, the render tag will iterate over the items in the collection
and execute the template for each item, binding the current item into the variable
name supplied by the var attribute.


	<div class="messagetitle">
		<g:message code="${message.title}" encodeAs="HTML"/>
	</div≫

Be careful to pass in the actual collection by using ${}. If just the name of the variable
is passed through, then the characters in the collection variable name provided
will be iterated over, rather than the items in the collection. For example, if we use
the following code, the messages collection will be iterated over:


	<g:render template="message" var="message" collection="${messages}" />

However, if we forget to reference the messages object and just pass through the
name of the object, we will end up iterating over the string “messages”:


	<g:render template="message" var="message" collection="messages" />

Template namespace

Grails 1.1 has introduced a template namespace to make rendering of templates even
easier. This option only works if the GSP file that renders the template is in the same
folder as the template itself. Consider the first example we saw when rendering a
template and passing a Map of parameters to be rendered:


	<g:render template="message" model="[message: myMessage]" />

Using the template namespace, this code would be simplified as follows:


	<tmpl:message message="${myMessage}"/>

As we can see, this is a much simpler syntax. Do remember though that this option is
only available when the GSP is in the same folder as the template.

Create the message and file templates

Now, we must extract the presentation logic on the home page, views/home/index.
gsp, to a message and file template. This will make the home page much simpler and
allow us to easily create other views that can render messages and files.

Create two new template files:

  • /views/message/_message.gsp
  • /views/file/_file.gsp

Taking the code from the index page, we can fill in _message.gsp as follows:


	<div class="amessage">
		<div class="messagetitle">
			<g:message code="message.title"
					args="${[message.title]}" encodeAs="HTML"/>
		</div>
		<div class="tagcontainer">
			<g:message code="tags.display"
					args="${[message.tagsAsString]}" />
		</div>
		<div class="messagetitlesupplimentary">
			<g:message code="message.user"
					args="${[message.user.firstName, message.user.
					lastName]}"/>
		</div>
		<div class="messagebody">
			<g:message code="message.detail"
					args="${[message.detail]}" encodeAs="HTML"/>
		</div>
	</div>

Likewise, the <div> that contains a file panel should be moved over to the new _
file.gsp. This means the main content of our home page (views/home/index.gsp)
becomes much simpler:


	<div class="panel">
		<h2>Messages</h2>
			<g:render template="/message/message"
					collection="${messages}" var="message"/>
	</div>
	<div class="panel">
		<h2>Files</h2>
			<g:render template="/file/file" collection="${files}" var="file"/>
	</div>

User tags

The next step is to allow users to register their interest in tags. Once we have
captured this information then we can start to personalize the home page. This is
going to be surprisingly simple, although it sounds like a lot! We just need to:

  • Create a relationship between Users and Tags
  • Create a controller to handle user profiles
  • Create a form that will allow users to specify the tags in which they
    are interested

User to tag relationship

Creating a relationship between users and tags is very simple. Users will select a
number of tags that they want to watch, but users themselves are not ‘tagged’, so the
User class cannot extend the Taggable class. Otherwise users would be returned
when performing a polymorphic query on Taggable for all objects with a certain tag.

Besides allowing a user to have a number of tags, it is also necessary to be able to add
tags to a user by specifying a space delimited string. We must also be able to return
the list of tags as a space delimited string.

The updates to the user class are:


	package app
	
	import tagging.Tagger
	class User {
		def tagService
		static hasMany = [watchedTags: Tagger]
		…
		def overrideTags( String tags ) {
			watchedTags?.each { tag -> tag.delete() }
			watchedTags = []
			watchedTags.addAll( tagService.createTagRelationships( tags ))
		}
		def getTagsAsString() {
			return ((watchedTags)?:[]).join(' ')
		}
	}

User ProfileController

The ProfileController is responsible for loading the current user for the My
Tags
form, and then saving the tags that have been entered about the user. Create
a new controller class called ProfileController.groovy under the grails-app/
controller/app folder, and add the following code to it:


	package app
	
	class ProfileController {
		def userService
		def myTags = {
			return ['user': userService.getAuthenticatedUser() ]
		}
		def saveTags = {
			User.get(params.id)?.overrideTags( params.tags )
			redirect( controller:'home' )
		}
	}

The myTags action uses userService to retrieve the details of the user making the
request and returns this to the myTags view. Remember, if no view is specified,
Grails will default to the view with the same name of the action.

The saveTags action overrides the existing user tags with the newly submitted tags.

The myTags form

The last step is to create the form view that will allow users to specify the tags
they would like to watch. We will create a GSP view to match the myTags action in
ProfileController. Create the folder grails-app/views/profile and then create
a new file myTags.gsp and give it the following markup:


	<%@ page contentType="text/html;charset=UTF-8" %>
	<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
		<meta name="layout" content="main"/>
		<title>My Tags</title>
	</head>
	<body>
		<g:form action="saveTags">
			<g:hiddenField name="id" value="${user.id}"/>
			<fieldset>
				<dl>
					<dt>My Tags</dt>
					<dd><g:textField name="tags" value="${user.tagsAsString}"
								size="35" class="bigfield"/></dd>
				</dl>
			</fieldset>
			<g:submitButton name="Save" value="Save"/>
			|
			<g:link controller="home">Cancel</g:link>
		</g:form>
	</body>
	</html>

This view will be rendered by the myTags action on the ProfileController and is
provided with a User instance. The form submits the tags to the saveTags action on
the ProfileController. The user id is put in a hidden field so we know which user
to add the tags to when the form is submitted, and any existing tags for the user are
rendered in the text field via the tagsAsString property.

Add a link to the myTags action in the header navigation from our layout in main.gsp:


	<div id="header">
		<jsec:isLoggedIn>
			<div id="profileActions">
				<span class="signout">
					<g:link controller="profile" action="myTags">My Tags</g:link> |
					<g:link controller="auth" action="signOut">Sign out</g:link>
				</span>
			</div>
		</jsec:isLoggedIn>
		<h1><g:link controller="home">Teamwork</g:link></h1>
	</div>

Now restart the application, log in as the default user and you will be able to specify
which tags you are interested in.

«»

Comments

comments

Pages: 1 2 3 4

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Speak Your Mind

*

Close
Please support the site
By clicking any of these buttons you help our site to get better