JSF 2 GETs Bookmarkable URLs
Join the DZone community and get the full member experience.
Join For FreeJSR-314: JavaServer Faces (JSF) 2.0 demonstrates a strong evolution of the JSF framework driven by de facto standards that emerged out of the JSF community and participating vendor's products. This article, the second installment in covering JSF 2.0 features contributed by Red Hat, or which Red Hat participated in extensively, covers the new features that bring formalized GET support to a framework traditionally rooted in POST requests. The primary building blocks of this support are view parameters and a pair of UI components that produce bookmarkable hyperlinks. Both features incubated in Seam and, therefore, should be familiar to any Seam developer. They are also features for which the JSF community has passionately pleaded.
Author's Note: Many thanks to Pete Muir, who played a pivotal role as technical editor of this series.
Read the other parts in this article series:
Part 1 - JSF 2: Seam's Other Avenue to Standardization
Part 2 - JSF 2 GETs Bookmarkable URLs
Part 3 -
Part 4 -
Part 5 -
Every user session must start somewhere. JSF was designed with the expectation that the user always begins on a launch view. This view captures initial state and allows the user to indicate which action to invoke by triggering a UI event, such as clicking a button. For instance, to view a mortgage loan, the user might enter its id into a text box and then click the "Lookup" button.
The assumption that this scenario is the norm is surprising since it overlooks that fact that the web was founded on the concept of a hyperlink. A hyperlink points to a resource (URI), which may already contain the original state and intent, such as to view a mortgage loan summary. There's no need to bother the user with a launch view in this case.
While hyperlinks are most often used in web sites, they apply to web applications as well (see this blog entry for a discussion about the difference between a web site and a web application). Hyperlinks support reuse by serving as an exchange language in composite applications. One application can link to a resource in another application in lieu of having to duplicate its functionality. In fact, the request may be coming from a legacy application that isn't even web-based. In that case, you'll likely be plopping the user into the web application somewhere in the middle. As it turns out, this situation is quite common. When you visit a blog, do you start on the search screen to find an entry to read? Not likely. More times than not, you click on a link to view a specific blog entry.
The point to take away from this discussion is that initial requests (referred to as non-faces requests in JSF) can be just as important as form submissions (faces requests), whether in a web site or web application. In the past, JSF has struggled to support the scenario cited above, placing much more emphasize on faces requests. JSF 2 rectifies this imbalance by introducing view parameters and hyperlink-producing UI components. View parameters allow the application to respond to a resource request by baking formal processing of request parameters into the JSF life cycle for both GET and POST requests. View parameters are not limited to consuming data. They are bi-directional. JSF 2 can propagate the data captured by view parameters when generating bookmarkable URLs, with complementary behavior for redirect URLs produced by redirect navigation cases.
We'll start by examining view parameters, how they are defined and how they are worked into the JSF life cycle. You'll then discover how they work in tandem with the new hyperlink-producing components and the navigation handler to bring "bookmarkable" support to JSF.
Introducing view parameters
The API documentation describes a view parameter, represented by the javax.faces.component.UIViewParameter component class, as a declarative binding between a request parameter and a model property. The binding to the model property is expressed using an EL value expression (e.g., #{blog.entryId}). If the expression is omitted, the request parameter is bound instead to a request-scoped variable with the same name.
Here's a simple example of a view parameter that maps the value of a request parameter named id to the JavaBean-style property named entryId on a managed bean named blog.
<f:viewParam name="id" value="#{blog.entryId}"/>
Assuming the entryId property on the blog managed bean is of type long, a value of 9 will be assigned to the property when the following URL is requested:
http://domain/blog/entry.jsf?id=9
But wait, there's more! The value of the request parameter is first converted and validated before being assigned to the model property. This behavior should sound familiar. That's because it mirrors the processing of form input bindings on a faces request. In a way, view parameters turn the query string into an alternative form submission. And like form inputs, view parameters are also processed during faces requests. The complete view parameter life cycle is covered later when we look at view parameter propagation
Before going any further, it's important to point out that view parameters are only available when using the new View Declaration Language (VDL), a standardized version of Facelets. The primary reason is because the JSR-314 EG agreed that no new features should be made to support JSP since it's deprecated as a view handler in JSF 2.
Perhaps you are thinking...
Isn't this already possible?
If you are savvy JSF developer, you're perhaps aware that it's already possible to map a request parameter value to a model property. The assignment is declared by referencing an element of the #{param} map in a <managed-property> element of a managed bean declaration. For instance, you could alternatively map the id request parameter to the blog managed bean in faces-config.xml as follows:
<managed-bean> <managed-bean-name>blog</managed-bean-name> <managed-bean-class>com.acme.Blog</managed-bean-class> <managed-property> <property-name>entryId</property-name> <value>#{param['id']}</value> </managed-property> </managed-bean>
The similiarities end there. View parameters go above and beyond this simple assignment by providing:
- View-oriented granularity (the property mapping in the managed bean definition is global to the application)
- Custom converters and/or validators (along with failure messages)
- Bi-directionality
It's hard to say which feature is the most important, but bi-directionality is certainly the most unique. Since view parameters are a mapping to a JavaBean-style property, the value can be read from the property and propagated to the next request using either the query string or the UI component tree state (depending on the type of request). You are going to find out how useful this bi-directionality can be later on in the article.
Suffice to say, while the property mapping in the managed bean definition works, it's pretty anemic. View parameters are far more adequate and robust in contrast. Pertaining to the topic in this article, view parameters are the key to bringing bookmarkable support to JSF. And since bookmarks link to specific views, so must view parameters.
It's all in the view
As you may have guessed, view parameters are view-oriented. That means they somehow need to be associated with one or more views (as opposed to being linked to a managed bean, for instance). Up to this point, however, there was no facility for associating extensible, non-rendering metadata with a JSF view. So the EG first had to find a place within the UI component tree to stick metadata like view parameters. That led to the introduction of the metadata facet of UIViewRoot. The next section will introduce this new facet and how it's used to host view parameters for a particular view, or even a set of views. Then we get into how view parameters get processed in the JSF life cycle.
The view metadata facet
View parameters provide information about how request parameters should be handled when a view is either requested or linked to. The view parameters are not rendered themselves. Therefore, we say that they are part of the view's metamodel and described using metadata. So the question is, "Where should this metadata live?"
It turns out that a JSF view, which is represented at the root by the javax.faces.component.UIViewRoot component class, already accommodates some metadata. Currently, this metadata consists of string values to define settings such as the locale, render kit, and content type of the view, and method expressions that designate view-specific phase observers. For example:
<f:view locale="en_US" afterPhase="#{myBean.afterPhaseListener}"> ... </f:view>
While values can be assigned to these metadata properties explicitly in Java code, more often they are assigned declaratively using corresponding attributes of the <f:view> component tag. But neither UIViewRoot or it's component tag <f:view> can accommodate complex metadata--that is, metadata which cannot be described by a single attribute. That's were the view metadata facet comes in.
The view metadata facet is a reserved facet of UIViewRoot, named javax_faces_metadata, that can hold an arbitrarily complex branch of UI components that provide additional metadata for a view. Facets are special because they are ignored by a UI component tree traversal, requiring an imperative request to step into one of them. This aspect makes a facet an ideal candidate for tucking away some metadata for the view that can be accessed on demand.
The view metadata facet looks like any other facet in the UI component tree. It must be declared as a direct descendant of <f:view> within a view template as follows:
<f:view> <f:facet name="javax_faces_metadata"> ... </f:facet> ... </f:view>
Note: If you are using Facelets, you may not be familiar with the <f:view> tag since it's optional in Facelets. When you add it to your template, it must be the outer-most component tag, but it does not have to be the root of the document.
Since the view metadata facet is a built-in facet, and is expected to be heavily used, the alias tag <f:metadata> was introduced as a shorthand for the formal facet definition shown above:
<f:view> <f:metadata> ... </f:metadata> ... </f:view>
We now have a place in the UI component tree to store metadata pertaining to the view. But why define the metadata in the view template? It's all about reuse and consistency.
Describing view metadata with UI components
There are two important benefits to defining the metadata within the view template. First, it circumvents introducing yet another XML file with its own schema that developers would have to learn. More importantly, it allows us to reuse the UI component infrastructure to define behavior, such as registering a custom converter or validator, or to extract common view parameters into an include template.
Since we're using UI components to describe the view metadata, then it makes sense to treat the UIViewParameter like any other input component. In fact, it extends UIInput. That allows us to register custom converters and validators on a UIViewParameter without any special reservations. Here's an example:
<f:viewParam name="id" value="#{blog.entryId}"> <f:validateLongRange minimum="1"/> </f:viewParam>
Note: Later in this series you'll learn that like input components, view parameters can enforce constraints defined by Bean Validation annotations (or XML), making the explicit validation tags such as this unnecessary.
But there is one caveat to embedding the view metadata in the template. Without special provisions, extracting the metadata would require building the entire view (i.e., UI component tree). Not only would this be expensive and unnecessary if the intent is not to render the view, it could also have side effects. When the component tree is built, value expressions in Facelets tag handlers get evaluated, potentially altering the state of the system.
To prevent these counteractions, the view metadata facet is given special treatment in the specification. Specifically, it must be possible to be extract and built it separately from the rest of the component tree. Earlier, I mentioned that view parameters are only available in Facelets, and not JSP, because of an executive decision. There's also a technical reason why view parameters rely on Facelets. Only Facelets can provide the necessary separation between template parsing and component tree construction that allows a template fragment to be processed in isolation. The result of this operation is a genuine UI component tree, represented by UIViewRoot, that contains only the view metadata facet and its children. For all intents and purposes, it's as though the view template only contained this one child element.
Using the following logic, it's possible to retrieve the metadata for an arbitrary view at any point in time. This data mining will come in to play later when we talk about view parameter propagation.
String viewId = "/your_view_id.xhtml" FacesContext ctx = FacesContext.getCurrentInstance(); ViewDeclarationLanguage vdl = ctx.getApplication().getViewHandler() .getViewDeclarationLanguage(ctx, viewId); ViewMetadata viewMetadata = vdl.getViewMetadata(ctx, viewId); UIViewRoot viewRoot = viewMetadata.createMetadataView(ctx); UIComponent metadataFacet = viewRoot.getFacet(UIViewRoot.METADATA_FACET_NAME);
At this point you could retrieve the UIViewParameter components, which are children of the facet, to perhaps access the view parameter mappings. More likely, though, you'll be looking for your own custom components so you can execute custom behavior before the view is rendered (e.g., view actions).
The extraction of the view metadata is very clever because, while it only builds a partial view, it still honors Facelets compositions. That means you can put your metadata into a common template and include it. Using some creative arrangement, you can apply common metadata to a pattern of views. Here's an example:
<f:view> <f:metadata> <ui:include src="WEB-INF/fragments/common_view_metadata.xhtml"/> ... </f:metadata> ... </f:view>
You've learned that defining a view metadata facet provides the following services for JSF:
- Arbitrarily complex metadata, which can reuse existing component infrastructure
- Metadata is kept with the view, or in a shared template, instead of in an external XML file
- Can be extracted and processed without any side effects (idempotent)
- Common metadata declarations can be shared across multiple views
Now that you are well versed in the view metadata facet, it's time to work out a concrete example of view parameters in practice. We'll look at how to enforce preconditions and load data on an initial request using information from the query string. Then you'll learn how that information gets propagated as the user navigates to other views.
Weaving parameters into the life cycle
This article has alluded several times to the use case of loading a blog entry from a URL by passing the value of the id parameter to our managed bean on an initial request. Let's allow this scenario to play out. Here's the URL the user might request coming into the site:
http://domain/blog/entry.jsf?id=9
We'll start by asking what we do with the value once it is assigned to the entryId property of the blog managed bean. One approach is to load the entry lazily as soon as it's referenced in the UI.
<h2>#{blog.blogEntry.title}</h2> <p>#{blog.blogEntry.content}</p>
Here's what the managed bean would look like to support this approach:
@ManagedBean(name = "blog") public class Blog { private Long entryId; private BlogEntry blogEntry; public Long getEntryId() { return entryId; } public void setEntryId(Long entryId) { this.entryId = entryId; } public BlogEntry getBlogEntry() { if (blogEntry == null) { blogEntry = blogRepository.findEntry(entryId); } return blogEntry; } }
Of course, it doesn't make any sense to display an entry without an id (and could even lead to a NullPointerException). So we should really make the id request parameter required. We'll also add a message if it is missing.
<f:metadata> <f:viewParam name="id" value="#{blog.entryId}" required="true" requiredMessage="No entry specified."> <f:validateLongRange minimum="1"/> </f:viewParam> ... </f:metadata>
In the case a required request parameter is missing, you can display the error message using the <h:messages> tag. Conversion and validation failures are recorded as global messages since there's no view element with which to associate.
<h:messages globalOnly="true"/>
But these preconditions still don't stop the view from being rendered if a request parameter is missing or invalid. What we need is a way to execute an initialization method that parallels an action invocation on a postback. That would allow us to get everything sorted before the user sees a response.
View initialization
While view parameters provide the processing steps from retrieving the request value to updating the model, they do not furnish the action invocation and navigation steps that are part of the faces request life cyle. That means you have to fall back to lazy loading the data as the view is being rendered (i.e., encoded). You are also missing a definitive point to fine tune the UI component tree programmatically before it's encoded. Fortunately, another new feature in JSF 2, system events, makes it possible to perform a series of initialization steps before view rendering begins.
Systems events notify registered listeners of interesting transition points in the JSF life cycle at a much finer-grained level than phase listeners. In particular, we are interested in the PreRenderViewEvent, which is fired immediately after the component tree is built (but not yet rendered). If the word "registered" evokes dreadful memories of XML descriptors, fear not. Observing the event we are interested in is just a matter of appending one or more <f:event> elements to the view metadata facet.
The <f:event> tag has two required attributes, type and listener. The type attribute is the name of the event to observe derived by removing the Event suffix from the end of the event class name and decaptializing the result. We are only interested in one event, preRenderView. The listener attribute is a method binding expression pointing to either a no-arguments method or a method that accepts a SystemEvent.
<f:metadata> ... <f:event type="preRenderView" listener="#{blog.loadEntry}"/> </f:metadata>
We can use this method to retrieve the blog entry before the view is rendered.
public void loadEntry() { blogEntry = blogRepository.findEntry(entryId); }
If the entry cannot be found, you could add conditional logic to the view to display an error message:
<c:if test="#{blog.blogEntry eq null}"> The blog entry you requested does not exist. </c:if>
Ideally, it would be better not to display the view at all. You can force a navigation to occur using the NavigationHandler API.
public void loadEntry() { try { blogEntry = blogRepository.findEntry(entryId); } catch (NoSuchEntryException e) { FacesContext ctx = FacesContext.getCurrentInstance(); ctx.getApplication().getNavigationHandler() .handleNavigation(ctx, "#{blog.loadEntry}", "invalid"); } }
The only problem is that the listener method is going to be invoked even if the view parameter could not be successfully converted, validated and assigned to the model property. Once again, there's a JSF 2 feature to the rescue. You can use the new isValidationFailed() method on FacesContext to check whether a conversion or validation failure occurred while processing the view parameters.
public void loadEntry() { FacesContext ctx = FacesContext.getCurrentInstance(); if (ctx.isValidationFailed()) { ctx.getApplication().getNavigationHandler() .handleNavigation(ctx, "#{blog.loadEntry}", "invalid"); return; } // load entry }
So far we have dealt with a trivial string to long conversion. But view parameters allow you to represent more complex data, as long as you have a converter that can marshal the value from (and to) a string. Let's assume that we want to allow the user to look at blog entries that fall within a range of dates. The before and after dates can be encoded into the URL as follows:
/entries.jsf?after=2007-12-31&before=2009-01-01
Those values can then be converted to Date objects using the <f:convertDateTime> converter tag and assigned to Date properties on a managed bean as follows:
<f:metadata> <f:viewParam name="after" value="#{blog.afterDateFiler}"> <f:convertDateTime type="date" pattern="yyyy-MM-dd"/> </f:viewParam> <f:viewParam name="before" value="#{blog.beforeDateFiler}"> <f:convertDateTime type="date" pattern="yyyy-MM-dd"/> </f:viewParam> <f:event type="preRenderView" listener="#{blog.loadEntries}"/> </f:metadata>
We again use a PreRenderViewEvent listener to load the data before the page is rendered, in this case filtering the collection of blog entries to be displayed.
Emulating the behavior of an action-oriented framework, which the previous examples have demonstrated, is one use of the PreRenderViewEvent. Another is to act as a life cycle callback for programmatically creating or tweaking the UI component tree after it is "inflated" from the view template. Perhaps you want to build part of the tree dynamically from a data structure. Accomplishing this in JSF would require "binding" a bean property to an existing UI component, declared using an EL value expression in the binding attribute of the tag. But this approach is really ugly because you have to put the tree-appending logic in either the JavaBean property getter or setter, depending on whether the view is being created or restored. The PreRenderViewEvent offers a much more definitive and self-documentating hook.
As you've seen, it's finally possible to respond to a bookmarkable URL in JSF (without pain or brittle code). But, up to this point, all we've done is take, take, take. For bookmarkable support to be complete, we need to be able to create bookmarkable URLs. That brings us to the topic of parameter propagation.
Push the parameters on
If view parameters were only capable of accepting data sent through the query string of the URL, even considering the built-in conversion and validation they provide, they really wouldn't be all that helpful. What makes them so compelling is that they are bi-directional, meaning they are also propagated to subsequent requests, and rather transparently. The subsequent request may be a faces request, which targets the current view, or a non-faces request to a view that has view parameters, which translates into a bookmarkable URL. A request for a bookmarkable URL can come from either a link in the page or a redirect navigation event. We'll look at how view parameters get propagated in all of these cases in this section.
Saved by the component tree
Let's return to the blog entry view and consider what happens if we have a comment form below the post. The comment form might be defined as follows:
<h:form> <h:inputText value="#{comment.name}" required="true"/> <h:inputTextarea value="#{comment.text}" required="true"/> <h:commandButton action="#{commentHandler.post}" value="Post"/> </h:form>
Notice that there is no reference to the id of the blog entry in this form. Assuming that the blog entry is not stored in session scope (or a third-party conversation), how will the handler know which entry the comment should be linked? This is where view parameter propagation blends with component tree state saving.
When encoding of the view (i.e., rendering) is complete, the view parameter values are tucked away in the saved state of the UI component tree. When the component tree is restored on a postback, such as when the comment form is submitted, the saved view parameter values are applied to the model. This allows view parameters to tie in nicely with the existing design of JSF. The initial state supplied to the view parameters by the URL can be maintained as long as the user interacts with the view (e.g., triggers faces requests through user interface events). You can think of view parameters as an elegant replacement for hidden form fields in this case.
If the user bookmarked the URL after posting a comment, however, the reference to the blog entry would be lost. That's because after a POST request, the browser location does not contain a query string. Here's what the user would see:
http://domain/blog/entry.jsf
If we are following best practices, we'll want to implement the Post/Redirect/Get pattern anyway. That gives us a opportunity to repopulate the query string of the URL. In the past, this would have required an explicit call to the redirect() method of ExternalContext inside the action method.
FacesContext.getCurrentInstance().getExternalContext().redirect("/entry.jsf?id=" + blog.getEntryId());
This explicit (and intrusive) call was necessary because the navigation case did not provide any way to append parameters to the query string. Now, view parameters can take care of this for us. We can tell JSF to encode the view parameters of the target view ID into the redirect URL by enabling the include-redirect-params attribute on the <redirect> element.
<navigation-rule> <from-view-id>/entry.xhtml</from-view-id> <navigation-case> <from-action>#{commentHandler.post}</from-action> <to-view-id>#{view.viewId}</to-view-id> <redirect include-view-params="true"/> </navigation-case> </navigation-rule>
We'll get into navigation more in the next article in this series. Let's talk about those regular old hyperlinks in the page. We want those to be bookmarkable as well. That means the state needs to be encoded into the URL they point to. Once again, view parameters come into play.
Bookmarkable links
Let's now assume we want to create a bookmarkable link (permalink) to the current blog entry. You can link directly to another JSF view using the outcome attribute of the new hyperlink-producing component tags, <h:link> and <h:button>. These component tags are represented by the component class javax.faces.component.UIOutcomeTarget. (The reason the attribute is named outcome and not viewId will be explained in the next article. For now, just know that the value of the outcome attribute can be a view ID). Both of these component tags support encoding the view parameters into the query string of the URL as signaled by the includeViewParams attribute. Here's how the permalink is defined:
<h:link outcome="/entry.xhtml" includeViewParams="true" value="Permalink"/>
The default value of includeViewParams is false. Since it's set to true, the view parameters are read in reverse and appended to the query string of the link. Here's the HTML that this component tag generated, assuming an entry id of 9:
<a href="/blog/entry.jsf?id=9">Permalink</a>
The link is produced using the new getBookmarkableURL() method on the ViewHandler API. This method calls through to the encodeBookmarkableURL() on the ExternalContext API to have the session ID token tacked on, if necessary. These methods complement the getRedirectURL() and encodeRedirectURL() methods on ViewHandler and ExternalContext, respectively. In a servlet environment, the implementations happen to be the same, but the extra methods serve as both a statement of intent and an extension point for environments where a link URL and a redirect URL are handled differently, such as a portlet.
Notice that the context path of the application (/blog) is prepended to the path, the extension is changed from the view suffix (.xhtml) to the JSF servlet mapping (.jsf) and the query string contains the name and value of the view parameter read from the model. If you had used an <h:outputLink> tag, you would have had to do all of these things manually. That's exactly why the EG felt it was necessary to introduce this component.
We can do one better. If the outcome attribute is absent, the current view ID is assumed. So we can shorten the tag to this:
<h:link includeViewParams="true" value="Permalink"/>
If you want the link to appear as a button, you can use the <h:button> component tag instead.
<h:button includeViewParams="true" value="Permalink"/>
However, note that JavaScript is required in this case to update the browser location when the button is clicked, as you can see from the generated HTML:
<button onclick="window.location.href="/blog/entry.jsf?id=9";">Permalink</button>
View parameters come in especially handy when the number of parameters to keep track of increases. For instance, let's consider the case when a user is searching for entries using a query string in a particular category and wants to paginate through the results. In this case, we are dealing with at least three parameters:
<f:metadata> <f:viewParam name="query" value="#{blog.searchString}"/> <f:viewParam name="category" value="#{blog.category}"/> <f:viewParam name="page" value="#{blog.page}"/> </f:metadata>
Yet the link to these search results still remains as simple as the permalink to an entry:
<h:link includeViewParams="true" value="Refresh query"/>
This component tag will produce HTML similar to this:
<a href="/blog/entries.jsf?category=JSF&query=features&page=2">Refresh</a>
What if we want to link back to the previous page? In that case, we cannot allow the view parameter named page be automatically written into the query string since that will just link us to the current page. We need an override. Fortunately, it's easy to override an encoded view parameter. You simply use the standard <f:param> tag, just as you would if you were defining a new query string parameter:
<h:link includeViewParams="true" value="Previous"> <f:param name="page" value="#{blog.page - 1}"/> </h:link>
View parameters that are encoded into links to the current view ID are pretty intuitive. Where things get tricky is when we use view parameters on a link to a different view ID. This requires putting on your thinking cap and doing some reasoning.
View parameter handoff
When a request is made for a URL, and in turn a JSF view ID, the view parameters defined in that view are used to map request parameters to model properties. But when the view parameters are encoded into a bookmarkable URL, the mappings are read from the target view ID. That's why it's especially important to be able to extract the view metadata from a template without having building a full component tree, as mentioned earlier. Otherwise, you would end up building a component tree for every view that is linked to in the current view. That would be very costly.
Let's consider a use case. Suppose that we want to create a link from the search results to a single entry. We would define the link as follows:
<ui:repeat var="_entry" value="#{blog.entries}"> <h2>#{_entry.title}</h2> <p>#{_entry.excerpt}</p> <p> <h:link outcome="/entry.xhtml" includeViewParams="true" value="View entry"> <f:param name="id" value="#{_entry.id}"/> </h:link> </p> </ui:repeat>
The question to ask yourself is this. "Are the search string, category and page offset included in the URL for the entry?". I hope you said "No". The reason is because when the URL for the entry link is built, the component tag reads the view parameter mappings defined in the /entry.xhtml template. The only parameter mapped in that template is entry id. In order to preserve the filter vector, the view parameters defined in the /entries.xhtml view need to also be in the /entry.xhtml template. Aha! Since these are shared view parameters, we should define them in a common template:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core"> <f:viewParam name="page" value="#{blog.page}"/> <f:viewParam name="query" value="#{blog.searchString}"/> <f:viewParam name="category" value="#{blog.category}"/> </ui:composition>
We can then include that template in each view that needs to preserve these view parameters:
<f:view> <f:metadata> <ui:include src="/WEB-INF/fragments/common_view_params.xhtml"/> <f:viewParam name="id" value="#{blog.entryId}"/> ... </f:metadata> </f:view>
Keep in mind that if the user navigates to the entry after performing a search, the URL for the entry shown in the browser's location bar will now contain the filter vector. But if you want the user to be able to return to the search filter (without using the back button), that's what you want. You can always provide a simple permalink to bookmark just the entry.
Even though you are now defining view parameters in each of the views, that doesn't mean the URL will become littered with empty query string parameters when they are not in use. View parameters are only encoded (i.e., added to the query string) if the value is not null. Otherwise, there is no trace of the view parameter.
You have now learned how view parameters are propagated during a postback, on a redirect and into a bookmarkable URL. The main benefit of this process is that it is transparent. You don't have to worry about each and every request parameter that comprises the state in the query string of the URL. Instead, JSF interprets the view parameter metadata defined in the template of the target view and automatically appends those name/value pairs to the URL when you activate this feature.
Bookmark it
View parameters serve as an alternative to storing state in the UI component tree, provide a starting point for the application, help integrate with legacy applications, assert preconditions of views, make views bookmarkable, and, with help of the new UIOutcomeTarget components or the enhancement to the redirect navigation case, produce links to those bookmarkable views.
This article began by introducing you to the view metadata facet, which is a general facility for defining a view's metamodel that reuses the existing UI component infrastructure. You learned that view parameters and PreRenderViewEvent listeners are the first standard implementations of view metadata. You saw how the combination of these two features allow you to capture initial state from URL query string, validate preconditions and load data for a view all before the view is rendered. Finally, you learned how view parameter values are propagated to subsequent requests.
This series continues by taking a deeper look at the navigation enhancements made in JSF 2 and explaining how those changes tie into the bookmarkability that you learned about in this article. So bookmark and check back again soon!
Opinions expressed by DZone contributors are their own.
Comments