JBoss.orgCommunity Documentation
One of the primary complaints of GWT to date has been that it is difficult to use "pure HTML" when building and skinning components. Inevitably one must turn to Java-based configuration in order to finish the job. In contrast, Errai strives to remove the need for Java styling. HTML template files are placed in the project source tree and referenced from custom Errai UI components in Java. Since Errai UI depends on Errai IOC and Errai CDI, dependency injection is supported in all custom components. Errai UI provides rapid prototyping and HTML5 templating for GWT.
The Errai UI module is directly integrated with Data Binding and Errai JPA but can also be used as a standalone project in any GWT client application by simply inheriting the Errai UI GWT module, and ensuring that you have properly using Errai CDI’s @Inject to instantiate your components:
Use the Errai Forge Addon Add Errai Features command and select Errai UI to follow along with this section.
Checkout the Manual Setup Section for instructions on how to manually add Errai UI to your project. If you work better by playing with a finished product, you can see a simple client-server project implemented using Errai UI here.
Since Errai 4.0, Errai UI components are no longer required to extend Composite
. In fact, extending Composite
is deprecated.
Though components can still be built with and as widgets, we encourage users to build components using purely native HTML elements.
An Errai UI component consists of a Java class (the templated bean), an HTML file (the template), and an optional CSS file (the template stylesheet).
The template and template stylesheet describe the look of your component. The templated bean uses the @org.jboss.errai.ui.shared.api.annotations.DataField
annotation
to declare mappings between fields in the templated bean and elements in the template.
As with most other features of Errai, dependency injection with CDI is the programming model of choice, so when interacting with components defined using Errai UI,
you should always @Inject
references to your Composite components.
In this example we use a single Errai UI component by injecting it and adding it to the DOM.
@EntryPoint
public class Application {
/*
* This type (org.jboss.errai.common.client.dom.Document) is a thin wrapper
* around the DOM document provided by Errai. More on this later.
*/
@Inject
private Document document;
@Inject
private TemplatedBean component;
@PostConstruct
public void init() {
documnet.getBody().appendChild(component.getElement());
}
}
In some cases you may need to display multiple instances of a component.
Here we create and add many components to the DOM using an injected org.jboss.errai.ioc.client.api.ManagedInstance<T>
.
@EntryPoint
public class Application {
private String[] colors = new String[]{"Blue", "Yellow", "Red"};
@Inject
private ManagedInstance<ColorComponent> provider;
@Inject
private Document document;
@PostConstruct
public void init() {
for(String color: colors) {
// ColorComponent should be @Dependent
// so that this produces a new instance each time.
ColorComponent component = provider.get();
component.setColor(c);
document.getBody().appendChild(component.getElement());
}
}
}
As previously mentioned, components in Errai UI consist of a Java class (the templated bean), an HTML file (the template),
and an optional CSS file (the template stylesheet). A Java class is a templated bean if the type is annotated with @Templated
.
Here is a basic templated bean with no Java fields mapped to UI elements.
LoginForm.java
@Templated
public class LoginForm {
}
Annotating the type with @Templated
and no argument declares that this bean should have a template file LoginForm.html
and optionally a stylesheet LoginForm.css
in the same package as LoginForm.java
.
When no argument is provided to @Templated
, Errai UI looks in the current package for a template file having
the simple class name of the templated bean, suffixed with .html
.
But @Templated
accepts an argument to define an alternatively named or located template, as in the proceeding example.
@Templated("my-template.html")
public class LoginForm {
/* Looks for my-template.html in LoginForm's package */
}
Fully qualified template paths are also supported, but must begin with a leading /:
@Templated("/org/example/my-template.html")
public class LoginForm {
/* Looks for my-template.html in package org.example */
}
The previous examples specify template files at compile time, but it is possible to use templates provided at runtime using a TemplateProvider
. These providers run asynchronously so that it is possible to fetch templates via HTTP requests. In fact Errai UI contains an implementation, ServerTemplateProvider
, that fetches templates using the @Templated
value as a url.
@Templated(value="home.html", provider=ServerTemplateProvider.class)
public class LoginForm {
/* Makes an HTTP request for the relative path "home.html" */
}
Custom implementations can also be used.
@Templated(provider=MyTemplateProvider.class)
public class LoginForm {
/* Makes an HTTP request for the relative path "home.html" */
}
@Dependent
public class MyTemplateProvider implements TemplateProvider {
@Override
public void provideTemplate(String location, TemplateRenderingCallback renderingCallback) {
String template;
/*
* Generate the template String.
* Note: The location parameter is the value from the @Templated annotation.
*/
renderingCallback.renderTemplate(template);
}
}
Templates in Errai UI may be designed either as an HTML snippet or as a full HTML document. You can even take an existing HTML page and use it as a template. With either approach, the id
, class
, and data-field
attributes in the template identify elements by name. These elements and their children are used in the component to add behavior, and use additional components to add functionality to the template. There is no limit to how many templated beans may share a given HTML template.
Here is a simple HTML fragment for a login form to accompany our @Templated LoginForm
bean.
<form>
<legend>Log in to your account</legend>
<label for="username">Username</label>
<input id="username" type="text" placeholder="Username">
<label for="password">Password</label>
<input id="password" type="password" placeholder="Password">
<button>Log in</button>
<button>Cancel</button>
</form>
This fragment can be used with our previous @Templated LoginForm
declaration as is.
You can also use a full HTML document that is more easily previewed during design. When doing this you must specify the location of the component’s root DOM Element within the template file using a "data-field"
, id
, or class
attribute matching the value of the @Templated annotation. There is no limit to how many templated beans may share a single HTML template.
LoginForm.java
@Templated("my-template.html#login-form")
public class LoginForm {
/* Specifies that <... id="login-form"> be used as the root Element of this component */
}
my-template.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>A full HTML document</title>
</head>
<body>
<div>
<form id="login-form"> #
![]()
<legend>Log in to your account</legend>
<label for="username">Username</label>
<input id="username" type="text" placeholder="Username">
<label for="username">Password</label>
<input id="password" type="password" placeholder="Password">
<button>Log in</button>
<button>Cancel</button>
</form>
</div>
<hr>
<footer id="theme-footer">
<p>(c) Company 2012</p>
</footer>
</body>
</html>
The HTML |
Multiple components may use the same template, specifying any elements as a their root elements. In particular, note that two or more components may declare the same DOM element of this template file as their root elements; there is no conflict because are each components instantiated with a unique copy of the template DOM rooted at the specified element at runtime (or from the root element if a fragment is not specified.)
For example, the component below also uses the same template file by referencing the template name, and specifying a fragment.
@Templated("my-template.html#theme-footer")
public class Footer {
/* Specifies that <... id="theme-footer"> be used as the root Element of this Widget */
}
Now that we know how to create a @Templated
bean and an HTML template, we can start wiring in functionality and behavior;
this is done by annotating fields and methods to replace specific sub-elements of the template DOM.
We can even replace portions of the template with other Errai UI components!
In order to substitute elements into the template DOM you must annotate fields in your templated bean with @DataField
and mark the HTML template element with a correspondingly named data-field
, id
, or class
attribute.
All replacements happen while the component is being constructed; thus, fields annotated with @DataField
must either be @Inject
ed
or manually initialized when the templated bean is instantiated.
The @DataField
types used in this example are thin HTML wrappers found in the org.jboss.errai.common.client.dom
package of errai-common.
It is still possible to use widgets in a @DataField
but this approach is not recommended.
/*
* Here the template file is implicitly LoginForm.html.
* The root element has id, class, or data-field "form".
*/
@Templated("#form")
public class LoginForm {
// This is the root element of the template, for adding this component to the DOM.
@Inject
@DataField
private Form form;
// If not otherwise specified, the name to match in the HTML template defaults
// to the name of the field; in this case, the name would be "username"
@Inject
@DataField
private TextInput username;
// The name to reference in the template can also be specified manually
@Inject
@DataField("pass")
private PasswordInput password;
// We can also choose to instantiate our own data fields. Injection is not required.
@DataField
private Button submit = (Button) Window.getDocument().createElement("button");
}
Field, method, and constructor injection are all supported by @DataField.
The previous LoginForm
example uses HTML wrapper elements from Errai Common, but there are other types that can be used as @DataFields
.
Here is a full list:
@JsType
that is a wrapper for a DOM element (including those provided in errai-common).com.google.gwt.dom.client.Element
type.org.jboss.errai.common.client.api.IsElement
.com.google.gwt.user.client.ui.Widget
type. (This approach is no longer recommended!).When programmatically adding components to the DOM, it is necessary to access their HTMLElement roots. Errai UI provides two ways of doing this.
Errai UI supports injecting any element in a template html file as a DataField in the templated bean, including the root element. Below is a simple example.
This interface provides a default implementation of org.jboss.errai.common.client.api.IsElement
that works for any templated bean. For example:
@Templated
public class MyBean implements org.jboss.errai.ui.client.local.api.IsElement {
}
@EntryPoint
public class Setup {
@Inject MyBean component;
@Inject Document doc;
@PostConstruct
public void attachBean() {
// The default implementation automatically returns
// the correct root element.
HTMLElement rootElement = component.getElement();
doc.getBody().appendChild(rootElement);
}
}
The getElement
call in the usage above returns the element as the org.jboss.errai.common.client.dom.HTMLElement
type from errai-common.
This section assumes basic familiarity with JS interop in GWT 2.8+.
Since 4.0, Errai UI supports working with JS interop wrappers of DOM elements. Additionally, Errai provides a concise way of allowing your custom JS interop wrappers to be injected into components.
Let’s redo our previous login form, but with a new, custom JS interop form element as the root. First we need our element wrapper.
@JsType(isNative = true)
@Element("form")
public interface NativeForm {
// Element methods and properties go here
}
Here we have a defined a JS interop interface that will be backed by a FormElement javascript object at runtime.
The org.jboss.errai.ui.shared.api.annotation.Element
annotation declares this type as injectable with Errai IOC;
at the container will call document.createElement("form")
in javascript to satisfy injection sites for this type.
Once you’ve defined your element wrapper, using it as a @DataField
works exactly as in previous examples.
/*
* Like the component from the previous example, but with a custom element wrapper as its root.
*/
@Templated("#form")
public class LoginForm {
// Now the root element is our custom JsType
@Inject
@DataField
private NativeForm form;
@Inject
@DataField
private TextInput username;
@Inject
@DataField("pass")
private PasswordInput password;
@DataField
private Button submit = (Button) Window.getDocument().createElement("button");
}
In our previous examples injecting Elements
or native JsTypes
, we did so with types that had a single possible tag name.
But consider the heading tags (h1, h2, …, h6). In gwt-user and errai-common these are all represented by HeadingElement
.
We might also wish to make our own @JsType
wrapper to represent these tags:
@JsType(isNative = true)
@Element({ "h1", "h2", "h3", "h4", "h5", "h6" })
public interface NativeHeading {
// Heading element methods and properties go here
}
With the above definition, injecting a NativeHeading
is ambiguous. Fortunately for both Elements
and native JsTypes
we can use the
javax.inject.Named
annotation to remove this ambgiuity by specifying a tag name at a injection site.
@Templated
public class Component {
// Injects a gwt-user HeadingElement with tag name "h1"
@Inject
@Named("h1")
@DataField
private HeadingElement h1;
// Injects a NativeHeading wrapper for an "h6" element
@Inject
@Named("h6")
@DataField
private NativeHeading h6;
}
Each @DataField
reference in the Java class must match an element in the HTML template. The matching of Java references to HTML elements is performed as follows:
@DataField
annotation has a value argument, that is used as the reference name. For fields, the default reference name is the field name. Method and constructor parameters have no default name, so they must always specify a value.data-field=name
, the Java reference will point to this element. If there is more than one such element, the Java reference points to the first.id=name
, the Java reference will point to this element. If there is more than one such element, the Java reference points to the first.Otherwise, if there is an element in the HTML template with a CSS style class name
, the Java reference will point to this element. If there is more than one such element, the Java reference points to the first. For elements with more than one CSS style, each style name is considered individually. For example:
<div class="eat drink be-merry">
matches Java references named eat
, drink
, or be-merry
.
If more than one Java reference matches the same HTML element in the template, it is an error. For example, given a template containing the element <div class="eat drink be-merry">
, the following Java code is in error:
@Templated
public class ErroneousTemplate {
@Inject @DataField
private Div eat;
@Inject @DataField
private Div drink;
}
because both fields eat
and drink
refer to the same HTML div
element.
So now we must ensure there are data-field
, id
, or class
attributes in the right places in our template HTML file. This, combined with the @DataField annotation in our component allows Errai UI to determine where and what should be composited when creating component instances.
<form id="form">
<legend>Log in to your account</legend>
<label for="username">Username</label>
<input id="username" type="text" placeholder="Username">
<label for="password">Password</label>
<input data-field="pass" id="password" type="password" placeholder="Password">
<button id="submit">Log in</button>
<button>Cancel</button>
</form>
Now, when we run our application, we will be able to interact with these fields in our component.
Three things are merged or modified when Errai UI creates a new component instance:
@DataFields
@DataField
is a Widget
that implements HasText
or HasHTML
or if it is a DOM element that has no children.@DataField
to the template.@Templated
public class StyledComponent implements IsElement {
@Inject
@DataField("field-1")
private Div div;
public StyledComponent() {
CSSStyleDeclaration style = div.getStyle();
style.setProperty("position", "fixed");
style.setProperty("top", "0");
style.setProperty("left", "0");
this.getElement().setId("outer-id");
}
}
<form>
<span data-field="field-1" style="display:inline;"> This element will become a div </span>
</form>
This text will be ignored.
<form id="outer-id">
<div data-field="field-1" style="display:inline;"> This element will become a div </div>
</form>
But why does the output look the way it does? Some things happened that may be unsettling at first, but once you understand why these things occur you will find these mechanisms extremely powerful.
When styling your templates, you should keep in mind that all attributes defined in the template file will take precedence over any preset attributes in your @DataFields
.
This "attribute merge" occurs only when the components are instantiated; subsequent changes to any attributes after construction will function normally.
In the example we defined a component that applied several styles to a child Widget in its constructor, but we can see from the output that the styles from the template have overridden them.
If styles must be applied in Java instead of in the template, @PostConstruct
or other methods should be favored over constructors to apply styles to fully-constructed components.
Element composition, however, functions inversely from attribute merging, and the <span>
defined in our template is actually replaced by the <div>
because of the Div in our component field. This does not, however, change the behavior of the attribute merge - the new <div>
was still be rendered inline,
because we have specified this style in our template, and the template always wins in competition with attributes set programatically before composition occurs.
It is this rule that allows nesting of Errai UI components, since the markup of an Errai UI component A
used as a @DataField
in component B
will override
the markup in the B
template.
In short, whatever is inside the @DataField
in your class will replace the children of the corresponding element in your template.
Additionally, because Div
is a thin Element wrapper the contents of the <span> "field-1" Element in the template were preserved;
however, this would not have been the case if the @DataField
specified for the element was not an element wrapper (for example if it was another Errai UI component).
In short, if you wish to preserve text or HTML contents of an element in your template, you can do one of two things: do not composite that Element with a @DataField
reference,
or ensure that the UI component being composited implements is a a DOM element with no children.
Dealing with User and DOM Events is a reality in rich web development, and Errai UI provides several approaches for dealing with all types of browser events using its "quick handler" functionality. It is possible to handle:
It is not possible to handle Native DOM events on Widgets because GWT overrides native event handlers when Widgets are added to the DOM. You must programatically configure such handlers after the Widget has been added to the DOM.
Each of the scenarios mentioned above use the same basic programming model for event handling: Errai UI wires methods annotated with @EventHandler("my-data-field")
(event handler methods) to handle events on the corresponding @DataField("my-data-field")
in the same component. Event handler methods annotated with a bare @EventHandler
annotation (no annotation parameter) are wired to receive events on the @Templated component itself.
A JS interop wrapped event is a type annotated with @JsType(isNative=true)
and @BrowserEvent
and provides an interface to the API of a browser DOM event.
This approach can be used to handle events on any kind of @DataField
or on a data-field
element in the HTML template. Since many browser event types share interfaces a quick handler with a JS interop event parameter must sometimes specify the browser event type (i.e. "click", "blur", "focus", etc.).
Here is an example of a quick handler for "click" events on an element @DataField
where the Event
parameter is a JS interop wrapped event type org.jboss.errai.common.client.dom.Event
. The Event
interfaces is annotated with @BrowserEvent
, but does not specify a value of usable browser event types, so the quick handler must specify the event type is listens to with the @ForEvent
annotation.
@Templated
public class ElementHandlerComponent {
@Inject
@DataField("b1")
private ButtonElement button;
@EventHandler("b1")
public void doSomething(@ForEvent("click") Event e) {
// do something
}
}
For @BrowserEvent
wrappers that specify event types, @ForEvent
may be omitted. In this case the quick handler is registered for all supported event types. In the example below, FocusEvent
is annotated with @BrowserEvent({"blur", "focus", "focusin", "focusout"})
, so the quick handler is registered for all four event: blur, focus, focusin, and focusout.
@Templated
public class ElementHandlerComponent {
@Inject
@DataField
private AnchorElement link;
@EventHandler("link")
public void doSomething(FocusEvent e) {
// do something
}
}
This approach can also be used with GWT Widget @DataFields
and template data-fields
that do not have a @DataField
.
@Templated
public class WidgetAndElementHandlerComponent {
@Inject
@DataField
private TextBox input;
// Handles focus-related events on the
// underlying HTML element of the TextBox field.
@EventHandler("input")
public void doSomething(FocusEvent e) {
// do something
}
// Handles dblclick events for the element in the
// template with id/class/data-field="button".
@EventHandler("button")
public void onClick(@ForEvent("dblclick") MouseEvent e) {
// do something
}
}
This approach handles GWT Event classes for Widgets that explicitly handle the given event type. If a Widget does not handle the Event type given in the @EventHandler
method’s signature, the application will fail to compile and appropriate errors will be displayed.
@Templated
public class WidgetHandlerComponent extends Composite {
@Inject
@DataField("b1")
private Button button;
@EventHandler("b1")
public void doSomethingC1(ClickEvent e) {
// do something
}
}
Errai UI also makes it possible to handle GWT events on native Elements which are specified as a @DataField
in the component class. This is useful when a full GWT Widget is not available for a given Element, or for GWT events that might not normally be available on a given Element type. This could occur, for instance, when clicking on a <div>
, which would normally not have the ability to receive the GWT ClickEvent
, and would otherwise require creating a custom DIV Widget to handle such an event.
@Templated
public class ElementHandlerComponent extends Composite {
@DataField("div-1")
private DivElement button = DOM.createDiv();
@EventHandler("div-1")
public void doSomethingC1(ClickEvent e) {
// do something
}
}
As with previous features, the DivElement
above could be replaced with a native JsType
(for example, Div
from errai-common).
The last approach is handles the case where native DOM events must be handled, but no such GWT event handler exists for the given event type. Alternatively, it can also be used for situations where Elements in the template should receive events, but no handle to the Element the component class is necessary (aside from the event handling itself.) Native DOM events do not require a corresponding @DataField
be configured in the class; only the HTML data-field
, id
, or class
template attribute is required.
<div>
<a id="link" href="/page">this is a hyperlink</a>
<div data-field="div"> Some content </div>
</div>
The @SinkNative
annotation specifies (as a bit mask) which native events the method should handle; this sink behaves the same in Errai UI as it would with DOM.sinkEvents(Element e, int bits)
. Note that a @DataField
reference in the component class is optional.
Only one @EventHandler may be specified for a given template element when @SinkNative is used to handle native DOM events.
@Templated
public class QuickHandlerComponent extends Composite {
@DataField
private AnchorElement link = DOM.createAnchor().cast();
@EventHandler("link")
@SinkNative(Event.ONCLICK | Event.ONMOUSEOVER)
public void doSomething(Event e) {
// do something
}
@EventHandler("div")
@SinkNative(Event.ONMOUSEOVER)
public void doSomethingElse(Event e) {
// do something else
}
}
Using asynchronous Javascript calls often make realizing the benefits of modern browsers difficult when it comes to form submission. But there is now a base class in Errai UI for creating @Templated
form widgets that are perfect for tasks such as creating a login form.
Here is a sample @Templated
login form class. This form has:
username
text fieldpassword
field@Dependent
@Templated
public class LoginForm extends AbstractForm {![]()
@Inject
private Caller<AuthenticationService> authenticationServiceCaller;
@Inject
@DataField
private TextBox username;
@Inject
@DataField
private PasswordTextBox password;
@DataField
private final FormElement form = DOM.createForm();![]()
@Inject
@DataField
private Button login;![]()
@Override
protected FormElement getFormElement() {
return form;![]()
}
@EventHandler("login")
private void loginClicked(ClickEvent event) {
authenticationServiceCaller.call(new RemoteCallback<User>() {
@Override
public void callback(User response) {
// Now that we're logged in, submit the form
submit();![]()
}
}).login(username.getText(), password.getText());
}
}
The key things that you should take from this example:
The class extends | |
The | |
The login button is a regular button widget, with a click handling method below. | |
The | |
After the user has successfully logged in asynchronously we call |
When a user successfully logs in via this example, the web browser should prompt them to remember the username and password (assuming this is a feature of the browser being used).
The most likely way to go wrong is to accidentally use the wrong types of elements in your template. It is very important that you use a proper from
element with input
elements with the exception of the submit button. Here is an html template that could accompany the LoginForm.java
example above:
<div>
<form data-field="form">
<input type="text" name="username" data-field="username">
<input type="password" name="password" data-field="password">
<button data-field="login">Sign In</button>
</form>
</div>
To reiterate, notice that the username
and password
fields are legitimate input
elements. This is because we want these values to be submitted when AbstractForm.submit()
is called (so that the browser notices them). However, we do not want there to be any way to submit the form other than calling AbstractForm.submit()
, so the button
element is notably missing the type="submit"
attribute pair.
A recurring implementation task in rich web development is writing event handler code for updating model objects to reflect input field changes in the user interface. The requirement to update user interface fields in response to changed model values is just as common. These tasks require a significant amount of boilerplate code which can be alleviated by Errai. Errai’s data binding module provides the ability to bind model objects to user interface fields, so they will automatically be kept in sync. While the module can be used on its own, it can cut even more boilerplate when used together with Errai UI.
In the following example, all @DataFields
annotated with @Bound
have their contents bound to properties of the data model (a User
object). The model object is injected and annotated with @Model
, which indicates automatic binding should be carried out. Alternatively, the model object could be provided by an injected DataBinder
instance annotated with @AutoBound
, see Declarative Binding for details.
@Templated
public class LoginForm {
@Inject
@Model
private User user;
@Inject
@Bound
@DataField
private InputElement name;
@Inject
@Bound
@DataField
private PasswordTextBox password;
@DataField
private Button submit = new Button();
}
Now the user object and the username
and password
fields in the UI are automatically kept in sync. No event handling code needs to be written to update the user object in response to input field changes and no code needs to be written to update the UI fields when the model object changes. So, with the above annotations in place, it will always be true that user.getUsername().equals(username.getText())
and user.getPassword().equals(password.getText())
.
By default, bindings are determined by matching field names to property names on the model object. In the example above, the field name
was automatically bound to the JavaBeans property name
of the model (user
object). If the field name does not match the model property name, you can use the property
attribute of the @Bound
annotation to specify the name of the property. The property can be a simple name (for example, "name") or a property chain (for example, user.address.streetName
). When binding to a property chain, all properties but the last in the chain must refer to @Bindable values.
The following example illustrates all three scenarios:
@Bindable
public class Address {
private String line1;
private String line2;
private String city;
private String stateProv;
private String country;
// getters and setters
}
@Bindable
public class User {
private String name;
private String password;
private Date dob;
private Address address;
private List<Role> roles;
// getters and setters
}
@Templated
public class UserComponent {
@Inject @AutoBound DataBinder<User> user;
@Inject @Bound InputElement name;
@Inject @Bound(property="dob") DatePicker dateOfBirth;
@Inject @Bound(property="address.city") TextBox city;
}
In UserComponent
above, the name
input element is bound to user.name
using the default name matching; the dateOfBirth
date picker is bound to user.dob
using a simple property name mapping; finally, the city
text box is bound to user.address.city
using a property chain. Note that the Address
class is required to be @Bindable
in this case.
Often you will need to bind a list of model objects so that every object in the list is bound to a corresponding component. This task can be accomplished using Errai UI’s ListWidget
class. Here’s an example of binding a list of users using the UserComponent
class from the previous example. First, we need to enhance UserComponent
to implement HasModel
.
@Templated
public class UserWidget implements HasModel<User> {
@Inject @AutoBound DataBinder<User> userBinder;
@Inject @Bound InputElement name;
@Inject @Bound(property="dob") DatePicker dateOfBirth;
@Inject @Bound(property="address.city") TextBox city;
public User getModel() {
userBinder.getModel();
}
public void setModel(User user) {
userBinder.setModel(user);
}
}
Now we can use UserComponent
to display items in a list.
@Templated
public class MyComponent {
@Inject @DataField ListWidget<User, UserComponent> userListWidget;
@PostConstruct
public void init() {
List<User> users = .....
userListWidget.setItems(users);
}
}
Calling setItems
on the userListWidget
causes an instance of UserComponent
to be displayed for each user in the list. The UserComponent
is then bound to the corresponding user object. By default, the widgets are arranged in a vertical panel. However, ListWidget
can also be subclassed to provide alternative behaviour. In the following example, we use a horizontal panel to display the widgets.
public class UserListWidget extends ListWidget<User, UserWidget> {
public UserList() {
super(new HorizontalPanel());
}
@PostConstruct
public void init() {
List<User> users = .....
setItems(users);
}
@Override
public Class<UserWidget> getItemWidgetType() {
return UserWidget.class;
}
}
An instance of ListWidget
can also participate in automatic bindings using @Bound
. In this case, setItems
never needs to be called manually. The bound list property and displayed items will automatically be kept in sync. In the example below a list of user roles is bound to a ListWidget
that displays and manages a RoleWidget
for each role in the list. Every change to the list returned by user.getRoles()
will now trigger a corresponding update in the UI.
@Templated
public class UserDetailView {
@Inject
@Bound
@DataField
private InputElement name;
@Inject
@Bound
@DataField
private PasswordTextBox password;
@Inject
@Bound
@DataField
private ListWidget<Role, RoleWidget> roles;
@DataField
private Button submit = new Button();
@Inject @Model
private User user;
}
The @Bound
annotation further allows to specify a converter to use for the binding (see Specifying Converters for details). This is how a binding specific converter can be specified on a data field:
@Inject
@Bound(converter=MyDateConverter.class)
@DataField
private TextBox date;
Errai’s DataBinder
also allows to register PropertyChangeHandlers
for the cases where keeping the model and UI in sync is not enough and additional logic needs to be executed (see Property Change Handlers for details).
Using components to build up a hierarchy of UI componenets functions exactly the same as when building hierarchies of GWT widgets or DOM elements. The only distinction might be that with Errai UI, @Inject
is preferred to manual instantiation.
@Templated
public class ComponentOne {
@Inject
@DataField("other-comp")
private ComponentTwo two;
}
This example works whether ComponentTwo
is a composite or non-composite component.
Templating would not be complete without the ability to inherit from parent templates, and Errai UI also makes this possible using simple Java inheritance. The only additional requirement is that components extending from a parent component must also be annotated with @Templated, and the path to the template file must also be specified in the child component’s annotation. Child components may specify @DataField
references that were omitted in the parent class, and they may also override @DataField
references (by using the same data-field
name) that were already specified in the parent component.
Extension templating is particularly useful for creating reusable page layouts with some shared content (navigation menus, side-bars, footers, etc.) where certain sections will be filled with unique content for each page that extends from the base template; this is commonly seen when combined with the MVP design pattern traditionally used in GWT applications.
<div class="container">
<div id="header"> Default header </div>
<div id="content"> Default content </div>
<div id="footer"> Default footer </div>
</div>
This component provides the common features of our page layout, including header and footer, but does not specify any content. The missing @DataField "content" will be provided by the individual page components extending from this parent component.
@Templated
public class PageLayout {
@Inject
@DataField
private HeaderComponent header;
@Inject
@DataField
private FooterComponent footer;
@PostConstruct
public final void init() {
// do some setup
}
}
We are free to fill in the missing "content" @DataField with a component of our choosing. Note that it is not required to fill in all omitted @DataField references.
@Templated("PageLayout.html")
public class LoginLayout extends PageLayout {
@Inject
@DataField
private LoginForm content;
}
We could also have chosen to override one or more @DataField
references defined in the parent component, simply by specifying a @DataField
with the same name in the child component, as is done with the "footer" data field below.
@Templated("PageLayout.html")
public class LoginLayout extends PageLayout {
@Inject
@DataField
private LoginForm content;
/* Override footer defined in PageLayout */
@Inject
@DataField
private CustomFooter footer;
}
Be aware that templates shouldn’t all be @Singleton
. Singleton templates will be shared, so you can’t attach the same template component twice. Duplicated template components should be dependent-scoped, so several instances will be created when they are injected.
When developing moderately-complex web applications with Errai, you may find yourself needing to do quite a bit of programmatic style changes. One common case is showing or enabling controls only if a user has the necessary permissions to use them. One part of the problem is securing those features from being used, and the other part which is an important usability consideration is communicating that state to the user.
Errai Security contains a RestrictedAccess
annotation that uses style sheet binding to implement a feature similar in nature to this example.
Let’s start with the example case I just described. We have a control that we only want to be visible if the user is an admin. So the first thing we do is create a style binding annotation.
@StyleBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Admin {
}
This defines Admin
as a stylebinding now we can use it like this:
@EntryPoint
@Templated
public class HelloWorldForm {
@Inject @Admin @DataField ButtonElement deleteButton;
@Inject SessionManager sessionManager;
@EventHandler("deleteButton")
private void handleSendClick(ClickEvent event) {
// do some deleting!
}
@Admin
private void applyAdminStyling(Style style) {
if (!sessionManager.isAdmin()) {
style.setVisibility(Style.Visibility.HIDDEN);
}
}
}
Now before the form is shown to the user the applyAdminStyling
method will be executed where the sessionManager
is queried to see if the user is an admin if not the delete button that is also annotated with @Admin
will be hidden from the view.
The above example took at Style
object as a parameter, but it is also possible to use an Element
. So the applyAdminStyling
method above could have also been written like this:
@Admin
private void applyAdminStyling(Element element) {
if (!sessionManager.isAdmin()) {
element.addClassName("disabled");
}
}
The CSS class "disabled" could apply the same style as before ("visibility: hidden") or it could have more complex behaviour that is dependent on the element type.
User interfaces often need to be available in different languages. Errai’s i18n support makes it easier for you to publish your web app in multiple languages. This section explains how to use this feature in your application.
To get started with Errai’s internationalization support, simply put @Bundle("bundle.json")
or @Bundle("bundle.properties")
annotation on your entry point and add an empty bundle.json
or bundle.properties
file to your classpath (e.g. to src/main/java or src/main/resources). Of course, you can name it differently provided the file extension is .json
or .properties
.
Errai will scan your HTML templates and process all text elements to generate key/value pairs for translation. It will generate a file called errai-bundle-all.json
and put it in your .errai
directory. If you used a JSON file in you @Bundle
annotation, you can copy this generated file and use it as a starting point for your custom translation bundles. If the text value is longer than 128 characters the key will get cut off and a hash appended at the end.
The translation bundle files use the same naming scheme as Java (e.g. bundle_nl_BE.json
or bundel_nl_BE.properties
for Belgian Dutch, and bundle_nl.json
or bundle_nl.properties
for plain Dutch). Errai will also generate a file called errai-bundle-missing.json
in the .errai
folder containing all template values for which no translations have been defined. You can copy the key/value pairs out of this file to create our own translations when you use a JSON file:
{ "StoresPage.Stores!" : "Stores!", "WelcomePage.As_you_move_toward_a_more_and_more_declarative_style,_you_allow_the_compiler_and_the_framework_to_catch_more_mistakes_up_front._-734987445" : "As you move toward a more and more declarative style, you allow the compiler and the framework to catch more mistakes up front. Broken links? A thing of the past!" }
Here are the same translations as specified by a properties file:
StoresPage.Stores! = Stores! WelcomePage.As_you_move_toward_a_more_and_more_declarative_style,_you_allow_the_compiler_and_the_framework_to_catch_more_mistakes_up_front._-734987445 = As you move toward a more and more declarative style, you allow the compiler and the framework to catch more mistakes up front. Broken links? A thing of the past!
If you want to use your own keys instead of these generated ones you can specify them in your templates using the data-i18n-key
attribute:
<html>
<body>
<div id="content">
<p data-i18n-key="welcome">Welcome to errai-ui i18n.</p>
<div>
...
By adding this attribute in the template you can translate it with the following:
{ "TemplatedClassName.welcome": "Willkommen bei Errai-ui i18n." }
or
TemplatedClassName.welcome=Willkommen bei Errai-ui i18n.
These keys are prefixed with the name of the @Templated
class to which they belong, and as such will only applied to the element with data-i18n-key="welcome"
in that classes template. You may also declare translations without the prefix that can apply to any template, like so:
{ "welcome": "Willkommen bei Errai-ui i18n." }
or
welcome=Willkommen bei Errai-ui i18n.
Either of these translations will apply to elements with data-i18n-key="welcome"
in any template.
Because your templates are designer templates and can contain some mock data that doesn’t need to be translated, Errai has the ability to indicate that with an attribute data-role=dummy
:
<div id=navbar data-role=dummy>
<div class="navbar navbar-fixed-top">
<div class=navbar-inner>
<div class=container>
<span class=brand>Example Navbar</span>
<ul class=nav>
<li><a>Item</a>
<li><a>Item</a>
</ul>
</div>
</div>
</div>
</div>
Here the template fills out a navbar with dummy elements, useful for creating a design, adding data-role=dummy
will not only exclude it form being translated it will also strip the children nodes from the template that will be used by the application.
When you have setup a translation of your application Errai will look at the browser locale and select the locale, if it’s available, if not it will use the default (bundle.json
). If the users of your application need to be able to switch the language manually, Errai offers a pre build component you can easily add to your page: LocaleListBox
will render a Listbox with all available languages. If you want more control of what this language selector looks like there is also a LocaleSelector
that you can use to query and select the locale for example:
@Templated
public class NavBar {
@Inject
private LocaleSelector selector;
@Inject @DataField @OrderedList
ListWidget<Locale, LanguageItem> language;
@AfterInitialization
public void buildLanguageList() {
language.setItems(new ArrayList<Locale>(selector.getSupportedLocales()));
}
...
// in LanguageItem we add a click handler on a link
@Inject
Navigation navigation;
@Inject
private LocaleSelector selector;
link.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
selector.select(model.getLocale());
navigation.goTo(navigation.getCurrentPage().name());
}
});
The @TranslationKey
annotation and TranslationService
class extend Errai’s i18n support to Java code. They provide a mechanism for developers to declare translation strings from within their GWT application code (as opposed to the HTML templates).
To do this, developers must annotate a field which represents the translation key with @TranslationKey
annotation. This key will then map to a value in the translation bundle file. Once the field is annotated appropriately, the developer must directly invoke the TranslationService’s format() method. This method call will perform a lookup in the translation service of the value mapped to the provided key. Note that value substitution using the {N}
format is supported.
As an example, consider the following code:
package org.example.ui.client.local;
public class AppMessages {
@TranslationKey(defaultValue = "I guess something happened!")
public static final String CUSTOM_MESSAGE = "app.custom-message";
@TranslationKey(defaultValue = "Hey {0}, I just told you something happened!")
public static final String CUSTOM_MESSAGE_WITH_NAME = "app.custom-message-with-name";
}
package org.example.ui.client.local;
@Dependent
@Templated
public class CustomComponent extends Composite {
@Inject
private TranslationService translationService;
@Inject
@DataField
private Button someAction;
@EventHandler("someAction")
private void doLogin(ClickEvent event) {
// do some action that may require a notification sent to the user
String messageToUser = translationService.format(AppMessages.CUSTOM_MESSAGE);
Window.alert(messageToUser);
String username = getCurrentUserName();
String messageToUserWithName = translationService.format(AppMessages.CUSTOM_MESSAGE_WITH_NAME, username);
Window.alert(messageToUserWithName);
}
}
Errai also supports LESS stylesheets for @Templated
. You can specify a CSS or LESS stylesheet path for a component with the stylesheet
attribute of @Templated
, as in the example below:
package org.jboss.errai.example;
@Templated(stylesheet = "main.less")
public class StyledComponent {
}
For the above example, during GWT compilation Errai will load the LESS stylesheet from the classpath (in this case with the path org/jboss/errai/example/main.less
) and compile it. At runtime the compiled CSS will be loaded.
You can omit the stylesheet
attribute if your LESS stylsheet follows the standard Errai UI naming conventions. For example:
package org.jboss.errai.example;
@Templated
public class StyledComponent {
}
Errai will automatically find and compile a LESS stylesheet for this template if it exists on the classpath at this path: org/jboss/errai/example/StyledComponent.less
When the stylesheet
attribute is not specified, Errai will only find a LESS stylesheet as described above if no CSS stylesheet exists at the default path.