Created
May 11, 2011 23:55
-
-
Save jonnywray/967659 to your computer and use it in GitHub Desktop.
Wicket component for a tabbed panel that gets tab definitions from a model thus allowing tabs to be defined dynamically
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.apache.wicket.ajax.AjaxRequestTarget; | |
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink; | |
import org.apache.wicket.extensions.markup.html.tabs.ITab; | |
import org.apache.wicket.markup.html.WebMarkupContainer; | |
import org.apache.wicket.model.IModel; | |
import java.util.List; | |
/** | |
* Ajax based extension of the dynamic tabbed panel | |
* | |
* @author Jonny Wray | |
*/ | |
public class AjaxDynamicTabbedPanel extends DynamicTabbedPanel{ | |
public AjaxDynamicTabbedPanel(final String id, final IModel<List<ITab>> tabModel){ | |
super(id, tabModel); | |
setOutputMarkupId(true); | |
setVersioned(false); | |
} | |
@Override | |
protected WebMarkupContainer newLink(final String linkId, final int index){ | |
return new AjaxFallbackLink<Void>(linkId) | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
public void onClick(final AjaxRequestTarget target) | |
{ | |
setSelectedTab(index); | |
if (target != null){ | |
target.add(AjaxDynamicTabbedPanel.this); | |
} | |
onAjaxUpdate(target); | |
} | |
}; | |
} | |
/** | |
* A template method that lets users add additional behavior when ajax update occurs. This | |
* method is called after the current tab has been set so access to it can be obtained via | |
* {@link #getSelectedTab()}. | |
* <p> | |
* <strong>Note</strong> Since an {@link AjaxFallbackLink} is used to back the ajax update the | |
* <code>target</code> argument can be null when the client browser does not support ajax and | |
* the fallback mode is used. See {@link AjaxFallbackLink} for details. | |
* | |
* @param target | |
* ajax target used to update this component | |
*/ | |
protected void onAjaxUpdate(final AjaxRequestTarget target) | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" xml:lang="en" lang="en"> | |
<wicket:panel> | |
<div wicket:id="tabs-container" class="tab-row"> | |
<ul> | |
<li wicket:id="tabs"> | |
<a href="#" wicket:id="link"><span wicket:id="title">[[tab title]]</span></a> | |
</li> | |
</ul> | |
</div> | |
<div wicket:id="panel" class="tab-panel">[panel]</div> | |
</wicket:panel> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.apache.wicket.Component; | |
import org.apache.wicket.WicketRuntimeException; | |
import org.apache.wicket.extensions.markup.html.tabs.ITab; | |
import org.apache.wicket.markup.ComponentTag; | |
import org.apache.wicket.markup.html.WebMarkupContainer; | |
import org.apache.wicket.markup.html.basic.Label; | |
import org.apache.wicket.markup.html.link.Link; | |
import org.apache.wicket.markup.html.list.Loop; | |
import org.apache.wicket.markup.html.list.LoopItem; | |
import org.apache.wicket.markup.html.panel.EmptyPanel; | |
import org.apache.wicket.markup.html.panel.Panel; | |
import org.apache.wicket.model.AbstractReadOnlyModel; | |
import org.apache.wicket.model.IModel; | |
import org.apache.wicket.model.Model; | |
import java.util.List; | |
/** | |
* Copy and paste extension of the standard Wicket TabbedPanel but using | |
* a model object to provide the tabs rather than a list. This allows the | |
* specific tabs to be dynamic and depend on model data rather than being | |
* hard-coded. | |
* | |
* @author Jonny Wray | |
*/ | |
public class DynamicTabbedPanel extends Panel { | |
private static final long serialVersionUID = 1L; | |
/** id used for child panels */ | |
public static final String TAB_PANEL_ID = "panel"; | |
private IModel<List<ITab>> tabModel; | |
private transient Boolean[] tabsVisibilityCache; | |
public DynamicTabbedPanel(final String id, final IModel<List<ITab>> tabModel){ | |
super(id, new Model<Integer>(-1)); | |
this.tabModel = tabModel; | |
final IModel<Integer> tabCount = new AbstractReadOnlyModel<Integer>(){ | |
private static final long serialVersionUID = 1L; | |
@Override | |
public Integer getObject(){ | |
return tabModel.getObject().size(); | |
} | |
}; | |
WebMarkupContainer tabsContainer = newTabsContainer("tabs-container"); | |
add(tabsContainer); | |
// add the loop used to generate tab names | |
tabsContainer.add(new Loop("tabs", tabCount) | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
protected void populateItem(final LoopItem item) | |
{ | |
final int index = item.getIndex(); | |
final ITab tab = tabModel.getObject().get(index); | |
final WebMarkupContainer titleLink = newLink("link", index); | |
titleLink.add(newTitle("title", tab.getTitle(), index)); | |
item.add(titleLink); | |
} | |
@Override | |
protected LoopItem newItem(final int iteration) | |
{ | |
return newTabContainer(iteration); | |
} | |
public boolean isVisible(){ | |
return tabCount.getObject() > 0; | |
} | |
}); | |
} | |
/** | |
* Generates the container for all tabs. The default container automatically adds the css | |
* <code>class</code> attribute based on the return value of {@link #getTabContainerCssClass()} | |
* | |
* @param id | |
* container id | |
* @return container | |
*/ | |
protected WebMarkupContainer newTabsContainer(final String id) | |
{ | |
return new WebMarkupContainer(id) | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
protected void onComponentTag(final ComponentTag tag) | |
{ | |
super.onComponentTag(tag); | |
tag.put("class", getTabContainerCssClass()); | |
} | |
}; | |
} | |
/* | |
* Generates a loop item used to represent a specific tab's <code>li</code> element. | |
*/ | |
protected LoopItem newTabContainer(final int tabIndex) | |
{ | |
return new LoopItem(tabIndex) | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
protected void onComponentTag(final ComponentTag tag) | |
{ | |
super.onComponentTag(tag); | |
String cssClass = (String)tag.getString("class"); | |
if (cssClass == null) | |
{ | |
cssClass = " "; | |
} | |
cssClass += " tab" + getIndex(); | |
if (getIndex() == getSelectedTab()) | |
{ | |
cssClass += " selected"; | |
} | |
if (getIndex() == getTabs().size() - 1) | |
{ | |
cssClass += " last"; | |
} | |
tag.put("class", cssClass.trim()); | |
} | |
@Override | |
public boolean isVisible() | |
{ | |
return getTabs().get(tabIndex).isVisible(); | |
} | |
}; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
protected void onBeforeRender() | |
{ | |
if (tabModel.getObject().size() == 0) | |
{ | |
// force an empty container to be created every time if we have no tabs | |
setSelectedTab(0); | |
} | |
else if(getSelectedTab() > tabModel.getObject().size()){ | |
setSelectedTab(0); | |
} | |
else if ((getSelectedTab() == -1) || (isTabVisible(getSelectedTab()) == false)) | |
{ | |
// find first visible selected tab | |
int selected = 0; | |
for (int i = 0; i < tabModel.getObject().size(); i++) | |
{ | |
if (isTabVisible(i)) | |
{ | |
selected = i; | |
break; | |
} | |
} | |
if (selected == tabModel.getObject().size()) | |
{ | |
/* | |
* none of the tabs are selected... | |
* | |
* we do not need to do anything special because the check in setSelectedTab() will | |
* replace the current tab panel with an empty one | |
*/ | |
selected = 0; | |
} | |
setSelectedTab(selected); | |
} | |
super.onBeforeRender(); | |
} | |
/** | |
* @return the value of css class attribute that will be added to a div containing the tabs. The | |
* default value is <code>tab-row</code> | |
*/ | |
protected String getTabContainerCssClass() | |
{ | |
return "tab-row"; | |
} | |
/** | |
* @return list of tabs that can be used by the user to add/remove/reorder tabs in the panel | |
*/ | |
public final List<? extends ITab> getTabs() | |
{ | |
return tabModel.getObject(); | |
} | |
/** | |
* Factory method for tab titles. Returned component can be anything that can attach to span | |
* tags such as a fragment, panel, or a label | |
* | |
* @param titleId | |
* id of title component | |
* @param titleModel | |
* model containing tab title | |
* @param index | |
* index of tab | |
* @return title component | |
*/ | |
protected Component newTitle(final String titleId, final IModel<?> titleModel, final int index){ | |
return new Label(titleId, titleModel); | |
} | |
/** | |
* Factory method for links used to switch between tabs. | |
* | |
* The created component is attached to the following markup. Label component with id: title | |
* will be added for you by the tabbed panel. | |
* | |
* <pre> | |
* <a href="#" wicket:id="link"><span wicket:id="title">[[tab title]]</span></a> | |
* </pre> | |
* | |
* Example implementation: | |
* | |
* <pre> | |
* protected WebMarkupContainer newLink(String linkId, final int index) | |
* { | |
* return new Link(linkId) | |
* { | |
* private static final long serialVersionUID = 1L; | |
* | |
* public void onClick() | |
* { | |
* setSelectedTab(index); | |
* } | |
* }; | |
* } | |
* </pre> | |
* | |
* @param linkId | |
* component id with which the link should be created | |
* @param index | |
* index of the tab that should be activated when this link is clicked. See | |
* {@link #setSelectedTab(int)}. | |
* @return created link component | |
*/ | |
protected WebMarkupContainer newLink(final String linkId, final int index) | |
{ | |
return new Link<Void>(linkId) | |
{ | |
private static final long serialVersionUID = 1L; | |
@Override | |
public void onClick() | |
{ | |
setSelectedTab(index); | |
} | |
}; | |
} | |
/** | |
* sets the selected tab | |
* | |
* @param index | |
* index of the tab to select | |
* @return this for chaining | |
*/ | |
public DynamicTabbedPanel setSelectedTab(final int index) | |
{ | |
if ((index < 0) || ((index >= tabModel.getObject().size()) && (index > 0))) | |
{ | |
throw new IndexOutOfBoundsException(); | |
} | |
setDefaultModelObject(index); | |
final Component component; | |
if ((tabModel.getObject().size() == 0) || !isTabVisible(index)) | |
{ | |
// no tabs or the currently selected tab is not visible | |
component = new EmptyPanel(TAB_PANEL_ID); | |
} | |
else | |
{ | |
// show panel from selected tab | |
ITab tab = tabModel.getObject().get(index); | |
component = tab.getPanel(TAB_PANEL_ID); | |
if (component == null) | |
{ | |
throw new WicketRuntimeException("ITab.getPanel() returned null. TabbedPanel [" + | |
getPath() + "] ITab index [" + index + "]"); | |
} | |
} | |
if (!component.getId().equals(TAB_PANEL_ID)) | |
{ | |
throw new WicketRuntimeException( | |
"ITab.getPanel() returned a panel with invalid id [" + | |
component.getId() + | |
"]. You must always return a panel with id equal to the provided panelId parameter. TabbedPanel [" + | |
getPath() + "] ITab index [" + index + "]"); | |
} | |
addOrReplace(component); | |
return this; | |
} | |
/** | |
* @return index of the selected tab | |
*/ | |
public final int getSelectedTab() | |
{ | |
return (Integer)getDefaultModelObject(); | |
} | |
/** | |
* | |
* @param tabIndex | |
* @return visible | |
*/ | |
private boolean isTabVisible(final int tabIndex) | |
{ | |
if (tabsVisibilityCache == null) | |
{ | |
tabsVisibilityCache = new Boolean[tabModel.getObject().size()]; | |
} | |
if (tabsVisibilityCache.length < tabIndex + 1) | |
{ | |
Boolean[] resized = new Boolean[tabIndex + 1]; | |
System.arraycopy(tabsVisibilityCache, 0, resized, 0, tabsVisibilityCache.length); | |
tabsVisibilityCache = resized; | |
} | |
if (tabsVisibilityCache.length > 0) | |
{ | |
Boolean visible = tabsVisibilityCache[tabIndex]; | |
if (visible == null) | |
{ | |
visible = tabModel.getObject().get(tabIndex).isVisible(); | |
tabsVisibilityCache[tabIndex] = visible; | |
} | |
return visible; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
@Override | |
protected void onDetach(){ | |
tabsVisibilityCache = null; | |
tabModel.detach(); | |
super.onDetach(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
div.tabpanel4 div.tab-row ul { | |
height: 20px; | |
margin: 0; | |
padding-left: 10px; | |
background: url( tabs4/tab_bottom.gif ) repeat-x bottom; | |
} | |
div.tabpanel4 div.tab-row li { | |
margin: 0; | |
padding: 0; | |
display: inline; | |
list-style-type: none; | |
} | |
div.tabpanel4 div.tab-row a:link, div.tabpanel4 div.tab-row a:visited { | |
float: left; | |
background: #f3f3f3; | |
font-size: 12px; | |
line-height: 14px; | |
font-weight: bold; | |
padding: 2px 10px 2px 10px; | |
margin-right: 4px; | |
border: 1px solid #ccc; | |
text-decoration: none; | |
color: #666; | |
} | |
div.tabpanel4 div.tab-row li.selected a:link, div.tabpanel4 div.tab-row a:visited.active { | |
border-bottom: 1px solid #fff; | |
background: #fff; | |
color: #000; | |
} | |
div.tabpanel4 div.tab-row a:hover { | |
background: #fff; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment