• cdi
  • components
  • contexts
  • converters
  • el
  • eventlisteners
  • exceptionhandlers
  • facesviews
  • filters
  • functions
  • managedbeans
  • push
  • resourcehandlers
  • scripts
  • servlets
  • taghandlers
  • utils
  • validators
  • viewhandlers
 - 
  • ContextParam
  • Cookie
  • Eager
  • FacesConverter
  • FacesValidator
  • GraphicImageBean
  • Param
  • ViewScoped

Available since OmniFaces 1.6

The CDI view scope annotation, with more optimal handling of bean destroy as compared to standard JSF one.

In standard JSF 2.0/2.1, the @PreDestroy annotated method on a view scoped bean was never invoked when the session expires. Since OmniFaces 1.6, this CDI view scope annotation will guarantee that the @PreDestroy annotated method is also invoked on session expire. Since JSF 2.2, this problem is solved on native JSF view scoped beans, hereby making this annotation superflous in JSF 2.2.

However, there may be cases when it's desirable to immediately destroy a view scoped bean as well when the browser unload event is invoked. I.e. when the user navigates away by GET, or closes the browser tab/window. None of the both JSF 2.2 view scope annotations support this. Since OmniFaces 2.2, this CDI view scope annotation will guarantee that the @PreDestroy annotated method is also invoked on browser unload. This trick is done by a synchronous XHR request via an automatically included helper script omnifaces:unload.js. There's however a small caveat: on slow network and/or poor server hardware, there may be a noticeable lag between the enduser action of unloading the page and the desired result. If this is undesireable, then better stick to JSF 2.2's own view scope annotations and accept the postponed destroy.

Since OmniFaces 2.3, the unload has been further improved to also physically remove the associated JSF view state from JSF implementation's internal LRU map in case of server side state saving, hereby further decreasing the risk at ViewExpiredException on the other views which were created/opened earlier. As side effect of this change, the @PreDestroy annotated method of any standard JSF view scoped beans referenced in the same view as the OmniFaces CDI view scoped bean will also guaranteed be invoked on browser unload.

Since OmniFaces 2.6, this annotation got a new attribute: saveInViewState. When using client side state saving, this attribute can be set to true in order to force JSF to store whole view scoped bean instances annotated with this annotation in the JSF view state instead of in the HTTP session. For more detail, see the saveInViewState().

In a nutshell: if you're on JSF 2.0/2.1, and you can't upgrade to JSF 2.2, and you want the @PreDestroy to be invoked on sesison expire too, then use OmniFaces 1.6+ with this view scope annotation. Or, if you're on JSF 2.2 already, and you want the @PreDestroy to be invoked on browser unload too, then use OmniFaces 2.2+ with this view scope annotation. Or, if you want to store whole view scoped beans in the JSF view state when using client side state saving, then use OmniFaces 2.6+ with this view scope annotation and the saveInViewState attribute set to true.

Related JSF issues:

Usage

Just use it the usual way as all other CDI scopes. Watch out with IDE autocomplete on import that you don't accidentally import standard JSF's own one.

import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;

@Named
@ViewScoped
public class OmniCDIViewScopedBean implements Serializable {}

Please note that the bean must implement Serializable, otherwise the CDI implementation will throw an exception about the bean not being passivation capable.

Under the covers, CDI managed beans with this scope are via ViewScopeManager by default stored in the session scope by an UUID based key which is referenced in JSF's own view map as available by UIViewRoot.getViewMap(). They are not stored in the JSF view state itself as that would be rather expensive in case of client side state saving.

In case you are using client side state saving by having the javax.faces.STATE_SAVING_METHOD context parameter set to true along with a valid jsf/ClientSideSecretKey in web.xml as below,

<context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
</context-param>
<env-entry>
    <env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value><!-- See http://stackoverflow.com/q/35102645/157882 --></env-entry-value>
</env-entry>

And you explicitly want to store the whole view scoped bean instance in the JSF view state, then set the annotation's saveInViewState attribute to true.

import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;

@Named
@ViewScoped(saveInViewState=true)
public class OmniCDIViewScopedBean implements Serializable {}

It's very important that you understand that this setting has potentially a major impact in the size of the JSF view state, certainly when the view scoped bean instance holds "too much" data, such as a collection of entities for a data table, and that such beans will in fact never expire as they are stored entirely in the javax.faces.ViewState hidden input field in the HTML page. Moreover, the @PreDestroy annotated method on such bean will explicitly never be invoked, even not on an unload as it's quite possible to save or cache the page source and re-execute it at a (much) later moment.

It's therefore strongly recommended to use this setting only on a view scoped bean instance which is exclusively used to keep track of the dynamically controlled form state, such as disabled, readonly and rendered attributes which are controlled by ajax events.

