Friday, July 16, 2010

KTM - Basic Customizations

The KTM application that I generated last week from my Roo script was nice but pretty basic. It provides a menu page, and a Create/List page for each database entity in the system. Within the list page are links that allow you to update or delete an entry. The interface probably doesn't compare all that well against something custom-built, but has all the features that you need to enter data. In fact, the cookie cutter interface can be an advantage - once you are used to it, you can move to using other applications (with similar cookie-cutter interfaces) without much of a learning curve.

However, KTM does need to be customized somewhat from what Roo generated. For one, you want to be able to tell it apart from other Roo generated applications you may be using. There are also some simple customizations that has the potential to enhance its usability. Most of these involve either writing no code or very little code.

This post describes these changes as a sequence of steps that I performed to get from the KTM interface shown at the bottom of the last post to the one shown at the bottom of this one. Where applicable, I will describe the code changes (Roo script and Java snippets) also.

Cosmetic Customizations

This just involves replacing the following image resources with custom ones. I used GIMP to build these versions. I am only marginally familiar with GIMP, but it is very well documented on the Internet, so it was not too much of a problem.

  • banner-graphic.png - I initially tried to modify the banner-graphic.png file provided, but then realized that it may be better to just create a similarly sized banner using GIMP from scratch. The cat image comes from a site offering free stock photos of cats (I think its from FotoSearch, but not sure.
  • favicon.ico - I took the stock photo and then scaled it to 16x16 pixels, saved it as a PPM, then used ppmtowinincon to convert it to an ICO file as described here.
  • springsource-logo.png - I built this from scratch, using the SpringSource icon already present in the original, taking the original Roo favicon ICO file and converting it back to a PPM using winicontoppm, and incorporating it into a "Spring-Roo powered logo".

As you see, I chose orange as my base color for the banner. The next thing I did was to change the color pallete in standard.css and alt.css from the olive green default to my banner background color.

I then went in to messages.properties and changed the welcome.titlepane, welcome.h3 and welcome.text properties so that the application name is in all-caps, and the welcome text contains a brief description of what the application does (or will do once its finished) instead of the Spring-Roo blurb.

Just to keeping things complete, and since Roo was kind enough to generate for me message_xx.properties files for 5 other locales, I went to Babel Fish and made translations of these three welcome.* properties to German, Dutch, Spanish and Italian and stuck them into the respective files. For the last one (English to Swedish) I used this Free Online English to Swedish Translater (there are other languages) powered by WordTran/NeuroTran. The translations may not be perfect, since they are machine generated, but they are good placeholders for when I actually have users in these locales :-).

Usability Customizations

This group of customizations are driven by usability considerations. While trying to enter data, I found places where these changes would make the application more usable. Some of these changes are driven using Roo scripts (resulting in the appropriate code being generated) or making (minor) tweaks the generated Java code (and letting Roo round-tripping make the associated changes). Broadly, there are four main classes of these customizations.

  • Overriding toString() for reference fields
  • Adding backreferences
  • Reordering entity fields
  • Adding Finders where appropriate
  • Reordering menu items

Overriding toString()

The default Roo generated toString() method is in the AspectJ class (ITD) referenced from the entity class using @RooToString annotation, and basically builds a pipe-delimited string of all the member variables in the entity. This creates really long entries in the drop down on the many side of the @OneToMany relationships in my model.

To fix that, I declared toString() methods in Client, Item, Person, Project and Task classes, removing the @RooToString annotation. This causes Roo to remove the associated ITD and honor my toString(). For the first four, the toString() just returns the name, like so:

1
2
3
4
    @Override
    public String toString() {
      return name;
    }

In case of Task, it is a join of the Item name and the task name, since a task name is not necessarily unique by itself, but is more likely to be unique for a given Item.

1
2
3
4
5
6
7
8
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(item == null ? "NULL" : item.toString()).
          append(" :: ").
          append(name);
        return buf.toString();
    }

The Roo docs state (or imply) that its not necessary to remove the @RooToString annotation by hand, but I had problems with mvn complaining about multiple toString() methods on application startup on my first try. Removing the annotation manually is not a big deal, so I just did it for the other entities - Roo immediately removes the associated toString() Aspect.

