Kotlin Javascript Target: Use Kotlin in the Browser

Kotlin Javascript Target: Use Kotlin in the Browser

This article is a tutorial and introduction to run Kotlin on the browser, by compiling Kotlin to JavaScript. We are going to see the simplest way to setup a project that use existing JavaScript libraries. We are also going to learn and use the awesome when expression of Kotlin. This expression is quite useful and a good example of the pragmatic approach of Kotlin. You will probably try to use it everywhere.

How Kotlin Supports Multiple Environments

Some languages saves development costs relying on a existing intermediate language or platform. The language you write is compiled or transpiled to a different language. So the existing compiler and/or virtual machines can be reused. This is the approach that Kotlin adopts by running on the JVM, which in turns can run almost everywhere.

The problem is that there are also specific environments that cannot be supported by a virtual machine because of their constraints. Two examples are: the browser and embedded systems. To support development on the browser we have to use JavaScript. So if we do not want to write directly using JavaScript we still have to transpile to JavaScript because that is the only language available on the browser. Furthermore an incredible amount of effort has been spent to optimize JavaScript engines.

By transpiling Kotlin to JavaScript we can use it both for client and server web development (through Node.js).

Why Using Kotlin Instead of JavaScript

Given that the support for JavaScript is so widespread you may ask yourself why using Kotlin and not directly JavaScript for web development. The first advantage is that you can use Kotlin for everything. You can create a web service and desktop application targeting the JVM and the corresponding web client targeting JavaScript. The second one is that JavaScript is not a good language for developing large applications.

It is a well known problem that is also addressed by TypeScript, a language that also transpile to JavaScript whose motto is JavaScript that scales. An alternative to the better language approach is to use complex JavaScript frameworks like Ember.

Kotlin For JavaScript

The current implementation targets ECMAScript 5.1 but there are plans to eventually target ECMAScript 2015 also.

The Kotlin support for JavaScript is not skin deep, but is geared both to the language and the modules. The problems are with the tools and the inherent incompatibilities between languages with dynamic and static typing.

Kotlin include libraries to support the manipulation of the DOM elements and also graphical elements using WebGL. The Kotlin compiler can also create JavaScript modules compatible with different systems: AMD, CommonJS and UMD. AMD are usually used client-side, CommonJS server-side (node.js) and UMD unify both systems.

The Kotlin ecosystem is still Java-based even when targeting Kotlin. This means that you are using Gradle, Maven, etc. instead of the node.js or JavaScript tools. That is because the compiler itself that transpiles to JavaScript is written for the JVM.

There is an inherent problem of compatibility between a dynamic typing language like JavaScript and one with static typing like Kotlin. The issue is that there are no type information in JavaScript, but Kotlin need them to work. So to use JavaScript libraries with Kotlin you have two options:

  1. You add type information
  2. You signal to Kotlin to ignore type checks

We are going to see how to ignore later. To add this information there is no smart way to do it. There is only the hard way: do it manually. Fortunately the Kotlin creators have included ts2kt a tool that translate TypeScript declarations in Kotlin ones. So you may be able to reuse them, but compatibility is not perfect. In fact for this tutorial we tried to use it for D3.js and it did not work without errors. That is a complex library, so your mileage may vary.

Our Project: Drawing With D3.js

The project for this tutorial is based on the D3.js example called US State Map. The D3 (Data-Driven Documents) is a famous library to create interesting and complex data visualizations. Our project repository is available on GitHub.

Given that the original code is released under the GPL3 so will be the code of our example. The idea is quite simple: a map of the USA with its states visible and colored according to the (randomly generated) temperature data. A tooltip will be displayed when the mouse hover a specific state.

This looping video shows a generated map.

We are also going to add a simple table listing the temperature data.

The project only deals with client-based code and not server-based.

Setup

There are many supported ways to use Kotlin for JavaScript: Gradle, Maven, IntelliJ IDEA and using directly the compiler on the command line. The suggested way is to use Gradle. For this tutorial we choose to directly use IntelliJ IDEA instead. That is because if you come from a Java you probably already know how to use Gradle. And in any case there is good documentation available to discover what plugin and dependencies include.

While it is by no means hard adding a build automation system this is not the focus of this tutorial, so we opt to use the simplest way.

Kotlin JavaScript Project

All you have to do is creating a New Project and selecting Kotlin (JavaScript). The name of our project is KotlinFunWeb.

We are also going to use also a library to simplify the creation of HTML code called Kotlinx.html, instead of using string concatenation or interpolation. So download kotlinx-html-js from the binary repository and add it to your project by going into Project Structure.

Adding a Kotlin library

