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.
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:
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.
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
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.
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!