View scope reset fields to default after performing an action - spring

First of all, my beans are managed by spring not by JSF and I am using custom view scope as described in this article. So if the behavior is weird for regular JSF2 and might be related to Spring, please tell me.
Bean:
public class DepartmentBean {
private DefaultTreeModel model;
public void preRender(ComponentSystemEvent event) throws Exception {
if (model == null) {
model = myService.buildModel();
}
}
public String clear() {
// resetting stuff
return "pretty:";
}
}
View:
<h:form>
<ice:panelGroup styleClass="crud-links">
<h:commandLink value="Delete" action="#{department.deleteDepartment}" />
</ice:panelGroup>
</h:form>
<h:form>
<ice:panelGroup>
<ice:tree id="tree" value="#{department.model}" var="item" hideRootNode="false" hideNavigation="false" imageDir="./xmlhttp/css/xp/css-images/">
<ice:treeNode>
<f:facet name="content">
<ice:panelGroup style="display: inline">
<ice:commandLink value="#{item.userObject.text}"></ice:commandLink>
</ice:panelGroup>
</f:facet>
</ice:treeNode>
</ice:tree>
</ice:panelGroup>
</h:form>
When page is loaded for first time the model object is populated with data, but when clicking delete button I notice that after clearing preRender() method is executed and the model (which was populated before clearing becomes null, and gets populated again, although I am in same page, and it should maintain the value)
Does the code have a problem that leads to such behavior, or this is the normal behavior?
If the problem maybe related to Spring or the custom view scope or the IceFaces, please advise.
UPDATE- REQUIREMENT:
I want to initialize the tree model on construction of the page, and while i am still on the page the tree model doesn't gets initialized again until i do that programatically .

oh my mistake, initialization should be inside #PostConstruct.

Related

OmniFaces <o:validateMultiple> resetting two PrimeFaces <p:selectOneMenu> after validation failed

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.

Primefaces p:cache

I'm having some problem with Primefaces p:cache component
This is example how its used on testpage/index.xhtml
<h:form>
<p:panel header="Testsite">
<p:cache region="testsite2"
key="testsite2#{user.id}#{user.defaultLanguage}">
<p:commandButton action="#{testBean.hello}" value="btn"
rendered="#{testBean.renderedButton}">
</p:commandButton>
</p:cache>
</p:panel>
</h:form>
and this is back end bean
#ManagedBean(name = "testBean")
#ViewScoped
public class TestBean {
#PostConstruct
public void init() {
System.out.println("init");
}
public void hello() {
System.out.println("hello");
}
public boolean isRenderedButton() {
System.out.println("isRenderedButton");
return true;
}
}
So on first page hit init and isRenderedButton message are printed normally as expected. After that when I click on button I do expect to see hello message printed, but that's not case here. Can anyone point me in right direction ?
According to Primefaces showcase for p:cache with buttons I was expecting this behavior.
Right now I am using Primefaces.DEFAULT_CHACHE_PROVIDER and later I will switch to ehcache.
I'm using PF 5.3, sun faces 2.2.12.
Thanks.
To answer myself (and maybe ill help someone), i was trying to create dynamic menu from database and i wanted to cache generated content with p:cache component. But back then every menu item would call bean method which would redirect user to page and that was problem in first place.
So i had something like this:
<p:menu>
<p:submenu label="Human resource">
<p:menuitem value="Search person" actionListener="#{bean.navigateUserToSearchPerson}"/>
</p:submenu>
I actually did not fix this problem (had no extra time to investigate problem), so i came up with idea to generate links for each menu item, so when user clicks on menu item, it would redirect him to new page. So now i don't have any AJAX calls in menu and caching works fine now.
Code example:
<p:cache region="appUiCache" key="panelMenu#{user.id}#{user.defaultLanguage}">
<p:panelMenu id="sm" model="#{bean.menuModel}" stateful="true" />
</p:cache>
Menu items are created dynamically from database:
DefaultMenuItem defaultMenuItem = new DefaultMenuItem(...);
defaultMenuItem.setIcon(item.getIcon());
defaultMenuItem.setUrl(item.getUrl()); <!-- This is new url part -->
This works fine now in production. Thanks.

