Skip to content
develop
Go to file
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

TC Build GWT3/J2CL compatible License Chat on Gitter

Elemento

Elemento simplifies working with GWT Elemental2. In a nutshell Elemento brings the following features to the table:

  • Type safe builders, event handlers and CSS selectors
  • Helper methods to manipulate the DOM tree
  • Ready to be used with GWT 2.8, GWT 2.9, J2CL and next gen GWT (GWT3)
  • Minimal dependencies
    • Elemental2 1.0.0:
      • com.google.elemental2:elemental2-core:1.0.0
      • com.google.elemental2:elemental2-dom:1.0.0
      • com.google.elemental2:elemental2-webstorage:1.0.0
    • org.gwtproject.event:gwt-event:HEAD-SNAPSHOT
    • org.gwtproject.safehtml:gwt-safehtml:1.0-SNAPSHOT

TOC

Get Started

Elemento is available in Maven Central. To use it add the following dependency to your POM:

<dependency>
    <groupId>org.jboss.elemento</groupId>
    <artifactId>elemento-core</artifactId>
    <version>HEAD-SNAPSHOT</version>
</dependency>

In your GWT module inherit from org.jboss.elemento.Core:

<module>
    <inherits name="org.jboss.elemento.Core"/>
</module>

Builder API

When working with GWT Elemental it is often awkward and cumbersome to create an hierarchy of elements. Even simple structures like

<section class="main">
    <input class="toggle-all" type="checkbox">
    <label for="toggle-all">Mark all as complete</label>
    <ul class="todo-list">
        <li>
            <div class="view">
                <input class="toggle" type="checkbox" checked>
                <label>Taste Elemento</label>
                <button class="destroy"></button>
            </div>
            <input class="edit">
        </li>
    </ul>
</section>

lead to a vast amount of Document.createElement() and chained Node.appendChild() calls. With Elemento creating the above structure is as easy as

import static org.jboss.elemento.Elements.*;
import static org.jboss.elemento.InputType.checkbox;
import static org.jboss.elemento.InputType.text;

HTMLElement section = section().css("main")
        .add(input(checkbox).id("toggle-all").css("toggle-all"))
        .add(label()
                .apply(l -> l.htmlFor = "toggle-all")
                .textContent("Mark all as complete"))
        .add(ul().css("todo-list")
                .add(li()
                        .add(div().css("view")
                                .add(input(checkbox)
                                        .css("toggle")
                                        .checked(true))
                                .add(label().textContent("Taste Elemento"))
                                .add(button().css("destroy")))
                        .add(input(text).css("edit"))))
        .element();

The class Elements provides convenience methods to create the most common elements. It uses a fluent API to create and append elements on the fly. Take a look at the API documentation for more details.

References

When creating large hierarchies of elements you often need to assign an element somewhere in the tree. Use an inline assignment together with element() to create and assign the element in one go:

import static org.jboss.elemento.Elements.*;

final HTMLElement count;
final HTMLElement footer = footer()
        .add(count = span().css("todo-count").element())
        .element();

Event Handlers

Elemento provides methods to easily register event handlers. There are constants for most of the known event types.

You can either add event handlers when building the element hierarchy:

import static org.jboss.elemento.Elements.*;
import static org.jboss.elemento.EventType.*;
import static org.jboss.elemento.InputType.checkbox;
import static org.jboss.elemento.InputType.text;

HTMLLIElement listItem = li()
        .add(div().css("view")
                .add(input(checkbox)
                        .css("toggle")
                        .on(change, event -> toggle()))
                .add(label()
                        .textContent("Taste Elemento")
                        .on(dblclick, event -> edit()))
                .add(button()
                        .css("destroy")
                        .on(click, event -> destroy())))
        .add(input(text)
                .css("edit")
                .on(keydown, this::keyDown)
                .on(blur, event -> blur()))
        .element();

or register them later using EventType.bind():

import org.gwtproject.event.shared.HandlerRegistration;
import static elemental2.dom.DomGlobal.alert;
import static org.jboss.elemento.EventType.bind;
import static org.jboss.elemento.EventType.click;

HandlerRegistration handler = bind(listItem, click, event -> alert("Clicked"));

The latter approach returns org.gwtproject.event.shared.HandlerRegistration which you can use to remove the handler again.

In order to make it easier to work with keyboard events, Elemento provides an enum with the most common keyboard codes:

import elemental2.dom.KeyboardEvent;
import static org.jboss.elemento.Key.Escape;
import static org.jboss.elemento.Key.Enter;

void keyDown(KeyboardEvent event) {
    if (Escape.match(event)) {
        ...
    } else if (Enter.match(event)) {
        ...
    }
}

Typesafe CSS Selectors

Elemento provides a typesafe selector API. It can be used to express simple CSS selector like .class or #id up to complex selectors like

#main [data-list-item|=foo] a[href^="http://"] > .fas.fa-check, .external[hidden]

This selector can be created with

import org.jboss.elemento.By;
import static org.jboss.elemento.By.AttributeOperator.CONTAINS_TOKEN;
import static org.jboss.elemento.By.AttributeOperator.STARTS_WITH;

