Bind <c:forEach><h:inputText> to a List<String> - jsf

I want the use to enter one or more value to the JSF inputText components and then put them in a List :
private List<String> stringList= new ArrayList<String>();
I want to do something like this :
<c:forEach id="myData" items="#{documentController.listeColonnes}" var="address" varStatus="loop">
<h:outputLabel value="#{address}" />
<h:inputText value="#{documentController.stringList[loop.index]}"/>
</c:forEach>
ListeColonnes has label values (ID,prenom .... )
I want to enter this values(12,sam,....) in a new arrayList ()
Any idea !

Ok, I got the same problem and I managed to make it work, after several trials and errors. This is was my case:
I wanted to create a dynamic UI that can be configured with JSON, so a took that data and put it inside a class called property.
After that, I need to collect the data given by the user to store it in a DB.
This was my approach:
JSF
<h:form id="forma_po" styleClass="form-body" prependId="false">
<c:forEach items="#{purchaseOrderPrinterBean.properties}" var="property">
<p:outputLabel value="#{message_report[property.label]}:"/>
<br/>
<p:inputTextarea id="#{property.name}" rows="#{property.lines}"
cols="#{property.numberOfCharacters}" required="true"
maxlength="#{property.lines * property.numberOfCharacters}"
autoResize="false" rendered="#{property.type == 'TEXT_AREA'}"
value="#{purchaseOrderPrinterBean.values[property.name]}">
<p:ajax event="change" listener="#{purchaseOrderPrinterBean.fieldValue}"/>
</p:inputTextarea>
<p:calendar id="popup_#{property.name}" rendered="#{property.type == 'DATE'}"
value="#{purchaseOrderPrinterBean.values[property.name]}" required="true"
pattern="#{property.format}" effect="slideDown">
<p:ajax event="dateSelect" listener="#{purchaseOrderPrinterBean.fieldValue}"/>
</p:calendar>
<br/>
</c:forEach>
</h:form>
As you can see, if the user had previously saved any data at all the I show it with the value attribute on both p:inputTextarea and p:calendar, this purchaseOrderPrinterBean.values[property.name] was of course a java.util.HashMap<String, Object> field, a problem I encounter with this was when the user gave or change the information, the values field did not update at all, hence the <p:ajax /> tags inside the p:inputTextarea and p:calendar.
Bean
In the bean I had this:
public void fieldValue(AjaxBehaviorEvent e) {
if (e.getSource() instanceof UIInput) {
UIInput input = (UIInput) e.getSource();
Object value = input.getValue();
String id = input.getId();
if(StringUtils.startsWith(id, "popup_")) {
id = StringUtils.remove(id, "popup_");
}
values.put(id, value);
}
}
This was the only case I found to make it work, a caution case, and I don't know why, but I had to put the prefix 'popup_' to the calendar, otherwise I got an error for duplicated ids.
Hope it helps, and excuse my bad english.

Try this
<ui:repeat value="#{documentController.listeColonnes}" var="address" varStatus="loop">
<h:outputLabel value="#{address}" />
<h:inputText value="#{documentController.stringList.get(loop.index)}"/>
</ui:repeat>

Related

Ajax not function on first submit, after page refresh only result will refresh

