Панели с вкладками используются так часто в компонентном веб-дизайне, что многие фреймворки предоставляют готовую реализацию. У ChenilleKit есть один для Гобелена. Давайте попробуем еще один.
Панель вкладок потребует двух компонентов.
- Компонент TabPanel для управления вкладками и ссылками на вкладки.
- Компонент Tab, представляющий вкладку
Создание этого компонента более увлекательно, так как для этого требуется двусторонняя связь между родительским и дочерним компонентами. TabPanel (родительский компонент) должен получить заголовок и отключить статус от компонента Tab (дочернего), в то время как компонент Tab должен проверить, когда следует отображать себя, в зависимости от выбранной вкладки в TabPanel. Для связи между родителями и детьми мы будем использовать службу Environment, а для связи между родителями мы будем использовать ComponentResources.
Служба Environment — это основанная на стеке служба, в которой мы можем поместить объект в стек на любой фазе рендеринга компонента, а затем выдвинуть объект, когда он не требуется. Объект доступен для всех фаз между вызовами push и pop. Этот дизайн на основе стека пригоден для связи из-за того, как работает рендеринг компонента. Для двух компонентов A и B, где A содержит B, рендеринг происходит таким образом
A.setupRender() -> A.beginRender() -> A.beforeRenderTemplate()->A.beforeRenderBody()->
B.setupRender() -> B.beginRender() -> B.beforeRenderTemplate()->B.beforeRenderBody()->
B.afterRenderBody()->B.afterRenderTemplate()->B.afterRender()->B.cleanupRender()
A.afterRenderBody()->A.afterRenderTemplate()->A.afterRender()->A.cleanupRender()
Все фазы дочернего компонента происходят в пределах фаз родительского компонента. Таким образом, мы можем легко выдвинуть объект в фазе до визуализации дочернего компонента и извлечь его на более поздней фазе. Для полного описания и понимания прочитайте это
Чтобы получить дочерние компоненты, нам нужны их идентификаторы. Информация передается в параметре tabs панели TabPanel. Идентификатор компонента можно использовать для получения фактического компонента с помощью ComponentResources.getContainerResources (). GetEmbeddedComponent ().
Исходный код для TabPanel показывает реализацию этих концепций
@Import(stylesheet = "tab-panel.css")
public class TabPanel implements ClientElement
{
@Parameter(value = "prop:componentResources.id",
defaultPrefix = BindingConstants.LITERAL, allowNull = false)
private String clientId;
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String active;
@SuppressWarnings("unused")
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String zone;
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String tabs;
private String assignedClientId;
@Property
private String currentTabId;
@SuppressWarnings("unused")
@Property
private int index;
@Inject
private JavaScriptSupport javaScriptSupport;
@Inject
private ComponentResources resources;
@Inject
private Environment environment;
@Inject
private Request request;
private String [] tabsCache;
public TabPanel()
{
}
//Testing purpose
TabPanel(String tabs,
String active,
String currentTabId,
JavaScriptSupport javaScriptSupport,
ComponentResources resources,
Environment environment)
{
this.tabs = tabs;
this.active = active;
this.currentTabId = currentTabId;
this.javaScriptSupport = javaScriptSupport;
this.resources = resources;
this.environment = environment;
}
void setupRender()
{
if(tabs == null || getTabs().length == 0)
{
throw new IllegalArgumentException("You must specify atleast one tab");
}
if(active == null)
{
active = getTabs()[0];
}
assignedClientId = javaScriptSupport.allocateClientId(clientId);
}
void beginRender()
{
environment.push(TabContext.class, new TabContext()
{
public boolean isActiveTab(String tabId)
{
return active != null && active.equals(tabId);
}
});
}
void afterRender()
{
environment.pop(TabContext.class);
}
public String getClientId()
{
return assignedClientId;
}
Object onSelectTab(String selected)
{
active = selected;
CaptureResultCallback<Object> callback = new CaptureResultCallback<Object>();
boolean handled = resources.triggerEvent(EventConstants.SELECTED,
new Object[]{selected}, callback);
if(request.isXHR() & !handled)
{
throw new TapestryException(String.format("Event %s not handled",
EventConstants.SELECTED), null);
}
return callback.getResult();
}
public String getCssClass()
{
return isActiveTab() ? "t-tab-active" : "t-tab-default";
}
public boolean isActiveTab()
{
return currentTabId.equals(active);
}
public String getActive()
{
return active;
}
public Tab getCurrentTab()
{
return getTab(currentTabId);
}
private Tab getTab(String tabId)
{
return (Tab) resources.getContainerResources().getEmbeddedComponent(tabId);
}
public String [] getTabs()
{
if(tabsCache == null)
{
tabsCache = TapestryInternalUtils.splitAtCommas(tabs);
}
return tabsCache;
}
}
<t:container xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>
<div class='t-tab-panel'>
<ul class='t-tab-links'>
<t:loop t:source='tabs' t:value='currentTabId' t:index='index'>
<t:unless test='currentTab.disabled'>
<li class='${cssClass}'>
<a t:zone='inherit:zone' t:type='eventlink'
t:context='currentTabId' t:event='selectTab'>${currentTab.title}</a>
</li>
</t:unless>
</t:loop>
</ul>
<div class='t-tab-content'>
<t:body/>
</div>
</div>
</t:container>
Мы нажимаем TabContext в фазе beginRender и открываем его в afterRender, что делает его доступным для вкладки для всех фаз визуализации компонентов. Важно отметить, что объект перемещается и выдвигается на этапах рендеринга и недоступен на этапах действия. Это означает, что вы не можете использовать этот объект в обработчиках событий.
TabContext — это простой интерфейс
public interface TabContext
{
boolean isActiveTab(String tabId);
}
Компонент Tab использует TabContext, чтобы проверить, должен ли он отображать свое содержимое. Если вкладка не активна или отключена, рендеринг пропускается, возвращая значение false в фазе beginRender.
public class Tab
{
@Parameter(required = true, defaultPrefix = BindingConstants.LITERAL, allowNull = false)
private String title;
@Parameter(value = "false", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
private boolean disabled;
@Environmental
private TabContext tabContext;
@Inject
private ComponentResources resources;
boolean beginRender()
{
return isActiveAndEnabled();
}
private boolean isActiveAndEnabled()
{
return tabContext.isActiveTab(resources.getId()) && !disabled;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public boolean getDisabled()
{
return disabled;
}
public void setDisabled(boolean disabled)
{
this.disabled = disabled;
}
}
<t:container xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'> <t:body/> </t:container>
Usage
The typical usage will be
public class TabPanelDemo
{
@Property
private String active;
void onActivate(String active)
{
this.active = active;
}
String onPassivate()
{
return active;
}
}
<html xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>
<body>
<div t:type='tawus/tabpanel' t:tabs='tabA, tabB,tabC' t:active='prop:active' t:id='outer'>
<div t:type='tawus/tab' title='Tab A' t:id='tabA'>Content of Tab A</div>
<div t:type='tawus/tab' title='Tab B' t:id='tabB'>Content of Tab B</div>
</div>
</div>
</body>
</html>
From http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry/