2
votes

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 !

1
Thats the implementation I used - in terms of correct approach I the impression i get is that you should avoid this kind of thing in JSF, but the reality is sometimes you need tofarrellmr
Why not use conversation scoped beans?Kukeltje
Because this particular project uses Spring IoC instead of CDI... so no built-in Conversation scope so far. :( In any case, I'm also looking into creating new (Spring) Scopes that can achieve the same functionality. So far, I have successfully created a TabScope for PrimeFaces TabView component. Each Tab can have its own scope of JSF beans, created when the tab is constructed, and destroyed when the tab is closed. I guess I could use the same approach to develop a more generic Conversation Scope ...Maikel Nait

1 Answers

0
votes

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?