Exposing server-side API to JavaScript

The new JavaScript integration functionality will allow you to easily publish methods that can be called with JavaScript on the client side. In effect, you can publish a JavaScript API for your application. Although you will probably not find yourself using this very often, it can be useful when integrating with JavaScript frameworks or embedding within legacy sites.

Exposing a notify() method that takes a message and displays that as a notification can be done in one simple block in e.g UI.init():

JavaScript.getCurrent().addFunction("notify", new JavaScriptFunction() {
  public void call(JSONArray arguments) throws JSONException {
    Notification.show(arguments.getString(0));
  }
});

This will expose the notify()-method globally in the window object. Technically it’s thus window.notify(), but you can call it by simply by notify(). Try entering notify("Hey!") into the Firebug or Developler Tools console, or javascript:notify("Hey!") into the address bar.

You’ll notice that this assumes there is a String in the first position of the array. Also, this will clutter the global namespace, which is generally not a good idea, unless you really have a specific need for that.

Let’s make a complete example with two arguments, some simple error handling, and namespacing:

JavaScript.getCurrent().addFunction("com.example.api.notify",
  new JavaScriptFunction() {
    public void call(JSONArray arguments) throws JSONException {
      try {
        String caption = arguments.getString(0);
        if (arguments.length() == 1) {
            // only caption
            Notification.show(caption);
        } else {
            // type should be in [1]
            Notification.show(caption,
                Type.values()[arguments.getInt(1)]);
        }
      } catch (JSONException e) {
        // We'll log in the console, you might not want to
        JavaScript.getCurrent().execute(
            "console.error('" + e.getMessage() + "')");
      }
    }
  });
}

Using the dotted notation for the method will automatically create those objects in the browser; you’ll call this method like so: com.example.api.notify("Hey!"). You do not have to use a long name like this, though - it’s up to you and your use-case.

The second thing to notice is that we now wrapped the code in a try-catch, so that the wrong number or wrong types of arguments does not cause an ugly stacktrace in our server logs. Again, how you should react to erroneous use of your exposed API depends on your use-case. We’ll log an error message to the browser console as an example.

We’re now accepting a second (integer) argument, and using that as type for the Notification.

Finally, we’ll add a link that will call the function, and work as a Bookmarklet. You can drag the link to your bookmarks bar, and when you invoke it when viewing the application with our exposed notify()-method, you will be prompted for a message that will then be sent to the method. Here is the plain HTML code for creating such a link:

<a href="javascript:(function(){com.example.api.notify(prompt('Message'),2);})();">Send message</a>

Here is the full source for our application:

import org.json.JSONArray;
import org.json.JSONException;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.JavaScript;
import com.vaadin.ui.JavaScriptFunction;
import com.vaadin.ui.Link;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Notification.Type;
import com.vaadin.ui.UI;

public class JSAPIUI extends UI {
  @Override
  public void init(VaadinRequest request) {

    JavaScript.getCurrent().addFunction("com.example.api.notify",
        new JavaScriptFunction() {
          public void call(JSONArray arguments) throws JSONException {
            try {
              String caption = arguments.getString(0);
              if (arguments.length() == 1) {
                // only caption
                Notification.show(caption);
              } else {
                // type should be in [1]
                Notification.show(caption,
                    Type.values()[arguments.getInt(1)]);
              }
            } catch (JSONException e) {
              // We'll log in the console, you might not want to
              JavaScript.getCurrent().execute(
                  "console.error('" + e.getMessage() + "')");
            }
          }
        });


    setContent(new Link(
        "Send message",
        new ExternalResource(
            "javascript:(function(){com.example.api.notify(prompt('Message'),2);})();")));
  }
}