How do you handle imperative web api's in LexiFi's ocaml-vdom?

What is the best way to handle imperative elements (canvas, webgl, etc) in ocaml-vdom?

I can create a canvas element with Vdom.elt in my view function, but that’s a bit useless as I can’t figure out a sane way to get the context and draw to it.

Other virtual dom implementations have a hooks mechanism, or similar, that allow executing a function on the dom element after vdom->dom transition, but ocaml-vdom doesn’t seem to have this. I believe the Custom.t element is probably the intended way to handle this, but I can’t figure out how to use it. Where does the Js_browser.Element.t come from in Vdom_blit.Custom.make?

2 Likes

Yes, Custom elements should be used. You need to extend Vdom.Custom.t with a constructor holding the internal state of the imperative component, and then create a Vdom_blit.Custom.handler that detects this constructor and produce a Vdom_blit.Custom.t object. To do so, the handler needs to create the DOM element (such as a canvas, using Js_browser) and pass it to Vdom_blit.Custom.make, together with a sync function. The handler can directly “talk” to the DOM element since it created it (in particular, it can use arguments of the Vdom.Custom.t constructor to initialize, e.g. draw an initial shape to the canvas). And the sync function is then in charge of talking again to the same DOM element (captured in its closure) to apply later changes to the state (stored in the constructor).

Does that make sense? If not, can you give a concrete example of a small application “workflow” that you would like to implement?

1 Like

Thanks, I think I get it now. This is what I came up with as a simple trial:

type Custom.t += Canvas of string

let canvas_handler _ctx c =
  match c with
  | Canvas _ ->
      let e = Document.create_element document "canvas" in
      let sync = function
        | Canvas color ->
          begin match Canvas.get_context e with
          | Some context ->
            Canvas.set_fill_style context (`Color color);
            Canvas.fill_rect context 0. 0. 100. 100.;
            true
          | None -> false
          end
        | _ -> false
      in
      ignore (sync c);
      Some (Vdom_blit.Custom.make ~sync e)
  | _ -> None

let canvas ?key ?a color = custom ?key ?a (Canvas color)

let () = Vdom_blit.(register (custom canvas_handler))
2 Likes

That’s about right. One thing: for performance reasons, you should keep track of the current color, and only send instructions to Canvas when it changes. Basically, for a Custom element, you need to implement the state diffing/DOM patching logic yourself.