Mar 1, 2011

Computed Tab Layout in a Custom Control

Our goal is to create a tab layout that can be updated dynamically from a configuration document.  We found two ways this can be accomplished, one is with a repeat control and the other is with property definitions.  The two different ways are explained below:

Overview
On our main Xpage, we have fields to get the data from the configuration document based on the revision date we want to use.  There is a document for each revision date.  This document contains a list of tab names and the custom controls to be used as the tabs content. 

Tab Names : 
Insured, Locations, Quote Summary, Application Questions, Application Information, Submission

Tabs (custom controls): 
tab_Insured_20110301.xsp, tab_Locations_20110101.xsp, tab_QuoteSummary_20110101.xsp, tab_ApplicationQuestions_20110101.xsp, tab_ApplicationInfo_20110101.xsp, tab_Submission_20110101.xsp

@DbLookup("", "RevisionDateConfigDocs", getComponent("RevisionDate").getValue(), "ElementListTabNames")

Next we have a custom control for the tab layout.  We have a Tabbed Panel with 10 tabs.  10 is our default, however we may not have that many tabs so we added logic to the rendered property to only show the tabs we need.     

Repeat Control
For each Tab Panel, the label is set to a value from the field we set on the main page.   A repeat control is added to the panel for the field with the list of custom control names.  The option “Create controls at page creation” needs to be marked so this doesn’t get created before our fields are set.  We set the starting index to the current tab (example:  first tab is 0, second tab is 1) and the repeat limit is always set to 1.  Inside each repeat control we have an Include Page container.  The page name is set to the repeat controls collection name.  

Here’s the logic for the first two tabs:

<xp:tabbedPanel id="Tabs" loaded="true">
   <xp:tabPanel id="tab0">            
      <xp:this.label>
<![CDATA[#{javascript:getComponent("RevisionTabNames").getValue()[0]}]]>
</xp:this.label> 
<xp:this.rendered>
<![CDATA[#{javascript:var tabs=getComponent("RevisionTabs").getValue()
if(tabs.length<1)return false
return true}]]>
</xp:this.rendered>
<xp:repeat id="repeat1" rows="1" var="tabData" repeatControls="true" first="0">                                     
<xp:this.value>
<![CDATA[#{javascript:getComponent("RevisionTabs").getValue()}]]></xp:this.value> 
<xp:include id="include0" pageName="${javascript:tabData}">           </xp:include>
      </xp:repeat>           
   </xp:tabPanel>

<xp:tabPanel id="tab1">             
     <xp:this.label>
        <![CDATA[#{javascript:getComponent("RevisionTabNames").getValue()[1]}]]>
      </xp:this.label>
<xp:this.rendered>
<![CDATA[#{javascript:var tabs=getComponent("RevisionTabs").getValue()
if(tabs.length<2)return false
return true}]]>
</xp:this.rendered>
<xp:repeat id="repeat2" rows="1" var="tabData" repeatControls="true" first="1">                                     
<xp:this.value>
<![CDATA[#{javascript:getComponent("RevisionTabs").getValue()}]]></xp:this.value>
            <xp:include id="include1" pageName="${javascript:tabData}">
</xp:include>
      </xp:repeat>           
</xp:tabPanel>
</xp:tabbedPanel>

Property Definitions
For each Tab Panel, the label is set to the names property we set before the page loads.  Inside each Tab Panel we have an Include Page container.  The page name is set to the pages property. 
      
Tabs.ssjs – function is called from the beforePageLoad event to set these properties.
function initTabs(){
var tNames = @DbLookup("", "RevisionDateConfigDocs",  getComponent("RevisionDate").getValue(), "ElementListTabNames");

var tRevision = @DbLookup("", "RevisionDateConfigDocs", getComponent("RevisionDate").getValue(),"ElementListTabs");

     
      for(var i in tNames){
            compositeData.Tabs.names[i] = tNames[i];
      }
     
      for(var i in tRevision){
            compositeData.Tabs.pages[i] = tRevision[i];
      }

compositeData.numTabs = tNames.length;

}

Here’s the logic for the first two tabs:
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
      xmlns:xc="http://www.ibm.com/xsp/custom"
      beforePageLoad="#{javascript:initTabs();}">
<xp:this.resources>
      <xp:script src="/Tabs.jss" clientSide="false"></xp:script>
</xp:this.resources>

<xp:tabbedPanel id="Tabs" loaded="true">

      <xp:tabPanel id="tab0">       
      <xp:this.label><![CDATA[#{javascript:compositeData.Tabs.names[0];}]]>
</xp:this.label>
      <xp:this.rendered>
      <![CDATA[#{javascript:if(compositeData.numTabs<1)return false 
         return true}]]>
      </xp:this.rendered>
      <xp:include pageName="${javascript:compositeData.Tabs.pages[0]}" id="include1"></xp:include>
</xp:tabPanel>

<xp:tabPanel id="tab1">
      <xp:this.label><![CDATA[#{javascript:compositeData.Tabs.names[1]}]]>
</xp:this.label>
      <xp:this.rendered>
      <![CDATA[#{javascript:if(compositeData.numTabs<2)return false
        return true}]]>
      </xp:this.rendered>
      <xp:include pageName="${javascript:compositeData.Tabs.pages[1]}" id="include2"></xp:include>
</xp:tabPanel>
</xp:tabbedPanel>
</xp:view>

Logic on the main xpage for the custom control and custom properties:
      <xc:cc_Tabs>
            <xc:this.Tabs>
                  <xc:Tabs>
                        <xc:this.names>
                              <xc:names></xc:names>
                        </xc:this.names>
                        <xc:this.pages>
                              <xc:pages></xc:pages>
                        </xc:this.pages>
                  </xc:Tabs>
            </xc:this.Tabs>
      </xc:cc_Tabs>

The Results

Summary
As you can see, there are at least two different ways to do the same thing.  Which one is better; repeat control or property definitions?  Or is there an even better way?  We would love your feedback on this subject.

No comments:

Post a Comment