Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Values of h:inputText inside ui:repeat are not processed

Tags:

jsf

primefaces

I want to process this form (valueChangueListener is not valid in real case).

This is the back bean:

public class TestBean extends PrivateBaseBean implements Serializable {
private List<String> strings;

@PostConstruct
public void init() {
    strings = new ArrayList<String>();
    strings.add("");
    strings.add("");
    strings.add("");
}

public void saveAction(ActionEvent event) {

    StringBuilder textToShowInMessage = new StringBuilder();
    for (String string : strings) {
        textToShowInMessage.append(string);
        textToShowInMessage.append("; ");
    }
    FacesMessage msg = new FacesMessage(super.getBundle().getString(
            textToShowInMessage.toString()), "");

    FacesContext.getCurrentInstance().addMessage(null, msg);
}

getters... setters...

An the view:

....
<h:form>
<ui:repeat var="string" value="#{testBean.strings}">
    <h:inputText value="#{string}" />
    <br />
</ui:repeat>
<p:commandButton value="#{msg.save}"
actionListener="#{testBean.saveAction}" icon="ui-icon-disk"
        update="@form" />
</h:form>
...

When the form is processed in the back bean string list always is blank.

How to process form intput's inside iteration, without any value changue listener?

There are some screenshots: Debug code

Form in browser

The same problem occurs with action or actionListener on

like image 367
Eric Garcia Avatar asked May 14 '13 14:05

Eric Garcia


2 Answers

Your problem is not connected with PrimeFaces <p:commandButton>'s behaviour, but rather with a scoping problem that is implicilty created when using the <ui:repeat> tag.

First of all, let's depart from your example. Basically, you've got

<ui:repeat value="#{bean.strings}" var="s">
    <h:inputText value="#{s}"/>
</ui:repeat>

with the backing List<String> strings.

The culprit is here: value="#{s}". The exported by <ui:repeat> variable s is visible only within its loop and it is not bound to any managed bean's property, but instead only to a local variable. Put it differently, s is not bound/equal to bean.strings[index] as one would expect and has no knowledge, as we see, where it originated from. So basically, you're off with a unilateral relationship: value from the bean is printed in your input properly, but the reverse is not happening.

The workarounds

Workaround #1: wrapper classes / model objects

The situation can be overcome by using a wrapper object for your class. In case of a string it could be a 'simple mutable string', like below:

public class MString {
    private String string;//getter+setter+constructor
}

In this case the iteration will be working as predicted:

<ui:repeat value="#{bean.mstrings}" var="ms">
    <h:inputText value="#{ms.string}"/>
</ui:repeat>

with the backing List<MString> mstrings.

Note that if you have your model class, like User, and will change its properties within <ui:repeat> the class itself will be effectively a wrapper, so that the properties will be set appropriately.

Workaround #2: chained property access

Another workaround consists of accessing an element of your collection directly from within a <h:inputText> tag. This way, any such property will be set by accessing the bean, then collection, then setting the property at the desired index. Excessively long, but that's how it is. As to the how question, <ui:repeat> provides for an exported current iteration status variable, varStatus, that will be used to access the array/collection in the managed bean.

In this case the iteration will also be working as predicted:

<ui:repeat value="#{bean.strings}" var="s" varStatus="status">
    <h:inputText value="#{bean.strings[status.index]}"/>
</ui:repeat>

with the ordinary backing List<String> strings.

like image 166
skuntsel Avatar answered Nov 14 '22 06:11

skuntsel


My workaround solution take the value directly from the page:

<ui:repeat id="repeat" value="#{bean.strings}" var="s" varStatus="status">
    <h:inputText id="x" value="#{s.field}"/>
    <h:commandLink style="margin: .5em 0" styleClass="commandLink" actionListener="#{bean.save(status.index)}" value="#{bundle.Send}"/>
</ui:repeat>

The save method:

public void save(String rowid) {
        String jsParam = Util.getJsParam("repeat:" + rowid + ":x");
        System.out.println("jsParam: " + jsParam); //persist...
    }

The getJsParam method:

public static String getJsParam(String paramName) {
    javax.faces.context.FacesContext jsf = javax.faces.context.FacesContext.getCurrentInstance();
    Map<String, String> requestParameterMap = jsf.getExternalContext().getRequestParameterMap();
    String paramValue = requestParameterMap.get(paramName);
    if (paramValue != null) {
        paramValue = paramValue.trim();
        if (paramValue.length() == 0) {
            paramValue = null;
        }
    }
    return paramValue;
}
like image 43
Eitan Rimon Avatar answered Nov 14 '22 05:11

Eitan Rimon