Steve Forsyth currently resides in sunny California with his wife and kids. Steve has worked in several different industries as well as worked with several software development technologies. His work has taken him from building financial reports for executives at Franklin Capitol, creating international websites for the worlds largest technology provider to the creation of custom web portals for large scale media applications. Steve is most comfortable with finding the needs of a client and turning those needs into a useful, timesaving tool. He currently has an affection for the Wicket Java application framework and hopes to some day see the end of cross-browser hell. Steve has posted 1 posts at DZone. View Full User Profile

Wicket Tutorial Series: The UI

03.13.2009
| 33642 views |
  • submit to reddit

Simple and Clean

have shown you a very simple form and how easy it is to create a working form submission, but have you noticed that there isn’t any java snippets of any kind in the html?
In my opinion, that is one of the best features of wicket, there ARE wicket:ids but those are attributes and tags that are ignored by most GUI designers such as Dreamweaver so the HTML can be ported back and forth if need be without the graphics designer hosing the developers hard work. Even if you have to take a fresh copy of the HTML… it is far simpler to just have to add the wicket:ids back in than to merge in all of the XML or JSP crud that most other Java frameworks force the developer to work with.
 
Gone are the days of System.outs in your jsps to figure out what is going on in there. With Wicket… all of your code is in Java classes which can be debugged easily with your favorite IDE. You can walk through your loops to see what you are populating and why. You can even debug portions of Ajax calls as Wicket Ajax enabled components hide the complexity of Ajax submissions and data retrieval.
 
Enough jabbering… let’s see some list action and paging goodness!
 

History

Form handling and components are wonderful but I think the history page shows off some of my favorite components within Wicket. There is a nice selection of different types of repeater components and a great paging component that we will use to display paste history.
 
Let’s start off by talking about the DataView component that we are going to use to display the pastes. The DataView component is a repeater that allows us to easily mark what we want to repeat within the html and fill in the data from our model object. This is done by adding the DataView to our page and then implementing the DataViews populateItem method as follows:

add(historyDataView = new DataView("history", new HistoryDataProvider(pasteService), 10) {
protected void populateItem(Item item) {
PasteItem pasteItem = (PasteItem) item.getModelObject();

PageParameters params = new PageParameters();
params.put("0", Long.toString(pasteItem.getId()));
item.add(new BookmarkablePageLink("viewLink", ViewPublicPage.class, params));

final String[] contentLines = pasteItem.getContent().split("\n");
item.add(new Label("lineCount", "(" + contentLines.length + " Line" + (contentLines.length > 1 ? "s" : "") + ")"));

item.add(new Label("posted", getElapsedTimeSincePost(pasteItem)));

List lines = new ArrayList();
int count = 0;
for (String contentLine : contentLines) {
count++;
if (count > 5) {
break;
} else {
lines.add(contentLine);
}
}
item.add(new ListView("content", lines) {
protected void populateItem(ListItem item) {
String content = (String) item.getModelObject();
Label contentLine = new Label("contentLine", ((item.getIndex() + 1) + " ").substring(0, 5) + content.replaceAll("\r", "").replaceAll("\n", ""));
item.add(contentLine);
if ((item.getIndex() + 1) % 2 == 0) {
item.add(new SimpleAttributeModifier("class", "highlight"));
}
}
});

item.add(new BookmarkablePageLink("viewLink2", ViewPublicPage.class, params) {
@Override
public boolean isVisible() {
return contentLines.length > 5;
}
});
}
});

and the corresponding html:

 <div wicket:id="history" class="historyItem">
<div class="view">
<div class="historyItemHeader">
<div class="historyItemView"><a wicket:id="viewLink" href="#">View Paste</a></div><div wicket:id="lineCount" class="historyItemLines">(27 lines)</div><div wicket:id="posted" class="historyItemTime">1 hour ago</div>
</div>
<div class="historyItemHeaderBottom"> </div>
<div wicket:id="content"><re wicket:id="contentLine">asdfl;kajsdf; a;sldkfj a;lskdjf</re></div>
<div class="historyItemView"><a wicket:id="viewLink2">More...</a></div>
</div>
</div>

Digging in… you mark with a wicket:id what you want to repeat… in our case, it is the container div for a history item which we marked as wicket:id=”history”. For every object (PasteItem) within our models list, we are going to get a new div with contents. For each object within the list, we add a BookmarkablePageLink which links to the paste view, the line count and elapsed time which we add as Label components, a repeater to display the first 5 lines of the paste, and a More link which displays only if there are more than 5 lines in the paste.
 
