Redirect on ViewExpiredException in AJAX request in JSF 2 [duplicate] - ajax

This question already has answers here:
Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same
(2 answers)
Closed 7 years ago.
When my session is expired in my Java EE 7, JSF web app.
I get a ViewExpiredException in ajax requests.
I would like to redirect to a page where it shows the user that the session is expired.
I tried browsing google and stackoverflow for a solution, but I didden't have any luck to get it to work with how I want it.
UPDATE:
I tried the solution posted # Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request
It did work at my login page, when using the login_submit button.
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html"
template="./../templates/login_template.xhtml">
<ui:define name="title">
<h:outputText value="#{bundle.login_title}"/>
</ui:define>
<ui:define name="content">
<h:outputText escape="false" value="#{bundle.login_message}"/>
<h:form>
<h:panelGrid columns="2" cellpadding="5">
<h:outputLabel for="username" value="#{bundle.login_username}"/>
<p:inputText id="username" type="text" value="#{authBean.username}" label="username"/>
<h:outputLabel for="password" value="#{bundle.login_password}"/>
<p:inputText id="password" type="password" value="#{authBean.password}" label="password"/>
<h:outputText value="#{bundle.login_invalid_password}" rendered="#{!authBean.validPassword and authBean.validUsername}"/>
<h:outputText value="#{bundle.login_invalid_username}" rendered="#{!authBean.validUsername}"/>
<p:commandButton value="#{bundle.login_submit}" action="#{authBean.doLogin}"/>
</h:panelGrid>
</h:form>
</ui:define>
</ui:composition>
But it doesn't work with this JSF in a xhtml page that is on a secure page, login page is a public page. For example the following xhtml is on a secure page:
<p:commandButton styleClass="button #{chartBean.isViewButtonActive('MONTH')}" update="dataChart dataTable #form" value="#{bundle.performance_year}" action="#{chartBean.changeModel('MONTH')}" />
This does an AJAX post request, but it gets catched by my #WebFilter. (It also happends with selectOneMenu from Primefaces) This filter checks if an user is logged in if not it redirects them to the login page. But for some reason with the example button I given above it also get catched by the #WebFilter and the ajax requests gets as response redirect specified in the #WebFilter. It doesn't get catched by the ExceptionHandler. The #WebFilter is only applied to secure pages, see below.
#WebFilter(
filterName = "AuthorizationFilter",
urlPatterns = {"/secure/*"},
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD}
)
public class AuthorizationFilter implements Filter {
Does anyone know how to solve this.

I do it like this in the webfilter, and it works fine:
// Check if user logged in, if not redirect to login.xhtml
if (loginBean == null || !((LoginBean) loginBean).isLoggedIn()) {
boolean isAjax = "XMLHttpRequest".equals(req.getHeader("X-Requested-With"));
if (!isAjax) {
res.sendRedirect(req.getContextPath() + "/login.xhtml");
} else {
// Redirecting an ajax request has to be done in the following way:
// http://javaevangelist.blogspot.com/2013/01/jsf-2x-tip-of-day-ajax-redirection-from.html
String redirectURL = res.encodeRedirectURL(req.getContextPath() + "/login.xhtml");
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"").append(redirectURL).append("\"></redirect></partial-response>");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/xml");
PrintWriter pw = response.getWriter();
pw.println(sb.toString());
pw.flush();
}
} else {
// Let chain of filters continue;
chain.doFilter(request, response);
}
Inspiration

Related

Is it possible to refresh a page through ajax in JSF?

I have something like this :
<h:commandButton action="#{MyBean.action()}">
<f:ajax render ="#all"/>
</h:commandButton>
Is it possible to replace the "#all" in the ajax render to refresh the page?
With this code my page doesn't refresh like if I press F5
That's not possible with ajax. Just remove <f:ajax> and send a redirect to self in action method.
<h:commandButton value="Submit and refresh" action="#{bean.action}" />
public String action() {
// ...
FacesContext context = FacesContext.getCurrentInstance();
return context.getViewRoot().getViewId() + "?faces-redirect=true";
}
Or if you actually don't need to perform a business action, just use <h:button> without an outcome.
<h:button value="Refresh only" />

PrimeFaces Ajax Navigation with Browser History/Hashtag

