Chris
Chris

Reputation: 85

Why can't I set f:selectItems in encodeBegin in composite component?

I'm trying to understand how to program composite components, and have followed the great example by balusc, and others, but I'm missing something.

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:cc="http://java.sun.com/jsf/composite">

<cc:interface componentType="simpleComponent">
    <cc:attribute name="value" required="true" type="example.LanguageDefinition"/>
    <cc:attribute name="possibilities" default="de_DE" type="java.lang.String"/>
</cc:interface>

<cc:implementation>     
    <p:selectOneMenu binding="#{cc.inputComponent}" converter="omnifaces.SelectItemsConverter" var="l"
        value="#{cc.attrs.value}">
        <p:ajax update="@form"/>
        <f:selectItems value="#{cc.languages}" var="l" itemLabel="#{l.label}"
            itemValue="#{l}" />
        <p:column>#{l.label}</p:column>
        <p:column>
            <p:graphicImage name="#{l.imgPath}" />
        </p:column>
    </p:selectOneMenu>      
</cc:implementation>

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;

import example.LanguageDefinition;

@FacesComponent("simpleComponent")
public class SimpleComponent extends UIInput implements NamingContainer {

private UIInput  inputComponent;

public UIInput getInputComponent() {
    return inputComponent;
}

public void setInputComponent(UIInput inputComponent) {
    this.inputComponent = inputComponent;
}

@Override
public Object getSubmittedValue() {
    return inputComponent.getSubmittedValue();
}

@Override
protected Object getConvertedValue(FacesContext context, Object newSubmittedValue) {
   return (String) newSubmittedValue;
}

@Override
public String getFamily() {
    return UINamingContainer.COMPONENT_FAMILY;
}

@Override
public void encodeBegin(FacesContext context) throws IOException {
    String possibilities = getAttributeValue("possibilities", "en_US");
    // In theory, build based on incoming possibilities, now hard code
    List<LanguageDefinition> languages = new ArrayList<LanguageDefinition>();
    languages.add(new LanguageDefinition("pt_PT",
            "images/flags/PT.gif", "Português (Portugal)"));
    languages.add(new LanguageDefinition("cs_CZ",
            "images/flags/CZ.gif", "Czech (Czech Republic)"));
    getStateHelper().put("languages", languages);
    super.encodeBegin(context);
}

@SuppressWarnings("unchecked")
public List<LanguageDefinition> getLanguages() {
    return (List<LanguageDefinition>) getStateHelper().get("languages");
}

/**
 * Return specified attribute value or otherwise the specified default if it's null.
 */
@SuppressWarnings("unchecked")
private <T> T getAttributeValue(String key, T defaultValue) {
    T value = (T) getAttributes().get(key);
    return (value != null) ? value : defaultValue;
}
}

This isn't working calling like this:

<ex:simpleComponent value="#{compBean.selected}"/>

Everything renders fine, but the submitted value is never passed back to my "compBean". However, if I set the selectItems "languages" in any other way other than doing it in encodeBegin it works... as a member variable with getters and setters, or using this code:

<c:set target="#{cc}" property="possibilities" value="#{cc.attrs.possibilities}"/>

In combination with this much simpler class:

import java.util.ArrayList;
import java.util.List;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;

import example.LanguageDefinition;

@FacesComponent("simpleComponent")
public class SimpleComponent extends UIInput implements NamingContainer {

@Override
public String getFamily() {
    return UINamingContainer.COMPONENT_FAMILY;
}

public void setPossibilities(String possibilities) {
    if (getStateHelper().get("possibilities") == null) {
        List<LanguageDefinition> languages = new ArrayList<LanguageDefinition>();
        languages.add(new LanguageDefinition("pt_PT",
                "images/flags/PT.gif", "Português (Portugal)"));
        languages.add(new LanguageDefinition("cs_CZ",
                "images/flags/CZ.gif", "Czech (Czech Republic)"));
        getStateHelper().put("languages", languages);
getStateHelper().put("possibilities", possibilities);
    }
}

public String getPossibilities() {
    return (String) getStateHelper().get("possibilities");
}

@SuppressWarnings("unchecked")
public List<LanguageDefinition> getLanguages() {
    return (List<LanguageDefinition>) getStateHelper().get("languages");
}
}

But, it doesn't seem right to do it this way (wasn't the way balusc did it), so I'm just trying to understand. Which way is better? Assuming the first, why is mine not working?

Thanks!

Upvotes: 0

Views: 616

Answers (1)

Chris
Chris

Reputation: 85

So... the reason this wasn't working was because I forgot to implement Serializable on my custom drop down list value object "example.LanguageDefinition".

Hopefully the next person won't have to look for this needle in the haystack as long as I did :)

Upvotes: 1

Related Questions