I also wanted to enforce name uniqueness for the first four classes and the (item,task) combination unique for the Task, but Roo does not provide a --unique switch during creation. I tried the @Column(unique=true) and also the class level @UniqueConstraint annotations, but was unable to make them work. There is an open bug to have the --unique feature, so I figured that when it became available, I could just use that, and did not investigate the JPA approach further.

Another thing I would like to see in the Roo scripting language is the ability to define how the generated code should look, such as indents (I use 2 spaces as you can see from my previous posts, while Roo generated code uses 4), style (K&R, GNU, etc) but that would be more of an icing on the cake kind of thing. I don't see a bug open for that in the Roo JIRA, so maybe once I am done with my application, I would request this.

Adding Backreference fields

While entering data for the various entities, I noticed that it would be more convenient if I knew about what the parent entity was in some cases. So I added backreferences (really reference fields) from project to client, task to project, hours to project and hours to person. Here is the relevant bits from my log.roo file.

1
2
3
4
5
6
7
8
field reference --class com.healthline.ktm.domain.Project \
  --fieldName client --type com.healthline.ktm.domain.Client
field reference --class com.healthline.ktm.domain.Task \
  --fieldName project --type com.healthline.ktm.domain.Project
field reference --class com.healthline.ktm.domain.Task \
  --fieldName project --type com.healthline.ktm.domain.Project
field reference --class com.healthline.ktm.domain.Hours \
  fieldName assignee --type com.healthline.ktm.domain.Person

Having these fields in here also allows us to set up finders for each of these entities (see below).

Reordering Entity Fields

I went into each entity class and reordered the member variables so the order in which they appear in the Create/Update form make sense for a person trying to enter data. I followed the same convention throughout, with name and backreference type reference fields at the top, followed by the regular fields, followed by drop downs to other non-backref type reference fields.

Adding Finders

Based on entering the data, I added finders where I thought they would be useful. For example, one may want to see all Items for a given Project, or all Tasks for a given Assignee, etc. Here is the relevant snippet from my log.roo file for the finders.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Finders for Item: find by project
finder list --class com.healthline.ktm.domain.Item
finder add --finderName findItemsByProject
// Finders for Task: find by person, item, project
finder list --class com.healthline.ktm.domain.Task
finder add --finderName findTasksByItem
finder add --finderName findTasksByProject
finder add --finderName findTasksByAssignee
// Finders for Hours: find by person, task
finder list --class com.healthline.ktm.domain.Hours
finder add --finderName findHoursesByAssignee
finder add --finderName findHoursesByTask
// Finders for Allocations: find by project, person
finder list --class com.healthline.ktm.domain.Allocations
finder add --finderName findAllocationsesByProject
finder add --finderName findAllocationsByAssignee

This immediately results in the ITDs for the Finders to be generated, and the relevant menu entries on the LHS navigation bar to be populated with Finder links.

Reordering Menu Items

Finally, I decided to group the entities by who was most likely to use them. For example, Persons would typically be created by an Administrator. Projects and Items would be created by managers. Managers would also be making changes to Project/Person Allocations. Developers would break up Items into Tasks and estimate them, as well as enter Hours burned for each task, so it makes sense to reorder them accordingly.

This involved changing the menu.jspx file - nothing major, just copying and pasting <li> blocks around. This is a bit risky, since any change to an entity would wipe out my changes, so I decided to do this last. In any case, at this point, changes to my entities should be minor, and so if necessary, I guess I will have to redo the changes (or grab the old version from my source repository).

Result

You can see the result of all these changes below. As you can see, the customizations themselves are not that major, but they definitely make a difference to the looks and usability of the application.

Most of the time I spent on KTM this week involved either thinking about what customization I wanted to do, figuring out where to put it in the Roo generated code, and what sequence to do it in so there is no repeated effort (ie, Roo does not overwrite my changes as a result of some other change). I guess that is as it should be for a RAD tool, you spend more time thinking than doing.

I also spent some time figuring out how to write my custom controllers that would take advantage of the data entered into the app to do the "interesting stuff" I keep talking about - I did make a small start in that direction, but don't have anything significant to write about.

Another thing I want to do is to apply security to the app. Setting up the default security with an in-memory authentication provider is easy enough (one line of Roo script code). However, I would like to use the Person data I am capturing to drive the security. I have some code written already, and hope to complete it in time for my next post.

2 comments (moderated to prevent spam):

Milan Agatonovic said...

Thanks Sujit,
great post, helpful as always.

Sujit Pal said...

Thanks Lemel and fundoo.