JBoss.orgCommunity Documentation
Starting in version 2.1, Errai offers a system for creating applications that have multiple bookmarkable pages. This navigation system has the following features:
Checkout the Manual Setup Section for instructions on how to manually add Errai Navigation to your project.
Errai Navigation has these main parts:
@Page
annotation marks any widget or Errai UI component as a page.TransitionTo<P>
, TransitionAnchor<P>
, and TransitionToRole<R>
classes are injectable types that provide links to other pages.Navigation
singleton offers control over the navigation system as a whole.The Navigation
singleton owns a GWT Panel called the navigation panel. This panel always contains a widget or component corresponding to the fragment ID (the part after the # symbol) in the browser’s location bar. Whenever the fragment ID changes for any reason (for example, because the user pressed the back button, navigated to a bookmarked URL, or simply typed a fragment ID by hand), the widget in the navigation panel is replaced by the widget associated with that fragment ID. Likewise, when the application asks the navigation system to follow a link, the fragment ID in the browser’s location bar is updated to reflect the new current page.
To declare a page, annotate any subclass of Widget or Errai UI templated component with the @Page
annotation:
@Page
public class ItemListPage extends Composite {
// Anything goes...
}
@Page
@Templated
public class ComponentPage {
// Anything goes...
}
By default, the name of a page is the simple name of the class that declares it. In the above examples, the ItemListPage
will fill the navigation panel whenever the browser’s location bar ends with #ItemListPage
and similarly ComponentPage
will be displayed when the location bar ends with #ComponentPage
. If you prefer a different page name, use the @Page
annotation’s path
attribute:
@Page(path="items")
public class ItemListPage extends Composite {
// Anything goes...
}
Each application must have exactly one default page. This requirement is enforced at compile time. This default page is displayed when there is no fragment ID present in the browser’s location bar.
Use the role = DefaultPage.class
attribute to declare the default starting page, like this:
@Page(role = DefaultPage.class)
public class WelcomePage extends Composite {
// Anything goes...
}
Pages are looked up as CDI beans, so you can inject other CDI beans into fields or a constructor. Pages can also have @PostConstruct
and @PreDestroy
CDI methods.
DefaultPage
is just one example of a page role. A page role is simply an interface used to mark @Page
types. The main uses for page roles:
Navigation
singleton, you can look up all pages that have a specific role.DefaultPage
) then it should extend UniquePageRole
, making it possible to navigate to the page by its role.There are four annotations related to page lifecycle events: @PageShowing
, @PageShown
, @PageHiding
, and @PageHidden
. These annotations designate methods so a page widget can be notified when it is displayed or hidden:
@Page
public class ItemPage extends VerticalPanel {
@PageShowing
private void preparePage() {
}
@PageHiding
private void unpreparePage() {
}
// Anything goes...
}
@PageHiding
method on the current (about-to-be-navigated-away-from) page is invoked@PageHidden
method on the just-removed page is invoked@Page
bean in the client-side bean manager (we’ll call this bean "the new page")@PageState
fields in the new page bean (more on this in the next section)@PageShowing
method of the new page is invoked@PageShown
method of the new page is invoked.The @PageShowing
and @PageShown
methods are permitted one optional parameter of type HistoryToken
— more on this in the next section.
The @PageHiding
and PageShowing
methods are also permitted one optional parameter of type NavigationControl
. If the parameter is present, the page navigation will not be carried out until NavigationControl.proceed()
is invoked, or NavigationControl.redirect(Class)
may be called to redirect to another page with the given class. In PageHiding
methods this is useful for interrupting page navigations and then resuming at a later time (for example, to prompt the user to save their work before transitioning to a new page). In PageShowing
methods, this is useful for redirecting a user before a page is shown based on business logic.
The lifespan of a Page instance is governed by CDI scope: Dependent and implict-scoped page beans are instantiated each time the user navigates to them, whereas Singleton and ApplicationScoped beans are created only once over the lifetime of the application. If a particular page is slow to appear because its UI takes a lot of effort to build, try marking it as a singleton.
A page widget will often represent a view on on instance of a class of things. For example, there might be an ItemPage that displays a particular item available at a store. In cases like this, it’s important that the bookmarkable navigation URL includes not only the name of the page but also an identifier for the particular item being displayed.
This is where page state parameters come in. Consider the following page widget:
@Page
public class ItemPage extends VerticalPanel {
@PageState
private int itemId;
// Anything goes...
}
This page would be reachable at a URL like http://www.company.com/store/#ItemPage;itemId=4
, assuming www.company.com
was the host address and store
was the application context. Before the page was displayed, the Errai UI Navigation framework would write the int
value 4
into the itemId
field.
Page state parameters can also be accessed using URLs with path parameters. In this case, you have to declare the template of the page’s path in the path
field of the @Page
annotation. As an example, consider the following code:
@Page(path="item/{itemID}/{customerID}")
public class ItemPage extends VerticalPanel {
@PageState
private int itemID;
@PageState
private String customerID;
// Anything goes...
}
There are three ways to pass state information to a page: by passing a Multimap to TransitionTo.go()
; by passing a Multimap to Navigation.goTo()
; or by including the state information in the path parameter or fragment identifier of a hyperlink as illustrated in the previous paragraph (use the HistoryToken
class to construct such a URL properly.)
A page widget can have any number of @PageState
fields. The fields can be of any primitive or boxed primitive type (except char
or Character
), String
, or a Collection
, List
, or Set
of the allowable scalar types. Nested collections are not supported.
@PageState
fields can be private, protected, default access, or public. They are always updated by direct field access; never via a setter method. The updates occur just before the @PageShowing
method is invoked.
In addition to receiving page state information via direct writes to @PageState
fields, you can also receive the whole Multimap in the @PageShowing
and @PageShown
methods through a parameter of type HistoryToken
. Whether or not a lifecycle method has such a parameter, the @PageState
fields will still be written as usual.
Page state values are represented in the URL in place of the corresponding parameter variables declared in the URL template (the path
field of the @Page
annotation. See Declaring a Page). If a parameter variable is declared in the URL template and is missing from the actual typed URL, it will cause a navigation error as Errai will not be able to match the typed URL to any template.
Any additional path parameters not found in the URL template are appended as key=value pairs separated by the ampersand (&
) character. Multi-valued page state fields are represented by repeated occurrences of the same key. If a key corresponding to a @PageState
field is absent from the state information passed to the page, the framework writes a default value: null
for scalar Object fields, the JVM default (0 or false) for primitives, and an empty collection for collection-valued fields. To construct and parse state tokens programmatically, use the HistoryToken
class.
To illustrate this further, consider the following example:
@Page(path="item/{itemID}/{customerID}")
public class ItemPage extends VerticalPanel {
@PageState
private int itemID;
@PageState
private String customerID;
@PageState
private int storeID;
// Anything goes...
}
Given the host "www.company.com"
, the context store
, and a state map with the values itemID=4231
, customerID=9364
, and storeID=0032
, the following URL will be generated:
www.company.com/store/#item/4231/9364;storeID=0032
If the value for storeID is undefined, the URL will be www.company.com/store/#item/4231/9364;storeID=0
.
If the URL typed into the browser is www.company.com/store/#item/4231;storeID=0032
, it will cause a navigation error (assuming there is no other page by this url) because there is a missing path parameter.
Errai now comes with support for pushState and path-parameter-based URLs. If HTML5 pushState is enabled Errai Navigation urls will not use the fragment-identifier (#). Thus the non-pushState url from the previous section, www.company.com/store/#item/4231/9364
, would become www.company.com/store/item/4231/9364
.
HTML5 pushState can be enabled by adding the following lines to your GWT host page:
<script type="text/javascript">
var erraiPushStateEnabled = true;
</script>
The application context must be the same as the application’s servlet web context deployed on the server. Errai attempts to infer the application context upon the first page load, but it can also be set manually. To explicitly declare the application context, you can use the setApplicationContext
method in the Navigation class, or set the erraiApplicationWebContext
variable in your GWT host page as follows:
<script type="text/javascript">
var erraiApplicationWebContext = "store";
</script>
In the event that the browser does not support HTML5, Errai automatically disables pushState functionality and reverts to a #-based URL format. That is, Errai uses fragment identifiers to refer to particular resources.
If the page that the user is trying to navigate to cannot be found, a 404 - Not Found page is displayed. You can override this functionality and display a custom page in the case of a page not found error. For example, to navigate to the GWT host page by default, add the following lines to your web.xml file:
<error-page>
<error-code>404</error-code>
<location>/</location>
</error-page>
The easiest way to declare a link between pages is to inject an instance of TransitionAnchor<P>
, where P
is the class of the target page.
Here is an example declaring an anchor link from the templated welcome page to the item list page. The first code sample would go in WelcomePage.java while the second would go in the WelcomePage.html, the associated html template.
@Page(role = DefaultPage.class)
@Templated
public class WelcomePage {
@Inject @DataField TransitionAnchor<ItemListPage> itemLink;
}
<div>
<a data-field="itemLink">Go to Item List Page</a>
</div>
You can inject any number of links into a page. The only restriction is that the target of the link must be a Widget type or Errai UI component that is annotated with @Page
. When the user clicks the link Errai will transition to the item list page.
Sometimes it is necessary to manually transition between pages (such as in response to an event being fired). To declare a manual link from one page to another, inject an instance of TransitionTo<P>
, where P
is the class of the target page.
This code declares a manual transition from the welcome page to the item list page:
@Page(role = DefaultPage.class)
public class WelcomePage extends Composite {
@Inject TransitionTo<ItemListPage> startButtonClicked;
}
You do not need to implement the TransitionTo
interface yourself; the framework creates the appropriate instance for you.
As with TransitionAnchor
, the only restriction is that the target of the link must be a Widget type or Errai UI component that is annotated with @Page
.
To follow a manual link, simply call the go()
method on an injected TransitionTo
object. For example:
@Page(role = DefaultPage.class)
public class WelcomePage extends Composite {
@Inject TransitionTo<ItemListPage> startButtonClicked;
public void onStartButtonPressed(ClickEvent e) {
startButtonClicked.go();
}
}
For convenience, it is also possible to transition to a page by its role using an injected TransitionToRole<R>
where R
is an interface extending UniquePageRole
. This type is used exactly as the TransitionTo
: just inject a parameterized instance and invoke the go()
method.
By injecting a TransitionToRole
into a @Page
, Errai will verify the existence of a single page with this role at compile-time.
Beginning in version 2.4, Errai will automatically attach the Navigation Panel to the Root Panel, but it is possible to override this behaviour by simply adding the Navigation Panel to another component manually. The best time to do this is during application startup, for example in the @PostConstruct
method of your @EntryPoint
class. By using the default behaviour you can allow Errai Navigation to control the full contents of the page, or you can opt to keep some parts of the page (headers, footers, and sidebars, for example) away from Errai Navigation by choosing an alternate location for the Navigation Panel.
The following example reserves space for header and footer content that is not affected by the navigation system:
@EntryPoint
public class Bootstrap {
@Inject
private Navigation navigation;
@PostConstruct
public void clientMain() {
VerticalPanel vp = new VerticalPanel();
vp.add(new HeaderWidget());
vp.add(navigation.getContentPanel());
vp.add(new FooterWidget());
RootPanel.get().add(vp);
}
}
This last example demonstrates a simple approach to defining the page structure with an Errai UI template. The final product is identical to the above example, but in this case the overall page structure is declared in an HTML template rather than being defined programmatically in procedural logic:
@Templated("#root")
@EntryPoint
public class OverallPageStructure {
@Inject
private Navigation navigation;
@Inject @DataField
private DivElement root;
@Inject @DataField
private Header header;
@Inject @DataField
private SimplePanel content;
@Inject @DataField
private Footer footer;
@PostConstruct
public void clientMain() {
// give over the contents of this.content to the navigation panel
content.add(navigation.getContentPanel());
// add this whole component to the DOM
Document.get().getBody().appendChild(root);
}
}
By default Errai uses com.google.gwt.user.client.ui.SimplePanel
as a container for navigation panel. Sometimes this is not sufficient and users would prefer using another implementation. For example a com.google.gwt.user.client.ui.SimpleLayoutPanel
that manages child size state.
To provide your own implementation of the navigation panel you must implement org.jboss.errai.ui.nav.client.local.NavigatingContainer
. For example:
public class NavigatingPanel implements NavigatingContainer {
SimplePanel panel = new SimpleLayoutPanel();
public void clear() {
this.panel.clear();
}
public Widget asWidget() {
return panel.asWidget();
}
public Widget getWidget() {
return panel.getWidget();
}
public void setWidget(Widget childWidget) {
panel.add(childWidget);
}
public void setWidget(IsWidget childWidget) {
panel.add(childWidget);
}
}
Then in your GWT module descriptor you need to override the default navigation panel (org.jboss.errai.ui.nav.client.local.NavigatingContainer
) by adding:
<replace-with class="com.company.application.client.NavigatingPanel">
<when-type-is class="org.jboss.errai.ui.nav.client.local.NavigatingContainer"/>
</replace-with>
When a user enters a url for an Errai page that does not exist an error is logged and the app navigates to the DefaultPage
. It is possible to override this behaviour by setting an error handler on Navigation
.
Here is an example of a class that registers a navigation error handler that redirects the user to a special PageNotFound
page:
@ApplicationScoped
public class NavigationErrorHandlerSetter {
@Inject
private Navigation navigation;
@PostConstruct
public void setErrorHandler() {
navigation.setErrorHandler(new PageNavigationErrorHandler() {
@Override
public void handleError(Exception exception, String pageName) {
navigation.goTo("PageNotFound");
}
@Override
public void handleError(Exception exception, Class<? extends PageRole> pageRole) {![]()
navigation.goTo("PageNotFound");
}
});
}
}
Because the pages and links in an Errai Navigation application are declared structurally, the framework gets a complete picture of the app’s navigation structure at compile time. This knowledge is saved out during compilation (and at page reload when in Dev Mode) to the file .errai/navgraph.gv
. You can view the navigation graph using any tool that understands the GraphViz (also known as DOT) file format.
One popular open source tool that can display GraphViz/DOT files is GraphViz. Free downloads are available for all major operating systems.
When rendered, a navigation graph looks like this:
In the rendered graph, the pages are nodes (text surrounded by an ellipse). The starting page is drawn with a heavier stroke. The links are drawn as arrows from one page to another. The labels on these arrows come from the Java field names the TransitionTo objects were injected into.