WorldTurner Blog

Friday Aug 20, 2010

Refactoring wicket Pages to Components

In this blog post I'm going a little deeper into the topic I started earlier. This is the second post on wicket "Pages versus Components" and the last one on this topic for now.
In this post I will explain what kind of issues we ran into when we refactored a wicket application that was based mostly on Pages to a new application based on Components. I'll explain first why we did that.

The code I worked on was developed by two collegues who worked on the project earlier. It is an administrative GUI for a larger application with many different areas that need to be administered.
There is a module to add products to a catalog, and others to edit texts that are displayed, one to add the locations of shops, and more.

They worked well, individually, but were developed as wicket Pages, not Components.  We wanted to integrate them in a tabbed user interface, and for that we wanted to use the TabbedPanel component from the wicket-extensions library. I recreated the situation in a small application that is attached to this blog entry.

This is what the original page-based application looked like:



And this is what it looks like inside a tabbed panel:
(The second tab is the toy application that I included in the previous blog post)

Superclass

The first thing to do is to change the superclass of each page from WebPage to Panel.
It is a bit more complicated in practice, as the pages in our application were derived from a common superclass:

public class ProductOverviewPage extends AbstractAdminPage {

The common superclass adds a css file to the page, which is not necessary anymore when the page is turned into a component, as this css will then be added by the page that contains this component. The declaration becomes:

public class ProductOverviewPage extends GenericPanel<Void> {

Note that the superclass doesn't become 'Panel' as expected, but GenericPanel. GenericPanel is a Panel subclass that we always use instead of Panel. It adds two features to the standard Panel class:

  • The model object is 'genericized' - the type of the model object is passed as a type parameter, and the methods getModel and getModelObject will return correctly typed models (objects).
  • Each subclass can override the method 'initializeGui'. This method is invoked just before the first time that the component is rendered. Initializing the GUI here instead of in the constructor allows us to configure the component before the GUI is generated, which gives us more flexibility and less of a need to pass everything as parameters to the constructor.

The change of superclass to GenericPanel results in compilation errors. A WebPage can have a no-argument constructor, but a normal Component can't. Any Component needs a component id, which is passed to it as the first argument to its constructor.

So the constructor becomes:

public ProductOverviewPage(String id) {

To complete this step, I rename all classes that have the word "Page" in their name to their equivalent with "Panel" in their names, so our ProductOverviewPage becomes ProductOverviewPanel.

Linking from one page to another

The next problem that we will see are compilation problems whenever BookmarkablePageLink is used. Those are links from one Page to another. As the GUI code is now based on Components, they can't be the target of a BookmarkablePageLink anymore. One solution would be to use the class ComponentPage from my previous post. In this case however, no need to have bookmarkable pages. Instead, I created a new wicket component "ComponentLink", that functions very much in the same way as BookmarkablePageLink.

ComponentLink basically replaces one component with another when the link is clicked. But consider this:

  • the overview page has links to 'edit' pages for each line in the overview table
  • each 'edit' page has a link back to the overview page

Now if we used an instance of the target component in ComponentLink, we could get infinite recursion. Why?

  • On the overview page, you'd have something like "add(new ComponentLink("id", new ProductEditPanel("panel", productModel)));"
  • On the edit page, you'd have something like "add(new ComponentLink("id", new ProductOverviewPanel("panel")));"

If they both do that in the constructor, you'd have constructors calling eachother infinitely. But this can be helped by the delayed initialization that GenericPanel provides.
Passing instantiated components to ComponentLink has other drawbacks. It costs more memory in the session, and you need to know the id of the component at a point in the code were you should have to know about this id - having to know the component id frequently violates proper encapsulation rules.

To address these problems, ComponentLink does not use instantiated component, but takes a Class object for the component that it links to. This is also analogous to BookmarkablePageLink, which takes the class of the page that it links to.
ComponentLink can also take arguments that will be passed to the constructor of the component. So in our ProductOverviewActionPanel, a panel this is used inside the overview table of ProductOverviewPage / ProductOverviewPanel, you'll see that we have replaced:

    BookmarkablePageLink<Void> editLink = new BookmarkablePageLink<Void>("editLink", ProductEditPage.class);
    editLink.setParameter("id", getModelObject().getId());
    add(editLink);


with:

    add(new PageComponentLink("editLink", ProductEditPanel.class, getModel()));

One more surprise here, I'm using PageComponentLink instead of ComponentLink. A BookmarkablePageLink always knows what it is replacing: the current page. A ComponentLink has to know which component it is replacing, and by default, it will replace the parent component of the link itself. Or one specific component that was passed to it in the constructor. PageComponentLink is subclass of ComponentLink that will replace not the parent component, but the nearest ancestor that is annotated with the @PageComponent annotation. The PageComponent annotation is placed on components that can function as standalone components on the ComponentPage from my previous blog post.

That's it

That's basically it. When you're refactoring, you usually find ways to refactor common code to base classes or utility classes, but that's not related to components per se.

I have again included all the code from my sample application. You can download it here. (A .tar.gz containing a maven source project) It contains both the page-based version and the component-based version shown above. It can be built with maven and easily run with "mvn tomcat:run".


Credits

In the sample application, I have used icons from the famfamfam silk icons collection. They are licensed under the Creative Commons Attribution license. For more information, see http://www.famfamfam.com/lab/icons/silk/

For the TabbedPanel styling, I used css and images from this article: http://www.alistapart.com/articles/slidingdoors/


Comments:

Post a Comment:
  • HTML Syntax: Allowed

Calendar

Feeds

Search

Links

Navigation