I am using Java EJB with JSF.
What I doing is just a simple search result from database and display it on a table using JSF with ajax. But why when first submitting, the result is not appear. And second time only the result will appear.
Below is my JSF page code
<h:form id="wholeForm">
<p:panelGrid id="resultTable" styleClass="result_table1" >
<p:row>
<p:column>
<div align="center" id="font-size1">
<h:selectOneRadio value="#{scanResult.typeOfScan}">
<f:selectItem itemValue="Carrier" itemLabel="Carrier"></f:selectItem>
<f:selectItem itemValue="Slot" itemLabel="Slot"></f:selectItem>
<f:selectItem itemValue="HD" itemLabel="HD"></f:selectItem>
<p:ajax event="change"></p:ajax>
</h:selectOneRadio>
Scan: <p:inputText styleClass="searchField" id="counter" value="#{scanResult.serialNumber}" a:autofocus="true">
<p:ajax event="keydown" update="wholeForm" onstart="if (event.keyCode != 13) { return false;}" listener="#{scanResult.checkResult()}" />
</p:inputText>
<br/><br/>
</div>
</p:column>
</p:row>
</p:panelGrid>
<p:panelGrid id="resultTable1" styleClass="result_table1" rendered="#{not empty scanResult.scanResultCarrier}" >
<c:forEach items="#{scanResult.scanResultCarrier}" var="result" >
<!-- ..do something and call out result -->
</p:column>
</p:row>
</c:forEach>
</p:panelGrid>
</h:form>
And my managed bean is as below
#Named(value = "scanResult")
#SessionScoped
public class scanResult implements Serializable {
//Some code here
public void checkResult() {
scanResultCarrier = new ArrayList<>();
scanResultSlot = new ArrayList<>();
System.out.println("checking");
if (typeOfScan.equals("Carrier")) {
System.out.println("Serial " + serialNumber);
scanResultCarrier = scanresultcarrierFacade.searchResultCarrier(serialNumber);
System.out.println(scanResultCarrier.size());
} else if (typeOfScan.equals("Slot")) {
scanResultSlot = scanresultslotFacade.searchResultSlot(serialNumber);
} else if (typeOfScan.equals("HD")) {
} else {
}
serialNumber = "";
}
}
I use System.out.println(lsitofresult.size()); to print out my result and the result is not blank. Mean that I successful retrieve my result from database.
But my result table not able to show out after I click on enter.
Then I notice that my url is as below
http://localhost:8080/outgoingScanSystem-war/faces/index.xhtml;jsessionid=8b6cefa932ff60984607ee38ec13
And after I refresh my page, the result will appear again.
And my url change to :
http://localhost:8080/outgoingScanSystem-war/faces/index.xhtml
May I know why? Is it related to URL? I have no idea where should I start my troubleshoot. Anyone can give me some guideline?
I see some problems here. First of all, you're using the session scope while your managed bean should be view scoped (there's no reason to use the session here, see the link below). That's the cause why your results are getting displayed when you refresh the page.
Second, I'd rather put the search results out from the form. I see no reason for them to be in the same form of the search itself. Then, when performing a searh you should only update the result list:
<p:ajax event="keydown" update="resultTable1" onstart="if (event.keyCode != 13) { return false;}" listener="#{scanResult.checkResult()}" />
Third, as you're using Primefaces, I encourage you to take a look to its p:remoteCommand tool in order to perform ajax-based calls to the beans from your JS code directly. This way you should avoid your second problem, which seems to be that you're preventing the standard form sending on the ajax start event (which might not even get called). You could do something like this:
<p:remoteCommand name="search" update="resultTable1" actionListener="#{scanResult.checkResult}" />
<p:inputText styleClass="searchField" id="counter" value="#{scanResult.serialNumber}"
a:autofocus="true" onkeypress="if (event.keyCode != 13) {search(); return false;}">
<!-- Update the input text value in the bean for each pressed key -->
<p:ajax event="keydown" />
</p:inputText>
See also:
How to choose the right bean scope?
Jsf calling bean method from input text when pressing enter

JSF - How can I retain the transient values within a ui:repeat after an immediate action

