Menu

AJSNI Usage Guide

What Is AJSNI?

The Aussom JavaScript Native Interface (AJSNI) lets Aussom-Script code call any native JavaScript library running in the browser. Instead of wrapping every JS library in Java, AJSNI provides a general-purpose bridge so you can work with libraries like Plotly, Leaflet, Chart.js, or any other JS code directly from your Aussom scripts.


Including AJSNI

Add this include at the top of your Aussom script:

include jsni;

This gives you two classes:

Class Type Purpose
js static Call JS functions, get/set globals, load scripts
jsobj object Hold a reference to a live JS object and call on it

Core Concepts

Data Types

When you pass values to JS or receive values back, AJSNI converts between Aussom and JavaScript automatically:

Aussom type JavaScript type
string string
int / double number
bool boolean
list array
map plain object {}
jsobj opaque object (by ref)
null null / undefined

The jsobj Handle

When a JS function returns a complex object (a map instance, a chart, a widget), AJSNI registers it in a JavaScript-side registry and gives Aussom a jsobj handle. You store that handle in a variable and use it to make further calls. The underlying JS object never crosses the JS/Aussom boundary -- only its integer ID does.

// js.call returns a jsobj when the result is a JS object.
chart = js.call("Chart.new", [ctx, config]);

// Call methods on the held object.
chart.call("update", []);

The js Static Class

All methods on js are static. No instance is needed.

js.call

Calls a global JavaScript function identified by a dotted path.

js.call(string FuncPath, Args = null)
  • FuncPath -- dotted path to the function, e.g. "Plotly.newPlot".
  • Args -- a list of arguments, or null for no arguments.
  • Returns a primitive, a map, a list, or a jsobj.
// Call a top-level function.
result = js.call("Math.max", [10, 20, 5]);

// Call a namespaced function with a jsobj argument.
map = js.call("L.map", ["map-div", {"zoomControl": true}]);
map.call("setView", [[48.8566, 2.3522], 13]);

