Managing URI Fragments
- Setting the URI Fragment
- Reading the URI Fragment
- Listening for URI Fragment Changes
- Supporting Web Crawling
Note
| This chapter contains instructions how to manage URI fragments. As browser support for HTML5 History API has improved, developers should in most cases developers instead use real URIs with pushState method. Read more from "Manipulating Browser History". |
A major issue in AJAX applications is that as they run in a single web page, bookmarking the application URL (or more generally the URI) can only bookmark the application, not an application state. This is a problem for many applications, such as product catalogs and discussion forums, in which it would be good to provide links to specific products or messages. Consequently, as browsers remember the browsing history by URI, the history and the
button do not normally work. The solution before HTML5 API was available was to use the fragment identifier part of the URI, which is separated from the primary part (address + path + optional query parameters) of the URI with the hash (#) character. For example:https://example.com/path#myfragment
The exact syntax of the fragment identifier part is defined in RFC 3986 (Internet standard STD 66) that defines the URI syntax. A fragment may only contain the regular URI path characters (see the standard) and additionally the slash and the question mark.
Vaadin offers two ways to enable the use of URI fragments: the high-level Navigator utility described in "Navigating in an Application" (if the legacy UriFragmentManager is configured for the Navigator) and the low-level API described here.
Setting the URI Fragment
You can set the current fragment identifier with the setUriFragment() method in the Page object.
Page.getCurrent().setUriFragment("mars");
Setting the URI fragment causes an UriFragmentChangeEvent, which is processed in the same server request. As with UI rendering, the URI fragment is changed in the browser after the currently processed server request returns the response.
Prefixing the fragment identifier with an exclamation mark enables the web crawler support described in Supporting Web Crawling.
Reading the URI Fragment
The current URI fragment can be acquired with the getUriFragment() method from the current Page object. The fragment is known when the init() method of the UI is called.
// Read initial URI fragment to create UI content
String fragment = getPage().getUriFragment();
enter(fragment);
To enable reusing the same code when the URI fragment is changed, as described next, it is usually best to build the relevant part of the UI in a separate method. In the above example, we called an enter() method, in a way that is similar to handling view changes with Navigator.
Listening for URI Fragment Changes
After the UI has been initialized, changes in the URI fragment can be handled with a UriFragmentChangeListener. The listeners are called when the URI fragment changes, but not when the UI is initialized, where the current fragment is available from the page object as described earlier.
For example, we could define the listener as follows in the init() method of a UI class:
public class MyUI extends UI {
@Override
protected void init(VaadinRequest request) {
getPage().addUriFragmentChangedListener(
new UriFragmentChangedListener() {
public void uriFragmentChanged(
UriFragmentChangedEvent source) {
enter(source.getUriFragment());
}
});
// Read the initial URI fragment
enter(getPage().getUriFragment());
}
void enter(String fragment) {
... initialize the UI ...
}
}
Application State Management with URI Fragment Utility shows an application that allows specifying the menu selection with a URI fragment and correspondingly sets the fragment when the user selects a menu item.
Supporting Web Crawling
Stateful AJAX applications can not normally be crawled by a search engine, as they run in a single page and a crawler can not navigate the states even if URI fragments are enabled. The Google search engine and crawler support a convention where the fragment identifiers are prefixed with exclamation mark, such as #!myfragment. The servlet needs to have a separate searchable content page accessible with the same URL, but with a _escaped_fragment_ parameter. For example, for /myapp/myui#!myfragment it would be /myapp/myui?_escaped_fragment_=myfragment.
You can provide the crawl content by overriding the service() method in a custom servlet class. For regular requests, you should call the super implementation in the VaadinServlet class.
public class MyCustomServlet extends VaadinServlet
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String fragment = request
.getParameter("_escaped_fragment_");
if (fragment != null) {
response.setContentType("text/html");
Writer writer = response.getWriter();
writer.append("<html><body>"+
"<p>Here is some crawlable "+
"content about " + fragment + "</p>");
// A list of all crawlable pages
String items[] = {"mercury", "venus",
"earth", "mars"};
writer.append("<p>Index of all content:</p><ul>");
for (String item: items) {
String url = request.getContextPath() +
request.getServletPath() +
request.getPathInfo() + "#!" + item;
writer.append("<li><a href='" + url + "'>" +
item + "</a></li>");
}
writer.append("</ul></body>");
} else
super.service(request, response);
}
}
The crawlable content does not need to be human readable. It can provide an index of links to other application states, as we did in the example above. The links should use the " #!" notation, but can not be relative to avoid having the _escaped_fragment_ parameter.
You need to use the custom servlet class in the web.xml deployment descriptor instead of the normal VaadinServlet class, as described in "Using a web.xml Deployment Descriptor".