My favorites | Sign in
Project Logo
                
Search
for
Updated Nov 06, 2009 by manuel.carrasco.m
Tutorial_ExportingGwtLibrariesToJavascript_en  

Exporting libraries developed in GWT to javascript.

Introduction

There are several advantages to using GWT to produce high quality javascript code. For instance, the programmer does not need to be an expert in javascript, nor know peculiarities of each browser, or spend time packing, compressing, obfuscating, calculating dependencies, etc. And also, there are many advantages in publishing your library not only in java but in javascript, mainly because developers using other languages and frameworks, can take advantage of it without knowing anything about java and gwt.

The relationship between GWT and javascript is the same than C and assembler. Why do you code in a low-level language which has different features for each platform, when you could do it in a high-level language, and give the compiler the responsibility of warning about mistakes, linking, optimising, obfuscating, and generating the most appropriate code for each platform?. Also, just as you can do with C inlining assembler, you can put javascript in your java code using JSNI.

The Basics

HTML code
  <script language='javascript'>
    window.hello("Manolo");
  </script>
  • Is it possible to simplify this process?. Of course, just use the gwt-exporter library, import it and make annotations in your java code, then the compiler will generate all the necessary code to populate your classes and methods in the object window.
  •  package jschismes.client;
     @Export
     public class HelloClass implements Exportable {
        String prefix = "Hello: ";
        public String helloMethod(String name) {
            return prefix + " " + name;
        }
     }
HTML code
  <script language='javascript'>
    var hello = new jschismes.client.HelloClass();
    hello.helloMethod("Manuel");
  </script>

Using GWT-exporter

  1. Only classes that implement Exportable and are annotated with @Export will be exported, and only their public methods will be available. By default gwt-exporter puts our classes in the object window under the package's name-space, but it can be changed using the annotation @ExportPackage.
  2. package jschismes.client;
    @ExportPackage("jsc")
    @Export
    public class DatePicker extends GWTCDatePickerAbstract implements Exportable {
      public void show(){
      }
    }
      <script language='javascript'>
         var picker = new jsc.DatePicker();
         picker.show();
      </script>
  3. Inherited classes and their public methods are not exported by default, unless they are specifically annotated with @Export. Even so, it will be necessary to implement the Exportable interface. Additionally, @Export annotation accepts an optional argument to change the name of the exported method.
  4. @Export
    @ExportPackage("jsc")
    public class DatePicker extends GWTCDatePickerAbstract implements Exportable {
      public void show(){
      }
    }
    public class GWTCDatePickerAbstract implements Exportable {
      @Export("foo")
      public void initialise(){
      }
    }
      <script language='javascript'>
         var picker = new jsc.DatePicker();
         picker.show();
         picker.foo();
      </script>
  5. In the entry-point of our application it will be necessary to specify which classes we want to export. However, exportable classes used as arguments or returned in application's methods will be exported automatically.
  6. public class JsChismes implements EntryPoint {
      public void onModuleLoad() {
        ((Exporter) GWT.create(DatePicker.class)).export();
      }
    }
  7. Also in the entry-point, because our library will be available after all the document is loaded, it is convenient to invoke a call-back javascript function.
  8. public class JsChismes implements EntryPoint {
      public void onModuleLoad() {
        onLoadImpl();
      }
      private native void onLoadImpl() /*-{
        if ($wnd.jscOnLoad && typeof $wnd.jscOnLoad == 'function') $wnd.jscOnLoad();
      }-*/;
    }
      <script language='javascript'>
        function jscOnLoad() {
            var picker = new jsc.DatePicker();
            picker.show();
        }
      </script>
  9. The arguments and return values of functions must use primitive types or objects which are marked as exportable.
  10. @Export
    public class DatePicker extends GWTCDatePickerAbstract implements Exportable {
      public String show(boolean a1, char a2, int a3, long a4, double a5, myClass a6){
      }
    }
    @Export
    public class myClass implements Exportable {
    }
  11. To pass javascript functions as arguments, use the annotation @ExportClosure and create an interface with the public methods you want to export. These methods will receive the same arguments and types as the javascript function.
  12. @Export
    @ExportPackage("jsc")
    @ExportClosure
    public interface JsClosure extends Exportable {
      public void execute(String par1, String par2);
    }
    @Export
    @ExportPackage("jsc")
    public class DatePicker implements Exportable {
      public executeJsClosure(JsClosure closure){
         closure.execute("Hello", "Friend");
      }
      <script language='javascript'>
         var picker = new jsc.DatePicker();
         picker.executeJsClosure(function(arg1, arg2) {
            alert(arg1 + "," + arg2);
         });
      </script>
  13. For objects that are implemented in both Javascript and GWT worlds, we need 'JSNI bridges'. An example of this, is the Date object which has different implementations, although the behaviours are similar.
  14. @Export
    @ExportPackage("jsc")
    public class DatePicker implements Exportable {
      Date currentDate = new Date();
      public JavaScriptObject getCurrentDate(){
         return timeToJsObject(currentDate.getTime());
      }
      public void setCurrentDate(JavaScriptObject date){
         currentDate = new Date(jsObjectToTime(date));
      }
    
      private static native JavaScriptObject timeToJsObject(double time) /*-{
        return new Date(time);
      }-*/;
      private static native double jsObjectToTime(JavaScriptObject d) /*-{
        return (d && d.getTime) ? d.getTime(): 0;
      }-*/;
    }
      <script language='javascript'>
         var picker = new jsc.DatePicker();
         picker.setCurrentDate(new Date());
         alert(picker.getCurrentDate());
      </script>
  15. Javascript constructors generally accept an object of type 'javascript properties' as argument. Therefore, it would be interesting if our classes could accept a javascript properties' object as argument in the constructor, being able to extract the configuration you want. This is achieved by creating a class that serves as a bridge between these javascript attributes and GWT.
  16. @Export
    @ExportPackage("jsc")
    public class DatePicker implements Exportable {
      String caption = "Make your selection";
      int type = 0;
      public DatePicker(JavaScriptObject prop) {
        JsProperties jsProp = new JsProperties(prop);
        this.type = jsProp.getInt("type");
        this.caption = jsProp.get("caption");
      });
    }
      <script language='javascript'>
         var picker = new jsc.DatePicker({
            type: 1,
            caption: "Select a date"
         });
      </script>
    public class JsProperties {
      JavaScriptObject prop = null;
      JsProperties(JavaScriptObject properties) {
        this.prop = properties;
      }
      public String get(String name) {
        return getImpl(this.prop, name);
      }
      public int getInt(String name) {
        String val = get(name);
        return val == null ? 0 : Integer.valueOf(val);
      }
      private static native String getImpl(JavaScriptObject p, String name) /*-{
        return p[name] ? p[name].toString() : p[name] === false ? "false" : null;
      }-*/;
    }

