Wednesday, May 20, 2009

Testing validation parameters using easyb in Grails

If you want to test out domain class constraints, you can use easyb as 
follows: 

My domain class is Subscription.groovy, and it has several 
parameters.  Only one parameter is required: subscriptionCode.  If you 
have any complex data types (such as my LmsTimeZone domain class) you 
need to provide those or the test fails.   When you validate, you can 
check the parameters that failed with the ensure method.  Here's an 
example: 

scenario "Subscription cannot be created without a subscription code", 

        given "A new Subscription",{ 
                subscription = new Subscription() 
        } 
        when "required parameters are supplied except subscriptionCode", { 
                subscription.subscriptionCode = null 
                subscription.activationTimeZone = LmsTimeZone.get(1) 
                subscription.expirationTimeZone = LmsTimeZone.get(1) 
        } 
        then "the subscription validation shows subscriptionCode is not 
supplied", { 
                subscription.validate().shouldBe false 
                ensure(subscription.errors) { 
                        contains "subscriptionCode" 
                } 
                subscription.save(flush:true).shouldBe null 
        } 



I saved this as SubscriptionStory.groovy in my test/behavior package. 
Make sure you use the keyword "Story" or "Behavior" when making your 
test classes or easyb will not pick up your test. 

Hope that helps someone out there, it took me a bit to figure it out! 

Friday, January 2, 2009

Grails Rich UI: Maintaining the tab on refresh

Ahh, after hours of hacking, I finally got this to work! (and I do mean hacking) It may not be the most elegant way of doing this, but it works. I'll re-engineer it later if necessary. I don't profess to be the worlds greatest JavaScripter, or Ajaxer, but here goes...

PROBLEM:
I use RichUI for my tabs in a Grails application. The tabs look good, and it works well, except for one problem. When you refresh the page, the tab reverts back to the first tab. So, if you do cool stuff like adding in something with a modal or dialog, and you go back to your page, it reverts back to the first tab. Sort of annoying and the customer complained.

RESEARCH:
Eric Miraglia at ericmiraglia.com sent me this helpful url: http://ericmiraglia.com/yui/demos/tabcookie.php
(Thanx!)

Which was close to what I needed. I also saw Tom Duerr's reply at the bottom of this page which was also very helpful:

However, I couldn't get Tom's stuff to work out of the box. The Ajax call just would not work for me. So here's what I did to modify it a bit.

THEORY:
I wanted to store the tab index in a session, since most of my other temp stuff was stored in the session. I didn't want to start down the cookie route in case people didn't want cookies on their machine. So, I used Tom's mods to RichUI to allow for a tab index to be inserted into the Tab View, and I stored the tab info in the session. Sounds easy, right? Well, it took me most of the day to get it working well.

IMPLEMENTATION:
First, make the mods to RichUI's TabViewRenderer.groovy just as Tom describes in the above link. Next, update your instantiation of tab viewer in your gsp page from the standard:

<richui:tabView id="tabView">

to this

< richui:tabView id="tabView" curTab="${curTab}"
event="processTab('type',${myObj?.id});">

The curTab parameter is where you insert the current tab index, starting at 0 for the left most tab, 1 for the next tab to the right, etc.

The event parameter holds a JavaScript method you want called whenever someone hits a tab.(i.e. an event occurrs) You don't have to pass anything to this JavaScript method. However, I pass in a "type" and an ID. I do this because I don't want the same tab coming up whenever the user selects another object, or if I reuse this code on another page. For instance, say some one wants to look at object 1, and they hit tab 3. Now, if they browse around and find object 2, they want to see tab 0 to start off with, not tab 3. So, I use the type, id, and tab number to make sure you are on the same page, viewing the same object. If a user selects a different item from the list, they start at the 0 tab again.

The JavaScript processTab method looks like this: (added to the gsp where the tab view is)

<script>
function processTab(type, id) {
var idx = tabView.get('activeIndex');
new Ajax.Request("/yourProject/yourController/currentTab?type=" +
type + "&id=" + id + "&currentTab=" + idx, { method:'get' });
}
</script>

