Composite component collapsiblePanel revisited

In my last post I showed you the composite component collapsiblePanel. In my final remark I mentioned that this component has a major design flaw. The logic for collapsing and expanding the content must be supplied by the user and is not part of the component. In this post I show you how this problem can be solved.

Every now and then parts of a composite component’s intended behaviour can only be implemented with Java code. Just like our logic for collapsing and expanding the panel. What we need is an extension point to integrate this code into a composite component. As composite components are internally built with a set of classical components the most obvious idea is to use a component for this purpose.

And that is exactly what JSF does. By setting the attribute componentType of cc:interface we can tell JSF to use a custom root compont for the composite component. For our example this means we create a component with a collapsed property and a toggle method. The following listing shows the code.

@FacesComponent("at.jsflive.CollapsiblePanel")
public class CollapsiblePanel extends UINamingContainer {

  enum PropertyKeys {collapsed}

  public boolean isCollapsed() {
    return (Boolean)getStateHelper().eval(
        PropertyKeys.collapsed, Boolean.FALSE);
  }

  public void setCollapsed(boolean collapsed) {
    getStateHelper().put(PropertyKeys.collapsed, collapsed);
  }

  public void toggle(ActionEvent e) {
    setCollapsed(!isCollapsed());
  }
}

As the root component of a composite component must be a naming container the class extends UINamingContainer.

The following listing shows the updated composite component. The changes compared to the version from the last post are small and mainly affect the attributes and their wiring to the internal components.

<cc:interface componentType="at.jsflive.CollapsiblePanel">
  <cc:attribute name="collapsed"/>
  <cc:actionSource name="toggle"/>
  <cc:facet name="header"/>
</cc:interface>
<cc:implementation>
  <h:panelGroup layout="block" styleClass="collapsiblePanel-header">
    <h:commandButton id="toggle" actionListener="#{cc.toggle}"
      styleClass="collapsiblePanel-img"
      image="#{resource[cc.collapsed
          ? 'jsflive:plus.png' : 'jsflive:minus.png']}"/>
    <cc:renderFacet name="header"/>
  </h:panelGroup>
  <h:panelGroup layout="block" rendered="#{!cc.collapsed}">
    <cc:insertChildren/>
  </h:panelGroup>
  <h:outputStylesheet library="jsflive" name="components.css"/>
</cc:implementation>

First, the required model attribute is replaced with the optional collapsed attribute. This one is used to set the initial collapse state from the outside. Its value is automatically considered by the StateHelper in isCollapsed().

The second change affects the method toggle and the attribute collapsed. As both of them are now provided by the root component the EL expressions for accessing them must be changed to cc.toggle and cc.collapsed. This is possible because cc now points to a component of class CollapsiblePanel. Using cc.attrs is no longer necessary.

The following listing shows the usage of the new component in a page. In comparison the last post the toggle method of the managed bean is no longer needed.

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:jl="http://java.sun.com/jsf/composite/jsflive">
<h:head>
  <title>JSFlive test page</title>
</h:head>
<h:body>
  <h:form id="form">
    <h:outputText value="JSFlive collapsible panel test page"/>
    <jl:collapsiblePanel id="panel"
        collapsed="#{testController.collapsed}">
      <f:facet name="header">
        <h3><h:outputText value="Collapsible information"/></h3>
      </f:facet>
      <h:outputText value="This information is collapsible."/>
    </jl:collapsiblePanel>
  </h:form>
</h:body>
</html>

With the changes presented in this post the composite component collapsiblePanel is now really self-contained and reusable.

One last note: managed bean properties referenced in collapsed are currently not updated if the state changes. This can be done by calling the following method in CollapsiblePanel.toggle():

private void setCollapsedValueExpression() {
  ELContext ctx = FacesContext.getCurrentInstance().getELContext();
  ValueExpression ve = getValueExpression(PropertyKeys.collapsed.name());
  if (ve != null) {
    ve.setValue(ctx, isCollapsed());
  }
}

The source code for the presented example can be found in the Github repository collapsible02.

Advertisements

3 responses to “Composite component collapsiblePanel revisited

  1. Pingback: Ajaxify composite component collapsiblePanel | JSFlive: Michael Kurz's JSF Weblog

  2. Hi Michael, danke erstmal für euer Engagement im Bereich JSF! Euer Buch ist sehr gelungen und das Online-Tutorial ist ebenso klasse. Schön wäre noch ein Tutorial zum erstellen von “klassischen” Komponenten von Beginn an + Projekt-Setup in Eclipse. Darüber würde ich mich (und andere sicherlich auch) sehr freuen.

    • Hallo und vielen Dank! Es freut mich wenn unser Buch/Tutorial ankommt. Die Eclipse-Anleitung werde ich vermutlich in der neuen Auflage zu JSF 2.2 (Herbst 2013) etwas erweitern (obwohl Eclipse bei uns ja keiner verwendet…).

      Vom Online-Tutorial gibt es ja bereits eine neue Version mit CDI, PrimeFaces und MyFaces CODI.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s