This is meant to be a quick guide on how to use JsInterop. More details can be found in the design doc.
For calling browser APIs you may want to consider Elemental which has autogenerated and accurate JsInterop abstractions for (common) JavaScript APIs.
// 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: [email protected]");
// Main.impl.java.js (transpiled from Main.java).
const Linkify = goog.require("goog.string.linkify");
Linkify.findFirstEmail("this is an email: [email protected]");
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="goog.html.sanitizer")
public class HtmlSanitizer {
public HtmlSanitizer(Builder builder){};
public native SafeHtml sanitize(String unsanitizedHtml);
public native HTMLSpanElement sanitizeToDomNode(String unsanitizedHtml);
public static native SafeHtml sanitize(String unsanitizedHtml);
// Note: The inner class without namespace inherits the parent as namespace.
@JsType(isNative=true)
public static class Builder {
public native Builder allowCssStyles();
}
}
// Usage
static void main() {
HtmlSanitizer.Builder builder = new HtmlSanitizer.Builder();
builder.allowCssStyles();
SafeHtml html = new HtmlSanitizer(builder).sanitize("<div />");
}
Similar to interfacing with Closure type with inner classes. However if you don't want to declare a Java type for the top level export, you can use 'dot' notation when specifying the type's name.
@JsType(isNative=true, name="bar.Foo" namespace="path.for.directory.of.type")
public class Foo {}
@JsType
- Interface with an extern type.
@JsType(isNative=true, namespace=JsPackage.GLOBAL)
public class RegExp {
public 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");
}
class Main {
@JsFunction
interface Callback {
void run();
}
// Global JavaScript method setTimeout().
@JsMethod(namespace=JsPackage.GLOBAL)
private static native int setTimeout(Callback callback, int delayMs);
public static void main() {
setTimeout(() -> {
// do something!
}, 1000);
}
}
// Existing ClosureEnum.js
/** @enum {string} */
const ClosureEnum = {
OK : 'Ok',
CANCEL : 'Cancel'
}
// Handwritten Java code to expose ClosureEnum to Java code.
@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.
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.
// MyClass.java
package com.google.example;
class MyClass {
@JsConstructor
public MyClass() {}
}
// User written JavaScript code using J2CL transpiled MyClass.java.
goog.module("example.module");
const MyClass = goog.require("com.google.example.MyClass");
new MyClass();
// Interop.java
package com.google.example;
class Interop {
@JsMethod
public void jsMethod() {}
// Cannot call this from JS.
public void notJsMethod() {}
}
// Interop.impl.java.js (transpiled from Interop.java)
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;
// Interop.java
package com.google.example;
public class Interop {
@JsProperty
public static final int staticProp = 10;
@JsProperty
public int instanceProp = 30;
// Getter for a JavaScript property named "instance"
@JsProperty
public static Interop getInstance() {
return new Interop();
}
}
// User written JavaScript code using J2CL transpiled Interop.java.
goog.module("com.google.example.Usage");
const Interop = goog.require("com.google.example.Interop")
console.log(Interop.staticProp); // 10
console.log(Interop.instance.instanceProp); // 30
See Optional Types in Closure Compiler.
// Main.java
public class Main {
@JsMethod
public void method1(@JsOptional @Nullable Double i) {}
}
// Main.impl.java.js (transpiled from Main.java)
class Main {
/**
* @param {number=} i // Note the "=" here.
* @return {void}
* @public
*/
method1(i) {}
}
Note: It is generally a good practice to mark @JsOptional
explicitly @Nullable
to catch potential problems with Java nullabilty checkers.
// Exported.java
package com.google.example;
@JsType
public class Exported {
public int prop;
public Exported() {
this.prop = 100;
}
public int value() {
return this.prop;
}
}
// User written JavaScript code using J2CL transpiled Exported.java.
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.value()); // 100
@JsEnum
enum ExposedEnum {
VALUE1,
VALUE2;
void someMethod();
}
// ExposedEnum.impl.java.js (transpiled from ExposedEnum.java)
/** @enum {number} */
const ExposedEnum = {
VALUE1 : 0,
VALUE2 : 1
}
// Note that no methods from original Java source are exposed here.
// Main.java
public class Main {
@JsMethod
public void method(@JsOptional @Nullable Object o) {
Console.log("Called method() with " + o);
}
// Alias method(@JsOptional @Nullable Object o). Calling method() (from Java code) is
// equivalent to calling method(null).
@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 @Nullable Object o1, @JsOptional @Nullable 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 @Nullable 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"
}
}
@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 from ExposedEnum.java)
/** @enum {?string} */
const ExposedEnum = {
VALUE1 : 'Value1',
VALUE2 : 'Value2'
}
// Some old existing bar.js
goog.provide('foo.bar');
/** @return {string} */
foo.bar = function(value) {
return 'bar';
};
// Handwritten Java code to expose foo.bar to Java code.
public class Main {
// Note that the method sets the namespace and leaves the name empty.
@JsMethod(namespace = "foo.bar", name = "")
public static native String bar();
}
// Some old existing bar.js
goog.provide('foo.bar');
/** @return {string} */
foo.bar = 'bar';
// Handwritten Java code to expose foo.bar to Java code.
public class Main {
// Note that the method sets the namespace and leaves the name empty.
@JsProperty(namespace = "foo.bar", name = "")
public static native String bar();
}