David Sills's muse is fueled by Bach, Beethoven, and Brahms (and Berlioz and Boulez). He has no life, which comports well with the über-geekitude to which he aspires. He reads and writes fitfully in English, Spanish, French, German, Italian (mostly medieval), C, VB, Perl.... Oh, and Java and XSLT, lots and lots of Java and XSLT. Trained somewhere in darkest Central America, he works today at DataSource, Inc., on the product team for the CASE tool Abri. David has posted 9 posts at DZone. View Full User Profile

Running the Table With JMesa

06.18.2008
| 44640 views |
  • submit to reddit

Customizing

And now, to business. The JMesa default is astounding, but no default is ever exactly like you want it. The ability to customize is critical. Also, defaults rarely exercise every feature, and this one is no exception.

Let's start with some requirements:

  • We will display the value of each HelloWorld item's toString method in an additional column
  • We will display more user-friendly values in the format column
  • We will ensure that columns that cannot be reasonably sorted are made unsortable
  • We will add columns containing links to edit and delete pages for the HelloWorld items
  • We will display images in the edit and delete columns
  • We will not display the Pk property of each item, but will pass its value to edit and delete pages as needed
  • We will enable the user to retrieve a comma-separated-values (CSV) copy of the table contents
  • We will enable the user to retrieve an Excel spreadsheet copy of the table contents
  • We will disable filtering and highlighting
  • We will reorganize the toolbar items in a different order

Believe it or not, implementing each of these features will be quite easy! and you'll begin to get a sense for the possibilities of JMesa.

ToString Column

Each HelloWorld item produces a formatted string within its toString method. This is not a JavaBean property method, so we cannot directly point the TableFacade at it. We want this value to be rendered (to use JMesa terminology) as the contents of a <td> (a cell) in each HTML row.

Cell contents are produced by implementations of the CellEditor interface. Its getValue method is passed the item to be displayed, the property to be called, and the current row count. Since only the item itself is actually needed for our purpose, the implementation is simple:

public class ToStringCellEditor implements CellEditor
{
@Override
public Object getValue(Object item, String property, int rowcount)
{
if (item == null)
{
return "";
}
return item.toString();
}
}

Of course, we'll need a column into which to put the results. All we need do is add an arbitrary value to the column properties list:

tableFacade.setColumnProperties("firstName", "lastName", "format", "toString");

This value is used to retrieve the column:

Row row = tableFacade.getTable().getRow();
Column column = row.getColumn("toString");
column.getCellRenderer().setCellEditor(new ToStringCellEditor());

