We are using an SPA approach in our JSF 2.2 + PrimeFaces enabled application.
The basic idea was originally described very well here:
Refreshing dynamic content with AJAX in JSF using SPA approach
But, as we know, using this SPA approach has a drawback when using #ViewScoped beans.
As we are actually always staying in the same JSF View, #ViewScoped beans are not removed from memory, when we replace the content of a panel group with the new SPA content.
I have found a solution, but I would like to know if it's a correct approach, and/or if there is anything missing.
Basically, in our NavigationService bean, which holds the name of the page to be rendered during the SPA AJAX request, we always execute the following code:
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
it.remove();
}
}
This should ensure that, after the new SPA snippet is rendered, all the previous existing #ViewScoped beans are removed.
However, I think the above code only removes the View Scoped beans, but not the related View States. Is that correct ?
I found an old blog entry which seems to do a little more logic:
http://javaevangelist.blogspot.sg/2014/08/jsf-21-tip-of-day-clearing-viewscope.html
but I dunno if it's correct as well.
Additionally, if we want to support multiple window tabs, our NavigationService bean, holding the current SPA snippet page name, must be #ViewScoped as well, and this introduces a small problem:
When running the above code for removing all the existing #ViewScoped beans... we have to exclude the NavigationService bean itself !! Otherwise we end up loading always the same page, because a new instance of the NavigationService is instantiated, with a default SPA page name, instead of the new one.
So, all in all, our code looks finally like this, where we keep a Map of "excluded" bean names, that we don't want to remove on a SPA page refresh (namely the NavigationService bean holding the SPA page name)
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
if(!exclusionViewScopedBeans.contains(entry.getKey())) {
logger.info("Removing an instance of a #ViewScoped bean -> " + entry.getKey());
it.remove();
}
}
}
Now the question ... is this the correct approach for handling these kind of SPA situations? Are we missing something here?
Any feedback would be greatly appreciated... thanks a lot in advance !
I think, there is no better solution for removing/clearing ViewScopedBeans in SPA (single page app) than yours Maikel:
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
it.remove();
}
}
I have tried find a lot, but SPA is not "official recommended" way of using JSF, that is multi-page app, so SPA requires more fiddling around with.
Or, Maikel, have you found or do you use something another way?
Related
This question already has answers here:
How to choose the right bean scope?
(2 answers)
Closed 2 years ago.
I'm trying to implement JSF backing beans using CDI beans as suggested by the depreciation of #ManagedBean and it's scope annotations, but I'm struggling with the right use examples, I'm trying to implement view backing bean with #Model (javax.enterprise.inject.Model) which is #Named #RequestScoped.
I found this question but it's using a ViewScope bean, how would I implement the same functionality with RequestScoped (Preferably #Model), What is best practice use of #Model in general?
Edit 1:
I tried creating a new Product in the EditProduct PostConstruct:
#Model
public class EditProduct {
private Product product; // +getter +setter
#Inject
private ProductService productService;
#PostConstruct
public void init(){
product = new Product();
}
public String save() {
productService.save(product);
return "/products?faces-redirect=true";
}
// ...
}
and then setting the product via
<f:viewParameter name="product-id" target="#{editProduct.product}"
converter="#{productConverter}" />
it's working but I'm looking for a best practice.
A request scoped backing bean is meant to keep the application memory footprint as low as possible hence using them for supporting views with #Model annotation makes a lot of sense, the draw back is having to reach for the persistence data storage on every request that deals with data so a best use case for #Model bean is:
Basically every thing.
things like:
Events handling for JSF pages
Lazy loading of data
Validation and converting and other code execution
ETC.... yes every thing else
Those things are easily done best in request scoped beans, but then what are the roles of other beans?
In simplistic terms we can assume:
#ViewScoped to support data heavy pages where user edits data with many interactions and each interaction is a request but hitting the database for each one will be costly.
#SessionScoped for session data, authentication, credentials and configuration for the user.
#ApplicationScoped the state-full singleton of CDI.
.... each other scope has it's uses, but for a good web application #Model should be the default and the others has specific uses cases.
You should be able to also add the #Named annotation and it will be exposed as editProduct.
https://memorynotfound.com/cdi-managed-bean-example-with-named/
EDIT: See comment
How can I get the posted form data in the backing component in the
processUpdates method?
#Override
public void processUpdates(FacesContext context) {
//get here rendered html code
}
Or can I get the posted form data in the decode method?
[Edit]:
My goal is to get the posted form data - Not to get the generated html code (Sry I wasn't precisely)
It is unclear what you want to achive, yet. I mean, at high level.
UIComponent.decode and processUpdates are medium-level lifecycle APIs which should be overriden when you want to extend the framework.
If you just need to use the framework, you need a managed bean, not a backing component.
Furthermore, generally only components that extend UIInput need to hook in those phases, because they are bound to a value="#{...}" value expression (which in turn refers to a managed bean, in most cases), and need to synchronize those values with the bound expression.
I suspect you are uselessly complicating your life: hooking into medium or low-level APIs is a real pain if you don't have an excellent understanding about how the framework operates.
Anyway, the standard request parameters decode into input component is this:
String clientId = this.getClientId(context);
Map<String, String> requestMap = context.getExternalContext().getRequestParameterMap();
String newValue = requestMap.get(clientId);
if (newValue != null)
{
this.setSubmittedValue(newValue);
}
Please, post the full xhtml facelet code (not the composite one, but the facelet using that composite), so I can understand where you want to go and I can try to point you to the right tool to use.
All of my pages are backed their own ViewScoped bean, but I'm finding that there are a lot of similar methods used on these pages. For example, a user may want to view dates in their preferred time zone so each time a page is loaded, the DB is queried for what their preferred time zone is.
So my initial thought was to create a ViewScoped bean to manage this. The timeZone value would be only be "good" for the lifetime of the page and they would be lazy-loaded to avoid unnecessary database hits:
#Named
#ViewScoped
public class Preference implements Serializable {
#Inject
private SessionManager sessionManager;
#EJB(name = "PreferencesReadFacade")
private PreferencesReadFacadeRemote prefReadFacade;
private HashMap<String, Object> cache = new HashMap<>();
/**
* #return the user's TimeZone preference
*/
public String getTimeZone() {
if(cache.get("TimeZone") == null) {
cache.put("TimeZone", prefReadFacade.getUserPreference(sessionManager.getUserId(), "TimeZone").toString());
}
return cache.get("TimeZone").toString();
}
}
Usage:
<h:outputText value="#{preference.timeZone}"/>
Is there anything wrong with this type of methodology? Is there a better way of doing something like this?
EDIT: Would like to add that I'm using ICEfaces and Omnifaces so if there are resources in these libraries at my disposal, I'm certainly open to using those.
Your approach is bsolutely correct - you may reuse the same bean in multiple pages regardless of its scope. If those pages are in the same scope, a bean would be reused, otherwise a new bean would be created with an empty cache. If the scope is ViewScoped, the bean would be recreated for every page, hence DB would be accessed first when the data is needed on after a page loads.
You may also make your common bean a base bean of other viewscoped beans, which are constructed for a particular page (they must remain viewscoped).
Or, you may inject your Preference bean into any other Named bean, which is used in your pages. In this way, you may inject it to a bean with any scope, but CDI will always give you the same bean for a view (within viewscope), but different when you redirect to a new page.
But your solution is equally correct, if not even better.
You can create one #SessionScoped bean and hold there user preferences like time zone. Then #Inject it to your #ViewScoped beans and get time zone from #SessionScoped. As long as http session lives, the DB query will be done only once in the user session if you do it in #PostConstruct and assign to variables.
I have a proper newbie SEAM question, I want to redirect the user to a different page from a backing bean
I know in most cases you should use pages.xml however there could be a number of different pages depending on the bean logic so it seems like it should be a lot easier to do from the bean.
I cant see any examples of people doing this so Im guessing there is a reason why, Maybe something like this would work??...
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
((HttpServletRequest) ec.redirect("http://example.com/");
You can use the Redirect component:
#Name("yourBean")
public class YourBean {
#In
Redirect redirect;
public void yourMethod() {
redirect.setViewId("/someView.xhtml");
redirect.setParameter("someParam", "someValue");
redirect.execute();
}
}
Or going with FacesManager:
FacesManager.instance().redirect("/someView.xhtml", paramMap,
conversationPropagationEnabled, includePageParams);
These only work for other JSF views (ie .xhtml). If you just want an arbitrary URL, you can use the FacesContext as you mentioned in your question.
I need to preload some data to be displayed when the page loads. The initialization steps are performed on a #PostConstruct-annotated method but now i need to use a parameter in order to get the data.
What i'm trying to do:
#PostConstruct
public void init()
{
List data = getDataFromDB(parameter) /*Need to read a parameter created somewhere else*/
}
Is there a way to achieve this?
Thanks in advance
It's kind of hard to say what you mean by "a parameter set somewhere else". I will assume that "somewhere else" means "sent from browser by HTTP". In such case you should create a standard property in your managed bean and:
in JSF 2.0 you could annotate it with #ManagedProperty("#{param.nameOfParameterToRead}")
in JSF 1.2 and less - use managed-property element in your bean description (faces-config.xml).
Like this:
#ManagedBean
#RequestScoped
class MyManagedBean {
#ManagedProperty("#{param.id}")
public Integer id;
#PostConstruct
public void init(){
data = getDataFromDB(id)
}
// setters and getters (mandatory, even though annotation is on an attribute!!!)
}
Careful: injecting properties does not use JSF converters, so it is best to inject strings and take care of conversion in your own code.
how about reading from Properties file, or fetching List from DB ??