A BookmarkablePageLink means we are going to have a “clean” URL and we have already covered the PageParameters. The Label has a convenience constructor to allow for Strings rather than having to wrap them in a model. As mentioned earlier, the line count and elapsed time are derived and therefore cannot be pulled from the model object but instead are set manually. Then we have another type of repeater to display the paste. I have chosen a ListView as I’m passing it a List and don’t need to worry about length or paging. The last component we add is the conditional link to the paste view where we override the isVisible method to tell Wicket whether or not this component is visible.
 
That covers the DataView… now, what about paging? Wicket has a PagingNavigator component that has a prebuilt paging mechanism that can be easily overridden to accommodate just about any type of paging look and feel that your little heart desires. The requirements for using the PagingNavigator are that you need to start with a reapeater that implements IPageable (DataView) and you will need to supply the DataView with a data provider that implements IDataProvider. I have chosen to extend DefaultDataProvider and implement as follows:

public class HistoryDataProvider extends DefaultDataProvider {

PasteService pasteService;

public HistoryDataProvider(PasteService pasteService) {
this.pasteService = pasteService;
}

public Iterator iterator(int first, int count) {
return pasteService.getLatestItems("web", count, first, false).iterator();
}

public int size() {
return new Long(pasteService.getLatestItemsCount("web")).intValue();
}

public IModel model(Object object) {
return new Model((PasteItem) object);
}

}

You can see that the data provider allows us to only pull what is displayed on the current page and gives the paging mechanism the overall count value via the size method. In return, the paging mechanism supplies the start and count for the pulling of what is to be displayed.
 
Last is the addition of the PagingNavigator components which I have chosen to show at the top and bottom of the list.
 
HistoryPage.html

<wicket:extend>
<div class="navContainer"><div wicket:id="pageNav" class="pageNav"><a href="#">Previous</a><a href="#">1</a><a href="#">2</a><a href="#">Next</a></div></div>
<div wicket:id="history" class="historyItem">
<div class="view">
<div class="historyItemHeader">
<div class="historyItemView"><a wicket:id="viewLink" href="#">View Paste</a></div><div wicket:id="lineCount" class="historyItemLines">(27 lines)</div><div wicket:id="posted" class="historyItemTime">1 hour ago</div>
</div>
<div class="historyItemHeaderBottom"> </div>
<div wicket:id="content"><re wicket:id="contentLine">asdfl;kajsdf; a;sldkfj a;lskdjf</re></div>
<div class="historyItemView"><a wicket:id="viewLink2">More...</a></div>
</div>
</div>
<div class="navContainer"><div wicket:id="pageNav2" class="pageNav"><a href="#">Previous</a><a href="#">1</a><a href="#">2</a><a href="#">Next</a></div></div>
</wicket:extend>

and HistoryPage.java

public class HistoryPage extends BasePage {

@SpringBean
PasteService pasteService;

SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");

DataView historyDataView;

public HistoryPage() {
super(HistoryPage.class);

add(historyDataView = new DataView("history", new HistoryDataProvider(pasteService), 10) {
protected void populateItem(Item item) {
PasteItem pasteItem = (PasteItem) item.getModelObject();

PageParameters params = new PageParameters();
params.put("0", Long.toString(pasteItem.getId()));
item.add(new BookmarkablePageLink("viewLink", ViewPublicPage.class, params));

final String[] contentLines = pasteItem.getContent().split("\n");
item.add(new Label("lineCount", "(" + contentLines.length + " Line" + (contentLines.length > 1 ? "s" : "") + ")"));

item.add(new Label("posted", getElapsedTimeSincePost(pasteItem)));

List lines = new ArrayList();
int count = 0;
for (String contentLine : contentLines) {
count++;
if (count > 5) {
break;
} else {
lines.add(contentLine);
}
}
item.add(new ListView("content", lines) {
protected void populateItem(ListItem item) {
String content = (String) item.getModelObject();
Label contentLine = new Label("contentLine", ((item.getIndex() + 1) + " ").substring(0, 5) + content.replaceAll("\r", "").replaceAll("\n", ""));
item.add(contentLine);
if ((item.getIndex() + 1) % 2 == 0) {
item.add(new SimpleAttributeModifier("class", "highlight"));
}
}
});

item.add(new BookmarkablePageLink("viewLink2", ViewPublicPage.class, params) {
@Override
public boolean isVisible() {
return contentLines.length > 5;
}
});
}
});

add(new PagingNavigator("pageNav", historyDataView));
add(new PagingNavigator("pageNav2", historyDataView));
}
}

