Skip to main content

A final look at the Switchlist JSF 2 component

Posted by driscoll on May 26, 2009 at 6:19 PM PDT

It's been a long time, but I've revisited the Switchlist component I blogged about here here and here. Read through those old entries to see where we are with things. I will assume you're already familiar with the content of those entries, we've got a lot to cover as it is.


Today, we're going to completely rework this code, to illustrate some best practices. We'll make the component use Ajax, we'll have it separate out the control from the model, and we'll make the whole component addressable with an Ajax tag itself as well.


First, the change in the backing Java classes. Previously, I'd just tossed together all the of the necessary functions into a single monolithic Java class, which exposed four data properties (which means 8 getters and setters), as well as two action methods, used in an action listener. For obvious reasons, this is a sloppy design. Better to have the data separated out, with the control methods hidden from the enduser.


With that in mind, we'll create an interface, let's call it ListHolder. It looks like this:


public interface ListHolder {

    public String[] getList();

    public void setList(String[] list);

    public Map getItems();

}

This encapsulates the data that each list operates on. We'll also make two classes that actually hold that data, like this:


@ManagedBean(name="listholder1")
@SessionScoped
public class ListHolder1 implements ListHolder, Serializable {

    private static final long serialVersionUID = -4047970327214634942L;

    String[] list = null;
    Map items = new LinkedHashMap();

    {
        items.put("one", "one");
        items.put("two", "two");
        items.put("three", "three");
        items.put("four", "four");
    }

    public String[] getList() {
        return list;
    }

    public void setList(String[] list) {
        this.list = list;
    }

    public Map getItems() {
        return items;
    }
}

Though we could use any class which implemented the interface. And lastly, we'll create a controller class:


@ManagedBean
@RequestScoped
public class SwitchlistController implements Serializable {

    private static final long serialVersionUID = -4002627066189080830L;

    ListHolder listholder1, listholder2;

    public String m1_2() {
        String[] list1 = listholder1.getList();
        Map<String, String> items2 = listholder2.getItems();
        Map<String, String> items1 = listholder1.getItems();
        if (list1 != null && list1.length > 0) {
            for (String item : list1) {
                items2.put(item, items1.remove(item));
            }
        }
        return null;
    }

    public String m2_1() {
        String[] list2 = listholder2.getList();
        Map<String, String> items2 = listholder2.getItems();
        Map<String, String> items1 = listholder1.getItems();
        if (list2 != null && list2.length > 0) {
            for (String item : list2) {
                items1.put(item, items2.remove(item));
            }
        }
        return null;
    }

    public void setListHolder1(ListHolder listholder1) {
        this.listholder1 = listholder1;
    }

    public void setListHolder2(ListHolder listholder2) {
        this.listholder2 = listholder2;
    }

}

Again, little here that's surprising - the m1_2 and m2_1 methods are now action methods, instead of actionListener methods, for reasons I'll go into in a minute. We also have two setter methods, which we'll use with setPropertyActionListener.


So, now we've got the model and the controller all laid out, let's see how this would be used in a view. Here's the tag we'd like to use in the using page:


<ez:switchlist id="switchlist"
   listholder1="#{listholder1}"
   listholder2="#{listholder2}"/>

Two parameters, each pointing to the listholder classes that implement the listholder interface.


Now, let's look at the interface in the composite component itself:


<cc:interface name="switchlist">

    <cc:attribute name="listholder1" required="true">
        <cc:attribute name="list" required="true"/>
        <cc:attribute name="items" required="true"/>
    </cc:attribute>
    <cc:attribute name="listholder2" required="true">
        <cc:attribute name="list" required="true"/>
        <cc:attribute name="items" required="true"/>
    </cc:attribute>
</cc:interface>

What's here: Only the CompositeComponent version of the interface that we've defined earlier. For each of the two attribute, there are two properties - list and items.


And lastly, let's take a look at the implementation.


