jon.recoil.org

Widget Demo

This page demonstrates interactive FRP widgets powered by Widget and Note.

#require "note";; #require "js_top_worker-widget";;

Static Widget

A simple widget that renders static HTML:

let open Widget.View in Widget.display ~id:"hello" ~handlers:[] (Element { tag = "div"; attrs = []; children = [Text "Hello from an OCaml widget!"] })

Counter (FRP with Note)

A counter driven by Note signals. Pressing the buttons sends events back to the worker, which updates the signal:

let inc_e, send_inc = Note.E.create () let dec_e, send_dec = Note.E.create () let count = let delta = Note.E.select [ Note.E.map (fun () n -> n + 1) inc_e; Note.E.map (fun () n -> n - 1) dec_e; ] in Note.S.accum 0 delta let counter_view n = let open Widget.View in Element { tag = "div"; attrs = [Class "counter"]; children = [ Element { tag = "button"; attrs = [Handler ("click", "dec")]; children = [Text "-"] }; Element { tag = "span"; attrs = [Style ("margin", "0 1em")]; children = [Text (string_of_int n)] }; Element { tag = "button"; attrs = [Handler ("click", "inc")]; children = [Text "+"] }; ] } let () = Widget.display ~id:"counter" ~handlers:[ "inc", (fun _ -> send_inc ()); "dec", (fun _ -> send_dec ()); ] (counter_view 0) let _logr = Note.S.log (Note.S.map counter_view count) (Widget.update ~id:"counter") let () = Note.Logr.hold _logr

Slider

An input slider that drives a signal. Moving the slider sends the value back to the worker:

let x_e, send_x = Note.E.create () let x = Note.S.hold 50 x_e let slider_view v = let open Widget.View in Element { tag = "div"; attrs = []; children = [ Element { tag = "label"; attrs = []; children = [Text (Printf.sprintf "X: %d" v)] }; Element { tag = "input"; attrs = [ Property ("type", "range"); Property ("min", "0"); Property ("max", "100"); Property ("value", string_of_int v); Handler ("input", "x"); ]; children = [] }; ] } let () = Widget.display ~id:"slider" ~handlers:["x", (fun v -> send_x (int_of_string (Option.get v)))] (slider_view 50) let _logr2 = Note.S.log (Note.S.map slider_view x) (Widget.update ~id:"slider") let () = Note.Logr.hold _logr2

Cross-cell Derived Widget

This widget derives from the slider signal x defined above. Moving the slider updates this widget too:

let doubled_view v = let open Widget.View in Element { tag = "div"; attrs = []; children = [ Text (Printf.sprintf "2x = %d" (v * 2)) ] } let () = Widget.display ~id:"doubled" ~handlers:[] (doubled_view (Note.S.value x)) let _logr3 = Note.S.log (Note.S.map doubled_view x) (Widget.update ~id:"doubled") let () = Note.Logr.hold _logr3

Text Entry

A text input with a button. Typing in the textarea fires text_changed, which updates the signal. Clicking "Shout" reads the current text and displays it in uppercase:

let text_e, send_text = Note.E.create () let text_s = Note.S.hold "hello world" text_e let shout_e, send_shout = Note.E.create () let shouted = Note.S.hold "" shout_e let text_entry_view txt = let open Widget.View in Element { tag = "div"; attrs = []; children = [ Element { tag = "textarea"; attrs = [ Property ("rows", "2"); Handler ("input", "text_changed"); ]; children = [Text txt] }; Element { tag = "button"; attrs = [ Handler ("click", "shout"); ]; children = [Text "Shout"] }; ] } let result_view s = let open Widget.View in Element { tag = "div"; attrs = [ Style ("font-family", "monospace"); Style ("padding", "0.5em"); ]; children = [Text s] } let () = Widget.display ~id:"text-entry" ~handlers:[ "text_changed", (fun v -> send_text (Option.value ~default:"" v)); "shout", (fun _ -> let current = Note.S.value text_s in send_shout (String.uppercase_ascii current)); ] (text_entry_view "hello world") let () = Widget.display ~id:"text-result" ~handlers:[] (result_view "") let _logr_text = Note.S.log text_s (fun _ -> ()) let () = Note.Logr.hold _logr_text let _logr4 = Note.S.log (Note.S.map result_view shouted) (Widget.update ~id:"text-result") let () = Note.Logr.hold _logr4