I have implemented a web application which is a one-page-design. Which basically loads a single page then updates with AJAX the central content. The code is the following:
<h:body>
<pe:layout id="page" fullPage="true">
<!-- West -->
<pe:layoutPane id="west" position="west" >
<f:facet name="header">Main Menu</f:facet>
<h:form id="form1">
<p:panelMenu id="panelMenu">
<p:submenu label="Persons">
<p:menuitem value="Person List" update=":centerpanel"
actionListener="#{layout.setAll('formPersonList.xhtml', 'Person List')}">
</p:menuitem>
</p:submenu>
</p:panelMenu>
</h:form>
</pe:layoutPane>
<!-- Center -->
<pe:layoutPane id="content" position="center">
<h:panelGroup id="centerpanel" layout="block">
<ui:include id="include" src="#{layout.navigation}" />
</h:panelGroup>
</pe:layoutPane>
</pe:layout>
</h:body>
This basically works, but I want to enable browser navigation as well. For example like: http://ticketmonster-jdf.rhcloud.com/ with the hashtags on the url. So using the back/forward button I can go to the equivalent option.
Any idea how to do this?
I have created a blog post explaining how to get this to work using jQuery BBQ, based on your question.
With jQuery BBQ you can keep track of state, history and allow bookmarking while dynamically modifying the page via AJAX and/or DHTML.. just click the links, use your browser's back and next buttons, reload the page..
First we should include jQuery BBQ.
<h:outputScript library="js" name="jquery.ba-bbq.min.js" />
Now consider we have the menu (with all our nav rules)
<p:submenu label="Meat">
<p:menuitem outcome="/meat/steak.xhtml" value="Steak" />
<p:menuitem outcome="/meat/burger.xhtml" value="Burger" />
<p:menuitem outcome="/meat/chicken.xhtml" value="Chicken" />
<p:menuitem outcome="/meat/kebab.xhtml" value="Kebab" />
</p:submenu>
Then the centered content
<pe:layoutPane id="content" position="center">
<h:panelGroup id="centerpanel" layout="block">
<ui:include id="include" src="#{mainBean.currentNav}" />
</h:panelGroup>
</pe:layoutPane>
the include reflects the currentNav clicked.
now define a remoteCommand inside the form
<p:remoteCommand name="updateNav"
actionListener="#{mainBean.updateNav()}"
update=":centerpanel"/>
This remoteCommand will update our currentNav based on the hashtag.
Create your JS file or include the following code into the document ready
$(document).ready(function() {
$('.ui-menuitem-link').click(function(event) {
event.preventDefault();
var currentNav = $(this).attr('href').
substr($(this).attr('href').indexOf("/faces") + 6)
window.location.hash = '#' + currentNav;
})
$(window).bind('hashchange', function(e) {
var url = $.param.fragment();
updateNav([{name: 'currentNav', value: url}]); //remoteCommand Call
})
$(window).trigger('hashchange');
});
Basically first we handle our clicks on the menu items, setting the hash of the window.
then we define an event of the hashchange of the window, using the help of jQuery BBQ.
ManagedBean
public void updateNav() {
FacesContext context = FacesContext.getCurrentInstance();
Map map = context.getExternalContext().getRequestParameterMap();
currentNav = (String) map.get("currentNav");
}
For a complete code, please see my newly post created for the question.
Primefaces Hash Navigation Using jQuery BBQ
And also the example is available on github.
Hope it helps.
If I understand well your question, you want to navigate with back/forward button. You can do this with LinkedList in your backing bean:
private LinkedList<String> historyForBackPage= new LinkedList<String>();
public void setLastBackPage(String navigationCase) {
historyForBackPage.push(navigationCase);
if (historyForBackPage.size() > yourMaxSize) {
historyForBackPage.pollLast();
}
}
public String getLastBackPage() {
return historyForBackPage.pop();
}
and always add last page when you call layout.setAll method. The simple commandButton call getLastBackPage() method. Before try it, please configure navigation case in faces-config.xml.
If your case cannot work navigation case, because you work only one XHTML, than you could add back/foward page name for your bean and render your page. May be simple JavaScript call onclick="window.history.go(-1); return false;" it could be usefull in your case. I don't know. Please try it!
In my answer I focused only to back button, but I think you can adapt foward button in same way.
By the way, the Breadcrumb is nice PrimeFaces feature.
If you want to catch browser back clicking action, you can use one JavaScript or Ajax script.
For Ajax script please check this answer https://stackoverflow.com/a/10050826/1047582.

Submitted form values not updated in model when adding <f:ajax> to <h:commandButton>

