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 widgets. Inevitably one must turn to Java-based configuration in order to finish the job. Errai, however, strives to remove the need for Java styling. HTML template files are placed in the project source tree, and referenced from custom "Composite components" (Errai UI Widgets) 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 widgets:
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 .
Before explaining how to create Errai UI components, it should be noted that these components behave no differently from any other GWT Widget once built. The primary difference is in A) their construction, and B) their instantiation. 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.
@EntryPoint
public class Application {
@Inject
private ColorComponent comp;
@PostConstruct
public void init() {
comp.setColor("blue");
RootPanel.get().add(comp);
}
}
@EntryPoint
public class Application {
private String[] colors = new String[]{"Blue", "Yellow", "Red"};
@Inject
private Instance<ColorComponent> instance;
@PostConstruct
public void init() {
for(String color: colors) {
ColorComponent comp = instance.get();
comp.setColor(c);
RootPanel.get().add();
}
}
}
Custom components in Errai UI are single classes extending from com.google.gwt.user.client.ui.Composite
, and must be annotated with @Templated.
@Templated
public class LoginForm extends Composite {
/* looks for LoginForm.html in LoginForm's package */
}
With default values, @Templated informs Errai UI to look in the current package for a parallel ".html"
template next to the Composite component Class; however, the template name may be overridden by passing a String into the @Templated annotation, like so:
@Templated("my-template.html")
public class LoginForm extends Composite {
/* 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 extends Composite {
/* looks for my-template.html in package org.example */
}
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 Composite component to add behavior, and use additional components to add functionality to the template. There is no limit to how many component classes may share a given HTML template.
We will begin by creating a simple HTML login form to accompany our @Templated LoginForm
composite component.
<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>
Or as a full HTML document which may be more easily previewed during design without running the application; however, in this case we must also specify the location of our component’s root DOM Element using a "data-field"
, id
, or class
attribute matching the value of the @Templated annotation. There is no limit to how many component classes may share a given HTML template.
@Templated("my-template.html#login-form")
public class LoginForm extends Composite {
/* Specifies that <... id="login-form"> be used as the root Element of this Widget */
}
Notice the corresponding HTML id
attribute in the form Element below (we could have used data-field
or class
instead). Note that multiple components may use the same template provided that they specify a corresponding data-field
, id
, or class
attribute. Also note that two or more components may share the same DOM elements; there is no conflict since components each receive a unique copy of the template DOM rooted at the designated element at runtime (or from the root element if a fragment is not specified.)
<!DOCTYPE html>
<html lang="en">
<head>
<title>A full HTML snippet</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>
For example’s sake, the component below could also use the same template. All it needs to do is reference the template name, and specify a fragment.
@Templated("my-template.html#theme-footer")
public class Footer extends Composite {
/* Specifies that <... id="theme-footer"> be used as the root Element of this Widget */
}
Now that we have created the @Templated Composite component 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 with other Widgets. We can even replace portions of the template with other Errai UI Widgets!
In order to composite Widgets into the template DOM, you annotate fields in your @Templated Composite component with @DataField, and mark the HTML template Element with a correspondingly named data-field
, id
, or class
attribute. This informs Errai UI which element in the template the Widget should replace. All replacements happen while the @Templated Composite component is being constructed; thus, fields annotated with @DataField must either be {{@Inject}}ed or provide their own Widget or Element instances in field initializers.
@Templated
public class LoginForm extends Composite {
// This element must be initialized manually because Element is not @Inject-able*/
@DataField
private Element form = DOM.createForm();
// 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 TextBox username;
// The name to reference in the template can also be specified manually
@Inject
@DataField("pass")
private PasswordTextBox password;
// We can also choose to instantiate our own Widgets. Injection is not required.
@DataField
private Button submit = new Button();
}
Note: Field, method, and constructor injection are all supported by @DataField.
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.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 extends Composite {
@Inject @DataField
private Label eat;
@Inject @DataField
private Label 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 Composite component allow 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 Widget.
Three things are merged or modified when Errai UI creates a new Composite component instance:
@DataField Widget
implements HasText
or HasHTML
@Templated
public class StyledComponent extends Composite {
@Inject
@DataField("field-1")
private Label div = new Label();
public StyledComponent() {
div.getElement().setAttribute("style", "position: fixed; top: 0; 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 we find that once you understand why these things occur, you’ll find the 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 Widgets. This "attribute merge" occurs only when the components are instantiated; subsequent changes to any attributes after Widget construction will function normally. In the example we defined a Composite 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 the template, @PostConstruct
or other methods should be favored over constructors to apply styles to fully-constructed Composite components.
Element composition, however, functions inversely from attribute merging, and the <span>
defined in our template was actually be replaced by the <div>
Label in our Composite 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. In short, whatever is inside the @DataField
in your class will replace the children of the corresponding element in your template.
Additionally, because Label
implements both HasText
and HasHTML
(only one is required,) the contents of this <span> "field-1" Element in the template were preserved; however, this would not have been the case if the @DataField
specified for the element did not implement HasText
or HasHTML
. 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 Widget being composited implements HasText
or HasHTML
.
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 three 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.
Probably the simplest and most common use-case, 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
}
}
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 extends Composite {
@Inject
@Model
private User user;
@Inject
@Bound
@DataField
private TextBox 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 UserWidget extends Composite {
@Inject @AutoBound DataBinder<User> user;
@Inject @Bound TextBox name;
@Inject @Bound("dob") DatePicker dateOfBirth;
@Inject @Bound("address.city") TextBox city;
}
In UserWidget
above, the name
text box 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 widget. This task can be accomplished using Errai UI’s ListWidget
class. Here’s an example of binding a list of users using the UserWidget
class from the previous example. First, we need to enhance UserWidget
to implement HasModel
.
@Templated
public class UserWidget extends Composite implements HasModel<User> {
@Inject @AutoBound DataBinder<User> userBinder;
@Inject @Bound TextBox name;
@Inject @Bound("dob") DatePicker dateOfBirth;
@Inject @Bound("address.city") TextBox city;
public User getModel() {
userBinder.getModel();
}
public void setModel(User user) {
userBinder.setModel(user);
}
}
Now we can use UserWidget
to display items in a list.
@Templated
public class MyComposite extends Composite {
@Inject @DataField ListWidget<User, UserWidget> userListWidget;
@PostConstruct
public void init() {
List<User> users = .....
userListWidget.setItems(users);
}
}
Calling setItems
on the userListWidget
causes an instance of UserWidget
to be displayed for each user in the list. The UserWidget
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 extends Composite {
@Inject
@Bound
@DataField
private TextBox 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 Composite components to build up a hierarchy of widgets functions exactly the same as when building hierarchies of GWT widgets. The only distinction might be that with Errai UI, @Inject
is preferred to manual instantiation.
@Templated
public class ComponentOne extends Composite {
@Inject
@DataField("other-comp")
private ComponentTwo two;
}
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 Composite components extending from a parent Composite 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 extends Composite {
@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 Widget 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;
}
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 extends Composite {
@Inject @Admin @DataField Button 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 the @Bundle("bundle.json")
annotation on your entry point and add an empty bundle.json
file to your classpath (e.g. to src/main/java or src/main/resources). Of course, you can name it differently.
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. 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
for Belgian Dutch and bundle_nl.json
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:
{
"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:
{
"Widget.welcome": "Willkommen bei Errai-ui i18n."
}
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 extends Composite {
@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. To get started using these you’ll have to create a LESS stylesheet and place it on the classpath of your project and declare their ordering with the StyleDescriptor
annotation. Every application should have 0 or 1 classes annotated with StyleDescriptor
like the following example:
package org.jboss.errai.example;
@StyleDescriptor({ "/main.less", "other.css" })
public class MyStyleDescriptor {
}
The two files listed above, main.less
and other.css
, will be compiled into a single stylesheet by Errai. The relative path for other.css
will be loaded relative to the package org.jboss.errai.example
.
It is only necessary to declare top-level stylesheets with the StyleDescriptor
. If a CSS or LESS resource is only meant to be imported by another LESS stylesheet, then it need only be on the classpath.
Errai will convert the LESS stylesheet to css, perform optimisations on it, and ensure that is get injected into the pages of your application. It will also obfuscate the class selectors and replace the use of those in your templates. To be able to use the selectors in your code you can use:
public class MyComponent extends Component {
@Inject
private LessStyle lessStyle;
...
@PostConstruct
private void init() {
textBox.setStyleName(lessStyle.get("input"));
}
}
Finally it will also add any deferred binding properties to the top of your LESS stylesheet, so for example you could use the user.agent in LESS like this:
.mixin (@a) when (@a = "safari") {
background-color: black;
}
.mixin (@a) when (@a = "gecko1_8") {
background-color: white;
}
.class1 { .mixin(@user_agent) }
Because a dot is not allowed in LESS variables it’s replaced with an underscore, so in the example above class1 will have a black background on Safari and Chrome and white on Firefox. On the top of this LESS stylesheet @user_agent: "safari" will get generated.