Note that we have just added the 2 PagingNavigators at the bottom of the code, passing in the DataView that we created above. That is it… you now have a fully functioning history page with paging navigation. Again, not sure it can get much easier than that.
 

Testing

Testing you say? Whoa… we can’t test the front-end without going through a lot of trouble can we?
 
Well… the truth is that Wicket provides a way to do quite a bit of front-end testing and it is pretty much as easy as testing any other Java code!
 
What would we want to test? Well, I believe we would want to test that a successful paste would indeed go to the correct page and that the view of the post would contain what we pasted. We might also want to see if our links work… do they go to the correct page? We don’t have a complicated application, so we are going to show a small test, but the testing framework can check for just about anything that can happen on a page. For now, take a look at this simple test:

public class TestPastePage extends AbstractIntegrationTest {

@SpringBeanByType
private PasteService svc;

@SpringBeanByType
private PasteItemDao dao;

protected WicketTester tester;

@Before
public void setup() {
AnnotApplicationContextMock appctx = new
AnnotApplicationContextMock();
appctx.putBean("pasteDao", dao);
appctx.putBean("pasteService", svc);

tester = new WicketTester(MysticPasteApplication.class);
WebApplication app = tester.getApplication();

app.addComponentInstantiationListener(new SpringComponentInjector(app, appctx));
}

@Test
public void testPaste() {
tester.startPage(PasteItemPage.class);
tester.assertRenderedPage(PasteItemPage.class);
FormTester ft = tester.newFormTester("pasteForm");
ft.select("type", 0);
ft.setValue("content", "blahblahblah");
ft.submit();
tester.assertRenderedPage(ViewPublicPage.class);
tester.assertContains("blahblahblah");
tester.assertLabel("type", "JAVA");
}

@Test
public void testHistoryMenuClick() {
tester.startPage(PasteItemPage.class);
tester.assertRenderedPage(PasteItemPage.class);
tester.clickLink("historyLinkContainer:historyLink");
tester.assertRenderedPage(HistoryPage.class);
}
}

Well… this looks simple enough. First, we’ll test to see if a paste works by looking at what happens in testPaste:

  • start the page we want to look at.
  • validate that the page was rendered and that we are still on this page.
  • setup the form tester.
  • set the values for the language drop-down and the paste content.
  • submit the form.
  • assert that it went to the page we were expecting it to go to next.
  • see if it contains the paste, in this case “blahblahblah”.
  • and finally, see if the label for Language is set to JAVA.

Very cool… the test passes… next we test the menu item history link. We open our starting page, kick off the link via clickLink which is set to our history link and then verify that it indeed went to our history page.
 
I bet you never thought testing front-end code could be so easy. The WicketTester does all the work so you can now have far greater test coverage than you would normally have with a web application.
 

Conclusion

Wicket allows a developer to create applications as rapidly as any framework I have seen to date while keeping the html as pristine as possible. Occasionally, I am forced to go back to older applications and deal with jsps both old and new and I always come away with a headache and nosebleed due to the punches taken in dealing with jsps and jstl. I wish I had the time and space to go into more details about some of the helpful components that Wicket offers and I haven’t even touched on Wickets Ajax components in this version of the MysticPaste application. Pay close attention to our blog to see follow up posts and Wicket higher learning as we make improvements to the MysticPaste application. We will also continue to post Wicket tips and tricks as we come across them.

Mystic Coders, LLC has been coding web magic since 2000. Mystic is a full-service Development Agency specializing in Enterprise development with Java. They are usually involved in developing enterprise-grade software for companies large and small, and have experience working in diverse industries, including b2b, b2c, and government-based projects. Mystic has done work with large companies such as LeapFrog, Nestlé, Harrah's Entertainment and the Los Angeles Conventions & Visitor's Bureau, among others. Andrew Lombardi, CTO of Mystic, is available for speaking engagements.

For more about Mystic, check us out at http://www.mysticcoders.com 

Published at DZone with permission of its author, Steve Forsyth.

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

Tags:

Comments

Ojitha Kumanayaka replied on Fri, 2009/03/13 - 4:03am

Great tutorial

Dick Larsson replied on Sun, 2009/03/15 - 7:17pm

Great material, thanks for sharing your knowledge..

Comment viewing options

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