I created a very simple example based on my project in order to illustrate my doubt. Just a way to register a person with a list of telephone numbers.
MainController.java
private String name;
private List<Phone> phoneList;
// Getters and Setters
#PostConstruct
private void init() {
phoneList = new ArrayList<>();
}
public static class Phone implements Serializable {
private String number;
// Getters and Setters
#Override
public String toString() {
return number != null ? number : "null";
}
}
public void add() {
phoneList.add(new Phone());
}
public void save() {
System.out.println("Name: " + name + "; " + phoneList.toString());
}
index.xhtml
<h:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText value="#{phone.number}" required="true" />
</ui:repeat>
<h:commandButton action="#{mainController.add()}" value="Add Phone" immediate="true" />
<h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
In my example, note that all phone fields that are added MUST be filled in (required = true).
The problem is: when I type name and click add (to add a phone) the value of the field is maintained. But when I type a first phone and click add, the phone's value is not maintained. This occurs for all fields within the component ui:repeat.
Is there a way to preserve the input values within a after an immediate request, as with the name field?
Extra note: Other strange behavior I noticed is when add at least two phone fields, let the first blank and fills the second, and saves the form. After a failed validation (due to phone blank), click add will make all fields are filled with the value of the second phone.
Wildfly 9.0.2, JSF Api (Jboss) 2.2.12
Thanks to #BalusC comment. The OmniFaces library has two taghandlers that can be used in this case. In both cases input values will be preserved in case of validation failure. Note that h:commandButton should be with <h:commandButton immediate="false" />.
ignoreValidationFailed
In this case all validation failures will be ignored (including converter failures). Note that the h:form have to be changed to o:form. Also, the failures messages will still be displayed, which can be solved putting a proper condition in the rendered attribute. The files will look like this:
index.xhtml
<o:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText value="#{phone.number}" required="true" />
</ui:repeat>
<h:commandButton action="#{mainController.add()}" value="Add Phone">
<o:ignoreValidationFailed />
</h:commandButton>
<h:commandButton action="#{mainController.save()}" value="Save" />
</o:form>
<h:messages rendered="#{facesContext.validationFailed}" />
skipValidators
In this case only the validation failures will be ignored (the converters will still run). The failures messages will not be displayed, except for the converters. Note that this taghandler is only available since the 2.3 version. The files will look like this:
index.xhtml
<h:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText value="#{phone.number}" required="true" />
</ui:repeat>
<h:commandButton action="#{mainController.add()}" value="Add Phone">
<o:skipValidators />
</h:commandButton>
<h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
The solution that I use to this problem is to create an external field to the loop, which stores a JSON containing the values that should be saved. This field, to be outside the loop, properly saves values after each try and restore the missing values when necessary. I use two functions JavaScript and JQuery library.
So the files would look like this:
index.xhtml
<h:outputScript library="jquery" name="jquery.min.js" />
<h:outputScript library="all" name="all.js" />
<h:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText styleClass="savePhoneNumber" value="#{phone.number}" required="true" onchange="saveUiRepeatInput('#{allPhoneNumber.clientId}', 'savePhoneNumber')" />
</ui:repeat>
<h:inputHidden id="allPhoneNumber" binding="#{allPhoneNumber}" />
<h:outputScript>loadUiRepeatInput('#{allPhoneNumber.clientId}', 'savePhoneNumber')</h:outputScript>
<h:commandButton action="#{mainController.add()}" value="Add Phone" immediate="true" />
<h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
all.js
function saveUiRepeatInput(inputAll, inputClass) {
document.getElementById(inputAll).value = JSON.stringify($('.' + inputClass).map(function() { return this.value; }).get());
}
function loadUiRepeatInput(inputAll, inputClass) {
var jsonAll = document.getElementById(inputAll).value;
if (jsonAll) {
var array = JSON.parse(jsonAll);
$('.' + inputClass).each(function(i) { if (i < array.length) this.value = array[i]; });
}
}
Although work perfectly (including via ajax, with some minor changes), it looks like a hack, not an ideal solution. So if anyone can help with any solution strictly based on JSF, I will be grateful. Thanks.

JSF datatable: adding and removing rows clear rows values

