I've got a JSF page with a ui:repeater tag that simply displays a list of strings and some controls to add a string to a list. When adding a string I use ajax to update the repeater tag and have the new string be shown immediately without the page refresh. Here's how my page looks like:
<h:body>
<h:form>
<p:inputText id="name" value="#{testController.newString}"/>
<p:commandButton value="Add" actionListener="#{testController.addString}" update="strings" />
</h:form>
<h:panelGroup id="strings">
<ui:repeat var="str" value="#{stringModel.strings}" varStatus="stringData">
<div>
<h:outputText value="#{str}" />
<h:inputText value="#{str}" />
</div>
</ui:repeat>
</h:panelGroup>
</h:body>
Everything works except the inputText component. After ui-repeater is updated with Ajax is still displays the text from the previous string. For example, assume that initially i have a list with 2 strings, "val1" and "val2". I enter a new string called "val3" and submit the form. List is updated correctly on the server side and the repeater is updated, it now has 3 elements. However, while the h:outputText in the newly added element will correctly show "val3", the inputText will be displayed with "val2" as a value. So i end up with something looking like this:
output tag input tag
val1 val1
val2 val2
val3 val2 (???)
The backing beans are very simple:
A view scoped model bean
#Component
#Scope("view")
public class StringModel {
private List<String> strings = Lists.newArrayList("Value 1");
public List<String> getStrings() {
return strings;
}
public void setStrings(List<String> strings) {
this.strings = strings;
}
}
And a request scoped controller bean:
#Component
#Scope("request")
public class TestController {
private String newString;
#Autowired private StringModel model;
public void addString() {
model.getStrings().add(newString);
}
public String getNewString() {
return newString;
}
public void setNewString(String newString) {
this.newString = newString;
}
}
I did some testing and this actually works the same way for any input component, be that textInput, textArea, etc. Any help would be highly appreciated.
I can't tell in detail exactly why it displays the wrong value after update (it'll be that the internal loop index of <ui:repeat> is broken — try a newer Mojarra version), but just referencing the string item by index from varStatus works. It'll also immediately fix the future problem of being unable to submit the edited string value when you put this list in a form, because the String class is immutable and doesn't have a setter.
<ui:repeat value="#{stringModel.strings}" var="str" varStatus="loop">
<div>
<h:outputText value="#{str}" />
<h:inputText value="#{stringModel.strings[loop.index]}" />
</div>
</ui:repeat>
EditableValueHolders inside ui:repeat are broken (by design) in the current version o JSF specs. It will not work, there is no way to fix it. Maybe new versions will make ui:repeat a proper component with support for saving states of its children. Maybe not.
If you change ui:repeat to h:dataTable, things should work (if not, then your problem is somewhere else and I was wrong).
Frankly, there is no workaround apart from using repeaters from some other libraries - you should find working repeaters in Tomahawk, Trinidad and many other places. Primefaces, AFAIR, does not have a pure repeater.
I also had exactly the same problem before. I solved it by putting the inputText in a form. I also copied your codes and put the h:inputText inside a h:form and it worked as well.
<h:form>
<ui:repeat value="#{stringModel.strings}" var="str" varStatus="loop">
<div>
<h:outputText value="#{str}" />
<h:inputText value="#{str}" />
</div>
</ui:repeat>
</h:form>
Related
I have an OmniFaces <o:validateMultiple> set to two <p:selectOneMenu>. The one dropdown box is for the home, the other for the away team (of a sports game).
The validator checks, if the combination of the two teams already exists in the schedule, if so, the validator method return false. (see table above in the example: combination already exists... the single item in the list)
Now the problem:
The two select boxes are reset to their null values, but in the background, they seem to keep the value.
The UI tells the user "selection has been cleared", but this is not what I or the users expect.
Example
Before: validation:
After validation:
QUESTION:
How can I restore the select boxes' values after validation fail or rather how do I simply keep the values?
I guess resetting the inputs is just JSF specified behavior?? 🤷♂️
Is it a problem with PrimeFaces (I guess not)?
Can it be done? If so, how?
PS: I don't think posting the JSF code for the select boxes could help here, but if it could, please leave a comment
JSF should preserve the state of the component tree as defined by the life cycle of the framework.
However, depending on how your JSF page functions you may choose to for example only partially process a page whenever an event occurs. In this case only the processed components will retain their state.
Let's take your use case as an example and first define a view:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui" xmlns:o="http://omnifaces.org/ui"
xmlns:of="http://omnifaces.org/functions">
<h:head>
<title>SelectOneMenu Validation</title>
</h:head>
<h:body>
<h:form>
<o:validateMultiple id="validateMatch" components="home away"
validator="#{selectBackingBean.onValidateMatch}"
message="The combination already exists or is invalid!" />
<p:dataList value="#{selectBackingBean.matches}" var="match">
<f:facet name="header">Home / Away</f:facet>
#{match.home} vs #{match.away}
</p:dataList>
<br/><br/>
<p:selectOneMenu id="home" label="Please select home..." value="#{selectBackingBean.selectedHome}">
<f:selectItems value="#{selectBackingBean.teams}" var="team"
itemValue="#{team}" itemLabel="#{team}" />
</p:selectOneMenu>
<p:selectOneMenu id="away" label="Please select away..." value="#{selectBackingBean.selectedAway}">
<f:selectItems value="#{selectBackingBean.teams}" var="team"
itemValue="#{team}" itemLabel="#{team}" />
</p:selectOneMenu>
<h:panelGroup>
<br/>
<h:message for="validateMatch" />
<h:outputText value="OK!" rendered="#{facesContext.postback and not facesContext.validationFailed}" />
</h:panelGroup>
<br/><br/>
<p:commandButton value="Save" action="#{selectBackingBean.onSave}" update="#form" />
</h:form>
</h:body>
</html>
Let's also define the backing bean holding the view state and the model:
#Data
#Named
#ViewScoped
public class SelectBackingBean implements Serializable {
private List<String> teams;
private List<Match> matches;
private String selectedHome;
private String selectedAway;
#Data
#AllArgsConstructor
public class Match {
private String home;
private String away;
}
#PostConstruct
private void init() {
teams = Arrays.asList("Malmö FF", "GAIS", "IFK Göteborg", "AIK");
matches = new ArrayList<>(Arrays.asList(new Match("Malmö FF", "AIK")));
}
public boolean onValidateMatch(FacesContext context, List<UIInput> components,
List<String> values) {
return values.get(0) != values.get(1) && !matches.contains(new Match(values.get(0), values.get(1)));
}
public void onSave() {
matches.add(new Match(selectedHome, selectedAway));
}
}
If we now run this we get the following results:
If we keep going and eventually run into a validation error we get the following:
Notice how it constantly retains the state of all components throghout. There are components in OmniFaces and JSF that allow you to control the state of selected parts of the component tree to get away from this behavior.
I'm guessing there is something going on in your code or that you are somehow resetting the backing bean values connected to the components.
Is there a way to postpone a keyup ajax request until a h:inputText value has reached a defined length?
I would like to reach the following goal:
a textInput field has to be filled with a combined date and time value. The expected format is: ddMMHHmm
Once the value reaches the length of 8 characters a new event object has to be added to an data list and should be displayed for confirmation immediately.
To confirm to add the new event the user simply presses enter inside this textInput field.
I don't know if there are different capabilities than using the ajax keyUp event to validate the input wihtout any further user interaction?
Here you see an very shortened example of my idea:
#Named
#SessionScoped
public class EventController {
private Date selectedDate; // +getter/+setter
private MyEvent event;
private List<MyEvent> events; // ArrayList<MyEvent>(), +getter
#PostConstruct
private void init() {
// load current events from DAO
}
public void validateInput() {
event = new MyEvent(selectedDate);
events.add(event);
}
public void confirmEvent() {
eventDAO.addEvent(event);
}
And the view:
<h:inputText
value="#{eventController.selectedDate}"
converter="#{comfortableDateTimeInputConverter}"
id="inputDateTime">
<f:ajax
<!-- pseudo code on !!! -->
executeCondition="<lengthOfInputField equals 8>"
<!-- pseudo code off !!! -->
execute="inputDateTime"
render="eventData"
event="keyup"
listener="#{eventController.validateInput}"
/>
</h:inputText>
<h:commandButton ... actionListener="#{eventController.confirmEvent}" />
<h:panelGroup id="eventData">
<h:dataTable var="..." value="#{eventController.events}">
// display event properties
</h:dataTable>
</h:panelGroup>
The ComfortableDateTimeInputConverter extracts the date an time parts of the input string and returns an date object.
I am using
primefaces 5.2
mojarra 2.2.8
Edit 1
As suggested by BalusC I modified my h:inputText, but nothing seems to happen. This is my original code exept the controller name. I've added a logging message inside eventController.validateNewEvent, but it seems not to be executed. Did I miss something?
<h:inputText
readonly="#{empty eventController.selectedPerson}"
value="#{eventController.selectedDate}"
id="inputDateTime"
tabindex="3"
converter="#{comfortableDateTimeInputConverter}"
onkeyup="return value.length >= 8"
onfocus="this.select()">
<f:ajax
event="keyup"
execute="inputDateTime"
listener="#{eventController.validateNewEvent}"
render="selectedDate txtDate listEvents" />
</h:inputText>
Also I tried to render="#all" at the ajax element, but still nothing happens. If i use event="blur" and leave the input with TAB it works like a charme ...
Edit 2 (resolved)
Replaced
onkeyup="return value.length >= 8"
with
onkeyup="return this.value.length >= 8"
and it works. See answer of BalusC ...
Just return false from onkeyup as long as value.length hasn't reached the desired value.
E.g.
<h:inputText ... onkeyup="return this.value.length >= 8">
<f:ajax event="keyup" ... />
</h:inputText>
I'm trying to use a p:ajax tag and then in that listener, i'm setting a value called "periodRendered". then i'm trying to update an h:outputLabel tag via an update from the p:ajax tag. It's not updating ajaxily and i'm thinking it's because a primefaces ajax tag can't update a standard jsf outputLabel tag.
Is my assumption correct and is there a more appropriate tag i should be using instead of h:outputLabel ?
<h:outputLabel for="addProgramTo" value="Add Program To" />
<p:selectOneMenu value="#{ppBacker.grantProgram.grant_project_id}" id="addProgramTo" size="1" styleClass="listBoxMedium">
<p:ajax process=":addProgram:addProgramTo" update=":addProgram:periodGrid, :addProgram:periodLabel" event="change" listener="#{ppBacker.addProgramListener}" />
<f:selectItems value="#{ppBacker.grantProjectDropDownList}" />
</p:selectOneMenu>
<h:outputLabel for="period" value="Period" id="periodLabel" rendered="#{ppBacker.periodRendered}">
You cannot update elements which are not rendered , rendered=false "is a JSF way to" to remove elements from the DOM Tree ,
its not like css display:none or visibility:hidden <- this two will keep the elements in the DOM tree but hidden , while the JSF rendered=false wont even render (keep) the element in the DOM tree (you wont even see it in the "view source" of the page)
So in you case you need to wrap the outputLabel with `panelGroup' and update the id of the wrapper
<h:panelGroup id="periodLabelWrapper">
<h:outputLabel for="period" value="Period" id="periodLabel" rendered="#{ppBacker.periodRendered}">
</h:panelGroup>
and refer to the wrapper (which always be in the DOM tree) id in the <p:ajax update attribute, like this:
<p:ajax process=":addProgram:addProgramTo" update=":addProgram:periodGrid, :addProgram:periodLabelWrapper" event="change" listener="#{ppBacker.addProgramListener}" />
Another solution would be the update the entire form like this <p:ajax update="#form" ... that way you don't need the wrap the outputLabel
regarding your comment question
how does #form update the un-rendered elements, but targeting them directly through id's does not?
You can't target an element in update which is not present in the page (rendered=false) "its not there"
But when you use update="#form" or update="someWrapperID" the form/wrapper "re evaluates" all its inner elements , including the ones with rendered="#{someBean.someCondition}" and the condition gets "re evaluated" so if it result to true the elemet will be displayed...
I'm not sure the reason for this behavior, but I'm putting money on the fact that p:ajax is going to require an active target for the update to work. Here's an example of it working/not working:
<h:body styleClass="center">
<f:view contentType="text/html">
<h:form id="test">
<h:outputLabel for="addProgramTo" value="Add Program To: " />
<h:selectOneMenu id="addProgramTo" value="#{testScope.target}">
<p:ajax update=":test:periodLabel, :test:wrapper" event="change" process="#form">
</p:ajax>
<f:selectItems value="#{testScope.values}"/>
</h:selectOneMenu>
<hr />
<p:outputPanel id="wrapper">
<h:outputLabel value="Works, has a target" rendered="#{testScope.timeToRender}"/>
</p:outputPanel>
<hr />
<h:outputLabel value="does not work" id="periodLabel" rendered="#{testScope.timeToRender}" />
</h:form>
</f:view>
</h:body>
//Bean...
#ViewScoped
#ManagedBean(name = "testScope")
public class TestScope
{
private String[] values = { "False", "What?", "True" };
private String target = "Nothing";
/**
* #return the target
*/
public String getTarget()
{
return target;
}
/**
* #return the values
*/
public String[] getValues()
{
System.out.println(values);
return values;
}
public boolean isTimeToRender()
{
return "True".equals(target);
}
/**
* #param target the target to set
*/
public void setTarget(String target)
{
this.target = target;
}
}
I don't have the time to dig into this one, but from a quick look (again QUICK) in the debugger it looks like it doesn't find the ID (it isn't rendered) so it doesn't get the update. Why? I'm not sure--the easy workaround is to place it inside something you can update (p:outputPanel) and render it as a span, then your code should work. Is this acceptable? Not really...
If you can deal with a full form update the following will work as well ->
<p:ajax update=":test" event="change" process="#form">
Since you're targeting the enclosing form it updates all the children. If I was doing this, that is the workaround I'd use. It isn't a lot of data and it is a heck of a lot cleaner. I'm not sure if this a bug or a feature. It feels buggy though.
Short answer: yes you can target a label. Long answer: you can't target one that isn't rendered. I really hope this helps.
I have an Edit product form which is pre-populated with values from DB . User can change one or more values and post the form back. One input field called t:inputFileUpload is rendered only after an ajax request ,if the user opts to change product image.During the final postback of the edit form through a save button,the bean is not updated with the value of t:inputFileUpload field.The relevant portion of the form is below:
<h:form>
<tr>
<td>Product Image*:</td>
<td>
<h:graphicImage url="#{addItem.prodFileName}" width="100" height="100"/>
<br /><h:commandLink value="change image" >
<f:ajax render="uploadimage" execute="#this" listener="#{addItem.ChangeImage}"/>
</h:commandLink>
</td>
</tr>
<tr >
<td>
<h:panelGroup id="uploadimage">
<t:inputFileUpload rendered="#{addItem.editImgChange}" label="editImage" value="#{addItem.uploadedFile}" />
<h:messages for="prodimage"/>
<h:inputHidden id="hiddeneditimgchange" value="#{addItem.editImgChange}" />
</h:panelGroup>
</td>
</tr>
<h:commandButton value="save" action="#{addItem.EditItem}" />
</h:form>
The AddItem bean is request scoped and the relevant part of its code is :
#ManagedBean
public class AddItem extends AbstractBean{
boolean editImgChange;
private UploadedFile uploadedFile;
//..
//getters and setters
public void ChangeImage(){
this.editImgChange=true;
}
public String EditItem() {
//some logic
}
}
I have read a few similar questions some of whose answers were to make the bean viewscoped.I have tried making the bean ViewScoped ,but it breaks my initial logic of prepopulating the form values. Since i am happy with RequestScoped , I have saved the state of the editImgChange flag ,if its turning off is affecting the updation of the t:inputFileUpload . When i looked at the bean properties all is fine ,the flag is true , but the uploadedFile property is null.
As per the comments, you used <h:inputHidden value="#{addItem.editImgChange}" /> to save the state. This is not going to work. The rendered attribute is evaluated during apply request values phase, while that hidden value is made available during update model values phase which is thus too late.
Since you're already using Tomahawk, use <t:saveState value="#{addItem.editImgChange}" /> instead. Or, just fix the problem which you encountered when making the bean view scoped. I don't forsee why that would be a problem. Perhaps you're using #PostConstruct and expecting that it's invoked on every request. You should then use <f:event type="preRenderView"> instead.
The problem is, that if a property is changed during an f:ajax request and a binded panelGroup should be newly created depending on that changed value, the old value is used.
This code will explain the problem.
Here is the backingbean TestBean:
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public String getName(){
return first+" "+last;
}
public void setDynamicPanel(HtmlPanelGroup panel){ }
public HtmlPanelGroup getDynamicPanel(){
Application app = FacesContext.getCurrentInstance().getApplication();
HtmlPanelGroup component = (HtmlPanelGroup)app.createComponent(HtmlPanelGroup.COMPONENT_TYPE);
HtmlOutputLabel label1 = (HtmlOutputLabel)app.createComponent(HtmlOutputLabel.COMPONENT_TYPE);
label1.setValue(" --> "+getFirst()+" "+getLast());
component.getChildren().add(label1);
return component;
}
and now the jsf/facelet code:
<h:form id="form">
<h:panelGrid columns="1">
<h:inputText id="first" value="#{testBean.first}" />
<h:inputText id="last" value="#{testBean.last}" />
<h:commandButton value="Show">
<f:ajax execute="first last" render="name dyn" />
</h:commandButton>
</h:panelGrid>
<h:outputText id="name" value="#{testBean.name}" />
<h:panelGroup id="dyn" binding="#{testBean.dynamicPanel}" />
</h:form>
After the page was initially loaded the outputText and panelGroup shows both "null" as first and last. But after the button is pressed, the outputText is updated well, but the the panelgroup shows again only "null". This is due to the problem, that the "binded method" dynamicPanel is executed before the update of the first and last properties.
how can workaround this behaviour or what is wrong with my code?
If you add the attribute immediate="true" to your input elements, the values will be applied during the "Apply Request Values" phase, and hence be present before your action executes. You may or may not need the immediate attribute set to true on the commandButton as well.