Question

I'm using Glassfish 3.2.2 and JSF 2.1.11

I'm trying to create a composite component that will take as parameters a string and a max number of characters and then will show only the max amount of characters, but it will have a "more" link next to it, that when clicked will expand the text to the full length and will then have a "less" link next to it to take it back to the max number of characters.

I'm seeing some weird behavior, so I'm wondering if I'm doing something wrong.

Here is my composite component definition:

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:composite="http://java.sun.com/jsf/composite"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:p="http://primefaces.org/ui">
<composite:interface componentType="expandableTextComponent">
    <composite:attribute name="name" required="true"/>
    <composite:attribute name="maxCharacters" required="true"/>
    <composite:attribute name="value" required="true"/>
</composite:interface>

<composite:implementation>
    <h:panelGroup id="#{cc.attrs.name}">
        <h:outputText value="#{fn:substring(cc.attrs.value, 0, cc.attrs.maxCharacters)}" rendered="#{fn:length(cc.attrs.value) le cc.attrs.maxCharacters}"/>

        <h:outputText value="#{fn:substring(cc.attrs.value, 0, cc.attrs.maxCharacters)}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters and !cc.expanded}" style="margin-right: 5px;"/>
        <h:outputText value="#{cc.attrs.value}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters and cc.expanded}" style="margin-right: 5px;"/>
        <p:commandLink actionListener="#{cc.toggleExpanded()}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters}" update="#{cc.attrs.name}">
            <h:outputText value="#{__commonButton.more}..." rendered="#{!cc.expanded}"/>
            <h:outputText value="#{__commonButton.less}" rendered="#{cc.expanded}"/>
        </p:commandLink>
</h:panelGroup>
</composite:implementation>
</html>

And here is the Java component:

@FacesComponent("expandableTextComponent")
public class ExpandableTextComponent extends UINamingContainer
{
    boolean expanded;

    public boolean isExpanded()
    {
        return expanded;
    }

    public void toggleExpanded()
    {
        expanded = !expanded;
    }
}

Unfortunately expanded is always false every time the toggleExpanded function is called.

However if I change the composite component to the following then it works.

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:composite="http://java.sun.com/jsf/composite"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:p="http://primefaces.org/ui">
<composite:interface componentType="expandableTextComponent">
    <composite:attribute name="name" required="true"/>
    <composite:attribute name="maxCharacters" required="true"/>
    <composite:attribute name="value" required="true"/>
</composite:interface>

<composite:implementation>
    <h:panelGroup id="#{cc.attrs.name}">
        <h:outputText value="#{fn:substring(cc.attrs.value, 0, cc.attrs.maxCharacters)}" rendered="#{fn:length(cc.attrs.value) le cc.attrs.maxCharacters}"/>

        <h:outputText value="#{fn:substring(cc.attrs.value, 0, cc.attrs.maxCharacters)}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters and !cc.expanded}" style="margin-right: 5px;"/>
        <p:commandLink actionListener="#{cc.toggleExpanded()}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters and !cc.expanded}" update="#{cc.attrs.name}" process="@this">
            <h:outputText value="#{__commonButton.more}..."/>
        </p:commandLink>

        <h:outputText value="#{cc.attrs.value}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters and cc.expanded}" style="margin-right: 5px;"/>
        <p:commandLink actionListener="#{cc.toggleExpanded()}" rendered="#{fn:length(cc.attrs.value) gt cc.attrs.maxCharacters and cc.expanded}" update="#{cc.attrs.name}" process="@this">
            <h:outputText value="#{__commonButton.less}"/>
        </p:commandLink>
    </h:panelGroup>
</composite:implementation>
</html>

If I place a breakpoint in the toggleExpanded function, it only gets called on the "more" link and not the "less" link. So the question is why doesn't it get called when I click on the "less" link? Shouldn't this code be equivalent to the code above?

Is there a better way to save state in a component?

Was it helpful?

Solution

Basically, you should be overridding UIComponent#saveState() and restoreState() according their documentation when you add custom properties/attributes to the component which should survive across multiple HTTP requests on the same view. The component instance is namely recreated on every request.

Since JSF 2.0, much better is to use the StateHelper directly in the attribute getters/setters. It's available by the inherited UIComponent#getStateHelper() method.

private enum PropertyKeys {
    expanded;
}

public void toggleExpanded() {
    setExpanded(!isExpanded());
}

public void setExpanded(boolean expanded) {
    getStateHelper().put(PropertyKeys.expanded, expanded);
}

public boolean isExpanded() {
    return (boolean) getStateHelper().eval(PropertyKeys.expanded, false);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top