March Docs

Bastion.JS

lib/js.march — Bastion.JS: WASM → JavaScript FFI interface.

Provides typed wrappers for calling JavaScript from WASM island modules. Use this when the built-in Cmd system doesn't cover a specific browser API.

── How it works ──────────────────────────────────────────────────────────────

March islands compile to wasm32-unknown-unknown. JavaScript objects cannot be passed directly across the WASM boundary. Bastion uses an opaque handle table: JS objects are stored in a JS-side registry and identified by integer handles (JsRef values). WASM code passes handles back to JS for further operations.

The March compiler emits import "bastion" "js_*" declarations for these FFI functions, and wasm-bridge.js provides the corresponding imports.

── Usage ──────────────────────────────────────────────────────────────────────

import Bastion.JS as JS
import Bastion.Cmd as Cmd

-- Read the clipboard using raw JS FFI
fn update(state, CopyText) do
  let _ = JS.call("navigator.clipboard.writeText", [JS.string_val(state.text)])
  (state, Cmd.none())
end

-- Query the DOM and read an attribute
fn update(state, ReadInput) do
  let input = JS.query_selector("#search-input")
  let value = match input do
    Some(el) -> Option.unwrap_or(JS.get_attribute(el, "value"), "")
    None     -> ""
  end
  ({ state | query = value }, Cmd.none())
end

── Note on closures ──────────────────────────────────────────────────────────

JS.add_event_listener accepts a callback function. The callback runs inside the JS event loop and must be a simple function (no captures from the island state) — the March compiler enforces this at the boundary. For reactive updates, prefer data-on-* HTML attributes or Cmd.after/every.

Types

typeJsRefJsRef = JsRef(Int)#
typeJsElementJsElement = JsElement(JsRef)#
typeJsEventJsEvent = JsEvent(JsRef)#

Functions

fnadd_event_listeneradd_event_listener(el : JsElement, event_name : String, handler : JsEvent -> a)#

Add an event listener to a DOM element. The callback receives a JsEvent and returns a message that is dispatched to the island's update function.

Note: the listener persists until the island is destroyed.  Use Cmd-based
patterns (data-on-* attributes) for most event handling.

    JS.add_event_listener(el, "custom-event", fn ev ->
      CustomEvent(JS.to_string(JS.event_detail(ev)) |> Option.unwrap_or(""))
    end)
fnbool_valbool_val(b : Bool) : JsRef#

Wrap a March Bool as a JsRef argument.

fncallcall(func_path : String, args : List(JsRef)) : JsRef#

Call a JavaScript function by dotted path and return the result as a JsRef.

func_path  — dotted path to the function, e.g. "navigator.clipboard.writeText"
args       — list of JsRef arguments

Returns JsRef(0) if the function does not exist or throws.

    let _ = JS.call("console.log", [JS.string_val("hello from March")])
    let result = JS.call("JSON.stringify", [JS.json_val(my_json)])
fnevaleval(js_code : String) : JsRef#

Evaluate a raw JavaScript expression string and return the result.

Use sparingly.  Prefer JS.call or JS.global when possible.

    let ts = JS.eval("Date.now()")
fnevent_detailevent_detail(ev : JsEvent) : JsRef#

Get the detail property of a CustomEvent.

    let detail_str = JS.event_detail(ev) |> JS.to_string()
fnget_attributeget_attribute(el : JsElement, attr : String) : Option(String)#

Get the value of an attribute on a DOM element. Returns None if the attribute is absent.

    let val = JS.get_attribute(el, "value")
    let cls = JS.get_attribute(el, "class")
fnglobalglobal(prop_path : String) : JsRef#

Read a global JavaScript property by dotted path.

    let href = JS.global("window.location.href")
    let user_agent = JS.global("navigator.userAgent")
fnint_valint_val(n : Int) : JsRef#

Wrap a March Int as a JsRef argument.

fnis_nullis_null(ref : JsRef) : Bool#

Return true if the JsRef is null or undefined.

fnjson_valjson_val(json_str : String) : JsRef#

Wrap a March String as a JSON value for JS.call. Serializes the string as a JSON object, useful for passing complex data.

fnnull_valnull_val() : JsRef#

The null JS value (JsRef 0).

fnquery_selectorquery_selector(selector : String) : Option(JsElement)#

Query the DOM for the first element matching selector. Returns None if no element matches.

    let input = JS.query_selector("#search-input")
    let btn   = JS.query_selector("button[type=submit]")
fnremove_attributeremove_attribute(el : JsElement, attr : String)#

Remove an attribute from a DOM element.

    JS.remove_attribute(el, "disabled")
fnset_attributeset_attribute(el : JsElement, attr : String, value : String)#

Set the value of an attribute on a DOM element.

    JS.set_attribute(el, "class", "active")
    JS.set_attribute(el, "disabled", "")
fnstring_valstring_val(s : String) : JsRef#

Wrap a March String as a JsRef argument for JS.call.

fnto_intto_int(ref : JsRef) : Option(Int)#

Extract a number from a JsRef as an Int. Returns None if not a number.

fnto_stringto_string(ref : JsRef) : Option(String)#

Extract a string from a JsRef. Returns None if the JS value is not a string, is null, or is undefined.