CDI/Weld - how to handle browser page refresh after ending conversation? - jsf

In the very early days of using CDI I'm navigating to a page with a long running
conversation active using faces-redirect=true, so I have a URL like ..myPage.xhtml?cid=1.
At some point I end the conversation, I mean this has to be a reasonable thing to do
or you might as well use session scope? Anyway, I end the conversation and then the
user hits F5, and then of course this causes a 'conversation not found' error as cid=1
doesn't exist.
Can anyone suggest how to get around this problem? I'm using the Steven Verborgh
ViewScoped implementation and simply using a conversation scoped bean to pass parameters
between pages. So I have for example 2 #ViewScoped beans each of which back page 1 & 2.
I #Inject the #ConversationScoped ParameterBean into both view scoped beans. I start the
conversation (parameterBean.getConversation().begin()) in the action method called from
page 1. In the preRenderView event for page 2 I take a reference to the properties of
the parameterBean into a page 2 backing bean instance variable and end the conversation,
it's done it's job and no longer required.
Except for the F5 problem it works fine. If anyone has any suggestions I'd appreciate it,
hopefully I'm not missing something really obvious. I kind of assuming there's no getting
away from a redirect.
Thanks.

There is no workaround for it. The default #ConversationScoped is utterly broken. I'm using the #ConversationScoped from MyFaces CODI instead. It solves all problems you can get with the standard scope.

Related

JSF - Appropriate bean scope for keeping data between pages but only "browser tab related"

I am creating a web application using JSF 2.2.20 in which I am implementing a "kinda wizard" flow which lets the user filling input fields and go back and forth the view pages through navigation. I am using a single bean for all these views.
Let's say I have views A.xhtml, B.xhtml, C.xhtml and D.xhtml, all managed by the same bean MyBean.java
I want my application to be "browser tab scoped", which means that
I do not want my bean's data be re-instantiated after every HTTP Request as it happens with #RequestScoped beans or after view changing as it happens with #ViewScoped, I want the data of my bean to be kept between view changes and redirections so the user can go back and forth between pages without losing the data he has already given.
I do not want to use the #SessionScoped scope since each time the user opens a new tab I want the bean to be re-instantiated starting from page "A.xhtml.
Is there any built-in way to achieve the scenario described above using the current JSF version? In case there is not any, could you please propose any workarounds?
Thanks in advance!
I think #ViewScoped is what you are looking for, but it depends on your exact usage.
Couple of notes:
Use javax.faces.view.ViewScoped. Don't use the deprecated managed bean annotation as it works differently.
#ViewScoped works by storing the beans in the view. So each time you load the page you get a view and a viewId that corresponds to that view. So effectively each load of the page (could be read as 'each browser tab') gets its own bean.
#ViewScoped is a passivating scope. That means your beans, and their injected Dependencies, do need to be Serializable.
Use a recent, up-to-date version of your app server, or if you bring in MyFaces manually, use the latest release. I found a number of older versions implementations buggy 5+ years ago, but it seems to work flawlessly now.
If there is a Page Navigation occurring, you probably want to use FlowScoped. This is a multi-page bean that stays alive until you end the 'flow'.
If neither of these two work, you can always implement your own scope which is surprisingly easy with CDI.

saving data in between pages

I have been reading about JSF navigation between pages in an application and I am still a little bit confused. I have a small web application with a series of pages with continue and previous buttons on each page. To save the valid data entered on the view, I am trying to figure out ways to make the valid form data stick when the user goes back and forward. Initially, I had the page views in session scope but I understood that its bad practice. The data that I am capturing can be a managed bean with composite objects within the bean that corresponds to the data that is captured in the pages. But, I read that managed bean in session is not a good idea either. After reading through so many posts, I feel that I should know the solution but its still not very clear as to how to save the validated data into JSF so that it displays when the user navigates back and forth? Appreciate your help.
Seems to me you're trying to accomplish some kind of wizard-like navigation. For this use-case I would suggest the use of a #ConversationScoped backend Bean.
This scope is not provided by the JSF- but by the CDI-framework which just work perfectly together.
To get this to work you essentially have to follow these steps:
Create a CDI Bean like:
#Named
#ConversationScoped
public class MyBean implements Serializable {
#Inject
private Conversation conversation;
...
}
#Named is the CDI alternative to JSFs #ManagedBean.
Your class should implement the Serializable interface to be able to passivate its state.
CDI #Inject the Conversation-object to use in your code.
Start the conversation anywhere inside your code via:
conversation.start();
Do your magic: process input, change data, navigate, persist to db,...
End the conversation (in your case at the last page, maybe after a click on a 'Finish'-Button)
conversation.end();
A quick google search came up with this pretty tutorial implementing a questionaire site which could be useful to you. Have a look at this.
Hope this helps. Have fun!
Are you not persisting the data to a database? If so, you can use a request-scoped managed bean, that way when you navigate back to the original page the updated records will be re-retrieved from the database and your changes will be displayed.

Doing something whenever user exits particular page