Basically, the tabView gives you the active index (the one someone just clicked on). This is the value you want to pass to your controller method, along with the type of object and the id. The controller will handle setting the values in the session. (I couldn't figure out how to set the session in JavaScript, so I let the Grails controller do it via an asynchronous Ajax call.) To get the parameters to the controller, I passed the parameters in the URL call. The prototype manual indicated you can do this with 'parameter', which is probably the proper way to do this, but I couldn't figure it out right away (and my frustration with this grew exponentially by the minute), so I just went all brute force on it and passed the parameters in the URL myself. Feel free to make it better.

Now, it was time to go to the controller and create the "currentTab" method where I grabbed the values from the params object:

def currentTab = {
session.curTab = params.currentTab
session.tabType = params.type
session.tabId = new Long(params.id)
return
}

Finally, back in the .gsp where the tab view is, I added this gem of logic:
<g:if test="${(session.curTab != 0) &&
(session.tabType == 'type') &&
(session.tabId == myObj?.id)}">
<g:set var="curTab" value="${session.curTab}"/>
</g:if>
<g:else>
<g:set var="curTab" value="0"/>
</g:else>

What this basically means is, set the curTab parameter to zero UNLESS the planets align and you actually viewing the same object, of the same type, with a tab index not already zero. (the one and only true case where I actually want to save the tab index!).

After all that, it finally worked. Hopefully someone out there will figure out an easier way, or just add all this into the next release of RichUI! You can also do something similar with GrailsUI or even YUI if you want. Eric's link above does it for you using YUI and cookies if you want to go that route.

Now, I just have to figure out how I can write a test to test this! Of course, I know what a real Agile developer would say: I should have written the test first. But, I'm just glad I got this bad boy working for now. I need to eventually go live with this thing.

-Keith

PS. I modified the plugin to allow for curTab to be set. Also, I used richui 0.4 and grails 1.0.4 for this. Using the latest richui 0.6 doesn't seem to work, so I'll have to do more research. Anyway, modify the TabViewRenderer.groovy in the src directory of the plugin and change line 17 to look like this:

builder.yieldUnescaped " var tabView = new YAHOO.widget.TabView(\"${attrs.id}\", {activeIndex:\"${attrs.curTab}\"});\n"

That is necessary for the plugin to tell YUI to set the activeIndex.

Thanks to Kevin Kruse for pointing this out!

Tuesday, December 2, 2008

Using the Filter Plugin for Grails

The filter plugin is a very useful tool if you want to be able to narrow down your lists in a convenient way.   For more info on the plugin check out http://www.grails.org/Filter+plugin.

Once you install the plugin, also check out the example app.  Some of the things that were not so obvious when using Filter are as follows:

1. Version 0.1 only works for the "list" action.  Make sure you only use this on the controller/list type of action.  Any other action, and it will not work.

2. Make sure you include prototype and scriptaculous in the header of your list.gsp.  This is important because the plugin will just sit there and do nothing without these libraries.

3. Use a template called _list.gsp for your list table.  Filter currently expects this.

4. Name your model data after your plugin with the "List" keyword added.  For example: if your controller is foobar, make sure your data model contains and is expecting a "foobarList" to iterate over for the list.  If you re-name this to something else, it won't work.

5. Add a div tag where the id is "list"around your entire list in the template.  Filter expects this when rendering.  (you can see an example in the example app you downloaded)

6. It doesn't hurt to initialize max and offset in your controller. 

class foobar {
  def list = {
    if (!params.max) params.max = 10
    if (!params.offset) params.offset = 0 
  }
}

7. Paginate requires "total" to be in the model.  Define total and return it in your controller.

One current limitation to the Filter plugin is this: If you want to use a narrowed result set (i.e. Foobar.findAllByStatus("Active")), and you set "total" in your controller using Foobar.countByStatus("Active"),  Filter only takes the count from the class when it finishes filtering.  That is, it does a clazz.count() which is basically a Foobar.count().  It also just takes the list from the parameters given (i.e. Foobar.list(params)).  What happens is, your count will be off in your pagination and result set after filtering.  You will see more data in your results after you  filter than the initial list.  You'll have to either modify the FilterController to fix this, or wait for a new release where this might be addressed.

