Archive

Posts Tagged ‘combobox’

JSF: SelectOneComboBox

January 13th, 2010 5 comments
Post Technology Stack
  • MyFaces 1.1.7 (JSF 1.1 spec)
  • Java 1.5
  • MyFaces Tomahawk 1.1.9
  • ExtJS 3.1 *

Not Again…

JSF, that steaming pile of undocumented, developer-hating obfuscation has yet again soaked up about 16 hours of my time to do something that should have been a three second configuration. Just to reiterate, much like Randall Munroe’s hatred of velociraptors spurs him to evaluate houses based on their ability to repulse dinosauriod attacks, I find myself now evaluating jobs by how close the project requirements are to actually using the JSF framework.

That said, when you’re a contractor, you use the tools selected for you by the almighty architects. In my case, it’s JSF or my paycheck. Being the Alpha Geek that I am, I gratefully welcomed the paycheck an opportunity to learn more about JSF.

What did they do now???

The crux of the problem is right in the title of this post. Combo box. JSF doesn’t have one, and I needed one. Being the Jack of All Tech that I am, I figured it would be relatively simple to grab the ExtJS combobox and front the JSF selectOneMenu control with it. So long as the ExtJS code didn’t monkey with all the javascript and DOM that the JSF selectOneMenu control created, I’d have myself a nice little combobox that worked with JSF. Well, if you’ve been coding for any longer than two minutes, you know just how screwed up things can get right after you utter the words “That was easy!”.

In this case, the problem emerged as an inability to submit the form of which the combobox was a part. Weirder still, submitting was only a problem when I entered in a value that was not in the dropdown list. Armed with Eclipse and 18mg of ritilin, I spent a few hours tracing the problem down to validation which myfaces was perpetrating on the submitted combobox value – validation that was not implemented as an official Validator.

Here I give you a few lines in javax.faces.components.UISelectOne from where the problem stems. Line 69 is perfectly fine, setting off an execution chain that includes getting the list of validators that have been assigned to the component and executing them. That is not where our problem validation happens, and that’s the problem. Line 77, when parsed, creates an iterator for the list of options created for the drop-down. That iterator is then sent to the _SelectItemsUtil.matchValue method which then makes sure that the submitted value for the drop-down component is one of the available options… and there’s the reason I will never get hours of my life back. See, we’ve got code that runs all the validators assigned the UISelect descendant class, then we’ve got what amounts to a hardcoded validation right after that. Isn’t that great!

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
protected void validateValue(FacesContext context, Object value)
{
    super.validateValue(context, value);
 
    if (!isValid() || value == null)
    {
        return;
    }
 
    // selected value must match to one of the available options
    if (!_SelectItemsUtil.matchValue(value, new _SelectItemsIterator(this)))
    {
        _MessageUtils.addErrorMessage(context, this, INVALID_MESSAGE_ID,
                        new Object[] {getId()});
        setValid(false);
    }
}

Showing the code who’s boss

You’d think that would be the end of the combobox quest. I mean, if checking the submitted value against the options is hardcoded into a core-core API class, you’re basically screwed, right? No getting around that… If you answered yes, please stop reading here, turn in your geek badge, and go read yourself some FARK. If you answered by opening up firefox to the myfaces api and started looking for chinks in the API armour, then read on! …Oh – and if your answer was to break out your Wiha precision screwdriver set and pop the hood off your rig, it’s time to switch to decaf.

* A few important notes first. This solution uses an ExtJS combobox for the UI portion of the hack. ExtJS is, unfortunately, a semi-free library. The “semi” part comes in if you use the library for any public facing user-interfaces which are not open source. Of course, the licensing scheme is waaaay more complicated than that, so I suggest you visit ExtJS’s Licensing Overview page to figure out just what your options are. At the time of this writing, I don’t know of any completely open-source combobox solutions that can interact successfully with the underlying generated JSF DOM structures. If you do, leave a comment!!! Otherwise, the Java/JSF portion of this hack takes care of the application side of things and should be usable (or make usable) any UI combobox solution.

Let’s start with the html/jsp code to make the front end of our combobox work:

1
2
3
4
5
6
7
8
9
10
11
12
13
<f:view>
<h:form id="carCreatorForm">
    <t:selectOneMenu id="colourSelect"
                             value="#{car.Colour}"
                             binding="#{selectOneComboHelper.htmlSelectOneMenu}">
        <t:selectItems var="colour"
                             value="#{colourShelf.colours}"
                             itemLabel="#{colour}"
                             itemValue="#{colour}"/>
    </t:selectOneMenu>