I'm learning how to use ajax within jsf, I made a page that does actually nothing, an input text that is filled with a number, submitted to the server, call the setter for that element with the value submitted, and display the getter's value.
Here's the simple bean's code:
#ManagedBean(name="helper",eager=true)
public class HealthPlanHelper {
String random = "1";
public void setRandomize(String s){
random = s;
System.out.println("Calling setter");
}
public String getRandomize(){
return random;
}
}
And the jsf page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head></h:head>
<h:body>
<h:form>
<h:commandButton action="nothing">
<f:ajax render="num"/>
</h:commandButton>
<h:inputText value="#{helper.randomize}" id="num"/>
</h:form>
</h:body>
</html>
As you see, this is a request scoped bean, whenever I click the button the server shows that it creates an instance of the bean, but the setter method is never called, thus, the getter return always "1" as the value of the string.
When I remove the the setter is called normally.
The <f:ajax> processes by default only the current component (read description of execute attribute). Basically, your code is exactly the same as this:
<h:form>
<h:commandButton action="nothing">
<f:ajax execute="#this" render="num"/>
</h:commandButton>
<h:inputText value="#{helper.randomize}" id="num"/>
</h:form>
In effects, only the <h:commandButton action> is been processed and the <h:inputText value> (and any other input field, if any) is completely ignored.
You need to change the execute attribute to explicitly specify components or sections you'd like to process during the ajax request. Usually, in order to process the entire form, #form is been used:
<h:form>
<h:commandButton action="nothing">
<f:ajax execute="#form" render="num"/>
</h:commandButton>
<h:inputText value="#{helper.randomize}" id="num"/>
</h:form>
See also:
Communication in JSF 2.0 - Ajax (asynchronous) POST form
commandButton/commandLink/ajax action/listener method not invoked or input value not updated (point 8)
Understanding PrimeFaces process/update and JSF f:ajax execute/render attributes

JSF commandbutton ajax: how to capture the success/fail event

my current code.
<h:form>
<h:panelGroup id="messagePanel" layout="block">
<h:messages errorStyle="color: red" infoStyle="color: green" layout="table"/>
</h:panelGroup>
<h:panelGrid columns="2">
// some form input stuff here..
</h:panelGrid>
<h:commandButton class="register-btn" action="#{accountController.create}" value="#{bundle.Register}">
<f:ajax event="action" execute="#form" render="messagePanel"/>
</h:commandButton>
</h:form>
messagePanel is where the validation errors displays.
I want to know how to capture the success event.
[done] if user input some incorrect values, it does an ajax validation and displays the incorrect values
[problem] if success it should redirect to another page.
**updated
create method
public String create() {
try {
getFacadeUser().create(currentUser);
JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Bundle").getString("AccountCreated"));
return prepareCreate();
} catch (Exception e) {
JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Bundle").getString("PersistenceErrorOccured"));
return null;
}
}
public String prepareCreate() {
current = new Account();
selectedItemIndex = -1;
return "index";
}
i'm using JSF 2.1
This is not correct way to do redirection with AJAX request. You should use ExternalContext.redirect() instead:
FacesContext.getCurrentInstance().getExternalContext().redirect("yourUrl");
In that way you will receive partial response from server to, which will do redirection:
<partial-response>
<redirect url="yourUrl">
</redirect>
</partial-response>
String which you returned in this method is irrelevant, you can even return null.

JSF AJax - How to render the output from an URL

I am trying to render the output of an URL in the same page, using JSF / Ajax (a4j) and so far I am having no luck. BTW, I am using RichFaces 4.0. For example, if the User clicks a row in a Table (or just a button), I like to show a web page right below the Table, that is relevant to the row clicked. Is there a way I can get the relevant URL using a Backing Bean and invoke that URL using A4J Tags? I searched for such tags or sample code and so far I could not find any.
Thanks for your help.
Embedding the result of an external URL in your webpage can only neatly be done with help of a HTML <iframe> element.
So, this should do:
<h:form>
<h:dataTable value="#{bean.websites}" var="website">
<h:column>#{website.name}</h:column>
<h:column>
<h:commandButton value="show" action="#{bean.setWebsite(website)}">
<f:ajax render=":website" />
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
<h:panelGroup id="website">
<iframe src="#{bean.website.url}"></iframe>
</h:panelGroup>
with
#ManagedBean
#ViewScoped
public class Bean {
private List<Website> websites; // +getter
private Website website; // +getter+setter
// ...
}

Resources