Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

explore a unified attribute interface #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 15 additions & 24 deletions example/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,46 +24,37 @@ let () =
; dataset_module
])
in
let h1 = Vnode.h1 Jv.null [ Vnode.text "Hello World" ] in
let tick () =
Vnode.h2 Jv.null [ Vnode.text (Date.to_locale_time_string (Date.now ())) ]
in
let h1 = Vnode.h1 [] [ Vnode.text "Hello World" ] in
let tick () = Vnode.h2 [] [ Vnode.text (Date.to_locale_time_string (Date.now ())) ] in
let svg_node =
Vnode.make_node
"svg"
(Jv.obj [| "attrs", Jv.obj [| "width", Jv.of_int 100; "height", Jv.of_int 100 |] |])
Attr.[ int "width" 100; int "height" 100 ]
[ Vnode.make_node
"circle"
(Jv.obj
[| ( "attrs"
, Jv.obj
[| "cx", Jv.of_int 50
; "cy", Jv.of_int 50
; "r", Jv.of_int 40
; "stroke", Jv.of_string "green"
; "stroke-width", Jv.of_int 4
; "fill", Jv.of_string "yellow"
|] )
|])
Attr.
[ int "cx" 50
; int "cy" 50
; int "r" 40
; string "stroke" "green"
; int "stroke-width" 4
; string "fill" "yellow"
]
[]
]
in
let make_counter count = Vnode.p Jv.null [ Vnode.text (Int.to_string count) ] in
let make_counter count = Vnode.p [] [ Vnode.text (Int.to_string count) ] in
let count = ref 0 in
let counter = ref (make_counter !count) in
let on_click () =
let on_click _ =
incr count;
let new_counter = make_counter !count in
patch (`Vnode !counter) new_counter;
counter := new_counter
in
let btn =
Vnode.button
(Jv.obj [| "on", Jv.obj [| "click", Jv.repr on_click |] |])
[ Vnode.text "Click me" ]
in
let btn = Vnode.button Attr.[ click on_click ] [ Vnode.text "Click me" ] in
let h2 = ref (tick ()) in
patch (`Element container) (Vnode.div Jv.null [ svg_node; h1; !h2; !counter; btn ]);
patch (`Element container) (Vnode.div [] [ svg_node; h1; !h2; !counter; btn ]);
ignore
(G.set_interval ~ms:1000 (fun () ->
let new_node = tick () in
Expand Down
43 changes: 43 additions & 0 deletions lib/attr.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Event_handler = struct
type t =
| Mouse of (Brr.Ev.Mouse.t Brr.Ev.t -> unit)
| Keyboard of (Brr.Ev.Keyboard.t Brr.Ev.t -> unit)
| Raw of (Jv.t -> unit)
end

type t =
| Attr of string * Jv.t
| Event of
{ on : string
; handler : Event_handler.t
}

type attrs =
{ attr : (string * Jv.t) list
; event : (string * Jv.t) list
}

let make_attr k f x = Attr (k, f x)
let bool k v = make_attr k Jv.of_bool v
let int k v = make_attr k Jv.of_int v
let float k v = make_attr k Jv.of_float v
let string k v = make_attr k Jv.of_string v
let click handler = Event { on = "click"; handler = Event_handler.Mouse handler }
let mouse_event on handler = Event { on; handler = Event_handler.Mouse handler }
let keyboard_event on handler = Event { on; handler = Event_handler.Keyboard handler }
let unsafe_event on handler = Event { on; handler = Event_handler.Raw handler }
let attrs_to_skip = [ "xmlns"; "xmlns:xlink" ]

let attrs_to_jv xs =
let obj =
List.fold_left
(fun acc attr ->
match attr with
| Attr (k, _) when List.mem (String.lowercase_ascii k) attrs_to_skip -> acc
| Attr (k, v) -> { acc with attr = (k, v) :: acc.attr }
| Event { on; handler } -> { acc with event = (on, Jv.repr handler) :: acc.event })
{ attr = []; event = [] }
xs
in
Jv.obj
[| "attrs", Jv.obj (Array.of_list obj.attr); "on", Jv.obj (Array.of_list obj.event) |]
11 changes: 11 additions & 0 deletions lib/attr.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type t

val bool : string -> bool -> t
val int : string -> int -> t
val float : string -> float -> t
val string : string -> string -> t
val click : (Brr.Ev.Mouse.t Brr.Ev.t -> unit) -> t
val mouse_event : string -> (Brr.Ev.Mouse.t Brr.Ev.t -> unit) -> t
val keyboard_event : string -> (Brr.Ev.Keyboard.t Brr.Ev.t -> unit) -> t
val unsafe_event : string -> (Jv.t -> unit) -> t
val attrs_to_jv : t list -> Jv.t
1 change: 1 addition & 0 deletions lib/snabbdom.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module M = struct
let dataset_module = get_global "datasetModule"
end

module Attr = Attr
module Vnode = Vnode

let init modules =
Expand Down
1 change: 1 addition & 0 deletions lib/snabbdom.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module M : sig
val dataset_module : t
end

module Attr = Attr
module Vnode = Vnode

val init : M.t list -> [< `Element of Brr.El.t | `Vnode of Vnode.t ] -> Vnode.t -> unit
9 changes: 5 additions & 4 deletions lib/vnode.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ type t = Jv.t
let h = get_global "h"
let to_jv t = t

type vnode_data = Jv.t
type children = Jv.t list
type create_with_children = vnode_data -> children -> t
type create_without_children = vnode_data -> t
type create_with_children = Attr.t list -> children -> t
type create_without_children = Attr.t list -> t

let make_node selector vnode_data children =
Jv.apply h [| Jv.of_string selector; vnode_data; Jv.of_jv_list children |]
Jv.apply
h
[| Jv.of_string selector; Attr.attrs_to_jv vnode_data; Jv.of_jv_list children |]

let text t = Jv.of_string t
let br data = make_node "br" data []
Expand Down
4 changes: 2 additions & 2 deletions lib/vnode.mli
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ type t

val to_jv : t -> Jv.t

type create_with_children = Jv.t -> t list -> t
type create_without_children = Jv.t -> t
type create_with_children = Attr.t list -> t list -> t
type create_without_children = Attr.t list -> t

val make_node : string -> create_with_children
val text : string -> t
Expand Down
69 changes: 23 additions & 46 deletions tyxml/snabbdom_tyxml.ml
Original file line number Diff line number Diff line change
Expand Up @@ -15,59 +15,36 @@ module Xml_vnode = struct
type mouse_event_handler = Brr.Ev.Mouse.t Brr.Ev.t -> unit
type keyboard_event_handler = Brr.Ev.Keyboard.t Brr.Ev.t -> unit
type touch_event_handler = Jv.t -> unit
type attrib = [ `Event of string | `Attr of string ] * Jv.t
type attrib = Attr.t

let float_attrib name f = `Attr name, Jv.of_float f
let int_attrib name f = `Attr name, Jv.of_int f
let string_attrib name f = `Attr name, Jv.of_string f
let space_sep_attrib name xs = `Attr name, Jv.of_string (String.concat " " xs)
let comma_sep_attrib name xs = `Attr name, Jv.of_string (String.concat "," xs)
let event_handler_attrib name handler = `Event name, Jv.repr handler
let mouse_event_handler_attrib name handler = `Event name, Jv.repr handler
let touch_event_handler_attrib name handler = `Event name, Jv.repr handler
let keyboard_event_handler_attrib name handler = `Event name, Jv.repr handler
let uri_attrib name value = `Attr name, Jv.of_string value
let uris_attrib name values = `Attr name, Jv.of_string (String.concat " " values)
(* TODO: This is just temporary to verify that this approach can work. Tyxml sends
events like [onclick] but snabbdom expects keys like [click] *)
let make_event_name name = String.sub name 2 (String.length name - 2)
let float_attrib name f = Attr.float name f
let int_attrib name f = Attr.int name f
let string_attrib name f = Attr.string name f
let space_sep_attrib name xs = string_attrib name (String.concat " " xs)
let comma_sep_attrib name xs = string_attrib name (String.concat "," xs)
let event_handler_attrib name handler = Attr.unsafe_event (make_event_name name) handler

type aname = string
type elt = Vnode.t
type ename = string
let mouse_event_handler_attrib name handler =
Attr.mouse_event (make_event_name name) handler

module StringSet = Set.Make (struct
type t = string
let touch_event_handler_attrib name handler =
Attr.unsafe_event (make_event_name name) handler

let compare a b =
let a = String.lowercase_ascii a in
let b = String.lowercase_ascii b in
String.compare a b
end)
let keyboard_event_handler_attrib name handler =
Attr.keyboard_event (make_event_name name) handler

let attributes_to_skip = StringSet.of_list [ "xmlns"; "xmlns:xlink" ]
let uri_attrib name value = string_attrib name value
let uris_attrib name values = string_attrib name (String.concat " " values)

let make_attrs (a : attrib list option) =
match a with
| None -> Jv.null
| Some attrs ->
let events, attrs =
List.fold_left
(fun (events, attrs) t ->
match t with
| `Attr name, v ->
if StringSet.mem name attributes_to_skip
then events, attrs
else events, (name, v) :: attrs
(* TODO: This is just temporary to verify that this approach can work. Tyxml
sends events like [onclick] but snabbdom expects keys like [click] *)
| `Event name, v ->
(String.sub name 2 (String.length name - 2), v) :: events, attrs)
([], [])
attrs
in
Jv.obj
[| "attrs", Jv.obj (Array.of_list attrs); "on", Jv.obj (Array.of_list events) |]
type aname = string
type elt = Vnode.t
type ename = string

let leaf ?a name = Vnode.make_node name (make_attrs a) []
let node ?a name children = Vnode.make_node name (make_attrs a) children
let leaf ?(a = []) name = Vnode.make_node name a []
let node ?(a = []) name children = Vnode.make_node name a children
let empty () = assert false
let comment _c = assert false
let pcdata = Vnode.text
Expand Down