Панели с вкладками используются так часто в компонентном веб-дизайне, что многие фреймворки предоставляют готовую реализацию. У 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/