f:param does not work with p:commandLink or h:commandLink on query string - jsf

f:param works great with h:link, but not with p:commandLink or h:commandLink.
For example, I have two pages test_first.xhtml and test_second.xhtml, and a backing java bean TestBean.java.
I start running test_first.xhtml.
If I click link1, which is a h:link, the page will redirect to test_second.xhtml. With the help of f:param, the address bar of the browser will show .../test_second.xhtml?id=1. On that page, testBean.userId gets printed.
If I click link2 or link3, the page redirects to test_second.xhtml. However, the address bar only shows .../test_second.xhtml, there is NO ?id=#! And testBean.userId does not get printed on that page.
How can I make commandLink work with f:param? Sometimes I want the link not to redirect to another page but to call some methods of bean depending on the data.
test_first.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<h:head/>
<h:body>
<h:form>
<h:link value="link1" outcome="test_second" >
<f:param name="id" value="1"/>
</h:link>
<br/><br/>
<h:commandLink value="link2" action="test_second?faces-redirect=true" >
<f:param name="id" value="2" />
</h:commandLink>
<br/><br/>
<p:commandLink value="link3" action="test_second?faces-redirect=true">
<f:param name="id" value="3" />
</p:commandLink>
<br/><br/>
</h:form>
</h:body>
</html>
test_second.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<f:metadata>
<f:viewParam name="id" value="#{testBean.userId}" />
</f:metadata>
<h:head/>
<h:body>
<h:form>
This is the second page.
<h:outputText value="Selected id is #{testBean.userId}" />
<h:commandButton value="Print page id" action="#{testBean.print()}" />
</h:form>
</h:body>
</html>
TestBean.java
#ManagedBean
#SessionScoped
public class TestBean implements Serializable{
private Integer userId;
public void print() {
System.out.println(userId);
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}

You misinterpreted the meaning of those two tags, namely <h:link> and <h:commandLink>, therefore, you also misinterpreted the meaning of <f:param> attached to either of the two. In anycase it is worthwhile to always read the documentation before asking the questions to get more insight.
<h:link> renders an HTML "a" anchor element. The value of the component is rendered as the anchor text and the outcome of the component is used to determine the target URL rendered in the "href" attribute. Any child UIParameter components are appended to the String to be output as the value of the "href" attribute as query parameters before rendering...
<h:commandLink> render an HTML "a" anchor element that acts like a form submit button* when clicked ... if the disabled attribute is not present, or its value is false. It renders "#" as the value of the "href" attribute, renders the current value of the component as the link text if it is specified and *renders JavaScript that is functionally equivalent to the following as the value of the "onclick" attribute:
document.forms['CLIENT_ID']['hiddenFieldName'].value='CLIENT_ID';
document.forms['CLIENT_ID']['PARAM1_NAME'].value='PARAM1_VALUE';
document.forms['CLIENT_ID']['PARAM2_NAME'].value='PARAM2_VALUE'; return false;
document.forms['CLIENT_ID'].submit()"
where hiddenFieldName is as described above, CLIENT_ID is the clientId of the UICommand component, PARAM_NAME and PARAM_VALUE are the names and values, respectively, of any nested UIParameter children.
In other words, within <h:link> tag nested <f:param> will end up as a query parameter of the generated URL, while within <h:commandLink> tag nested <f:param> will end up as a request parameter with a given value.
While the first one is clear, the second one deserves a better elaboration. To understand what it does, consider that if we abstract away from the details <h:commandLink> sends a POST request and attaches all nested <f:param> tags as request parameters. But it is up to you how you will handle them, as navigation is entirely in your hands.
So, the first option is to set a hardcoded action attribute, which use case is dubious, like in action="second-page", in which way you didn't pass any query parameter at all. What will be done is POSTing to the same view and forwarding to the second without undertaking any action. Quite a dumb action.
The second option is to specify an action method, like in action="#{bean.action}". In this case you must handle navigation in the provided action method, i.e. return null/void from the method for a postback, or return a navigation case outcome as a string to make a forward to the specified view. As for the request parameters that you passed with <f:param> they will be available with standard JSF means like #ManagedProperty("#{param.name}") on a request-scoped bean, or by calling ExternalContext#getRequestParameterMap() in any-scoped bean, for example, in action method, like in String param = externalContext.getRequestParameterMap().get("name"). So now you have your parameter in action method that you're free to use how you like, just adhere to a set of rules that exist for URLs.
Two things left worth mentioning. Remember that request parameters passed with calling the command link will be available only within that same request, as you might expect it to survive a faces-redirect=true that basically fires another request. The other option is to specify includeviewparams=true to pass through the paramaters of the current view, if that's desired, as mentioned in the other answer.

You could do it by concatenating the parameters with & directly at the action attribute:
<p:commandLink value="link3" action="test_second?faces-redirect=true&id=3"/>
Update 1
You might also consider to add &includeViewParams=true. This way view parameters of your target navigation will be included automatically.

Related

Why are validations from my JSF composite facet being done when the facet is not rendered

I have a problem that validations from a composite's facet are being fired even when I do not render the composite.
I stripped the problem down to the following barebones code.
Here is the composite entityDetailPanel:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui"
xmlns:common="http://java.sun.com/jsf/composite/common">
<composite:interface>
<composite:attribute name="prefix" required="true" />
<composite:facet name="lowerPanel"/>
</composite:interface>
<composite:implementation>
<h:form id="#{cc.attrs.prefix}entityDetailForm2"
styleClass="#{cc.attrs.prefix}EntityDetailPanelForm #{cc.attrs.prefix}Listener" >
<p:messages id="#{cc.attrs.prefix}messages" autoUpdate="true" closable="true"/>
<p:commandButton
value="SAVE"
update="#(.#{cc.attrs.prefix}Listener), #(.#{cc.attrs.prefix}EntityDetailPanelForm}"/>
<composite:renderFacet name="lowerPanel" rendered="false"/>
</h:form>
</composite:implementation>
</ui:composition>
And here is the invocation:
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:common="http://xmlns.jcp.org/jsf/composite/common">
<common:entityDetailPanel id="foo" prefix="Instruments">
<f:facet name="lowerPanel">
<!-- <p:inputText id="assetClassPrompt" required="true" requiredMessage="Why do we get this message?"/>-->
<p:selectOneMenu id="assetClassPrompt" required="true" requiredMessage="Why do we get this message?"
value="#{instrumentController.selectedData.assetClass}">
<f:selectItem itemLabel="foo" itemValue="foo"/>
<f:selectItem itemLabel="bar" itemValue="bar"/>
</p:selectOneMenu>
</f:facet>
</common:entityDetailPanel>
</ui:composition>
The combobox does NOT show on the screen (because it's not rendered), but why would I be getting a validation for something that's not rendered?
This is what I see when I click the SAVE button:
Stranger yet, is that I see THIS validation error even on other invocations of the composite that do NOT have that combobox.
I also noticed that if I do not include a unique ID on the <messages> tag, the message from one use of the composite will show up in other uses of the composite.
Is this a PrimeFaces or JSF bug, or am I missing something?
You might notice that I have a commented out <inputText> tag. It's worth mentioning that when I replace the <selectOneMenu> and replace it with the <inputText> I no longer see the problem.
I thought it might help to elucidate a bit on the larger problem I'm trying to solve.
I want to create something akin to a <p:layout> that has both fixed elements (for all uses of the composite) and non-fixed elements/panels which are passed in parametrically (for EACH use of the component).
Here is a screenshot where the items indicated in read are things that vary with each invocation of the composite. Everything else is always present in all invocations of the composite.
As you can see, the parameters are:
A button panel (buttons vary depending on context)
Some additional fields to add to the end of a form (which might contain validations
An entire lower panel (which might contain validations)
It's worth mentioning that all these things are validated together (for "SAVE" buttons), so it's desirable to have the <form> tag be within the composite output (which includes the panels passed in as parameters).
This problem is two-fold.
First, the <cc:renderFacet> is never designed to work this way. It does not support the rendered attribute. That it somehow works is because the facet is internally re-interpreted as an UIPanel component and all attributes are (incorrectly) automatically inherited from the tag. You should not rely on that. The rendered attribute is incorrectly considered during render response, causing confusing behavior that it "works". This is technically a bug in the JSF implementation. The attributes are (correctly) not inherited during the postback, causing the trouble you observed. The components are still decoded and validated "in spite of" that they are not rendered.
Second, the <p:inputText> extends from UIInput which checks before validation if there's any submitted value. A submitted value of null is interpreted as complete absence of the input field in the form, so it's skipped. A submitted value of an empty string is interpeted as an empty value, so it's validated. The <p:selectOneMenu>, however, has overriden the standard UIInput behavior and considers null the same way as an empty string. Even when the submitted value is null (which means that the input field wasn't in the form at all), it's still being validated. This is technically a bug in PrimeFaces side.
Your intent is at least clear: conditionally render a facet. The <cc:xxx> tags are evaluated during Facelets compile time (which is a step before view build time), so conditionally building the <cc:renderFacet> using JSTL <c:if> will also not ever work.
Your best bet is redefining "render lower panel" as a composite attribute, and create a backing component to explicitly copy this attribute into the facet after it's being added to the view.
<cc:interface componentType="entityDetailPanelComposite">
...
<cc:facet name="lowerPanel" />
<cc:attribute name="renderLowerPanel" type="java.lang.Boolean" default="false" />
</cc:interface>
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
...
<cc:renderFacet name="lowerPanel" />
...
</cc:implementation>
#FacesComponent("entityDetailPanelComposite")
public class EntityDetailPanelComposite extends UINamingContainer {
public void init() {
UIComponent lowerPanel = getFacets().get("lowerPanel");
ValueExpression renderLowerPanel = getValueExpression("renderLowerPanel");
if (renderLowerPanel != null) {
lowerPanel.setValueExpression("rendered", renderLowerPanel); // It's an EL expression.
} else {
lowerPanel.getAttributes().put("rendered", getAttributes().get("renderLowerPanel")); // It's a literal value, or the default value.
}
}
}
This has the additional benefit you can specify it from client on.
<my:entityDetailPanel ... renderLowerPanel="true" />

commandLink with ui:repeat in composite component

I am trying to use links in a composite component, my links are in a simple array and do not belong to any managed bean.
<composite:interface>
<composite:attribute name="links" required="true" />
</composite:interface>
<composite:implementation>
<ui:param name="linksSplit" value="#{fn:split(cc.attrs.links, ',')}" />
<ui:repeat var="link" value="#{linksSplit}" >
<h:commandLink value="#{option}" action="#{link}" />
</ui:repeat>
</composite:implementation>
I am getting the following error :
Identity '#{link}' does not reference a MethodExpression instance, returned type: java.lang.String
We are supposed to use String or bean methods in EL expression but I don't understand why we cannot evaluate a parameter which is a String (link in my case).
If I put a real String referenced in my faces-config, it works
<h:commandLink value="#{option}" action="#navigate" />
If you have an explanation or a workaround to get my link working, it would be great
When specifying an EL expression in <h:commandLink action>, it's interpreted as a method expression returning a String (or void if you don't want to navigate). See also the tag documentation:
Name action
Type javax.el.MethodExpression
(signature must match java.lang.Object action())
Description MethodExpression representing the application action to invoke when this component is activated by the user. The expression must evaluate to a public method that takes no parameters, and returns an Object (the toString() of which is called to derive the logical outcome) which is passed to the NavigationHandler for this application.
Given the fact that you seem to want pure page-to-page navigation links, you're actually going in the wrong direction as to using <h:commandLink> for that. You should instead be using <h:link> for that. It generates SEO-friendly and bookmarkable GET links instead of a piece of JavaScript which submits a parent POST form.
<ui:repeat var="link" value="#{linksSplit}" >
<h:link value="#{option}" outcome="#{link}" />
</ui:repeat>
See also:
When should I use h:outputLink instead of h:commandLink?
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Note that this all has nothing to do with composite components. You'd have had exactly the same problem when using this in a normal page.

lose view page parameter

i have page my.xhtml:
<f:metadata>
<f:viewParam name="id"/>
</f:metadata>
...
<h:form>
Current id is: "#{id}"
<h:commandButton action="#{bean.doSomething}" value="Do some logic..">
<f:param name="id" value="#{id}"/>
</h:commandButton>
</h:form>
and Bean.java:
#ManagedBean
#ViewScoped
public class Bean {
....
public void doSomething(){
//do some logick, don't use id parameter
}
}
When I get to page first time with id=10 I can see on page Current id is: "10"..
When I click on button page is reload and I can see on page Current id is: "10". again.
But when I click on button third time I can see on page Current id is: ""., i lose id parameter and I don't understand why?
I know I can do this with save parameter value in bean
(by add to f:viewParam:
<f:metadata>
<f:viewParam name="id" value="#{bean.value}/>
</f:metadata>
), but can I do this without save parameter value in bean?
h:button works with f:param but h:commandButton does not. In this case, your best is to bind the view parameter to a bean property as you explain the last. The #ViewScoped bean will retain its state as long as you call void action methods.
You could also pass it as a parameter to the action method (there are several ways to do that), but this doesn't make sense for a method which has nothing to do with that value.
See also:
Passing parameters to action methods in JSF
Communication in JSF

Retaining GET request query string parameters on JSF form submit

I have 3 pages:
main.xhtml
agreement.xhtml
generated.xhtml
The agreement.xhtml needs two parameters to load correctly: serviceId and site. So, a normal url looks like this: /app/agreement.xhtml?site=US&serviceId=AABBCC.
I have this button on agreement.xhtml
<h:form>
<h:commandButton value="Generate License File" action="#{agreement.generateMethod}" />
</h:form>
The #RequestScoped bean #{agreement} has this method:
public String generateMethod(){
.......
return "generated";
}
I need that, on click, the generateMethod() method is executed, and after it's done, the user is redirected to the generated.xhtml page. What's happening is that, on click, the page browser sends the user to /app/agreement.xhtml and, since it's not sending the parameters site and serviceId, it crashes.
I tried making the generateMethod() return a "generated?faces-redirect=true", but still nothing. Any ideas?
Your concrete problem is caused because a JSF <h:form> submits by default to the current request URL without any query string. Look closer at the generated HTML output, you'll see
<form action="/app/agreement.xhtml" ...>
You'd thus explicitly need to include those request parameters yourself. There are several ways to solve this. If you weren't sending a redirect, then you could just add them as hidden inputs to the JSF form.
<h:form>
<input type="hidden" name="site" value="#{param.site}" />
<input type="hidden" name="serviceId" value="#{param.serviceId}" />
...
</h:form>
Only, those parameters won't reappear in URL in browser's address bar. This isn't a problem if you're only using using ajax on the same page. The <h:inputHidden> is by the way not suitable as it will confusingly lose its value when a conversion or validation error occurs on the form.
In order to get them to reappear in URL, you need <f:viewParam> and includeViewParams. In order to get includeViewParams to work, you need to declare the following in both the source page agreement.xhtml ...
<f:metadata>
<f:viewParam name="site" value="#{agreement.site}" />
<f:viewParam name="serviceId" value="#{agreement.serviceId}" />
</f:metadata>
... and the target page generated.xhtml:
<f:metadata>
<f:viewParam name="site" value="#{generated.site}" />
<f:viewParam name="serviceId" value="#{generated.serviceId}" />
</f:metadata>
Now you can send a redirect including the view parameters as follows:
public String generateMethod() {
// ...
return "generated?faces-redirect=true&includeViewParams=true";
}
Do note that the bean should be #ViewScoped in order to keep those parameters alive between opening the page with the form and submitting the form, also on validation errors. Otherwise, when sticking to a #RequestScoped bean, you should be retaining them as <f:param> in the command components:
<h:commandButton ...>
<f:param name="site" value="#{generated.site}" />
<f:param name="serviceId" value="#{generated.serviceId}" />
</h:commandButton>
There's no way to set them for <f:ajax> inside input components, your bean should then really be #ViewScoped.
Alternatively, if you happen to use JSF utility library OmniFaces already, then you could also just replace the <h:form> by <o:form> as follows (see also showcase example):
<o:form>
That's basically all. This will generate a <form action> with current query string included.
<form action="/app/agreement.xhtml?site=US&serviceId=AABBCC" ...>
Those request parameters are then just available in the request parameter map of the form submit. You don't need additional metadata/viewparams and you also don't need to send a redirect and your bean can be kept #RequestScoped, if necessary.
public String generateMethod() {
// ...
return "generated";
}
See also:
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Your generateMethod would have to return
return "generated?site=US&serviceId=AABBCC&faces-redirect=true";
You can even replace & with & but escape it in your xhtml.
In your generated.xhtml you can catch the parameters that are being passed with <f:viewParam> like this
<f:metadata>
<f:viewParam name="site" value="#{yourBean.site}"/><!--Make sure you have a setter-->
<f:viewParam name="serviceId" value="#{yourBean.serviceId}"/><!--Make sure you have a setter-
</f:metadata>
<h:head>

Getting viewParam in POST

Consider the following code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:o="http://omnifaces.org/ui">
<f:metadata>
<o:viewParam name="selected" value="#{testBacking.selected}" >
</o:viewParam>
</f:metadata>
<h:head>
<title>
title
</title>
</h:head>
<h:body>
<o:form includeViewParams="true">
<h:commandButton action="#{testBacking.go()}" value="go">
<f:ajax execute="#all" render="#all"/>
</h:commandButton>
</o:form>
</h:body>
</html>
The action method:
public void go() {
System.out.println("go() is called");
Collection<UIViewParameter> viewParams = ViewMetadata.getViewParameters(FacesContext.getCurrentInstance().getViewRoot());
for (UIViewParameter viewParam : viewParams) {
System.out.println(viewParam.getName() +" = "+ viewParam.getValue());
}
}
The action method returns correct viewParam name, but the value always null. If this is the intended behaviour, how <o:form includeViewParams="true"> helps in practical usage?
I am using Mojarra 2.1.12 and Omnifaces 1.1.
Your bean is apparently request scoped. The <o:viewParam> is intented to be used in combination with view scoped beans only because it prevents the model being converted/validated/updated again and again on every postback to the same view even though it was already done during the initial request and is still there as property of the view scoped bean. This improves overall performance when the model is being bound to an expensive converter which performs the job based on DB calls. The showcase example also shows this clearly.
To achieve this, the <o:viewParam> basically skips this model conversion/validation/update job during any postback. A request scoped bean is however newly created on every request and the initial model value is thus lost on every postback and due to the design of <o:viewParam> not being set at all.
You can solve this by placing the bean in the view scope, or by just using the standard <f:viewParam> instead.
As to the <o:form includeViewParams="true">, this is only useful on synchronous postbacks. If you remove the <f:ajax> then you'll see how it's useful. Without includeViewParams="true" the URL becomes the one without the view parameter, i.e. without ?selected=somevalue and it becomes thus unbookmarkable. This has no strict relationship with <o:viewParam>, it works as good in combination with <f:viewParam>.

Resources