I have a internet shop application. I wanted to improve "removing from the cart" feature. I don't want to ask an user if he wants to "really remove" the position from the cart, instead I'd like to allow him cancel the operation.
Currently when user clicks "X" button at the product in the cart, row of table changes to "removed, click to cancel". In the back there is a list of CartPosition objects every with flag removed. Removed product is still on list, but the flag is set to true. It makes product still visible in proper position on the list, but on the view side I can render it another way. When user clicks another "X", previous one is premamently removed from the list and new one is marked as removed.
Shopping cart is session scoped CDI bean. It means, when I mark some position removed and go somewhere else, then go back to the cart, I'll still have one row marked as removed. I just don't want this.
I thought about changing the bean to JSF one, then create another view scoped which could keep record marked as removed and check if it's empty, if so I could remove the one from list. I have no idea if it's going to work. I'll try this approach, but I'd rather keep my beans managed by CDI.
Is there a way to handle this without implementing my above idea?
EDIT: #BalusC, your assumptions are wrong. You should rather suppose, that I don't exactly know how can I mix CDI and JSF. I actually use JSF 2.2, what means that javax.faces.view.ViewScoped annotation is available for me. Anyway I still don't understand the idea. I understood, that I have to rely on some ViewScoped bean. It's clear, but how it has to be composed. Can you explain your idea? I tried few things, but it looked it didn't work correctly. For example my #PreDestroy method of ViewScoped bean was never called and it's constructed lazily only when I try to use it.
Do the actual remove job in #PreDestroy annotated method of the session or view scoped bean.
#PreDestroy
public void destroy() {
// Do the actual remove job here.
}
Given that your question implies that you don't have CDI based javax.faces.view.ViewScoped available, which is new since JSF 2.2, this in turn suggests that you're still on JSF 2.0/2.1 and thus only have JSF based javax.faces.bean.ViewScoped at hands, then it should be noted that its #PreDestroy is broken in several circumstances. You'd then best do the #PreDestroy in the CDI based #SessionScoped bean. Alternatively, you could use OmniFaces #ViewScoped which has fixed and improved the #PreDestroy of a #ViewScoped bean in several ways. It even gets invoked on window unload.
As far as I understood your problem is the session. One way to solve the problem is to set the list of removed products in a request attribute and remove it from the session bean so that on the next request they will disappear.
Hope that helps

Bean value update problem in jsf 1.1

I have One Jsf form that contains 2 Beans. First Bean scope: Session. Second Bean scope: request.
When some values are added via First Bean to second Bean, the updated value is not displayed in the form. I don't know how to do. Please help me.
Either you fired a redirect which caused the request scoped bean being renewed, or the values are updated at the wrong moment which caused them being overridden later in the JSF lifecycle.
To fix the first, ensure that you aren't including <redirect/> in navigation case nor did call ExternalContext#redirect() in bean action method. To fix the second, well, run a debugger. This article may also help a lot in understanding what's going on under the JSF hoods.
BalusC has shown what are the possible reasons for this problem.
In case it is the redirection one, removing the redirect solves the problem, but adds another - the user is asked to resubmit the form if he refreshes the page.
A preferred way to achieve such transitions from one page to another is to use a conversation. MyFaces Orchestra provides a special conversation scope to solve this and many more problems.

Whats the best way of sending parameters between pages?

We are using JSF in our project (im pretty new to it) were every page have a back bean Java file.
In order to move (redirect) from one page to another, i need to put all the parameters (search criteria) in the request scope before redirecting and then retrieve it back in the next page constructor. When you have few pages deep and you want to come back to the top, it becomes really annoying to maintain.
For example, if i have page 1 with advanced search filters, which redirects to page 2, depending on the chosen item, and from page 2, you get another list were you can go to page 3 for details. Now each time i need to put all the params in the request scope/read them again, store them in hidden fields and get them back.
Whats exactly wrong with this method and whats a better way to do it in JSF?
EDIT: the environment is IBM Rational Application Developer (RAD), which have its own JSF implementation. Not sure if that makes a difference.
Putting request scoped data in session scope will bite you (very) hard if you're going to open the same page in multiple windows/tabs. Only use the session scope if the data itself is also really session scoped (excellent examples are the "logged-in user" and the "shopping cart", you want it to be exactly the same throughout the entire session). Again, don't put request scoped data in the session scope. It hurts both you and the enduser.
Just design your beans smart (it makes no sense to have different beans containing the same data) and make use of h:inputHidden where needed, if necessary in combination with managed property injection. It's indeed a bit a pain to code and maintain. You can on the other hand also just grab Tomahawk <t:saveState> if the to-be-passed data is actually as big as a "whole" managed bean. It costs only a single line in the JSF page and has always been of great assistance.
*For example, if i have page 1 with advanced search filters, which redirects to page 2, depending on the chosen item, and from page 2, you get another list were you can go to page 3 for details. Now each time i need to put all the params in the request scope/read them again, store them in hidden fields and get them back.
Whats exactly wrong with this method and whats a better way to do it in JSF?*
There's nothing wrong with this method. Maybe you coded it the wrong way which caused that it looks unnecessarily overcomplicated. I can't tell much as long as you don't post details about the code used.
As per your edit:
EDIT: the environment is IBM Rational Application Developer (RAD), which have its own JSF implementation. Not sure if that makes a difference.
This is not true. IBM doesn't have any JSF implementation. It has just a component library (the poorly maintained hx prefixed components, also known as "Faces Client Framework"). WSAD/RAD ships with Sun JSF RI (Mojarra) as standard JSF implementation, although it's usually a heavily outdated version. Ensure that you keep it updated.
I'm only starting out with JSF too to be honest, but I thought you can save managed beans in the session scope, thus being able to access the bean on each request? You can also save the state client-side avoiding nastiness about session stickyness and stuff.
So you could save the data you are currently passing as request parameters in a session-scoped managed bean, and it will be available to any requests in that user's session, destroyed when the session times out or is deliberately invalidated (say on user logout).
I don't think JSF currently supports conversation state which I think might be the exact solution to your problem, maybe a session scoped managed bean would be the pragmatic solution?
Make your managed-bean session scoped.
If you are using MyFaces you can use PageFlowScope. If using Seam then use Conversation scope.
If pageflowscope or conversation scope is not available, then use session scoped beans. In addition you can use PhaseListener to initialize or execute specific methods before the page gets called. In you case if the flow is page1 -> page2 -> page3, then initialize the session scoped bean in PhaseListener if page1 gets called.
I'll update with more info if you need.

Resources