Going native
A guide to using pure html5 ui and integrating non-gwt javascript with errai 4.0

By Max Barkley, Software Engineer at Red Hat
What is errai?

a web framework built with gwt
-
Dependency Injection
-
Client-Side Templating
-
Two-Way Data-Binding
-
Client-Side JAX-RS Support
-
CDI Events over HTTP
What is errai?

declarative Good, Boilerplate bad
-
Annotation-Driven features express intent without boilerplate
-
Uses CDI APIs (dependency injection and CDI events)

compile-time safety
-
Leverages the Java type system whenever possible
-
Validate annotation use at compile-time
Outline

errai app basics
-
Dependency injection
-
Templating
-
Data-Binding

JS Interop Basics
-
@JsType(isNative=true)
-
@JsProperty
-
@JsOverlay

Errai JS Interop Support
-
Using JS interop DOM wrappers in Errai templates
-
Writing/using wrappers for web components
-
Integrating third-party JS libraries
errai basics

Declarative Dependency Injection
@ApplicationScoped
public class SingletonService {
// ...
}
public class FooProducer {
@Produces static Foo newFoo() {
// ...
}
}
-
Injection points are dependencies of a bean
public class ServiceConsumer {
@Inject SingletonService service;
// ...
}
public class FooConsumer {
@Inject
public FooConsumer(Foo foo) {
// ...
}
}
-
Beans are objects with managed deps and lifecycles
errai basics

Declarative Dependency Injection
-
Dependencies are resolved based on type and qualifiers
-
Qualifiers are annotations for beans and injection points
-
they remove ambiguity when injecting common supertypes
-
@Named("Visa")
public class Visa
implements CreditCard {
// ...
}
@Named("Amex")
public class Amex
implements CreditCard {
// ...
}
-
For example...
This is ambgiuous
@Inject CreditCard card;
And this is not
@Inject @Named("Visa") CreditCard card;
errai basics

Declarative Dependency Injection
-
Declaratively define app entrypoints
@EntryPoint
public class AppStartup {
@PostConstruct
public void start() {
// Startup logic goes here...
}
}
-
@EntryPoint is a scope (like @ApplicationScoped)
-
but it is eagerly instantiated
-
-
@PostConstruct is a life-cycle method, invoked after the bean is created
errai basics

Client-side Templating
-
Create UI components declaratively
-
A component has two parts:
-
Template (HTML, CSS/LESS)
-
@Templated bean (a Java class)
-
errai basics

Client-side Templating
-
A template consists of:
-
An HTML file (mandatory)
-
A CSS or LESS stylesheet (optional)
-
errai basics

Client-side Templating
-
The HTML file can be a fragment or a full page
<!-- Template as fragment -->
<form class="form">
<input class="username" type="text">
<input class="password" type="password">
</form>
<!DOCTYPE html>
<html>
<head></head>
<body>
<!-- Any fragment in the body can be used -->
<form class="form">
<input class="username" type="text">
<input class="password" type="password">
</form>
</body>
</html>
errai basics

Client-side Templating
-
A @Templated bean is a Java class that defines behaviour
-
Is declared with the @Templated annotation
-
the paths are optional if convention is followed
-
IsElement allows access to the root element (optional)
-
@Templated(value = "HomeScreen.html", stylesheet = "HomeScreen.css")
public class HomeScreen implements IsElement {
// ...
}
-
If the above compiles, then so does this
@Templated
public class HomeScreen implements IsElement {
// ...
}
errai basics

Client-side Templating
-
Use fragment identifiers to replace elements in your template with fields in your @Templated bean
-
In your template, a fragment identifier is an id, class, or data-field attribute value.
<form id="userForm">
<input data-field="username" type="text">
<input class="password" type="password">
</form>
errai basics

Client-side Templating
-
In your @Templated bean use @DataField to map fields to fragment identifiers
// The "userForm" fragment in "UserForm.html" is the component root
@Templated("#userForm")
public class UserForm {
// Maps to fragment "username"
@Inject @DataField TextBox username;
// Maps to fragment "password"
@Inject @DataField("password") PasswordTextBox pass;
}
<form id="userForm">
<input data-field="username" type="text">
<input class="password" type="password">
</form>
errai basics