This is a great plugin with lots of potential.  

Enjoy!
-Keith 

Thursday, July 31, 2008

Creating a new Grails tag to convert military time to standard time

I have a situation where I need to store time as military time (i.e. 0900 is 9:00 AM), but I want to display it as standard time. So, I created a new tag library in Grails to perform this for me. I then simply use the new tag in my gsp page, and all is well. Here's the code you would put in your taglib directory:

class MyTagLib {
def standardTime = {attrs, body ->
out << showStdTime(body())
}

def showStdTime(militaryTime) {
def hours
def minutes
def ampm = "AM"
if (militaryTime.trim().size() != 4) {
return militaryTime
}
try {
hours = new Integer(militaryTime[0..1])
minutes = militaryTime[2..3]

if (hours > 12) {
ampm = "PM"
hours -= 12
}

if (hours == 12) {
ampm = "PM"
}

if (hours == 0) {
hours = 12
}
} catch (Exception e) {
return "Format Exception: " + e.toString()
}

return hours + ":" + minutes + " " + ampm
}
}

And here's the tag as it looks in my gsp page (substitute the lt for <>. I couldn't get the formatting working right):

<g:standardtime>${myClass.closingTime}</g:standardTime>

Wednesday, March 26, 2008

More on changing a button name when toggling show/hide

Here's a cool way of making the div area hidden first:

http://you.gotfoo.org/using-scriptaculous-to-toggle-a-div/

Dynamic messaging and i18n in Grails

When using Grails for internationalization (i18n), you sometimes want to send some dynamic data to insert into the string. For instance, you don't just want to say "User not found.", you want to say "Keith Cochran not found.". So, to do that, you need to pass that information at runtime. Here's how to do that.

In your messages.properties file, add an {x} where you want your dynamic parameters to go. Number them starting at 0. For instance, define user.not.found in messages.properties like this:

user.not.found={0} {1} not found

Now, when you call up that particular string from a controller, pass in the variables you want to display at runtime:

flash.message = message(code: "user.not.found",
args: [user.firstname, user.lastname])


This makes your messaging back to the user much more informative.

-Keith

Notes on Grails Internationalization (i18n)

When using Grails for web app development, it's desirable to place all text in messages.properties files. When you do this, you can easily change the text to another language, and that makes internationalization easier. You still have to deal with other issues such as date formats and currency, but at least the text is easy to modify that way.

Once you have all your properties files created, you can make change languages by passing in the lang parameter and setting it to the language of your choice. For instance, if you want to list out some values, you can call the "list" action and pass it the Spanish language as a parameter.

<g:link action="list" params="[lang:'es']">

Note: Once you pass this parameter to any page, your app will now be in that language until you change it.

So, in order to facilitate this, you need to create the necessary files and reference the properties in your code. For example, create a messages.properties file and add text such as "Search" for your home page.

home.search=Search

Now, you can create a messages_es.properties that contains your Spanish translations:

home.search=Buscar

So, once you have all the text defined in your messages.properties file(s), (one for each language you are translating to) all you then need to do is reference that particular property in your code. Grails picks that up and inserts the appropriate property for you. Here are some tips on doing that in your code.

In the GSP page, if you want to reference the file in just plain text, use the g:message tag:

<g:message code="home.search" default="Search"/>

This tells Grails to find the property "home.search" in the properties file, and insert that value here. If it cannot find "home.search" in your properties file, it will insert whatever is in the default parameter.

When using the sortableColumn GSP tag, use the "titleKey" parameter to set the property as follows:

<g:sortableColumn property="title" titleKey="certification.title"/>

To set the text of a button, you can pass in a variable from the controller. I've used the flash scope to pass data back to the view, but you can use another object if you want to and return it. First, grab the value from the flash scope in the GSP page as follows:

<input type="submit" value="${flash.search}"/>

Next, in the controller action, just set the flash.search parameter before you return:

flash.search = message(code: 'home.search')

Now, the action in the controller will grab the message and throw it into the flash.search variable. You simply read that variable in the GSP page and it's all good. There's probably a better way of reading that parameter directly in the GSP page, and when I find it, I'll post an update.