Of course, this means that the getValue method of the ToStringCellEditor will always be passed a bogus property value, but since the editor doesn't use it, that's no problem. (Note that we've also left off the pk column as per requirements.)

User-Friendly Format Column

We continue by introducing a more user-friendly value into the format column. The format string "{0}, {1}! {2} {3} {4}" looks ugly and most likely won't be understood by an end user. The only real information it conveys is that it is the default value. We'll use a Spring MessageSource to supply something a little easier on the eyes at runtime.

First, we'll add a property to the messages.properties file loaded by Spring at application startup:

format.{0},\ {1}!\ {2}\ {3}\ {4}=Default format

(The backslashes are needed to escape the white space in the key.)

As we have already seen, a CellEditor is needed to change a cell's displayed value. Using MessageSource to produce the display value at runtime requires a few more lines than the ToStringCellEditor:

public class SpringMessageCellEditor implements CellEditor
{
MessageSource source;
String prefix;
Locale locale;

public SpringMessageCellEditor(MessageSource source, String prefix,
Locale locale)
{
this.source = source;
this.prefix = prefix;
this.locale = locale;
}

public Object getValue(Object item, String property, int rowcount)
{
if (item != null)
{
try
{
return source.getMessage(prefix + "." +
PropertyUtils.getProperty(item,
property), null, locale);
}
catch (IllegalAccessException ignore) { }
catch (InvocationTargetException ignore) { }
catch (NoSuchMethodException ignore) { }
}
return null;
}
}

We still have to add this editor to the column displaying the format property:

Column column = row.getColumn("format");
column.getCellRenderer().setCellEditor(new
SpringMessageCellEditor(messageSource, "format", locale);
 

Unsortable Columns

Next, we want the table to know that some columns are unsortable. Columns are typically sorted by property value, but we just added a column that corresponds to no property, that displays the output of the toString method. If the user clicked on the header of that column, he or she would wind up with a very ugly NullPointerException message.

Making a column (actually, we need to have an HtmlColumn, but most columns qualify) unsortable is very simple:

htmlColumn.setSortable(false);

With this, no onClick method will be generated for the column header, preventing users from accidentally causing a mess.

Edit and Delete Columns

Now we'll add columns containing links to edit and delete pages for HelloWorld items. I prefer using icons to buttons saying "Edit" and "Delete", as it reduces the amount of textual information the user must process. Tables typically present a lot of information in a compact space, making user overload a problem worthy of attention.

To do this, we'll need a CellEditor (by now, you knew that was coming!). Since this is functionality I use a lot, let's design it for reuse, refactoring out reusable code into one class, and code tailored to this project into another.

ImageCellEditor encapsulates the general process of setting up an image with a link, and includes a method that will let subclasses override the default processing of the link:

public class ImageCellEditor extends AbstractContextSupport
implements CellEditor
{
private String image;
private String alt;
private String link;

public ImageCellEditor(String image, String alt, String link)
{
this.image = image;
this.alt = alt;
this.link = link;
}

public Object getValue(Object item, String property, int rowcount)
{
CoreContext context = getCoreContext();
String imagePath =
context.getPreference("html.imagesPath");
StringBuilder img = new StringBuilder();
if (link != null && link.trim().length() != 0)
{
img.append("<a href=\"")
.append(processLink(item, property, rowcount, link))
.append("\"/>");
}
img.append("<img src=\"")
.append(getWebContext().getContextPath())
.append(imagePath)
.append(image)
.append("\" title=\"")
.append(alt)
.append("\" alt=\"")
.append(alt)
.append("\"/>");
if (link != null && link.trim().length() != 0)
{
img.append("</a>");
}
return img.toString();
}

/**
* This method can be overridden by subclasses to handle specific
* HTML link needs.
*/
public String processLink(Object item, String property,
int rowcount, String link)
{
return link;
}
}

This is our opportunity to introduce CoreContext and WebContext, two important classes that plug our code into the JMesa infrastructure. Extending AbstractContextSupport gets us JavaBean property methods for these objects (just a convenience; I could have implemented the interface ContextSupport, but then I would have had to write the property methods myself).

The CoreContext has many uses; our immediate purpose for it is to retrieve a value configured in the jmesa.properties file. This was pointed to in web.xml:

<context-param>
<param-name>jmesaPreferencesLocation</param-name>
<param-value>WEB-INF/jmesa.properties</param-value>
</context-param>

It contains a preference called "html.imagesPath" that replaces the default path from which JMesa retrieves images:

html.imagesPath=/images/

This means we won't have to hard-code a part of the image URL. (There are a lot more configurable preferences: for details, see the JMesa web site.)

The WebContext provides us with the servlet context path, again letting us avoid hard-coding the image URL:

getWebContext().getContextPath()

Getting back to the two image columns, we have a requirement to pass the Pk property of the appropriate HelloWorld to the edit or delete pages when the images are clicked. Adding this property to the link is easy, using the MessageFormat class to process the link argument of the application-specific subclass:

public class HelloWorldImageCellEditor extends ImageCellEditor
{
public String processLink(Object item, String property,
int rowcount, String link)
{
return MessageFormat.format(link, ((HelloWorld) item).getPk());
}
}

After creating the editor, we can retrieve the context objects for it from the TableFacade:

ImageCellEditor editor = new HelloWorldImageCellEditor("edit.gif",
messageSource.getMessage("image.edit.alt", null, locale),
"edit.html?pk={0,number,integer}");
editor.setWebContext(tableFacade.getWebContext());
editor.setCoreContext(tableFacade.getCoreContext());

Now we have the images and the links. But it would be awfully nice if the images could be centered within the column, something notoriously difficult to achieve with CSS style sheets. What would work would be to use the align and valign attributes of the cell. How can we do that?

The cell itself, as opposed to its contents, is rendered by the interface CellRenderer. Unfortunately, the HtmlCellRenderer sub-interface that comes with JMesa has no method for adding attributes. The Decorator and Template patterns, however, come to the rescue. Again, we implement the functionality for reuse as two classes, the first a generic decorator with an additional template method:

public abstract class AttributedHtmlCellRendererDecorator implements
HtmlCellRenderer
{
// all other methods will be delegated to this renderer
protected HtmlCellRenderer renderer;

public AttributedHtmlCellRendererDecorator(HtmlCellRenderer renderer)
{
this.renderer = renderer;
}

public Object render(Object item, int rowcount)
{
HtmlBuilder html = new HtmlBuilder();
html.td(2);
html.width(getColumn().getWidth());
addAttributes(html);
html.style(getStyle());
html.styleClass(getStyleClass());
html.close();
String property = getColumn().getProperty();
Object value = getCellEditor().getValue(item, property, rowcount);
if (value != null)
{
html.append(value.toString());
}
html.tdEnd();
return html.toString();
}

/**
* Subclasses will add attributes.
*/
public abstract void addAttributes(HtmlBuilder html);
}


The second will be a subclass that adds the specific attributes we need:

public class AlignedHtmlCellRendererDecorator extends AttributedHtmlCellRendererDecorator
{
private String align;
private String valign;

public AlignedHtmlCellRendererDecorator(HtmlCellRenderer renderer,
String align, String valign)
{
super(renderer);
this.align = align;
this.valign = valign;
}

@Override
public void addAttributes(HtmlBuilder html)
{
html.align(align);
html.valign(valign);
}
}

Whew, that was a mouthful! However, our images will come out nicely centered in the column, and we've learned a good deal more about how the JMesa API works.

There will be edit and delete pages to link to, of course, but these are not of interest here and are completely trivial in the Eclipse project.

CSV and Excel Output

In JMesa terminology, output other than HTML is called exporting the table. As complex as it might seem, it's actually the easiest part of the process. Again, a single line of code will do all we need:

tableFacade.setExportTypes(response, org.jmesa.limit.ExportType.CSV,
org.jmesa.limit.ExportType.EXCEL);

That's really all there is to it! (OK, you have to include some JAR files in the library, but what did you expect, magic?)

Filtering and Highlighting

Making a row (we need an HtmlRow) unfilterable and unhighlighted is just as simple as making a column unsortable:

htmlRow.setFilterable(false);
htmlRow.setHighlighter(false);

With this, no filtering row or icons will be generated above the column header and the highlighting feature will be turned off.

Toolbar

The code to reorganize the toolbar is quite straightforward; while we're at it, we need to include icons for the various output formats:

public class ReorderedToolbar extends AbstractToolbar
{
@Override
public String render()
{
if (ViewUtils.isExportable(getExportTypes()))
{
addExportToolbarItems(getExportTypes());
addToolbarItem(ToolbarItemType.SEPARATOR);
}

MaxRowsItem maxRowsItem = (MaxRowsItem)
addToolbarItem(ToolbarItemType.MAX_ROWS_ITEM);
if (getMaxRowsIncrements() != null)
{
maxRowsItem.setIncrements(getMaxRowsIncrements());
}

addToolbarItem(ToolbarItemType.SEPARATOR);
addToolbarItem(ToolbarItemType.FIRST_PAGE_ITEM);
addToolbarItem(ToolbarItemType.PREV_PAGE_ITEM);
addToolbarItem(ToolbarItemType.NEXT_PAGE_ITEM);
addToolbarItem(ToolbarItemType.LAST_PAGE_ITEM);

return super.render();
}
}

I arranged the icons by simply specifying the order in which they are added to the toolbar. They look more natural to me this way; your mileage may vary. Note that we delegate the messy work of actually rendering the toolbar to the JMesa superclass.

Legacy
Article Resources: 
Published at DZone with permission of its author, David Sills.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Nicolas Frankel replied on Wed, 2008/06/18 - 10:19am

Hello,

Don't get me wrong, but displaying tabular data in HTML is done since a loooooong time with the DisplayTaglib. There's a short article on my blog if you're interested. They use taglibs but I see it as an advantage.

For more informations, see their site or their live demos which are really bluffing when you see the tiny amount of config needed on the JSP.

Cheers.

Nicolas

 

 

Benoit Guerout replied on Wed, 2008/06/18 - 10:53am

I've a very bad recollection of the last time I've tried to create a table JMesa (old but wonderfull displaytag solved easly the problem).

But, may be, this tutorial will help me to succeed the next time i'll try JMesa.

Jeff Johnston replied on Wed, 2008/06/18 - 7:19pm

What a great article...I was very excited to read this today :)! It was very well thought out and complete. You really tapped into just how customizable JMesa is. By design, just about every aspect of JMesa can be tweaked and modified with very little code. I will for sure link to this article from the home page of JMesa and reference it in the tutorials!

I am going to see about implementing your extended attributes notion. I think that is a nice improvement! I also hope to make some adapters for Spring so that those that choose to use the Spring framework can tie into Spring's notion of messages and preferences. It wouldn't be hard to do and would be a completely optional runtime dependency so non-Spring developers would not be effected.

Nicolas, there is a complete tag library with JMesa. The tag library is just a thin wrapper around the API. In addition JMesa has first class Groovy support and, in the next release, will even have Grails support with GSP Tags. The latter is currently being built by a member of the JMesa community and I am very excited to see that work as well. I have seen other work by this developer and it is always top notch. Also, as the article mentions, JMesa can also be put in an editable mode and will track your changes automatically.

bguerout, sorry to hear your first go around wasn't so great. How long ago was that? Based on how few questions I get nowadays I can say with confidence that your next experience should be really good. It is a very solid API! If you have any problems either post on the groups or email me directly. I try to get back ASAP as I know how frustrating it is to wait for replies when there are questions.

I wanted to mention that JMesa was designed to be an API first and foremost. It also embraces the Model 2 environment. Both of these give it the advantage of being able to use JMesa with just about any framework and regardless of the view technology being used.

David, if you don't mind, I also wanted to mention a few subtle improvements to your example that may make things easier:

  • You can actually turn off filtering at the row level by setting filterable to false. This will cause the filter row to not be rendered and the filter toolbar items to not display.
  • You might want to consider extending AbstractCellEditor as that will give you access to the Column, as well as the CoreContext and WebContext.
  • You do not need to inject your CellEditor with the CoreContext or WebContext. The support interfaces are there so the API can do that automatically for you when you set the editor on the renderer.
  • You can also define bogus column names instead of null for the column properties that do not map to a bean attribute. That enables you to reference the columns by name instead of position. It seems I usually have columns like "chkbox" myself that do not actually map to bean properties.

Once again, congratulations on a great article!

-Jeff Johnston

 

David Sills replied on Sun, 2008/06/22 - 1:59am in response to: Nicolas Frankel

Nicolas:

Thanks for your comment and thanks for reading.

It was, in fact, using Display Taglib for a while that got me to looking around for something easier to customize and with a few more features. I agree completely that if it meets your needs, it's a terrific library. It simply didn't meet mine.

That said, chacun à son goût, as the French say. The beauty of the Java world today is that we have enough open-source code around that everyone can use whatever they feel comfortable with.

Cheers,

David Sills

David Sills replied on Sun, 2008/06/22 - 2:01am in response to: Jeff Johnston

Jeff:

Thanks to you for authoring JMesa! Without it, of course, the article would have been, shall we say, less interesting?

I have incorporated some of your comments into the code and resources. I would be pleased if you wanted to link to the article.

All the best! 

David Sills

Neil Juan replied on Fri, 2008/08/22 - 9:07pm

I guess were on same track, I'm using JMesa for quite a while, in my swf application. If I do have problems, could I ask for help?

David Sills replied on Sat, 2008/08/23 - 1:23pm in response to: Neil Juan

Sure, but your best bet is the Google JMesa group: http://groups.google.com/group/jmesa/topics. Jeff Johnston (the creator of JMesa) moderates, and is extremely friendly and helpful.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.