...
</form>
</view>

A few assumptions:

  1. colourShelf is an object that holds available colors and is defined as a JSF managed bean, and it has a getColours method that returns a Java List collection of color names.
  2. car is also defined as a JSF managed bean, and has a setColour method.
  3. selectOneComboHelper is defined as a JSF managed bean, and will be discussed shortly.
  4. I dislike JSF immensely.

The above code is standard for creating a selectOneMenu component for MyFaces using the Tomahawk taglibs. The selectOneMenu tag has the value attribute tying the selected value to the “car” managed bean. The selectItems tag creates the option list. In short:

  1. The value attribute says to call the getColours method on the colourShelf managed bean, which in the example’s case returns a Java List collection of colour name strings.
  2. The var attribute states that “colour” will be the variable name in which the string colour names from the list will be stored.
  3. The selectItems tag iterates over the strings in the colour name list, creating new option elements for the select element created by the selectOneMenu tag.

Now that we have our list menu, here’s the code that makes it a combobox:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
<head>
    <!-- Ext relies on its default css so include it here. -->
    <!-- This must come BEFORE javascript includes! -->
    <link rel="stylesheet" type="text/css" href="js/extjs/resources/css/ext-all.css">
 
    <!-- Include here your own css files if you have them. -->
 
    <!-- First of javascript includes must be an adapter... -->
    <script type="text/javascript" src="js/extjs/adapter/ext/ext-base.js"></script>
 
    <!-- ...then you need the Ext itself, either debug or production version. -->
    <script type="text/javascript" src="js/extjs/ext-all-debug.js"></script>
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->
    <script type="text/javascript">
 
    // Path to the blank image must point to a valid location on your server
    Ext.BLANK_IMAGE_URL = 'js/extjs/resources/images/default/s.gif';
 
    // Main application entry point
    Ext.onReady(function() {
        var converted = new Ext.form.ComboBox({
            typeAhead: true,
            triggerAction: 'all',
            transform:'carCreatorForm:colourSelect',
            width:135
        });
    });
    </script>
</head>
<body>
...

The above is standard ExtJS – you include all the ExtJS scripts in the head element of the page, and then use the Ext.onReady function to initialise your ExtJS UI components. The secret to getting ExtJS to recognise and transform the JSF select box is to remember that the HTML form element IDs JSF generates are the name of the form first, a colon, and then the ID you assigned the element. In the case of the code above, our form name is “carCreatorForm”, and the ID of the select menu is “colourSelect”, hence “carCreatorForm:colourSelect”.

The ExtJS combobox transform attribute tells the component that it isn’t going to create a combobox from scratch, but will instead create one from an existing select element (I’ve gotta tell ya – I love those guys at ExtJS!). That means when the html page’s body’s “onLoad” event fires, ExtJS will seek out a DOM element with the name “carCreatorForm:colourSelect”, suck all the information out of it, remove it, and replace it with pod-person combobox version.

Managing JSF shortcomings

With the user-interface portion of our little hack done, we turn now to the underlying MyFaces code that so offends our idea of “How Things Should Work”. Honestly, I don’t have time to figure out if this same “bug” exists in other JSF implementations, or even later versions of MyFaces. If not, then you are already home free and the rest of this post only serves to show you how clever I am (I am extremely clever!). If you try the above and find that the page refreshes without any form elements getting bound to their backing beans, however, this next part my serve as a guide, if not an outright shortcut to getting a working combobox.

To recap (damn, this is a long post!), the symptoms of the problem is the lack of anything actually happening when you hit the form’s submit button. The screen refreshes, but none of the form values are bound to their associated backing beans. Another symptom is this nebulous entry in your log files:

DEBUG | 2010-01-12 15:19:18,550 | LifecycleImpl.java:178 | exiting from lifecycle.execute in PROCESS_VALIDATIONS(3) because getRenderResponse is true from one of the after listeners

According to the code, there should have been another error along the lines of:

javax.faces.component.UISelectOne.INVALID

…but no amount of fiddling with Log4j would make it appear.

