How to get the value of an input in a declarative way with Tyxml_js?

Dear fellow Js-of-OCamlers,

I am converting an old project using custom HTML helpers to Tyxml. The syntax is surprisingly close but Tyxml is much more generic. Specifically, I am using Js_of_ocaml_tyxml to get both “constant” HTML nodes and React ones. I have had some success and am overall very pleased with how things are going.

I am however hitting a wall right now when trying to write an <input> element that, when written into, triggers changes on the page (eg. a search box). Basically, I would want to get something like:

open Js_of_ocaml_tyxml
open Tyxml_js.Html

let (search_text, set_search_text) = React.S.create ""

let search_box =
  input ~a:[
    a_input_type `Text;
    a_placeholder "Search for anything (it's magic!)";
    a_oninput
      (fun event ->
        (* somehow use [set_search_text] on the value held by this input *)
        false);
  ] ();

to work. It doesn’t look to me like I can extract the new value of the <input> element from the event passed to onchange. I considered making let search_box recursive and trying to use To_dom.of_input to get a Dom element from which I can extract the value, but this doesn’t really work for many reasons, and To_dom.of_input feels like an escape hatch at this point. If I am going down that road I could also create the <input> element without onchange, then get a Dom element from it and then add an onchange manually to it, or register a JavaScript event listener.

So I think I can hack it together so that it works; but is there not a more declarative way? The Tyxml way? Any help would be appreciated as well as pointer to documentation on this. I have tried to find my way through the API documentation of Js_of_ocaml but I am a bit lost.

Thank you very much in advance,

Best,
– Niols

Hi,
Here are two ways to accomplish what you want :wink:

open Js_of_ocaml
open Js_of_ocaml_tyxml.Tyxml_js

let (search_text, set_search_text) = React.S.create ""

let imperative_search_box =
  let t =
    let open Html in
    input ~a:[
      a_input_type `Text;
      a_placeholder "Search for anything (it's magic!)";
    ] ()
  in
  let inp = To_dom.of_input t in
  ignore (Dom_events.listen inp Dom_html.Event.input (fun _ _ ->
    set_search_text (Js.to_string inp##.value);
    false
  ));
  t

let somewhat_declarative_search_box =
  let open Html in
  input ~a:[
    a_input_type `Text;
    a_placeholder "Search for anything (it's magic!)";
    a_oninput (fun e ->
      Js.Opt.iter e##.target (fun elt ->
        Js.Opt.iter (Dom_html.CoerceTo.input elt) (fun inp ->
          set_search_text (Js.to_string inp##.value)
        )
      );
      false
    );
  ] ()

For the ##. syntax to work, you’ll have to use js_of_ocaml’s ppx. If you use dune, just add (preprocess (pps js_of_ocaml-ppx)) to your executable stanza. Full example:

(executable
 (name client)
 (modes js)
 (libraries js_of_ocaml-tyxml)
 (preprocess (pps js_of_ocaml-ppx)))

Amazing! Your imperative_search_box is what I had managed to do in the end but was unhappy with. Your somewhat_declarative_search_box is basically what I was hoping for but I just couldn’t find my way through the event object.

I’m already familiar with the ##. syntax, although my LSP setup seems to hate it and I’m not sure I fully understand what happens behind the scenes in JSOO. That will be for another day.

Thank you so much! :slight_smile:

If this is in reference to not understanding what the ##. syntax desugars to, if you’re using dune, you can run dune describe pp on a source file to see what that file looks like post-preprocessing. An example w/ a react-jsoo utility function:

  • Before
let value_of_event evt = (React.Event.Form.target evt |> to_input_element)##.value |> Js.to_string
  • After
let value_of_event evt =
  ((fun (type res) ->
      fun (type t0) ->
        fun (t0 : t0 Js_of_ocaml.Js.t) ->
          fun (_ : t0 -> < get: res   ;.. >  Js_of_ocaml.Js.gen_prop) ->
            (Js_of_ocaml.Js.Unsafe.get t0 (Js_of_ocaml.Js.string "value") : 
            res))
     ((React.Event.Form.target evt) |> to_input_element : < .. > 
                                                            Js_of_ocaml.Js.t)
     (fun x -> x#value))
    |> Js.to_string

Hey @Niols , it’s been a while !

Unfortunately, you have to step outside of Tyxml_js to get interactive behaviors.

There is a slightly nicer interface for event handlers in Eliom, but nobody really ported that (or figured out something else nice and uniform) for Tyxml_js.
Maybe a first step would be to obtain a self in the onevent handler ? In any case, ideas would be welcome. :slight_smile:

Neat, thank you very much!

Hey @Drup, indeed! But you’re not getting rid of me so easily, especially since most of my currents projects are web-related.

I don’t mind so much stepping outside of Tyxml_js and falling back to Dom manipulations; I’m familiar with those and used them quite a bit already. I was looking for a way to keep some “declarativeness” in my code despite falling back on Dom manipulations and I like what this post came up with.

Of course, I’d also love a cleaner interface. Maybe I’ll have ideas after playing with this one!