ml-in-barcelona / jsoo-react

js_of_ocaml bindings for ReactJS. Based on ReasonReact.
https://ml-in-barcelona.github.io/jsoo-react
MIT License
138 stars 19 forks source link

OCaml syntax: support for custom elements (i.e. web components) #111

Closed glennsl closed 2 years ago

glennsl commented 2 years ago

To interact with web components there needs to be some way to instantiate custom elements, and to have those elements take custom attributes.

Three options I can see that I believe would work today:

  1. Write calls to React.createDOMElementVariadic manually
  2. Write wrapper components in Reason (I would prefer not to depend on Reason just for that though.)
  3. Write wrapper components in JS, and associated bindings.

Are there any other options? It would be nice to have a bit more convenience.

Somewhat related to #93, as having a way to pass custom attributes to any element would also solve that.

jchavarri commented 2 years ago

I think using createElementVariadic might take one already quite far. Here is an idea (haven't tested, but I think should work):

Picking from the ppx tests, e.g.:

https://github.com/ml-in-barcelona/jsoo-react/blob/81ad4f7280ea712a891e95bf3ac2f3faa5c51e8d/ppx/test/pp_ocaml.expected#L39-L45

We can do st like:

module Brick_flipbox = struct
  let make ~custom_prop ~data_text () =
    let open Js_of_ocaml.Js in
    React.Dom.createDOMElementVariadic "brick-flipbox"
      ~props:
        ( Unsafe.obj
            [| ("custom-prop", Unsafe.inject (bool custom_prop))
             ; ("data-text", Unsafe.inject (string data_text)) |]
          : React.Dom.domProps )
      []
end

Then this can be consumed from either OCaml or Reason components, e.g.:

Reason

let el = <Brick_flipbox custom_prop=false data_text="foo" />

or OCaml

let el = Brick_flipbox.make ~custom_prop:true ~data_text:"foo" ()
jchavarri commented 2 years ago

TIL attrs can only be strings, so the example above wouldn't work in real world (should convert bool to string 😅 ) https://open-wc.org/guides/knowledge/attributes-and-properties/#attributes

But the main idea / approach still stands

glennsl commented 2 years ago

Thanks for the example! That already doesn't look too bad. I think we could do even better though, especially if we assume only string props, by providing a little helper function:

let custom name ?props=[||] children =
  let open Js_of_ocaml.Js in
  let props: domProps = 
    props |> Array.map (fun (key, value) -> (key, Unsafe.inject (string value)))
          |> Unsafe.obj
  in
  createDOMElementVariadic "brick-flipbox" ~props children
module Brick_flipbox = struct
  let make ~custom_prop ~data_text () =
    React.Dom.custom "brick-flipbox"
      ~props:
         [| ("custom-prop", custom_prop)
         ;  ("data-text", data_text) 
         |]
      []
end

Unfortunately it isn't the case that all props are attributes. We might for example want to allow onClick and other event handlers to be passed. On the other hand, any custom events would have to use a ref and be attached directly to the DOM anyway, so perhaps it wouldn't be so bad to do that for all events if we gain the convenience of having a helper function such as this.

jchavarri commented 2 years ago

Yeah, I think including this helper function makes sense, even if some cases would still require manual creation as you said.

As you seem to be exploring with custom components 🙂 maybe once you explore this approach with real world cases, you could submit a PR with the function(s) that you think are really valuable to include in the library? 🙏 I have zero experience with custom elements.

glennsl commented 2 years ago

Yeah, absolutely! I don't know if we're actually going to use web components (in the near future) though. We do today, and that's what prompted me to look into this, but one of the benefits of using React is the large ecosystem, so it's very possible that we'll just bind to a set of React components instead. I'll eventually get to this one way or the other though :)

jchavarri commented 2 years ago

@glennsl I consider this done with h function you added + DSL. Do you think we can close it?

https://github.com/ml-in-barcelona/jsoo-react/blob/7767fc1a62f0530ea2e62ca50ff43672f50e522a/lib/dom_dsl_core.ml#L25-L28

glennsl commented 2 years ago

Absolutely. Although we might still tweak the exact details of how to do this, it's definitely possible now. Thanks for the reminder!