Permalink
Branch: master
Find file Copy path
d0de9b4 Nov 11, 2018
1 contributor

Users who have contributed to this file

331 lines (260 sloc) 6.85 KB

J2CL JsInterop by Example

This is meant to be a quick guide on how to use JsInterop.

Calling JavaScript from Java

For calling browser APIs you may want to consider Elemental which has autogenerated and accurate JsInterop abstractions for (common) JavaScript APIs.

@JsType - Interface with an extern type.

Note that @JsType is just syntactical sugar for applying @JsConstructor, @JsMethod or@JsProperty to all public constructors, methods and properties respectively.

@JsType(isNative=true, namespace=JsPackage.GLOBAL)
class RegExp {
  public native RegExp(String pattern, String... flags);
  public native boolean test();
  public String flags;
}

// Usage
static void main() {
  RegExp re = new RegExp("asdf", "g");
  String flags = re.flags; // "g"
  re.test("hello");
}

@JsType - Interface with Closure utilities.

// JsInterop for goog.string.linkify
class ClosureLinkify {
  @JsMethod(namespace="goog.string.linkify")
  public static native String findFirstEmail(String text);
  @JsMethod(namespace="goog.string.linkify")
  public static native String findFirstUrl(String text) {}
}

Example usage:

// Main.java
ClosureLinkify.findFirstEmail("this is an email: aaa@gmail.com");
// Main.java.js (translated).
const Linkify = goog.require("goog.string.linkify");
Linkify.findFirstEmail("this is an email: aaa@gmail.com");

@JsFunction - Implement JavaScript callbacks in Java

class Main {

  @JsFunction
  interface Callback {
    void run();
  }

  @JsMethod(namespace=JsPackage.GLOBAL)
  private static native int setTimeout(Callback callback, int delayMs);

  public static void main() {
    setTimeout(() -> {
      // do something!
    }, 1000);
  }
}

@JsEnum - Interface with an existing Closure enum

// Existing ClosureEnum.js
/** @enum {string} */
const ClosureEnum = {
  OK : 'Ok',
  CANCEL : 'Cancel'
}
@JsEnum(isNative=true)
enum ClosureEnum {
  OK,
  CANCEL
}

You can add String value field and set hasCustomValue=true if you would like get the underlying String value of the enum.

Calling Java from JavaScript

Java transpiled by J2CL is not directly callable from JavaScript by default. J2CL Java classes have mangled method and property names to preserve Java semantics in the translated code.

Mangled names are not public APIs for Java classes and the mangling scheme is subject to change. Instead, JsInterop annotations are the safe way to provide JavaScript APIs.

@JsConstructor - Instantiate a Java class from JavaScript.

// MyClass.java
package com.google.example;

class MyClass {
  @JsConstructor
  public MyClass() {}
}
goog.module("example.module");
const MyClass = goog.require("com.google.example.MyClass");

new MyClass();

@JsMethod

// Interop.java
package com.google.example;

class Interop {
  @JsMethod
  public void jsMethod() {}
  // Cannot call this from JS.
  public void notJsMethod() {}
}
// Generated Interop.java.js
goog.module("com.google.example.Interop");

class Interop {
  jsMethod() {}
  // This is mangled. Don't call it. The name is subject to change.
  m_notJsMethod__() {}
}

exports = Interop;

@JsProperty

// Interop.java
package com.google.example;

public class Interop {
  @JsProperty
  public static final int staticProp = 10;

  @JsProperty
  public int instanceProp = 30;

  @JsMethod
  public static Interop getInstance() {
    return new Interop();
  }
}
// usage.js
goog.module("com.google.example.Usage");

const Interop = goog.require("com.google.example.Interop")

console.log(Interop.staticProp); // 10
console.log(Interop.getInstance().instanceProp); // 30

@JsOptional - Closure optional parameters

See Optional Types in Closure Compiler.

// Main.java
public class Main {
  @JsMethod
  public void method1(@JsOptional Double i) {}
}
// Main.java.js (transpiled)
class Main {
  /**
   * @param {number=} d // Note the "=" here.
   * @return {void}
   * @public
   */
  method1(i, d) {}
}

@JsType - Expose an entire class to JavaScript

// Exported.java
package com.google.example;

@JsType
public class Exported {
  public int prop;
  public Exported() {
    this.prop = 100;
  }
  public int getProp() {
    return this.prop;
  }
}
// usage.js
goog.module("com.google.example.usage");
const Exported = goog.require("com.google.example.Exported");

let e = new Exported();
console.log(e.prop); // 100
console.log(e.getProp()); // 100

@JsEnum - Expose a Java enum to JavaScript

See Enum Types in Closure

@JsEnum
enum ExposedEnum {
  VALUE1,
  VALUE2;

  void someMethod();
}
// ExposedEnum.impl.java.js (transpiled)
/** @enum {number} */
const ExposedEnum = {
  VALUE1 : 0,
  VALUE2 : 1
}
// Note that no methods from original Java source are exposed here.

Advanced topics

Subclassing a class with overloaded methods

// Main.java
public class Main {
  @JsMethod
  public void method(@JsOptional Object o) {
    Console.log("Called method() with " + o);
  }
  @JsMethod
  public native void method() {}

  private void main() {
    method("Hello"); // logs to console "Called method() with Hello"
    method(); // logs to console "Called method() with null"
  }
}

// SubMain.java
public class SubMain extends Main {
  @JsMethod
  public void method(@JsOptional Object o1, @JsOptional Object o2) {
    Console.log("Called method(Object, Object) with " + o1 + " " +  o2);
  }

  // Override the original implementation with a native method to make sure
  // there is still single implementation of the method in the class.
  @JsMethod
  public native void method(@JsOptional Object o);

  private void main() {
    method("Hello", "GoodBye"); // logs to console "Called method() with Hello GoodBye"
    method("Hello"); // logs to console "Called method() with Hello null"
    method(); // logs to console "Called method() with null null"
  }
}

Exposing a Java enum with custom value to JavaScript

@JsEnum(hasCustomValue=true)
enum ExposedEnum {
  VALUE1("Value1"),
  VALUE2("Value2");

  // An instance field named 'value' defines the custom value type.
  String value;

  // A single constructor with this shape is needed to define custom values.
  ExposedEnum(String value) {
    this.value = value;
  }
}
// ExposedEnum.impl.java.js (transpiled)
/** @enum {?string} */
const ExposedEnum = {
  VALUE1 : 'Value1',
  VALUE2 : 'Value2'
}