How to bind a JS library for OCaml
Accessing a JS variable, ex: document:
Write in .ml:
let v = (Js.Unsafe.js_expr "window")##.document
Alternatively, the global object can be used. In the browser, it refers to window.
let v = Js.Unsafe.global##.document
and in .mli:
val v : ... Js.t
Be careful the function Js_of_ocaml.Js.Unsafe.js_expr and the value Js_of_ocaml.Js.Unsafe.global are not typed. Verify the library documentation before writing the type.
Binding a JS function
Example from the Js module:
let decodeURI (s : js_string t) : js_string t =
Js.Unsafe.fun_call (Js.Unsafe.js_expr "decodeURI") [|Js.Unsafe.inject s|]
Have a look at the Js_of_ocaml.Js.Unsafe module API.
Using a JS constructor, ex: F:
Write in .ml:
let f = Js.Unsafe.global##._F
and in .mli:
val f : (... -> ... Js.t) Js.constr
and if you want to use JS overloading, do, for example:
val f_fromInt : (int -> ... Js.t) Js.constr
val f_fromString : (js_string t -> ... Js.t) Js.constr
val f_blah : (#Dom_html.element t -> js_string t -> ... Js.t) Js.constr
Accessing or modifying a JS property to an element
When a property is missing in the OCaml interface of an element (for example it has been dynamically added by a library), you can access using unsafe features:
(Js.Unsafe.coerce elt)##.blah
If you want to add yourself a new property:
(Js.Unsafe.coerce elt)##.blah := v
Here, v may be a JS value or an OCaml value.
If you want to do that in type safe manner, just define new types for the extended elements, or wrap the unsafe functions inside a getter and setter.
Binding a JS object
Write in .ml and in .mli:
class type my_js_type = object
(* read only property, read value with t##.prop1 *)
method prop1 : int readonly_prop
(* write only property, write value with t##.prop2 := 3.14 *)
method prop2 : float writeonly_prop
(* both read and write *)
method prop3 : int prop
(* method or property starting with a capital letter can be prepend
with an underscore. *)
method _Array : ... (* to access the JavasScript property or method Array *)
(* Define two methods with different types, that translate to
the same JavaScript method. *)
method my_fun_int : int -> unit meth
method my_fun_string : js_string t -> unit meth
(* Both will actually call the my_fun JavaScript method. *)
(* To call a javascript method starting with one underscore *)
method __hiddenfun : ..
method __hiddenfun_ : ..
method __hiddenfun_something : ..
(* This will call the _hiddenfun Javascript method *)
(* To call the javascript method '_' *)
method __ : ..
end
Example binding some constants:
For example if the JS class is used to define three constants thelib.Theclass.VALUEA, thelib.Theclass.VALUEB, thelib.Theclass.VALUEC,
Since ocaml doesn't allows method name to start with capitalised letter, we can add an _
write in .ml and .mli:
type thetype
class type theclass = object
method _VALUEA : thetype readonly_prop
method _VALUEB : thetype readonly_prop
method _VALUEC : thetype readonly_prop
end
and in .ml:
let theclass = (Js.Unsafe.js_expr "thelib")##._Theclass
and in .mli:
val theclass : theclass t
Constructing JS objects manually
If you want to construct a JS object manually (without calling a function or a constructor), you can use the Ppx syntax extension.
For example:
let options = object%js
val x = 3 (* read-only prop *)
val mutable y = 4 (* read/write prop *)
end
You can also use the unsafe Js_of_ocaml.Js.Unsafe.obj.
Set/get variables
You can access every variable through the global javascript object (window):
If the variable var has type t Js.t
let set (x:t Js.t) = Js.Unsafe.global##.var := x
let get x : t Js.t = Js.Unsafe.global##.var
Object property with multiple types
If you want to read a property of an object which can have multiple types, you can define an intermediate type to do typesafe casting ex:
Suppose the object obj has a property prop which can be either a string or a Dom node:
type dom_or_string
class type obj = object
method prop : dom_or_string Js.t prop
end
let obj : obj Js.t = Js.Unsafe.js_expr "obj"
let string_constr : Js.js_string Js.t Js.constr = Js.Unsafe.global##._String
let cast_string (x:dom_or_string Js.t) : Js.js_string Js.t Js.opt =
if Js.instanceof x string_constr
then Js.some (Js.Unsafe.coerce x)
else Js.null
let node_constr : Dom.node Js.t Js.constr = Js.Unsafe.global##._Node
let cast_node (x:dom_or_string Js.t) : Dom.node Js.t Js.opt =
if Js.instanceof x node_constr
then Js.some (Js.Unsafe.coerce x)
else Js.null
Check availability of method
It is frequent that some method are not to be implemented in some browser.
To check the presence of method met:
let check_met obj = Js.Optdef.test ((Js.Unsafe.coerce obj)##.met)