Table of Contents
After completing this set of articles, I decided to take a look at this same project and try and implement it with a framework that didn't provide conversational and flow functionality. Rather than start from scratch with something like PHP or Spring MVC which would make the statefulness completely manual, I decided to try and write it in Apache Wicket which is another framework I'm a fan of. Wicket is a framework which totally abstracts the HTTP and web mechanisms allowing the developer to write their application using pure java and Object Oriented code, more like a Swing Application.
This isn't meant to be a strict head to head against Seam and Spring for a number of reasons. First, Seam and SWF both offer built-in functionality to solve the problems we are trying to work around. Wicket just gives you the environment to build your own solutions to those problems. The value-add from Wicket is that it stays out of your way and only acts as a very thin layer between your OO Java code and the server. Because Seam and Spring Web Flow were supposed to handle the issues of creating stateful web applications, I had a 'rule' that the applications should be mainly built using the features out of the box rather than those which were created from code. With Wicket, just about every page you write involves writing some code so that rule doesn't quite apply.
If there is any comparison it would be how easy it is to implement similar functionality using Wicket. Granted, there is a lot of code in Seam and SWF that does a lot of work, however I think it quite likely that implementing lightweight alternatives to some of the most used core functionalities of these frameworks is possible with Wicket.
Creating an application with Wicket is simple, simpler
than the other two frameworks especially if you are
using Maven. The Wicket web site also includes
instructions on starting a Wicket project with Maven. To
set up the application in Eclipse, just create a new web
application, and create a subclass of a wicket
WebApplication
. The only requirement here is that you implement the
getHomePage
method that returns the default page class. Only a
minimal amount of xml needs to be added to
web.xml
to add the Wicket servlet and give the class name of the
Web Application class. There is no other XML
configuration required for Wicket.
Example 5.1.
WicketTrackerApplication.java
public class WicketTrackerApplication extends WebApplication {
@Override
public Class getHomePage() {
return ProjectListPage.class; //we'll define this later
}
}
Example 5.2.
Web.xml
<filter> <filter-name>WicketTrackerApp</filter-name> <filter-class> org.apache.wicket.protocol.http.WicketFilter </filter-class> <init-param> <param-name>applicationClassName</param-name> <param-value> org.wicketracker.web.WicketTrackerApplication </param-value> </init-param> </filter> <filter-mapping> <filter-name>WicketTrackerApp</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Wicket pages work by defining a page class and some HTML
with the same file name for each page in the
application. The HTML and the class reside in the same
source package, but Wicket can be configured to keep the
HTML in another location. In a Wicket page, the HTML
contains the markup and in the corresponding class, you
create java-side components to be bound to the markup.
The concept is easy to understand once you see an
example. We'll start with our project view page which
consists of the
ProjectViewPage.java
class and the markup
ProjectViewPage.html
. I've also created a simple template called
BasePage
which also consists of some HTML and a class from which
our pages will extend. This is markup inheritance and is
how Wicket handles templating.
Example 5.3.
ProjectViewPage.html
interface
<html>
<body>
<wicket:extend>
<table>
<tr>
<td class="label">ID</td>
<td><span wicket:id="id">{id}</span></td>
</tr>
<tr>
<td class="label">Title</td>
<td><span wicket:id="title">{title}</span></td>
</tr>
</table>
</wicket:extend>
</body>
</html>
I used a table for the layout simply because I didn't
want to clutter the HTML with additional markup to make
things lay out nicely. Here we have the Id and Title
captions created as spans that contain
wicket:id
attributes. These attributes provide a way to bind the
markup to the server side components we will create in
our backing java code.
Example 5.4.
ProjectViewPage.java
class
public class ProjectViewPage extends BasePage {
public ProjectViewPage(PageParameters parameters) {
Long id = ParamUtils.getLongObject(parameters, "projectId");
Project project = EMF.createEntityManager().find(Project.class, id);
add(new Label("id",project.getId().toString()));
add(new Label("title",project.getTitle()));
}
}
In the Java class for the project view page, we get the
projectId
from the page parameters, load the project and then
create the Wicket label components using the
project
object to provide values for the components.
We can create a similar page with the same kind of code
for the
Issue
object which we will call
IssueViewPage
. When we get to them, the edit pages for the project
and issues will be somewhat similar which makes you
realize that one key fundamental to developing with
Wicket is that you can leverage all the best Java design
practices to reduce complexity and repetition. For
example, the code to grab the Id from the parameters and
load the object could be genericized including handling
errors for missing parameters or missing entities. When
you start using Wicket, you almost get a case of code
freeze as you start thinking of the possibilities for
creating very re-usable code and mini-frameworks with
just a small amount of code. In these examples, I have
opted not to go a generic route but have focused on the
different ways that Wicket will let you do certain
things.
One common feature in Wicket is the concept of models
which provides Wicket components with source data for
display. Initially, you might find yourself just passing
the String values to the component as the model like we
did in the
projectView
page. Models can also be objects that implement the
IModel
interface. This simple interface provides a mechanism
for the component to consume the model as needed from
the implementation. The implementation could just hold a
simple object reference, or it could trigger the
fetching of data from the database or some external
source. Again, the abstraction leads to unlimited
possibilities.
I implemented the IssueView page a little differently by
using a
CompoundPropertyModel
which can be applied to a parent container such as a
page, form or panel and be shared with the child
components in that container. The child components take
their values from the model using the reflected property
values based on the component ids. The
CompoundPropertyModel
becomes far more useful when we are editing values and
it provides the two way binding by reading the values
from the model and pushing them back into the model on
the update.
Example 5.5.
IssueViewPage.java
public class IssueViewPage extends BasePage {
public IssueViewPage(PageParameters parameters) {
Long id = ParamUtils.getLongObject(parameters, "issueId");
initPage(id);
}
public void initPage(final Long id) {
Issue issue = EMF.createEntityManager().find(Issue.class, id);
IModel compound = new CompoundPropertyModel(issue);
setModel(compound);
add(new Label("title"));
add(new Label("description"));
add(new Label("id"));
add(new Label("project.title"));
add(new Label("status.title"));
}
}
Example 5.6.
IssueViewPage.html
<html>
<body>
<wicket:extend>
<table>
<tr>
<td class="label">ID</td>
<td><span wicket:id="id">{id}</span></td>
</tr>
<tr>
<td class="label">Title</td>
<td><span wicket:id="title">{title}</span></td>
</tr>
<tr>
<td class="label">Description</td>
<td><span wicket:id="description">{description}</span></td>
</tr>
<tr>
<td class="label">Project</td>
<td><span wicket:id="project.title">{project}</span></td>
</tr>
<tr>
<td class="label">Status</td>
<td><span wicket:id="status.title">{status}</span></td>
</tr>
</table>
</wicket:extend>
</body>
</html>
Note that the text in curly braces acts as a simple
visual placeholder for the content while we edit the
page and serves no purpose. We call our
initPage
method to setup the page components, and since we are
binding the form to a
ComponentPropertyModel
we need to give the components and the associated markup
the same
wicket:id
attribute as the name of the properties they will be
bound to. We also have properties on the entity that are
objects (i.e. project and status) so we need to name our
wicket components like we would access the property in
java or JSF i.e.
project.title
. We bind our model to the top level page and the child
components use that model to obtain the values based on
their name. There is a logical process to locating a
model for a component which involves seeing if one is
attached to the component, and then searching up the
component hierarchy until it finds one. In this case, it
finds the compound property model and uses its component
name to get the actual value. If you aren't a fan of
reflection or you have some more complex needs, you can
always just push the values into the component like we
did for the project view page. Note though that you
would also have to handle extracting the values from the
components when the page was submitted.
Now lets take a look at the code for the project list
page which uses the
ProjectListPage.java
class and the
ProjectListPage.html
markup. We want to fetch the list of projects, and then
bind them to some table markup in the page. Let's start
with the markup since it will help make the page code
clearer.
Example 5.7.
ProjectListPage.html
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/"> <body> <wicket:extend> <table class="dataTable"> <tr> <th>ID</th> <th>Title</th> <th>Link</th> </tr> <tr wicket:id="listView"> <td><span wicket:id="id">[ID]</span></td> <td><span wicket:id="title">[Title]</span></td> <td><a href="#" wicket:id="viewLink">view</a></td> </tr> </table> </wicket:extend> </body> </html>
This is the markup for the project view page. It
contains a HTML table with a header row, a row with a
listView
wicket id on it, and some columns. The columns contain
either spans or a link with a wicket id in it. Now let
us look at the page code for the project listing page.
Example 5.8.
ProjectListPage.java
public class ProjectListPage extends BasePage {
public ProjectListPage() {
super();
initPage();
}
public void initPage() {
LoadableDetachableModel model = new LoadableDetachableModel() {
@Override
protected Object load() {
ProjectDao dao = new ProjectDao();
dao.setEntityManager(EMF.createEntityManager());
return dao.listProjects();
}
};
ListView listView = new ListView("listView", model) {
@Override
protected void populateItem(ListItem item) {
//get the project for this row
Project project = (Project) item.getModelObject();
//add the components for this row
item.add(new Label("id", project.getId().toString()));
item.add(new Label("title", project.getTitle()));
//create a link for the project view page
PageParameters params = new PageParameters();
params.add("projectId", project.getId().toString());
item.add(new BookmarkablePageLink("viewLink",
ProjectViewPage.class, params));
}
};
add(listView);
}
}
This code has a couple of new things going on. For the
model, we used a
LoadableDetachableModel
which acts as a proxy and only loads the actual data
when needed by calling the
load
method. This data is detached (set to null) when the
detach()
method is called which means that we only keep our data
around as long as we need it and it is not stored with
the page object, the Model proxy is stored instead which
saves server session space. Once we have our model, we
bind it to a
ListView
component which provides a means of iterating over a
collection of items. For each row, the
populateItem
method is called and components can be added to that
row.
We also created our first link in Wicket. I found
linking was a little cumbersome at first given that
there is now obvious way to do it. In the HTML, we
create a link with an empty
href
attribute and we give it a wicket id. In the java code,
we create a link component with the same component Id, a
Wicket
WebPage
class, and we add the optional parameters to pass the
projectId
for that row. If you execute the application, you will
see that the link generated is
projects?wicket:bookmarkablePage=:org.wicketracker.web.pages.projects.ProjectViewPage&projectId=3
. Kind of ugly huh? Thankfully, we can beautify it by
mounting the page as one that can be bookmarked. In our
main Web Application class, we can add the following
code to make our URLs more user friendly.
Example 5.9.
WicketTrackerApplication.java
public class WicketTrackerApplication extends WebApplication {
@Override
public Class getHomePage() {
return ProjectListPage.class;
}
@Override
protected void init() {
super.init();
mountBookmarkablePage("/projects", ProjectListPage.class);
mountBookmarkablePage("/projectView", ProjectViewPage.class);
mountBookmarkablePage("/issueView", IssueViewPage.class);
}
}
Now when we look at our URLs, we will get something like
http://localhost:8080/WicketIssue/projectView/projectId/3/
.
Our project view page needs to have a list of the issues
for that project which entails grabbing the list of
issues for the project and putting them in a table. We
will use a Wicket panel which are similar to facelets in
that they have markup that can be re-used in several
pages. Unlike facelets, there is code associated with
the panel as there is with most other Wicket elements.
The panel is built the same way pages are, by using
markup and then code behind it binding the markup to
server side components. One key difference is that
typically the model is passed into the panel
constructor. Usually the data displayed in a panel
depends on the context of the parent page in which it is
displayed. Consider a panel to edit an address where the
address to be edited depends wholly on the page it is
contained in (person address, company address etc). In
such cases, the panel itself is not responsible for
loading the data, instead it relies on the containing
page to pass it the data. Of course, this is not always
the case, and the required data may be generated in the
panel as the situation demands (i.e. a panel that lets
you search for items). In our simple case, we want to
pass in a list of Issues as the model into the
constructor. However, good design with Wickets
flexibility says we can create constructors (or static
methods) that take a
projectId
and create a model for the issues for that project and
pass that on to the proper panel constructor. We can
also write something similar to promote type safety so
this panel is only ever passed a list of
Issue
objects which are then wrapped in a model instance.
Example 5.10.
IssueListPanel.html
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/"> <body> <wicket:panel> <table class="dataTable"> <tr> <th>ID</th> <th>Title</th> <th>Status</th> <th></th> </tr> <tr wicket:id="issueList"> <td><span wicket:id="id">[ID]</span></td> <td><span wicket:id="title">[Title]</span></td> <td><span wicket:id="status">[Status]</span></td> <td><a href="#" wicket:id="linkViewIssue">view</a><br/> </td> </tr> </table> </wicket:panel> </body> </html>
Example 5.11.
IssueListPanel.java
package org.wicketracker.web.pages.issues;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.wicketracker.model.model.Issue;
public class IssueListPanel extends Panel {
public IssueListPanel(String id, IModel model) {
super(id, model);
bindList();
}
public void bindList() {
ListView listView = new ListView("issueList",getModel()) {
@Override
protected void populateItem(ListItem item) {
Issue issue = (Issue) item.getModelObject();
item.add(new Label("id",issue.getId().toString()));
item.add(new Label("title",issue.getTitle()));
item.add(new Label("status",issue.getStatus().getTitle()));
PageParameters params = new PageParameters();
params.add("issueId", issue.getId().toString());
item.add(new BookmarkablePageLink("linkViewIssue",IssueViewPage.class,params));
}
};
add(listView);
}
}
The concept should be fairly simple. The constructor for
the Panel takes a model that we then bind to our
ListView
called
issueList
. This
issueList
is referenced in the markup in the table row. In the
ListView
anonymous class, we implemented the
populateItem
method to bind the data for that row to the issue object
for that row. We don't see any code to actually fetch
issues because we expect the parent container to do that
for us since all this panel knows is that it expects a
list of
Issue
objects. However, this code doesn't enforce that rule,
you could send in a list of any type wrapped in a model
and it would accept it. However, Wicket allows you to
enforce type safety by creating methods to accept typed
parameters.
There is a small gotcha here in that you need to think
ahead about the interface to these panels. For example,
if the panel relied on a compound property model in the
view, is it up to the panel owner (the page) to wrap the
data in that model, or should the panel receive the raw
objects and then wrap them in a property model? It seems
that the best strategy is to use the latter since then
the panel gets to create the exact model it needs, and
the caller is presented with a type safe interface where
it passes in (a list of) typed objects. However, this
leads to the problem of scoping the model, since you are
passing raw objects, how is the panel to know whether it
is detachable, or whether it is to be serialized in the
page? To get around this, you could pass in an
IModel
instance that will be wrapped by the panel in a compound
property model, but then we lose type safety. Current
versions of Wicket (1.3.6) don't have support for
generics which is a shame since genericized models would
be a great help in cases like this. Regardless, code
needs to be documented to describe how to interface with
these components.
This issue list panel can be reused in several places where we could pass in different lists of issues. Here we will use it in the project view page to show the list of issues for the project. To do this, we need to add the panel placeholder to the project view page and add the panel and pass it a model in the page code.
Example 5.12.
Additional markup in
ProjectViewPage.html
for the issues list panel.
<div wicket:id="issues"/>
Example 5.13.
Additional code in
ProjectViewPage.java
for the issues list panel in
initPage()
IModel issuesModel = new LoadableDetachableModel() {
@Override
protected Object load() {
ProjectDao dao = new ProjectDao();
dao.setEntityManager(EMF.createEntityManager());
return dao.findIssuesForProject(id);
}
};
add(new IssueListPanel("issues",issuesModel));
That's it, pretty simple. This code also handles the issue of state management so the list is not saved with the page since we are using a detachable model.
To edit a project, we go through the same steps as
before creating an HTML page and a java class with the
same name, and putting our markup in the HTML file and
binding it to components in the java class. This time,
we will be creating text editors and submit button
components. In this section, we will use a
Form
component to handle the form submission. We will create
an inner class to define the form which will contain the
edit controls and handle the submission. In the form
submission, we will save the updates to the
Project
instance.
Example 5.14.
ProjectEditPage.html
<html>
<body>
<wicket:extend>
<form wicket:id="form">
<table>
<tr>
<td class="label">ID</td>
<td><span wicket:id="id">{id}</span></td>
</tr>
<tr>
<td class="label">Title</td>
<td><input wicket:id="title" /></td>
</tr>
</table>
<input type="submit" value="Save" wicket:id="saveButton"/>
<input type="submit" value="Cancel" wicket:id="cancelButton"/>
</form>
</wicket:extend>
</body>
</html>
Example 5.15.
ProjectEditPage.java
public class ProjectEditPage extends BasePage {
private final Long id;
private final EntityManager entityManager;
private final Project project;
public ProjectEditPage(PageParameters parameters) {
id = ParamUtils.getLongObject(parameters, "projectId");
entityManager = EMF.createEntityManager();
if (id == null) {
project = new Project();
} else {
project = entityManager.find(Project.class, id);
}
initPage();
}
public void initPage() {
IModel propModel = new CompoundPropertyModel(project);
add(new ProjectEditForm("form", propModel));
}
}
We are keeping hold of the instance of the
Project
object and the
EntityManager
instance in the page object. This makes it easier to
handle persisting the entity back to the same entity
manager without attachment issues. This is a
questionable practice since this page will probably be
serialized and it would include the entity manager which
may or may not be serializable. There may also be issues
in a replicated environment where replicating the entity
manager may have unintended consequences. We use a
CompoundPropertyModel
assigned to the form to set the values of the individual
editors (Id and Title). The form component is set up
server side using an inner class called
ProjectEditForm
.
Example 5.16.
ProjectEditForm
inner class in the
ProjectEditPage
class.
class ProjectEditForm extends Form {
public ProjectEditForm(String id, IModel model) {
super(id, model);
add(new Label("id"));
add(new TextField("title"));
Button saveButton = new Button("saveButton");
Button cancelButton = new Button("cancelButton") {
@Override
public void onSubmit() {
setResponsePage(ProjectListPage.class);
}
};
cancelButton.setDefaultFormProcessing(false);
add(cancelButton);
add(saveButton);
}
@Override
protected void onSubmit() {
super.onSubmit();
//get the project from the model
Project project = (Project) getModelObject();
ProjectDao dao = new ProjectDao();
dao.setEntityManager(entityManager);
dao.saveProject(project);
//redirect to the project view page
PageParameters params = new PageParameters();
params.add("projectId", project.getId().toString());
setResponsePage(ProjectViewPage.class, params);
}
}
Another way we could have done this is by not using an
inner class and reproducing this code in the main page
class, and adding the components to a
Form
instance we created. We can demonstrate this in the
IssueEditPage.java
class.
For the issue edit page, our markup is pretty much the same as you would expect, text editors placed inside a form with a save and cancel button. The HTML hierarchy follows the same structure as the correlating components in the java class. The page at the top with a child form, and the id, title etc.. as child components.
Example 5.17.
IssueEditPage.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/">
<body>
<wicket:extend>
<form wicket:id="form">
<table>
<tr>
<td class="label">ID</td>
<td><span wicket:id="id">{id}</span></td>
</tr>
<tr>
<td class="label">Title</td>
<td><input wicket:id="title" value="{title}" /></td>
</tr>
<tr>
<td class="label">Description</td>
<td><input wicket:id="description" value="{description}" /></td>
</tr>
<tr>
<td class="label">Project</td>
<td><span wicket:id="project.title">Project.title</span></td>
</tr>
<tr>
<td class="label">Status</td>
<td><input wicket:id="status.title" value="{status}" /></td>
</tr>
</table>
<input type="submit" value="Save" wicket:id="saveButton" />
<input type="submit" value="Cancel" wicket:id="cancelButton" />
<a href="projects">Back To Projects</a>
</form>
</wicket:extend>
</body>
</html>
For now, I am leaving the project and status drop down selections as text fields. We'll look at turning them into drop down boxes later. Here we are creating the form and attaching the child components within our page instance as opposed to using an inner class.
Example 5.18.
IssueEditPage.java
public class IssueEditPage extends BasePage {
private static final Logger log = Logger.getLogger(IssueEditPage.class);
private final Long id;
private final Long projectId;
private final EntityManager entityManager;
private Issue issue;
public IssueEditPage(PageParameters parameters) {
id = ParamUtils.getLongObject(parameters, "issueId");
projectId = ParamUtils.getLongObject(parameters, "projectId");
entityManager = EMF.createEntityManager();
initPage();
}
public void initPage() {
//we check to see if an id was passed and if not,
//we create a new Issue object if there is a project Id available
//If there is no project or Issue Id, then we go back to the project List
//We can also add security logic here to see if they can edit/add Issues.
if (id == null) {
if (projectId == null) {
getSession().error("You cannot create an issue without a project Id");
setResponsePage(ProjectListPage.class);
return;
}
// create new issue
issue = new Issue();
Project project = entityManager.find(Project.class, projectId);
if (project == null) {
getSession().error("Project Not Found");
setResponsePage(ProjectListPage.class);
return;
}
issue.setProject(project);
} else {
//load the issue
issue = entityManager.find(Issue.class, id);
if (issue == null) {
getSession().error("Issue Not Found");
setResponsePage(ProjectListPage.class);
return;
}
}
//create the model around the issue object
IModel model = new CompoundPropertyModel(issue);
//create our form instance
Form form = new Form("form",model) {
@Override
protected void onSubmit() {
IssueDao dao = new IssueDao();
dao.setEntityManager(entityManager);
dao.save(issue);
setResponsePage(IssueViewPage.class,new PageParameters("issueId="+issue.getId().toString()));
}
};
//add the form components
form.add(new TextField("title"));
form.add(new TextField("description"));
form.add(new Label("id"));
form.add(new Label("project.title"));
form.add(new TextField("status.title"));
//create the form buttons
Button saveButton = new Button("saveButton");
//when cancelling, we check to see if it was a new issue
//if so, we go to the project page
//otherwise, we go to the issue view page
Button cancelButton = new Button("cancelButton") {
@Override
public void onSubmit() {
super.onSubmit();
PageParameters params = new PageParameters();
if (issue.getId() == null) {
//goto project view since this issue doesn't exist
params.add("projectId", projectId.toString());
setResponsePage(ProjectViewPage.class,params);
} else {
//goto issue view page
params.add("issueId", issue.getId().toString());
setResponsePage(IssueViewPage.class,params);
}
}
};
//we don't want to handle the submit on clicking the button
cancelButton.setDefaultFormProcessing(false);
//add the buttons to the form
form.add(saveButton);
form.add(cancelButton);
//add the form
add(form);
}
}
That's a lot of code, but it actually does quite a bit.
We perform some real-world validation on the input
parameters and if it fails we immediately jump to
another page and pass an error message. If the user
cancels the editing, we jump to either the issue view
page if this was an existing Issue, or to the project
page if this was a new issue (and therefore there is
nothing to view). Compared to the XML based navigation
of JSF this appears nice and tidy as well as being
flexible enough to handle complex cases with java code.
Furthermore, we didn't have to jump to different files
to handle the different pieces of the process. If we get
to a point where a page needs multiple navigation rules
depending on how it is used, we can implement some
additional code later, even in a separate class if needs
be and refactor our existing code. Also, rather than
create a new inner class, we created our own
Form
instance, bound it to the model and added components to
it in the page itself. The
defaultFormProcessing
on the button is similar to the immediate attribute on
JSF buttons. When this is turned off, Wicket skips the
conversion, validation and model updating stages of the
request process.
So far, we have handled the basics of a web application. Loading, displaying, editing and saving data. The Seam vs Spring Web Flow articles also tackle more complex issues such as stateful navigation. The main test for this was the ability to edit an object (like the issue), go to another page to select a project and then come back to the issue editor with the original issue changes intact and the new project selected. The goal was to see if the frameworks would provide us with high level functionality without forcing us to develop unscalable and complex low level solutions.
First though, let's start by creating drop downs
from entity objects and bind them to an object
property and we'll see if Wicket handles the
conversion and update easily and correctly. We'll
start with fetching the list of status values in the
IssueEditPage
.
Example 5.19.
Adding issue status drop downs
IssueEditPage.java
.
public class IssueEditPage extends BasePage {
...
...
private final List<IssueStatus> statuses;
public IssueEditPage(PageParameters parameters) {
...
...
//load the list of issue status values
IssueDao dao = new IssueDao();
dao.setEntityManager(entityManager);
statuses = dao.listStatuses();
initPage();
}
public void initPage() {
...
...
DropDownChoice statusSelect = new DropDownChoice("status",statuses);
form.add(statusSelect);
...
...
}
We removed the original component for
status.title
and replaced it with the above drop down choice. We
also changed the HTML markup to use a select.
Example 5.20.
HTML to use a select instead of text in
IssueEditPage.html
<select wicket:id="status" value="{status}" />
Because we are using the compound model in the form
which references the issue, we need to refer to the
status
property of the
Issue
object. This code will give us the ability to show
the issue state in a drop down and when we save it,
write the value back to the issue's status property
because we are using a
ComponentPropertyModel
.
As you can see, it works, almost. We need to display
the correct text in the drop down as we currently
display the
toString()
value. Looking at the options for creating a
DropDownChoice
, we can also pass in an
IChoiceRenderer
. We add that as an anonymous class to the
constructor. Obviously, the better solution is to
creating a class that implements this interface for
this type so it can be reused anywhere we need to
display the issue status information.
Example 5.21.
Adding the
IChoiceRenderer
to
IssueEditPage.java
DropDownChoice statusSelect = new DropDownChoice("status",statuses,new IChoiceRenderer() {
public Object getDisplayValue(Object object) {
return ((IssueStatus)object).getTitle();
}
public String getIdValue(Object object, int index) {
return ((IssueStatus)object).getId().toString();
}
});
That was pretty painless and avoided the problems of
having to wrap the objects in a
DataModel
as JSF does. When we change values, and save the
issue, the correct value is put into the status
property thanks to the
CompoundPropertyModel
.
Now let's see how we can navigate from the issue
edit page, select a project and go back to the issue
edit page to resume editing the issue. First let's
consider how we do this since we are completely on
our own on how we implement it. We want to navigate
to the project page and when we select a project,
put it into the
project
property on the issue we are editing and return to
the issue edit page. This is all while keeping any
changes we made to the issue before we navigated
away to the project page and back. Let's look at the
project list page first to see how this might be
done. We want to add a column containing a select
link in the list of projects in the project list
page to select that project. In the backing code, we
add this link component and give it an on click
handler which calls a method to handle the
selection. The
handleSelect
method is empty and will be filled in by the calling
page since only that page knows what to do when you
select an entity.
Example 5.22.
Adding the select link in the
ProjectListPage.java
.
public void initPage() {
LoadableDetachableModel model = new LoadableDetachableModel() {
...
};
ListView listView = new ListView("listView", model) {
@Override
protected void populateItem(ListItem item) {
final Project project = (Project) item.getModelObject();
item.add(new Label("id", project.getId().toString()));
item.add(new Label("title", project.getTitle()));
...
...
//Adding the select link here
item.add(new Link("select") {
@Override
public void onClick() {
log.debug("Submit clicked on project");
handleSelect(project);
}
});
}
};
add(listView);
}
In our
ProjectListPage.html
page we add the link into a new column in the table
showing the list of projects.
Example 5.23.
ProjectListPage.html
<table class="dataTable"> <tr> <th>ID</th> <th>Title</th> <th>Link</th> <th>Select</th> </tr> <tr wicket:id="listView"> <td><span wicket:id="id">[ID]</span></td> <td><span wicket:id="title">[Title]</span></td> <td> <a href="#" wicket:id="viewLink">view</a> <a href="#" wicket:id="editLink">edit</a> </td> <!-- New Select Link Goes Here --> <td><a href="#" wicket:id="select">Select</a></td> </tr> </table>
Going back to our issue editor page, we add a button next to our project display text to invoke the project selection.
Example 5.24.
Adding the project change button to our
IssueEditPage.html
.
<tr>
<td class="label">Project</td>
<td>
<span wicket:id="project.title" >{project}</span>
<input type="submit" wicket:id="select" value="Change"/>
</td>
</tr>
We then add the corresponding "Change Project"
button component in the
IssueEditPage.java
code. In the
onSubmit()
method of the button we set the response to point to
an instance of the project list page. We pass an
instance that we create here as an anonymous class
because we want to override the
handleSelect
method in the
ProjectListPage
class to set the
project
property on the
issue
entity in this page. After assigning the project, we
set the response page to this instance of the
IssueEditPage
class. This causes us to end up back on the original
page that we called the project list page from with
our original changes to the issue object. To spice
things up a bit I put in some validation so you
couldn't select the project with an id of 3 just to
see how easy it is to validate that selection and
stay on that same page while passing a message back
to the user.
Example 5.25.
Adding the project selection button in
ProjectEditPage.java
.
Button changeProject = new Button("btnSelectProject") {
@Override
public void onSubmit() {
setResponsePage(new ProjectListPage() {
@Override
public void handleSelect(Project entity) {
if (entity.getId() == 3) {
getSession().error(
"cannot select project with Id 3");
} else {
issue.setProject(entity);
setResponsePage(ProjectEditPage.this);
}
}
});
}
};
changeProject.setDefaultFormProcessing(false);
form.add(changeProject);
add(form);
You may have noticed is that the select link is
always displayed on the project list. In the Seam
and Spring JSF versions, we always hid it if there
was no selection possible. The reason I have not
done so here is because there are ways to do it by
componentizing your table and columns which I didn't
get in to here. However, the solution is relatively
trivial, but requires replacing the table with a
DataTable
component and defining your columns in code. The
DataTable
is part of the wicket extensions.
This solution for handling more complex navigation works, and it works really well. It doesn't involve a mess of HTML or XML, just some Java code. Granted, a rigid implementation like this can soon get complex if you need something more sophisticated, however, Wicket makes it easy to knock up a more generic version in a small amount of time. This mechanism is re-usable, and the project list page knows nothing about the calling page which is how we want it. One subtle gem here is the fact that unlike the managed environments of Seam and Spring, you can just create a new anonymous class and override methods to change functionality. This isn't possible with Seam and Spring because they are managed environments and you never create your own components, therefore such dynamic interactions can be harder to implement leaving you to hard wire the relationships between the issue editor and the project selection.
Wicket is a really good framework to use, it lays out a
minimalist set of ground rules and gives you a great
environment to work in. The first important aspect is
the ability for Wicket to hold page state which enables
you to hold objects between requests. This is a
fundamental piece of both Seam and SWF, but Wicket
offers this in a simplified fashion where only the page
is held as opposed to whatever conversational objects
you invoke as part of the flow, as well as incidental
ones created by the framework (just look at the list of
objects in a conversion in the
seam.debug
page).
Wicket HTML markup consists of markers into which Wicket will place the component HTML. This makes editing the view straightfoward and doesn't push too much decision making and certainly no business logic into the view . On the back end, in the page object we add the components, couple them to to models and set property values on the components that alters the final HTML. None of this is controlled from the view HTML, it is all done on the java server side. This server side only approach makes component instantiation easier and more natural but can be verbose. You gain the benefit of IDE features which apply to strongly typed Java code such as static typing, code completion, refactoring as well as renaming. To access components in JSF you define a component in a web page, and it can optionally have a binding to a bean property which will hold the JSF component reference on the server side. Wicket just holds the components as a plain old java variable (pojv?) which is natural and efficient without the need to lookup EL component bindings, but, this comes at the expense of having to explicitly declare each component declaration , creation and binding in code. It has been claimed that you can see Wicket markup by opening up the HTML file which is true but since you should be using templating and probably runtime specific stylesheets (i.e, theming) it would be hard to get a good idea of what the page looks like.
Other frameworks (I'm mostly considering Seam and SWF here) have far more rules to obey and they are somewhat more contextual in that they only apply under certain circumstances. They do deliver far more in terms of features, but following the 80/20 principle, 20% of the features will be used 80% of the time, and simplified versions of that can be written in Wicket. I'm not suggesting that you need to consider implementing Drools or full conversion support in Wicket. You could however code individual pages to share objects across pages and requests which is mostly what conversations are used for. However, Wicket does provide built in support for providing pagination components and handling it on the back end in the model and these do work well.
I found Wicket to be fairly simple to use, and rarely had to go look at any reference documentation once the underlying prinicples were understood. I had a little extra work to look up info related to links, templating and displaying data tables, but beyond that, most things could be found using code completion to give me an idea of what I was looking for.
I have a couple of solid concerns and one 'philosophical' concern which is that Wicket really lets you 'explore the studio space', and some people will take this and build huge monuments to good coding practices and OO design. Other developers will take this and create spaghetti code. Regardless, Wicket gives you free will to do with your code what you will, and when you start using it, you'll start spotting all sorts of places where you can invoke the DRY principle (Don't Repeat Yourself) and reduce code repetition. The inverse is that the project might become a victim of Astronaut Architects or code freeze as you try and define the perfect architecture on top of Wicket. Another aspect of this is that any discussion of best practices for Wicket cannot exclude a discussion of best practices for OO code such as using anonymous versus inner versus top level classes, composition over inheritance, and use of GoF design patterns. This is a huge (endless?) discussion which could detract from actually getting things done. The Seam and SWF frameworks expect and direct the boilerplate code to be handled a certain way which as long as it works well is a benefit. Yes, I'm saying that too much freedom can be a bad thing.
Another problem is the potential for abusing the session which is a noted concern by critics of Wicket. Common sense should be applied so you don't hold unnecessary data in the page that ends up being serialized in the session. Using detachable models can help by reloading the data each time the page is rendered. However, this same problem will exist in all stateful frameworks, and caution is required by any framework that handles state in the session. There is also some chatter of problems when holding references to other pages and handling circular references to pages upon serialization. Passing page references around may make stateful coding easier, but it seems that it too could come back to haunt you. Seam and SWF store the state in conversations which time out and are destroyed. While this allows any number of conversations, because they are managed, all the pieces of the conversation time out and are destroyed together and it can help control session sizes.
While the current version of Wicket (1.3.6) doesn't use generics, version 1.4 which is now in release candidate 6 at the time of writing does use generics. However, one problem is the incompatibility between applications using Wicket 1.3.x working with Wicket 1.4. On this basis, you would need to think hard on whether to start with the current stable release even if it is incompatible with the next version which is currently only a release candidate.
JSF , Seam and SWF all depend on EL expressions (or variants of) which can get expensive when you have an expression with multiple elements that need evaluating especially since JSF could repeat the evaluation more than once in a page. In Wicket, you are typically binding the actual object to a model which uses reflection on the object to obtain property values for reading and writing. This is far more efficient since it doesn't have to repeatedly lookup the initial object in a massive dictionary of available objects several times a page.
Wicket appears to be much faster than the JSF based frameworks. No doubt this is partly because of the expense of JSF and EL as well as the bundling of all the other features in the frameworks. Another 'feature' if you want to see it that way is that it can be used with Google App Engine for Java unlike JSF (and therefore Seam and Spring Faces). Web Beans also comes with an example that uses Wicket and GAE, or you can also use Spring with GAE.
Wicket does lack some of the functionality and flexibility of Seam and SWF . Seam offers JSF based email and pdf and excel documents from JSF documents by using the already defined data components letting you do more with what you have already built. Both SWF and Seam come with Dependency Injection capabilities whereas Wicket does not although there are add-ons for Spring, Google Guice, injecting EJBs and even Web Beans. Wicket has a disconnect with injection frameworks since it is an unmanaged and (semi-?)stateful framework. The fact that page instances can be serialized means we need special handling for references to managed beans so we don't serialize and replicate them. However, there is a very capable work around with the spring annotations extensions. Wicket also comes AJAX ready with some easy to use AJAX controls and an AJAX debugger so you can see what messages are going on behind the scenes (a very nice idea).
One important note is that by using POJOs in an unmanaged environment we can very easily subclass web pages or create anonymous class instances with different handlers for different events. We can't just create a new anonymous Spring Bean or Seam component out of thin air and augment it with new features. This is a huge plus for Wicket since this flexibility lets you extend classes in ways such that the base class remains ignorant of the context it is being used. For example, a search and select popup panel should know nothing about the recipient of the selection (the parent page). To get such features in Seam or Spring, you need to rely on the workflow engine to act as the middle man between the different pieces.
Overall, Wicket is a great framework to use, and I hope it really receives the success it deserves as a great lightweight component framework.
As I was thinking about this, I started comparing the frameworks in terms of different languages :
Wicket = Assembler - Small, powerful, but has lengthy code that quickly gets messy if you aren't able to organize OO code well. Little stands between you and the bare metal and this gives you a lot of control. To get more complex things done, you need to build on top of it to create a more advanced framework.
Spring Web Flow = C++ - It's fast, it's popular, (Spring at least is already in widespread use), but some pieces don't fit well with others at times. A robust but no frills development experience.
Seam = Delphi - Almost as capable as C++, and covers a broader range of problems well. It's not as popular, but very easy to use with excellent tooling and all round integration. Solves a number of problems inherent in the other solutions. Mildly slower than C++ (at least early on,not so much later).
They all have different appeals and benefits from different perspectives. If you are looking at frameworks in terms of career, looking at job listings, JSF is leading most web frameworks aside from Struts. Seam may be slow in picking up momentum, but it is ahead of Wicket and SWF. SWF is helped by massive Spring adoption while Seam is almost alien to existing EJB or POJO based applications. As far as popularity goes, Spring Faces is probably the better option with the JSF/Spring/Spring Web Flow combination looking good on a resume. This is interesting, because while I think this is a great option, it has a couple of fairly deep flaws that the others don't which would put them ahead of SWF.
If I were looking at it from the perspective of a single person startup for a small project (which I'm also considering), then this introduces a few more issues. If you want to keep hosting cheap, perhaps even running on something like Google App Engine, then JSF is problematic as not only does 1.2 not run on GAE, but it would probably chew up more resources (on any host) than you would like. For me, Wicket would be the choice there as it is more lightweight and session size is less of a concern given that you have a little more control. Obviously, depending on your needs, Wicket could be a winner in larger projects and isn't limited to smaller projects. With a larger team and a more complex project, more groundwork is needed to standardize the coding practices so you don't have one too many differing coding styles. JSF and Seam/SWF pretty much direct you down that path with one most obvious way of doing most things. Wicket is also useful where alternatives might be overkill such as a shopping cart type app with little state management and user CRUD operations. I really like the Wicket programming model, I just don't think it is applicable in all cases (just like assembly language isn't).
Lastly, I think Seam leads in terms of features and productivity in a cohesive package. I can start a new project and easily code CRUD pages in a matter of minutes with just a few lines of code (mostly XHTML). I can then extend that to provide emails, pdfs and excel spreadsheets, ajax, Seam remoting, and web services using the same backing beans. I don't have to worry about state management, OSiV, Lazy Initializations, I can introduce workflow any time I want and I don't have to worry about pages where I don't want to use workflow and having to come up with an alternate method of getting to my data. This is all wrapped up in a nice cool IDE that is so good I use it with Wicket and Spring Web Flow development. While it has a number of blemishes, most of them can be worked around, and while it has a steeper learning curve, it's not impossible to learn, and once you get it, the productivity can be quite a pay off. Interestingly enough, I have trained people to use Seam whom I cannot imagine would be as quick to pick up the nuances of good OO design in order to work with Wicket.
Lastly, I think it will be interesting to see how the choice of stateful and semi-stateful web frameworks will grow. There is a beta version of Conversations for Tapestry , and Web Beans (JSR 299) will provide a common framework for handling conversation scoped beans as well as a standard for (conversational) dependency injection. It will be interesting to see what new solutions arise from these and how they differ and improve on these pioneering stateful frameworks.
| http://www.andygibson.net/ | Copyright © 2008 Andy Gibson |