JSF update inputText after selectOneMenu choice

4ndro1d picture 4ndro1d · Feb 22, 2013 · Viewed 15.9k times · Source

I want to change the inputTexts' values when I choose another Skin from my selectOneMenu. Everything is doing well, my Converter gives back the right Object from the menu, but the inputTexts are not updated.

<h:form>
    <h:selectOneMenu id="dropdownSkin"
        value="#{helloBean.currentSkin}" defaultLabel="Select a skin.."
        valueChangeListener="#{helloBean.skinValueChanged}" immediate="true"
        onchange="this.form.submit()" converter="SkinConverter" >
        <f:selectItems value="#{helloBean.mySkinsSI}" var="c"
            itemValue="#{c.value}" />
    </h:selectOneMenu>


    <br />
    <h:inputText id="name" value="#{helloBean.currentSkin.title}"></h:inputText>
    <br />
    <h:inputText id="tcolor" value="#{helloBean.currentSkin.titleBar.textColor}"></h:inputText>
    <br />
    <h:inputText id="bcolor" value="#{helloBean.currentSkin.titleBar.backgroundColorStart}"></h:inputText>
</h:form>

Here is what my Bean looks like. I debugged it and the Object currentSkin is set correctly. Now i need to know how to update the textfields content.

@ManagedBean
@SessionScoped
public class HelloBean implements Serializable {

private static final long serialVersionUID = 1L;

private List<ExtendedSkin> mySkins;
private List<SelectItem> mySkinsSI;
private ExtendedSkin currentSkin;

public void skinValueChanged(ValueChangeEvent e) {
    currentSkin = (ExtendedSkin) e.getNewValue();
    FacesContext.getCurrentInstance().renderResponse();
}

public List<ExtendedSkin> getMySkins() {
    mySkins = XMLParser.readExtendedSkins();
    return mySkins;
}

public List<SelectItem> getMySkinsSI() {
    mySkinsSI = new LinkedList<SelectItem>();
    for (ExtendedSkin s : getMySkins()) {
        mySkinsSI.add(new SelectItem(s, s.getTitle()));
    }
    return mySkinsSI;
}

public void setMySkinsSI(List<SelectItem> myItems) {
    this.mySkinsSI = myItems;
}

public ExtendedSkin getCurrentSkin() {
    if (currentSkin == null) {
        currentSkin = getMySkins().get(0);
    }
    return currentSkin;
}

public void setCurrentSkin(ExtendedSkin currentSkin) {
    this.currentSkin = currentSkin;
}
}

Answer

Luiggi Mendoza picture Luiggi Mendoza · Feb 22, 2013

The problem here is that the converter is doing its work filling the helloBean.currentSkin object, but the values in the <h:inputText> that are bounded to this helloBean.currentSkin: title, textColor and backgroundColorStart will be send to the server and replace the actual values that were loaded by the converter. In other words:

  • The converter is executed and builds the helloBean.currentSkin based on the selected value.
  • The <h:inputText id="name"> empty value is sent to server and will be injected in helloBean.currentSkin.title. Same behavior for the other 2 <h:inputText>s.
  • The view will be loaded using the selected helloBean.currentSkin and it will load the helloBean.currentSkin.title with the empty value. Same behavior for the other 2 <h:inputText>s.

There are two possible solutions to this problem:

  1. Move the <h:inputText>s outside the form, so the empty values won't be send to the server. When loading the view, it will maintain the values loaded in the converter.

    <h:form>
        <h:selectOneMenu id="dropdownSkin"
            value="#{helloBean.currentSkin}" defaultLabel="Select a skin.."
            valueChangeListener="#{helloBean.skinValueChanged}" immediate="true"
            onchange="this.form.submit()" converter="SkinConverter" >
            <f:selectItems value="#{helloBean.mySkinsSI}" var="c"
                itemValue="#{c.value}" />
        </h:selectOneMenu>
    </h:form>
    <br />
    <h:inputText id="name" value="#{helloBean.currentSkin.title}"></h:inputText>
    <!-- rest of Facelets code... -->
    
  2. Since you're loading the helloBean.currentSkin while changing the selected value on your dropdownlist, you can add ajax behavior using <f:ajax> tag component inside the <h:selectOneMenu> and update the fields in a cleaner way. I would opt for this solution.

    <h:form>
        <!-- Note that there's no need of the onchange JavaScript function -->
        <h:selectOneMenu id="dropdownSkin"
            value="#{helloBean.currentSkin}" defaultLabel="Select a skin.."
            valueChangeListener="#{helloBean.skinValueChanged}" immediate="true"
            converter="SkinConverter" >
            <f:selectItems value="#{helloBean.mySkinsSI}" var="c"
                itemValue="#{c.value}" />
            <f:ajax process="@this" render="name tcolor bcolor" />
        </h:selectOneMenu>
        <br />
        <h:inputText id="name" value="#{helloBean.currentSkin.title}" />
        <h:inputText id="tcolor" value="#{helloBean.currentSkin.titleBar.textColor}" />
        <br />
        <h:inputText id="bcolor"
            value="#{helloBean.currentSkin.titleBar.backgroundColorStart}" />
    </h:form>
    

You can learn more about <f:ajax> in online tutorial like this one.

Since you're going to use an ajax call in your page, you should change your managed bean scope from @SessionScoped to @ViewScoped. More info about this here: Communication in JSF 2