JAVA

Creating a Vaadin Flow Server-Side API for a Javascript component

8 January, 2019
Today we are going to explore some tips about how to create a Java server-side wrapper for a regular javascript component using Vaadin Flow. As you might notice, this popular framework has good documentation about how to integrate an existing Polymer Component, but there is no much info about how to integrate a regular Javascript component, so we will show you one easy approach of doing that.
What we want to do is, to show you how to create a nice Java API around a typical javascript component, so you can easily integrate it in your java applications.
Of course, there are many ways to build javascript visual components around there, we will focus on the ones that use this common pattern: you create a html element (like a Div), and then you instruct the library to “enhance” it.
Then we have one important feature that is usually wanted: bidirectional communication between the server and the client.
For this short tutorial we want to build a Vaadin Flow wrapper around one interesting library that is able to draw time line graphs: vis.js. This library is based on the previous premise: to enhance (in this case) a div html tag.
Let’s start.

Step 1: Overview of your component

First, it’s a good idea to use a starter to build a project based on Vaadin. We will use this one, which is perfect for creating a new component. Because it’s mandatory to specify a base GitHub project, just leave the one that is offered, you can change that later.
We want our component to be easy to use; hence, following Vaadin conventions, we will create a simple class that will extend a Div component. This class will be able to render the component in the client side, exposing some methods that will give the developer the ability for interacting with it in several ways:

public class Timeline extends Div {

}

Step 2: Defining dependencies

If we were going to use the component in a regular web page, the first thing that we would need to do is to tell the browser to fetch the libraries and styles, and the same holds true for a Vaadin Application.

You have three ways to do this:

  • Download the library and just copy the static resources in your src/main/resources/META-INF/resources directory, and they will be served in your web application.
  • Use a CDN
  • Use a webjar dependency

The first option is stable enough, but it could bring problems if you forget to unzip something (additionally version management is a bit cumbersome). The second one is a good choice, but you can’t always find out there the library that you want to use. Another drawback is that users with restricted access to the internet will not be able to use your component.
So we will use the webjar dependency strategy. For a detailed explanation, we recommend you to read this article.
In our case, this step involves basically adding the following to your pom file:

<dependency>
 <groupId>org.webjars.npm</groupId>
 <artifactId>vis</artifactId>
 <version>4.21.0</version>
</dependency>

But how  to tell the browser to only import the library and styles when the component is needed? In Vaadin Flow that is easy, just use a couple of annotations in your component:

@JavaScript("/webjars/vis/4.21.0/dist/vis.js")
@StyleSheet("/webjars/vis/4.21.0/dist/vis-timeline-graph2d.min.css")
public class Timeline extends Div {

}

Step 3: Creating the component instance

According to the vis.js documentation, you need to do three things to give life to our component:

  1. Find the div that will host the visual component
  2. Create some configuration data
  3. Create the TimeLine

This is done by this plain javascript snippet:

// DOM element where the Timeline will be attached
var container = document.getElementById('visualization');

// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([
  {id: 1, content: 'item 1', start: '2013-04-20'},
  {id: 2, content: 'item 2', start: '2013-04-14'},
  {id: 3, content: 'item 3', start: '2013-04-18'},
  {id: 4, content: 'item 4', start: '2013-04-16', end: '2013-04-19'},
  {id: 5, content: 'item 5', start: '2013-04-25'},
  {id: 6, content: 'item 6', start: '2013-04-27'}
]);

// Configuration for the Timeline
var options = {};

// Create a Timeline
var timeline = new vis.Timeline(container, items, options);

There are several ways of calling this from our component, we will use the executeJavaScript() method of the useful Page Vaadin object.
For that we will create a String that will contain this javascript function, with only some modifications. First, we will provide the container as a parameter. The container is basically our Div class (TimeLine). We can send it as a parameter and then reference it inside the function using the “$0” placeholder. We could send more parameters by using “$1”,”$2” and so on.
Now we need to send the Items, for that we need to create a class for those:

public class Item {
 
 private Integer id;
 private LocalDate start;
 private LocalDate end;
 private String content;
 public Item(Integer id, LocalDate start, LocalDate end, String content) {
  super();
  this.id = id;
  this.start = start;
  this.end = end;
  this.content = content;
 }

  /* getters and setters */

 protected String toJSON() {
  JsonObject js = Json.createObject();
  if (getId()!=null) js.put("id", getId());
  if (getContent()!=null) js.put("content", getContent());
  if (getStart()!=null) js.put("start", getStart().toString());
  if (getEnd()!=null) js.put("end", getEnd().toString());
  
  return js.toJson();
 }

}

To generate the function we are going to use to initiate the library, we will use this method:

private String createInitFunction() {
 String function = "  // Create a DataSet (allows two way data-binding)n" + 
   "  var items = new vis.DataSet([n" + 
   items.stream().map(item->item.toJSON()).collect(Collectors.joining(",")) +
   "  ]);n" + 
   "n" + 
   "  // Configuration for the Timelinen" + 
   "  var options = {};n" + 
   "n" + 
   "  // Create a Timelinen" + 
   "  var timeline = new vis.Timeline($0, items, options);" + 
   "  $0.timeline = timeline";
 return function;
}

We will explain the line “$0.timeline = timeline” later.
Finally, we need to invoke this initialization function from the constructor, which will receive a list of items:

public Timeline(Item ... items) {
 this.items = Arrays.asList(items);
 String initFunction = createInitFunction();
 UI.getCurrent().getPage().executeJavaScript(initFunction , this); 
}

We can now test our component. With just this little line in the DemoView we can run the application and have an early look of it running in our browser:

@Route("")
public class DemoView extends Div {
    public DemoView() {
        add(new Timeline(new Item(1,LocalDate.now(), LocalDate.now().plusDays(2), "Task 1")));
    }   
}

