Update 4 Sep 2019: The example shows how to use Lit with current Vaadin 14 LTS. This means that you can use this approach right now, if you wish. |
LitElement is a new lightweight way to create web components, powered by a templating library called lit-html. Lit leverages standard browser functionality to great effect; it’s efficient, expressive and extensible.
import { LitElement, html } from 'lit-element';
class SimpleGreeting extends LitElement {
static get properties() {
return {
name: { type: String }
}
}
render() {
return html`<p>Hello, ${this.name}</p>`;
}
}
customElements.define('simple-greeting', SimpleGreeting);
<simple-greeting name='Vaadin'></simple-greeting>
When combining Lit with Vaadin you can make full use of Vaadin, including built-in secure bi-directional communication and server push.
Quick summary for the impatient: You can use LitElement today with Vaadin 14 but there is no @Id mapping like with PolymerTemplate and no shared model. If you think one of those would be beneficial or some other feature, please comment below. |
With Vaadin 14, you have a component hierarchy which is owned by the server side. To add a LitElement
template to the hierarchy, it must have a server side Java class. For the above example, it can be created as:
@Tag("simple-greeting")
@JsModule("./simple-greeting.js")
public class SimpleGreetingComponent extends Component {
public SimpleGreetingComponent() {
getElement().setProperty("name", "Java");
}
}
When added to e.g. a layout, this SimpleGreetingComponent
will
Ensure the LitElement
file simple-greeting.js
(in /frontend) is loaded (@JsModule("./simple-greeting.js")
Create a DOM element using the <simple-greeting>
tag (@Tag("simple-greeting")
)
Set the name
property to Java
so the component will render "Hello, Java" (getElement().setProperty("name", "Java");
)
With the above in order, you can either use your Lit template as the root of a view by adding @Route
to SimpleGreetingComponent
, or you can use it inside another component as e.g. add(new SimpleGreetingComponent())
.
The @JsModule annotation must start with ./ when the template is in the /frontend folder of your project. Otherwise the file is assumed to be inside /node_modules . This is also how JavaScript modules work. |
Sometimes you want to add a server side Java component inside a Lit template. To accomplish this, you can create a <slot>
element in your template. Any element added to the template will be rendered inside this <slot>
.
For instance, if you want to use a server side Grid
configured automatically from a bean in your template you could do
render() {
return html`
<h1>Select a person</h1>
<slot></slot>
`
}
and on the server side
public class MyTemplate extends Component implements HasComponents {
public MyTemplate() {
Grid<Person> grid = new Grid<>(Person.class);
add(grid);
}
}
If you want to add components to multiple locations in your template, you can give names to your slots. Then you need to set a slot
attribute on the child component for it to be added to that particular slot, e.g.
<slot name="grid"></slot>
grid.getElement().setAttribute("slot", "grid");
add(grid);
You can use the available Element
methods to set properties for a template as shown above, e.g. getElement().setProperty("name", "Java")
.
There is currently no setProperty overload for beans so you need to manually convert those to Json first (see flow#4731). |
In a LitElement
you can add element event listeners using the @event-type=${e ⇒ something()}
syntax.
To get the events to the server, you can either listen for a given event on the server:
// Listen for "some-event" on the template root element
getElement().addEventListener("some-event", e-> {
System.out.println("some-event was fired in the browser");
});
Or you can export server side methods and call them directly from the browser
@ClientCallable
public void somethingHappened() {
System.out.println("Something happened in the browser");
}
render() {
return html`
<button @click="${e => this.$server.somethingHappened()}">Do something</button>
`
}
We created a fully working example which consists of a lazy loading list of persons which are also plotted on a map. Push is used to update person locations in real time.
Try the demo and view the source.
Initially we thought about integrating LitElement the same way we have integrated PolymerTemplate
with @Id
and model support. It has however turned out that not only is @Id
technically challenging to do, but it also causes confusion among users.
The main problems with @Id
seem to be that it does not fully work how users would expect as it does not sync the component/element 100% as that would be very expensive (as in massive communication between browser and server). Instead, the component (element) is created in the browser and then a server side component instance is attached to it. Both sides only know about the parts they somehow deem "necessary". Because of this, e.g. Grid
columns initialized in the template are not available on the server.
Another problem with @Id
is that there is no clear owner of the component as it exists on both sides and both sides are kind of the master of the component. When using a template with a <slot>
, it becomes clear that the template is rendered purely in the browser and if a server side component is added to the <slot>
then that component is owned by the server.
The final(?) problem with @Id
is that the server side component instance is created automatically. There is no way for example to create a Grid
instance as new Grid(Person.class)
. So when you want to initialize a Grid
based on a bean, you end up using a slot anyway.
The model has some nice features like the possibility to send beans, lists of beans, maps etc directly to the client. These should however be more generic features that allow sending a bean as an element property to any element.
The challenges with the PolymerTemplate
model is similar to the problems with having an @Id
mapped components on both the server and the client. You quickly end up in the situation where you add some properties to the model on the server, some on the client and then you are no longer really sure who owns the model and what should be there.
It has also turned out that in many cases when you are working with templates, the data you want to use in any given template is not really bound to that template but instead you would want it to be shared with other templates. When the model is part of the template itself, it guides you in the wrong direction.
In some cases you also want the template / browser to have control over when data is loaded and what happens after it has been loaded. When using the model, you have little control over this, compared to initiating the data operations from the template.
What do you think about this way of mixing Java components and template? Do you miss @Id
and Model
? What else would make it more awesome?
Hit the comment section, or use this feedback-form if you prefer.