-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathweb_reconciler.mlx
148 lines (122 loc) · 3.93 KB
/
web_reconciler.mlx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
(** This is an implementation of a reconciler for DOM elements via js_of_ocaml:
http://ocsigen.org/js_of_ocaml/3.1.0/api/Dom_html
This is just an example but you could use this to create interesting CLI
apps, with a react-like functional API! *)
open Js_of_ocaml
open Brisk_reconciler
let str = string_of_int
(* Step 1: Define the reconciler template *)
type node = Dom_html.element Js.t
let insertNode ~(parent : node) ~(child : node) ~position:_ =
Dom.appendChild parent child;
parent
let deleteNode ~(parent : node) ~(child : node) ~position:_ =
Dom.removeChild parent child;
parent
let moveNode ~parent ~child:_ ~from:_ ~to_:_ = parent
let onStale = (Remote_action.create () : unit Remote_action.t);;
addStaleTreeHandler (fun () -> Remote_action.send ~action:() onStale)
(* Step 2: Define some native components (aka primitives) *)
let document = Dom_html.window##.document
let%nativeComponent view ~children () hooks =
( { make =
(fun () ->
let node = Dom_html.createDiv document in
node)
; configureInstance = (fun ~isFirstRender:_ node -> node)
; children
; insertNode
; deleteNode
; moveNode }
, hooks )
let%nativeComponent text ~text () hooks =
( { make =
(fun () ->
let node = Dom_html.createSpan document in
node##.innerHTML := Js.string text;
node |> Dom_html.element)
; configureInstance =
(fun ~isFirstRender:_ n ->
let node = (Obj.magic n : Dom_html.imageElement Js.t) in
node##.innerHTML := Js.string text;
node |> Dom_html.element)
; children = empty
; insertNode
; deleteNode
; moveNode }
, hooks )
let%nativeComponent button ~onPress ~title () hooks =
( { make =
(fun () ->
let node =
Dom_html.createButton ~_type:(Js.string "button") document
in
let t = Js.string title in
node##.title := t;
node##.innerHTML := t;
node##.onclick :=
Dom_html.handler (fun _e -> onPress (); Js.bool false);
node |> Dom_html.element)
; configureInstance =
(fun ~isFirstRender:_ n ->
let node = (Obj.magic n : Dom_html.imageElement Js.t) in
let t = Js.string title in
node##.title := t;
node##.innerHTML := t;
node##.onclick :=
Dom_html.handler (fun _e -> onPress (); Js.bool false);
node |> Dom_html.element)
; children = empty
; insertNode
; deleteNode
; moveNode }
, hooks )
(* Step 3: Use the reconciler + native elements to create something great! *)
let action = Remote_action.create ()
let%component counterButtons () =
let%hook count, setCount = Hooks.state 0 in
let _unsubscribe =
Remote_action.subscribe ~handler:(fun n -> setCount (fun _ -> n)) action
in
<view>
<button
title="Decrement"
onPress=(fun () -> setCount (fun count -> count - 1))
/>
<text text=("Counter: " ^ str count) />
<button
title="Reset after 1s"
onPress=(fun () ->
ignore
(Dom_html.setTimeout
(fun () -> Remote_action.send ~action:0 action)
1000.))
/>
<button
title="Increment"
onPress=(fun () -> setCount (fun count -> count + 1))
/>
</view>
let render () =
<view> <text text="Hello World" /> <counterButtons /> </view>
(* Step 4: Make the first render *)
let main () =
let rendered =
ref
(RenderedElement.render
{ node = Dom_html.getElementById_exn "app"
; insertNode
; deleteNode
; moveNode }
(render ()))
in
let _unsubscribe =
Remote_action.subscribe
~handler:(fun () ->
let nextElement = RenderedElement.flushPendingUpdates !rendered in
RenderedElement.executeHostViewUpdates nextElement |> ignore;
rendered := nextElement)
onStale
in
RenderedElement.executeHostViewUpdates !rendered |> ignore
let () = main ()