Friday, November 30, 2007

An Ajax tabbed panel with lazy loading tabs and a wait indicator for Wicket

I needed an Ajax Tabbed Panel with tabs that load only when clicked and and a wait indicator (a.k.a. hourglass or wait cursor) when the tab is being loaded. This example just shows how to use existing Wicket 1.3 components for those who haven't used these components before.


First, an example of how to use it:


MyTabPage.java


public class MyTabPage extends WebPage
{
private static final long serialVersionUID = 1L;

public MyTabPage()
{
add(HeaderContributor.forCss(MyTabPage.class, "MyTabPage.css"));

final List tabs = new ArrayList();

tabs.add(new MyTab("Tab One")
{
private static final long serialVersionUID = 1L;
@Override
public Panel createPanel()
{
return new TabOnePanel();
}
});
tabs.add(new MyTab("Tab Two")
{
private static final long serialVersionUID = 1L;
@Override
public Panel createPanel()
{
return new TabTwoPanel();
}
});
final MyTabbedPanel tabpanel = new MyTabbedPanel("tabpanel", tabs);
add(tabpanel);
}
}

MyTabPage.html


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en"
lang="en">

<body>
<div wicket:id="tabpanel">[tabbed panel will be here]</div>
</body>
</html>

The CSS for the project:


MyTabPage.css


div.mytabpanel div.tab-row{
float:left;
width: 100%;
border-bottom: 1px solid #3e6581;
background-color: #e6e6e6;
}

div.mytabpanel div.tab-row ul {
margin:0;
list-style:none;
}

div.mytabpanel div.tab-row li {
float:left;
width: 10%;
}

div.mytabpanel div.tab-row a {
display:block;
text-decoration:none;
font-weight:bold;
color:#949494;
}

div.mytabpanel div.tab-row li.selected a {
color:#333;
}

div.mytab {
padding: 5em;
}

The tabbed panel class:


MyTabbedPanel.java


public class MyTabbedPanel extends Panel
{
private static final long serialVersionUID = 1L;

private final TabbedPanel tabbedPanel;

public MyTabbedPanel(String id, List tabs)
{
super(id);

tabbedPanel = new AjaxTabbedPanel("mytabpanel", tabs)
{
private static final long serialVersionUID = 1L;
// Override newLink to supply an IndicatingAjaxLink (wait cursor)
@Override
protected WebMarkupContainer newLink(String linkId, final int index)
{
final WebMarkupContainer c = new IndicatingAjaxLink(linkId)
{
private static final long serialVersionUID = 1L;

public void onClick(AjaxRequestTarget target)
{
setSelectedTab(index);
if (target != null)
{
target.addComponent(MyTabbedPanel.this);
}
onAjaxUpdate(target);
}
};
return c;
}
};
tabbedPanel.setOutputMarkupId(true);
MyTabbedPanel.this.setOutputMarkupId(true);
add(tabbedPanel);
}

public static class MyTab extends AbstractTab
{
private static final long serialVersionUID = 1L;

private Panel p;

public MyTab(String label)
{
super(new Model(label));
}
@Override
public Panel getPanel(String panelId)
{
if (p == null)
{
// Lazily create the panel
p = createPanel();
if (!TabbedPanel.TAB_PANEL_ID.equals(p.getId()))
{
throw new IllegalArgumentException(
"Panel id must be TabbedPanel.TAB_PANEL_ID");
}
p.setOutputMarkupId(true);
}
return p;
}
protected Panel createPanel()
{
throw new IllegalArgumentException("Must provide a panel");
}
}
}

MyTabbedPanel.html


<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.sourceforge.net/" xml:lang="en" lang="en">
<body>
<wicket:panel>
<div wicket:id="mytabpanel" class="mytabpanel"></div>
</wicket:panel>
</body>
</html>

Finally, a simple panel displayed by a tab:


TabOnePanel.java


public class TabOnePanel extends Panel
{
private static final long serialVersionUID = 1L;

public TabOnePanel()
{
super(TabbedPanel.TAB_PANEL_ID);

add(new Label("mylabel", "Tab One Contents"));
}
}

TabOnePanel.html


<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<wicket:panel>
<div wicket:id="mylabel" class="mytab"></div>
</wicket:panel>
</body>
</html>

4 comments:

Eyal said...

Thanks,
this post was really useful for me.

Marius Snyman said...

Brilliant. This is how examples should be presented. I have not used AjaxTabbedPanel before but once I have worked through your example I was geared for production implementation.

marius1maru said...

Great article, but I have a question....how can I move the busy indicator so that it will show in another place?
ex. I want my busy indicator to appear not on the right side of every tab, but always on the right side of the last tab

Julian Sinai said...

Instead of overriding newLink to create an IndicatingAjaxLink, you could probably create an AjaxLink and instead attach a WicketAjaxIndicatorAppender to the tabbed panel itself. I've used WicketAjaxIndicatorAppender but not in this particular use case, so you'll have to try it and see.