Using Navigation in JSF - jsf

Framework: JSF 2.0.
My problem: I have a page, called login.xhtml. Whenever someone view that page, it invoked a filter, called Authentication filter. The filter will check, if user already loged in, it will be redirected to default based on user's role (for example: Admin will go to "admin/admin.xhtml", student will be redirected to "user/user.xhtml").
My solution: Using the JSF Navigation
My config:
faces-config.xml
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>AD</from-outcome>
<to-view-id>/admin/admin.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>US</from-outcome>
<to-view-id>/user/user.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
The redirection using navigation:
public static void redirectUsingNavigation(String from, String outCome) {
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, from, outCome);
}
My question:
When i run the redirectUsingNavigation("*","AD") or redirectUsingNavigation(null,"AD"), it does not redirect me to admin.xhtml (i'm still on login.xhtml). How to fix this?
Any framework support me on this problem?

That code is not redirecting at all. It's just forwarding the request. It's just using the same request object for a different target page). A real redirect instructs the browser to send a new request on the given Location header. You see this change being reflected back in browser address bar.
You need to either add faces-redirect=true parameter to the outcome to trigger the redirect (this is particularly useful if you're using implicit navigation instead of verbose navigation cases):
navigationHandler.handleNavigation(facesContext, from, outCome + "?faces-redirect=true")
Or add <redirect/> to the navigation cases if you want them to always take place:
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>AD</from-outcome>
<to-view-id>/admin/admin.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>US</from-outcome>
<to-view-id>/user/user.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
Last but not least, you need to ensure that handleNavigation() is called before the response is committed, otherwise it won't work at all and your server logs would be littered with IllegalStateException: response already committed errors. You can make use of <f:event type="preRenderView"> to invoke a bean action before the response is committed.
See also:
jsf navigation question
Hit a bean method and redirect on a GET request

Related

Define a navigation case in faces-config.xml to do page reload on a certain outcome?

I want to define a navigation case in faces-config.xml to do a page reload/refresh, without navigating to another page on certain outcome from any view id. How do I define it in faces-config.xml as I dont have any specific to-viewId to hardcode in faces-config.xml ?
You can use EL expressions in your faces-config.xml file.
To return to the current view, for example:
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>#{view.viewId}</to-view-id>
</navigation-case>

JSF redirect to another bean from bean method

I have a scenario where I have to invoke another bean and registered bean attributes before rendering the JSF page as JSF page have reference to bean attributes for rendering response.
I am looking for best option to implement such scenario. I tried to represent required scenario in form of pseudo-code for better understanding.
Page A --> have button "View Detail" bind with BeanOne.getDetail() -- Scope is session.
BeanOne.getDetail()
process the request
- call the backend method to get detail
- if (type == XYZ) then invoke BeanTwo.getTypeXYZ() and redirect as per navigation rule defined for BeanTwo.getTypeXYZ()
- else if (type == RST) then invoke BeanThree.getTypeRST() and redirect as per navigation rule defined for BeanThree.getTypeRST()
BeanTwo.getTypeXYZ()
process the request
call the backend method to get detail
set this.attributeB = value returned from backend method.
navigate to Page B
BeanTwo.getTypeRST()
process the request
call the backend method to get detail
set this.attributeC = value returned from backend method.
navigate to Page C
Something like having bean method name in navigation rule
<navigation-rule>
<from-view-id>Page A</from-view-id>
<navigation-case>
<from-action>#{beanOne.getDetail}</from-action>
<from-outcome>xyz</from-outcome>
<to-view-id>#{beanTwo.getTypeXYZ}</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{beanOne.getDetail}</from-action>
<from-outcome>rst</from-outcome>
<to-view-id>#{beanTwo.getTypeRST}</to-view-id>
</navigation-case>
</navigation-rule>
Did you try using navication-rule in faces-config.xml
<navigation-rule>
<navigation-case>
<from-outcome>XYZ</from-outcome>
<to-view-id>pageB.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>RST</from-outcome>
<to-view-id>pageC.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
in bean
getTypeXYZ() {
...
NavigationHandler nh = facesContext.getApplication().getNavigationHandler();
nh.handleNavigation(facesContext, null, "XYZ");
}

Navigation rule not firing

I have a navigation rule that looks like this:
<navigation-rule>
<display-name>forgotPwd</display-name>
<from-view-id>/login/forgotpwd.jsf</from-view-id>
<navigation-case>
<from-outcome>pass</from-outcome>
<to-view-id>/login/pwdresetcomplete.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>fail</from-outcome>
<to-view-id>/login/forgotpwd.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
So I do this to trigger the navigation rule:
Navigate to /login/forgotpwd.jsf?uuid=fed8b3f7-ed33-4941-8306-3d2afbb8d1d0
This page has the following form:
<h:form id="resetForm">
<!-- Omitting the login fields -->
<h:commandButton type="submit" id="resetPassword" value="Save" styleClass="btn small" action="#{registration.resetPassword}" >
<f:param name="uuid" value="#{facesContext.externalContext.requestParameterMap.uuid}" />
</h:commandButton>
</h:form>
(Takes the uuid passed as a query string parameter, and will call the registration.resetPassword function.)
Here is the registration.resetPassword java code:
(Details omitted for brevity)
public String resetPassword() {
// If good
return "pass";
// If bad
return "fail"
}
Problem: When it returns "pass", the navigation rule is not firing. It goes back to /login/forgotpwd.jsf instead of /login/pwdresetcomplete.jsf
Is this because it has the UUID parameter appended to it? Why is my navigation not firing?
Is there some log4j logging that I can trigger to see why this isn't working?
<from-view-id>/login/forgotpwd.jsf</from-view-id>
The from-view-id must be the view ID, not the virtual URL. So, it should not contain .jsf extension.
<from-view-id>/login/forgotpwd.xhtml</from-view-id>
By the way, request parameters are in the view available by #{param}. This saves some characters as compared to #{facesContext.externalContext.requestParameterMap}.
<f:param name="uuid" value="#{param.uuid}" />
See also implicit objects in EL. Another by the way, it's not exactly clear which JSF version you're using, but since JSF 2.0 (which I guess you're using as you're on Facelets already) you don't need the navigation case XML hell anymore. You can just return the (relative and/or extension-less) to-view-id directly from the action method.
public String resetPassword() {
// If good
return "pwdresetcomplete";
// If bad
return "forgotpwd"
}
This way you can get rid of the whole <navigation-rule>. See also implicit navigation.
I added these two rules... now it works. Not sure which one did it but using the from-action made it a lot more happy.
<navigation-rule>
<display-name>forgotPwd</display-name>
<from-view-id>/login/forgotpwd.xhtml</from-view-id>
<navigation-case>
<from-action>#{registration.resetPassword}</from-action>
<from-outcome>pass</from-outcome>
<to-view-id>/login/pwdresetcomplete.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{registration.resetPassword}</from-action>
<from-outcome>fail</from-outcome>
<to-view-id>/login/forgotpwd.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<display-name>forgotPwd</display-name>
<from-view-id>/login/forgotpwd.jsf</from-view-id>
<navigation-case>
<from-action>#{registration.resetPassword}</from-action>
<from-outcome>pass</from-outcome>
<to-view-id>/login/pwdresetcomplete.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{registration.resetPassword}</from-action>
<from-outcome>fail</from-outcome>
<to-view-id>/login/forgotpwd.xhtml</to-view-id>
</navigation-case>
</navigation-rule>

JSF 1: execute bean method and set parameters for next page

I'm new to JSF.
What i want is to click on something and when i do i want to run a bean method AND send the request with GET so i can set Parameters that will show on the URL of the next page.
I tried with this navigation-rule, the method will execute and return "success_selectedUserToCard", it will forward me to-view-id but the parameters are removed:
<navigation-rule>
<navigation-case>
<from-outcome>success_selectedUserToCard</from-outcome>-->
<to-view-id>/jspx/user_to_card.faces?tab=usertocard&selectedLink=usertocardManagementLink</to-view-id>
</navigation-case>
</navigation-rule>
So my URL on the following page will be only: /jspx/user_to_card.faces
P.S. I am using JSF 1. Cannot move to JSF 2
Use ExternalContext#redirect() instead.
public void submit() {
// Do your job here.
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
externalContext.redirect("/jspx/user_to_card.faces?tab=usertocard&selectedLink=usertocardManagementLink");
}
Nasty, but since you aren't using JSF 2.0 which supports includeViewParams, there's no other way.

get GET parameters in JSF's managed bean

Can someone tell me how to catch parameters passed from URI in JSF's managed bean?
I have a navigation menu all nodes of which link to some navigation case. And i have two similar items there: Acquiring products and Issuing products. They have the same page but one different parameter: productType. I try to set it just by adding it to URL in "to-view-id" element like this:
<navigation-case>
<from-outcome>acquiring|products</from-outcome>
<to-view-id>/pages/products/list_products.jspx?productType=acquiring</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>issuing|products</from-outcome>
<to-view-id>/pages/products/list_products.jspx?productType=issuing</to-view-id>
</navigation-case>
But i can't get this "productType" from my managed bean. I tried to get it through FacesContext like this:
FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("productType")
And like this:
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
request.getParameter("productType");
And i tried to include it as a parameter of managed bean in faces-config.xml and then getting it through ordinary setter:
<managed-bean>
<managed-bean-name>MbProducts</managed-bean-name>
<managed-bean-class>my.package.product.MbProducts</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>productType</property-name>
<value>#{param.productType}</value>
</managed-property>
</managed-bean>
...
public class MbProducts {
...
public void setProductType(String productType) {
this.productType = productType;
}
...
}
But neither of these ways have helped me. All of them returned null. How can i get this productType? Or how can i pass it some other way?
The navigation rule by default does a forward. I.e. it reuses the initial request. Whatever way you try to access the request parameters in the forwarded resource, it will always try to grab them from the initial and already-processed request.
To fix this, you need to fire a redirect instead of forward. It creates a brand new request (you also see this reflecting back in the browser address bar).
In JSF, adding
<redirect/>
to the navigation case should do.

Resources