<cc:implementation>
    <h:outputStylesheet name="switchlist/switchlist.css"/>
    <div id="#{cc.clientId}">
    <h:selectManyListbox id="list1" value="#{cc.attrs.listholder1.list}" styleClass="switchlist">
        <f:selectItems value="#{cc.attrs.listholder1.items}"/>
    </h:selectManyListbox>
    <h:panelGroup id="buttonGroup" styleClass="switchlistButtons">
        <h:commandButton id="move1to2" value="&gt;&gt;" action="#{switchlistController.m1_2}"
                         styleClass="switchlistButton">
            <f:setPropertyActionListener value="#{cc.attrs.listholder1}" target="#{switchlistController.listHolder1}"/>
            <f:setPropertyActionListener value="#{cc.attrs.listholder2}" target="#{switchlistController.listHolder2}"/>
            <f:ajax execute="@this list1" render="list1 list2"/>
        </h:commandButton>
        <h:commandButton id="move2to1" value="&lt;&lt;" action="#{switchlistController.m2_1}"
                         styleClass="switchlistButton">
            <f:setPropertyActionListener value="#{cc.attrs.listholder1}" target="#{switchlistController.listHolder1}"/>
            <f:setPropertyActionListener value="#{cc.attrs.listholder2}" target="#{switchlistController.listHolder2}"/>
            <f:ajax execute="@this list2" render="list1 list2"/>
        </h:commandButton>
    </h:panelGroup>
    <h:selectManyListbox id="list2" value="#{cc.attrs.listholder2.list}" styleClass="switchlist">
        <f:selectItems value="#{cc.attrs.listholder2.items}"/>
    </h:selectManyListbox>
    </div>
</cc:implementation>

Now, top to bottom, let's look at what we've done here.


First, wrap everything in a div, and give it an id that's the same as the composite component as a whole. This is something you'll need to do to let ajax tags operate on your component - otherwise, there won't be anything in your page with the id of the component you just added - in our case "switchlist", in the using page. There would only be "switchlist:buttonGroup" and such. Doing this also means that you can better do styling with css, since you now have a block you can work with, instead of a loose group of tags with no wrapper.


Next, see that we have a bunch of references that look like #{cc.attrs.listholder1.list}. This is how we get access to the properties that we defined in the interface section, above, as well as in the ListHolder interface.


Also, note that we've changed from using an actionListener attribute on the buttons, to an action. We're also passing values into the controller via the f:setPropertyActionListener - these two things are related. If we'd just continued to use the actionListener method of executing a method, the setPropertyActionListeners wouldn't be called until after the actionListener - which makes sense, since those listeners are called in the order they're added, but not terribly useful for our purposes. So we instead use a action, which returns null - meaning no navigation.


Lastly, note that for making this ajax enabled, we simply need to say: f:ajax execute="@this list2" render="list1 list2". @this means to execute the button it's placed on, which calls the action method. Executing list2 (note the relative path, it uses the same semantics as UIComponent.findComponent()) is necessary for populating the contents of the items array. And lastly, the render command is necessary to show the results.


So, a brief recap:

  • Separate out your model from your controller. See how clean the code looks now?
  • You can nest attributes in the composite:interface section.
  • Once nested, you can access those values with dot notation.
  • Wrap your components in a top level div or span with the cc.clientId - you'll be glad later.
  • You can use setPropertyActionListener to pass params, but you'll need to use an action method. This is no different from standard JSF 1.2. Indeed, using the Ajax tag is usually just a matter of adding it to already working code. Neat!
  • @this refers the current component. (And, by the way @form refers the the wrapping form, and @all and @none are kinda self explanatory)

Lots of code in a short space, but we've already covered a lot in other blogs. Questions? Ask away, in the comments, and I'll do my best to answer.


And, one last note: If you're attending JavaOne, this example is the basis for a talk that Ryan Lubke and I will be giving at JavaOne - BOF 4146, on Wednesday, at 745pm, Esplanade 307. Come on by and ask questions. I'll post the slides and the full example code in a later blog, after the conference.

Related Topics >>

Comments

Um, whoops. Yes, you're of course right - the reference to #{listholder} in the setProp will blow up the minute you really try to reuse it. I've fixed this bug in my blog, and in the associated demo. One clarification - the controller (why the quotes BTW?) needs to have request scope, rather than none scope (which would be truely stateless), since it's actually having it's state primed for that request by the setProp calls - though as you point out, method parameters should also allow @NoneScoped to be used, as well as a cleaner API.