Anyhoo, that debug log message is the clue to what is really happening. When the code decides that you are vainly trying to trick it by providing a value that wasn’t on the approved option list, it stops the bus at the third JSF lifecycle step of “Process Validations”, turns it around and leaves the way it drove in. Just so you know, the fourth lifecycle step is “Update Model Values”, which is where the form values would have been bound to the backing beans. It’s also where the bathroom was, which is why you get that bloated, unfulfilled feeling when you see the page refresh with no joy for the backing beans.

Giving JSF more “options”

I know this post is aging you where you sit, that your coffee is cold, and that you are starting to worry about deep vein thrombosis, but there’s only two parts to this hack and all the background’s been covered, so just stay with me a few more paragraphs!

I’ve seen very few APIs that were bullet proof – meaning that I couldn’t screw with them. The general rule is the more complex or less well-developed the API, the easier it is to screw with. That axiom in mind, JSF is extremely easy to screw with! Let’s start with a “helper” class, and when I say helper, I mean l33t h4x0r c0Dz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.facets.examples;
 
import java.util.List;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import org.apache.commons.lang.StringUtils;
import org.apache.myfaces.component.html.ext.HtmlSelectOneMenu;
 
public class SelectOneComboBox {
 
    private HtmlSelectOneMenu component;
 
    public HtmlSelectOneMenu getHtmlSelectOneMenu() {
        return this.component;
    }
 
    public void setHtmlSelectOneMenu(HtmlSelectOneMenu component) {
        this.component = component;
        @SuppressWarnings("unchecked")
        // Get _ComponentChildrenList, with list of child components of the SelectOneMenu component.
        List children = component.getChildren();
        if(children==null || children.isEmpty()) {
            // If no children, page is probably just being rendered, not submitted, so nothing
            // needs to be (or can be) done.
            return;
        }
        // Tell myfaces to marshal the submitted values so we can get to them. 
        component.decode(FacesContext.getCurrentInstance());
        // This is the previous value of the component - the last value to be submitted.
        Object value = component.getValue();
        // This is the current value of the component - the value just submitted.
        Object subValue = component.getSubmittedValue();
        if(!(subValue instanceof String)) {
            component.setSubmittedValue(value);
            return;
        }
        String valueStr = (String)subValue;
        // Get the only child of the SelectOneMenu - the SelectItems component.
        UISelectItems itemsComp = (UISelectItems)children.get(0);
        // Get the array of values from the SelectOneMenu.
        SelectItem[] items = (SelectItem[])itemsComp.getValue();
        // Is the submitted value one of the listed option values?
        for(SelectItem item : items) {
            if(!StringUtils.isBlank((String)item.getValue()) && item.getValue().equals(valueStr)) {
                return; // Submitted value is a listed option - nothing needs to be done.
            }
        }
        // Create a new value for the written-in value of the combobox.
        SelectItem newValue = new SelectItem(valueStr,valueStr);
 
        // Create and fill a new array for the existing option values and the one written in value.
        SelectItem[] newItems = new SelectItem[items.length+1];
        for(int i=0;i<items.length; i++) {
            newItems[i] = items[i];
        }
        newItems[items.length] = (new SelectItem(valueStr));
        // Set the new value array on the SelectItems component so the backing list now includes
        // the written-in value.
        itemsComp.setValue(newItems);
        // Tell the component that the submitted value is an object from the backing list.
        component.setSubmittedValue(newValue);
    }
}

For the most part, everything you need to know about the class exists as inline comments in the code above. Regardless of whether the backing list for the SelectItems component contains strings or objects of your own design, the above should work because it is called between the second and third lifecycle steps (2=Apply Request Values, 3=Process Validations). Since the “Process Validations” step is where everything is converted to the underlying object model from the necessary string values for the front-end HTML UI, strings are all you really have to deal with. I would be careful if you are using Enums as your backing list objects, however, as you can’t just create a new Enum member on the fly from a new value.

To get this to work, you need two pieces of code. The first you saw in the jsp code above, specifically the “binding” attribute of the SelectOneMenu component. That snippet hands the select component object to the SelectOneComboBox.setHtmlSelectOneMenu method above. In order for the SelectOneComboBox object to exist, we need to add it as a managed bean to your faces-config file, like so:

<faces-config>
    <managed-bean>
        <managed-bean-name>selectOneComboHelper</managed-bean-name>
        <managed-bean-class>com.facets.examples.SelectOneComboBox</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>
    ...

Congratulations! Not only do you now have a working JSF combo box, but more importantly you have made it through my longest post yet!