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

  1. DOM Elements and Web Components
    • @Inject
    • @DataField
    • @Bound
  2. 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

GWT Con 2016

By Max Barkley

GWT Con 2016

Presentation slides for GWT Con 2016

  • 1,860
Loading comments...

More from Max Barkley