I have a h:datatable showing a list of rows, and the fields of each row are input fields.
I render an "Add Row" button before the table, and a "Remove Row" button on each row of the table.
The baking bean is viewScoped, and the buttons add/remove elements from the java list in the backing bean, and then return to the same view.
I set the immediate attribute to "true" in the buttons in order to not validate the input fields when I add or remove a row.
Everything works ok but one thing: the values of the input fileds are cleared. I thought that the view kept the values beacuse the bean is viewScoped.
How can I achieve adding/removing rows without triggering validations and keeping the values that were already typed by the user in the form?
My view:
<h:form>
<h:commandButton value="Añadir Fila" immediate="true" action="#{tablaController.addRowAction}" />
<h:dataTable value="#{tablaController.lista}" var="fila" cellpadding="0" cellspacing="0" border="1">
<f:facet name="header">TABLA</f:facet>
<h:column>
<f:facet name="header"><h:outputLabel value="NOMBRE" /></f:facet>
<h:inputText id="nom" value="#{fila.nombre}" />
<h:message for="nom" class="msjError" />
</h:column>
<h:column>
<f:facet name="header"></f:facet>
<h:commandButton value="Quitar Fila" immediate="true" action="#{tablaController.removeRowAction(fila)}" />
</h:column>
</h:dataTable>
</h:form>
My backing bean:
#ManagedBean(name="tablaController")
#ViewScoped
public class TablaController {
private List<Fila> lista;
...
public TablaController() { }
...
#PostConstruct
public void init() {
this.lista = new ArrayList<Fila>();
for (int i=0; i<5; i++) {
Fila fila = new Fila();
fila.setNombre("");
this.lista.add(i,fila);
}
}
...
public String addRowAction () {
Fila fila = new Fila();
fila.setNombre("");
this.lista.add(fila);
return "";
}
public String removeRowAction (Fila f) {
boolean exito = this.lista.remove(f);
return "";
}
...
}
UPDATE --> MY SOLUTION:
I write here my solution if someone is interested.
The problem is that I use immediate="true" to skip validations, but this makes to skip the update_model_values too, so that the values entered by the user in the form are lost after clicking the add/remove buttons and re-redenring the page.
As I use "JSR-303 bean validation", my solution was to skip validations using the f:validateBean to enable/disable them. Depending on the button I click, if I want the validations to execute, I enable the bean validation (for example in a "submit" button), and if I want to skip them, I disable bean validation (like in the add/remove row buttons). But anyway the update_model_values always executes, so the values are not lost.
Here's the view:
<h:form>
<f:validateBean disabled="#{!empty param['disableValidation']}">
<h:commandButton value="Añadir Fila" action="#{tablaController.addRowAction}">
<f:param name="disableValidation" value="true" />
</h:commandButton>
<h:dataTable value="#{tablaController.lista}" var="fila" cellpadding="0" cellspacing="0" border="1">
<f:facet name="header">TABLA</f:facet>
<h:column>
<f:facet name="header"><h:outputLabel value="NOMBRE" /></f:facet>
<h:inputText id="nom" value="#{fila.nombre}" />
<h:message for="nom" class="msjError" />
</h:column>
<h:column>
<f:facet name="header"></f:facet>
<h:commandButton value="Quitar Fila" action="#{tablaController.removeRowAction(fila)}">
<f:param name="disableValidation" value="true" />
</h:commandButton>
</h:column>
</h:dataTable>
<h:commandButton value="Submit" action="#{tablaController.saveData}" />
</f:validateBean>
</h:form>
The backing bean:
#ManagedBean(name="tablaController")
#ViewScoped
public class TablaController {
private List<Fila> lista;
...
public TablaController() { }
...
#PostConstruct
public void init() {
this.lista = new ArrayList<Fila>();
for (int i=0; i<5; i++) {
Fila fila = new Fila();
fila.setNombre("fila "+i);
this.lista.add(i,fila);
}
}
...
public String addRowAction () {
Fila fila = new Fila();
fila.setNombre("");
this.lista.add(fila);
return "";
}
public String removeRowAction (Fila f) {
this.lista.remove(f);
return "";
}
...
public String saveData () {
...
//processes the valid data
//for example, calls to a service method to store them in a database
...
return "";
}
...
}
I set the immediate attribute to "true" in the buttons in order to not validate the input fields when I add or remove a row.
immediate="true" is the wrong tool for the job. It should be used to prioritize validation, not to enable/disable validation. The difference is rather huge as you encountered yourself.
You want to trigger validation conditionally. In case of e.g. required="true" that'd be as easy as
<h:inputText ... required="#{saveButtonPressed}" />
where #{saveButtonPressed} evaluates true when the save button is pressed. E.g. when its client ID is present in request parameter map.
In case of JSR 303 bean validation, that'd be a matter of
<f:validateBean disabled="#{not saveButtonPressed}">
<h:inputText ... />
</f:validateBean>
or with OmniFaces <o:validateBean> which allows controlling that on a per-command basis.
<h:commandButton id="add" ...>
<o:validateBean disabled="true" />
</h:commandButton>
I had exactly the same problem. In short, you can NOT use immediate for action that update data table(UIData) or facelet repeat. Short explanation:submitted values are not kept for re-display if inputs in UIData do not go through validation. Long explanation can be found here: long explanation and a related bug in Mojarra

JSF: what is the "correct" way of doing this?