// Pass a jsobj back as an argument.
tile = js.call("L.tileLayer", ["https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {}]);
tile.call("addTo", [map]);

js.get

Gets a global JavaScript variable or property by dotted path.

js.get(string PropPath)
version = js.get("Plotly.version");
title   = js.get("document.title");

js.set

Sets a global JavaScript variable or property by dotted path. The value must be a JSON string.

js.set(string PropPath, string ValueJson)
js.set("myApp.debug", "true");
js.set("myApp.label", "\"Hello\"");

js.exists

Returns true if a global variable or function exists at the given path.

js.exists(string PropPath)

Use this to check whether a library has loaded before using it:

if (js.exists("Plotly")) {
    this.render();
} else {
    js.loadScript("https://cdn.plot.ly/plotly-2.35.2.min.js", ::onLoaded, ::onError);
}

js.eval

Evaluates a JavaScript code string and returns the result.

js.eval(string Code)
count = js.eval("document.querySelectorAll('.item').length");
flag  = js.eval("navigator.onLine");

Use js.eval for quick one-off expressions. For anything more complex, prefer js.call so argument passing is handled safely.

js.loadScript

Dynamically loads an external JavaScript file by injecting a <script> tag. Fires OnLoad when the script is ready, or OnError with an error message if loading fails.

js.loadScript(string Url, callback OnLoad, callback OnError = null)
class Main {
    public main(args) {
        js.loadScript("https://cdn.plot.ly/plotly-2.35.2.min.js",
                      ::onLoaded, ::onError);
    }

    public onLoaded() {
        // Plotly is now available.
        this.renderChart();
    }

    public onError(msg) {
        Doc.getElementById("status").setHtml("Load failed: " + msg);
    }
}

js.promise

Calls a Promise-returning JavaScript function and bridges the result to Aussom callbacks. OnResolve receives the resolved value as an Aussom type. OnReject receives the rejection error as a string. If the called function returns a plain value instead of a Promise, OnResolve is still called with that value.

js.promise(string FuncPath, Args, callback OnResolve, callback OnReject = null)
class Main {
    public main(args) {
        js.promise("fetch", ["/api/data"], ::onData, ::onError);
    }

    public onData(response) {
        // response is a jsobj (the Response object).
        js.promise("__parseJson", [response], ::onParsed, ::onError);
    }

    public onParsed(data) {
        // data is a map or list converted from the JSON body.
        Doc.getElementById("result").setHtml(js.toJson(data));
    }

    public onError(err) {
        Doc.getElementById("result").setHtml("Error: " + err);
    }
}

js.toJson

Serializes any Aussom value (string, number, bool, list, map, or jsobj) to a JSON string. Useful for inspecting values or setting JS globals.

js.toJson(Value)
data = {"x": [1, 2, 3], "y": [10, 20, 30]};
raw  = js.toJson(data);            // '{"x":[1,2,3],"y":[10,20,30]}'
js.set("myApp.dataset", raw);

js.fromJson

Parses a JSON string into Aussom values (maps, lists, and primitives).

js.fromJson(string Json)
parsed = js.fromJson("{\"name\":\"Alice\",\"score\":99}");
c.log(parsed["name"]);   // Alice

The jsobj Class

A jsobj holds an opaque reference to a live JavaScript object. You normally get one back from js.call() or jsobj.call() when the result is a complex JS object (not a string, number, or boolean).

jsobj.call

Calls a named method on the underlying JS object.

jsobj.call(string Method, Args = null)
map = js.call("L.map", ["map-div"]);
map.call("setView", [[48.8566, 2.3522], 13]);

marker = js.call("L.marker", [[48.8584, 2.2945]]);
marker.call("addTo", [map]);
marker.call("bindPopup", ["<b>Eiffel Tower</b>"]);

jsobj.get

Gets a property from the underlying JS object. Returns a primitive, map, list, or jsobj.

jsobj.get(string Prop)
inner = chart.get("canvas");   // returns a jsobj for chart.canvas

jsobj.set

Sets a property on the underlying JS object. The value must be a JSON string.

jsobj.set(string Prop, string ValueJson)
obj.set("visible", "true");
obj.set("label",   "\"Hello\"");

jsobj.getString / getNumber / getBool

Convenience methods that read a single property directly as a typed Aussom value, without going through the full envelope round-trip.

jsobj.getString(string Prop)   // returns string
jsobj.getNumber(string Prop)   // returns double
jsobj.getBool(string Prop)     // returns bool
name  = player.getString("name");
score = player.getNumber("score");
alive = player.getBool("isAlive");

jsobj.isNull

Returns true if this handle points to a JavaScript null or undefined.

jsobj.isNull()
result = js.call("document.getElementById", ["missing-id"]);
if (result.isNull()) {
    c.log("Element not found.");
}

jsobj.toJson

Serializes the underlying JS object to a JSON string. Returns an empty string for objects that cannot be serialized (DOM nodes, closures, etc.).

jsobj.toJson()
raw = dataObj.toJson();
c.log(raw);   // {"key":"value","count":3}

jsobj.release

Removes this handle from the internal registry, allowing the JavaScript engine to reclaim the underlying object. Call release() when you are done with a jsobj to prevent handle leaks on pages that create many JS objects over time. Do not use the jsobj after calling release().

jsobj.release()
img = js.call("document.createElement", ["img"]);
img.call("decode", []);
img.release();   // done; free the handle

Passing jsobj Values as Arguments

When you pass a jsobj in an argument list, AJSNI encodes it as an internal {"__jsref": id} token and resolves it back to the live JS object on the JS side before the call. This lets you chain library calls naturally:

map    = js.call("L.map", ["map-div"]);
marker = js.call("L.marker", [[51.505, -0.09]]);
marker.call("addTo", [map]);       // passes map jsobj as an argument

Loading a Library First

Most third-party libraries are not present on the page until loaded. The typical pattern is:

class Main {
    public main(args) {
        if (js.exists("L")) {
            this.run();
        } else {
            js.loadScript("https://unpkg.com/leaflet@1.9.4/dist/leaflet.js",
                          ::onLoaded, ::onError);
        }
    }

    public onLoaded() {
        this.run();
    }

    public onError(msg) {
        Doc.getElementById("status").setHtml("Failed: " + msg);
    }

    public run() {
        map = js.call("L.map", ["map-div"]);
        map.call("setView", [[51.505, -0.09], 13]);
    }
}

Complete Example: Plotly Chart

include jsni;
include html;

class Main {
    public main(args) {
        if (js.exists("Plotly")) {
            this.renderChart();
        } else {
            js.loadScript("https://cdn.plot.ly/plotly-2.35.2.min.js",
                          ::onLoaded, ::onError);
        }
    }

    public onLoaded() { this.renderChart(); }

    public onError(msg) {
        Doc.getElementById("status").setHtml("Load error: " + msg);
    }

    public renderChart() {
        trace = {};
        trace["type"] = "bar";
        trace["x"]    = ["Jan", "Feb", "Mar"];
        trace["y"]    = [42, 58, 73];

        layout = {};
        layout["title"] = "Monthly Sales";

        js.call("Plotly.newPlot", ["chart-div", [trace], layout]);
        Doc.getElementById("status").setHtml("Chart ready.");
    }
}

Complete Example: Fetch with Promises

include jsni;
include html;

class Main {
    public main(args) {
        js.promise("fetch", ["/api/users"], ::onFetch, ::onError);
    }

    public onFetch(resp) {
        // resp is a jsobj (the Fetch Response).
        // Call resp.json() to parse the body -- it also returns a Promise.
        js.promise("__respJson", [resp], ::onData, ::onError);
    }

    public onData(data) {
        // data is a list or map parsed from the JSON body.
        Doc.getElementById("output").setHtml(js.toJson(data));
    }

    public onError(err) {
        Doc.getElementById("output").setHtml("Error: " + err);
    }
}

Note: resp.json() is a method on the Response object, not a global function. To bridge it with js.promise, create a small JS helper that wraps it:

<script>
  window.__respJson = function(r) { return r.json(); };
</script>

Known Limitations

  1. No live callback binding -- Aussom cannot register a function that JS calls repeatedly in a synchronous loop (e.g., D3 per-datum accessors). Use pre-built JS helper functions for that logic and call them from Aussom.

  2. No this rebinding -- jsobj.call("method", args) always calls the method with the held object as this. There is no way to supply an arbitrary this context.

  3. Chained calls create multiple handles -- Each jsobj.call() that returns an object creates a new jsobj handle, even when the JS library returns this for chaining. The chain works correctly; just remember to release() intermediate handles if memory is a concern.

  4. Large dataset performance -- Passing a list of 10,000+ items through JSON serialization is feasible but slow. For large datasets, load data directly into JS via js.eval() or a preloaded script, then reference it by a global variable name.