Java for web frontend developers, part 2: getting started with J2CL

treblereel

Dmitrii Tikhomirov

Posted on June 6, 2022

Java for web frontend developers, part 2: getting started with J2CL

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.

1) Standalone applications

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 
Enter fullscreen mode Exit fullscreen mode

@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());
         }
       });
 }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
 }
}
Enter fullscreen mode Exit fullscreen mode

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');
       }
   );
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
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);
 }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
treblereel
Dmitrii Tikhomirov

Posted on June 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related