Thanks Jim. Actually, it works much better when you replace <:setPropertyActionListener value="#{listholder1}" target="#{switchlistController.listHolder1}"/> with <f:setPropertyActionListener value="#{cc.attrs.listholder1}" target="#{switchlistController.listHolder1}"/> :-) The key observation is that what you call a "controller" must be stateless so that it can serve requests from any component instance. Since the latest Glassfish build supports action method parameters, you can say sayonara to the sPAL tags and rewrite the example as <h:commandButton id="move2to1" value="&lt;&lt;" action="#{switchlistController.m2_1(cc.attrs.listholder1, cc.attrs.listholder2)}"/>

Cay - The controller is requestscoped - and since you can only manipulate one list set per request (via ajax), there are as a result no concurrency issues. Even if you abandon ajax, it should still work, since the list controller isn't accessed unless you hit the button which moves things from one list to the other. Hitting "submit" somewhere else on the page just submits the current values of each list, without moving them anywhere. You still have to create two listholders for each switchlist in the page, of course - but that's to be expected, since you're operating on different datasets, otherwise, why have more than one component?

Hi Jim: How does this technique work when you have more than one switchlist component on the page? Wouldn't each of them access the same SwitchlistController?

You'll find the information you're looking for - documentation on all the composite namespace elements - in what are called the PDL docs. You can find the PDL docs linked off of the release notes - if you look back a couple posts, you'll see that we annouced beta1 (which is b13) - so you should revisit our website download section anyway. The PDL (Page Definition Language) docs cover all the f: tags, h: tags, composite: tags and ui: tags, as well as c: and fn: from JSTL. Many parts of the docs have small examples as well. These are just as important as JavaDoc, so you should get used to looking there. If you still have questions after that, you can always post them to our webtier@glassfish.dev.sun.com mailing list. Also, your question makes me realize I should write a blog hitting each of the elements, and their use. That will have to happen after JavaOne.

Hmm, the site has eaten all my JSF example code; second try: Hi Jim, Thanks for your usefull blogposts about JSF2. I'm currently looking into composite components. Here is my example: <lp:loginBox userName="#{loginBean.userId}" password="#{loginBean.password}" loginAction="#{loginBean.doLogin}"> I had a question about the <composition:interface>. Is <composite:attribute> the only supported child element? From reading older posts on Ed Burns blog I have also seen the following possibilities: - <composite:actionSource> - <composite:valueHolder> - <composite:facet> - <composite:editableValueHolder> - .. other behavioural interfaces?? Are these deprecated or still available in current version of JSF2? At least <composite:editableValueHolder> seems to work ok in Mojarra 2.0b12 Now I'm trying <composite:actionSource>: <composite:actionSource name="loginAction" /> But then how can I access this "loginAction" in my composite component. I have: <h:commandButton id="login" type="submit" action="#{cc.attrs.loginAction}" value="Login" styleClass="button"/> but then I receive: The class 'LoginManagedBean' does not have the property 'doLogin'. I have seen the syntax: <composite:attribute name="loginAction" targets="login" required="true" method-signature="String f1()" /> That does work; but to me the method-signature seems more verbose/difficult? Wim

Hi Jim, Thanks for your usefull blogposts about JSF2. I'm currently looking into composite components. Here is my example: I had a question about the . Is the only supported child element? From reading older posts on Ed Burns blog I have also seen the following possibilities: - - - - - .. other behavioural interfaces?? Are these deprecated or still available in current version of JSF2? At least seems to work ok in Mojarra 2.0b12 Now I'm trying : But then how can I access this "loginAction" in my composite component. I have: but then I receive: The class 'LoginManagedBean' does not have the property 'doLogin'. I have seen the syntax: That does work; but to me the method-signature seems more verbose/difficult? Wim

Thanks for the tip where to find the PDL docs (eg. https://javaserverfaces.dev.java.net/nonav/rlnotes/2.0.0/index.html for 2.0beta1). I was looking in the spec :-) I just updated to mojarra jsf2.0 beta1 on my Glassfish v3 and will dive into the composite: PDL documentation. Wim