Статьи

Tab-панель для Гобелена

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