Running the Table With JMesa
Putting It All Together
We'll refactor out reusable code once more in writing a Factory to encapsulate building our customized table, starting with an abstract class:
public abstract class AbstractTableFactory
{
protected abstract String getTableName();
protected abstract void configureColumns(TableFacade
tableFacade, Locale locale);
protected abstract void configureUnexportedTable(TableFacade
tableFacade, Locale locale);
protected abstract ImageCellEditor getEditImageCellEditor(Locale
locale);
protected abstract ImageCellEditor getDeleteImageCellEditor(
Locale locale);
public TableFacade createTable(HttpServletRequest request,
HttpServletResponse response, Collection items)
{
TableFacade tableFacade = new TableFacadeImpl(getTableName(),
request);
tableFacade.setItems(items);
tableFacade.setStateAttr("return");
configureTableFacade(response, tableFacade);
Locale locale = request.getLocale();
configureColumns(tableFacade, locale);
if (! tableFacade.getLimit().isExported())
{
configureUnexportedTable(tableFacade, locale);
}
return tableFacade;
}
public void configureTableFacade(HttpServletResponse response,
TableFacade tableFacade)
{
tableFacade.setExportTypes(response, getExportTypes());
tableFacade.setToolbar(new ReorderedToolbar());
Row row = tableFacade.getTable().getRow();
if (row instanceof HtmlRow)
{
HtmlRow htmlRow = (HtmlRow) row;
htmlRow.setFilterable(false);
htmlRow.setHighlighter(false);
}
}
protected ExportType[] getExportTypes()
{
return null;
}
protected void configureColumn(Column column, String title,
CellEditor editor)
{
configureColumn(column, title, editor, false, true);
}
protected void configureColumn(Column column, String title,
CellEditor editor, boolean filterable, boolean sortable)
{
column.setTitle(title);
if (editor != null)
{
column.getCellRenderer().setCellEditor(editor);
}
if (column instanceof HtmlColumn)
{
HtmlColumn htmlColumn = (HtmlColumn) column;
htmlColumn.setFilterable(filterable);
htmlColumn.setSortable(sortable);
}
}
protected void configureEditAndDelete(Row row, WebContext webContext,
CoreContext coreContext, Locale locale)
{
HtmlComponentFactory factory = new
HtmlComponentFactory(webContext, coreContext);
HtmlColumn col = factory.createColumn((String) null);
col.setFilterable(false);
col.setSortable(false);
CellRenderer renderer = col.getCellRenderer();
ImageCellEditor editor = getEditImageCellEditor(locale);
editor.setWebContext(webContext);
editor.setCoreContext(coreContext);
renderer.setCellEditor(editor);
col.setCellRenderer(new
AlignedHtmlCellRendererDecorator((HtmlCellRenderer)
renderer, "center", "middle"));
row.addColumn(col);
col = factory.createColumn((String) null);
col.setFilterable(false);
col.setSortable(false);
renderer = col.getCellRenderer();
editor = getDeleteImageCellEditor(locale);
editor.setWebContext(webContext);
editor.setCoreContext(coreContext);
renderer.setCellEditor(editor);
col.setCellRenderer(new
AlignedHtmlCellRendererDecorator((HtmlCellRenderer)
renderer, "center", "middle"));
row.addColumn(col);
}
}
This has a lot of code (note the abstract methods ), in part because I know I usually want edit and delete columns. One line that might pass by unnoticed in all this, however, is really quite something:
tableFacade.setStateAttr("return");When this attribute is set, JMesa uses the Memento design pattern to save the state of its tables. When you return to a table page and include the attribute you specify here in the URL, you return to the exact place you left: the page number to which you had moved before leaving the table, the number of values displayed per page, and so forth.
The application-specific concrete class, after all this, can be pretty simple:
public class HelloWorldTableFactory extends AbstractTableFactory
{
protected MessageSource messageSource;
public void setMessageSource(MessageSource messageSource)
{
this.messageSource = messageSource;
}
@Override
protected String getTableName()
{
return "results";
}
@Override
protected ExportType[] getExportTypes()
{
return new ExportType[] { CSV, EXCEL };
}
@Override
protected void configureColumns(TableFacade tableFacade, Locale locale)
{
tableFacade.setColumnProperties("firstName", "lastName",
"format", "toString");
Row row = tableFacade.getTable().getRow();
configureColumn(row.getColumn("firstName"),
messageSource.getMessage("column.firstName", null,
locale), null);
configureColumn(row.getColumn("lastName"),
messageSource.getMessage("column.lastName", null,
locale), null);
configureColumn(row.getColumn("format"),
messageSource.getMessage("column.format", null, locale),
new SpringMessageCellEditor(messageSource, "format",
locale), false, false);
configureColumn(row.getColumn("toString"),
messageSource.getMessage("column.toString", null, locale),
new ToStringCellEditor(), false, false);
}
@Override
protected void configureUnexportedTable(TableFacade tableFacade,
Locale locale)
{
HtmlTable table = (HtmlTable) tableFacade.getTable();
table.setCaption(messageSource.getMessage("table.caption", null,
locale));
configureEditAndDelete(table.getRow(),
tableFacade.getWebContext(),
tableFacade.getCoreContext(), locale);
}
@Override
protected ImageCellEditor getEditImageCellEditor(Locale locale)
{
return new HelloWorldImageCellEditor("edit.gif",
messageSource.getMessage("image.edit.alt", null, locale),
"edit.html?pk={0,number,integer}");
}
@Override
protected ImageCellEditor getDeleteImageCellEditor(Locale locale)
{
return new HelloWorldImageCellEditor("delete.gif",
messageSource.getMessage("image.delete.alt", null, locale),
"delete.html?pk={0,number,integer}");
}
}
Controller
We end as we began, with a Spring MVC Controller to launch all this infrastructure. Since the details of table creation are encapulated in a factory, this is uncluttered: the only decision to be made is whether or not the table is to be exported. If it is exported, the results will be written directly to the output stream of the response; if not, they'll be rendered as a string containing our HTML table:
public class CustomJMesaSearchController extends AbstractController
{
private HelloWorldService helloWorldService;
private HelloWorldTableFactory tableFactory;
public void setHelloWorldService(HelloWorldService helloWorldService)
{
this.helloWorldService = helloWorldService;
}
public void setTableFactory(HelloWorldTableFactory tableFactory)
{
this.tableFactory = tableFactory;
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest
request, HttpServletResponse response) throws Exception
{
Set<HelloWorld> results = helloWorldService.findAll();
TableFacade tableFacade =
tableFactory.createTable(request, response, results);
if (tableFacade.getLimit().isExported())
{
tableFacade.render();
return null;
}
return new ModelAndView("results", "results",
tableFacade.render());
}
}
We are actually reusing the same JSP page as in the basic JMesa setup: the only difference is in the Java code that generates the table. One more change in jmesa-servlet.xml to create everything and tie it all together:
<bean id="tableFactory"
class="com.javalobby.article.jmesa.util.HelloWorldTableFactory">
<property name="messageSource" ref="messageSource"/>
</bean>
<bean id="customSearchController"
class="com.javalobby.article.jmesa.web.CustomJMesaSearchController">
<property name="helloWorldService" ref="helloWorldService"/>
<property name="tableFactory" ref="tableFactory"/>
</bean>
...
<prop key="/search.html">customSearchController</prop>
And how different the display looks!:
Figure 5.: A customized search result
Ajax
Finally, the table looks like we want it to, but it's irritating having to resubmit the form each time we want to make a change. Isn't that the sort of thing Ajax is supposed to help us avoid?
The answer is, of course, yes! So how do we leverage Ajax to help us? Fortunately, the JMesa folks have already worked that out. There are two parts to the solution: changes to the controller and changes to the JSP page.
<form name="resultsForm" action="search.html"><b>
<div id="results-div"></b>
${results}<b>
</div></b>
</form>
<script type="text/javascript">
function onInvokeAction(id, action)
{
setExportToLimit(id, '');<b>
var parameterString = createParameterStringForLimit(id);
$.get('${pageContext.request.contextPath}/search.html?ajax=true&'
+ parameterString,
function(data)
{
$("#results-div").html(data);
}
);
}
function onInvokeExportAction(id, action)
{
location.href="${pageContext.request.contextPath}/search.html?" +
createParameterStringForLimit(id);
}
</script>
In our previous solution, the onInvokeAction Javascript method called createHiddenInputFieldsForLimitAndSubmit, which submitted the form. In the Ajax solution, it assembles parameters for the TableFacade class and sends a request for the HTML for table display, adding a parameter to indicate that it's an Ajax request. Then a callback Javascript function substitutes the returned HTML for the contents of the <div> that now holds the table.
The simplicity and unusual syntax of the latter code come courtesy of the jQuery Ajax library, which is thoughtfully used by JMesa:
<script type="text/javascript" src="js/jquery-1.2.3.min.js"></script>
The controller, of course, needs to interpret this new request correctly. This is just one more branch on the decision tree we saw in the previous controller:
public class AjaxJMesaSearchController extends AbstractController
{
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest
request, HttpServletResponse response) throws Exception
{
Set<HelloWorld> results = helloWorldService.findAll();
TableFacade tableFacade = tableFactory.createTable(request,
response, results);
if (tableFacade.getLimit().isExported())
{
tableFacade.render();
return null;
}
else if ("true".equals(request.getParameter("ajax")))
{
String encoding = response.getCharacterEncoding();
byte[] contents = tableFacade.render()
.getBytes(encoding);
response.getOutputStream().write(contents);
return null;
}
return new ModelAndView("ajax-results", "results",
tableFacade.render());
}
}
Of course, we have to make Spring aware of the controller change in jmesa-servlet.xml:
<bean id="ajaxSearchController" class="com.javalobby.article.jmesa.web.AjaxJMesaSearchController">
<property name="helloWorldService" ref="helloWorldService"/>
<property name="tableFactory" ref="tableFactory"/>
</bean>
...
<prop key="/search.html">ajaxSearchController</prop>
That's all there is to it! The table looks and acts just as it did, except now it refreshes without resubmitting the form each time.
Conclusion
Now I don't have to like tables: I can program them in Java and not worry about them on a display JSP. This makes the page cleaner, gives me more functionality out-of-box, and enables me to nix at least some of the languages I'd otherwise have to fuss with. What's not to like?
I hope you'll take a good look at JMesa and see if it can make your life easier, and that this article helps you decide. Good luck!
Installation of the Eclipse Project
Installing the Eclipse project is not difficult; the included Ant build file and these instructions assume Tomcat as the deployment target (I'm using version 6.0.14 with JDK 6.0_03). If you want to use another servlet container, though, feel free to modify the instructions and the Ant file as needed:
- download the ZIP archive
- unzip the archive to any directory; it will create its own top-level subdirectory
- open the project as a Java project in Eclipse
- the project must use the Java 6 compiler (available from "http://java.sun.com/javase/6/")
- the Tomcat installation must be version 6 (available from "http://tomcat.apache.org/download-60.cgi")
- open the build file and modify the path to the Tomcat root
- add an external JAR file to the Eclipse project build path from the Tomcat installation: lib/servlet-api.jar
- run the Ant "deploy" target, which will build automatically
- open a browser and point it to "http://localhost:8080/running-jmesa-examples/" or to an equivalent URL for your setup
(N.B. Some code in the project has been refactored from the way it appears in the article.)
- « first
- ‹ previous
- 1
- 2
- 3
- 4
- Login or register to post comments
- 18912 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)










Comments
nfrankel 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
bguerout 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:
Once again, congratulations on a great article!
-Jeff Johnston
David Sills replied on Sun, 2008/06/22 - 1:59am
in response to: nfrankel
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
blitzmoiko 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: blitzmoiko