OmniFaces 2.2 has been released
This version brings a bunch of new utility methods in
As usual, in the What's new page of the showcase site you can find an overview of all what's been added/changed/fixed for 2.2. The top three additions are the second life for the
Maven users: use
Instead being potentially deprecated, the OmniFaces
The trick is done by a synchronous XHR request during
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, even though the server side action may take place in only a few milliseconds (in case you're doing expensive tasks inside
In below example, the
This version brings a bunch of new utility methods in
Faces
, Messages
and Components
classes, a new <o:viewAction>
component, a new @ContextParam
annotation, a whole FileServlet
, and a second life for the @ViewScoped
annotation.As usual, in the What's new page of the showcase site you can find an overview of all what's been added/changed/fixed for 2.2. The top three additions are the second life for the
@ViewScoped
annotation, the new and refactored "BalusC FileServlet", and the new <o:viewAction>
.Installation
Non-Maven users: download OmniFaces 2.2 JAR and drop it in/WEB-INF/lib
the usual way, replacing the older version if any.Maven users: use
<version>2.2</version>
.<dependency>
<groupId>org.omnifaces</groupId>
<artifactId>omnifaces</artifactId>
<version>2.2</version>
</dependency>
For users who don't want to use CDI, there's the CDI-less 1.12.1 with
all 2.2 enhancements and fixes (but no brand new 2.x additions!).
UPDATE: FacesViews was broken in 1.12, so a 1.12.1 has been baked with
the hotfix. FacesViews continues to work fine in 2.2, so there's no 2.2.1.Second life for @ViewScoped
In standard JSF 2.0/2.1, the@PreDestroy
annotated
method on a standard JSF view scoped bean was never invoked when the
session expires. This was solved since OmniFaces 1.6 with its new CDI @ViewScoped
annotation. However, since JSF 2.2 this problem is solved on JSF's own @ViewScoped
beans, hereby basically making the OmniFaces CDI @ViewScoped
annotation superflous in JSF 2.2. You could as good just stick to JSF's own @ViewScoped
annotation.Instead being potentially deprecated, the OmniFaces
@ViewScoped
got a second life: immediately invoke @PreDestroy
when the browser unloads the page instead of waiting until the session
is expired. In other words, when the user navigates away by a GET link,
or closes the browser tab/window, or refreshes the page (by GET), then
the OmniFaces @ViewScoped
bean will immediately be destroyed. None of the both JSF 2.2 @ViewScoped
annotations support this.The trick is done by a synchronous XHR request during
beforeunload
event (thus not on the main thread! ;) ) via a helper script omnifaces:unload.js
which is automatically included when an OmniFaces @ViewScoped
bean is created. The XHR request sends a special request parameter omnifaces.event=unload
along with the OmniFaces specific view scope ID and the JSF javax.faces.ViewState
ID:var xhr = new XMLHttpRequest();
xhr.open("POST", window.location.href.split(/[?#;]/)[0], false);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("omnifaces.event=unload&id=" + id + "&" + VIEW_STATE_PARAM + "=" + viewState);
The existing OmniFaces RestorableViewHandler
has been extended with below lines:if (isUnloadRequest(context)) {
UIViewRoot createdView = createView(context, viewId);
createdView.restoreViewScopeState(context, getRenderKit(context).getResponseStateManager().getState(context, viewId));
BeanManager.INSTANCE.getReference(ViewScopeManager.class).preDestroyView();
responseComplete();
return createdView;
}
In a nutshell: when the omnifaces.event=unload
parameter
is present, then only create the view and restore any view scoped beans
(and thus don't build the view! which might be time consuming and unnecessarily create new beans) and finally destroy those beans, hereby immediately invoking the @PreDestroy
and freeing unused memory space on demand. After that, JSF is
instructed to immediately render the response and return the dummy
(non-built!) view, preventing potential unnecessary ViewExpiredException
.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, even though the server side action may take place in only a few milliseconds (in case you're doing expensive tasks inside
@PreDestroy
method, better delegate it to an @Asynchronous
EJB method or so). If the slow network/hardware lag is in your case
noticeable and thus undesireable, then better stick to JSF 2.2's own @ViewScoped
annotation and accept the postponed destroy.New FileServlet with advanced HTTP range and caching support
The well known "BalusC FileServlet" from the blog article FileServlet supporting resume and caching has been cleaned, refactored and reworked into an abstract template. Now, instead of copypasting and editing the code from the blog, you can simply extend fromorg.omnifaces.servlet.FileServlet
and implement the getFile()
method in its simplest form as below:@WebServlet("/media/*")
public class MediaFileServlet extends FileServlet {
@Override
protected File getFile(HttpServletRequest request) throws IllegalArgumentException {
return new File("/var/webapp/media", request.getPathInfo());
}
}
When referenced in e.g. a HTML5 <video>
tag like below:<video src="#{request.contextPath}/media/video.mp4" controls="controls" />
Then the FileServlet
will worry about properly streaming the media range requests. I.e. the servlet is able to return specific parts of the media file on a request with a HTTP Range
header. For example, only the bytes at exactly the index 1000 until
2000 on a file of 10MB long. This is mandatory for many media players in
order to be able to skip a certain range of the media stream quickly
enough and/or to improve buffering speed by creating multiple
connections which each requests different parts of the file. Also
download accelerators will take benefit of this on large files. They
will then simply open multiple HTTP connections which each downloads a
specific range of the file and afterwards put the pieces together.Fix unintuitive "if" attribute of <f:viewAction>
Theif
attribute of the standard <f:viewAction>
not really intuitive. It is checked during APPLY_REQUEST_VALUES
phase instead of INVOKE_APPLICATION
phase. This would make several straightforward use cases to fail.In below example, the
FooConverter
may convert a non-null
parameter to null
without causing a validation or conversion error, and the intent is to redirect the current page to otherpage.xhtml
when the converted result is null
.
<f:viewParam name="foo" value="#{bean.foo}" converter="fooConverter" />
<f:viewAction action="otherpage" if="#{bean.foo eq null}" />
However, this fails because the if
attribute runs before
the conversion has taken place, when the component wants to check
whether it should queue the action event. This happens during APPLY_REQUEST_VALUES
phase. The OmniFaces <o:viewAction>
solves this by postponing the evaluation of the if
attribute to the INVOKE_APPLICATION
phase.
<f:viewParam name="foo" value="#{bean.foo}" converter="fooConverter" />
<o:viewAction action="otherpage" if="#{bean.foo eq null}" />
The implementation of the extended component is really simple. It's nothing more than below:@Override
public void broadcast(FacesEvent event) throws AbortProcessingException {
if (super.isRendered()) {
super.broadcast(event);
}
}
@Override
public boolean isRendered() {
return !isImmediate() || super.isRendered();
}
It gives a bit of thinking as the if
attribute is basically a renamed rendered
attribute, and one would wonder why !isImmediate()
is checked instead of e.g. the current phase ID or a hardcoded true
. But this way the original and intented behavior of immediate
attribute is maintained. Actually, the <o:viewAction immediate="true">
behaves exactly the same as a <f:viewAction immediate="true">
.
No comments:
Post a Comment