We put it under the lib directory. We have it created just under the root project directory, but you can put it wherever you want.

The next step is creating a .kt file inside the src directory. We chose the name main.kt, but the name is not relevant. This file will contain our Kotlin code for this tutorial.

For the moment it will contain only the following code to test that everything works:

import kotlin.browser.*
import kotlin.dom.*

fun main(args: Array<String>) {
    document.getElementById("tooltip")?.appendText("The first step")
}

The ?. is necessary because the element could not exist, so the result of getElementBy could be null. In such case the call to appendText will not be executed, but there will be no error. Apart from that, it looks like normal JavaScript code.

The last step is to add index.html, a normal HTML file that will include the JavaScript code.

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<div id="tooltip"></div>
<script type="text/javascript" src="out/production/kotlinfunweb/lib/kotlin.js"></script>
<script type="text/javascript" src="out/production/kotlinfunweb/kotlinfunweb.js"></script>
</body>

The key part are the two script tags: the first one include the standard KotlinJS library and the second one include our code transpiled to JavaScript.

Once you build the project the resulting structure should look like this:

The directory layout of the project

The files under the out directory, including the directory itself, are generated when you build the project. The .meta files include code to support reflection and you do not need them to run the project.

All that remains to do is to access the file index.html in a web browser to check the result.

The Generated JavaScript Code

