Best Practices from Oracle Development's A‑Team

Programmatically expanding and collapsing an af:panelSplitter


The ADF Faces af:panelSplitter is a component with client-side behavior, when you expand or collapse the splitter in the browser, no request is sent to the server. This imposes some challenges when you want to change the splitter state in Java code at the server-side as explained in this article.

Main Article

Last week a customer asked me for help to implement a scenario where the search area part, displayed in the top part of an af:panelSplitter should be collapsed when the user presses the search button, in order for the results part, shown in the bottom part of the af:panelSplitter to take as much screen real estate as possible. Quite easy at first sight, just bind the collapsed property of the splitter to a boolean managed bean property and change the value of this property when clicking the search button. Got it to work in 5 minutes, but.... it only worked the first time. After we manually expanded the top part of the splitter again and clicked the search button, the top part no longer auto-collapsed. We figured this was because the value of our collapsed managed bean property was no longer in synch with the actual splitter collapsed state. I first looked for a disclosureListener property on the splitter to keep it in synch but to my (initial) surprise there is no such property. On second thought this makes perfect sense though: expanding or collapsing a splitter in the browser is a client-side only action, nothing happens on the server. So, we needed to trigger a server action when collapsing the splitter to synchronize the server-side state of the splitter. I will explain the steps using this little sample:


Here is the page source of this sample:

<af:document id="d1">   <f:facet name="metaContainer">     <af:resource source="/js/lib.js" type="javascript"></af:resource>   </f:facet>   <af:form id="f1">     <af:panelSplitter id="ps1" orientation="vertical"                       collapsed="#{viewScope.SplitterBean.collapsed}">       <f:facet name="first">         <af:panelHeader text="Splitter Collapse Test" id="ph1">           <af:panelGroupLayout id="pgl1" layout="scroll">             <af:commandButton text="Collapse this part" id="cb1">               <af:setActionListener from="#{true}"                                     to="#{viewScope.SplitterBean.collapsed}"/>             </af:commandButton>           </af:panelGroupLayout>         </af:panelHeader>       </f:facet>       <f:facet name="second">         <af:panelHeader text="Second part of splitter" id="ph2">           <af:panelGroupLayout id="pgl2" layout="scroll">             <af:commandButton text="Expand top part" id="cb2">               <af:setActionListener from="#{false}"                                     to="#{viewScope.SplitterBean.collapsed}"/>             </af:commandButton>             <af:commandButton text="Collapse top part" id="cb3">               <af:setActionListener from="#{true}"                                     to="#{viewScope.SplitterBean.collapsed}"/>             </af:commandButton>           </af:panelGroupLayout>         </af:panelHeader>       </f:facet>       <af:clientListener type="click" method="splitterClick"/>       <af:serverListener type="handleSplitterClick"                          method="#{viewScope.SplitterBean.handleSplitterEvent}"/>     </af:panelSplitter>   </af:form> </af:document>

And here is the code of the splitterBean:

public class SplitterBean {   boolean collapsed = false;   public void setCollapsed(boolean collapsed)   {     this.collapsed = collapsed;   }   public boolean isCollapsed()   {     return collapsed;   }   public void handleSplitterEvent(ClientEvent clientEvent)   {     RichPanelSplitter splitter = (RichPanelSplitter) clientEvent.getComponent();     boolean serverSideCollapsedState = splitter.isCollapsed();     // server side state is not yet in synch with client state     // so we set the reverse value for collapsed.     splitter.setCollapsed(!serverSideCollapsedState);     setCollapsed(!serverSideCollapsedState);   } }

And the javascript library function:

function splitterClick(evt) {   AdfCustomEvent.queue(evt.getSource(), "handleSplitterClick",{}, true);   evt.cancel(); }

This is how this works:

  • When manually expanding the splitter in the browser, the clientListener fires.
  • The clientListener calls the splitterClick Javascript method which queues an event picked up by the serverListener.
  • The serverListener calls the server-side handleSplitterEvent method in the splitter managed bean
  • The handleSplitter method synchronizes the client-side splitter state with the server-side state

You can download the sample application here (JDeveloper

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha