2

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 !

Community
  • 1
  • 1
Maikel Nait
  • 251
  • 3
  • 18
  • 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 to – farrellmr Aug 18 '16 at 08:37
  • Why not use conversation scoped beans? – Kukeltje Sep 01 '16 at 07:14
  • 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 Sep 02 '16 at 01:58

1 Answers1

0

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?

Radek
  • 393
  • 3
  • 15
  • SPA is not an unreccommended way either... You just cannot use a functionality that is related to specific views. You **can** use other scoped beans (conversation comes to mind) that you can more easily control instead of 'abusing' viewscoped beans. And you can read that he sort of solved it like this – Kukeltje Oct 05 '17 at 19:23
  • Hi Radek and Kukeltje. So far, this is the approach that we are using, and works fine, as far as the server side is concerned. Biggest problem at the end, was on the client side, as many PrimeFaces jQuery UI widgets are not fully SPA-ready, causing big memory leaks. We have to loop through all the PF widgets on each SPA page before moving to a new view, and manually unbind events for specific PF widgets, and some other tricky scenarios (disable pollings, idle timers, key bindings...). But so far, I can say we are using JSF 2.2 with PrimeFaces and SPA approach without big problems now. – Maikel Nait Oct 09 '17 at 05:49
  • Hi Maikel (and Kukeltje) - thank you very much for yours experiences and options. I'm glad to see, that this SPA approach is working in real world. We have started using Omnifaces (have intalled CDI on Tomcat before) with their ViewScopedBean (that is destroyed after every POST/GET request, not like ViewScopedBean in standard JSF 2.3). So now we have a possibility use Omnifaces/ViewScopedBean with SPA approach. I hope, that is good choice. – Radek Oct 10 '17 at 10:55