Saturday, August 2, 2008

Grails with Multi Language Select Lists

Lets say you have a customer who has multiple languages spoken in their country. They need their system to support all three languages. The user will choose the language when they log in. This is easy to solved for labels by using the i18n message files.

It is not so straight forward when you need to support Select lists. In this example, I'm going to show how we can handle Address Type using i18n message files in Grails. This same concept can be applied to other type of data. E.g. Day of Week, Country.

Lets define my class. Notice, how the constraint for addressType has values of RESIDENTIAL or POSTAL. This corresponds to values in the messages.properties and messages_fr.properties(I want to support french for this application. Everyone knows the French hate speaking English).


class Address {
String addressType
String addressLine1
String addressLine2
String townCity
String postCode
String toString() {"${this.addressLine1+" "+ this.addressLine2+" "+ this.townCity}"}

static constraints = {
addressType(blank:false,inList: ['RESIDENTIAL','POSTAL'])
addressLine1(blank:false)
addressLine2()
townCity(blank:false)
postCode()
}
}


My message.properties...

address.addressType.RESIDENTIAL=Residential
address.addressType.POSTAL=Postal


My message_fr.properties...

address.addressType.RESIDENTIAL=RĂ©sidentieM
address.addressType.POSTAL=Postal


Now I need to change my gsp pages to reference the new entries in message.properties.

Firstly Create.gsp and Update.gsp, I've change the input to a select...

<tr class="prop">
<td valign="top" class="name">
<label for="addressType">Address Type:</label>
</td>
<td valign="top" class="value ${hasErrors(bean:address,field:'addressType','errors')}">
<g:select name="addressType" from="${['RESIDENTIAL','POSTAL']}" valueMessagePrefix="address.addressType" value="${fieldValue(bean:address,field:'addressType')}"/>
</td>
</tr>


Show.jsp also needs to be changed....

<tr class="prop">
<td valign="top" class="name">Address Type:</td&gt
<td valign="top" class="value"><g:message code="address.addressType.${fieldValue(bean:address, field:'addressType')}"/></td>
</tr>


And finally list.jsp...

<td><g:message code="address.addressType.${fieldValue(bean:address, field:'addressType')}"/></td>


Hope this solution helps. Comments appreciated.


Sunday, July 27, 2008

Filtering a List

While the list screen is useful with sorting and pagination, it would be nice to filter the number of items on a list. This tutorial provides a way of implementing this.

For this tutorial, I'll show an employee filter. To make things a bit more useful, I'll also allow the user to filter by Department. Shown below is the datamodel.



My domain classes are as follows.

class Employee {
String firstName

String lastName

Department department

static mapping = {
version false }
}


class Department {
String name
String toString() {"${this.name}"}

static mapping = {
version false }
}


Lets have a look at the list without filters. Notice that there are around 100 employees from different departments.

















I want to be able to filter by Department, First Name, and Last Name. I will add the code for the filters in views/employee/list.gsp. I created this file by running
grails generate-all Employee

The following snippits of code is from my edited version of list.gsp. Notice, I have added 3 new inputs fields. I have placed them above the List table. Notice I have used flash to default values. More on this later. You can see the full source here

<g:select name='department'
noSelection="['':'']"
optionKey="id"
value="${flash.department}"
from='${Department.list()}'>
</g:select>

<input type="text" id="firstName" name="firstName" value="${flash.firstName}"/>

<input type="text" id="lastName" name="lastName" value="${flash.lastName}"/>



The list screen now looks like this.
















Of course, the filtering won't actually work unless we modify the controller to change the database query. He is the list method of the EmployeeController.groovy. The key thing to note is that I'm putting the request parameter back into flash so I can have the value defaulted when the list.gsp displays.

def list = {
//keep these values so we can rerender on the filters
flash.firstName = params.firstName
flash.lastName = params.lastName
flash.department = params.department

if(!params.max) {
params.max = 10
}
def query
def criteria = Employee.createCriteria()
def results

query = {
and {
like("firstName", params.firstName + '%')
like("lastName", params.lastName + '%')

if(params.department){
def selectedDepartment = Department.get(Integer.parseInt(params.department))
eq('department', selectedDepartment )
}
}
}

results = criteria.list(params, query)

render(view:'list', model:[ employeeList: results ])

}


I also need to change my list.gsp slightly. Note, I've changed the pagination tag and the each tag. We need to pass the flash params in the paginate tag so the controller will still know what the filter criteria was when the user selects next/prev. Full source code here.

<g:each in="${employeeList}" status="i" var="employee">


<g:paginate total="${employeeList.getTotalCount()}" params="${flash}"/>


And thats it, you should now be able to filter on Department, First Name, and Last Name. I suppose this could be scaffolded. It might get a bit more complex when dealing with dates, but it is not impossible.



Friday, July 18, 2008

Grails with Jboss and log4j

This is my solution for the Grails and Jboss/log4j issue. I found my jboss deployment of my grails war would freeze when it got up to the log4j section of the deployment. The issue is documented elsewhere, but I didn't like the solutions I found. This solution is to remove the log4j files from the generated war.

Add the following lines to the bottom of grails-app/conf/Config.groovy

grails.war.resources = {stagingDir ->
def toRemove = ["$stagingDir/WEB-INF/lib/log4j-1.2.15.jar", "$stagingDir/WEB-INF/classes/log4j.properties"]
.each {

delete(file: it)
}
}

The idea and code for this came from this post http://ryantownshend.ca/article/show/2
Thanks Ryan