By selector = By.group(
        By.id("main")
                .desc(By.data("listItem", CONTAINS_TOKEN, "foo")
                        .desc(By.element("a").and(By.attribute("href", STARTS_WITH, "http://"))
                                .child(By.classname(new String[]{"fas", "fa-check"})))),
        By.classname("external").and(By.attribute("hidden"))
);

The selector can be used to find single or all HTML elements:

import org.jboss.elemento.By;
import static org.jboss.elemento.By.AttributeOperator.STARTS_WITH;
import static org.jboss.elemento.Elements.a;
import static org.jboss.elemento.Elements.body;

By selector = By.element("a").and(By.attribute("href", STARTS_WITH, "http://"));
for (HTMLElement element : body().findAll()) {
    a(element).css("external");
}

Custom Elements

Elemento makes it easy to create custom elements. As for Elemento custom elements are a composite of HTML elements and / or other custom elements. They're ordinary classes which can hold state or register event handlers. The only requirement is to implement IsElement<E extends HTMLElement> and return a root element:

import static org.jboss.elemento.Elements.*;

class TodoItemElement implements IsElement<HTMLElement> {
    
    private final HTMLElement root;
    private final HTMLInputElement toggle;
    private final HTMLElement label;
    private final HTMLInputElement summary;

    TodoItemElement(TodoItem item) {
        this.root = li().data("item", item.id)
                .add(div().css("view")
                        .add(toggle = input(checkbox).css("toggle")
                                .checked(item.completed)
                                .element())
                        .add(label = label().textContent(item.text).element())
                        .add(destroy = button().css("destroy").element()))
                .add(summary = input(text).css("edit").element())
                .element();
        this.root.classList.toggle("completed", item.completed);
    }
    
    @Override
    public HTMLElement element() {
        return root;
    }
    
    // event handlers omitted
}

The builder API has support for IsElement<E extends HTMLElement> which makes it easy to use custom elements when building the element hierarchy:

import static org.jboss.elemento.Elements.ul;

TodoItemRepository repository = ...;
TodoItemElement[] itemElements = repository.items().stream()
        .map(TodoItemElement::new)
        .toArray();
ul().addAll(itemElements).element();

Goodies

Besides the builder API, Elemento comes with a bunch of static helper methods that roughly fall into these categories:

  1. Get notified when an element is attached to and detached from the DOM tree.
  2. Iterate over elements.
  3. Methods to manipulate the DOM tree (add, insert and remove elements).
  4. Methods to manipulate an element.
  5. Methods to generate safe IDs.

See the API documentation of Elements for more details.

Attach / Detach

Implement Attachable to get notified when an element is attached to and detached from the DOM tree. The attachable interface provides a static method to easily register the callbacks to attach(MutationRecord) and detach(MutationRecord):

import elemental2.dom.MutationRecord;
import org.jboss.elemento.Attachable;
import org.jboss.elemento.IsElement;
import static elemental2.dom.DomGlobal.console;
import static org.jboss.elemento.Elements.li;

class TodoItemElement implements IsElement<HTMLElement>, Attachable {
    
    private final HTMLElement root;

    TodoItemElement(TodoItem item) {
        this.root = li().element();
        Attachable.register(root, this);
    }
    
    @Override
    public HTMLElement element() {
        return root;
    }

    @Override
    public void attach(MutationRecord mutationRecord) {
        console.log("Todo item has been attached");
    }

    @Override
    public void detach(MutationRecord mutationRecord) {
        console.log("Todo item has been detached");
    }
}

Elemento uses the MutationObserver API to detect changes in the DOM tree and passes an MutationRecord instance to the attach(MutationRecord) and detach(MutationRecord) methods. This instance contains additional information about the DOM manipulation.

Iterators / Iterables / Streams

Elemento provides several methods to iterate over node lists, child elements or elements returned by a selector. There are methods which return Iterator, Iterable and Stream.

See the API documentation of Elements for more details.

J2CL / GWT 3

Elemento is ready to be used with J2CL and GWT 3.0. It has no dependencies to classes from com.google.gwt. Elemento depends only on org.gwtproject.event:gwt-event, org.gwtproject.safehtml:gwt-safehtml and Elemental2 1.0.0, but can also be used with Elemental2 1.0.0-RC1. In this case you have to override the Elemental2 dependencies. See the GWT 2.8 sample for more infos.

Samples

Elemento comes with different implementations of the TodoMVC application.

  • GWT 2.8: Uses GWT 2.8
  • GWT 2.9: Uses GWT 2.9
  • GWT 3.0: Uses GWT HEAD-SNAPSHOT and Elemental2 1.0.0
  • J2CL: Uses J2CL
  • Crysknife: Uses J2CL, Crysknife CDI and Crysknife templates

All samples are available online at https://hal.github.io/elemento/index.html.

Contributing

If you want to contribute to Elemento, please follow the steps in contribution.

Get Help

If you need help feel free to contact us at Gitter, browse the API documentation or file an issue.

You can’t perform that action at this time.