@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 */
}
<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>
@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 */
}
<!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>
@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 */
}
@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
.
@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.
<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:
<div>
<a id="link" href="/page">this is a hyperlink</a>
<div data-field="div"> Some content </div>
</div>
@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
}
}
Here is a sample @Templated
login form class. This form has:
@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 |
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())
.
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;
}
@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);
}
}
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;
}
}
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).
@Templated("PageLayout.html")
public class LoginLayout extends PageLayout {
@Inject
@DataField
private LoginForm content;
}
@Templated("PageLayout.html")
public class LoginLayout extends PageLayout {
@Inject
@DataField
private LoginForm content;
/* Override footer defined in PageLayout */
@Inject
@DataField
private CustomFooter footer;
}
@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);
}
}
}
@Admin
private void applyAdminStyling(Element element) {
if (!sessionManager.isAdmin()) {
element.addClassName("disabled");
}
}
{
"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!"
}
<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."
}
<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>
@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());
}
});
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.