Client-side Templating
-
@EventHandler declares handlers for GWT Widget events and native DOM events
-
Uses fragment identifiers to declare which UI element the handler is for
errai basics

Client-side Templating
@Templated
public class UserForm {
// ...
@Inject @DataField Button submit; // Optional
@EventHandler("submit")
public void onClick(@ForEvent("click") MouseEvent event) {
// ...
}
}
-
This declares a listener for a native browser event
-
Listens for the "click" event
-
Listens on the "submit" element in the template
-
Doesn't require there to be a @DataField for "submit"
-
errai basics

Client-side Templating
@Templated
public class UserForm {
// ...
@Inject @DataField Button submit; // Mandatory
@EventHandler("submit")
public void onClick(ClickEvent event) {
// ...
}
}
-
This declares a listener for a GWT widget event
-
Listens for the ClickEvent
-
Listens on the "submit" @DataField
-
Requires there to be a @DataField that is a GWT widget
-
errai basics

Declarative Data-Binding
-
Data-binding synchronizes state between a data-model and UI elements
-
Data-binding in Errai is two-way
-
model changes propogate to the UI
-
UI changes propogate to the model
-
errai basics

Declarative Data-Binding
-
Any type annotated with @Bindable can be used as a data-binding model
-
Properties of a model must be primitives, Strings, or other @Bindable types
-
Properties are inferred based on getter/setter names
-
This example has the properties "name" and "address"
-
@Bindable
public class User {
private String name;
private Address address; // Address must be @Bindable
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
errai basics

Declarative Data-Binding
-
Use @Model in UI components to declare a field as a data-binding model
-
Use @Bound to declare UI fields as being data-bound to model properties
-
Binds to property with field name by default
-
Can bind to nested properties using simple notation
-
@Templated
public class NewUserForm {
@Inject @Model User user;
@Inject @Bound @DataField TextBox name;
@Inject @Bound("address.street") @DataField TextBox street;
@Inject @Bound("address.number") @DataField IntegerBox number;
}
JS Interop Basics

Declarative JS Interoperability
-
JS interop is a set of annotations for defining interfaces between GWT-compiled and native JavaScript
-
Supports two use cases: consuming and publishing
-
In this talk we will focus on consuming
JS Interop Basics

@JsType
-
@JsType declares an API for a JavaScript object
-
When isNative=false (the default) the usage is for publishing
-
Preserves names of all public members so they can be called from external JavaScript
-
package org.errai.example;
@JsType
public class ExportedService {
private String value; // Not exported
public String getValue() { return value; } // Exported
}
// All good
var service = new org.errai.example.ExportedService();
service.getValue();
// This property is undefined -- the name has been obfuscated
service.value;
JS Interop Basics

@JsType(isNative=true)
-
A native @JsType is for consuming external JavaScript
-
Native @JsTypes have no code generated — they are compile-time bindings for external JavaScript objects
-
This means that methods cannot have implementations (with one exception)
-
@JsType(isNative=true)
public interface Window {
void alert(String msg);
// ...
}
window.alert("Hello World!");
window.alert("Hello World!");
Java
JavaScript
JS Interop Basics

@JsProperty
-
We can use @JsProperty to compile method invocations to property access
-
Property name is inferred for getter/setter methods
-
@JsType(isNative=true)
public interface InputElement {
@JsProperty String getValue();
@JsProperty void setValue(String value);
}
String old = input.getValue();
input.setValue("new");
var old = input.value;
input.value = "new";
Java
JavaScript
JS Interop Basics

@JsOverlay
-
We can use @JsOverlay to add convenience methods on top of native @JsTypes
-
@JsOverlay methods have lots of restrictions
-
Can only access public members
-
Must be effectively final
-
@JsType(isNative=true)
public class NumberInputElement {
public String value;
@JsOverlay public final int getValue() {
return Integer.parseInt(value);
}
}
errai js interop support

Use Cases
-
DOM Elements and Web Components
-
@Inject
-
@DataField
-
@Bound
-
-
Third-party JS libraries
-
@Inject objects form library APIs
-
errai js interop support

Use Case 1: DOM Elements
-
As a best practice, prefer the DOM to GWT Widgets
-
Errai 4 has a collection of JS interop element wrappers
-
The collection isn't complete but has most common elements (Div, Span, Heading, Input, ...)
errai js interop support

Use Case 1: DOM Elements
-
You can @Inject any of these wrappers
@Inject Div div;
@Inject Form form;
@Inject Span span;
-
Some wrappers are used for multiple elements
-
Use @Named to resolve ambiguity
-
@Inject @Named("h1") Heading title;
@Inject @Named("h2") Heading subtitle;
@Inject @Named("div") HTMLElement div;
-
Inject a @Named("tagname") HTMLElement to get an instance of any HTMLElement
errai js interop support

Use Case 1: DOM Elements
-
You can use any of these wrappers as @DataFields
@Templated
public class Example {
@Inject @DataField Div div;
@Inject @DataField Form form;
@Inject @Named("h1") @DataField Heading title;
}
errai js interop support

Use Case 1: DOM Elements
-
You can use @Bound with any of these wrappers
@Templated
public class Example {
@Inject @Model User user;
@Inject @Named("h1")
@Bound @DataField Heading name;
@Inject
@Bound("address.street") @DataField TextInput street;
}
-
A @Bound Heading, like most other elements, will have its text content synchronized
-
A @Bound TextInput will have its value property synchronized
-
TextArea and all other form inputs work similarly
-
errai js interop support

Use Case 1: DOM Elements
-
You can easily add your own element definitions
@JsType(isNative = true)
@Element("div")
public interface Div extends HTMLElement {
// ...
}
-
@Element makes this type injectable
-
The container creates instances of TextInput by calling document.createElement("div") and casting
-
<div></div>
errai js interop support

Use Case 1: DOM Elements
-
Below is Errai's TextInput wrapper (most of the API is inherited)
@JsType(isNative = true)
@Element("input")
@Property(name = "type", value = "text")
public interface TextInput extends Input {
}
-
@Property makes the container set the "type" attribute to the value "text"
<input type="text">
@JsType(isNative = true)
@Element("input")
@Property(name = "type", value = "password")
public interface PasswordInput extends Input {
}
<input type="password">
errai js interop support

Use Case 1: DOM Elements
-
You can define elements, like Heading, with multiple tags
@JsType(isNative = true)
@Element({"h1", "h2", "h3", "h4", "h5", "h6"})
public interface Heading extends HTMLElement {
// ...
}
-
This declaration forces use of qualifiers at Heading injection points
@Inject @Named("h1") Heading h1;
@Inject @Named("h2") Heading h2;
@Inject @Named("h3") Heading h3;
@Inject @Named("h4") Heading h4;
@Inject @Named("h5") Heading h5;
@Inject @Named("h6") Heading h6;
@Inject Heading ambiguous;
Valid
Ambiguous
errai js interop support

Use Case 1: Web Components
-
Wrapping a web component works just like wrapping an element
@Element("paper-input")
@JsType(isNative = true)
public interface PaperInput extends HTMLElement {
// ...
}
-
This is a wrapper for the Polymer paper-input element
-
It uses @Element because you can create web components with document.createElement
-
After adding Polymer to your host page, you can start injecting PaperInputs
errai js interop support

Use Case 1: Web Components
@Inject @Bound PaperInput input;
-
But how does binding to a paper-input work?
-
Recall that data-binding synchronizes element text content for elements that aren't input
-
But a paper-input behaves like an input element — we'd like to bind to the value property
<paper-input>Content</paper-input>
<paper-input value="Content">
errai js interop support

Use Case 1: Web Components
@Element("paper-input")
@JsType(isNative = true)
public interface PaperInput extends HTMLElement, HasValue<String> {
// ...
}
-
First we need to extend HasValue<T>
-
This is not the HasValue<T> in gwt-user
-
This is an Errai interface used by data-binding
-
Now data-binding will use the getValue/setValue methods
@JsType(isNative = true)
public interface HasValue<T> {
T getValue();
void setValue(T value);
}
errai js interop support

Use Case 1: Web Components
@Element("paper-input")
@JsType(isNative = true)
public interface PaperInput extends HTMLElement, HasValue<String> {
@Override @JsProperty String getValue();
@Override @JsProperty void setValue(String value);
}
-
Declare an element property for binding with @JsProperty
-
Data-binding will use the setValue/getValue methods
-
Because of the @JsProperty annotations, these are compiled to property access
String old = input.getValue();
input.setValue("foo");
var old = input.value;
input.value = "foo";
Java
JavaScript
errai js interop support

Use Case 1: Web Components
-
You can do a similar trick if you'd like data-binding to use a method on the object
String old = input.getValue();
input.setValue("foo");
var old = input.getter();
input.setter("foo");
Java
JavaScript
@Override @JsMethod(name = "getter") String getValue();
@Override @JsMethod(name = "setter") void setValue(String value);
errai js interop support

Use Case 1: Web Components
-
For more complex bindings, use @JsOverlay method
@Element("a")
@JsType(isNative = true)
public interface BindableEmailAnchor extends Anchor, HasValue<String> {
@JsOverlay @Override
default String getValue() {
return getTextContent();
}
@JsOverlay @Override
default void setValue(final String value) {
setTextContent(value);
setHref("mailto:" + value);
}
}
errai js interop support

Use Case 2: Third-party libraries
-
Errai's dependency injection makes it easy to define injectors for third-party JavaScript objects
-
In most cases, the JavaScript object you want is globally scoped or supplied by a factory method
-
In either case you can declare a @Produces method
errai js interop support

Use Case 2: Third-party libraries
-
Example: JQuery
-
JQuery has a globally scoped "$" function
-
$ wraps and enhances HTML elements
-
$(element).children().first().remove();
errai js interop support

Use Case 2: Third-party libraries
-
Example: JQuery
-
First we need to wrap the API
-
@JsFunction @FunctionalInterface
public static interface JQuery {
JQueryElement wrap(HTMLElement element);
}
@JsType(isNative = true)
public static interface JQueryElement extends HTMLElement {
// ...
}
errai js interop support

Use Case 2: Third-party libraries
-
Example: JQuery
-
Then we need a way to access the object
-
A couple options:
-
@JsType(isNative = true)
public abstract class JQueryProducer {
@JsProperty(namespace = JsPackage.GLOBAL)
public static JQuery $;
}
@JsType(isNative = true)
public abstract class JQueryProducer {
@JsProperty(name = "$", namespace = JsPackage.GLOBAL)
public static native JQuery get();
}
A method makes this read-only from GWT code
errai js interop support

Use Case 2: Third-party libraries
-
Example: JQuery
-
With previous definitions, we can write this
-
JQueryProducer.$
.wrap(element)
.remove();
$(element).remove();
Java
JavaScript
-
How can we @Inject the JQuery function?
JQueryProducer.get()
.wrap(element)
.remove();
or
errai js interop support

Use Case 2: Third-party libraries
-
Example: JQuery
-
Just add @Produces
-
With either of these we can inject the $ function
-
@JsType(isNative = true)
public abstract class JQueryProducer {
@Produces @JsProperty(namespace = JsPackage.GLOBAL)
public static JQuery $;
}
@JsType(isNative = true)
public abstract class JQueryProducer {
@Produces
@JsProperty(name = "$", namespace = JsPackage.GLOBAL)
public static native JQuery get();
}
@Inject JQuery $;
errai js interop support

Use Case 2: Third-party libraries
-
Injecting the JQuery function into a constructor makes testing in Java SE easy
@Templated
public class Form {
@Inject
public Form(JQuery $) {
// ...
}
}
@Test
public void formWithJQuery() {
Form form = new Form(new JQueryMock());
// ...
}
take away

errai basics
-
Dependency Injection
@Inject
@Templated
@DataField
-
Templating
@Model
@Bound
-
Data-Binding
@Bindable

errai JSInterop Support
-
Use native DOM wrappers with UI logic
-
Easily define custom element and web component wrappers
-
Inject wrappers for elements, web components, and JS library APIs for unit-testable code
Thank you

Some Links
-
Tutorial: github.com/errai/errai-tutorial
-
Errai Framework Website: erraiframework.org
GWT Con 2016
By Max Barkley