The generated file KotlinFunWeb.js should look similar to this:

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'KotlinFunWeb'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'KotlinFunWeb'.");
}
var KotlinFunWeb = function (_, Kotlin) {
  'use strict';
  var appendText = Kotlin.kotlin.dom.appendText_46n0ku$;
  function main(args) {
    var tmp$;
    (tmp$ = document.getElementById('tooltip')) != null ? appendText(tmp$, 'The first step') : null;
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('KotlinFunWeb', _);
  return _;
}(typeof KotlinFunWeb === 'undefined' ? {} : KotlinFunWeb, kotlin);

The code looks somewhat normal. You could notice that the name of the functions (e.g. _main_kand9s$) are mangled. This means that the original name is transformed and not copied verbatim. This happens because you can overload functions in Kotlin and this is needed to translate them in JavaScript.  This will happen also to the functions that you define, although you can control them with an annotation that we will see later. Also, your code is by default part of a JS module.

If you need to call Kotlin code from JavaScript it is also important to read the documentation for understanding how Kotlin types are translated in JavaScript. There are a couple of important things to notice:

  • There’s no 64 bit integer number in JavaScript, so kotlin.Long is not mapped to any JavaScript object, it depends on a class in the Kotlin runtime library.
  • kotlin.Any is mapped to JavaScript Object
  • kotlin.Array is mapped to JavaScript Array, but Kotlin collections (e.g. List, Map, etc.) depends on a class in the Kotlin runtime library.

In practical terms this means that  if you need to work with JavaScript it may be better to create the data objects with JavaScript and then use it from Kotlin than viceversa.

Copying The D3 Example

Now we are going to copy the aforementioned D3 example.

We have to download d3.v3.min.js (that I renamed in d3.js), uStates.js and include them under the lib directory.

Then we have to add this code to our index.html file.

<svg width="960" height="600" id="statesvg"></svg> <!-- svg to hold the map. -->
<script src="lib/uStates.js"></script> <!-- creates uStates. -->
<script src="lib/d3.js"></script>

This code adds the d3 library, the custom functions to display the USA States and the svg tag that will contain the map. You should also add the style tag and its content (not shown here) from the index.html included in the D3 example.

The file uStates contain the data on the shape of the USA states and a few functions to display a tooltip with temperature data when the mouse hover a state.

Using JavaScript In Kotlin

Replace the main function of our Kotlin code with the following.

fun main(args: Array) {
    js("""
    var sampleData ={};	/* Sample random data. */
	["HI", "AK", "FL", "SC", "GA", "AL", "NC", "TN", "RI", "CT", "MA",
	"ME", "NH", "VT", "NY", "NJ", "PA", "DE", "MD", "WV", "KY", "OH",
	"MI", "WY", "MT", "ID", "WA", "DC", "TX", "CA", "AZ", "NV", "UT",
	"CO", "NM", "OR", "ND", "SD", "NE", "IA", "MS", "IN", "IL", "MN",
	"WI", "MO", "AR", "OK", "KS", "LS", "VA"]
		.forEach(function(d){
			var low=Math.round(100*Math.random()),
				mid=Math.round(100*Math.random()),
				high=Math.round(100*Math.random());
			sampleData[d]={low:d3.min([low,mid,high]), high:d3.max([low,mid,high]),
					avg:Math.round((low+mid+high)/3), color:d3.interpolate("#0295D5", "#F88909")(low/100)};
		});
    """)

    js("uStates.draw(\"#statesvg\", sampleData, _.tooltipHtmlJs);")

    val d3_kt: dynamic = js("d3.select(self.frameElement)")
    d3_kt.style("height", "600px")
}

Here we can see one way of combining existing JS code in a Kotlin application. We can just reuse some JavaScript code we have, as-is. Just notice that the function js should be used with string constants. Technically you can use compile-time constants. So interpolation with local variables is allowed in the form such as "my name is: ${name}", but only if name is a constant. So in practical terms only string constants works.

It is quite easy to understand what the code does: it generate random temperature data for each state and then it generate the tooltip for each state.

In the first js function we only changed the colors of the d3.interpolate call to match the colors of the Kotlin logo. The function d3.interpolate uses the given colors as the starting and ending points of the scale. Basically the first color is associated with the value 0, while the second one is associated to 1. Any value in between is associated to a color obtained by mixing the two extremes: the lower the value, the closer the color will be to the low extreme, the higher the value, the closer the color will be to the high extreme. Notice also how by using """ we are able to use a multiline string constant.

The first meaningful difference is in the second call to js. This is modified because we ported the original JavaScript function in a Kotlin one called tooltipHtml (we are going to see it later). Since the JavaScript code will be copied verbatim with the rest of our code we do not need to use the full qualified name of our function. It will be inside the JavaScript module generated by the compiler. Instead if you want to use Kotlin code from external JavaScript code you must use the full qualified name, including the name of the module of our project.

The third call to js is peculiar. Actually the call per se is normal, but its result is assigned to a dynamic constant d3_kt. The dynamic keyword switch off the static typing checks and so anything can be used as methods of d3_kt, even non-existing functions (e.g., d3_kt.heatDeathOfTheUniverse("now"). Of course, if you actually call a non-existing function at runtime the call will fail.

On the other hand if the function exist somewhere, for instance is defined in JavaScript libraries, the call will succeed. That is why the following call works as normal. It is important that you choose a valid name for the variable in the JavaScript environment. That is to say that if you pick one that conflict with an existing one in JavaScript, like d3, the code will compile, but it will fail at runtime.

The tooltipHtml Function

The tooltipHtml function receives as arguments the name of the state and the data on the temperatures. With them it generates the HTML that shows the data for the state.

The original function is concise, but also messy. It is a long concatenation of strings that is hard to read and easy to get wrong. The kotlinx.html library instead gives you a type-safe builder that guarantees the proper structure to the HTML code by forcing you to create  valid HTML.

First, we have to import the proper packages for kotlinx.html and traversing the DOM.

import kotlin.browser.*
import org.w3c.dom.*
import kotlinx.html.*
import kotlinx.html.dom.*
import kotlinx.html.js.*

Then we have to define the class that hold the temperature data and the color for a state.

data class Stats(val low: Int, val avg: Int, val high: Int, val color: String)

Finally we can see the code of the function.

@JsName("tooltipHtmlJs")
fun tooltipHtml (name: String, values: Stats) : HTMLDivElement
{
    return document.create.div {
        h4 { +name }
        table {
            tr {
                td { +"Low" }
                td { +values.low.toString() }
            }
            tr {
                td { +"Avg" }
                td { +values.avg.toString() }
            }
            tr {
                td { +"High" }
                td { +values.high.toString() }
            }
            tr {
                td { +"Variability" }
                td { +variability(values) }
            }
        }
    }

    return tooltip
}

The JsName annotation serves to control the name of the generated JavaScript function, which is needed if you want to call it from JavaScript.

As you can see the code is longer, but safer and cleaner. You simply cannot put a tr tag outside a table one, try as you might it will not compile. There are only a couple of things to remember:

  1. The first thing that you have to remember is to make sure that you pass a String where you need it and not an object.
  2. The second one is to prefix the call with the + operator. This is needed because Kotlin transform the + in a call to the unaryPlus function defined by the library. That function add the argument to the children nodes of the current node.

So this works by relying on smart conventions and good functional support. Which means that you can also create a similar library in Kotlin.

Remember that the table, td,  etc.  are nothing more than functions that accepts a lambda as one of their arguments. And according to the convention of the language you can put the lambda out of the parentheses if it is last argument and omit the parentheses if the lambda is the only argument.

The last thing to see is the variability column that we added. It display the results of the call of a variability function that we are going to see now.

The Power Of When

The variability function relies on the when expression to do most of the work and returning the appropriate String.

fun variability(state: Stats) : String
{
    return when(state.high - state.low)
    {
        in 1 .. 20 -> "Low"
        in 21 .. 50 -> "Medium"
        else -> "High"
    }
}

You can think of this expression as a smart swich statement. A traditional switch does not support complex branch conditions, but only constants value. Instead when does support arbitrary branch conditions and more.

Our example here shows how to use a range as a branch condition and return a corresponding String. The else branch is the equivalent of default and can be excluded only if the compiler thinks that all cases are covered by the defined branches. This happen, for instance, if the argument of when is a boolean expression and you include a true and a false branch.

It also important to remember that you cannot activate a cascade of branches omitting the break statement. That is because there is no break statement at all, the first matching branch is chosen and nothing else is checked. Though you can make sure that more conditions activate the same branch combining the conditions separated with a comma (e.g. in 1 .. 20, in 51 .. 60 )

When can be used as an expression or as a statement.

Adding The JavaScript Library

Before building the project now we have to include the JavaScript code for the kotlin.html library in the index.html file. Just makes sure to include it before your generated JavaScript code and after the standard runtime library.

<script type="text/javascript" src="out/production/kotlinfunweb/lib/kotlin.js"></script>
<script type="text/javascript" src="out/production/kotlinfunweb/lib/kotlinx-html-js.js"></script>
<script type="text/javascript" src="out/production/kotlinfunweb/kotlinfunweb.js"></script>

Once you build the project the result should look like this image.

A tooltip over a map showing the USA States

The Last Touches

The final code that you can see in repository also adds a table. We add a few styles, a call to a new function drawTable inside the main function and of course the mentioned new function and another one called drawTable. The code it is not complex, and you can see fully in the repository. Here we are only going to see a few interesting tidbits.

fun drawTable(tableDiv: HTMLDivElement, data: dynamic)
{
    val states = js("Object.keys(data);")
    states.sort()

    [..]

    for(i in 1 .. 50)
    {
        var tr = document.create.tr {
            td { +states[i].toString()}
            td { +data[states[i]].low.toString()}
            td { +data[states[i]].avg.toString()}
            td { +data[states[i]].high.toString()}
            td(classes = styleTable(data[states[i]])) { +variability(data[states[i]])}
        }

        table.appendChild(tr)
    }

   [..]

The data argument is a JavaScript object passed using js("sampleData") and the first thing we do is using a JavaScript reflection function to access the elements of the JavaScript object. While using the js function can be cumbersome for long pieces of code it is also nice and easy for such small things.

Inside the for cycle we can see how to add CSS classes to the HTML generated with kotlinx.html. We also call the styleTable function.

fun styleTable(state: Stats) = when
{
    (state.high - state.low < 33) && (state.avg < 33) -> "low"  // always cold
    (state.high - state.low > 33) && (state.avg > 33) &&; (state.avg < 66) -> "middle" 
    (state.high - state.low > 33) && (state.avg < 66) -> "high" // always hot

}

As you can see, you can also call the when expression without an argument. In that case the conditions are boolean expressions. That is to say the first branch that is true is executed. In similar situations when is not a replacement for a switch, but for a long chain of if-else. A clearer and more concise alternative.

When a function return the result of an expression you can use a short notation: omit parentheses and use a equal sign (=) instead. These are called single-expression functions in the documentation. You can also omit the return type when the compiler can determine which one it is.

Summary

Kotlin is a great choice for complex web applications if you already know it or if you are using the language for other parts of your application, such as the backend. It allows you to reuse either your competencies or the code, avoiding to duplicate the effort to write the same logic on both sides. It permits to write applications in a more efficient, productive and safe code compared to using pure JavaScript.

If this is not the case, and you are not using Kotlin anywhere else Kotlin could not be your best choice. In that case you might be better served with TypeScript. Kotlin for JavaScript is certainly not bad, actually we think that the Kotlin language is superior to TypeScript. But TypeScript is probably more widespread in that environment and it can be more easily integrated in a typical JavaScript or node.js workflow. Furthermore it can be easily used with the kind of lightweight IDEs that JavaScript developer are used to, like Visual Studio Code.

In our previous comparison between Kotlin and Scala, we said that one of the strong points of Kotlin is that it can be quickly learned by Java developers. Here the situation is reversed: TypeScript is more closely related to JavaScript, which means that it can be quickly learned by experienced JavaScript developers.

The companion repository for this article is available on GitHub

2 replies
    • Gabriele Tomassetti
      Gabriele Tomassetti says:

      For some reason the angle brackets were removed when pasting the code. It should have been Array<String>, instead of Array. Now it has been corrected.

      Thanks for your report.

      Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *