9 min read

Customizing the home page in Grails

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 article, 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 MyTags 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.

Grails 1.1 Web Application Development

LEAVE A REPLY

Please enter your comment!
Please enter your name here