Step 4: Client-Server communication

First, let’s see how to invoke a given method of our javascript component from the server side.
Suppose we want to invoke the method focus() of the timeline javascript component. The catch in here is that we need a reference to the timeline object created by the initialization method. That was accomplished with the line $0.timeline = timeline on the initialization function, we basically stored a reference to it in our div element.
Then we can invoke a method by using:

public void focus(Item item) {
 UI.getCurrent().getPage().executeJavaScript("$0.timeline.focus($1)", this,item.getId());
}

In this method we receive a given item and send the id as a parameter to the method focus, as specified by the public API of the timeline.
Now the other way around: our javascript component will produce events that we want to catch from the server side. How to do that? By creating a custom Event. In our case, we want to be able to do something when a given item is clicked; thus, we need to define the ItemClickEvent:

@DomEvent("click")
static public class ItemClickEvent extends ComponentEvent<Timeline> {
 
 private Item item;
 
  public ItemClickEvent(Timeline source, boolean fromClient,
   @EventData("element.timeline.getEventProperties(event).item") int id) {
  super(source, fromClient);
  source.items.stream().filter(item -> item.getId().equals(id)).findFirst()
    .ifPresent(founditem -> this.item = founditem);
 }

  public Item getItem() {
  return item;
 }
}

Lot of things to say for this code:
With @DomEvent(“click”) we are telling flow that we want to listen for “click” events fired in the client side
We are extending ComponentEvent<Timeline>. That will give us some basic stuff like for example the ability of unregistering the listener
With @EventData, we are informing that we want to bring some data along the event. Basically we are calling the method getEventProperties(event) of the stored timeline instance, and then retrieving the item that was clicked. The item is offered by a integer, which we will use to obtain the correspondent Item java instance in the server side.
Ok, now we have the event, but we need to offer the API for registering a listener that will receive our event:

public Registration addItemClickListener(ComponentEventListener<ItemClickEvent> listener) {
 return addListener(ItemClickEvent.class, listener);
}

As you can see, our method will receive a ComponentEventListener based on our ItemClickEvent, and with that it will call the inherited method addListener() that will take care of the necessary tasks for making it work.

Step 5: Use it and have fun!

Using our component is as simple as this:

Timeline tl = new Timeline(new Item(1,LocalDate.now(), LocalDate.now().plusDays(2), "Task 1"));

We can also do something when a given item is clicked:

tl.addItemClickListener(ev->Notification.show("Clicked on item: " + ev.getItem().getContent()));

Finally we can focus on a task:

Button b1 = new Button("Focus task 1");
b1.addClickListener(ev->tl.focus(items[0]));

And that’s it!
Here’s an animated GIF that shows the main features of this tutorial:

If you want to play around, the sources are available in GitHub. The example is structured as a sequence of steps, where each commit represents an incremental change from the Add-on Component Starter for Flow
We are planning to release a component based on this example, stay tuned!

Martín López
By Martín López

Systems Engineer, coding is my passion. Strong experience in Java Ecosystem, and frameworks like Vaadin, Hibernate and Spring. Always willing to help companies to design build workflows and how to use the correct tools for producing high quality software.

Join the conversation!
Profile Picture

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

  • Dell Wraight says:

    Bookmarked!, I really like your website!

  • Noe Harpster says:

    Saved as a favorite!, I like your website!

  • Reinaldo Szigethy says:

    Saved as a favorite!, I like it!

  • Isaac Rassel says:

    tҺe website іѕ really good, I enjoy your website!

  • Tracy Caiazzo says:

    Saved as a favorite!, I really like it!

  • Sung Letrent says:

    “This site was… how do you say it? Relevant!! Finally I’ve found something that helped me. Cheers!”

  • Latia Hursey says:

    “Having read this I believed it was really informative. I appreciate you finding the time and effort to put this information together. I once again find myself personally spending a significant amount of time both reading and posting comments. But so what, it was still worthwhile!”

  • Huong Mohaupt says:

    Bookmarked!, I enjoy it!

  • Joanne Besemer says:

    Bookmarked!, I enjoy it!

  • Flor Greenlief says:

    Thanks, I like it!

  • bookkeeping services says:

    An impressive share! I have just forwarded this onto a co-worker who had been doing a little research on this. And he actually bought me dinner simply because I found it for him… lol. So let me reword this…. Thanks for the meal!! But yeah, thanks for spending time to talk about this matter here on your website.

  • Sami says:

    Hi Martin! This is a good practical explanation. Documentation at https://vaadin.com/docs/latest/create-ui/element-api/client-server-rpc is bit shallow and could link here. How big you see if this is updated to Vaadin 14+? Should webjars still work?

    • Martín López says:

      Hi Sami!

      Thanks for your feedback. This sample project is old, I did a short test updating it to the latest Vaadin 14 version and it is not working for some reason, it seems to be related to webjars. When this article was written npm support was still green, maybe by switching from webjars to npm it should work. I’ll try to test it in that way later and let you know.

      Regards!

  • Sergiu says:

    Hi Martin,

    I’m having the same issue as Sami with regard to webjars and running on Vaddin 14.
    Any feedback ?

    Thanks

    • Martín López says:

      Hi!

      I pushed a new branch in the repository that is working on Vaadin 14, would you mind to take a look?

      The best way would be to use npm imports but I tried and I couldn’t make it work (I couldn’t even find the exact version that we’re using on the tutorial on npmjs). So I thought that given that these are regular JavaScript libraries, and not JavaScript modules, it is just a matter of changing the JavaScript annotation and replace it with UI.getCurrent().getPage().addJavaScript()

      Let me know if that is working also for you.

      Regards!