This setting is NOT recommended when using server side state saving. It has basically no effect and it only adds unnecessary serialization overhead. The system will therefore throw an IllegalStateException on such condition.

Configuration

By default, the maximum number of active view scopes is hold in a LRU map in HTTP session with a default size equal to the first non-null value of the following context parameters:

If none of those context parameters are present, then a default size of 20 will be used. When a view scoped bean is evicted from the LRU map, then its @PreDestroy will also guaranteed to be invoked.

This setting has no effect when saveInViewState attribute is set to true.

Using window.onbeforeunload

If you have a custom onbeforeunload handler, then it's strongly recommended to use plain vanilla JS window.onbeforeunload = function instead of e.g. jQuery $(window).on("beforeunload", function) or DOM window.addEventListener("beforeunload", function) for this. This way the @ViewScoped unload can detect it and take it into account and continue to work properly. Otherwise the view scoped bean will still be destroyed in background even when the user cancels and decides to stay in the same page.

Below is a kickoff example how to properly register it, assuming jQuery is available, and that "stateless" forms and inputs (for which you don't want to trigger the unsaved data warning) have the class stateless set:

$(document).on("change", "form:not(.stateless) :input:not(.stateless)", function() {
    $("body").data("unsavedchanges", true);
});
OmniFaces.Util.addSubmitListener(function() { // This hooks on Mojarra/MyFaces/PrimeFaces ajax submit events too.
    $("body").data("unsavedchanges", false);
});
window.onbeforeunload = function() {
    return $("body").data("unsavedchanges") ? "You have unsaved data. Are you sure you wish to leave this page?" : null;
};

CDI issues in EAR

Note that CDI has known issues when OmniFaces is bundled in multiple WARs in a single EAR and the CDI feature is based on an Extension. It's important to understand that those issues are not related to OmniFaces, but to the broken CDI spec. For an overview of those issues, please refer Known issues of OmniFaces CDI features in combination with specific application servers.

Demo

CDI view scoped bean

Status:

  • It's now: Fri Mar 24 17:59:53 EDT 2017
  • Session ID: WlIhi-KehuulZl4XWd0TVUy3
  • CDI view scoped bean: org.omnifaces.showcase.cdi.CdiViewScopedBean@89ea87

Messages from CDI view scoped bean:

  • PostConstruct invoked: org.omnifaces.showcase.cdi.CdiViewScopedBean@89ea87

JSF view scoped bean

Status:

  • It's now: Fri Mar 24 17:59:53 EDT 2017
  • Session ID: WlIhi-KehuulZl4XWd0TVUy3
  • JSF view scoped bean: org.omnifaces.showcase.cdi.JsfViewScopedBean@353c5f

Messages from JSF view scoped bean:

  • PostConstruct invoked: org.omnifaces.showcase.cdi.JsfViewScopedBean@353c5f
Demo source code
<h3>CDI view scoped bean</h3>
<h:form id="cdiViewScopedForm">
    <p>Status:</p>
    <ul>
        <li>It's now: #{now}</li>
        <li>Session ID: #{session.id}</li>
        <li>CDI view scoped bean: #{cdiViewScopedBean}</li>
    </ul>
    <p>
        <h:commandButton value="submit form without ajax" action="#{cdiViewScopedBean.submit}" />
        <f:ajax execute="@form" render="@form :jsfViewScopedForm">
            <h:commandButton value="submit form with ajax" action="#{cdiViewScopedBean.submit}" />
            <h:commandButton value="rebuild view" action="#{cdiViewScopedBean.rebuildView}" />
            <h:commandButton value="navigate on POST" action="#{cdiViewScopedBean.navigate}" />
        </f:ajax>
        <h:button value="refresh page" />
    </p>
    <p>Messages from CDI view scoped bean:</p>
    <h:messages for="cdiViewScopedForm" />
</h:form>

<hr />

<h3>JSF view scoped bean</h3>
<h:form id="jsfViewScopedForm">
    <p>Status:</p>
    <ul>
        <li>It's now: #{now}</li>
        <li>Session ID: #{session.id}</li>
        <li>JSF view scoped bean: #{jsfViewScopedBean}</li>
    </ul>
    <p>
        <h:commandButton value="submit form without ajax" action="#{jsfViewScopedBean.submit}" />
        <f:ajax execute="@form" render="@form :cdiViewScopedForm">
            <h:commandButton value="submit form with ajax" action="#{jsfViewScopedBean.submit}" />
            <h:commandButton value="rebuild view" action="#{jsfViewScopedBean.rebuildView}" />
            <h:commandButton value="navigate on POST" action="#{jsfViewScopedBean.navigate}" />
        </f:ajax>
        <h:button value="refresh page" />
    </p>
    <p>Messages from JSF view scoped bean:</p>
    <h:messages for="jsfViewScopedForm" />
</h:form>