Interop

Of course one important cornerstone of WebAssembly development is inter-operating with JavaScript based APIs.

Elements provides strongly-typed support for working with the Browser APIs and the Document Object Model (DOM) via the Browser class, but oftentimes you will also want to interact with your own JavaScript code, hosted in a separate .js files or in your core .html.

This inter-op works both ways:

Accessing WebAssembly Types from JavaScript

Accessing your Elements classes from JavaScript is easy.

On the WebAssembly side, simply make sure your class is marked with the Export Aspect.

type
  [Export]
  Program = public class
  public
    method HelloWorld();
[Export]
public class Program 
{
    public void HelloWorld() 
@Export
public class Program {
    public void HelloWorld() 
@Export
public class Program 
{
    public void HelloWorld() 
<Export>
Public Class Program

  Public Sub HelloWorld

In JavaScript, you can then instantiate an instance of the class simply by calling a method matching its name, on the module, using its name followed by parenthesis. You then can call any public member on it.

You can see this in action with the Program class in the default template:

var program = module.Program();
program.HelloWorld();

If your constructor or your method take parameters, you can of course pass these within the parenthesis. Make sure to only use parameter or return types that are compatible with JavaScript.

Calling JavaScript Functions from WebAssembly

The easiest way to access JavaScript from your Elements code is to use the WebAssembly.Eval method. This method takes a string that can be any arbitrary JavaScript code, but for the purpose of making inter-op calls, it can be a function call to a function inside your JavaScript.

Essentially it works the same as the eval function provided by JavaScript itself.

var x := WebAssembly.Eval('DoSomething(10)');
var x = WebAssembly.Eval("DoSomething(10)");
let x = WebAssembly.Eval("DoSomething(10)")
var x = WebAssembly.Eval("DoSomething(10);");
var x = WebAssembly.Eval("DoSomething(10)");
Dim x = WebAssembly.Eval("DoSomething(10)")

This single line of Elements code could call a function that is declared, for example, like this:

function DoSomething(someParam)
{
   ...
   return 5;
}

Declaring Strongly-Typed Method Stubs

The languages extern/external/native/Declare syntax can be used to declare strongly typed global function stubs that can be called, letting the compiler generate the necessary calls to WebAssembly.Eval() under the hood:

method DoSomething(someParam: Integer): Integer; external;
public extern int DoSomething(int someParam);
public __external func int DoSomething(int someParam);
public native int DoSomething(int someParam);
Declare Function DoSomething(someParam As Int) As Int

The DoSomething method can now be called directly anywhere from Elements WebAssembly code, with strongly-typed parameters and result. Under the hood, the compiler will emit the proper call back to JavaScript.

Calling JavaScript Object APIs from WebAssembly

You can also obtain references to JavaScript object instances from your Elements code. For example, an Eval call as shown above might return such an object, as do many of the existing Browser, NodeJS and DOM APIs exposed by Island RTL.

By default, such objects are typed as Dynamic, which means that – just as in JavaScript itself – the compiler has no intrinsic knowledge of what methods or properties might be available on the object. The compiler will let you make calls to any arbitrary member, and the calls will be dispatched dynamically at runtime – failing at runtime if they cannot be completed (again, just as they would in JavaScript itself).

var x := Eval('GimmeSomeObject()');
x.LetsMakeACall();
var x = Eval("GimmeSomeObject()");
x.LetsMakeACall();
let x = Eval("GimmeSomeObject()")
x.LetsMakeACall()
var x = Eval("GimmeSomeObject();");
x.LetsMakeACall();
var x = Eval("GimmeSomeObject()")
x.LetsMakeACall()
dim x = Eval("GimmeSomeObject()")
x.LetsMakeACall()

This code obtains a Javascript object by calling the GimmeSomeObject function defined in JavaScript. The variable x will be typed as Dynamic, letting us call any method we want.

Creating Strongly-Typed Interfaces

If you know the exact API of a JavaScript object, you can create a strongly typed interface that describes the available members, on the Elements side. You do this by adding the DynamicInterface(GetType(EcmaScriptObject)) Aspect to the interface:

type
  [DynamicInterface(GetType(EcmaScriptObject))
  ISomeObject = public interface
    method LetsMakeACall;
  end;
[DynamicInterface(GetType(EcmaScriptObject))
public interface ISomeObject
{
    void LetsMakeACall();
}
@DynamicInterface(GetType(EcmaScriptObject)
public interface ISomeObject {
    void LetsMakeACall()
}
@DynamicInterface(GetType(EcmaScriptObject)
public interface ISomeObject 
{
    void LetsMakeACall();
}
<DynamicInterface(GetType(EcmaScriptObject)>
Public Interface ISomeObject
    Sub LetsMakeACall()
End Interface

Once implemented, simply cast your Dynamic object reference to an the interface, and you can now make strongly-typed calls to the object that will be checked by the compiler (and you will get code completion, as well):

var x := Eval('GimmeSomeObject()') as ISomeObject;
x.LetsMakeACall();
var x = (ISomeObject)Eval("GimmeSomeObject()");
x.LetsMakeACall();
let x = Eval("GimmeSomeObject()") as! ISomeObject
x.LetsMakeACall()
var x = Eval("GimmeSomeObject();") as ISomeObject;
x.LetsMakeACall();
dim x = CType(Eval("GimmeSomeObject()"), ISomeObject)
x.LetsMakeACall()

Now, x is strongly-typed to be a ISomeObject, and the compiler will enforce that you only call known members. And you will get code completion, as well – for example CC after x. would show you LetsMakeACall as valid option.

Predefined Interfaces

Island RTL already contains pre-defined dynamic interfaces for dozens of common JavaScript objects used by the Browser's Document Object Mode; (DOM). these are declared in the RemObjects.Elements.WebAssembly.DOM namespace.

See Also