Dmitrii Tikhomirov
Posted on June 6, 2022
In the previous article I described how to create and run a simple Java application in a web browser using j2cl-maven-plugin, so if you aren’t familiar with J2CL or Google Closure Compiler I’d recommend taking a look at that article first.
In this article I’m going to show you 3 scenarios of running J2CL applications:
1) Running our app as a standalone application
2) Calling methods of our application from JavaScript via a window object
3) Exporting methods from our Java to JavaScript environment
Before we begin:
In the examples below, I'll be rewriting parts of the demos code. To try out complete demos you can check my GitHub repo that contains all demos in one place.
If you have questions about running the demos, check my previous article.
First, I created a simple POJO called User.java
with several properties inside and a simple form with corresponding inputs that were passed to the properties of the POJO. To marshall an instance of User to XML I’ll be using the mapper-xml project. It’s an implementation of JAXB (Java Architecture for XML Binding) for GWT and J2CL. I like JAXB because it’s very flexible and allows us to process complex XML structures by annotating Java Beans.
@XMLMapper
public class User {
private String firstName;
private String secondName;
private int age;
… getters and setters omitted
@XMLMapper
is necessary here to trigger a JAXB annotation processor to generate the User_XMLMapperImpl
marshaller for this POJO. User_XMLMapperImpl
can be used to serialize and deserialize User to XML and back.
public class App {
private AbstractObjectMapper mapper = User_XMLMapperImpl.INSTANCE;
public void onModuleLoad() {
HTMLDivElement xmlDivElement = (HTMLDivElement) DomGlobal.document.getElementById("xml");
HTMLButtonElement submit = (HTMLButtonElement) DomGlobal.document.getElementById("submit");
HTMLInputElement firstName = (HTMLInputElement) DomGlobal.document.getElementById("firstName");
HTMLInputElement secondName =
(HTMLInputElement) DomGlobal.document.getElementById("secondName");
HTMLInputElement age = (HTMLInputElement) DomGlobal.document.getElementById("age");
submit.addEventListener(
"click",
evt -> {
try {
User user = new User();
user.setFirstName(firstName.textContent);
user.setSecondName(secondName.textContent);
String _age = age.textContent.trim();
user.setAge(Integer.parseInt(age.value.trim()));
String xml = mapper.write(user);
xmlDivElement.textContent = xml;
$("#exampleModal").modal("show");
} catch (Exception e) {
DomGlobal.alert("Exception " + e.getMessage());
}
});
}
}
As you can see, I have added a listener to the submit
button that collects values from the fields and sets them to the instance of the User object. When it’s done I call a mapper, which is an instance of the User’s marshaller, pass User into it and print the resulting XML on the screen. Simple and nice.
2) Calling an XML marshaller from an existing application
Let’s say, we already have an existing JavaScript application and we want to add an XML export feature to it.
var User = function () {
this.firstName = '';
this.secondName = '';
this.age = -1;
}
The User object from the existing JavaScript application should be bound to the existing User.java
, which is its representation in Java. To achieve that we need to do 2 steps. First, we add the @JsType
annotation to the User (@JsType
is part of the JsInterop API of J2CL and definitely deserves a separate article):
@XMLMapper
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "User")
public class User {
private String firstName;
private String secondName;
private int age;
Secondly, we have to tell the Closure Compiler that a JavaScript User object exists in the global scope and it has to deal with it, so I added an extern (something like d.ts
from TS) for it:
/**
* @externs
*/
/**
* @constructor
*/
var User = function () {}
/**
* @type {string|undefined}
*/
User.prototype.firstName;
/**
* @type {string|undefined}
*/
User.prototype.secondName;
/**
* @type {number|undefined}
*/
User.prototype.age;
The next step is to publish our marshaller to make it discoverable from JavaScript:
public class App {
private AbstractObjectMapper mapper = User_XMLMapperImpl.INSTANCE;
public void onModuleLoad() {
Js.asPropertyMap(DomGlobal.window)
.set(
"toXml",
(OnCall)
user -> {
String xml = null;
try {
xml = mapper.write(user);
} catch (Exception e) {
DomGlobal.alert("Exception " + e.getMessage());
}
return Promise.resolve(xml);
});
}
@JsFunction
private interface OnCall {
Promise<String> invoke(User user);
}
}
As you can see, I created the OnCall
function. It will receive a JavaScript User object, pass it to the marshaller and return a Promise<String>
with a resulting XML. I bind this function to a window object using the property name toXML
. So all we need is to call it from JavaScript, like this:
$("#submit").on( "click", function() {
var user = new User();
user.firstName = $('#firstName').val();
user.secondName = $('#secondName').val();
user.age = $('#age').val();
window.toXml(user).then(function (result) {
$(".modal-body #xml").text(result);
$('#exampleModal').modal('show');
}
);
});
3) Using Google Closure Compiler to export methods to JavaScript
The Closure Compiler has very powerful ways to simplify interoperability with an existing JavaScript environment. To simplify its usage even further I created a project that helps with it. All we need is to add the following Maven dependencies and annotate the method we want to export with the @GWT3Export
annotation, like I did here:
<dependency>
<groupId>org.treblereel.j2cl.processors</groupId>
<artifactId>annotations</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>org.treblereel.j2cl.processors</groupId>
<artifactId>processors</artifactId>
<version>0.4.1</version>
<scope>provided</scope>
</dependency>
public class App {
private AbstractObjectMapper mapper = User_XMLMapperImpl.INSTANCE;
@GWT3Export
public Promise<String> toXml(User user) {
String xml = null;
try {
xml = mapper.write(user);
} catch (Exception e) {
DomGlobal.alert("Exception " + e.getMessage());
}
return Promise.resolve(xml);
}
}
The very last thing left: call it for the JavaScript:
<script>
var marshaller = new org.treblereel.j2cl.helloworld.App();
var User = function () {
this.firstName = '';
this.secondName = '';
this.age = -1;
}
$("#submit").on( "click", function() {
var user = new User();
user.firstName = $('#firstName').val();
user.secondName = $('#secondName').val();
user.age = $('#age').val();
marshaller.toXml(user).then(function (result) {
$(".modal-body #xml").text(result);
$('#exampleModal').modal('show');
}
);
});
</script>
In this article I have shown you a few examples of how we can deal with applications written in Java and compiled to JavaScript with the help of the J2CL Maven plugin. There are many topics I would like to touch upon in my upcoming articles from the low-level ones like writing externs and using the JsInterop API, to the high-level ones like modern UI libraries and advanced frameworks.
If you like my articles, feel free to join GWT and J2CL channels, follow me on Twitter, LinkedIn or GitHub or even support me on Patreon or Stripe.
Posted on June 6, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.