Blog

Access to the underworld of JavaScript

By  
Michael Vogt
·
On Feb 12, 2013 1:44:00 PM
·

Access to the underworld of JavaScript

One big advantage of Vaadin is, that it lets you define your application front end from a very high level, and cares about the nitty gritty details for you. But sometimes the requirements for your application can’t be realized that way. This is when you can create your own custom widgets.

And there are these nasty situations, when even this is not enough. Possible reasons for this can be:

  • You need direct access to JavaScript from your client side
  • You need access to your client side from JavaScript
  • You need to access a function or field that is declared private in the default WidgetSet, kind of like reflection on the server side
  • The code the client side compiler generates is too slow

This is when JSNI, the JavaScript Native Interface will save your day. It allows you to do all of the above. Wonderful, right?

Well, there is a catch, of course. Please be advised to use this with care. You are completely on your own when using it. There is nothing left that can deal with the nasty peculiarities of the JavaScript environment for you. You have to care about browser differences on your own, and make sure your code does not leak memory and is secure, for example.

For Java, the function body is completely opaque, as is any JavaScript object. This means, that you need to use a JavaScript debugger to debug it, as it is not accessible from the debugger in Eclipse when you use dev mode. Other than that, JSNI works very much transparently.

Now, with this out of the way, here is how it works:

  • add the native keyword to the function signature
  • wrap the body of the function between this special notation /*-{}-*/;
  • write JavaScript directly between the curly braces

That’s all. An empty JSNI function looks like this:

private native void jsniFunction()
/*-{
           
           
            
}-*/;
           

or

public static native String jsniFunction( int value)
/*-{
           
           
            
           return “Parameter value: ” + value;
}-*/;
           

or any combination of it.

You may wonder why the start of the comment is at the beginning of the line. This is to help Eclipse to format it correctly when you use its source code formatting function.

Here is now, how you can do what is listed above.

You need direct access to JavaScript from your client side code

First, load your JavaScript in your host page. One easy way to do so, is to add the annotation @JavaScript({ "external.js" }) to your UI class. Complete description of this feature can be found right here in the blog

external.js could contain something like this:

function callme(String name) {
    alert("Hello " + name);
}

Then you can access it with

private native void jsniFunction() /*-{
           $wnd.callme("Vaadin");
}-*/;

You need access to client side code from JavaScript

First you create the function, that calls into your client side code. This needs a special syntax that looks like this.

[instance-expr.]@class-name::method-name(param-signature)(arguments)

with

  • instance-expr. : must be present when calling an instance method and must be absent when calling a static method
  • class-name : is the fully-qualified name of the class in which the method is declared (or a subclass thereof)
  • param-signature : is the internal Java method signature as specified at JNI Type Signatures but without the trailing signature of the method return type since it is not needed to choose the overload
  • arguments : is the actual argument list to pass to the called method

An example follows in the next section.

Now, to let JavaScript access your function, register it during setup of your application like this in the global Window object of JavaScript:

private native void registerJsniFunction() /*-{
           $wnd.jsnifunction = this.@com.example.Example::callIntoJava;
}-*/;

Note: As the client side code is running inside a frame, you can use $wnd and $doc to easily access the correct top Window and Document element.

You need to access a function or field that is declared private in the default WidgetSet

This is maybe the most overlooked possible usage of JSNI. Because all front end code gets compiled to JavaScript, all the functions and variables are globally available. This means, when Java access restrictions don’t allow you to access a function or field, it can easily be accessed with JSNI. Example from the DataGrid, where I needed access to the ScrollPanel inside it:

private native ScrollPanel getTableDataScroller()
    /*-{
       return this.@com.google.gwt.user.cellview.client.CustomDataGrid::tableDataScroller;
    }-*/;

Then you can access it, if it would be available from Java in the first place:

public HandlerRegistration addScrollHandler(ScrollHandler handler) {
           return getTableDataScroller().addScrollHandler(handler);
    }

The code the client side compiler generates is too slow

Even when the compiler tries really hard to create the best possible JavaScript, maybe your profiler tells you differently. In such a case, you can provide your own implementation. But please, when you find such a case, don’t forget to post a bug for GWT.

There are some specifics I did not mention to keep the samples simple. Please look them up in the GWT documentation. In addition to that, have a look at JavaScript Overlay Types, which give you easy access to JavaScript objects, like JSON for example.

I hope I could show you how powerful JSNI is. But as always, with power comes a lot of responsibility. So please be careful ;-)

This concludes my posts about low level GWT functionality. Next time, when all goes well, I would like to write how you can add WAI-ARIA support to your custom widgets.


Michael works at the Vaadin team as GWT expert helping our customers in building custom widgets and solutions on top of Vaadin Framework and GWT.

Michael Vogt
You haven't yet written a blog author profile for yourself. Go to My Account page to write a short description of yourself.
Other posts by Michael Vogt