STJS and jQuery

We were already using jQuery when we decided to build STJS. GWT handles the cross-browser issue by generating separate code for each target platform. We believe that it’s easier to leave this task to external libraries such as jQuery.

To be able to use jQuery in Java (with STJS) you need to have what we call a “bridge”. This is a Java code composed mainly of Java interfaces and empty static methods that mimic the Javascript functions. The static methods have no body or throw UnsupportedMethodException.

The jQuery core bridge

The bridge to the jQuery core functionality is found in the org.stjs.javascript.jquery.JQueryCore interface. Here is an excerpt from this interface.

@SyntheticType
public interface JQueryCore<FullJQuery extends JQueryCore<?>> {
 public FullJQuery addClass(String className);

public boolean hasClass(String className);

public FullJQuery removeClass();

...

The interface is annotated with @SyntethicType as this bridged type does not exist in the corresponding Javascript code (no JS function called JQueryCore).

One of the most handy features of jQuery is its ability to chain methods together, so it was important for us to represent this feature in java. So as you can see, the most of the methods return object of the type FullJQuery, which is a generic parameter that has to conform to the JQueryCore interface. This element is the key point that allows extending jQuery via plugins, as I explain later.

To make the usage of jQuery API more precised in Java, we tried to propose methods with the signature as strict as possible – so we tried to avoid Object or Object … parameters wherever possible. This was quite a challenging task  – and we didn’t get it quite right from the first try 🙂 as the library’s documentation is not always precise regarding the parameters’ types. Here is a short excerpt:

public Object css(String propertyName);
public FullJQuery css(String propertyName, Object value);
public FullJQuery css(Map<String, ? extends Object> propertyMap);

Even though when you write regular (non bridge) Java classes for STJS overloading is forbidden, for clarity purposes, when you write bridges this is allowed. As you can imagine, in our case all the three methods refer to the SAME Javascript function, but where the overloading is handled internally by the function.

function( elem, name, value ) {
 return value !== undefined ?
 jQuery.style( elem, name, value ) :
 jQuery.css( elem, name );

Plugins

The problem is that jQuery has a design that might seem unfamiliar to most of Java developers. I don’t know of a Java library where your library’s plugins add methods to your classes! Most of the time,you have an API and accompanying SPI, such as javax.crypto.Cipher and javax.crypto.CipherSpi or javax.imageio and javax.imageio.spi, and the plugins are added by implementing the SPI, whereas clients still use the API. In fact the Java language itself is not yet ready for this type of “extension”. The concept is called “Extension Methods” and the C# implementation is the closest to this concept. The Java implementation that should arrive in Java 8 is not quite helpful for this type of design.

One of the issues of Javascript in general and of jQuery in particular is the heavy overloading of the methods (especially for plugins). The same method can be used for activating the plugin, setting options or more unexpected – call methods and return values. Even though we tried to “strongly-type” the interface by providing several method signatures for the different plugins, there is still the problem of the return type (and additional parameters) that changes depending on the first parameter called “method name”. Here is an example:

@SyntheticType
public interface Draggable<FullJQuery extends JQueryCore<?>> {
 public FullJQuery draggable();
 public FullJQuery draggable(DraggableOptions<FullJQuery> options);
 public FullJQuery draggable(String methodName);
 public Object draggable(String option, String optionName);
 public FullJQuery draggable(String option, DraggableOptions<FullJQuery> options);
 public FullJQuery draggable(String option, String optionName, Object value);
}

One of the problems we had when we used jQuery prior to STJS was that we never knew by heart what options a plugin offers or what parameters you can access in the event handler. That’s why, instead of proposing as a parameter a simple Map for the options we went further and we strongly-typed that part too. So here how it looks (partially) the options parameter and the ui object for the Draggable plugin:

@SyntheticType
abstract public class DraggableUI<FullJQuery extends JQueryCore<?>> {
 public FullJQuery helper;
 public Position position;
 public Position offset;
}

@SyntheticType
public class DraggableOptions<FullJQuery extends JQueryCore<?>> {
 public boolean disabled = false;
 public boolean addClasses = true;
 public Object appendTo = "parent";
 public String axis = "false";
 public String cancel = ":input,option";
 public String connectToSortable = "false";
...

The jQuery core and the UI plugins are all brought together in the interface JQueryAndPlugins:

@SyntheticType
public interface JQueryAndPlugins<FullJQuery extends JQueryAndPlugins<?>> extends JQueryCore<FullJQuery>, //
 JQueryUI<FullJQuery>,//
 Accordion<FullJQuery>,//
 AutoComplete<FullJQuery>,//
 Button<FullJQuery>,//
 Datepicker<FullJQuery>,//
 Dialog<FullJQuery>,//
 Draggable<FullJQuery>,//
 Droppable<FullJQuery>,//
 Progressbar<FullJQuery>,//
 Resizable<FullJQuery>,//
 Selectable<FullJQuery>,//
 Slider<FullJQuery>,//
 Sortable<FullJQuery>,//
 Tabs<FullJQuery>//
{
}

This interface is intended to be further subclassed by the users who wish to add other jQuery plugins. If you plan to use only the plugins listed above you can use the JQuery interface that is a shorter name for the previous interface:

@SyntheticType
public interface JQuery extends JQueryAndPlugins<JQuery> {//
}

Writing your own plugin bridge

It may happen that you need to add a new jQuery plugin in your code (let’s call it MyPlugin). Following the same rules as for the UI plugins you’d write the interface representing your plugin:

@SyntheticType
public interface MyPlugin<FullJQuery extends JQueryCore<?>> {
 public FullJQuery myplugin();
 public FullJQuery myplugin(MyPluginOptions<FullJQuery> options);
 public FullJQuery myplugin(String methodName);
 public Object myplugin(String option, String optionName);
 public FullJQuery myplugin(String option, String optionName, Object value);
}

Then, wherever you previously used the JQuery interface you should use the MyJQueryLib interface defined as follows:

public interface MyJQueryLib<FullJQuery extends MyJQueryLib<?>>
 extends MyPlugin<FullJQuery>,
 JQueryAndPlugins<FullJQuery>{
}

To make it easier (no need for cast) you can re-write the global $ method to return your new JQuery interface as it’s described in the next paragraph.

The global methods and object

There are basically two ways to access the jQuery functionality from Javascript: via the $ global object or via the $ global method (I assume the $.noConflict is not used).
The global object gives you access to data and methods not necessarily linked to a DOM node, like $.browser or $.inArray().
The global method $()  uses a selector to build a list of DOM elements to which subsequent calls will be applied.

In STJS, the global  jQuery method and object are defined in the class: org.stjs.javascript.jquery.GlobalJQuery. They are defined as follows:

abstract public class GlobalJQuery {

public static GlobalJQuery $;

public static <FullJQuery extends JQueryAndPlugins<?>> FullJQuery $(String path) {
 throw new UnsupportedOperationException();
 }

...

}

The GlobalJQuery object contains all the methods and fields that can be accessed with $. like $.browser and $.inArray.
As you can see, the $ method returns a jQuery wrapper object that provides you all the methods of  jQuery core and UI plugins. If you decide to add new plugin and if you want to access to the newly added methods without casting, you may need to define your own GlobalJQuery inheriting from the STJS one:

@GlobalScope
abstract public class MyGlobalJQuery extends GlobalJQuery {
 public static MyGlobalJQuery $;

public static MyJQuery $(String path) {
throw new UnsupportedOperationException();
 }

..

}

Even if you don’t add methods to your MyGlobalJQuery instance you still have to re-define the $ global object in order to be able to statically import in your code the $ object and method

import static my.package.MyGlobalJQuery.$;

I hope this article gave you some insight on the challenges we had implementing this bridge. It may hopefully serve you as a starting point for implementing a bridge to other similar Javascript libraries.

Next article will describe how to implement a jQuery widget using STJS.

Advertisements

About axcraciun
Open source developer

8 Responses to STJS and jQuery

  1. Rory Kienow says:

    Very interesting details you have observed, appreciate it for posting.

  2. Xpto says:

    Hi, this is really a nice idea. Nice job.
    I like that you keep it simple.

    I was doing some testing and came across a problem.
    I have a class that has a method starting with ‘$’, that I’m “bridging” it to a third-party javascript library method. What happens is that the java script generated is always a field insted of a method.

    Ex:
    @STJSBridge
    public class Scope {
    public void $watch(String bind){
    throw new UnsupportedOperationException();
    }
    }

    then I used it a class like this:
    scope.$watch(“option”);

    and it translate t:
    scope.watch = “option”;

    but what I want is:
    scope.$watch(“option”);

    I know that ‘$’ is a special character to mark fields in interfaces, but in a class, it should be ignored since we can add fields to classes, or I’m missing something?

    Regards

  3. axcraciun says:

    Thank you for you encouragements.
    We made unfortunately a design error by building the rules for special methods ($XXX) inside the generator. $method(param) is treated like an assignment. The reason behind this was that most of the bridges are built as interfaces, where you cannot define fields!

    No simple quick fix (read “backward compatible”) is possible in the current version 😦
    The “special generation” for $XXX methods is enabled for maximum 2 parameters. So an ugly fix for your problem would be to add two more dummy parameters on both the Scope interface and in the code you use scope:


    public class Scope {
    public void $watch(String bind, String dummy1, String dummy2){...}
    }

    and when you use it:

    scope.$watch("option", null, null)

    Normally this should not affect the execution of your generated Javascript code.

    For the next major version we’ll abandon the idea of “special” methods, by using instead an annotation on the “special” method like @STJSTemplate(“getter”). We may let the users define their own template, but this is not yet completely defined. But unfortunately, this change will not be backward compatible.

    • Xpto says:

      Thanks for the quick reply.

      One way to maintain the retrocompatibility would be to use an annotation to ignore the special rule.
      Ex:
      public class Scope {
      @Strict
      public void $watch(String bind){…}
      }

      Anyway, for now I will change your source code and use a diferent special characters(s) and wait for the new major release. I Dont want to use the ugly hack 😛

      And now for something completely diferent…
      Is there a way to generate all javascript to one output file?
      It is not pratical, as a project grows, to list all javascript generated in the header of a html file.
      If an implementation does not exist, a possible solution could be to optionaly annotate a java class like @GenerateTo(“app/main.js”) and all classes having this annotation would be generated to “main.js” in a folder named “app”.

      • axcraciun says:

        You’re idea is cool. I’ll try to include it in the next bug fix release (maybe the annotation will be called slightly differently).

        For your 2nd question – we’re currently using a library called packtag that does also minifying and gzip (putting everything in one resource). But you still have to add them one by one …

        We’re working now on a system were the code is minified more aggressively (like Google closure) so we may introduce also this feature. We still have to decide whether this goes in the maven config or in the annotations (or both). And if they are more generic: e.g. **/view/** -> app/view.js

      • Xpto says:

        packtag is a jsp taglib and that doesn’t work for me, as I’m not using jsp. I’m tinkering with AngularJS.

        For production, minified js is great, but when developing, it is impossible to debug a minified js (at least I can’t). So, if the system that you are working on could have the possibility to turn off the minification (like a development/production switch), that would be awesome.

      • axcraciun says:

        I made a new release (1.2.3) that contains the annotation @Template. This will be used to replace all the “special methods” and maybe find a way to let the users provide their own templates. For the moment only @Template(“none”) should be used when you want the method to be left untouched.

        It’s interesting how you’re using ST-JS 🙂 What do you use on the server side to provide data for your application?

        We’d like to promote ST-JS bridges to Javascript libraries (open source or not). Did you have to build one for AngularJS? If yes, would you agree to distribute it?

      • Xpto says:

        Cool. I’m going to start using it.

        I’m going to use RESTful services on the server side (Apache CXF JAX-RS + Spring + Spring Security).

        The project (for a friend of mine) is at the very beginning so I don’t have yet a ST-JS bridge for AngularJS. When I have the bridge I won’t mind to distribute it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: