Team blog

Ceylon on mobile devices

Ceylon already runs on the JVM, whether bare-bones, via JBoss Modules, Vert.x, Java EE Servlet containers such as WildFly, or OSGi containers, as well as on JavaScript VMs such as Node.js and the browser. But today we're going to explain how to run Ceylon on mobile devices, not just in the browser (though it does play a part in it), but as applications, via Apache Cordova.

Apache Cordova allows you to write applications for every mobile platform, including Android and iOS, using nothing but HTML, CSS and JavaScript. Since Ceylon compiles to JavaScript this is perfect as it allows us to run our Ceylon applications on iOS, via the JavaScript compiler backend.

Note that this article is using Ceylon 1.2.3 which is not yet released, because the JavaScript runtime in Cordova on Android had one peculiarity we had to work around in the language module JavaScript implementation. Luckily you can get nightly builds of Ceylon 1.2.3 and the Ceylon 1.2.3 SDK.

Writing your Ceylon Cordova application

Installing Apache Cordova

First, install Apache Cordova and add two platforms. I haven't been able to test the iOS platform since it requires an OSX platform to build and an iOS device to test, and I lack both, so I will explain how to package for Android and the browser, and let you guys try it out for iOS, but I have enough faith in Apache Cordova that it will Just Work™.

# Install npm, the Node.js package manager
$ sudo apt-get install npm
# Then install Apache Cordova
$ npm install cordova

Small note: for me this installed things in ./node_modules/cordova and the Apache Cordova command in ./node_modules/cordova/bin/cordova, so adapt your path as you must.

# Create your application
$ cordova create ceylon-cordova-demo
$ cd ceylon-cordova-demo
# Now add the browser and Android platforms
$ cordova platform add browser
$ cordova platform add android

At this point you have your application ready to be checked in your browser:

$ cordova platform run browser

Or in an Android emulator, provided you have downloaded the Android SDK already:

$ ANDROID_HOME=.../Android/Sdk cordova platform build android
$ ANDROID_HOME=.../Android/Sdk cordova platform run android

Getting a little side-tracked about styling

Writing an application using just HTML and CSS means you have to make some effort for it to look good, and instead I decided to delegate to use Polymer so that my application would have the look and feel of Android Material Design applications to feel even more like a native application on Android. No doubt a similar look and feel exists for iOS.

So let's download Polymer in our application's HTML sources:

$ npm install bower
$ cd www
$ bower init
# At this point just hit enter/Yes/No until it's set up 
$ bower install --save Polymer/polymer
$ bower install --save Polymerelements/paper-item

And now edit www/index.html to use Polymer:

<script src="bower_components/webcomponentsjs/webcomponents.js"></script>
<link rel="import" href="bower_components/paper-item/paper-item.html">
<link rel="import" href="bower_components/paper-item/paper-item-body.html">

You should also remove the default CSS:

<link rel="stylesheet" type="text/css" href="css/index.css">

Getting require.js and jQuery

Ceylon compiles to JavaScript modules by way of require.js, so we're going to have to download it too:

$ cd www/js
$ wget http://requirejs.org/docs/release/2.2.0/minified/require.js

Our Ceylon demo will use jQuery to add elements to the HTML page, so we also need it:

$ cd www
$ bower install --save jquery

Now edit www/index.html to use both:

<script type="text/javascript" src="js/require.js"></script>
<script src="bower_components/jquery/dist/jquery.js"></script>

Writing the Ceylon application

We're going to write a trivial application that queries Ceylon Herd for the list of modules, to display them in a list.

Let's start by creating a Ceylon module in source/cordova/demo/module.ceylon:

module cordova.demo "1.0.0" {
    import ceylon.json "1.2.3";
}

And our application's main method in source/cordova/demo/run.ceylon:

import ceylon.json { parseJson = parse, JsonObject = Object, JsonArray = Array }

shared void run() {
    dynamic {
        // The HTML element where we'll add our items
        dynamic target = jQuery("#target");
        // The function called when we get data from the server
        void success(dynamic data){
            // Parse the JSON
            assert(is JsonObject json = parseJson(data),
                   is JsonArray results = json["results"]);
            // Iterate modules
            for(res in results){
              assert(is JsonObject res);
              // Get the list of versions
              assert(is String name = res["module"],
                     is JsonArray versions = res["versions"]);
              // Join them
              value versionText = ", ".join(versions.narrow<String>());
              // Now add the HTML items
              dynamic item = jQuery("<paper-item/>");
              dynamic body = jQuery("<paper-item-body two-line/>").appendTo(item);
              jQuery("<div/>").text(name).appendTo(body);
              jQuery("<div secondary/>").text(versionText).appendTo(body);
              target.append(item);
            }
        }
        // Query Herd for the list of modules
        jQuery.get("https://modules.ceylon-lang.org/api/1/complete-modules?module=ceylon.", null, success, "text");
    }
}

Now, obviously using jQuery to add HTML is far from ideal, so I can't wait for someone to extend ceylon.html to allow Polymer web components!

We can now compile our application for JavaScript:

$ ceylon compile-js

And copy our compiled module and all its dependencies to where the Apache Cordova application will find them in www/modules:

$ ceylon copy --with-dependencies --js --out www/modules cordova.demo/1.0.0

Invoking the Ceylon module from the Cordova application

Because we're going to use require.js inline and connect to Ceylon Herd, we have to adjust the Apache Cordova permissions in www/index.html, so find that line and edit it as such:

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self' 'unsafe-inline' 
               https://fonts.googleapis.com
               https://fonts.gstatic.com;
               connect-src *">

We're left with just invoking our Ceylon function in www/index.html:

<script type="text/javascript">
  // tell require.js where our Ceylon modules are 
  require.config({
    baseUrl:'modules',
  });
  // when the document is ready
  jQuery(function(){
    // load our Ceylon module
    require(['cordova/demo/1.0.0/cordova.demo-1.0.0'], function(client) {
      // and call our run method
      client.run();
    });
});
</script>

And setting up the target HTML elements where we're going to add every loaded module (in the same file):

<body id="app" unresolved>
  <app-shell class="fit">
    <div id="target" role="listbox"></div>
  </app-shell>
</body>

Trying it

And that's it, try it out in your browser:

$ cordova platform run browser

Or in an Android emulator:

$ ANDROID_HOME=.../Android/Sdk cordova platform build android
$ ANDROID_HOME=.../Android/Sdk cordova platform run android

If you have OSX and iOS dev tools, please try this with the iOS Cordova platform and let me know how it works :)

In the future, we would benefit from having a type-safe API in front of the Cordova JavaScript API that lets you access native mobile APIs such as the camera, GPS, contacts, but even without it you can already use them using dynamic blocks.

And now for the teaser… this is only one method to run Ceylon on Android, because obviously it may be more desirable to use the JVM compiler backend and integrate with the Android Tools to run Ceylon on Android using only type-safe APIs. Don't worry, it's coming, and next week I will show you how :)

Object construction and validation

When porting Java code to Ceylon, I sometimes run into Java classes where the constructor mixes validation with initialization. Let's illustrate what I mean with a simple but very contrived example.

Some bad code

Consider this Java class. (Try not to write code like this at home, kiddies!)

public class Period {

    private final Date startDate;
    private final Date endDate;

    //returns null if the given String
    //does not represent a valid Date
    private Date parseDate(String date) {
       ...
    }

    public Period(String start, String end) {
        startDate = parseDate(start);
        endDate = parseDate(end);
    }

    public boolean isValid() {
        return startDate!=null && endDate!=null;
    }

    public Date getStartDate() {
        if (startDate==null) 
            throw new IllegalStateException();
        return startDate;
    }

    public Date getEndDate() {
        if (endDate==null)
            throw new IllegalStateException();
        return endDate;
    }
} 

Hey, I warned you it was going to be contrived. But it's really not uncommon to find stuff like this in real Java code.

The problem here is that even if validation of the input parameters (in the elided parseDate() method) fails, we still receive an instance of Period. But the Period we get isn't actually in a "valid" state. What do I mean by that, precisely?

Well, I would say that an object is in an invalid state if it can't respond meaningfully to its public operations. In this case, getStartDate() and getEndDate() can throw an IllegalStateException, which is a condition I would consider not "meaningful".

Another way to look at this is that what we have here is a failure of type safety in the design of Period. Unchecked exceptions represent a "hole" in the type system. So a more typesafe design for Period would be one which never uses unchecked exceptions—that doesn't throw IllegalStateException, in this case.

(Actually, in practice, in real code, I'm more likely to encounter a getStartDate() which doesn't check for null, and actually results in a NullPointerException further down the line, which is even worse.)

We can easily translate the above Period class to Ceylon:

shared class Period(String start, String end) {

    //returns null if the given String
    //does not represent a valid Date
    Date? parseDate(String date) => ... ;

    value maybeStartDate = parseDate(start);
    value maybeEndDate = parseDate(end);

    shared Boolean valid
        => maybeStartDate exists 
        && maybeEndDate exists;

    shared Date startDate {
        assert (exists maybeStartDate);
        return maybeStartDate;
    }

    shared Date endDate {
        assert (exists maybeEndDate);
        return maybeEndDate;
    }
} 

And, of course, this code suffers from the same problem as the original Java code. The two assertions are screaming at us that there is a problem with the typesafety of the code.

Making the Java code better

How could we improve this code in Java. Well, here's a case where Java's much-maligned checked exceptions would be a really reasonable solution! We could slightly change Period to throw a checked exception from its constructor:

public class Period {

    private final Date startDate;
    private final Date endDate;

    //throws if the given String
    //does not represent a valid Date
    private Date parseDate(String date)
            throws DateFormatException {
       ...
    }

    public Period(String start, String end) 
            throws DateFormatException {
        startDate = parseDate(start);
        endDate = parseDate(end);
    }

    public Date getStartDate() {
        return startDate;
    }

    public Date getEndDate() {
        return endDate;
    }
} 

Now, with this solution, we can never get a Period in an invalid state, and the code which instantiates Period is obligated by the compiler to handle the case of invalid input by catching the DateFormatException somewhere.

try {
    Period p = new Period(start, end);
    ...
}
catch (DateFormatException dfe) {
    ...
}

This is a good and excellent and righteous use of checked exceptions, and it's unfortunate that I only rarely find Java code which uses checked exceptions like this.

Making the Ceylon code better

What about Ceylon? Ceylon doesn't have checked exceptions, so we'll have to look for a different solution. Typically, in cases where Java would call for use of a function that throws a checked exception, Ceylon would call for the use of a function that returns a union type. Since the initializer of a class can't return any type other than the class itself, we'll need to extract some of the mixed initialization/validation logic into a factory function.

//returns DateFormatError if the given 
//String does not represent a valid Date
Date|DateFormatError parseDate(String date) => ... ;

shared Period|DateFormatError parsePeriod
        (String start, String end) {
    value startDate = parseDate(start);
    if (is DateFormatError startDate) {
        return startDate;
    }
    value endDate = parseDate(end);
    if (is DateFormatError endDate)  {
        return endDate;
    }
    return Period(startDate, endDate);
}

shared class Period(startDate, endDate) {
    shared Date startDate;
    shared Date endDate;
} 

The caller is forced by the type system to deal with DateFormatError:

value p = parsePeriod(start, end);
if (is DateFormatError p) {
    ...
}
else {
    ...
}

Or, if we didn't care about the actual problem with the given date format (probable, given that the initial code we were working from lost that information), we could just use Null instead of DateFormatError:

//returns null if the given String 
//does not represent a valid Date
Date? parseDate(String date) => ... ;

shared Period? parsePeriod(String start, String end)
    => if (exists startDate = parseDate(start), 
           exists endDate = parseDate(end))
       then Period(startDate, endDate)
       else null;

shared class Period(startDate, endDate) {
    shared Date startDate;
    shared Date endDate;
} 

At least arguably, the approach of using a factory function is superior, since in general it obtains better separation between validation logic and object initialization. This is especially useful in Ceylon, where the compiler enforces some quite heavy-handed restrictions on object initialization logic in order to guarantee that all fields of the object are assigned exactly once.

Summary

In conclusion:

  • Try to separate validation from initialization, wherever reasonable.
  • Validation logic doesn't usually belong in constructors (especially not in Ceylon).
  • Don't create objects in "invalid" states.
  • An "invalid" state can sometimes be detected by looking for failures of typesafety.
  • In Java, a constructor or factory function that throws a checked exception is a reasonable alternative.
  • In Ceylon, a factory function that returns a union type is a reasonable alternative.

Ceylon 1.2.2 is now available

Four months after the last major release, and exactly one month after the 1.2.1 update, Ceylon 1.2.2 is a new maintenance release, with over 70 issues closed, including new features, improvements and bug fixes such as:

  • you can now use java.lang.Iterable and Java arrays in for statements and comprehensions,
  • the [] lookup operator works on Java lists, maps, and arrays,
  • the in operator works on java.util.Collection and, last but not least,
  • a new ceylon bootstrap command to make it really easy to distribute code to people that don't have Ceylon installed.

Note that for the JVM backend, this release is backwards-compatible with the previous releases (1.2.0 and 1.2.1), which means you can use modules compiled with 1.2.0 on a 1.2.2 distribution out of the box. This is not as easy the other way around, if you want to run modules compiled for 1.2.2 on a 1.2.0 distribution, which is why we recommend you upgrade to 1.2.2.

Sadly, on the JavaScript backend, we had to break binary compatibility to fix serious interoperation issues, and so modules compiled for 1.2.2 and 1.2.0 are not compatible. Versions 1.2.1 and 1.2.2 are binary compatible but can still give problems when used together. We recommend you upgrade your distribution to 1.2.2 and recompile your modules.

About Ceylon

Ceylon is a modern, modular, statically typed programming language for the Java and JavaScript virtual machines. The language features a flexible and very readable syntax, a unique and uncommonly elegant static type system, a powerful module architecture, and excellent tooling, including an awesome Eclipse-based IDE.

Ceylon enables the development of cross-platform modules that execute portably in both virtual machine environments. Alternatively, a Ceylon module may target one or the other platform, in which case it may interoperate with native code written for that platform.

In the box

This release includes:

  • a complete language specification that defines the syntax and semantics of Ceylon in language accessible to the professional developer,
  • a command line toolset including compilers for Java and JavaScript, a documentation compiler, a test runner, a WAR archive packager, and support for executing modular programs on the JVM and Node.js,
  • a powerful module architecture for code organization, dependency management, and module isolation at runtime,
  • the language module, our minimal, cross-platform foundation of the Ceylon SDK, and
  • a full-featured Eclipse-based integrated development environment.

Language

Ceylon is a highly understandable object-oriented language with static typing. The language features:

  • an emphasis upon readability and a strong bias toward omission or elimination of potentially-harmful or potentially-ambiguous constructs and toward highly disciplined use of static types,
  • an extremely powerful and uncommonly elegant type system combining subtype and parametric polymorphism with:
    • first-class union and intersection types,
    • both declaration-site and use-site variance, and
    • the use of principal types for local type inference and flow-sensitive typing,
  • a unique treatment of function and tuple types, enabling powerful abstractions, along with the most elegant approach to null of any modern language,
  • first-class constructs for defining modules and dependencies between modules,
  • a very flexible syntax including comprehensions and support for expressing tree-like structures,
  • fully-reified generic types, on both the JVM and JavaScript virtual machines, and a unique typesafe metamodel.

More information about these language features may be found in the feature list and quick introduction.

IDE

Ceylon IDE now features the following improvement, along with bugfixes:

  • support for the ceylon bootstrap command

SDK

The platform modules, recompiled for 1.2.2, are available in the shared community repository, Ceylon Herd.

This release introduces a single new platform module:

  • ceylon.buffer is a cross-platform module for converting between text and binary forms of data.

Along with some enhancements to existing modules:

  • ceylon.file now has functions for creating temporary files and directories,
  • ceylon.net now has support for template handlers,
  • ceylon.html was rewritten according to HTML5 specification and with support for lazy evaluation.

Web IDE

You can try Ceylon using the Web IDE, featuring syntax highlighting, interactive error reporting, autocompletion, online documentation, and persistence and code sharing via Gist.

The Web IDE serves a dual purpose as a standard example demonstrating the use of Ceylon for web application development and deployment to the OpenShift cloud platform.

Community

The Ceylon community site, http://ceylon-lang.org, includes documentation, and information about getting involved.

Source code

The source code for Ceylon, its specification, and its website, is freely available from GitHub.

Information about Ceylon's open source licenses is available here.

Issues

Bugs and suggestions may be reported in GitHub's issue tracker.

Migrating from Ceylon 1.2.0

Migration from Ceylon 1.2.0 is easy. To recompile a module for 1.2.2:

  • First ensure that its dependencies have also been recompiled.
  • If it imports a Ceylon SDK platform module, upgrade the version number specified by the module import statement from "1.2.0" to "1.2.2" .
  • If it was compiled against Ceylon 1.2.0 you should still be able to use it in 1.2.2 for the JVM backend, as it is backwards-compatible. Sadly, this is not the case for the JavaScript backend, and so you will need to recompile your modules with 1.2.2.

Acknowledgement

As always, we're deeply grateful to the community volunteers who contributed a substantial part of the current Ceylon codebase, working in their own spare time. The following people have contributed to Ceylon:

Gavin King, Stéphane Épardaud, Tako Schotanus, Tom Bentley, David Festal, Enrique Zamudio, Bastien Jansen, Emmanuel Bernard, Aleš Justin, Tomáš Hradec, James Cobb, Ross Tate, Max Rydahl Andersen, Mladen Turk, Lucas Werkmeister, Roland Tepp, Diego Coronel, Matej Lazar, John Vasileff, Toby Crawley, Julien Viet, Loic Rouchon, Stephane Gallès, Ivo Kasiuk, Corbin Uselton, Paco Soberón, Michael Musgrove, Daniel Rochetti, Henning Burdack, Luke deGruchy, Rohit Mohan, Griffin DeJohn, Casey Dahlin, Gilles Duboscq, Tomasz Krakowiak, Alexander Altman, Alexander Zolotko, Alex Szczuczko, Andrés G. Aragoneses, Anh Nhan Nguyen, Brice Dutheil, Carlos Augusto Mar, Charles Gould, Chris Gregory, klinger, Martin Voelkle, Mr. Arkansas, Paŭlo Ebermann, Vorlent, Akber Choudhry, Renato Athaydes, Flavio Oliveri, Michael Brackx, Brent Douglas, Lukas Eder, Markus Rydh, Julien Ponge, Pete Muir, Nicolas Leroux, Brett Cannon, Geoffrey De Smet, Guillaume Lours, Gunnar Morling, Jeff Parsons, Jesse Sightler, Oleg Kulikov, Raimund Klein, Sergej Koščejev, Chris Marshall, Simon Thum, Maia Kozheva, Shelby, Aslak Knutsen, Fabien Meurisse, Sjur Bakka, Xavier Coulon, Ari Kast, Dan Allen, Deniz Türkoglu, F. Meurisse, Jean-Charles Roger, Johannes Lehmann, allentc, Nikolay Tsankov, Chris Horne, Gabriel Mirea, Georg Ragaller, Harald Wellmann, Oliver Gondža, Stephen Crawley, Byron Clark, Francisco Reverbel, Jonas Berlin, Luke Hutchison, Nikita Ostroumov, Santiago Rodriguez, Sean Flanigan.

ceylon.test new and noteworthy

Module ceylon.test (simple framework for writing repeatable tests) is an integral part of Ceylon SDK since its first version and in the latest release 1.2.1 brings several handy new features, namely:

Let's look at them in more details.


Parameterized tests

Parameterized tests allow developers to run the same test over and over again using different values, where each invocation of a test function is reported individually. A classical example for usage of parameterized tests is with a function computing Fibonacci numbers.

shared {[Integer, Integer]*} fibonnaciNumbers => 
    {[1, 1], [2, 1], [3, 2], [4, 3], [5, 5], [6, 8] ...};

test
parameters(`value fibonnaciNumbers`)
shared void shouldCalculateFibonacciNumber(Integer input, Integer result) {
    assert(fibonacciNumber(input) == result);
}

In this example, we use annotation parameters to specify the source of argument values, which will be used during test execution. You can use any top level value or unary function with a compatible type as the source of argument values. The argument provider can be specified for the whole function, as in this example, or individually for each parameter, then the test framework will execute the test for each combination of provided values. For example, a function with one parameter whose argument provider yields two values and a second parameter whose argument provider yields three values, will be executed six times.

This functionality is based on a general mechanism, which can be easily extended, e.g. serving values from data file or randomized testing. For more information see documentation to ArgumentProvider and ArgumentListProvider.


Conditional execution

In some scenarios, the condition if the test can be reliable executed is known only in runtime. For this purpose it is useful to be able explicitly declare those assumption, as for example in following test. When the assumption is not met, verified with assumeTrue() function, then the test execution is interupted and the test is marked as aborted.

test
shared void shouldUseNetwork() {
    assumeTrue(isOnline);
    ...
}

Alternatively, it is possible to specify test condition declaratively, via custom annotation which satisfy SPI interface TestCondition. In fact the ignore annotation is just simple implementation of this concept.


Grouped assertions

Sometimes you don't want to interrupt your test after first failed assertions, because you are interested to know all possible failures. In that case you can use assertAll() function, which will verify all given assertions and any failures will report together.

assertAll([
    () => assertEquals(agent.id, "007"),
    () => assertEquals(agent.firstName, "James"),
    () => assertEquals(agent.lastName, "Bond")]);

Tagging and filtering

Test functions/methods and their containers (classes, packages) can be tagged, via annotation tag. For example, a test which is failing randomly for unknown reasons can be marked as unstable.

test
tag("unstable")
shared void shouldSucceedWithLittleLuck() { ... }

Those tags can later be used for filtering tests. Either in inclusive style (only tests with specified tag will be executed).

$ceylon test --tag=unstable com.acme.mymodule

Or visa versa for exclusion (only tests without specified tag will be executed).

$ceylon test --tag=!unstable com.acme.mymodule

Extension points

Extension points are general mechanisms which allow to extend or modify default framework behavior and better integration with 3rd party libraries (e.g. custom reporters, integration with DI frameworks). The easiest way to register extensions is with annotation testExtension, which can be placed on test itself, or on any of its container. Currently the following extension points are available, and new ones can be added if needed:


Reporting

These two last features have already been available for some time, but they could easily have slipped your attention. The first is nice html report with results of test execution, to enable it, run the test tool with --report option, it will be generated under report/test(-js) subdirectory.

The second is support for Test Anything Protocol (TAP), which allow integration with CI servers. To enable run the test tool with --tap option.


And if you don't have enough, just look on excellent library, built on top of ceylon.test which enables BDD style of test development and much more, called specks.

Ceylon in the browser (again)

As you might (or might not) know, Ceylon is more than a JVM language. It has been possible to compile Ceylon code to JavaScript for a long time, but other platforms such as Dart or LLVM are around the corner.

Having a JS backend means that you can actually write Ceylon code that can be run in a web browser, giving the opportunity to share code between the server and the client. The web IDE is a very good example of this. Up until now, using Ceylon in a browser wasn't really straightforward though. The good news is, Ceylon 1.2.1 brought two major features that overcome this problem:

Let's see how these fit together.

Creating a new project

First things first, we need an empty project that will hold two modules:

  • com.acme.client is a native("js") module that imports ceylon.interop.browser:

    native("js") module com.acme.client "1.0.0" { import ceylon.interop.browser "1.2.1-1"; }

  • com.acme.server is a native("jvm") module that imports ceylon.net:

    native("jvm") module com.acme.server "1.0.0" { import ceylon.net "1.2.1"; }

Serving Ceylon modules

In order to run com.acme.client in a browser, we have to import it from an HTML file. The recommended way is to use RequireJS:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Hello from Ceylon!</title>
</head>
<body>
    <div id="container">
    </div>
    <script src="//requirejs.org/docs/release/2.1.22/minified/require.js"></script>
    <script type="text/javascript">
    require.config({
        baseUrl : 'modules'
    });
    require(
            [ 'com/acme/client/1.0.0/com.acme.client-1.0.0' ],
            function(app) {
                app.run();
            }
    );
    </script>
</body>
</html>

Here, we tell RequireJS to use the prefix modules when downloading artifacts from the server, which means we need something on a server that will listen on /modules, parse module names and serve the correct artifact.

Option 1: using a Ceylon server

Ceylon SDK 1.2.1 introduced a new endpoint named RepositoryEndpoint, that uses a RepositoryManager to look up module artifacts in one or more Ceylon repositories, like the compiler or Ceylon IDE do:

import ceylon.net.http.server.endpoints {
    RepositoryEndpoint
}
import ceylon.net.http.server {
    newServer
}

"Run the module `com.acme.server`."
shared void run() {
    value modulesEp = RepositoryEndpoint("/modules");
    value server = newServer { modulesEp };

    server.start();
}

By default, this endpoint will look for artifacts in your local Ceylon repository, but also in the compiler's output directory. This greatly simplifies our development workflow, because each time we modify files in com.acme.client, Ceylon IDE will rebuild the JS artifacts, which can then be immediately refreshed in the browser.

Finally, to serve static files (HTML, CSS, images etc), we need a second endpoint that uses serveStaticFile to look up files in the www folder, and serve index.html by default:

function mapper(Request req) 
        => req.path == "/" then "/index.html" else req.path;

value staticEp = AsynchronousEndpoint(
    startsWith("/"), 
    serveStaticFile("www", mapper),
    {get}
);

value server = newServer { modulesEp, staticEp };

If we start the server and open http://localhost:8080/, we can see in the web inspector that the modules are correctly loaded:

Option 2: using static HTTP servers

Option 1 is interesting if you already have a backend written in Ceylon. Otherwise, it might be a little too heavy because you're basically starting a Ceylon server just to serve static files. Luckily, there's a way to create a standard Ceylon repository containing a module and all its dependencies: ceylon copy.

ceylon copy --with-dependencies com.acme.client

This command will copy the module com.acme.client and all its dependencies to a given folder (by default ./modules), preserving a repository layout like the one RequireJs expects. This means we can start httpd or nginx and bind them directly on the project folder. Modules will be loaded from ./modules, we just have to configure the server to look for other files in the www directory.

Attention though, each time we modify dependencies of com.acme.client, we will have to run ceylon copy again to update the local repository.

Option 2 is clearly the way to go for client apps that don't require a backend. Like option 1, it doesn't force you to publish artifacts in ~/.ceylon/repo.

Of course, if you are running a local Ceylon JS application, and your browser allows you to include files directly from the filesystem, you can also avoid the HTTP server and load everything for the filesystem.

Using browser APIs

Now that we have bootstrapped a Ceylon application running in a browser, it's time to do actual things that leverage browser APIs. To do this, we'll use the brand new ceylon.interop.browser which was introduced in the Ceylon SDK 1.2.1 a few days ago. Basically, it's a set of dynamic interfaces that allow wrapping native JS objects returned by the browser in nice typed Ceylon instances. For example, this interface represents the browser's Document:

shared dynamic Document satisfies Node & GlobalEventHandlers {
    shared formal String \iURL;
    shared formal String documentURI;
    ...
    shared formal HTMLCollection getElementsByTagName(String localName);
    shared formal HTMLCollection getElementsByClassName(String classNames);
    ...
}

An instance of Document can be retrieved via the toplevel object document, just like in JavaScript:

shared Document document => window.document;

Note that window is also a toplevel instance of the dynamic interface Window.

ceylon.interop.browser contains lots of interfaces related to:

Making an AJAX call, retrieving the result and adding it to a <div> is now super easy in Ceylon:

import ceylon.interop.browser.dom {
    document,
    Event
}
import ceylon.interop.browser {
    newXMLHttpRequest
}

shared void run() {
    value req = newXMLHttpRequest();

    req.onload = void (Event evt) {
        if (exists container = document.getElementById("container")) {
            value title = document.createElement("h1");
            title.textContent = "Hello from Ceylon";
            container.appendChild(title);

            value content = document.createElement("p");
            content.innerHTML = req.responseText;
            container.appendChild(content);
        }
    };

    req.open("GET", "/msg.txt");
    req.send();
}

Going further

Dynamic interfaces are really nice when it comes to using JavaScript objects in Ceylon. They are somewhat similar to TypeScript's type definitions, which means in theory, it is possible to use any JavaScript framework directly from Ceylon, provided that someone writes dynamic interfaces for its API.

The Ceylon team is currently looking for ways to load TypeScript definitions and make them available to Ceylon modules, which would greatly simplify the process of adding support for a new framework/API.

The complete source code for this article is available on GitHub.

A live example is available on the Web IDE.