I need a method to be executed whenever my component gets rendered, i.e. when it gets created for the first time and on every update.
A way of doing it is using f:metadata and f:event for the event "preRenderComponent". It works perfectly well, unless I also have a f:metadata with f:viewParam in the view holding the component. If I have a f:viewParam in the parent view, f:event, in the component, gets ignored.
How to solve this problem, why does it happen?
Here's some code to reproduce the problem:
The view (index.xhtml):
<h:body>
<f:metadata>
<f:viewParam name="vparam" value="#{index.vparam}" />
</f:metadata>
<comp:component />
</h:body>
The managedBean (Index.java)
#ManagedBean(name="index")
public class Index implements Serializable {
private String vparam;
public void setVparam(String vparam) {
this.vparam = vparam;
}
public String getVparam() {
return vparam;
}
}
The component view (component.xhtml):
<h:body>
<composite:interface componentType="component" />
<composite:implementation>
<f:metadata>
<f:event type="preRenderComponent" listener="#{cc.generateString}" />
</f:metadata>
<h:outputText value="#{cc.string}" />
</composite:implementation>
</h:body>
The component's java class (Component.java):
#FacesComponent(value="component")
public class Component extends UINamingContainer implements Serializable {
private String string;
public void generateString(){
System.out.println("*** PRE RENDERING COMPONENT...");
System.out.flush();
string = "It worked!";
}
public String getString(){
return string;
}
}
The expected output of this code is: the string "It worked!" printed in the index page.
The actual output is: nothing, since a null string is printed as "" by the h:outputText.
In the log file, "*** PRE RENDERING COMPONENT..." is not printed, it's never executed.
If I remove the following line from index.xhtml, everything works fine:
<f:viewParam name="vparam" value="#{index.vparam}" />
Why does the presence of "viewParam" in the component's parent page makes JSF to ignore "f:event" in the component?
Thanks in advance for any answer.
As per the <f:metadata> documentation, the view can have only one <f:metadata> which needs to go in template client (the top level XHTML page which is been opened upon HTTP request).
The <f:metadata> in the composite is displaced. It doesn't belong there. Get rid of it. The <f:event> does by itself not require to be placed inside <f:metadata>. This works as intented:
<composite:implementation>
<f:event type="preRenderComponent" listener="#{cc.generateString}" />
...
</composite:implementation>
Your concrete problem is caused because the second <f:metadata> is ignored altogether including all of its children.
True, you're seeing a lot of <f:metadata><f:viewParam><f:event type="preRenderView"> examples, but the <f:event> is in this specific case actually a workaround and just for self-documentary purposes placed inside the very same <f:metadata> as the <f:viewParam>s it needs to work with. In JSF 2.2, the right way has been introduced in flavor of <f:viewAction>. See also What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
Related
I have a big form that I need to reuse in multiple pages. So, I decided to create a
<ui:composition> that contains the form and include it in some pages (page1.xhtml and page2.xhtml).
form.xhtml:
<ui:composition ...>
<!-- The form goes here -->
</ui:composition>
This form has a controller called FormController.
In page1.xhtml and page2.xhtml I just include the form using a <ui:include> tag:
<ui:include src="/WEB-INF/.../form.xhtml"/>
I need to initialize a property in the FormController bean, so, in page1.xhtml I decided to set an attribute with the Id that I need (for example 5):
<c:set var="id" scope="request" value ="5"/>
And in the controller I just get the value of this attribute:
#PostConstruct
public init() {
Long id = ((HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).getAttribute("id");
//Do some queries to the database
}
Until know, everything works fine. But in page2.xhtml the "initialization" of the bean property has to be done after an ajax request, so I used the following code:
<h:selectOneMenu ...>
<f:selectItems ...>
<f:ajax listener="#{otherBean.doSomething}" render="panel"/>
</h:selectOneMenu>
<h:panelGroup id="panel">
<c:set var="id" scope="request" value ="#{otherBean.id}"/>
<ui:include src="/WEB-INF/.../form.xhtml"/>
</h:panelGroup>
What is weird is that this works just the first time I select an element in the <h:selectOneMenu>. The second time, the doSomething() method is called but the panel is not rendered (I don't know why, you know why?), so I decided to explore the following alternative that works well in both pages, but I feel that it isn't a good solution:
#{bean.init(otherBean.id)}
<ui:include src="/WEB-INF/modules/company/company.xhtml"/>
As you see, I am just calling an init method (before the <ui:include>) with the argument I need. In the controller I just set the property and do the corresponding queries:
public init(Long id) {
this.id = id;
//Do some queries
}
What do you thing about this solution?
If the form has to be initialized at start, you can use
<f:metadata>
<f:viewAction action="#{otherBean.initSomething('MYID2')}"/>
</f:metadata>
If the form has to be initialized by an action
<h:commandButton action='#{otherBean.doSomething('MYID1')}'...>
or
<f:ajax listener="#{otherBean.doSomething('MYID')}" .../>
I have a simple Form in my JSF page that looks as follows:
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<f:view>
<h:head>
<f:metadata>
<f:viewParam name="cities" value="#{myBean.cities}" />
</f:metadata>
</h:head>
<h:body>
<h:form>
<h:messages id="messages" />
<h:selectManyCheckbox value="#{myBean.cities}" label="Select City" >
<f:selectItems value="#{myBean.allCities}" />
</p:selectManyCheckbox>
<h:commandButton value="Submit">
<f:ajax execute="#form" render="output"/>
</h:commandButton>
</h:form>
<h:panelGroup id="output">
Number of Selected Cities: #{myBean.cities.size()}
</h:panelGroup>
</h:body>
</f:view>
</html>
The matching backing bean:
With following bean methods:
#Named
#RequestScoped
public class MyBean {
private List<String> cities = new ArrayList<>();
public List<String> getCities() {
return cities;
}
public void setCities(List<String> cities) {
this.cities = cities;
}
public List<String> getAllCities() {
return new ArrayList<>(Arrays.asList(new String [] {
"Los Angeles",
"St. Louis",
"San Francisco",
"Dallas",
}));
}
}
Observations:
I have added logging to the entry and exit of the getCities and setCities methods. During refresh of the JSF page, the getCities method gets called. However, when submitting, the setCities never gets called.
I do not get any errors or exceptions in the console log (javax.faces.PROJECT_STAGE set to DEVELOPMENT in web.xml).
No errors are being passed to <h:messages/>
set methods do not get called for any form input fields. The page behaves 'odd'.
The issue seems similar to Issue #3 on the accepted answer from commandButton/commandLink/ajax action/listener method not invoked or input value not set/updated, however I do not have an apparent conversion problem for <p:selectCheckboxMenu>. As per documentation it should be able to handle a List<String> perfectly.
So in addition to fixing the obvious problem, how can we make sure any errors related to this are made visible, rather than having this silent type of failure?
In short, <f:viewParam> does not yet support list of values.
The signature of the cities property is List<String>.
Note that when we are trying to pass a parameter for cities (append ?cities=Dallas to the end of the request URL), we suddenly do get an appropriate conversion error in <h:messages/> stating:
Conversion Error setting value 'Dallas' for 'null Converter'.
Also as per referenced posts, we should also include the messages for ajax updates:
<h:commandButton value="Submit">
<f:ajax execute="#form" render="output"/>
<f:ajax execute="#form" render="messages"/>
</h:commandButton>
When following this, not during first visit, but at least during submit we get an appropriate error:
Conversion Error setting value '[]' for 'null Converter'.
I am not sure why the <f:viewParam>'s need to be set during an ajax-style submit though?
To solve, you can either provide a <f:converter>, or provide additional getter/setters that handles setting/getting the Array or List type based on a String.
Picking a quick solution we can change the <f:viewParam> as follows:
<f:viewParam name="cities" value="#{myBean.citiesCsv}" />
While for the backing bean, we add following method:
public void setCitiesCsv(String csv) {
if (csv.isEmpty()) {
cities = new ArrayList<>();
} else {
cities =
Stream.of(csv.split(","))
.collect(Collectors.toCollection(ArrayList<String>::new));
}
}
For repeated such efforts, we should probably consider an appropriate converter to be used instead, which we can custom build for simple CSV conversions if it suits our purpose.
Related posts
commandButton/commandLink/ajax action/listener method not invoked or input value not set/updated (Item #3)
Distinguish between conversion failure and validation failure in o:viewParamValidationFailed
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
I have 2 views (a.xhtml and b.xhtml) one of which contains a link to the other. The first view:
uses the current view map by setting some value to it;
points to b.xhtml with h:link using includeViewParams="true" in order to automatically include view parameters in the link's query string.
a.xhtml:
<f:view >
<f:metadata>
<f:viewAction>
<!-- just set any value to force view map creation... -->
<f:setPropertyActionListener target="#{viewScope.username}" value="John" />
</f:viewAction>
</f:metadata>
<h:link id="alink" value="Go to B" outcome="b" includeViewParams="true" />
<h:form>
<h:commandButton id="away" action="b" value="Navigate away" immediate="false" />
</h:form>
</f:view>
</html>
and b.xhtml:
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<f:view >
<f:metadata>
<f:viewParam id="id" name="userid" value="1" />
</f:metadata>
</f:view>
</html>
Also I am creating here a ViewMapListener in order to demonstrate 'spurious' view map destruction event calls that happen once a.xhtml gets visited. In my faces-config.xml I'm having this entry:
<system-event-listener>
<system-event-listener-class>org.my.TestViewMapListener</system-event-listener-class>
<system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
<source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>
where TestViewMapListener is like this:
public class TestViewMapListener implements ViewMapListener {
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
if (event instanceof PreDestroyViewMapEvent) {
PreDestroyViewMapEvent viewMapEvent = (PreDestroyViewMapEvent)event;
UIViewRoot viewRoot = (UIViewRoot)viewMapEvent.getComponent();
System.out.println("PreDestroyViewMapEvent: "+viewRoot.getViewId());
}
}
...
Once the page a.xhtml is rendered, the listener prints out the following line:
PreDestroyViewMapEvent: /b.xhtml
which is strange because b.xhtml has never been visited. When I navigate away with "Navigate away" button a correct event is printed as expected:
PreDestroyViewMapEvent: /a.xhtml
The incorrect event is fired only if I am using includeViewParams="true" on the link. By debugging, I can see that it happens because com.sun.faces.application.view.ViewMetadataImpl.createMetadataView(FacesContext) is temporarily setting to FacesContext a UIViewRoot for b.xhtml where a shallow copy of the original view map is created and set to temporary view root. This is probably done in order to correctly detect values of query string parameters for the link; it also temporarily turns off events for the time of the manipulations however it turns them back on too early (see 'finally' block), so view map destruction event is 'incorrectly' fired for a temporary copy of the view map, while no events for the original view map itself are expected at this time. It's a headache because I need to take some additional actions in order to detect whether it is the original map is destroyed or it is a spurious event for its 'ghost'.
Is this a bug or a desired behavior? I am using Mojarra 2.2.12.
I have the following XHTML:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>TODO supply a title</title>
</head>
<body>
<f:metadata>
<f:viewParam id="productCV" name="productName" value="#{productBean.product}"
converter="#{productConverter}" required="true"/>
</f:metadata>
<ui:composition template="/templates/mastertemplate.xhtml">
<!-- Define the page title for this page-->
<ui:define name="pageTitle">
<h:outputFormat value="#{msgs.productPageTitle}">
<f:param value="#{productBean.product.description}"/>
</h:outputFormat>
</ui:define>
<!-- Pass the categoryName parameter to the sidebar so the category of this product is highlighted-->
<ui:param name="categoryName" value="#{productBean.product.categoryName}"/>
<ui:define name="content">
<!-- If productconversion failed, show this error-->
<h:message id="error" for="productCV" style="color: #0081c2;" rendered="#{productBean.product == null}" />
<!-- If productconversion succeeded show the product page-->
<h:panelGroup rendered="#{productBean.product != null}">
<p>#{productBean.product.description} #{productBean.product.categoryName}</p>
<h:form>
<h:commandLink action="#{cartBean.addItemToCart(productBean.product)}">
<f:ajax event="action" render=":cart :cartPrice" />
<h:graphicImage value="resources/img/addToCart.gif"/>
</h:commandLink>
</h:form>
</h:panelGroup>
</ui:define>
</ui:composition>
</body>
</html>
At the top I accept a String as GET param which I run through a converter and then get a Product object, I place this in the productBean.product, that bean has a setter and getter for the Product attribute, that's all.
I then use this object to show info etc. this works fine. I also add commandLink to add it to my cart using AJAX. This refuses to work if my ProductBean is in RequestScope, when I put it in SessionScope it works, but will only add the product 1 time.
As best I know this should be a straight forward RequestScope, I don't understand why it does work with SessionScope.
I have read through this post but I don't think I'm violating any of those rules.
For completeness, this is my ProductBean:
import be.kdg.shop.model.stock.Product;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
#Named
#RequestScoped
public class ProductBean {
private static final Logger logger = Logger.getLogger(ProductBean.class.getName());
private Product product;
public ProductBean() {}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
Your bean is request scoped. So the bean instance lives as long as a single HTTP request-response cycle.
When the page with the form is requested for the first time, a new bean instance is created which receives a concrete product property as view parameter. After generating and sending the associated response, the bean instance is garbaged, because it's the end of the request.
When the form is submitted, effectively a new HTTP request is fired and thus a new bean instance is created with all properties set to default, including the product property. This way #{productBean.product} is null for the entire request. The rendered attribute of a parent component of the command link will evaluate false. The command link action is therefore never decoded. This matches point 5 of commandButton/commandLink/ajax action/listener method not invoked or input value not updated which you already found, but apparently didn't really understood.
The solution is to put the bean in the view scope. A view scoped bean lives as long as you're interacting (submitting/postbacking) with the same JSF view. Standard JSF offers #ViewScoped for this. As you're using CDI instead of JSF to manage beans, your best bet is the CDI #ConversationScoped. This is relatively clumsy (you've to start and end the scope yourself), so some CDI extension such as MyFaces CODI which offers a #ViewAccessScoped may be more useful.
See also:
How to choose the right bean scope?
This question already has answers here:
Retaining GET request query string parameters on JSF form submit
(2 answers)
Closed 6 years ago.
I'm facing the following issue: in one page, I list all users of my application and have an "edit" button for each one, which is a "GET" link with ?id=<userid>.
The edit page has a <f:viewParam name="id" value="#{editUserBean.id}"/> in metadata.
If I made some input mistakes and submit (I use CDI Weld Bean validation), the page is displayed again, but I've lost the ?id=... in the URL and so lose the user id of the user I'm editing.
I've looked at a similar problem described in JSF validation error, lost value, but the solution with inputhidden (or worse, with tomahawk, which looks overkill) requires lot of uggly code.
I've tried adding a "Conversation" with CDI, and it is working, but it looks like too much overkill to me again.
Does there exists a simple solution in JSF to preserve view parameters in case of validation errors?
[My environment: Tomcat7 + MyFaces 2.1.0 + Hibernate Validator 4.2.0 + CDI(Weld) 1.1.2]
Interesting case. For everyone, the following minimal code reproduces this:
Facelet:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<f:metadata>
<f:viewParam id="id" name="id" value="#{viewParamBean.id}"/>
</f:metadata>
<h:body>
<h:messages />
#{viewParamBean.id} <br/>
<h:form>
<h:inputText value="#{viewParamBean.text}" >
<f:validateLength minimum="2"/>
</h:inputText>
<h:commandButton value="test" action="#{viewParamBean.actionMethod}"/>
</h:form>
</h:body>
</html>
Bean:
#ManagedBean
#RequestScoped
public class ViewParamBean {
private long id;
private String text;
public void actionMethod() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
If you call the Facelet with viewparam.xhtml?id=12 it will display the 12 onscreen. If you then input something valid, e.g. aaaaa, the id will disappear from the URL, but keeps being displayed on screen (owning to the stateful nature of ui components).
However... as OP mentioned, as soon as any validator error occurs (e.g. entering a), the id will be permanently lost. Entering valid input afterwards will not bring it back. It almost seems like a bug, but I tried both Mojarra 2.1 and Myfaces 2.1 and both have the same behavior.
Update:
After some inspection, the problem seems to be in this method of `UIViewParameter' (Mojarra):
public void encodeAll(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
// if there is a value expression, update view parameter w/ latest value after render
// QUESTION is it okay that a null string value may be suppressing the view parameter value?
// ANSWER: I'm not sure.
setSubmittedValue(getStringValue(context));
}
And then more specifically this method:
public String getStringValue(FacesContext context) {
String result = null;
if (hasValueExpression()) {
result = getStringValueFromModel(context);
} else {
result = (null != rawValue) ? rawValue : (String) getValue();
}
return result;
}
Because hasValueExpression() is true, it will try to get the value from the model (the backing bean). But since this bean was request scoped it will not have any value for this request, since validation has just failed and thus no value has ever been set. In effect, the stateful value of UIViewParameter is overwritten by whatever the backing bean returns as a default (typically null, but it depends on your bean of course).
One workaround is to make your bean #ViewScoped, which is often a better scope anyway (I assume you use the parameter to get a user from a Service, and it's perhaps unnecessary to do that over and over again at every postback).
Another alternative is to create your own version of UIViewParameter that doesn't try to get the value from the model if validation has failed (as basically all other UIInput components do).
You don't actually loose the view parameter. f:viewParam is stateful, so even if it's not in the URL, it's still there. Just put a break point or system.out in the setter bound to view param.
(if you google on viewParam stateless stateful you'll find some more info)
I've the same in my Application. I switched to #ViewAccessScoped which allows way more elegant implementations.
<f:metadata>
<f:viewParam id="id" name="id" value="#{baen.id}"/>
</f:metadata>
Or when you the first time get parameter from url, save it in session map and continue use from that map, and after save/or update the form clean map.
This is tricky, but you can try to restore view parameters with History API:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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">
<f:metadata >
<f:viewParam name="param1" value="#{backingBean.viewParam1}" />
<f:viewParam name="param2" value="#{backingBean.viewParam2}" />
<f:viewAction action="#{view.viewMap.put('queryString', request.queryString)}" />
</f:metadata>
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<ui:fragment rendered="#{facesContext.postback}" >
<script type="text/javascript">
var url = '?#{view.viewMap.get('queryString')}';
history.replaceState({}, document.title, url);
</script>
</ui:fragment>
<h:form>
<h:inputText id="name" value="#{backingBean.name}" />
<h:message for="name" style="color: red" />
<br />
<h:commandButton value="go" action="#{backingBean.go}" />
</h:form>
<h:messages globalOnly="true" />
</h:body>
</html>