I've been using JSF for a while but there's something that has always confused me. Hopefully someone can help.
Simple example, there's a page that shows a table of "Person"s and when you click on the "Person" name, it takes you to a page to view the details of the "Person".
Typically, I implement a personSearch.jsf page like this:
<h:dataTable value="#{personHandler.persons}" var="person">
<h:column>
<h:commandLink action="#{personHandler.show( person.id )}" >
<h:outputText value="#{person.name}" />
</h:commandLink>
</h:column>
</h:dataTable>
And I implement a personView.jsf page like this:
<h:panelGrid columns="2">
<h:outputText value="Person ID:" />
<h:outputText value="#{personHandler.selectedPerson.id}" />
<h:outputText value="Person Name:" />
<h:outputText value="#{personHandler.selectedPerson.name}" />
</h:panelGrid>
PersonHandler.show(Integer personId) sets personHandler.selectedPerson and then redirects to the personView page.
This all works fine when PersonHandler is a session bean. But I prefer it to be a request scoped bean because the user may have several windows open and I don't want there to be only one selected person per session.
So my question is, what's the "correct" way to do this JSF? I was once able to get what I wanted using a4j:keepAlive on the personHandler, but that always felt like a kludge. Again, this is something I've never understood about JSF.
Any help is greatly appreciated!
rob
If the view is supposed to be bookmarkable, pass the person ID as a GET request parameter instead of a POST request "parameter".
<h:outputLink value="viewperson.xhtml">
<f:param name="id" value="#{person.id}" />
</h:outputLink>
This way you can use two #RequestScoped beans, one for the list and one for the view. You can preload the selected person as follows:
#ManagedProperty(value="#{param.id}")
private Long id;
#PostConstruct
public void init() {
selectedPerson = personDAO.find(id);
}
If it is not supposed to be bookmarkable, then just create a single view which renders the view state conditionally.
<ui:fragment rendered="#{!personHandler.viewMode}">
<h:form>
<h:dataTable value="#{personHandler.persons}" var="person">
<h:column>
<h:commandLink value="#{person.name}" action="#{personHandler.show(person)}" />
</h:column>
</h:dataTable>
</h:form>
</ui:fragment>
<ui:fragment rendered="#{personHandler.viewMode}">
<h:form>
...
<h:commandLink value="Go back" action="#{personHandler.back}" />
</h:form>
</ui:fragment>
(You can if necessary split out the content of the both framgents to another Facelet files which you include by <ui:include>)
This way you can use a single #ViewScoped bean with action methods returning void or null.
public void show(Person selectedPerson) {
this.selectedPerson = selectedPerson;
}
public void back() {
selectedPerson = null;
}
public boolean isViewMode() {
return selectedPerson != null;
}
You can even wrap the whole view in some
<h:panelGroup id="container">
and nest the following in both command links to let Ajax magic do the work
<f:ajax execute="#form" render=":container" />

Change the properties of an Input within a ui:repeat

I'd like to change the "required" property of an InputText that is located within an ui:repeat, but I'm not able to access to the component from the ManagedBean:
<h:selectManyCheckbox id="required" value="#{test.required}"
layout="lineDirection" converter="javax.faces.Integer">
<f:ajax event="change" listener="#{test.update}" />
<f:selectItems value="#{test.selectable}"></f:selectItems>
</h:selectManyCheckbox>
<ui:repeat value="#{test.names}" var="name" id="repeat">
<h:panelGrid columns="3">
<h:outputLabel id="nameLabel">name:</h:outputLabel>
<h:inputText id="name" value="#{name}"
validator="#{test.validateName}" />
<h:message for="name"></h:message>
</h:panelGrid>
</ui:repeat>
I'm trying to use the findComponent method, but it does not work:
public void update(AjaxBehaviorEvent event) {
for(Integer i: selectable) {
UIViewRoot vr = FacesContext.getCurrentInstance().getViewRoot();
HtmlInputText input = (HtmlInputText)vr.findComponent("form:repeat:"+i+":name");
input.setRequired(required.contains(i));
}
}
The ui:repeat doesn't repeat the components in the view root, it repeats the component's output in the rendered HTML output.
There are several ways to achieve this properly. One of them is to use a value object instead and set the requireness there. E.g. a List<Item> wherein Item has the properties String name and boolean required.
<ui:repeat value="#{test.items}" var="item" id="repeat">
<h:panelGrid columns="3">
<h:outputLabel id="nameLabel">name:</h:outputLabel>
<h:inputText id="name" value="#{item.name}" required="#{item.required}" validator="#{test.validateName}" />
<h:message for="name"></h:message>
</h:panelGrid>
</ui:repeat>
There are more ways, but since the JSF version you're using and the functional requirement is unclear, it's only guessing which way is the most applicable in your case.

Resources