How to send data to a faces-flow?

My use case: the user choose a questionnaire in a form. When the form is submitted, a faces-flow is started to display the questions of the questionnaire.
To send the questionnaire to the flow, in the bean of the flow I inject the CDI bean of the page which contains the form.
I wonder if there are other ways to send the questionnaire to the flow. If there are several ways, what's the best one?
You can pass parameters via the form and get them in the initializer method called at the initialization of your flow.
Form (just replace the inputHidden parameter with whatever you're using to select your questionnaire)
<h:form id="myForm" prependId="false">
<h:commandLink value="Enter myFlow" action="my-flow"/>
<h:inputHidden id="parameter" name="parameter" value="8"/>
</h:form>
Flow
#Produces #FlowDefinition
public Flow defineFlow(#FlowBuilderParameter FlowBuilder flowBuilder) {
String flowId = "my-flow";
flowBuilder.id("", flowId);
flowBuilder.initializer("#{myFlowBean.startFlow()}");
...
}
Backing bean
#Named
#FlowScoped("my-flow")
public class MyFlowBean implements Serializable {
public void startFlow() {
String parameter = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("parameter");
//now do sthg with the parameter, such as fetching the questionnaire
....
}
}
See this answer for more details
Addition to "thomas.g"s helpful answer:
I had the same problem, but could not fix it with the hiddenInput approach. Despite the prependId="false" attribute my id and name of the hidden input field got changed by the primefaces p:dataTable element I used. The issue could be fixed with the f:param elemtent inside the h:commandLink element:
<h:commandLink value="Enter myFlow" action="my-flow" >
<f:param name="parameter" value="8"/>
</h:commandLink>
I hope this might be helpfull to someone with a similar problem.
This can be done in the flow xml file using the initializer tag
<initializer>
#{myFlowBean.startFlow()}
</initializer>
to call the your initialize method in the flow scoped bean

JSF 2: page redirect after ajax call

I'm stuck in a navigation case problem similar to this one.
In a few words, I'm trying to redirect navigation from one page to another, using an ajax rendered h:commandLink.
Here's the backing bean
#ManagedBean
public class StartBean {
public void search(){
FacesContext
.getCurrentInstance()
.getExternalContext()
.getFlash()
.put("result", "hooray!")
;
}
public String showResult(){
return "result?faces-redirect=true";
}
}
and the starting page
<h:body>
<h:form prependId="false">
<h:commandButton value="Click" action="#{startBean.search}">
<f:ajax execute="#this" render="#form"/>
</h:commandButton>
<br/>
<h:commandLink
action="#{startBean.showResult()}"
rendered="#{flash.result != null}"
value="#{flash.result}"
/>
</h:form>
</h:body>
whereas result page is just showing a message. Both pages are on web module context root.
It happens that the h:commandLink is correctly displayed after ajax submit, but clicking on it causes a page refresh. It doesn't redirect towards the result page, as expected.
After it, if page is reloaded (F5), result page is shown. It seems to be a rendering cycle matter.
Any suggestion?
Thanks in advance.
The rendered attribute of all input and command components is re-evaluated when the form is submitted. So if it evaluates false, then JSF simply won't invoke the action. The Flash scope is terminated when the request/response of the search() method is finished. It isn't there in the Flash scope anymore when you send the request of the showResult(). I suggest to put the bean in the view scope and bind the rendered attribute to its property instead.
#ManagedBean
#ViewScoped
public class StartBean {
private String result;
public void search(){
result = "hooray";
}
public String showResult(){
return "result?faces-redirect=true";
}
public String getResult() {
return result;
}
}
with
<h:commandLink
action="#{startBean.showResult}"
rendered="#{startBean.result != null}"
value="#{startBean.result}"
/>
See also:
commandButton/commandLink/ajax action/listener method not invoked or input value not updated

h:inputText inside ui:repeater displays wrong value after an ajax update

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>

Resources