Creating and configuring the GWT module to export

It is recommended that you create a new module to export your GWT library. In fact, it is better not modify your original GWT library, so you can publish it as a '.jar' file without depending on gwt-exporter. These are the steps to do that:
  1. Create a new module and import both, your library and gwt-exporter in the '.gwt.xml', and set the property export to 'yes'
  2. <module>
      <inherits name="com.google.code.p.gwtchismes.GWTChismes"/>
      <inherits name="org.timepedia.exporter.Exporter"/>
      <set-porperty name="export" value="yes"/>
    </module>
  3. Create as many classes as objects that you want to export into javascript. These classes will implement the Exportable interface, will use gwt-exporter's annotations, and will have to extend the original classes of your gwt library, publishing a constructor that accepts 'javascript properties', and the public methods you want to be available from javascript.
  4. @Export
    public class Alert extends GWTCAlert implements Exportable {
      private JsProperties jsProp;
      public Alert(JavaScriptObject prop) {
        super();
        this.jsProp = new JsProperties(prop);
        if (jsProp.defined("className"))
          super.setStyleName(jsProp.get("className"));
      }
      public void show(int seconds) {
        super.show(seconds);
      }
      public void alert(String msg) {
        super.alert(msg);
      }
      public void hide() {
        super.hide();
      }
    }
  5. Create an entry-point which notifies GWT which classes to export, and executes the call-back function.
  6. <module>
      [...]
      <entry-point class="jschismes.client.JsChismes"/>
    </module>
    public class JsChismes implements EntryPoint {
      public void onModuleLoad() {
        ((Exporter) GWT.create(Alert.class)).export();
        onLoadImpl();
      }
      private native void onLoadImpl() /*-{
        if ($wnd.jscOnLoad && typeof $wnd.jscOnLoad == 'function') $wnd.jscOnLoad();
      }-*/;
    
  7. Compile the module using the cross-site linker, so the produced library could be hosted in any domain or in any CDN server.
  8. <module>
      [...]
      <add-linker name="xs"/>
    </module>

Note: So far GWT 1.6.x & 1.7.0 don't support xs linker in hosted mode, so you have to comment this line at development time, or you can create another .gwt.xml file for this purpose.

Documenting the produced Library

Unless java packages that can be easily used letting the java IDE make suggestions about the classes, methods, parameters, etc, to make use of a javascript library it is necessary to have a little documentation about the usage of it. So, don't forget to create and maintain the appropriate documentation of your library.

Unfortunately there isn't any tool to produce this documentation automatically, but you can document your library in the java code using some proprietary conventions, and then create your own parser to produce a wiki markup page. I use this technique and a home-brewed ugly perl script to do that.

References and examples


©2009 Manolo Carrasco Moñino


Sign in to add a comment
Hosted by Google Code