├── test-ocaml ├── tester.ml ├── test_client.ml ├── test_client_btn_update_span.ml ├── test_client_counter.ml ├── test_client_counter_debug_beginner.ml ├── test_client_on_with_options.ml ├── test_client_http_task.ml ├── test_client_counter_debug_standard.ml ├── test_client_counter_debug_program.ml ├── index.html ├── test_client_attribute_removal.ml └── test_client_drag.ml ├── test-reason ├── tester.re ├── test_client.re ├── test_client_btn_update_span.re ├── test_client_counter.re ├── test_client_counter_debug_beginner.re ├── test_client_http_task.re ├── test_client_counter_debug_standard.re ├── test_client_counter_debug_program.re ├── test_client_on_with_options.re ├── test_client_attribute_removal.re └── test_client_drag.re ├── src-ocaml ├── tea_svg_events.ml ├── web_date.ml ├── web_formdata.ml ├── web_json.ml ├── web.ml ├── web_event.ml ├── tea_html_cmds.ml ├── tea.ml ├── web_document.ml ├── tea_promise.ml ├── tea_mouse.ml ├── tea_program.ml ├── web_window_localstorage.ml ├── tea_cmd.ml ├── web_window_history.ml ├── tea_time.ml ├── tea_animationframe.ml ├── tea_result.ml ├── web_location.ml ├── tea_navigation.ml ├── tea_ex.ml ├── web_window.ml ├── tea_random.ml ├── web_node.ml └── tea_sub.ml ├── src-reason ├── tea_svg_events.re ├── web_date.re ├── web_formdata.re ├── web.re ├── web_json.re ├── tea_html_cmds.re ├── web_event.re ├── tea.re ├── web_document.re ├── tea_program.re ├── tea_time.re ├── tea_mouse.re ├── web_window_localstorage.re ├── tea_animationframe.re ├── tea_cmd.re ├── tea_result.re ├── web_window_history.re ├── tea_ex.re ├── web_location.re ├── tea_navigation.re ├── web_window.re ├── tea_random.re ├── tea_sub.re ├── web_node.re └── tea_task.re ├── .npmignore ├── .gitignore ├── lib └── js │ ├── test-ocaml │ ├── tester.js │ ├── test_client.js │ ├── test_client_btn_update_span.js │ ├── test_client_counter.js │ ├── test_client_counter_debug_beginner.js │ ├── test_client_http_task.js │ ├── test_client_counter_debug_standard.js │ ├── test_client_counter_debug_program.js │ ├── test_client_attribute_removal.js │ └── test_client_on_with_options.js │ └── src-ocaml │ ├── web_event.js │ ├── tea_svg_events.js │ ├── web_date.js │ ├── web_formdata.js │ ├── tea_program.js │ ├── web.js │ ├── tea_html_cmds.js │ ├── tea.js │ ├── web_document.js │ ├── web_json.js │ ├── web_window_localstorage.js │ ├── web_window_history.js │ ├── tea_time.js │ ├── tea_promise.js │ ├── tea_cmd.js │ ├── tea_animationframe.js │ ├── tea_mouse.js │ ├── web_window.js │ ├── tea_result.js │ ├── web_location.js │ ├── web_node.js │ ├── tea_ex.js │ ├── tea_navigation.js │ ├── tea_random.js │ └── tea_sub.js ├── .travis.yml ├── bsconfig.json ├── bin └── dual-syntax ├── DCO.md ├── LICENSE ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── package.json └── doc └── integration_react-bs-tea.md /test-ocaml/tester.ml: -------------------------------------------------------------------------------- 1 | let a = 42 2 | -------------------------------------------------------------------------------- /test-reason/tester.re: -------------------------------------------------------------------------------- 1 | let a = 42; 2 | -------------------------------------------------------------------------------- /src-ocaml/tea_svg_events.ml: -------------------------------------------------------------------------------- 1 | (* open Vdom *) 2 | -------------------------------------------------------------------------------- /src-reason/tea_svg_events.re: -------------------------------------------------------------------------------- 1 | /* open Vdom */ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.merlin 3 | /lib/bs 4 | /lib/js/test 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.merlin 3 | /lib/bs 4 | /lib/js/test 5 | /lib/ocaml 6 | npm-debug.log 7 | 8 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/tester.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | var a = 42; 6 | 7 | exports.a = a; 8 | /* No side effect */ 9 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_event.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ 3 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_svg_events.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | before_script: 5 | - npm install bs-platform 6 | script: 7 | - npm run build:all 8 | cache: 9 | directories: 10 | - "node_modules" 11 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_date.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function now(param) { 6 | return Date.now(); 7 | } 8 | 9 | exports.now = now; 10 | /* No side effect */ 11 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_formdata.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function append(key, value, f) { 6 | return f.append(key, value); 7 | } 8 | 9 | exports.append = append; 10 | /* No side effect */ 11 | -------------------------------------------------------------------------------- /src-reason/web_date.re: -------------------------------------------------------------------------------- 1 | type t = Js.t({.}); 2 | 3 | type date_obj = {. [@bs.meth] "now": unit => float}; 4 | 5 | [@bs.new] external create_date : unit => t = "Date"; 6 | 7 | [@bs.val] external date_obj : date_obj = "Date"; 8 | 9 | let now = () => date_obj##now(); 10 | -------------------------------------------------------------------------------- /src-ocaml/web_date.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type t = < 4 | > Js.t 5 | 6 | 7 | type date_obj = < 8 | now : unit -> float [@bs.meth]; 9 | > Js.t 10 | 11 | 12 | external create_date : unit -> t = "Date" [@@bs.new] 13 | 14 | external date_obj : date_obj = "Date" [@@bs.val] 15 | 16 | 17 | let now () = date_obj##now () 18 | -------------------------------------------------------------------------------- /src-ocaml/web_formdata.ml: -------------------------------------------------------------------------------- 1 | 2 | class type _formdata = object 3 | method append : string -> string -> unit 4 | (* method append_blob : string -> Web_blob.t -> string -> unit *) 5 | end [@bs] 6 | type t = _formdata Js.t 7 | 8 | external create : unit -> t = "FormData" [@@bs.new] 9 | 10 | let append key value f = f##append key value 11 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bucklescript-tea", 3 | "version": "0.15.0", 4 | "bsc-flags": ["-bs-cross-module-opt"], 5 | "package-specs": ["commonjs"], 6 | "sources": [ 7 | "src-ocaml", 8 | { 9 | "dir": "test-ocaml", 10 | "type": "dev" 11 | } 12 | ], 13 | "warnings": { 14 | "error" : true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src-reason/web_formdata.re: -------------------------------------------------------------------------------- 1 | class type _formdata = 2 | [@bs] 3 | { 4 | pub append: (string, string) => unit 5 | }; 6 | /* method append_blob : string -> Web_blob.t -> string -> unit */ 7 | 8 | type t = Js.t(_formdata); 9 | 10 | [@bs.new] external create : unit => t = "FormData"; 11 | 12 | let append = (key, value, f) => f##append(key, value); 13 | -------------------------------------------------------------------------------- /src-ocaml/web_json.ml: -------------------------------------------------------------------------------- 1 | 2 | include Js.Json 3 | 4 | type nothingYet 5 | external stringify : 't -> nothingYet Js.null -> int -> string = "JSON.stringify" [@@bs.val] 6 | 7 | let string_of_json ?(indent=2) value = 8 | match Js.Undefined.toOption value with 9 | | None -> "undefined" 10 | | Some v -> 11 | try stringify v Js.Null.empty indent 12 | with _ -> "" 13 | 14 | let of_type (type a) (_v : a kind) (x : a) : t = 15 | Obj.magic x 16 | 17 | let null : Js_types.null_val = Obj.magic Js.null 18 | -------------------------------------------------------------------------------- /src-reason/web.re: -------------------------------------------------------------------------------- 1 | module Event = Web_event; 2 | 3 | module Node = Web_node; 4 | 5 | module Document = Web_document; 6 | 7 | module Date = Web_date; 8 | 9 | module Window = Web_window; 10 | 11 | module Location = Web_location; 12 | 13 | module Json = Web_json; 14 | 15 | module XMLHttpRequest = Web_xmlhttprequest; 16 | 17 | module FormData = Web_formdata; 18 | 19 | let polyfills = () => { 20 | let () = Node.remove_polyfill(); 21 | let () = Window.requestAnimationFrame_polyfill(); 22 | (); 23 | }; 24 | -------------------------------------------------------------------------------- /src-reason/web_json.re: -------------------------------------------------------------------------------- 1 | include Js.Json; 2 | 3 | type nothingYet; 4 | 5 | [@bs.val] 6 | external stringify : ('t, Js.null(nothingYet), int) => string = 7 | "JSON.stringify"; 8 | 9 | let string_of_json = (~indent=2, value) => 10 | switch (Js.Undefined.toOption(value)) { 11 | | None => "undefined" 12 | | Some(v) => 13 | try (stringify(v, Js.Null.empty, indent)) { 14 | | _ => "" 15 | } 16 | }; 17 | 18 | let of_type = (type a, _v: kind(a), x: a) : t => Obj.magic(x); 19 | 20 | let null: Js_types.null_val = Obj.magic(Js.null); 21 | -------------------------------------------------------------------------------- /src-ocaml/web.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | module Event = Web_event 4 | 5 | 6 | module Node = Web_node 7 | 8 | 9 | module Document = Web_document 10 | 11 | 12 | module Date = Web_date 13 | 14 | 15 | module Window = Web_window 16 | 17 | 18 | module Location = Web_location 19 | 20 | 21 | module Json = Web_json 22 | 23 | 24 | module XMLHttpRequest = Web_xmlhttprequest 25 | 26 | module FormData = Web_formdata 27 | 28 | 29 | let polyfills () = 30 | let () = Node.remove_polyfill () in 31 | let () = Window.requestAnimationFrame_polyfill () in 32 | () 33 | -------------------------------------------------------------------------------- /src-reason/tea_html_cmds.re: -------------------------------------------------------------------------------- 1 | let focus = id => 2 | Tea_cmd.call(_enqueue => { 3 | let ecb = _ => 4 | switch (Js.Nullable.toOption(Web.Document.getElementById(id))) { 5 | | None => Js.log(("Attempted to focus a non-existant element of: ", id)) 6 | | Some(elem) => Web.Node.focus(elem) 7 | }; 8 | 9 | /* One to get out of the current render frame*/ 10 | let cb = _ => ignore(Web.Window.requestAnimationFrame(ecb)); 11 | /* And another to properly focus */ 12 | ignore(Web.Window.requestAnimationFrame(cb)); 13 | (); 14 | }); 15 | -------------------------------------------------------------------------------- /test-reason/test_client.re: -------------------------------------------------------------------------------- 1 | let counter = Test_client_counter.(main); 2 | let counter_debug_beginner = Test_client_counter_debug_beginner.(main); 3 | let counter_debug_standard = Test_client_counter_debug_standard.(main); 4 | let counter_debug_program = Test_client_counter_debug_program.(main); 5 | let btn_update_span = Test_client_btn_update_span.(main); 6 | let attribute_removal = Test_client_attribute_removal.(main); 7 | let drag = Test_client_drag.(main); 8 | let on_with_options = Test_client_on_with_options.(main); 9 | let http_task = Test_client_http_task.(main); 10 | -------------------------------------------------------------------------------- /src-ocaml/web_event.ml: -------------------------------------------------------------------------------- 1 | (* type target = < 2 | value : string Js.undefined [@bs.get]; 3 | > Js.t *) 4 | 5 | type 'node t = < 6 | target : 'node Js.undefined [@bs.get]; 7 | keyCode : int [@bs.get]; 8 | preventDefault : unit -> unit [@bs.meth]; 9 | stopPropagation : unit -> unit [@bs.meth]; 10 | > Js.t 11 | 12 | type 'node cb = 'node t -> unit [@bs] 13 | 14 | type options = bool (* false | true (* TODO: Define a javascript record as another option *) *) 15 | 16 | 17 | type popstateEvent = < 18 | > Js.t 19 | 20 | type popstateCb = popstateEvent -> unit [@bs] 21 | -------------------------------------------------------------------------------- /src-reason/web_event.re: -------------------------------------------------------------------------------- 1 | /* type target = < 2 | value : string Js.undefined [@bs.get]; 3 | > Js.t */ 4 | type t('node) = { 5 | . 6 | [@bs.get] "target": Js.undefined('node), 7 | [@bs.get] "keyCode": int, 8 | [@bs.meth] "preventDefault": unit => unit, 9 | [@bs.meth] "stopPropagation": unit => unit, 10 | }; 11 | 12 | type cb('node) = (. t('node)) => unit; 13 | 14 | type options = bool; /* false | true (* TODO: Define a javascript record as another option *) */ 15 | 16 | type popstateEvent = Js.t({.}); 17 | 18 | type popstateCb = (. popstateEvent) => unit; 19 | -------------------------------------------------------------------------------- /src-ocaml/tea_html_cmds.ml: -------------------------------------------------------------------------------- 1 | let focus id = 2 | Tea_cmd.call 3 | (fun _enqueue -> 4 | let ecb _ = 5 | match Js.Nullable.toOption (Web.Document.getElementById id) with 6 | | None -> 7 | Js.log ("Attempted to focus a non-existant element of: ", id) 8 | | Some elem -> Web.Node.focus elem 9 | in 10 | (* One to get out of the current render frame*) 11 | let cb _ = ignore (Web.Window.requestAnimationFrame ecb) in 12 | (* And another to properly focus *) 13 | ignore (Web.Window.requestAnimationFrame cb); 14 | ()) 15 | 16 | 17 | -------------------------------------------------------------------------------- /test-ocaml/test_client.ml: -------------------------------------------------------------------------------- 1 | let counter = let open Test_client_counter in main 2 | let counter_debug_beginner = let open Test_client_counter_debug_beginner in main 3 | let counter_debug_standard = let open Test_client_counter_debug_standard in main 4 | let counter_debug_program = let open Test_client_counter_debug_program in main 5 | let btn_update_span = let open Test_client_btn_update_span in main 6 | let attribute_removal = let open Test_client_attribute_removal in main 7 | let drag = let open Test_client_drag in main 8 | let on_with_options = let open Test_client_on_with_options in main 9 | let http_task = let open Test_client_http_task in main 10 | -------------------------------------------------------------------------------- /src-ocaml/tea.ml: -------------------------------------------------------------------------------- 1 | (* TODO: Remove this once Bucklescript upgrade to OCaml 4.03+ as that version include result *) 2 | module Result = Tea_result 3 | 4 | module Cmd = Tea_cmd 5 | 6 | module Sub = Tea_sub 7 | 8 | module App = Tea_app 9 | 10 | module Debug = Tea_debug 11 | 12 | module Html = Tea_html 13 | 14 | module Html2 = Tea_html2 15 | 16 | module Svg = Tea_svg 17 | 18 | module Task = Tea_task 19 | 20 | module Program = Tea_program 21 | 22 | module Time = Tea_time 23 | 24 | module Json = Tea_json 25 | 26 | module Navigation = Tea_navigation 27 | 28 | module Random = Tea_random 29 | 30 | module AnimationFrame = Tea_animationframe 31 | 32 | module Mouse = Tea_mouse 33 | 34 | module Http = Tea_http 35 | 36 | module Ex = Tea_ex 37 | -------------------------------------------------------------------------------- /src-reason/tea.re: -------------------------------------------------------------------------------- 1 | /* TODO: Remove this once Bucklescript upgrade to OCaml 4.03+ as that version include result */ 2 | module Result = Tea_result; 3 | 4 | module Cmd = Tea_cmd; 5 | 6 | module Sub = Tea_sub; 7 | 8 | module App = Tea_app; 9 | 10 | module Debug = Tea_debug; 11 | 12 | module Html = Tea_html; 13 | 14 | module Html2 = Tea_html2; 15 | 16 | module Svg = Tea_svg; 17 | 18 | module Task = Tea_task; 19 | 20 | module Program = Tea_program; 21 | 22 | module Time = Tea_time; 23 | 24 | module Json = Tea_json; 25 | 26 | module Navigation = Tea_navigation; 27 | 28 | module Random = Tea_random; 29 | 30 | module AnimationFrame = Tea_animationframe; 31 | 32 | module Mouse = Tea_mouse; 33 | 34 | module Http = Tea_http; 35 | 36 | module Ex = Tea_ex; 37 | -------------------------------------------------------------------------------- /test-ocaml/test_client_btn_update_span.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | open Tea.Html 3 | 4 | type msg = 5 | | Trigger 6 | [@@bs.deriving {accessors}] 7 | 8 | type model = (string option * string option) 9 | 10 | let update' model = function 11 | | Trigger -> 12 | let (left, _) = model in 13 | (left, Some "right") 14 | 15 | let render_model = function 16 | | (Some _, Some _) -> 17 | input' [value "This should be on screen"] [] 18 | | _ -> 19 | span [] [text "nothing"] 20 | 21 | let view' model = 22 | div [] 23 | [ button [onClick Trigger] [text "trigger rerender"] 24 | ; render_model model 25 | ] 26 | 27 | 28 | 29 | let main = 30 | beginnerProgram { 31 | model = (Some "left", None); 32 | update = update'; 33 | view = view' 34 | } 35 | -------------------------------------------------------------------------------- /bin/dual-syntax: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | git diff --cached --name-only --diff-filter=ACM | while read file; do 6 | if [[ "$file" =~ (\.ml|\.mli)$ ]]; then 7 | # Convert from OCaml to ReasonML 8 | newfile=$(echo $file | sed 's/-ocaml/-reason/' | sed 's/\.ml/\.re/') 9 | echo "Converting $file to $newfile" 10 | node_modules/.bin/bsrefmt --parse=ml -p re $file >$newfile 11 | git add "$newfile" 12 | elif [[ "$file" =~ (\.re|\.rei)$ ]]; then 13 | # Convert from ReasonML to OCaml 14 | newfile=$(echo $file | sed 's/-reason/-ocaml/' | sed 's/\.re/\.ml/') 15 | echo "Converting $file to $newfile" 16 | node_modules/.bin/bsrefmt --parse=re -p ml $file >$newfile 17 | git add "$newfile" 18 | else 19 | [[ "$VERBOSE" = "" ]] || echo "Leaving $file as is" 20 | fi 21 | done 22 | 23 | -------------------------------------------------------------------------------- /test-reason/test_client_btn_update_span.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | open Tea.Html; 4 | 5 | [@bs.deriving {accessors: accessors}] 6 | type msg = 7 | | Trigger; 8 | 9 | type model = (option(string), option(string)); 10 | 11 | let update' = model => 12 | fun 13 | | Trigger => { 14 | let (left, _) = model; 15 | (left, Some("right")); 16 | }; 17 | 18 | let render_model = 19 | fun 20 | | (Some(_), Some(_)) => input'([value("This should be on screen")], []) 21 | | _ => span([], [text("nothing")]); 22 | 23 | let view' = model => 24 | div( 25 | [], 26 | [ 27 | button([onClick(Trigger)], [text("trigger rerender")]), 28 | render_model(model), 29 | ], 30 | ); 31 | 32 | let main = 33 | beginnerProgram({ 34 | model: (Some("left"), None), 35 | update: update', 36 | view: view', 37 | }); 38 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_program.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Curry = require("bs-platform/lib/js/curry.js"); 5 | var Caml_option = require("bs-platform/lib/js/caml_option.js"); 6 | 7 | function spawn(initState, update, shutdown) { 8 | var state = { 9 | contents: Caml_option.some(initState) 10 | }; 11 | return (function (procMsg) { 12 | var match = state.contents; 13 | if (match !== undefined) { 14 | var model = Caml_option.valFromOption(match); 15 | if (procMsg) { 16 | state.contents = Curry._2(update, model, procMsg[0]); 17 | return /* () */0; 18 | } else { 19 | Curry._1(shutdown, model); 20 | state.contents = undefined; 21 | return /* () */0; 22 | } 23 | } else { 24 | return /* () */0; 25 | } 26 | }); 27 | } 28 | 29 | var testing1 = 42; 30 | 31 | exports.spawn = spawn; 32 | exports.testing1 = testing1; 33 | /* No side effect */ 34 | -------------------------------------------------------------------------------- /test-ocaml/test_client_counter.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | open Tea.Html 3 | 4 | type msg = 5 | | Increment 6 | | Decrement 7 | | Reset 8 | | Set of int 9 | 10 | let update model = function 11 | | Increment -> model + 1 12 | | Decrement -> model - 1 13 | | Reset -> 0 14 | | Set v -> v 15 | 16 | 17 | let view_button title msg = 18 | button 19 | [ onClick msg 20 | ] 21 | [ text title 22 | ] 23 | 24 | let view model = 25 | div 26 | [] 27 | [ span 28 | [ style "text-weight" "bold" ] 29 | [ text (string_of_int model) ] 30 | ; br [] 31 | ; view_button "Increment" ( 32 | if model >= 3 then 33 | Decrement 34 | else 35 | Increment 36 | ) 37 | ; br [] 38 | ; view_button "Decrement" Decrement 39 | ; br [] 40 | ; view_button "Set to 42" (Set 42) 41 | ; br [] 42 | ; if model <> 0 then view_button "Reset" Reset else noNode 43 | ] 44 | 45 | 46 | let main = 47 | beginnerProgram { 48 | model = 4; 49 | update; 50 | view; 51 | } 52 | -------------------------------------------------------------------------------- /test-reason/test_client_counter.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | open Tea.Html; 4 | 5 | type msg = 6 | | Increment 7 | | Decrement 8 | | Reset 9 | | Set(int); 10 | 11 | let update = model => 12 | fun 13 | | Increment => model + 1 14 | | Decrement => model - 1 15 | | Reset => 0 16 | | Set(v) => v; 17 | 18 | let view_button = (title, msg) => button([onClick(msg)], [text(title)]); 19 | 20 | let view = model => 21 | div( 22 | [], 23 | [ 24 | span([style("text-weight", "bold")], [text(string_of_int(model))]), 25 | br([]), 26 | view_button( 27 | "Increment", 28 | if (model >= 3) { 29 | Decrement; 30 | } else { 31 | Increment; 32 | }, 33 | ), 34 | br([]), 35 | view_button("Decrement", Decrement), 36 | br([]), 37 | view_button("Set to 42", Set(42)), 38 | br([]), 39 | if (model != 0) { 40 | view_button("Reset", Reset); 41 | } else { 42 | noNode; 43 | }, 44 | ], 45 | ); 46 | 47 | let main = beginnerProgram({model: 4, update, view}); 48 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Web_node = require("./web_node.js"); 5 | var Web_window = require("./web_window.js"); 6 | 7 | function polyfills(param) { 8 | Web_node.remove_polyfill(/* () */0); 9 | Web_window.requestAnimationFrame_polyfill(/* () */0); 10 | return /* () */0; 11 | } 12 | 13 | var $$Event = /* alias */0; 14 | 15 | var $$Node = /* alias */0; 16 | 17 | var $$Document = /* alias */0; 18 | 19 | var $$Date = /* alias */0; 20 | 21 | var $$Window = /* alias */0; 22 | 23 | var $$Location = /* alias */0; 24 | 25 | var Json = /* alias */0; 26 | 27 | var $$XMLHttpRequest = /* alias */0; 28 | 29 | var $$FormData = /* alias */0; 30 | 31 | exports.$$Event = $$Event; 32 | exports.$$Node = $$Node; 33 | exports.$$Document = $$Document; 34 | exports.$$Date = $$Date; 35 | exports.$$Window = $$Window; 36 | exports.$$Location = $$Location; 37 | exports.Json = Json; 38 | exports.$$XMLHttpRequest = $$XMLHttpRequest; 39 | exports.$$FormData = $$FormData; 40 | exports.polyfills = polyfills; 41 | /* No side effect */ 42 | -------------------------------------------------------------------------------- /DCO.md: -------------------------------------------------------------------------------- 1 | Developer's Certificate of Origin 1.1 2 | 3 | By making a contribution to this project, I certify that: 4 | 5 | 1. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or 6 | 7 | 2. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or 8 | 9 | 3. The contribution was provided directly to me by some other person who certified (1), (2) or (3) and I have not modified it. 10 | 11 | 4. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. 12 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_html_cmds.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | 6 | function focus(id) { 7 | return /* EnqueueCall */Block.__(2, [(function (_enqueue) { 8 | var ecb = function (param) { 9 | var match = document.getElementById(id); 10 | if (match == null) { 11 | console.log(/* tuple */[ 12 | "Attempted to focus a non-existant element of: ", 13 | id 14 | ]); 15 | return /* () */0; 16 | } else { 17 | return match.focus(); 18 | } 19 | }; 20 | var cb = function (param) { 21 | window.requestAnimationFrame(ecb); 22 | return /* () */0; 23 | }; 24 | window.requestAnimationFrame(cb); 25 | return /* () */0; 26 | })]); 27 | } 28 | 29 | exports.focus = focus; 30 | /* No side effect */ 31 | -------------------------------------------------------------------------------- /src-ocaml/web_document.ml: -------------------------------------------------------------------------------- 1 | 2 | (* TODO: Polyfill document if it is missing, like on node or in native *) 3 | 4 | type t = < 5 | body : Web_node.t [@bs.get]; 6 | createElement : string -> Web_node.t [@bs.meth]; 7 | createElementNS : string -> string -> Web_node.t [@bs.meth]; 8 | createComment : string -> Web_node.t [@bs.meth]; 9 | createTextNode : string -> Web_node.t [@bs.meth]; 10 | getElementById : string -> Web_node.t Js.null_undefined [@bs.meth]; 11 | location : Web_location.t [@bs.get]; 12 | > Js.t 13 | 14 | external document : t = "document" [@@bs.val] 15 | 16 | let body () = document##body 17 | 18 | let createElement typ = document##createElement typ 19 | 20 | let createElementNS namespace key = document##createElementNS namespace key 21 | 22 | let createComment text = document##createComment text 23 | 24 | let createTextNode text = document##createTextNode text 25 | 26 | let getElementById id = document##getElementById id 27 | 28 | let createElementNsOptional namespace tagName = 29 | match namespace with 30 | | "" -> document##createElement tagName 31 | | ns -> document##createElementNS ns tagName 32 | 33 | let location () = document##location 34 | -------------------------------------------------------------------------------- /src-ocaml/tea_promise.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | let cmd promise tagger = 4 | let open Vdom in 5 | Tea_cmd.call (function callbacks -> 6 | let _ = promise 7 | |> Js.Promise.then_ (function res -> 8 | match tagger res with 9 | | Some msg -> 10 | let () = !callbacks.enqueue msg in 11 | Js.Promise.resolve () 12 | | None -> Js.Promise.resolve () 13 | ) 14 | in 15 | () 16 | ) 17 | 18 | 19 | let result promise msg = 20 | let open Vdom in 21 | Tea_cmd.call (function callbacks -> 22 | let enq result = 23 | !callbacks.enqueue (msg result) 24 | in 25 | let _ = promise 26 | |> Js.Promise.then_ (function res -> 27 | let resolve = enq (Tea_result.Ok res) in 28 | Js.Promise.resolve resolve 29 | ) 30 | |> Js.Promise.catch (function err -> 31 | let err_to_string err = 32 | {j|$err|j} in 33 | let reject = enq (Tea_result.Error (err_to_string err)) in 34 | Js.Promise.resolve reject 35 | ) 36 | in 37 | () 38 | ) 39 | -------------------------------------------------------------------------------- /src-reason/web_document.re: -------------------------------------------------------------------------------- 1 | /* TODO: Polyfill document if it is missing, like on node or in native */ 2 | type t = { 3 | . 4 | [@bs.get] "body": Web_node.t, 5 | [@bs.meth] "createElement": string => Web_node.t, 6 | [@bs.meth] "createElementNS": (string, string) => Web_node.t, 7 | [@bs.meth] "createComment": string => Web_node.t, 8 | [@bs.meth] "createTextNode": string => Web_node.t, 9 | [@bs.meth] "getElementById": string => Js.null_undefined(Web_node.t), 10 | [@bs.get] "location": Web_location.t, 11 | }; 12 | 13 | [@bs.val] external document : t = "document"; 14 | 15 | let body = () => document##body; 16 | 17 | let createElement = typ => document##createElement(typ); 18 | 19 | let createElementNS = (namespace, key) => 20 | document##createElementNS(namespace, key); 21 | 22 | let createComment = text => document##createComment(text); 23 | 24 | let createTextNode = text => document##createTextNode(text); 25 | 26 | let getElementById = id => document##getElementById(id); 27 | 28 | let createElementNsOptional = (namespace, tagName) => 29 | switch (namespace) { 30 | | "" => document##createElement(tagName) 31 | | ns => document##createElementNS(ns, tagName) 32 | }; 33 | 34 | let location = () => document##location; 35 | -------------------------------------------------------------------------------- /test-ocaml/test_client_counter_debug_beginner.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | open Tea.Html 3 | 4 | type msg = 5 | | Increment 6 | | Decrement 7 | | Reset 8 | | Set of int 9 | 10 | let string_of_msg = function 11 | | Increment -> "Increment" 12 | | Decrement -> "Decrement" 13 | | Reset -> "Reset" 14 | | Set _ -> "Set" 15 | 16 | let update model = function 17 | | Increment -> model + 1 18 | | Decrement -> model - 1 19 | | Reset -> 0 20 | | Set v -> v 21 | 22 | 23 | let view_button title msg = 24 | button 25 | [ onClick msg 26 | ] 27 | [ text title 28 | ] 29 | 30 | let view model = 31 | div 32 | [] 33 | [ span 34 | [ style "text-weight" "bold" ] 35 | [ text (string_of_int model) ] 36 | ; br [] 37 | ; view_button "Increment" ( 38 | if model >= 3 then 39 | Decrement 40 | else 41 | Increment 42 | ) 43 | ; br [] 44 | ; view_button "Decrement" Decrement 45 | ; br [] 46 | ; view_button "Set to 42" (Set 42) 47 | ; br [] 48 | ; if model <> 0 then view_button "Reset" Reset else noNode 49 | ] 50 | 51 | let main = 52 | Tea.Debug.beginnerProgram { 53 | model = 4; 54 | update; 55 | view; 56 | } string_of_msg 57 | -------------------------------------------------------------------------------- /src-ocaml/tea_mouse.ml: -------------------------------------------------------------------------------- 1 | 2 | type position = { 3 | x : int; 4 | y : int; 5 | } 6 | 7 | 8 | let position = 9 | let open Tea_json.Decoder in 10 | map2 (fun x y -> {x; y}) 11 | (field "pageX" int) 12 | (field "pageY" int) 13 | 14 | 15 | let registerGlobal name key tagger = 16 | let open Vdom in 17 | let enableCall callbacks_base = 18 | let callbacks = ref callbacks_base in 19 | let fn = fun ev -> 20 | let open Tea_json.Decoder in 21 | let open Tea_result in 22 | match decodeEvent position ev with 23 | | Error _ -> None 24 | | Ok pos -> Some (tagger pos) in 25 | let handler = EventHandlerCallback (key, fn) in 26 | let elem = Web_node.document_node in 27 | let cache = eventHandler_Register callbacks elem name handler in 28 | fun () -> 29 | let _ = eventHandler_Unregister elem name cache in 30 | () 31 | in Tea_sub.registration key enableCall 32 | 33 | let clicks ?(key="") tagger = 34 | registerGlobal "click" key tagger 35 | 36 | let moves ?(key="") tagger = 37 | registerGlobal "mousemove" key tagger 38 | 39 | let downs ?(key="") tagger = 40 | registerGlobal "mousedown" key tagger 41 | 42 | let ups ?(key="") tagger = 43 | registerGlobal "mouseup" key tagger 44 | -------------------------------------------------------------------------------- /src-reason/tea_program.re: -------------------------------------------------------------------------------- 1 | type processMsg('msg) = 2 | | PushMsg('msg) 3 | | Kill; 4 | 5 | let spawn = (initState, update, shutdown) => { 6 | let state = ref(Some(initState)); 7 | let onMessage = procMsg => 8 | switch (state^) { 9 | | None => () 10 | | Some(model) => 11 | switch (procMsg) { 12 | | PushMsg(msg) => 13 | let () = state := update(model, msg); 14 | (); 15 | | Kill => 16 | let () = shutdown(model); 17 | let () = state := None; 18 | (); 19 | } 20 | }; 21 | onMessage; 22 | }; 23 | 24 | /* let testing0 = 25 | let s = spawn 42 (fun model -> let () = Js.log model in function 26 | | `Inc -> Some (model + 1) 27 | | `Dec -> Some (model - 1) 28 | ) (fun _ -> ()) in 29 | let () = s (PushMsg `Dec) in 30 | let () = s (PushMsg `Dec) in 31 | let () = s Kill in 32 | let () = s (PushMsg `Dec) in 33 | () */ 34 | module type Process = { 35 | /* This module should be `import`ed into a module that will become a persistent process. 36 | That process should have a handleMsg callback to handle its own message types. 37 | It should call itself 38 | */ 39 | type msg; 40 | let handleMsg: msg => unit; 41 | }; 42 | 43 | let testing1 = 42; 44 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | var Result = /* alias */0; 6 | 7 | var Cmd = /* alias */0; 8 | 9 | var Sub = /* alias */0; 10 | 11 | var App = /* alias */0; 12 | 13 | var Debug = /* alias */0; 14 | 15 | var Html = /* alias */0; 16 | 17 | var Html2 = /* alias */0; 18 | 19 | var Svg = /* alias */0; 20 | 21 | var Task = /* alias */0; 22 | 23 | var Program = /* alias */0; 24 | 25 | var Time = /* alias */0; 26 | 27 | var Json = /* alias */0; 28 | 29 | var Navigation = /* alias */0; 30 | 31 | var Random = /* alias */0; 32 | 33 | var AnimationFrame = /* alias */0; 34 | 35 | var Mouse = /* alias */0; 36 | 37 | var Http = /* alias */0; 38 | 39 | var Ex = /* alias */0; 40 | 41 | exports.Result = Result; 42 | exports.Cmd = Cmd; 43 | exports.Sub = Sub; 44 | exports.App = App; 45 | exports.Debug = Debug; 46 | exports.Html = Html; 47 | exports.Html2 = Html2; 48 | exports.Svg = Svg; 49 | exports.Task = Task; 50 | exports.Program = Program; 51 | exports.Time = Time; 52 | exports.Json = Json; 53 | exports.Navigation = Navigation; 54 | exports.Random = Random; 55 | exports.AnimationFrame = AnimationFrame; 56 | exports.Mouse = Mouse; 57 | exports.Http = Http; 58 | exports.Ex = Ex; 59 | /* No side effect */ 60 | -------------------------------------------------------------------------------- /src-reason/tea_time.re: -------------------------------------------------------------------------------- 1 | type t = float; 2 | 3 | /* type 'msg mySub = 4 | | Every of t * (t -> 'msg) 5 | 6 | 7 | type 'msg myCmd = 8 | | Delay of t * (unit -> 'msg) */ 9 | 10 | let every = (~key, interval, tagger) => { 11 | open Vdom; 12 | let enableCall = callbacks => { 13 | let id = 14 | Web.Window.setInterval( 15 | () => callbacks.enqueue(tagger(Web.Date.now())), 16 | interval, 17 | ); 18 | /* let () = Js.log ("Time.every", "enable", interval, tagger, callbacks) in */ 19 | () => 20 | /* let () = Js.log ("Time.every", "disable", id, interval, tagger, callbacks) in */ 21 | Web.Window.clearTimeout(id); 22 | }; 23 | Tea_sub.registration(key, enableCall); 24 | }; 25 | 26 | let delay = (msTime, msg) => 27 | Tea_cmd.call(callbacks => { 28 | let _unhandledID = 29 | Web.Window.setTimeout(() => Vdom.(callbacks^.enqueue(msg)), msTime); 30 | (); 31 | }); 32 | 33 | /* Generic Helpers */ 34 | 35 | let millisecond = 1.0; 36 | 37 | let second = 1000.0 *. millisecond; 38 | 39 | let minute = 60.0 *. second; 40 | 41 | let hour = 60.0 *. minute; 42 | 43 | let inMilliseconds = t => t; 44 | 45 | let inSeconds = t => t /. second; 46 | 47 | let inMinutes = t => t /. minute; 48 | 49 | let inHours = t => t /. hour; 50 | -------------------------------------------------------------------------------- /test-reason/test_client_counter_debug_beginner.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | open Tea.Html; 4 | 5 | type msg = 6 | | Increment 7 | | Decrement 8 | | Reset 9 | | Set(int); 10 | 11 | let string_of_msg = 12 | fun 13 | | Increment => "Increment" 14 | | Decrement => "Decrement" 15 | | Reset => "Reset" 16 | | Set(_) => "Set"; 17 | 18 | let update = model => 19 | fun 20 | | Increment => model + 1 21 | | Decrement => model - 1 22 | | Reset => 0 23 | | Set(v) => v; 24 | 25 | let view_button = (title, msg) => button([onClick(msg)], [text(title)]); 26 | 27 | let view = model => 28 | div( 29 | [], 30 | [ 31 | span([style("text-weight", "bold")], [text(string_of_int(model))]), 32 | br([]), 33 | view_button( 34 | "Increment", 35 | if (model >= 3) { 36 | Decrement; 37 | } else { 38 | Increment; 39 | }, 40 | ), 41 | br([]), 42 | view_button("Decrement", Decrement), 43 | br([]), 44 | view_button("Set to 42", Set(42)), 45 | br([]), 46 | if (model != 0) { 47 | view_button("Reset", Reset); 48 | } else { 49 | noNode; 50 | }, 51 | ], 52 | ); 53 | 54 | let main = 55 | Tea.Debug.beginnerProgram({model: 4, update, view}, string_of_msg); 56 | -------------------------------------------------------------------------------- /src-ocaml/tea_program.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type 'msg processMsg = 4 | | PushMsg of 'msg 5 | | Kill 6 | 7 | let spawn initState update shutdown = 8 | let state = ref (Some initState) in 9 | let onMessage procMsg = 10 | match !state with 11 | | None -> () 12 | | Some model -> 13 | ( match procMsg with 14 | | PushMsg msg -> 15 | let () = state := (update model msg) in 16 | () 17 | | Kill -> 18 | let () = shutdown model in 19 | let () = state := None in 20 | () 21 | ) in 22 | onMessage 23 | 24 | 25 | 26 | 27 | 28 | (* let testing0 = 29 | let s = spawn 42 (fun model -> let () = Js.log model in function 30 | | `Inc -> Some (model + 1) 31 | | `Dec -> Some (model - 1) 32 | ) (fun _ -> ()) in 33 | let () = s (PushMsg `Dec) in 34 | let () = s (PushMsg `Dec) in 35 | let () = s Kill in 36 | let () = s (PushMsg `Dec) in 37 | () *) 38 | 39 | 40 | 41 | module type Process = sig 42 | (* This module should be `import`ed into a module that will become a persistent process. 43 | That process should have a handleMsg callback to handle its own message types. 44 | It should call itself 45 | *) 46 | 47 | type msg 48 | 49 | val handleMsg : msg -> unit 50 | end 51 | 52 | let testing1 = 42 53 | -------------------------------------------------------------------------------- /src-reason/tea_mouse.re: -------------------------------------------------------------------------------- 1 | type position = { 2 | x: int, 3 | y: int, 4 | }; 5 | 6 | let position = 7 | Tea_json.Decoder.( 8 | map2((x, y) => {x, y}, field("pageX", int), field("pageY", int)) 9 | ); 10 | 11 | let registerGlobal = (name, key, tagger) => { 12 | open Vdom; 13 | let enableCall = callbacks_base => { 14 | let callbacks = ref(callbacks_base); 15 | let fn = ev => 16 | Tea_json.Decoder.( 17 | Tea_result.( 18 | switch (decodeEvent(position, ev)) { 19 | | Error(_) => None 20 | | Ok(pos) => Some(tagger(pos)) 21 | } 22 | ) 23 | ); 24 | let handler = [@implicit_arity] EventHandlerCallback(key, fn); 25 | let elem = Web_node.document_node; 26 | let cache = eventHandler_Register(callbacks, elem, name, handler); 27 | () => { 28 | let _ = eventHandler_Unregister(elem, name, cache); 29 | (); 30 | }; 31 | }; 32 | Tea_sub.registration(key, enableCall); 33 | }; 34 | 35 | let clicks = (~key="", tagger) => registerGlobal("click", key, tagger); 36 | 37 | let moves = (~key="", tagger) => registerGlobal("mousemove", key, tagger); 38 | 39 | let downs = (~key="", tagger) => registerGlobal("mousedown", key, tagger); 40 | 41 | let ups = (~key="", tagger) => registerGlobal("mouseup", key, tagger); 42 | -------------------------------------------------------------------------------- /test-ocaml/test_client_on_with_options.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | 3 | type msg = 4 | | Click 5 | | Set_value of int 6 | [@@bs.deriving {accessors}] 7 | 8 | 9 | let update model = function 10 | | Click -> model + 1 11 | | Set_value n -> n 12 | 13 | 14 | let view model = 15 | let open Tea.Html2 in 16 | let open Tea.Html2.Attributes in 17 | let open Tea.Html2.Events in 18 | let open Tea.Json in 19 | let clientX = Decoder.field "clientX" Decoder.int in 20 | div [] (List.map (fun e -> div [] [e]) [ 21 | model |> string_of_int |> text; 22 | button [onClick Click] [text "onClick"]; 23 | button [on ~key:"" "click" (Decoder.succeed Click)] [text "on \"click\""]; 24 | a [href "https://www.google.com"] [text "a normal link"]; 25 | a [ 26 | href "https://www.google.com"; 27 | onWithOptions ~key:"" "click" { defaultOptions with preventDefault = true } (Tea.Json.Decoder.succeed Click); 28 | ] [text "a link with prevent default"]; 29 | button [on ~key:"" "click" (Decoder.map set_value clientX)] [text "on \"click\", use clientX value"]; 30 | input' [type' "text"; on ~key:"" "input" (Decoder.map (fun v -> v |> int_of_string |> set_value) targetValue)] []; 31 | ]) 32 | 33 | 34 | let main = 35 | beginnerProgram { 36 | model = 0; 37 | update; 38 | view; 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016-2017 OvermindDL1 (Gabriel Robertson) 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU Lesser General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | In addition to the permissions granted to you by the LGPL, you may combine 9 | or link a "work that uses the Library" with a publicly distributed version 10 | of this file to produce a combined library or application, then distribute 11 | that combined work under the terms of your choosing, with no requirement 12 | to comply with the obligations normally placed on you by section 4 of the 13 | LGPL version 3 (or the corresponding section of a later version of the LGPL 14 | should you choose to use a later version). 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU Lesser General Public License for more details. 20 | 21 | You should have received a copy of the GNU Lesser General Public License 22 | along with this program; if not, write to the Free Software 23 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 24 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_document.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function body(param) { 6 | return document.body; 7 | } 8 | 9 | function createElement(typ) { 10 | return document.createElement(typ); 11 | } 12 | 13 | function createElementNS(namespace, key) { 14 | return document.createElementNS(namespace, key); 15 | } 16 | 17 | function createComment(text) { 18 | return document.createComment(text); 19 | } 20 | 21 | function createTextNode(text) { 22 | return document.createTextNode(text); 23 | } 24 | 25 | function getElementById(id) { 26 | return document.getElementById(id); 27 | } 28 | 29 | function createElementNsOptional(namespace, tagName) { 30 | if (namespace === "") { 31 | return document.createElement(tagName); 32 | } else { 33 | return document.createElementNS(namespace, tagName); 34 | } 35 | } 36 | 37 | function $$location(param) { 38 | return document.location; 39 | } 40 | 41 | exports.body = body; 42 | exports.createElement = createElement; 43 | exports.createElementNS = createElementNS; 44 | exports.createComment = createComment; 45 | exports.createTextNode = createTextNode; 46 | exports.getElementById = getElementById; 47 | exports.createElementNsOptional = createElementNsOptional; 48 | exports.$$location = $$location; 49 | /* No side effect */ 50 | -------------------------------------------------------------------------------- /test-ocaml/test_client_http_task.ml: -------------------------------------------------------------------------------- 1 | open Tea 2 | open Tea.Html 3 | type ('ok,'err) result = ('ok,'err) Tea_result.t 4 | 5 | type msg = 6 | | GotResponse of (string, string) result 7 | | Req 8 | [@@bs.deriving accessors] 9 | 10 | let update model = function 11 | | GotResponse (Ok t) -> t, Cmd.none 12 | | GotResponse (Error t) -> t, Cmd.none 13 | | Req -> model, 14 | Http.getString "https://jsonplaceholder.typicode.com/todos/1" |> Http.toTask |> Task.mapError Http.string_of_error 15 | |> Task.andThen (fun res -> Ex.LocalStorage.setItem "todo-1" res) 16 | |> Task.andThen (fun () -> Http.getString "https://jsonplaceholder.typicode.com/todos/2" |> Http.toTask |> Task.mapError Http.string_of_error) 17 | |> Task.andThen (fun res -> Ex.LocalStorage.setItem "todo-2" res) 18 | |> Task.andThen (fun () -> Task.succeed "both saved") 19 | |> Task.attempt gotResponse 20 | 21 | 22 | let view model = 23 | div [] [ 24 | button [onClick Req] [text "execute"]; 25 | text model; 26 | ] 27 | 28 | let som = function 29 | | GotResponse (Ok _) -> "GotResponse Ok" 30 | | GotResponse (Error _) -> "GotResponse Error" 31 | | Req -> "Req" 32 | 33 | let main = 34 | Tea.Debug.standardProgram { 35 | init = (fun () -> "nothing", Cmd.none); 36 | subscriptions = (fun _ -> Sub.none); 37 | update; 38 | view; 39 | } som 40 | -------------------------------------------------------------------------------- /src-ocaml/web_window_localstorage.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type t = < 4 | length : int [@bs.get]; 5 | clear : unit -> unit [@bs.meth]; 6 | key : int -> string [@bs.meth]; 7 | getItem : string -> string [@bs.meth]; 8 | removeItem : string -> unit [@bs.meth]; 9 | setItem : string -> string -> unit [@bs.meth]; 10 | > Js.t 11 | 12 | let length window = match Js.Undefined.toOption window##localStorage with 13 | | None -> None 14 | | Some localStorage -> Some (localStorage##length) 15 | 16 | 17 | let clear window = match Js.Undefined.toOption window##localStorage with 18 | | None -> None 19 | | Some localStorage -> Some (localStorage##clear ()) 20 | 21 | 22 | let key window idx = match Js.Undefined.toOption window##localStorage with 23 | | None -> None 24 | | Some localStorage -> Some (localStorage##key idx) 25 | 26 | 27 | let getItem window key = match Js.Undefined.toOption window##localStorage with 28 | | None -> None 29 | | Some localStorage -> 30 | try Some (localStorage##getItem key) 31 | with _ -> None 32 | 33 | 34 | let removeItem window key = match Js.Undefined.toOption window##localStorage with 35 | | None -> None 36 | | Some localStorage -> Some (localStorage##removeItem key) 37 | 38 | 39 | let setItem window key value = match Js.Undefined.toOption window##localStorage with 40 | | None -> None 41 | | Some localStorage -> Some (localStorage##setItem key value) 42 | -------------------------------------------------------------------------------- /test-ocaml/test_client_counter_debug_standard.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | open Tea.Html 3 | 4 | type msg = 5 | | Increment 6 | | Decrement 7 | | Reset 8 | | Set of int 9 | 10 | let string_of_msg = function 11 | | Increment -> "Increment" 12 | | Decrement -> "Decrement" 13 | | Reset -> "Reset" 14 | | Set _ -> "Set" 15 | 16 | let init () = 4, Tea.Cmd.none 17 | 18 | let subscriptions _ = Tea.Sub.none 19 | 20 | let update model = function 21 | | Increment -> model + 1, Tea.Cmd.none 22 | | Decrement -> model - 1, Tea.Cmd.none 23 | | Reset -> 0, Tea.Cmd.none 24 | | Set v -> v, Tea.Cmd.none 25 | 26 | 27 | let view_button title msg = 28 | button 29 | [ onClick msg 30 | ] 31 | [ text title 32 | ] 33 | 34 | let view model = 35 | div 36 | [] 37 | [ span 38 | [ style "text-weight" "bold" ] 39 | [ text (string_of_int model) ] 40 | ; br [] 41 | ; view_button "Increment" ( 42 | if model >= 3 then 43 | Decrement 44 | else 45 | Increment 46 | ) 47 | ; br [] 48 | ; view_button "Decrement" Decrement 49 | ; br [] 50 | ; view_button "Set to 42" (Set 42) 51 | ; br [] 52 | ; if model <> 0 then view_button "Reset" Reset else noNode 53 | ] 54 | 55 | let main = 56 | Tea.Debug.standardProgram { 57 | init; 58 | update; 59 | view; 60 | subscriptions; 61 | } string_of_msg 62 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_json.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Js_json = require("bs-platform/lib/js/js_json.js"); 5 | 6 | function string_of_json($staropt$star, value) { 7 | var indent = $staropt$star !== undefined ? $staropt$star : 2; 8 | if (value !== undefined) { 9 | try { 10 | return JSON.stringify(value, null, indent); 11 | } 12 | catch (exn){ 13 | return ""; 14 | } 15 | } else { 16 | return "undefined"; 17 | } 18 | } 19 | 20 | function of_type(_v, x) { 21 | return x; 22 | } 23 | 24 | var classify = Js_json.classify; 25 | 26 | var test = Js_json.test; 27 | 28 | var decodeString = Js_json.decodeString; 29 | 30 | var decodeNumber = Js_json.decodeNumber; 31 | 32 | var decodeObject = Js_json.decodeObject; 33 | 34 | var decodeArray = Js_json.decodeArray; 35 | 36 | var decodeBoolean = Js_json.decodeBoolean; 37 | 38 | var decodeNull = Js_json.decodeNull; 39 | 40 | var $$null = null; 41 | 42 | exports.classify = classify; 43 | exports.test = test; 44 | exports.decodeString = decodeString; 45 | exports.decodeNumber = decodeNumber; 46 | exports.decodeObject = decodeObject; 47 | exports.decodeArray = decodeArray; 48 | exports.decodeBoolean = decodeBoolean; 49 | exports.decodeNull = decodeNull; 50 | exports.string_of_json = string_of_json; 51 | exports.of_type = of_type; 52 | exports.$$null = $$null; 53 | /* No side effect */ 54 | -------------------------------------------------------------------------------- /src-ocaml/tea_cmd.ml: -------------------------------------------------------------------------------- 1 | type 'msg applicationCallbacks = 'msg Vdom.applicationCallbacks 2 | type 'msg t = 3 | | NoCmd: _ t 4 | | Mapper: 5 | ('msg Vdom.applicationCallbacks ref -> 'msgB Vdom.applicationCallbacks ref) 6 | * 'msgB t -> 'msg t 7 | | Batch: 'msg t list -> 'msg t 8 | | EnqueueCall: ('msg applicationCallbacks ref -> unit) -> 'msg t 9 | let none = NoCmd 10 | let batch cmds = ((Batch (cmds))[@explicit_arity ]) 11 | let call call = ((EnqueueCall (call))[@explicit_arity ]) 12 | let fnMsg fnMsg = 13 | let open Vdom in 14 | ((EnqueueCall ((fun callbacks -> (!callbacks).enqueue (fnMsg ())))) 15 | [@explicit_arity ]) 16 | let msg msg = 17 | let open Vdom in 18 | ((EnqueueCall ((fun callbacks -> (!callbacks).enqueue msg))) 19 | [@explicit_arity ]) 20 | let rec run : type msg. msg applicationCallbacks ref -> msg t -> unit = 21 | fun callbacks -> 22 | function 23 | | NoCmd -> () 24 | | ((Mapper (mapper, cmd))[@implicit_arity ]) -> 25 | let subCallbacks = mapper callbacks in run subCallbacks cmd 26 | | ((Batch (cmds))[@explicit_arity ]) -> 27 | List.fold_left (fun () -> fun cmd -> run callbacks cmd) () cmds 28 | | ((EnqueueCall (cb))[@explicit_arity ]) -> cb callbacks 29 | let map : type a b. (a -> b) -> a t -> b t = 30 | fun func -> 31 | fun cmd -> 32 | let mapper = Vdom.wrapCallbacks func in ((Mapper (mapper, cmd)) 33 | [@implicit_arity ]) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | If you'd like to help us improve and extend BuckleScript-TEA, then we welcome your contributions! 4 | 5 | Below you will find some basic steps required to be able to contribute to the project. If you have any questions about this process or any other aspect of contributing to a DCO-style open source project, feel free to open an issue and we'll get your questions answered as quickly as we can. 6 | 7 | ## Contribution Licensing 8 | 9 | Since BuckleScript-TEA is distributed under the terms of the [LGPL Version 3](LICENSE), contributions that you make are licensed under the same terms. In order for us to be able to accept your contributions, we will need explicit confirmation from you that you are able and willing to provide them under these terms, and the mechanism we use to do this is called a Developer's Certificate of Origin [DCO](DCO.md). This is very similar to the process used by the Linux(R) kernel, Samba, and many other open source projects. 10 | 11 | To participate under these terms, all that you must do is include a line like the following as the last line of the commit message for each commit in your contribution (git can insert this line for you automatically with the `git commit -s` flag): 12 | 13 | ``` 14 | Signed-Off-By: Random J. Developer 15 | ``` 16 | 17 | You must use your real name (sorry, no pseudonyms, and no anonymous contributions). 18 | -------------------------------------------------------------------------------- /src-ocaml/web_window_history.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type t = < 4 | length : int [@bs.get]; 5 | back : unit -> unit [@bs.meth]; 6 | forward : unit -> unit [@bs.meth]; 7 | go : int -> unit [@bs.meth]; 8 | pushState : Js.Json.t -> string -> string -> unit [@bs.meth]; 9 | replaceState : Js.Json.t -> string -> string -> unit [@bs.meth]; 10 | state : Js.Json.t [@bs.get]; 11 | > Js.t 12 | 13 | 14 | let length window = match Js.Undefined.toOption window##history with 15 | | None -> -1 16 | | Some history -> history##length 17 | 18 | let back window = match Js.Undefined.toOption window##history with 19 | | None -> () 20 | | Some history -> history##back 21 | 22 | let forward window = match Js.Undefined.toOption window##history with 23 | | None -> () 24 | | Some history -> history##forward 25 | 26 | let go window to' = match Js.Undefined.toOption window##history with 27 | | None -> () 28 | | Some history -> history##go to' 29 | 30 | let pushState window state title url = match Js.Undefined.toOption window##history with 31 | | None -> () 32 | | Some history -> history##pushState state title url 33 | 34 | let replaceState window state title url = match Js.Undefined.toOption window##history with 35 | | None -> () 36 | | Some history -> history##replaceState state title url 37 | 38 | let state window = match Js.Undefined.toOption window##history with 39 | | None -> Js.Undefined.empty 40 | | Some history -> history##state 41 | -------------------------------------------------------------------------------- /test-ocaml/test_client_counter_debug_program.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | open Tea.Html 3 | 4 | type msg = 5 | | Increment 6 | | Decrement 7 | | Reset 8 | | Set of int 9 | 10 | let string_of_msg = function 11 | | Increment -> "Increment" 12 | | Decrement -> "Decrement" 13 | | Reset -> "Reset" 14 | | Set _ -> "Set" 15 | 16 | let init () = 4, Tea.Cmd.none 17 | 18 | let subscriptions _ = Tea.Sub.none 19 | 20 | let update model = function 21 | | Increment -> model + 1, Tea.Cmd.none 22 | | Decrement -> model - 1, Tea.Cmd.none 23 | | Reset -> 0, Tea.Cmd.none 24 | | Set v -> v, Tea.Cmd.none 25 | 26 | 27 | let view_button title msg = 28 | button 29 | [ onClick msg 30 | ] 31 | [ text title 32 | ] 33 | 34 | let view model = 35 | div 36 | [] 37 | [ span 38 | [ style "text-weight" "bold" ] 39 | [ text (string_of_int model) ] 40 | ; br [] 41 | ; view_button "Increment" ( 42 | if model >= 3 then 43 | Decrement 44 | else 45 | Increment 46 | ) 47 | ; br [] 48 | ; view_button "Decrement" Decrement 49 | ; br [] 50 | ; view_button "Set to 42" (Set 42) 51 | ; br [] 52 | ; if model <> 0 then view_button "Reset" Reset else noNode 53 | ] 54 | 55 | let main = 56 | Tea.Debug.program { 57 | init; 58 | update; 59 | view; 60 | subscriptions; 61 | shutdown = (fun _model -> Tea.Cmd.none); 62 | } string_of_msg 63 | -------------------------------------------------------------------------------- /src-ocaml/tea_time.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type t = float 4 | 5 | 6 | (* type 'msg mySub = 7 | | Every of t * (t -> 'msg) 8 | 9 | 10 | type 'msg myCmd = 11 | | Delay of t * (unit -> 'msg) *) 12 | 13 | 14 | let every ~key interval tagger = 15 | let open Vdom in 16 | let enableCall callbacks = 17 | let id = (Web.Window.setInterval (fun () -> callbacks.enqueue (tagger (Web.Date.now ())) ) interval) in 18 | (* let () = Js.log ("Time.every", "enable", interval, tagger, callbacks) in *) 19 | fun () -> 20 | (* let () = Js.log ("Time.every", "disable", id, interval, tagger, callbacks) in *) 21 | Web.Window.clearTimeout id 22 | in Tea_sub.registration key enableCall 23 | 24 | 25 | let delay msTime msg = 26 | Tea_cmd.call 27 | ( fun callbacks -> 28 | let _unhandledID = 29 | Web.Window.setTimeout 30 | ( fun () -> 31 | let open Vdom in 32 | !callbacks.enqueue msg 33 | ) 34 | msTime 35 | in () 36 | ) 37 | 38 | 39 | (* Generic Helpers *) 40 | 41 | let millisecond = 1.0 42 | 43 | 44 | let second = 45 | 1000.0 *. millisecond 46 | 47 | 48 | let minute = 49 | 60.0 *. second 50 | 51 | 52 | let hour = 53 | 60.0 *. minute 54 | 55 | 56 | let inMilliseconds t = 57 | t 58 | 59 | 60 | let inSeconds t = 61 | t /. second 62 | 63 | 64 | let inMinutes t = 65 | t /. minute 66 | 67 | 68 | let inHours t = 69 | t /. hour 70 | -------------------------------------------------------------------------------- /src-ocaml/tea_animationframe.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type t = 4 | { time : Tea_time.t 5 | ; delta : Tea_time.t 6 | } 7 | 8 | let every ?(key="") tagger = 9 | let open Vdom in 10 | let enableCall callbacks = 11 | (* let () = Js.log ("rAF", "enable") in *) 12 | let lastTime = ref (Web.Date.now ()) in 13 | let id = ref None in 14 | let rec onFrame _time = 15 | let time = Web.Date.now () in 16 | match !id with 17 | | None -> () 18 | | Some _i -> 19 | let ret = 20 | { time = time 21 | ; delta = if time < !lastTime then 0.0 else time -. !lastTime 22 | } in 23 | let () = lastTime := time in 24 | let () = callbacks.enqueue (tagger ret) in 25 | match !id with 26 | | None -> () 27 | | Some _stillActive -> 28 | let () = id := Some (Web.Window.requestAnimationFrame onFrame) in 29 | () in 30 | let () = id := Some (Web.Window.requestAnimationFrame onFrame) in 31 | fun () -> match !id with 32 | | None -> () 33 | | Some i -> 34 | (* let () = Js.log ("rAF", "disable") in *) 35 | let () = Web.Window.cancelAnimationFrame i in 36 | let () = id := None in 37 | () 38 | in Tea_sub.registration key enableCall 39 | 40 | 41 | let times ?(key="") tagger = 42 | every 43 | (fun ev -> tagger ~key:key ev.time) 44 | 45 | 46 | let diffs ?(key="") tagger = 47 | every 48 | (fun ev -> tagger ~key:key ev.delta) 49 | -------------------------------------------------------------------------------- /test-reason/test_client_http_task.re: -------------------------------------------------------------------------------- 1 | open Tea; 2 | open Tea.Html; 3 | type result('ok, 'err) = Tea_result.t('ok, 'err); 4 | 5 | [@bs.deriving accessors] 6 | type msg = 7 | | GotResponse(result(string, string)) 8 | | Req; 9 | 10 | let update = model => 11 | fun 12 | | GotResponse(Ok(t)) => (t, Cmd.none) 13 | | GotResponse(Error(t)) => (t, Cmd.none) 14 | | Req => ( 15 | model, 16 | Http.getString("https://jsonplaceholder.typicode.com/todos/1") 17 | |> Http.toTask 18 | |> Task.mapError(Http.string_of_error) 19 | |> Task.andThen(res => Ex.LocalStorage.setItem("todo-1", res)) 20 | |> Task.andThen(() => 21 | Http.getString("https://jsonplaceholder.typicode.com/todos/2") 22 | |> Http.toTask 23 | |> Task.mapError(Http.string_of_error) 24 | ) 25 | |> Task.andThen(res => Ex.LocalStorage.setItem("todo-2", res)) 26 | |> Task.andThen(() => Task.succeed("both saved")) 27 | |> Task.attempt(gotResponse), 28 | ); 29 | 30 | let view = model => 31 | div([], [button([onClick(Req)], [text("execute")]), text(model)]); 32 | 33 | let som = 34 | fun 35 | | GotResponse(Ok(_)) => "GotResponse Ok" 36 | | GotResponse(Error(_)) => "GotResponse Error" 37 | | Req => "Req"; 38 | 39 | let main = 40 | Tea.Debug.standardProgram( 41 | { 42 | init: () => ("nothing", Cmd.none), 43 | subscriptions: _ => Sub.none, 44 | update, 45 | view, 46 | }, 47 | som, 48 | ); 49 | -------------------------------------------------------------------------------- /test-reason/test_client_counter_debug_standard.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | open Tea.Html; 4 | 5 | type msg = 6 | | Increment 7 | | Decrement 8 | | Reset 9 | | Set(int); 10 | 11 | let string_of_msg = 12 | fun 13 | | Increment => "Increment" 14 | | Decrement => "Decrement" 15 | | Reset => "Reset" 16 | | Set(_) => "Set"; 17 | 18 | let init = () => (4, Tea.Cmd.none); 19 | 20 | let subscriptions = (_) => Tea.Sub.none; 21 | 22 | let update = model => 23 | fun 24 | | Increment => (model + 1, Tea.Cmd.none) 25 | | Decrement => (model - 1, Tea.Cmd.none) 26 | | Reset => (0, Tea.Cmd.none) 27 | | Set(v) => (v, Tea.Cmd.none); 28 | 29 | let view_button = (title, msg) => button([onClick(msg)], [text(title)]); 30 | 31 | let view = model => 32 | div( 33 | [], 34 | [ 35 | span([style("text-weight", "bold")], [text(string_of_int(model))]), 36 | br([]), 37 | view_button( 38 | "Increment", 39 | if (model >= 3) { 40 | Decrement; 41 | } else { 42 | Increment; 43 | }, 44 | ), 45 | br([]), 46 | view_button("Decrement", Decrement), 47 | br([]), 48 | view_button("Set to 42", Set(42)), 49 | br([]), 50 | if (model != 0) { 51 | view_button("Reset", Reset); 52 | } else { 53 | noNode; 54 | }, 55 | ], 56 | ); 57 | 58 | let main = 59 | Tea.Debug.standardProgram( 60 | {init, update, view, subscriptions}, 61 | string_of_msg, 62 | ); 63 | -------------------------------------------------------------------------------- /src-ocaml/tea_result.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | (* TODO: Remove this when Bucklescript is updated to OCaml 4.03 as it includes result *) 4 | type ('a, 'b) t (* result *) = ('a,'b) result = 5 | | Ok of 'a 6 | | Error of 'b 7 | 8 | let result_to_option = function 9 | | Ok a -> Some a 10 | | Error _ -> None 11 | 12 | let option_of_result = function 13 | | Ok a -> Some a 14 | | Error _ -> None 15 | 16 | let ok = function 17 | | Ok a -> Some a 18 | | Error _ -> None 19 | 20 | let error = function 21 | | Ok _ -> None 22 | | Error e -> Some e 23 | 24 | let rec last_of = function 25 | | [] -> failwith "`Tea.Result.do` must never be passed the empty list" 26 | | [last] -> last 27 | | next :: tl -> 28 | match next with 29 | | Error _ as e -> e 30 | | Ok _ -> last_of tl 31 | 32 | let rec accumulate = function 33 | | [] -> Ok [] 34 | | [last] -> 35 | begin match last with 36 | | Error _ as e -> e 37 | | Ok o -> Ok [o] 38 | end 39 | | next :: tl -> 40 | match next with 41 | | Error _ as e -> e 42 | | Ok o -> 43 | match accumulate tl with 44 | | Error _ as e -> e 45 | | Ok os -> Ok (o :: os) 46 | 47 | let first fst = function 48 | | Error _ as e -> e 49 | | Ok _ -> fst 50 | 51 | let rec error_of_any = function 52 | | [] -> None 53 | | hd :: tl -> 54 | match hd with 55 | | Error e -> Some e 56 | | Ok _ -> error_of_any tl 57 | 58 | let error_of_first fst = function 59 | | Error e -> Some e 60 | | Ok _ -> error fst 61 | -------------------------------------------------------------------------------- /test-reason/test_client_counter_debug_program.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | open Tea.Html; 4 | 5 | type msg = 6 | | Increment 7 | | Decrement 8 | | Reset 9 | | Set(int); 10 | 11 | let string_of_msg = 12 | fun 13 | | Increment => "Increment" 14 | | Decrement => "Decrement" 15 | | Reset => "Reset" 16 | | Set(_) => "Set"; 17 | 18 | let init = () => (4, Tea.Cmd.none); 19 | 20 | let subscriptions = (_) => Tea.Sub.none; 21 | 22 | let update = model => 23 | fun 24 | | Increment => (model + 1, Tea.Cmd.none) 25 | | Decrement => (model - 1, Tea.Cmd.none) 26 | | Reset => (0, Tea.Cmd.none) 27 | | Set(v) => (v, Tea.Cmd.none); 28 | 29 | let view_button = (title, msg) => button([onClick(msg)], [text(title)]); 30 | 31 | let view = model => 32 | div( 33 | [], 34 | [ 35 | span([style("text-weight", "bold")], [text(string_of_int(model))]), 36 | br([]), 37 | view_button( 38 | "Increment", 39 | if (model >= 3) { 40 | Decrement; 41 | } else { 42 | Increment; 43 | }, 44 | ), 45 | br([]), 46 | view_button("Decrement", Decrement), 47 | br([]), 48 | view_button("Set to 42", Set(42)), 49 | br([]), 50 | if (model != 0) { 51 | view_button("Reset", Reset); 52 | } else { 53 | noNode; 54 | }, 55 | ], 56 | ); 57 | 58 | let main = 59 | Tea.Debug.program( 60 | {init, update, view, subscriptions, shutdown: _model => Tea.Cmd.none}, 61 | string_of_msg, 62 | ); 63 | -------------------------------------------------------------------------------- /src-reason/web_window_localstorage.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | . 3 | [@bs.get] "length": int, 4 | [@bs.meth] "clear": unit => unit, 5 | [@bs.meth] "key": int => string, 6 | [@bs.meth] "getItem": string => string, 7 | [@bs.meth] "removeItem": string => unit, 8 | [@bs.meth] "setItem": (string, string) => unit, 9 | }; 10 | 11 | let length = window => 12 | switch (Js.Undefined.toOption(window##localStorage)) { 13 | | None => None 14 | | Some(localStorage) => Some(localStorage##length) 15 | }; 16 | 17 | let clear = window => 18 | switch (Js.Undefined.toOption(window##localStorage)) { 19 | | None => None 20 | | Some(localStorage) => Some(localStorage##clear()) 21 | }; 22 | 23 | let key = (window, idx) => 24 | switch (Js.Undefined.toOption(window##localStorage)) { 25 | | None => None 26 | | Some(localStorage) => Some(localStorage##key(idx)) 27 | }; 28 | 29 | let getItem = (window, key) => 30 | switch (Js.Undefined.toOption(window##localStorage)) { 31 | | None => None 32 | | Some(localStorage) => 33 | try (Some(localStorage##getItem(key))) { 34 | | _ => None 35 | } 36 | }; 37 | 38 | let removeItem = (window, key) => 39 | switch (Js.Undefined.toOption(window##localStorage)) { 40 | | None => None 41 | | Some(localStorage) => Some(localStorage##removeItem(key)) 42 | }; 43 | 44 | let setItem = (window, key, value) => 45 | switch (Js.Undefined.toOption(window##localStorage)) { 46 | | None => None 47 | | Some(localStorage) => Some(localStorage##setItem(key, value)) 48 | }; 49 | -------------------------------------------------------------------------------- /src-reason/tea_animationframe.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | time: Tea_time.t, 3 | delta: Tea_time.t, 4 | }; 5 | 6 | let every = (~key="", tagger) => { 7 | open Vdom; 8 | let enableCall = callbacks => { 9 | /* let () = Js.log ("rAF", "enable") in */ 10 | let lastTime = ref(Web.Date.now()); 11 | let id = ref(None); 12 | let rec onFrame = _time => { 13 | let time = Web.Date.now(); 14 | switch (id^) { 15 | | None => () 16 | | Some(_i) => 17 | let ret = { 18 | time, 19 | delta: 20 | if (time < lastTime^) { 21 | 0.0; 22 | } else { 23 | time -. lastTime^; 24 | }, 25 | }; 26 | let () = lastTime := time; 27 | let () = callbacks.enqueue(tagger(ret)); 28 | switch (id^) { 29 | | None => () 30 | | Some(_stillActive) => 31 | let () = id := Some(Web.Window.requestAnimationFrame(onFrame)); 32 | (); 33 | }; 34 | }; 35 | }; 36 | let () = id := Some(Web.Window.requestAnimationFrame(onFrame)); 37 | () => 38 | switch (id^) { 39 | | None => () 40 | | Some(i) => 41 | /* let () = Js.log ("rAF", "disable") in */ 42 | let () = Web.Window.cancelAnimationFrame(i); 43 | let () = id := None; 44 | (); 45 | }; 46 | }; 47 | Tea_sub.registration(key, enableCall); 48 | }; 49 | 50 | let times = (~key="", tagger) => every(ev => tagger(~key, ev.time)); 51 | 52 | let diffs = (~key="", tagger) => every(ev => tagger(~key, ev.delta)); 53 | -------------------------------------------------------------------------------- /src-reason/tea_cmd.re: -------------------------------------------------------------------------------- 1 | type applicationCallbacks('msg) = Vdom.applicationCallbacks('msg); 2 | 3 | type t('msg) = 4 | | NoCmd: t(_) 5 | | Mapper( 6 | ref(Vdom.applicationCallbacks('msg)) => 7 | ref(Vdom.applicationCallbacks('msgB)), 8 | t('msgB), 9 | ) 10 | : t('msg) 11 | | Batch(list(t('msg))): t('msg) 12 | | EnqueueCall(ref(applicationCallbacks('msg)) => unit): t('msg); 13 | 14 | let none = NoCmd; 15 | 16 | let batch = cmds => Batch(cmds); 17 | 18 | let call = call => EnqueueCall(call); 19 | 20 | let fnMsg = fnMsg => 21 | Vdom.(EnqueueCall(callbacks => callbacks^.enqueue(fnMsg()))); 22 | 23 | let msg = msg => Vdom.(EnqueueCall(callbacks => callbacks^.enqueue(msg))); 24 | 25 | let rec run: type msg. (ref(applicationCallbacks(msg)), t(msg)) => unit = 26 | callbacks => 27 | fun 28 | | NoCmd => () 29 | | [@implicit_arity] Mapper(mapper, cmd) => { 30 | let subCallbacks = mapper(callbacks); 31 | run(subCallbacks, cmd); 32 | } 33 | | Batch(cmds) => 34 | List.fold_left(((), cmd) => run(callbacks, cmd), (), cmds) 35 | | EnqueueCall(cb) => 36 | /* let () = Js.log ("Cmd.run", "enqueue", cb) in */ 37 | cb(callbacks); 38 | 39 | /* let wrapCallbacks func callbacks = */ 40 | /* let open Vdom in */ 41 | /* ref */ 42 | /* { enqueue = (fun msg -> !callbacks.enqueue (func msg)) */ 43 | /* } */ 44 | 45 | let map: type a b. (a => b, t(a)) => t(b) = 46 | (func, cmd) => { 47 | let mapper = Vdom.wrapCallbacks(func); 48 | [@implicit_arity] Mapper(mapper, cmd); 49 | }; 50 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | In alphabetical order in their requisite sections. 4 | 5 | ## Original Author(s): 6 | 7 | - @overminddl1 (Gabriel Robertson) 8 | 9 | ## Bucklescript 10 | 11 | - @bobzhang (Hongbo Zhang): Significant work on Bucklescript itself and for adding useful features to Bucklescript that have significantly helped this project's ease-of-use. We all owe him a gratitude. :-) 12 | 13 | ## Special Thanks 14 | 15 | - @alfredfriedrich (Peter Klett): Adding further to the API. 16 | 17 | - @feluxe: Starter-kit provider. 18 | 19 | - @IwanKaramazow: Bug finder extraordinaire, thanks for catching all those corner cases! Also helping flesh out the API. 20 | 21 | - @jackalcooper: Bug finder extraordinaire, thanks for catching those Bucklescript issues! Also helped flesh out the API. 22 | 23 | - @jordwest: Adding further to the API. 24 | 25 | - @tcoopman: Adding further to the API. 26 | 27 | - @soren-n: Adding further to the API. 28 | 29 | - @utkarshkukreti: Bug finder extraordinaire! 30 | 31 | - @neochrome (Johan Stenqvist): Adding further to the API. 32 | 33 | - @canadaduane (Duane Johnson): Creating the auto-conversion between the OCaml and Reason files, tremendously useful so people can see how each work! 34 | 35 | - @dboris (Boris Dobroslavov): Improving model history debug format. 36 | 37 | - @seadynamic8: Better matching to the base API, I.E. Bug Fixing. 38 | 39 | - @pbiggar (Paul Biggar): Adding type signatures, bug fixing, code cleanup. 40 | 41 | - @BenSchZA (Benjamin Scholtz): Documenting method to integrate BS-TEA into ReasonReact components. 42 | -------------------------------------------------------------------------------- /src-reason/tea_result.re: -------------------------------------------------------------------------------- 1 | /* TODO: Remove this when Bucklescript is updated to OCaml 4.03 as it includes result */ 2 | type t /* result */('a, 'b) = 3 | | Ok('a) 4 | | Error('b); 5 | 6 | let result_to_option = 7 | fun 8 | | Ok(a) => Some(a) 9 | | Error(_) => None; 10 | 11 | let option_of_result = 12 | fun 13 | | Ok(a) => Some(a) 14 | | Error(_) => None; 15 | 16 | let ok = 17 | fun 18 | | Ok(a) => Some(a) 19 | | Error(_) => None; 20 | 21 | let error = 22 | fun 23 | | Ok(_) => None 24 | | Error(e) => Some(e); 25 | 26 | let rec last_of = 27 | fun 28 | | [] => failwith("`Tea.Result.do` must never be passed the empty list") 29 | | [last] => last 30 | | [next, ...tl] => 31 | switch (next) { 32 | | Error(_) as e => e 33 | | Ok(_) => last_of(tl) 34 | }; 35 | 36 | let rec accumulate = 37 | fun 38 | | [] => Ok([]) 39 | | [last] => 40 | switch (last) { 41 | | Error(_) as e => e 42 | | Ok(o) => Ok([o]) 43 | } 44 | | [next, ...tl] => 45 | switch (next) { 46 | | Error(_) as e => e 47 | | Ok(o) => 48 | switch (accumulate(tl)) { 49 | | Error(_) as e => e 50 | | Ok(os) => Ok([o, ...os]) 51 | } 52 | }; 53 | 54 | let first = fst => 55 | fun 56 | | Error(_) as e => e 57 | | Ok(_) => fst; 58 | 59 | let rec error_of_any = 60 | fun 61 | | [] => None 62 | | [hd, ...tl] => 63 | switch (hd) { 64 | | Error(e) => Some(e) 65 | | Ok(_) => error_of_any(tl) 66 | }; 67 | 68 | let error_of_first = fst => 69 | fun 70 | | Error(e) => Some(e) 71 | | Ok(_) => error(fst); 72 | -------------------------------------------------------------------------------- /src-reason/web_window_history.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | . 3 | [@bs.get] "length": int, 4 | [@bs.meth] "back": unit => unit, 5 | [@bs.meth] "forward": unit => unit, 6 | [@bs.meth] "go": int => unit, 7 | [@bs.meth] "pushState": (Js.Json.t, string, string) => unit, 8 | [@bs.meth] "replaceState": (Js.Json.t, string, string) => unit, 9 | [@bs.get] "state": Js.Json.t, 10 | }; 11 | 12 | let length = window => 13 | switch (Js.Undefined.toOption(window##history)) { 14 | | None => (-1) 15 | | Some(history) => history##length 16 | }; 17 | 18 | let back = window => 19 | switch (Js.Undefined.toOption(window##history)) { 20 | | None => () 21 | | Some(history) => history##back 22 | }; 23 | 24 | let forward = window => 25 | switch (Js.Undefined.toOption(window##history)) { 26 | | None => () 27 | | Some(history) => history##forward 28 | }; 29 | 30 | let go = (window, to') => 31 | switch (Js.Undefined.toOption(window##history)) { 32 | | None => () 33 | | Some(history) => history##go(to') 34 | }; 35 | 36 | let pushState = (window, state, title, url) => 37 | switch (Js.Undefined.toOption(window##history)) { 38 | | None => () 39 | | Some(history) => history##pushState(state, title, url) 40 | }; 41 | 42 | let replaceState = (window, state, title, url) => 43 | switch (Js.Undefined.toOption(window##history)) { 44 | | None => () 45 | | Some(history) => history##replaceState(state, title, url) 46 | }; 47 | 48 | let state = window => 49 | switch (Js.Undefined.toOption(window##history)) { 50 | | None => Js.Undefined.empty 51 | | Some(history) => history##state 52 | }; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bucklescript-tea", 3 | "version": "0.15.0", 4 | "description": "TEA for Bucklescript", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "bsb clean", 8 | "clean:all": "bsb -clean-world", 9 | "build": "bsb", 10 | "build:all": "bsb -make-world", 11 | "watch": "bsb -w", 12 | "build:test:client": "browserify --standalone=Test_client --outfile=lib/js/test-ocaml/app_test_client.js lib/js/test-ocaml/test_client.js", 13 | "watch:test:client": "watchify --standalone=Test_client --outfile=lib/js/test-ocaml/app_test_client.js lib/js/test-ocaml/test_client.js", 14 | "watch:test": "run-p watch watch:test:*", 15 | "prebuild:test": "run-s build", 16 | "build:test": "run-p build:test:*", 17 | "pretest": "run-s build:test", 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "dual-syntax": "bin/dual-syntax" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/overminddl1/bucklescript-tea.git" 24 | }, 25 | "keywords": [ 26 | "TEA", 27 | "Bucklescript", 28 | "OCaml" 29 | ], 30 | "author": "OvermindDL1", 31 | "license": "LGPL-3.0-or-later", 32 | "bugs": { 33 | "url": "https://github.com/overminddl1/bucklescript-tea/issues" 34 | }, 35 | "homepage": "https://github.com/overminddl1/bucklescript-tea#readme", 36 | "devDependencies": { 37 | "browserify": "^14.0.0", 38 | "bs-platform": "^7.1.1", 39 | "npm-run-all": "4.0.2", 40 | "pre-commit": "^1.2.0", 41 | "watchify": "^3.11.1" 42 | }, 43 | "pre-commit": [ 44 | "dual-syntax" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_window_localstorage.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Caml_option = require("bs-platform/lib/js/caml_option.js"); 5 | 6 | function length($$window) { 7 | var match = $$window.localStorage; 8 | if (match !== undefined) { 9 | return Caml_option.some(match.length); 10 | } 11 | 12 | } 13 | 14 | function clear($$window) { 15 | var match = $$window.localStorage; 16 | if (match !== undefined) { 17 | return Caml_option.some(match.clear()); 18 | } 19 | 20 | } 21 | 22 | function key($$window, idx) { 23 | var match = $$window.localStorage; 24 | if (match !== undefined) { 25 | return Caml_option.some(match.key(idx)); 26 | } 27 | 28 | } 29 | 30 | function getItem($$window, key) { 31 | var match = $$window.localStorage; 32 | if (match !== undefined) { 33 | try { 34 | return Caml_option.some(match.getItem(key)); 35 | } 36 | catch (exn){ 37 | return ; 38 | } 39 | } 40 | 41 | } 42 | 43 | function removeItem($$window, key) { 44 | var match = $$window.localStorage; 45 | if (match !== undefined) { 46 | return Caml_option.some(match.removeItem(key)); 47 | } 48 | 49 | } 50 | 51 | function setItem($$window, key, value) { 52 | var match = $$window.localStorage; 53 | if (match !== undefined) { 54 | return Caml_option.some(match.setItem(key, value)); 55 | } 56 | 57 | } 58 | 59 | exports.length = length; 60 | exports.clear = clear; 61 | exports.key = key; 62 | exports.getItem = getItem; 63 | exports.removeItem = removeItem; 64 | exports.setItem = setItem; 65 | /* No side effect */ 66 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_window_history.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function length($$window) { 6 | var match = $$window.history; 7 | if (match !== undefined) { 8 | return match.length; 9 | } else { 10 | return -1; 11 | } 12 | } 13 | 14 | function back($$window) { 15 | var match = $$window.history; 16 | if (match !== undefined) { 17 | return match.back; 18 | } else { 19 | return /* () */0; 20 | } 21 | } 22 | 23 | function forward($$window) { 24 | var match = $$window.history; 25 | if (match !== undefined) { 26 | return match.forward; 27 | } else { 28 | return /* () */0; 29 | } 30 | } 31 | 32 | function go($$window, to$prime) { 33 | var match = $$window.history; 34 | if (match !== undefined) { 35 | return match.go(to$prime); 36 | } else { 37 | return /* () */0; 38 | } 39 | } 40 | 41 | function pushState($$window, state, title, url) { 42 | var match = $$window.history; 43 | if (match !== undefined) { 44 | return match.pushState(state, title, url); 45 | } else { 46 | return /* () */0; 47 | } 48 | } 49 | 50 | function replaceState($$window, state, title, url) { 51 | var match = $$window.history; 52 | if (match !== undefined) { 53 | return match.replaceState(state, title, url); 54 | } else { 55 | return /* () */0; 56 | } 57 | } 58 | 59 | function state($$window) { 60 | var match = $$window.history; 61 | if (match !== undefined) { 62 | return match.state; 63 | } 64 | 65 | } 66 | 67 | exports.length = length; 68 | exports.back = back; 69 | exports.forward = forward; 70 | exports.go = go; 71 | exports.pushState = pushState; 72 | exports.replaceState = replaceState; 73 | exports.state = state; 74 | /* No side effect */ 75 | -------------------------------------------------------------------------------- /test-reason/test_client_on_with_options.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | [@bs.deriving {accessors: accessors}] 4 | type msg = 5 | | Click 6 | | Set_value(int); 7 | 8 | let update = model => 9 | fun 10 | | Click => model + 1 11 | | Set_value(n) => n; 12 | 13 | let view = model => { 14 | open Tea.Html2; 15 | open Tea.Html2.Attributes; 16 | open Tea.Html2.Events; 17 | open Tea.Json; 18 | let clientX = Decoder.field("clientX", Decoder.int); 19 | div( 20 | [], 21 | List.map( 22 | e => div([], [e]), 23 | [ 24 | model |> string_of_int |> text, 25 | button([onClick(Click)], [text("onClick")]), 26 | button( 27 | [on(~key="", "click", Decoder.succeed(Click))], 28 | [text("on \"click\"")], 29 | ), 30 | a([href("https://www.google.com")], [text("a normal link")]), 31 | a( 32 | [ 33 | href("https://www.google.com"), 34 | onWithOptions( 35 | ~key="", 36 | "click", 37 | {...defaultOptions, preventDefault: true}, 38 | Tea.Json.Decoder.succeed(Click), 39 | ), 40 | ], 41 | [text("a link with prevent default")], 42 | ), 43 | button( 44 | [on(~key="", "click", Decoder.map(set_value, clientX))], 45 | [text("on \"click\", use clientX value")], 46 | ), 47 | input'( 48 | [ 49 | type'("text"), 50 | on( 51 | ~key="", 52 | "input", 53 | Decoder.map(v => v |> int_of_string |> set_value, targetValue), 54 | ), 55 | ], 56 | [], 57 | ), 58 | ], 59 | ), 60 | ); 61 | }; 62 | 63 | let main = beginnerProgram({model: 0, update, view}); 64 | -------------------------------------------------------------------------------- /test-ocaml/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Testing 5 | 6 | 7 | 8 | 9 |
10 | All code is available on github. 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_time.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Curry = require("bs-platform/lib/js/curry.js"); 6 | var Tea_sub = require("./tea_sub.js"); 7 | var Web_window = require("./web_window.js"); 8 | 9 | function every(key, interval, tagger) { 10 | var enableCall = function (callbacks) { 11 | var id = Web_window.$$setInterval((function (param) { 12 | return Curry._1(callbacks.enqueue, Curry._1(tagger, Date.now())); 13 | }), interval); 14 | return (function (param) { 15 | return window.clearTimeout(id); 16 | }); 17 | }; 18 | return Tea_sub.registration(key, enableCall); 19 | } 20 | 21 | function delay(msTime, msg) { 22 | return /* EnqueueCall */Block.__(2, [(function (callbacks) { 23 | Web_window.$$setTimeout((function (param) { 24 | return Curry._1(callbacks.contents.enqueue, msg); 25 | }), msTime); 26 | return /* () */0; 27 | })]); 28 | } 29 | 30 | var second = 1000.0 * 1.0; 31 | 32 | var minute = 60.0 * second; 33 | 34 | var hour = 60.0 * minute; 35 | 36 | function inMilliseconds(t) { 37 | return t; 38 | } 39 | 40 | function inSeconds(t) { 41 | return t / second; 42 | } 43 | 44 | function inMinutes(t) { 45 | return t / minute; 46 | } 47 | 48 | function inHours(t) { 49 | return t / hour; 50 | } 51 | 52 | var millisecond = 1.0; 53 | 54 | exports.every = every; 55 | exports.delay = delay; 56 | exports.millisecond = millisecond; 57 | exports.second = second; 58 | exports.minute = minute; 59 | exports.hour = hour; 60 | exports.inMilliseconds = inMilliseconds; 61 | exports.inSeconds = inSeconds; 62 | exports.inMinutes = inMinutes; 63 | exports.inHours = inHours; 64 | /* No side effect */ 65 | -------------------------------------------------------------------------------- /test-ocaml/test_client_attribute_removal.ml: -------------------------------------------------------------------------------- 1 | open Tea.App 2 | open Tea.Html 3 | 4 | type model = { 5 | selected: string option; 6 | languages: string list 7 | } 8 | 9 | type message = 10 | | Select of string 11 | | Delete 12 | [@@bs.deriving {accessors}] 13 | 14 | let render_selected = function 15 | | Some selected -> 16 | div [] 17 | [ text ("you selected " ^ selected) 18 | ; div [onClick Delete] [text "delete selection"]] 19 | | None -> div [] [text "Nothing selected"] 20 | 21 | (* let lang l is_selected = 22 | * let baseProps = [onClick (Select l); style "color" "blue"] in 23 | * let props = if is_selected == true then (style "border" "1px solid black")::baseProps else baseProps 24 | * in 25 | * li props [text l] *) 26 | 27 | let lang l is_selected = 28 | li 29 | [ onClick (Select l) 30 | ; style "color" "blue" 31 | ; if is_selected then style "border" "1px solid black" else noProp 32 | ; if is_selected then Vdom.attribute "" "lang" l else noProp 33 | ] 34 | [ text l ] 35 | 36 | let render_languages selected languages = 37 | let is_selected selected language = 38 | match selected with 39 | | Some l -> language == l 40 | | None -> false 41 | in 42 | let rendered = List.map (fun l -> lang l (is_selected selected l)) languages in 43 | ul [] rendered 44 | 45 | let update state = function 46 | | Select lang -> { state with selected = Some lang} 47 | | Delete -> { state with selected = None } 48 | 49 | let view state = 50 | div [] 51 | [ render_selected state.selected 52 | ; render_languages state.selected state.languages] 53 | 54 | let main = 55 | let initialState = { 56 | selected = Some "Erlang"; 57 | languages = ["Erlang"; "Ocaml"; "Clojure"] 58 | } in 59 | beginnerProgram { 60 | model = initialState; 61 | update; 62 | view; 63 | } 64 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_promise.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Curry = require("bs-platform/lib/js/curry.js"); 6 | var Caml_option = require("bs-platform/lib/js/caml_option.js"); 7 | 8 | function cmd(promise, tagger) { 9 | return /* EnqueueCall */Block.__(2, [(function (callbacks) { 10 | promise.then((function (res) { 11 | var match = Curry._1(tagger, res); 12 | if (match !== undefined) { 13 | Curry._1(callbacks.contents.enqueue, Caml_option.valFromOption(match)); 14 | return Promise.resolve(/* () */0); 15 | } else { 16 | return Promise.resolve(/* () */0); 17 | } 18 | })); 19 | return /* () */0; 20 | })]); 21 | } 22 | 23 | function result(promise, msg) { 24 | return /* EnqueueCall */Block.__(2, [(function (callbacks) { 25 | var enq = function (result) { 26 | return Curry._1(callbacks.contents.enqueue, Curry._1(msg, result)); 27 | }; 28 | promise.then((function (res) { 29 | return Promise.resolve(enq(/* Ok */Block.__(0, [res]))); 30 | })).catch((function (err) { 31 | var err_to_string = function (err) { 32 | return "" + (String(err) + ""); 33 | }; 34 | return Promise.resolve(enq(/* Error */Block.__(1, [err_to_string(err)]))); 35 | })); 36 | return /* () */0; 37 | })]); 38 | } 39 | 40 | exports.cmd = cmd; 41 | exports.result = result; 42 | /* No side effect */ 43 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Test_client_drag = require("./test_client_drag.js"); 5 | var Test_client_counter = require("./test_client_counter.js"); 6 | var Test_client_http_task = require("./test_client_http_task.js"); 7 | var Test_client_btn_update_span = require("./test_client_btn_update_span.js"); 8 | var Test_client_on_with_options = require("./test_client_on_with_options.js"); 9 | var Test_client_attribute_removal = require("./test_client_attribute_removal.js"); 10 | var Test_client_counter_debug_program = require("./test_client_counter_debug_program.js"); 11 | var Test_client_counter_debug_beginner = require("./test_client_counter_debug_beginner.js"); 12 | var Test_client_counter_debug_standard = require("./test_client_counter_debug_standard.js"); 13 | 14 | var counter = Test_client_counter.main; 15 | 16 | var counter_debug_beginner = Test_client_counter_debug_beginner.main; 17 | 18 | var counter_debug_standard = Test_client_counter_debug_standard.main; 19 | 20 | var counter_debug_program = Test_client_counter_debug_program.main; 21 | 22 | var btn_update_span = Test_client_btn_update_span.main; 23 | 24 | var attribute_removal = Test_client_attribute_removal.main; 25 | 26 | var drag = Test_client_drag.main; 27 | 28 | var on_with_options = Test_client_on_with_options.main; 29 | 30 | var http_task = Test_client_http_task.main; 31 | 32 | exports.counter = counter; 33 | exports.counter_debug_beginner = counter_debug_beginner; 34 | exports.counter_debug_standard = counter_debug_standard; 35 | exports.counter_debug_program = counter_debug_program; 36 | exports.btn_update_span = btn_update_span; 37 | exports.attribute_removal = attribute_removal; 38 | exports.drag = drag; 39 | exports.on_with_options = on_with_options; 40 | exports.http_task = http_task; 41 | /* Test_client_drag Not a pure module */ 42 | -------------------------------------------------------------------------------- /test-reason/test_client_attribute_removal.re: -------------------------------------------------------------------------------- 1 | open Tea.App; 2 | 3 | open Tea.Html; 4 | 5 | type model = { 6 | selected: option(string), 7 | languages: list(string), 8 | }; 9 | 10 | [@bs.deriving {accessors: accessors}] 11 | type message = 12 | | Select(string) 13 | | Delete; 14 | 15 | let render_selected = 16 | fun 17 | | Some(selected) => 18 | div( 19 | [], 20 | [ 21 | text("you selected " ++ selected), 22 | div([onClick(Delete)], [text("delete selection")]), 23 | ], 24 | ) 25 | | None => div([], [text("Nothing selected")]); 26 | 27 | /* let lang l is_selected = 28 | * let baseProps = [onClick (Select l); style "color" "blue"] in 29 | * let props = if is_selected == true then (style "border" "1px solid black")::baseProps else baseProps 30 | * in 31 | * li props [text l] */ 32 | let lang = (l, is_selected) => 33 | li( 34 | [ 35 | onClick(Select(l)), 36 | style("color", "blue"), 37 | if (is_selected) { 38 | style("border", "1px solid black"); 39 | } else { 40 | noProp; 41 | }, 42 | if (is_selected) { 43 | Vdom.attribute("", "lang", l); 44 | } else { 45 | noProp; 46 | }, 47 | ], 48 | [text(l)], 49 | ); 50 | 51 | let render_languages = (selected, languages) => { 52 | let is_selected = (selected, language) => 53 | switch (selected) { 54 | | Some(l) => language === l 55 | | None => false 56 | }; 57 | let rendered = 58 | List.map(l => lang(l, is_selected(selected, l)), languages); 59 | ul([], rendered); 60 | }; 61 | 62 | let update = state => 63 | fun 64 | | Select(lang) => {...state, selected: Some(lang)} 65 | | Delete => {...state, selected: None}; 66 | 67 | let view = state => 68 | div( 69 | [], 70 | [ 71 | render_selected(state.selected), 72 | render_languages(state.selected, state.languages), 73 | ], 74 | ); 75 | 76 | let main = { 77 | let initialState = { 78 | selected: Some("Erlang"), 79 | languages: ["Erlang", "Ocaml", "Clojure"], 80 | }; 81 | beginnerProgram({model: initialState, update, view}); 82 | }; 83 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_btn_update_span.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("../src-ocaml/vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Tea_app = require("../src-ocaml/tea_app.js"); 7 | var Tea_html = require("../src-ocaml/tea_html.js"); 8 | 9 | function update$prime(model, param) { 10 | return /* tuple */[ 11 | model[0], 12 | "right" 13 | ]; 14 | } 15 | 16 | function render_model(param) { 17 | if (param[0] !== undefined && param[1] !== undefined) { 18 | return Tea_html.input$prime(undefined, undefined, /* :: */[ 19 | /* RawProp */Block.__(0, [ 20 | "value", 21 | "This should be on screen" 22 | ]), 23 | /* [] */0 24 | ], /* [] */0); 25 | } 26 | return Tea_html.span(undefined, undefined, /* [] */0, /* :: */[ 27 | /* Text */Block.__(1, ["nothing"]), 28 | /* [] */0 29 | ]); 30 | } 31 | 32 | function view$prime(model) { 33 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 34 | Tea_html.button(undefined, undefined, /* :: */[ 35 | Vdom.onMsg("click", /* Trigger */0), 36 | /* [] */0 37 | ], /* :: */[ 38 | /* Text */Block.__(1, ["trigger rerender"]), 39 | /* [] */0 40 | ]), 41 | /* :: */[ 42 | render_model(model), 43 | /* [] */0 44 | ] 45 | ]); 46 | } 47 | 48 | var partial_arg_model = /* tuple */[ 49 | "left", 50 | undefined 51 | ]; 52 | 53 | var partial_arg = { 54 | model: partial_arg_model, 55 | update: update$prime, 56 | view: view$prime 57 | }; 58 | 59 | function main(param, param$1) { 60 | return Tea_app.beginnerProgram(partial_arg, param, param$1); 61 | } 62 | 63 | var trigger = /* Trigger */0; 64 | 65 | exports.trigger = trigger; 66 | exports.update$prime = update$prime; 67 | exports.render_model = render_model; 68 | exports.view$prime = view$prime; 69 | exports.main = main; 70 | /* Tea_html Not a pure module */ 71 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_cmd.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var List = require("bs-platform/lib/js/list.js"); 5 | var Vdom = require("./vdom.js"); 6 | var Block = require("bs-platform/lib/js/block.js"); 7 | var Curry = require("bs-platform/lib/js/curry.js"); 8 | 9 | function batch(cmds) { 10 | return /* Batch */Block.__(1, [cmds]); 11 | } 12 | 13 | function call(call$1) { 14 | return /* EnqueueCall */Block.__(2, [call$1]); 15 | } 16 | 17 | function fnMsg(fnMsg$1) { 18 | return /* EnqueueCall */Block.__(2, [(function (callbacks) { 19 | return Curry._1(callbacks.contents.enqueue, Curry._1(fnMsg$1, /* () */0)); 20 | })]); 21 | } 22 | 23 | function msg(msg$1) { 24 | return /* EnqueueCall */Block.__(2, [(function (callbacks) { 25 | return Curry._1(callbacks.contents.enqueue, msg$1); 26 | })]); 27 | } 28 | 29 | function run(_callbacks, _param) { 30 | while(true) { 31 | var param = _param; 32 | var callbacks = _callbacks; 33 | if (typeof param === "number") { 34 | return /* () */0; 35 | } else { 36 | switch (param.tag | 0) { 37 | case /* Mapper */0 : 38 | var subCallbacks = Curry._1(param[0], callbacks); 39 | _param = param[1]; 40 | _callbacks = subCallbacks; 41 | continue ; 42 | case /* Batch */1 : 43 | return List.fold_left((function(callbacks){ 44 | return function (param, cmd) { 45 | return run(callbacks, cmd); 46 | } 47 | }(callbacks)), /* () */0, param[0]); 48 | case /* EnqueueCall */2 : 49 | return Curry._1(param[0], callbacks); 50 | 51 | } 52 | } 53 | }; 54 | } 55 | 56 | function map(func, cmd) { 57 | var mapper = function (param) { 58 | return Vdom.wrapCallbacks(func, param); 59 | }; 60 | return /* Mapper */Block.__(0, [ 61 | mapper, 62 | cmd 63 | ]); 64 | } 65 | 66 | var none = /* NoCmd */0; 67 | 68 | exports.none = none; 69 | exports.batch = batch; 70 | exports.call = call; 71 | exports.fnMsg = fnMsg; 72 | exports.msg = msg; 73 | exports.run = run; 74 | exports.map = map; 75 | /* No side effect */ 76 | -------------------------------------------------------------------------------- /src-reason/tea_ex.re: -------------------------------------------------------------------------------- 1 | /* Everything here is not in Elm and is purely used as an extension and may vanish at any time if a better API comes out. */ 2 | 3 | let render_event = (~key="", msg) => { 4 | open Vdom; 5 | let enableCall = callbacks => { 6 | let () = callbacks.on(AddRenderMsg(msg)); 7 | () => callbacks.on(RemoveRenderMsg(msg)); 8 | }; 9 | Tea_sub.registration(key, enableCall); 10 | }; 11 | 12 | module LocalStorage = { 13 | open Tea_task; 14 | open Tea_result; 15 | 16 | let length = 17 | nativeBinding(cb => 18 | switch (Web.Window.LocalStorage.length(Web.Window.window)) { 19 | | None => cb(Error("localStorage is not available")) 20 | | Some(value) => cb(Ok(value)) 21 | } 22 | ); 23 | 24 | let clear = 25 | nativeBinding(cb => 26 | switch (Web.Window.LocalStorage.clear(Web.Window.window)) { 27 | | None => cb(Error("localStorage is not available")) 28 | | Some(value) => cb(Ok(value)) 29 | } 30 | ); 31 | let clearCmd = () => Tea_task.attemptOpt(_ => None, clear); 32 | 33 | let key = idx => 34 | nativeBinding(cb => 35 | switch (Web.Window.LocalStorage.key(Web.Window.window, idx)) { 36 | | None => cb(Error("localStorage is not available")) 37 | | Some(value) => cb(Ok(value)) 38 | } 39 | ); 40 | 41 | let getItem = key => 42 | nativeBinding(cb => 43 | switch (Web.Window.LocalStorage.getItem(Web.Window.window, key)) { 44 | | None => cb(Error("localStorage is not available")) 45 | | Some(value) => cb(Ok(value)) 46 | } 47 | ); 48 | 49 | let removeItem = key => 50 | nativeBinding(cb => 51 | switch (Web.Window.LocalStorage.removeItem(Web.Window.window, key)) { 52 | | None => cb(Error("localStorage is not available")) 53 | | Some(value) => cb(Ok(value)) 54 | } 55 | ); 56 | let removeItemCmd = key => Tea_task.attemptOpt(_ => None, removeItem(key)); 57 | 58 | let setItem = (key, value) => 59 | nativeBinding(cb => 60 | switch (Web.Window.LocalStorage.setItem(Web.Window.window, key, value)) { 61 | | None => cb(Error("localStorage is not available")) 62 | | Some () => cb(Ok()) 63 | } 64 | ); 65 | let setItemCmd = (key, value) => 66 | Tea_task.attemptOpt(_ => None, setItem(key, value)); 67 | }; 68 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_animationframe.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Curry = require("bs-platform/lib/js/curry.js"); 5 | var Tea_sub = require("./tea_sub.js"); 6 | 7 | function every($staropt$star, tagger) { 8 | var key = $staropt$star !== undefined ? $staropt$star : ""; 9 | var enableCall = function (callbacks) { 10 | var lastTime = { 11 | contents: Date.now() 12 | }; 13 | var id = { 14 | contents: undefined 15 | }; 16 | var onFrame = function (_time) { 17 | var time = Date.now(); 18 | var match = id.contents; 19 | if (match !== undefined) { 20 | var ret_delta = time < lastTime.contents ? 0.0 : time - lastTime.contents; 21 | var ret = { 22 | time: time, 23 | delta: ret_delta 24 | }; 25 | lastTime.contents = time; 26 | Curry._1(callbacks.enqueue, Curry._1(tagger, ret)); 27 | var match$1 = id.contents; 28 | if (match$1 !== undefined) { 29 | id.contents = window.requestAnimationFrame(onFrame); 30 | return /* () */0; 31 | } else { 32 | return /* () */0; 33 | } 34 | } else { 35 | return /* () */0; 36 | } 37 | }; 38 | id.contents = window.requestAnimationFrame(onFrame); 39 | return (function (param) { 40 | var match = id.contents; 41 | if (match !== undefined) { 42 | window.cancelAnimationFrame(match); 43 | id.contents = undefined; 44 | return /* () */0; 45 | } else { 46 | return /* () */0; 47 | } 48 | }); 49 | }; 50 | return Tea_sub.registration(key, enableCall); 51 | } 52 | 53 | function times($staropt$star, tagger) { 54 | var key = $staropt$star !== undefined ? $staropt$star : ""; 55 | return every(undefined, (function (ev) { 56 | return Curry._2(tagger, key, ev.time); 57 | })); 58 | } 59 | 60 | function diffs($staropt$star, tagger) { 61 | var key = $staropt$star !== undefined ? $staropt$star : ""; 62 | return every(undefined, (function (ev) { 63 | return Curry._2(tagger, key, ev.delta); 64 | })); 65 | } 66 | 67 | exports.every = every; 68 | exports.times = times; 69 | exports.diffs = diffs; 70 | /* No side effect */ 71 | -------------------------------------------------------------------------------- /src-ocaml/web_location.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | type t = < 4 | href : string [@bs.get] [@bs.set]; 5 | protocol : string [@bs.get] [@bs.set]; 6 | host : string [@bs.get] [@bs.set]; 7 | hostname : string [@bs.get] [@bs.set]; 8 | port : string [@bs.get] [@bs.set]; 9 | pathname : string [@bs.get] [@bs.set]; 10 | search : string [@bs.get] [@bs.set]; 11 | hash : string [@bs.get] [@bs.set]; 12 | username : string [@bs.get] [@bs.set]; 13 | password : string [@bs.get] [@bs.set]; 14 | origin : string [@bs.get]; 15 | > Js.t 16 | 17 | let getHref location = location##href 18 | let setHref location value = location##href #= value 19 | 20 | let getProtocol location = location##protocol 21 | let setProtocol location value = location##protocol #= value 22 | 23 | let getHost location = location##host 24 | let setHost location value = location##host #= value 25 | 26 | let getHostname location = location##hostname 27 | let setHostname location value = location##hostname #= value 28 | 29 | let getPort location = location##port 30 | let setPort location value = location##port #= value 31 | 32 | let getPathname location = location##pathname 33 | let setPathname location value = location##pathname #= value 34 | 35 | let getSearch location = location##search 36 | let setSearch location value = location##search #= value 37 | 38 | let getHash location = location##hash 39 | let setHash location value = location##hash #= value 40 | 41 | let getUsername location = location##username 42 | let setUsername location value = location##username #= value 43 | 44 | let getPassword location = location##password 45 | let setPassword location value = location##password #= value 46 | 47 | let getOrigin location = location##origin 48 | 49 | 50 | type location = 51 | { href : string 52 | ; protocol : string 53 | ; host : string 54 | ; hostname : string 55 | ; port : string 56 | ; pathname : string 57 | ; search : string 58 | ; hash : string 59 | ; username : string 60 | ; password : string 61 | ; origin : string 62 | } 63 | 64 | let asRecord location = 65 | { href = location##href 66 | ; protocol = location##protocol 67 | ; host = location##host 68 | ; hostname = location##hostname 69 | ; port = location##port 70 | ; pathname = location##pathname 71 | ; search = location##search 72 | ; hash = location##hash 73 | ; username = location##username 74 | ; password = location##password 75 | ; origin = location##origin 76 | } 77 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_mouse.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("./vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Curry = require("bs-platform/lib/js/curry.js"); 7 | var Tea_sub = require("./tea_sub.js"); 8 | var Tea_json = require("./tea_json.js"); 9 | var Caml_option = require("bs-platform/lib/js/caml_option.js"); 10 | 11 | var position = Tea_json.Decoder.map2((function (x, y) { 12 | return { 13 | x: x, 14 | y: y 15 | }; 16 | }), Tea_json.Decoder.field("pageX", Tea_json.Decoder.$$int), Tea_json.Decoder.field("pageY", Tea_json.Decoder.$$int)); 17 | 18 | function registerGlobal(name, key, tagger) { 19 | var enableCall = function (callbacks_base) { 20 | var callbacks = { 21 | contents: callbacks_base 22 | }; 23 | var fn = function (ev) { 24 | var match = Tea_json.Decoder.decodeEvent(position, ev); 25 | if (match.tag) { 26 | return ; 27 | } else { 28 | return Caml_option.some(Curry._1(tagger, match[0])); 29 | } 30 | }; 31 | var handler = /* EventHandlerCallback */Block.__(0, [ 32 | key, 33 | fn 34 | ]); 35 | var elem = document; 36 | var cache = Vdom.eventHandler_Register(callbacks, elem, name, handler); 37 | return (function (param) { 38 | Vdom.eventHandler_Unregister(elem, name, cache); 39 | return /* () */0; 40 | }); 41 | }; 42 | return Tea_sub.registration(key, enableCall); 43 | } 44 | 45 | function clicks($staropt$star, tagger) { 46 | var key = $staropt$star !== undefined ? $staropt$star : ""; 47 | return registerGlobal("click", key, tagger); 48 | } 49 | 50 | function moves($staropt$star, tagger) { 51 | var key = $staropt$star !== undefined ? $staropt$star : ""; 52 | return registerGlobal("mousemove", key, tagger); 53 | } 54 | 55 | function downs($staropt$star, tagger) { 56 | var key = $staropt$star !== undefined ? $staropt$star : ""; 57 | return registerGlobal("mousedown", key, tagger); 58 | } 59 | 60 | function ups($staropt$star, tagger) { 61 | var key = $staropt$star !== undefined ? $staropt$star : ""; 62 | return registerGlobal("mouseup", key, tagger); 63 | } 64 | 65 | exports.position = position; 66 | exports.registerGlobal = registerGlobal; 67 | exports.clicks = clicks; 68 | exports.moves = moves; 69 | exports.downs = downs; 70 | exports.ups = ups; 71 | /* position Not a pure module */ 72 | -------------------------------------------------------------------------------- /test-reason/test_client_drag.re: -------------------------------------------------------------------------------- 1 | open Tea; 2 | 3 | open Tea.App; 4 | 5 | open Tea.Html; 6 | 7 | open Tea.Mouse; 8 | 9 | [@bs.deriving {accessors: accessors}] 10 | type msg = 11 | | DragStart(position) 12 | | DragAt(position) 13 | | DragEnd(position); 14 | 15 | type drag = { 16 | start: position, 17 | current: position, 18 | }; 19 | 20 | type model = { 21 | position, 22 | drag: option(drag), 23 | }; 24 | 25 | let init = () => ({ 26 | position: { 27 | x: 200, 28 | y: 200, 29 | }, 30 | drag: None, 31 | }, Cmd.none); 32 | 33 | let getPosition = ({position, drag}) => 34 | switch (drag) { 35 | | None => position 36 | | Some({start, current}) => { 37 | x: position.x + current.x - start.x, 38 | y: position.y + current.y - start.y, 39 | } 40 | }; 41 | 42 | let updateHelp = ({position} as model) => 43 | fun 44 | | DragStart(xy) => {position, drag: Some({start: xy, current: xy})} 45 | | DragAt(xy) => { 46 | position, 47 | drag: 48 | switch (model.drag) { 49 | | None => None 50 | | Some(drag) => Some({...drag, current: xy}) 51 | }, 52 | } 53 | | DragEnd(_) => {position: getPosition(model), drag: None}; 54 | 55 | let update = (model, msg) => (updateHelp(model, msg), Cmd.none); 56 | 57 | let subscriptions = model => 58 | switch (model.drag) { 59 | | None => Sub.none 60 | | Some(_) => Sub.batch([Mouse.moves(dragAt), Mouse.ups(dragEnd)]) 61 | }; 62 | 63 | let px = number => string_of_int(number) ++ "px"; 64 | 65 | let onMouseDown = 66 | onCB("mousedown", "", ev => 67 | Json.Decoder.decodeEvent(Json.Decoder.map(dragStart, Mouse.position), ev) 68 | |> Result.result_to_option 69 | ); 70 | 71 | let view = model => { 72 | let realPosition = getPosition(model); 73 | div( 74 | [ 75 | onMouseDown, 76 | styles([ 77 | ("background-color", "#3C8D2F"), 78 | ("cursor", "move"), 79 | ("width", "100px"), 80 | ("height", "100px"), 81 | ("border-radius", "4px"), 82 | ("position", "absolute"), 83 | ("left", px(realPosition.x)), 84 | ("top", px(realPosition.y)), 85 | ("color", "white"), 86 | ("display", "flex"), 87 | ("align-items", "center"), 88 | ("justify-content", "center"), 89 | ]), 90 | ], 91 | [text("Drag Me!")], 92 | ); 93 | }; 94 | 95 | let main = standardProgram({init, update, view, subscriptions}); 96 | -------------------------------------------------------------------------------- /src-reason/web_location.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | . 3 | [@bs.get] [@bs.set] "href": string, 4 | [@bs.get] [@bs.set] "protocol": string, 5 | [@bs.get] [@bs.set] "host": string, 6 | [@bs.get] [@bs.set] "hostname": string, 7 | [@bs.get] [@bs.set] "port": string, 8 | [@bs.get] [@bs.set] "pathname": string, 9 | [@bs.get] [@bs.set] "search": string, 10 | [@bs.get] [@bs.set] "hash": string, 11 | [@bs.get] [@bs.set] "username": string, 12 | [@bs.get] [@bs.set] "password": string, 13 | [@bs.get] "origin": string, 14 | }; 15 | 16 | let getHref = location => location##href; 17 | 18 | let setHref = (location, value) => location##href#=value; 19 | 20 | let getProtocol = location => location##protocol; 21 | 22 | let setProtocol = (location, value) => location##protocol#=value; 23 | 24 | let getHost = location => location##host; 25 | 26 | let setHost = (location, value) => location##host#=value; 27 | 28 | let getHostname = location => location##hostname; 29 | 30 | let setHostname = (location, value) => location##hostname#=value; 31 | 32 | let getPort = location => location##port; 33 | 34 | let setPort = (location, value) => location##port#=value; 35 | 36 | let getPathname = location => location##pathname; 37 | 38 | let setPathname = (location, value) => location##pathname#=value; 39 | 40 | let getSearch = location => location##search; 41 | 42 | let setSearch = (location, value) => location##search#=value; 43 | 44 | let getHash = location => location##hash; 45 | 46 | let setHash = (location, value) => location##hash#=value; 47 | 48 | let getUsername = location => location##username; 49 | 50 | let setUsername = (location, value) => location##username#=value; 51 | 52 | let getPassword = location => location##password; 53 | 54 | let setPassword = (location, value) => location##password#=value; 55 | 56 | let getOrigin = location => location##origin; 57 | 58 | type location = { 59 | href: string, 60 | protocol: string, 61 | host: string, 62 | hostname: string, 63 | port: string, 64 | pathname: string, 65 | search: string, 66 | hash: string, 67 | username: string, 68 | password: string, 69 | origin: string, 70 | }; 71 | 72 | let asRecord = location => { 73 | href: location##href, 74 | protocol: location##protocol, 75 | host: location##host, 76 | hostname: location##hostname, 77 | port: location##port, 78 | pathname: location##pathname, 79 | search: location##search, 80 | hash: location##hash, 81 | username: location##username, 82 | password: location##password, 83 | origin: location##origin, 84 | }; 85 | -------------------------------------------------------------------------------- /test-ocaml/test_client_drag.ml: -------------------------------------------------------------------------------- 1 | open Tea 2 | open Tea.App 3 | open Tea.Html 4 | open Tea.Mouse 5 | 6 | type msg = 7 | | DragStart of position 8 | | DragAt of position 9 | | DragEnd of position 10 | [@@bs.deriving {accessors}] 11 | 12 | 13 | type drag = 14 | { start : position 15 | ; current : position 16 | } 17 | 18 | type model = 19 | { position : position 20 | ; drag : drag option 21 | } 22 | 23 | 24 | let init () = 25 | ( {position = {x = 200; y = 200}; drag = None}, Cmd.none ) 26 | 27 | let getPosition {position; drag} = 28 | match drag with 29 | | None -> 30 | position 31 | 32 | | Some {start; current} -> 33 | { x = position.x + current.x - start.x 34 | ; y = position.y + current.y - start.y 35 | } 36 | 37 | 38 | let updateHelp ({position} as model) = function 39 | | DragStart xy -> 40 | { position 41 | ; drag = Some {start = xy; current = xy} 42 | } 43 | 44 | | DragAt xy -> 45 | { position 46 | ; drag = match model.drag with 47 | | None -> None 48 | | Some drag -> Some {drag with current = xy} 49 | } 50 | 51 | | DragEnd _ -> 52 | { position = getPosition model 53 | ; drag = None 54 | } 55 | 56 | 57 | let update model msg = 58 | ( updateHelp model msg, Cmd.none ) 59 | 60 | 61 | let subscriptions model = 62 | match model.drag with 63 | | None -> 64 | Sub.none 65 | 66 | | Some _ -> 67 | Sub.batch [ Mouse.moves dragAt; Mouse.ups dragEnd ] 68 | 69 | 70 | let px number = 71 | (string_of_int number) ^ "px" 72 | 73 | let onMouseDown = 74 | onCB "mousedown" "" (fun ev -> 75 | Json.Decoder.decodeEvent (Json.Decoder.map dragStart Mouse.position) ev 76 | |> Result.result_to_option 77 | ) 78 | 79 | let view model = 80 | let realPosition = getPosition model in 81 | div 82 | [ onMouseDown 83 | ; styles 84 | [ "background-color", "#3C8D2F" 85 | ; "cursor", "move" 86 | 87 | ; "width", "100px" 88 | ; "height", "100px" 89 | ; "border-radius", "4px" 90 | ; "position", "absolute" 91 | ; "left", px realPosition.x 92 | ; "top", px realPosition.y 93 | 94 | ; "color", "white" 95 | ; "display", "flex" 96 | ; "align-items", "center" 97 | ; "justify-content", "center" 98 | ] 99 | ] 100 | [ text "Drag Me!" 101 | ] 102 | 103 | 104 | let main = 105 | standardProgram { 106 | init; 107 | update; 108 | view; 109 | subscriptions; 110 | } 111 | -------------------------------------------------------------------------------- /src-ocaml/tea_navigation.ml: -------------------------------------------------------------------------------- 1 | 2 | type ('flags, 'model, 'msg) navigationProgram = 3 | { init : 'flags -> Web.Location.location -> 'model * 'msg Tea_cmd.t 4 | ; update : 'model -> 'msg -> 'model * 'msg Tea_cmd.t 5 | ; view : 'model -> 'msg Vdom.t 6 | ; subscriptions : 'model -> 'msg Tea_sub.t 7 | ; shutdown : 'model -> 'msg Tea_cmd.t 8 | } 9 | 10 | 11 | let getLocation () = 12 | Web.Location.asRecord (Web.Document.location ()) 13 | 14 | 15 | 16 | let notifier : (Web.Location.location -> unit) option ref = ref None 17 | 18 | let notifyUrlChange () = 19 | match !notifier with 20 | | None -> () 21 | | Some cb -> 22 | let location = getLocation () in 23 | let () = cb location in 24 | () 25 | 26 | 27 | let subscribe tagger = 28 | let open Vdom in 29 | let enableCall callbacks = 30 | let notifyHandler location = 31 | callbacks.enqueue (tagger location) in 32 | let () = notifier := Some notifyHandler in 33 | let handler : Web.Node.event_cb = fun [@bs] _event -> 34 | notifyUrlChange () in 35 | let () = Web.Window.addEventListener "popstate" handler false in 36 | fun () -> Web.Window.removeEventListener "popstate" handler false 37 | in Tea_sub.registration "navigation" enableCall 38 | 39 | 40 | 41 | let replaceState url = 42 | let _ = Web.Window.History.replaceState Web.Window.window (Js.Json.parseExn "{}") "" url in 43 | () 44 | 45 | 46 | let pushState url = 47 | let _ = Web.Window.History.pushState Web.Window.window (Js.Json.parseExn "{}") "" url in 48 | () 49 | 50 | 51 | let modifyUrl url = 52 | Tea_cmd.call (fun _enqueue -> 53 | let () = replaceState url in 54 | let () = notifyUrlChange () in 55 | () 56 | ) 57 | 58 | 59 | let newUrl url = 60 | Tea_cmd.call (fun _enqueue -> 61 | let () = pushState url in 62 | let () = notifyUrlChange () in 63 | () 64 | ) 65 | 66 | 67 | let go step = 68 | Tea_cmd.call (fun _enqueue -> 69 | let _ = Web.Window.(History.go window) step in 70 | let () = notifyUrlChange () in 71 | () 72 | ) 73 | 74 | let back step = go (-step) 75 | let forward step = go step 76 | 77 | 78 | let navigationProgram locationToMessage stuff = 79 | let init flag = 80 | stuff.init flag (getLocation ()) in 81 | 82 | let subscriptions model = 83 | Tea_sub.batch 84 | [ subscribe locationToMessage 85 | ; stuff.subscriptions model 86 | ] in 87 | 88 | let open! Tea_app in 89 | program 90 | { init = init 91 | ; update = stuff.update 92 | ; view = stuff.view 93 | ; subscriptions = subscriptions 94 | ; shutdown = stuff.shutdown 95 | } 96 | -------------------------------------------------------------------------------- /src-reason/tea_navigation.re: -------------------------------------------------------------------------------- 1 | type navigationProgram('flags, 'model, 'msg) = { 2 | init: ('flags, Web.Location.location) => ('model, Tea_cmd.t('msg)), 3 | update: ('model, 'msg) => ('model, Tea_cmd.t('msg)), 4 | view: 'model => Vdom.t('msg), 5 | subscriptions: 'model => Tea_sub.t('msg), 6 | shutdown: 'model => Tea_cmd.t('msg), 7 | }; 8 | 9 | let getLocation = () => Web.Location.asRecord(Web.Document.location()); 10 | 11 | let notifier: ref(option(Web.Location.location => unit)) = ref(None); 12 | 13 | let notifyUrlChange = () => 14 | switch (notifier^) { 15 | | None => () 16 | | Some(cb) => 17 | let location = getLocation(); 18 | let () = cb(location); 19 | (); 20 | }; 21 | 22 | let subscribe = tagger => { 23 | open Vdom; 24 | let enableCall = callbacks => { 25 | let notifyHandler = location => callbacks.enqueue(tagger(location)); 26 | let () = notifier := Some(notifyHandler); 27 | let handler: Web.Node.event_cb = (. _event) => notifyUrlChange(); 28 | let () = Web.Window.addEventListener("popstate", handler, false); 29 | () => Web.Window.removeEventListener("popstate", handler, false); 30 | }; 31 | Tea_sub.registration("navigation", enableCall); 32 | }; 33 | 34 | let replaceState = url => { 35 | let _ = 36 | Web.Window.History.replaceState( 37 | Web.Window.window, 38 | Js.Json.parseExn("{}"), 39 | "", 40 | url, 41 | ); 42 | (); 43 | }; 44 | 45 | let pushState = url => { 46 | let _ = 47 | Web.Window.History.pushState( 48 | Web.Window.window, 49 | Js.Json.parseExn("{}"), 50 | "", 51 | url, 52 | ); 53 | (); 54 | }; 55 | 56 | let modifyUrl = url => 57 | Tea_cmd.call(_enqueue => { 58 | let () = replaceState(url); 59 | let () = notifyUrlChange(); 60 | (); 61 | }); 62 | 63 | let newUrl = url => 64 | Tea_cmd.call(_enqueue => { 65 | let () = pushState(url); 66 | let () = notifyUrlChange(); 67 | (); 68 | }); 69 | 70 | let go = step => 71 | Tea_cmd.call(_enqueue => { 72 | let _ = Web.Window.(History.go(window))(step); 73 | let () = notifyUrlChange(); 74 | (); 75 | }); 76 | 77 | let back = step => go(- step); 78 | let forward = step => go(step); 79 | 80 | let navigationProgram = (locationToMessage, stuff) => { 81 | let init = flag => stuff.init(flag, getLocation()); 82 | 83 | let subscriptions = model => 84 | Tea_sub.batch([ 85 | subscribe(locationToMessage), 86 | stuff.subscriptions(model), 87 | ]); 88 | 89 | open! Tea_app; 90 | program({ 91 | init, 92 | update: stuff.update, 93 | view: stuff.view, 94 | subscriptions, 95 | shutdown: stuff.shutdown, 96 | }); 97 | }; 98 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_counter.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("../src-ocaml/vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Tea_app = require("../src-ocaml/tea_app.js"); 7 | var Tea_html = require("../src-ocaml/tea_html.js"); 8 | 9 | function update(model, param) { 10 | if (typeof param === "number") { 11 | switch (param) { 12 | case /* Increment */0 : 13 | return model + 1 | 0; 14 | case /* Decrement */1 : 15 | return model - 1 | 0; 16 | case /* Reset */2 : 17 | return 0; 18 | 19 | } 20 | } else { 21 | return param[0]; 22 | } 23 | } 24 | 25 | function view_button(title, msg) { 26 | return Tea_html.button(undefined, undefined, /* :: */[ 27 | Vdom.onMsg("click", msg), 28 | /* [] */0 29 | ], /* :: */[ 30 | /* Text */Block.__(1, [title]), 31 | /* [] */0 32 | ]); 33 | } 34 | 35 | function view(model) { 36 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 37 | Tea_html.span(undefined, undefined, /* :: */[ 38 | Vdom.style("text-weight", "bold"), 39 | /* [] */0 40 | ], /* :: */[ 41 | /* Text */Block.__(1, [String(model)]), 42 | /* [] */0 43 | ]), 44 | /* :: */[ 45 | Tea_html.br(/* [] */0), 46 | /* :: */[ 47 | view_button("Increment", model >= 3 ? /* Decrement */1 : /* Increment */0), 48 | /* :: */[ 49 | Tea_html.br(/* [] */0), 50 | /* :: */[ 51 | view_button("Decrement", /* Decrement */1), 52 | /* :: */[ 53 | Tea_html.br(/* [] */0), 54 | /* :: */[ 55 | view_button("Set to 42", /* Set */[42]), 56 | /* :: */[ 57 | Tea_html.br(/* [] */0), 58 | /* :: */[ 59 | model !== 0 ? view_button("Reset", /* Reset */2) : Tea_html.noNode, 60 | /* [] */0 61 | ] 62 | ] 63 | ] 64 | ] 65 | ] 66 | ] 67 | ] 68 | ] 69 | ]); 70 | } 71 | 72 | var partial_arg = { 73 | model: 4, 74 | update: update, 75 | view: view 76 | }; 77 | 78 | function main(param, param$1) { 79 | return Tea_app.beginnerProgram(partial_arg, param, param$1); 80 | } 81 | 82 | exports.update = update; 83 | exports.view_button = view_button; 84 | exports.view = view; 85 | exports.main = main; 86 | /* Tea_html Not a pure module */ 87 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_window.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function history(param) { 6 | return window.history; 7 | } 8 | 9 | function localStorage(param) { 10 | return window.localStorage; 11 | } 12 | 13 | function $$location(param) { 14 | return window.location; 15 | } 16 | 17 | function requestAnimationFrame(callback) { 18 | return window.requestAnimationFrame(callback); 19 | } 20 | 21 | function cancelAnimationFrame(id) { 22 | return window.cancelAnimationFrame(id); 23 | } 24 | 25 | function $$clearTimeout(id) { 26 | return window.clearTimeout(id); 27 | } 28 | 29 | function $$setInterval(cb, msTime) { 30 | return window.setInterval(cb, msTime); 31 | } 32 | 33 | function $$setTimeout(cb, msTime) { 34 | return window.setTimeout(cb, msTime); 35 | } 36 | 37 | function addEventListener(typ, listener, options) { 38 | return window.addEventListener(typ, listener, options); 39 | } 40 | 41 | function removeEventListener(typ, listener, options) { 42 | return window.removeEventListener(typ, listener, options); 43 | } 44 | 45 | function requestAnimationFrame_polyfill(param) { 46 | return (// requestAnimationFrame polyfill 47 | (function() { 48 | var lastTime = 0; 49 | var vendors = ['ms', 'moz', 'webkit', 'o']; 50 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 51 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 52 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 53 | || window[vendors[x]+'CancelRequestAnimationFrame']; 54 | } 55 | 56 | if (!window.requestAnimationFrame) 57 | window.requestAnimationFrame = function(callback, element) { 58 | var currTime = new Date().getTime(); 59 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 60 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 61 | timeToCall); 62 | lastTime = currTime + timeToCall; 63 | return id; 64 | }; 65 | 66 | if (!window.cancelAnimationFrame) 67 | window.cancelAnimationFrame = function(id) { 68 | clearTimeout(id); 69 | }; 70 | }())); 71 | } 72 | 73 | var $$History = /* alias */0; 74 | 75 | var LocalStorage = /* alias */0; 76 | 77 | exports.$$History = $$History; 78 | exports.LocalStorage = LocalStorage; 79 | exports.history = history; 80 | exports.localStorage = localStorage; 81 | exports.$$location = $$location; 82 | exports.requestAnimationFrame = requestAnimationFrame; 83 | exports.cancelAnimationFrame = cancelAnimationFrame; 84 | exports.$$clearTimeout = $$clearTimeout; 85 | exports.$$setInterval = $$setInterval; 86 | exports.$$setTimeout = $$setTimeout; 87 | exports.addEventListener = addEventListener; 88 | exports.removeEventListener = removeEventListener; 89 | exports.requestAnimationFrame_polyfill = requestAnimationFrame_polyfill; 90 | /* No side effect */ 91 | -------------------------------------------------------------------------------- /src-ocaml/tea_ex.ml: -------------------------------------------------------------------------------- 1 | let render_event ?(key= "") msg = 2 | let open Vdom in 3 | let enableCall callbacks = 4 | let () = callbacks.on ((AddRenderMsg (msg))[@explicit_arity ]) in 5 | fun () -> callbacks.on ((RemoveRenderMsg (msg))[@explicit_arity ]) in 6 | Tea_sub.registration key enableCall 7 | module LocalStorage = 8 | struct 9 | open Tea_task 10 | open Tea_result 11 | let length = 12 | nativeBinding 13 | (fun cb -> 14 | match Web.Window.LocalStorage.length Web.Window.window with 15 | | None -> 16 | cb ((Error ("localStorage is not available")) 17 | [@explicit_arity ]) 18 | | ((Some (value))[@explicit_arity ]) -> 19 | cb ((Ok (value))[@explicit_arity ])) 20 | let clear = 21 | nativeBinding 22 | (fun cb -> 23 | match Web.Window.LocalStorage.clear Web.Window.window with 24 | | None -> 25 | cb ((Error ("localStorage is not available")) 26 | [@explicit_arity ]) 27 | | ((Some (value))[@explicit_arity ]) -> 28 | cb ((Ok (value))[@explicit_arity ])) 29 | let clearCmd () = Tea_task.attemptOpt (fun _ -> None) clear 30 | let key idx = 31 | nativeBinding 32 | (fun cb -> 33 | match Web.Window.LocalStorage.key Web.Window.window idx with 34 | | None -> 35 | cb ((Error ("localStorage is not available")) 36 | [@explicit_arity ]) 37 | | ((Some (value))[@explicit_arity ]) -> 38 | cb ((Ok (value))[@explicit_arity ])) 39 | let getItem key = 40 | nativeBinding 41 | (fun cb -> 42 | match Web.Window.LocalStorage.getItem Web.Window.window key with 43 | | None -> 44 | cb ((Error ("localStorage is not available")) 45 | [@explicit_arity ]) 46 | | ((Some (value))[@explicit_arity ]) -> 47 | cb ((Ok (value))[@explicit_arity ])) 48 | let removeItem key = 49 | nativeBinding 50 | (fun cb -> 51 | match Web.Window.LocalStorage.removeItem Web.Window.window key 52 | with 53 | | None -> 54 | cb ((Error ("localStorage is not available")) 55 | [@explicit_arity ]) 56 | | ((Some (value))[@explicit_arity ]) -> 57 | cb ((Ok (value))[@explicit_arity ])) 58 | let removeItemCmd key = 59 | Tea_task.attemptOpt (fun _ -> None) (removeItem key) 60 | let setItem key value = 61 | nativeBinding 62 | (fun cb -> 63 | match Web.Window.LocalStorage.setItem Web.Window.window key value 64 | with 65 | | None -> 66 | cb ((Error ("localStorage is not available")) 67 | [@explicit_arity ]) 68 | | ((Some (()))[@explicit_arity ]) -> 69 | cb ((Ok (()))[@explicit_arity ])) 70 | let setItemCmd key value = 71 | Tea_task.attemptOpt (fun _ -> None) (setItem key value) 72 | end -------------------------------------------------------------------------------- /src-ocaml/web_window.ml: -------------------------------------------------------------------------------- 1 | 2 | (* TODO: Polyfill window if it is missing, like on node or in native *) 3 | 4 | module History = Web_window_history 5 | 6 | module LocalStorage = Web_window_localstorage 7 | 8 | type timeoutHandlerID = int 9 | 10 | type t = < 11 | history : History.t Js.Undefined.t [@bs.get]; 12 | location : Web_location.t [@bs.get]; 13 | clearTimeout : timeoutHandlerID -> unit [@bs.meth]; 14 | requestAnimationFrame : (float -> unit) -> int [@bs.meth]; 15 | cancelAnimationFrame : int -> unit [@bs.meth]; 16 | setInterval : (unit -> unit) -> float -> timeoutHandlerID [@bs.meth]; 17 | setTimeout : (unit -> unit) -> float -> timeoutHandlerID [@bs.meth]; 18 | addEventListener : string -> Web_node.t Web_event.cb -> Web_event.options -> unit [@bs.meth]; 19 | removeEventListener : string -> Web_node.t Web_event.cb -> Web_event.options -> unit [@bs.meth]; 20 | localStorage : LocalStorage.t Js.Undefined.t [@bs.get]; 21 | > Js.t 22 | 23 | external window : t = "window" [@@bs.val] 24 | 25 | 26 | let history () = window##history 27 | 28 | let localStorage () = window##localStorage 29 | 30 | let location () = window##location 31 | 32 | (* requestAnimationFrame callback is a float timestamp in milliseconds *) 33 | let requestAnimationFrame callback = window##requestAnimationFrame callback 34 | 35 | let cancelAnimationFrame id = window##cancelAnimationFrame id 36 | 37 | let clearTimeout id = window##clearTimeout id 38 | 39 | let setInterval cb msTime = window##setInterval cb msTime 40 | 41 | let setTimeout cb msTime = window##setTimeout cb msTime 42 | 43 | let addEventListener typ listener options = window##addEventListener typ listener options 44 | 45 | let removeEventListener typ listener options = window##removeEventListener typ listener options 46 | 47 | 48 | 49 | (* Polyfills *) 50 | 51 | let requestAnimationFrame_polyfill : unit -> unit = fun () -> 52 | [%bs.raw{| 53 | // requestAnimationFrame polyfill 54 | (function() { 55 | var lastTime = 0; 56 | var vendors = ['ms', 'moz', 'webkit', 'o']; 57 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 58 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 59 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 60 | || window[vendors[x]+'CancelRequestAnimationFrame']; 61 | } 62 | 63 | if (!window.requestAnimationFrame) 64 | window.requestAnimationFrame = function(callback, element) { 65 | var currTime = new Date().getTime(); 66 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 67 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 68 | timeToCall); 69 | lastTime = currTime + timeToCall; 70 | return id; 71 | }; 72 | 73 | if (!window.cancelAnimationFrame) 74 | window.cancelAnimationFrame = function(id) { 75 | clearTimeout(id); 76 | }; 77 | }()) 78 | |}] 79 | -------------------------------------------------------------------------------- /src-reason/web_window.re: -------------------------------------------------------------------------------- 1 | /* TODO: Polyfill window if it is missing, like on node or in native */ 2 | module History = Web_window_history; 3 | 4 | module LocalStorage = Web_window_localstorage; 5 | 6 | type timeoutHandlerID = int; 7 | 8 | type t = { 9 | . 10 | [@bs.get] "history": Js.Undefined.t(History.t), 11 | [@bs.get] "location": Web_location.t, 12 | [@bs.meth] "clearTimeout": timeoutHandlerID => unit, 13 | [@bs.meth] "requestAnimationFrame": (float => unit) => int, 14 | [@bs.meth] "cancelAnimationFrame": int => unit, 15 | [@bs.meth] "setInterval": (unit => unit, float) => timeoutHandlerID, 16 | [@bs.meth] "setTimeout": (unit => unit, float) => timeoutHandlerID, 17 | [@bs.meth] 18 | "addEventListener": 19 | (string, Web_event.cb(Web_node.t), Web_event.options) => unit, 20 | [@bs.meth] 21 | "removeEventListener": 22 | (string, Web_event.cb(Web_node.t), Web_event.options) => unit, 23 | [@bs.get] "localStorage": Js.Undefined.t(LocalStorage.t), 24 | }; 25 | 26 | [@bs.val] external window : t = "window"; 27 | 28 | let history = () => window##history; 29 | 30 | let localStorage = () => window##localStorage; 31 | 32 | let location = () => window##location; 33 | 34 | /* requestAnimationFrame callback is a float timestamp in milliseconds */ 35 | let requestAnimationFrame = callback => 36 | window##requestAnimationFrame(callback); 37 | 38 | let cancelAnimationFrame = id => window##cancelAnimationFrame(id); 39 | 40 | let clearTimeout = id => window##clearTimeout(id); 41 | 42 | let setInterval = (cb, msTime) => window##setInterval(cb, msTime); 43 | 44 | let setTimeout = (cb, msTime) => window##setTimeout(cb, msTime); 45 | 46 | let addEventListener = (typ, listener, options) => 47 | window##addEventListener(typ, listener, options); 48 | 49 | let removeEventListener = (typ, listener, options) => 50 | window##removeEventListener(typ, listener, options); 51 | 52 | /* Polyfills */ 53 | let requestAnimationFrame_polyfill: unit => unit = 54 | () => [%bs.raw 55 | {| 56 | // requestAnimationFrame polyfill 57 | (function() { 58 | var lastTime = 0; 59 | var vendors = ['ms', 'moz', 'webkit', 'o']; 60 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 61 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 62 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 63 | || window[vendors[x]+'CancelRequestAnimationFrame']; 64 | } 65 | 66 | if (!window.requestAnimationFrame) 67 | window.requestAnimationFrame = function(callback, element) { 68 | var currTime = new Date().getTime(); 69 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 70 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 71 | timeToCall); 72 | lastTime = currTime + timeToCall; 73 | return id; 74 | }; 75 | 76 | if (!window.cancelAnimationFrame) 77 | window.cancelAnimationFrame = function(id) { 78 | clearTimeout(id); 79 | }; 80 | }()) 81 | |} 82 | ]; 83 | -------------------------------------------------------------------------------- /src-ocaml/tea_random.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | let () = Random.self_init () 5 | 6 | 7 | let minInt = min_int 8 | 9 | let maxInt = max_int 10 | 11 | let minFloat = min_float 12 | 13 | let maxFloat = max_float 14 | 15 | 16 | type 'typ t = 17 | | Generator of (Random.State.t -> 'typ) 18 | 19 | 20 | let bool = 21 | Generator (fun state -> Random.State.bool state) 22 | 23 | 24 | let int min max = 25 | let (min, max) = if min < max then (min, max) else (max, min) in 26 | Generator (fun state -> min + Random.State.int state (max - min + 1)) 27 | 28 | 29 | let float min max = 30 | Generator (fun state -> min +. Random.State.float state (max -. min)) 31 | 32 | 33 | let list count (Generator genCmd) = 34 | let rec buildList state i acc = if i > 0 then buildList state (i - 1) (genCmd state :: acc) else acc in 35 | Generator (fun state -> buildList state count []) 36 | 37 | 38 | let map func (Generator genCmd) = 39 | Generator 40 | (fun state -> 41 | func (genCmd state) 42 | ) 43 | 44 | let map2 func (Generator genCmd1) (Generator genCmd2) = 45 | Generator 46 | (fun state -> 47 | let res1 = genCmd1 state in 48 | let res2 = genCmd2 state in 49 | func res1 res2 50 | ) 51 | 52 | let map3 func (Generator genCmd1) (Generator genCmd2) (Generator genCmd3) = 53 | Generator 54 | (fun state -> 55 | let res1 = genCmd1 state in 56 | let res2 = genCmd2 state in 57 | let res3 = genCmd3 state in 58 | func res1 res2 res3 59 | ) 60 | 61 | let map4 func (Generator genCmd1) (Generator genCmd2) (Generator genCmd3) (Generator genCmd4) = 62 | Generator 63 | (fun state -> 64 | let res1 = genCmd1 state in 65 | let res2 = genCmd2 state in 66 | let res3 = genCmd3 state in 67 | let res4 = genCmd4 state in 68 | func res1 res2 res3 res4 69 | ) 70 | 71 | let map5 func (Generator genCmd1) (Generator genCmd2) (Generator genCmd3) (Generator genCmd4) (Generator genCmd5) = 72 | Generator 73 | (fun state -> 74 | let res1 = genCmd1 state in 75 | let res2 = genCmd2 state in 76 | let res3 = genCmd3 state in 77 | let res4 = genCmd4 state in 78 | let res5 = genCmd5 state in 79 | func res1 res2 res3 res4 res5 80 | ) 81 | 82 | let andThen func (Generator genCmd) = 83 | Generator 84 | (fun state -> 85 | let res = genCmd state in 86 | let (Generator userGen) = func res in 87 | userGen state 88 | ) 89 | 90 | let pair gen1 gen2 = 91 | map2 (fun a b -> a, b) gen1 gen2 92 | 93 | 94 | 95 | let generate tagger (Generator genCmd) = 96 | Tea_cmd.call (fun callbacks -> 97 | let state = Random.get_state () in 98 | let genValue = genCmd state in 99 | let () = Random.set_state state in 100 | let open Vdom in 101 | !callbacks.enqueue (tagger genValue) 102 | ) 103 | 104 | 105 | (* Generate Values Manually *) 106 | 107 | type seed = 108 | | Seed of Random.State.t 109 | 110 | let step (Generator genCmd) (Seed state) = 111 | let newState = Random.State.copy state in 112 | genCmd newState, Seed newState 113 | 114 | let initialSeed seed = 115 | Seed (Random.State.make [| seed |]) 116 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_result.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Caml_option = require("bs-platform/lib/js/caml_option.js"); 6 | var Caml_builtin_exceptions = require("bs-platform/lib/js/caml_builtin_exceptions.js"); 7 | 8 | function result_to_option(param) { 9 | if (param.tag) { 10 | return ; 11 | } else { 12 | return Caml_option.some(param[0]); 13 | } 14 | } 15 | 16 | function option_of_result(param) { 17 | if (param.tag) { 18 | return ; 19 | } else { 20 | return Caml_option.some(param[0]); 21 | } 22 | } 23 | 24 | function ok(param) { 25 | if (param.tag) { 26 | return ; 27 | } else { 28 | return Caml_option.some(param[0]); 29 | } 30 | } 31 | 32 | function error(param) { 33 | if (param.tag) { 34 | return Caml_option.some(param[0]); 35 | } 36 | 37 | } 38 | 39 | function last_of(_param) { 40 | while(true) { 41 | var param = _param; 42 | if (param) { 43 | var tl = param[1]; 44 | var last = param[0]; 45 | if (tl && !last.tag) { 46 | _param = tl; 47 | continue ; 48 | } else { 49 | return last; 50 | } 51 | } else { 52 | throw [ 53 | Caml_builtin_exceptions.failure, 54 | "`Tea.Result.do` must never be passed the empty list" 55 | ]; 56 | } 57 | }; 58 | } 59 | 60 | function accumulate(param) { 61 | if (param) { 62 | var tl = param[1]; 63 | var last = param[0]; 64 | if (tl) { 65 | if (last.tag) { 66 | return last; 67 | } else { 68 | var e = accumulate(tl); 69 | if (e.tag) { 70 | return e; 71 | } else { 72 | return /* Ok */Block.__(0, [/* :: */[ 73 | last[0], 74 | e[0] 75 | ]]); 76 | } 77 | } 78 | } else if (last.tag) { 79 | return last; 80 | } else { 81 | return /* Ok */Block.__(0, [/* :: */[ 82 | last[0], 83 | /* [] */0 84 | ]]); 85 | } 86 | } else { 87 | return /* Ok */Block.__(0, [/* [] */0]); 88 | } 89 | } 90 | 91 | function first(fst, e) { 92 | if (e.tag) { 93 | return e; 94 | } else { 95 | return fst; 96 | } 97 | } 98 | 99 | function error_of_any(_param) { 100 | while(true) { 101 | var param = _param; 102 | if (param) { 103 | var hd = param[0]; 104 | if (hd.tag) { 105 | return Caml_option.some(hd[0]); 106 | } else { 107 | _param = param[1]; 108 | continue ; 109 | } 110 | } else { 111 | return ; 112 | } 113 | }; 114 | } 115 | 116 | function error_of_first(fst, param) { 117 | if (param.tag) { 118 | return Caml_option.some(param[0]); 119 | } else { 120 | return error(fst); 121 | } 122 | } 123 | 124 | exports.result_to_option = result_to_option; 125 | exports.option_of_result = option_of_result; 126 | exports.ok = ok; 127 | exports.error = error; 128 | exports.last_of = last_of; 129 | exports.accumulate = accumulate; 130 | exports.first = first; 131 | exports.error_of_any = error_of_any; 132 | exports.error_of_first = error_of_first; 133 | /* No side effect */ 134 | -------------------------------------------------------------------------------- /src-reason/tea_random.re: -------------------------------------------------------------------------------- 1 | let () = Random.self_init(); 2 | 3 | let minInt = min_int; 4 | 5 | let maxInt = max_int; 6 | 7 | let minFloat = min_float; 8 | 9 | let maxFloat = max_float; 10 | 11 | type t('typ) = 12 | | Generator(Random.State.t => 'typ); 13 | 14 | let bool = Generator(state => Random.State.bool(state)); 15 | 16 | let int = (min, max) => 17 | let (min, max) = (min < max) ? (min, max) : (max, min); 18 | Generator(state => min + Random.State.int(state, max - min + 1)); 19 | 20 | let float = (min, max) => 21 | Generator(state => min +. Random.State.float(state, max -. min)); 22 | 23 | let list = (count, Generator(genCmd)) => { 24 | let rec buildList = (state, i, acc) => 25 | if (i > 0) { 26 | buildList(state, i - 1, [genCmd(state), ...acc]); 27 | } else { 28 | acc; 29 | }; 30 | Generator(state => buildList(state, count, [])); 31 | }; 32 | 33 | let map = (func, Generator(genCmd)) => 34 | Generator(state => func(genCmd(state))); 35 | 36 | let map2 = (func, Generator(genCmd1), Generator(genCmd2)) => 37 | Generator( 38 | state => { 39 | let res1 = genCmd1(state); 40 | let res2 = genCmd2(state); 41 | func(res1, res2); 42 | }, 43 | ); 44 | 45 | let map3 = 46 | (func, Generator(genCmd1), Generator(genCmd2), Generator(genCmd3)) => 47 | Generator( 48 | state => { 49 | let res1 = genCmd1(state); 50 | let res2 = genCmd2(state); 51 | let res3 = genCmd3(state); 52 | func(res1, res2, res3); 53 | }, 54 | ); 55 | 56 | let map4 = 57 | ( 58 | func, 59 | Generator(genCmd1), 60 | Generator(genCmd2), 61 | Generator(genCmd3), 62 | Generator(genCmd4), 63 | ) => 64 | Generator( 65 | state => { 66 | let res1 = genCmd1(state); 67 | let res2 = genCmd2(state); 68 | let res3 = genCmd3(state); 69 | let res4 = genCmd4(state); 70 | func(res1, res2, res3, res4); 71 | }, 72 | ); 73 | 74 | let map5 = 75 | ( 76 | func, 77 | Generator(genCmd1), 78 | Generator(genCmd2), 79 | Generator(genCmd3), 80 | Generator(genCmd4), 81 | Generator(genCmd5), 82 | ) => 83 | Generator( 84 | state => { 85 | let res1 = genCmd1(state); 86 | let res2 = genCmd2(state); 87 | let res3 = genCmd3(state); 88 | let res4 = genCmd4(state); 89 | let res5 = genCmd5(state); 90 | func(res1, res2, res3, res4, res5); 91 | }, 92 | ); 93 | 94 | let andThen = (func, Generator(genCmd)) => 95 | Generator( 96 | state => { 97 | let res = genCmd(state); 98 | let Generator(userGen) = func(res); 99 | userGen(state); 100 | }, 101 | ); 102 | 103 | let pair = (gen1, gen2) => map2((a, b) => (a, b), gen1, gen2); 104 | 105 | let generate = (tagger, Generator(genCmd)) => 106 | Tea_cmd.call(callbacks => { 107 | let state = Random.get_state(); 108 | let genValue = genCmd(state); 109 | let () = Random.set_state(state); 110 | Vdom.(callbacks^.enqueue(tagger(genValue))); 111 | }); 112 | 113 | /* Generate Values Manually */ 114 | 115 | type seed = 116 | | Seed(Random.State.t); 117 | 118 | let step = (Generator(genCmd), Seed(state)) => { 119 | let newState = Random.State.copy(state); 120 | (genCmd(newState), Seed(newState)); 121 | }; 122 | 123 | let initialSeed = seed => Seed(Random.State.make([|seed|])); 124 | -------------------------------------------------------------------------------- /doc/integration_react-bs-tea.md: -------------------------------------------------------------------------------- 1 | ## ReasonReact Integration Example 2 | 3 | This example describes how to integrate the Bucklescript TEA framework into a ReasonReact component. This is useful if you want to migrate an existing ReasonReact project to use Bucklescript TEA, or simply use it for a single component. You'll soon learn to love "The Elm Architecture" way of designing frontend components and their logic. 4 | 5 | See issue https://github.com/OvermindDL1/bucklescript-tea/issues/121 for the beginning of this discussion. The resulting code can be seen below. 6 | 7 | The process is as follows: 8 | 1. Import the Reason/OCaml Bucklescript-TEA component. 9 | 2. Get access to the HTML DOM document object, to later be able to get a reference to the React HTML element. 10 | 3. Use a ReasonReact `reducerComponent` so that you can more easily store the TEA component state and manage its lifecycle. 11 | 4. When the React component mounts, the `didMount` callback is called and the `RenderMain` action is sent. 12 | 5. Because there are side-effects in the `RenderMain` action handler, use the `UpdateWithSideEffects` method to change the current state from `Start` to `Initialized`. 13 | 6. The Bucklescript-TEA component is mounted on the DIV HTML element using a predefined HTML `id` - in this case `Tea`. 14 | 7. The ReasonReact component can now be used in an existing ReasonReact project, and using React props for example the TEA component can be interacted with via the `pushMsg` method to pass actions through to the TEA component, like `Reset` as seen in the example. 15 | 16 | ```reason 17 | open Tea; /* Reason/OCaml Bucklescript-TEA component, as seen in existing examples */ 18 | 19 | /* 20 | EXAMPLE CONSOLE LOG (using BuckleScripts Js module) 21 | */ 22 | Js.log("Hello, from BuckleScript and Reason!"); 23 | 24 | type document; /* abstract type for a document object */ 25 | [@bs.send] external getElementById: (document, string) => Js.null_undefined(Web.Node.t) = "getElementById"; 26 | [@bs.val] external doc: document = "document"; 27 | 28 | type action = RenderMain; 29 | type state = Start | Initialized; 30 | 31 | // Define interface method types and create mutable references 32 | type pushMsg = (msg) => unit; 33 | let pushMsg: pushMsg = (msg) => (); 34 | let pushMsgRef = ref(pushMsg); 35 | 36 | type shutdown = (unit) => unit; 37 | let shutdown: shutdown = () => (); 38 | 39 | type getHtmlString = (unit) => string; 40 | let getHtmlString: getHtmlString = () => ""; 41 | 42 | type interface = (unit) => Tea_app.programInterface(msg); 43 | 44 | [@bs.obj] 45 | external makeProgramInterface: 46 | ( 47 | ~pushMsg: 'msg => unit, 48 | ~shutdown: unit => unit, 49 | ~getHtmlString: unit => string 50 | ) => 51 | Tea_app.programInterface('msg) = 52 | ""; 53 | 54 | let interfaceRef = ref(makeProgramInterface(~pushMsg=(msg)=>(), ~shutdown=()=>(), ~getHtmlString=()=>"")); 55 | 56 | let id = "Tea"; 57 | let component = ReasonReact.reducerComponent(id); 58 | let make = (_children) => { 59 | ...component, 60 | initialState: _state => Start, 61 | reducer: (_action, _state) => 62 | switch (_action) { 63 | | RenderMain => UpdateWithSideEffects(Initialized, (_) => { 64 | { 65 | interfaceRef := main(getElementById(doc, id))(); 66 | pushMsgRef := (interfaceRef^)##pushMsg; 67 | pushMsgRef^(Reset); 68 | } 69 | }) 70 | }, 71 | didMount: _self => _self.send(RenderMain), 72 | render: _self =>
73 | } 74 | ``` -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_counter_debug_beginner.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("../src-ocaml/vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Tea_html = require("../src-ocaml/tea_html.js"); 7 | var Tea_debug = require("../src-ocaml/tea_debug.js"); 8 | 9 | function string_of_msg(param) { 10 | if (typeof param === "number") { 11 | switch (param) { 12 | case /* Increment */0 : 13 | return "Increment"; 14 | case /* Decrement */1 : 15 | return "Decrement"; 16 | case /* Reset */2 : 17 | return "Reset"; 18 | 19 | } 20 | } else { 21 | return "Set"; 22 | } 23 | } 24 | 25 | function update(model, param) { 26 | if (typeof param === "number") { 27 | switch (param) { 28 | case /* Increment */0 : 29 | return model + 1 | 0; 30 | case /* Decrement */1 : 31 | return model - 1 | 0; 32 | case /* Reset */2 : 33 | return 0; 34 | 35 | } 36 | } else { 37 | return param[0]; 38 | } 39 | } 40 | 41 | function view_button(title, msg) { 42 | return Tea_html.button(undefined, undefined, /* :: */[ 43 | Vdom.onMsg("click", msg), 44 | /* [] */0 45 | ], /* :: */[ 46 | /* Text */Block.__(1, [title]), 47 | /* [] */0 48 | ]); 49 | } 50 | 51 | function view(model) { 52 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 53 | Tea_html.span(undefined, undefined, /* :: */[ 54 | Vdom.style("text-weight", "bold"), 55 | /* [] */0 56 | ], /* :: */[ 57 | /* Text */Block.__(1, [String(model)]), 58 | /* [] */0 59 | ]), 60 | /* :: */[ 61 | Tea_html.br(/* [] */0), 62 | /* :: */[ 63 | view_button("Increment", model >= 3 ? /* Decrement */1 : /* Increment */0), 64 | /* :: */[ 65 | Tea_html.br(/* [] */0), 66 | /* :: */[ 67 | view_button("Decrement", /* Decrement */1), 68 | /* :: */[ 69 | Tea_html.br(/* [] */0), 70 | /* :: */[ 71 | view_button("Set to 42", /* Set */[42]), 72 | /* :: */[ 73 | Tea_html.br(/* [] */0), 74 | /* :: */[ 75 | model !== 0 ? view_button("Reset", /* Reset */2) : Tea_html.noNode, 76 | /* [] */0 77 | ] 78 | ] 79 | ] 80 | ] 81 | ] 82 | ] 83 | ] 84 | ] 85 | ]); 86 | } 87 | 88 | var partial_arg = { 89 | model: 4, 90 | update: update, 91 | view: view 92 | }; 93 | 94 | function main(param, param$1) { 95 | return Tea_debug.beginnerProgram(partial_arg, string_of_msg, param, param$1); 96 | } 97 | 98 | exports.string_of_msg = string_of_msg; 99 | exports.update = update; 100 | exports.view_button = view_button; 101 | exports.view = view; 102 | exports.main = main; 103 | /* Tea_html Not a pure module */ 104 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_http_task.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("../src-ocaml/vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Curry = require("bs-platform/lib/js/curry.js"); 7 | var Tea_ex = require("../src-ocaml/tea_ex.js"); 8 | var Tea_html = require("../src-ocaml/tea_html.js"); 9 | var Tea_http = require("../src-ocaml/tea_http.js"); 10 | var Tea_task = require("../src-ocaml/tea_task.js"); 11 | var Tea_debug = require("../src-ocaml/tea_debug.js"); 12 | 13 | function gotResponse(param_0) { 14 | return /* GotResponse */[param_0]; 15 | } 16 | 17 | function update(model, param) { 18 | if (param) { 19 | return /* tuple */[ 20 | param[0][0], 21 | /* NoCmd */0 22 | ]; 23 | } else { 24 | return /* tuple */[ 25 | model, 26 | Tea_task.attempt(gotResponse, Tea_task.andThen((function (param) { 27 | return /* Task */[(function (cb) { 28 | return Curry._1(cb, /* Ok */Block.__(0, ["both saved"])); 29 | })]; 30 | }), Tea_task.andThen((function (res) { 31 | return Tea_ex.LocalStorage.setItem("todo-2", res); 32 | }), Tea_task.andThen((function (param) { 33 | return Tea_task.mapError(Tea_http.string_of_error, Tea_http.toTask(Tea_http.getString("https://jsonplaceholder.typicode.com/todos/2"))); 34 | }), Tea_task.andThen((function (res) { 35 | return Tea_ex.LocalStorage.setItem("todo-1", res); 36 | }), Tea_task.mapError(Tea_http.string_of_error, Tea_http.toTask(Tea_http.getString("https://jsonplaceholder.typicode.com/todos/1")))))))) 37 | ]; 38 | } 39 | } 40 | 41 | function view(model) { 42 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 43 | Tea_html.button(undefined, undefined, /* :: */[ 44 | Vdom.onMsg("click", /* Req */0), 45 | /* [] */0 46 | ], /* :: */[ 47 | /* Text */Block.__(1, ["execute"]), 48 | /* [] */0 49 | ]), 50 | /* :: */[ 51 | /* Text */Block.__(1, [model]), 52 | /* [] */0 53 | ] 54 | ]); 55 | } 56 | 57 | function som(param) { 58 | if (param) { 59 | if (param[0].tag) { 60 | return "GotResponse Error"; 61 | } else { 62 | return "GotResponse Ok"; 63 | } 64 | } else { 65 | return "Req"; 66 | } 67 | } 68 | 69 | function partial_arg_init(param) { 70 | return /* tuple */[ 71 | "nothing", 72 | /* NoCmd */0 73 | ]; 74 | } 75 | 76 | function partial_arg_subscriptions(param) { 77 | return /* NoSub */0; 78 | } 79 | 80 | var partial_arg = { 81 | init: partial_arg_init, 82 | update: update, 83 | view: view, 84 | subscriptions: partial_arg_subscriptions 85 | }; 86 | 87 | function main(param, param$1) { 88 | return Tea_debug.standardProgram(partial_arg, som, param, param$1); 89 | } 90 | 91 | var req = /* Req */0; 92 | 93 | exports.gotResponse = gotResponse; 94 | exports.req = req; 95 | exports.update = update; 96 | exports.view = view; 97 | exports.som = som; 98 | exports.main = main; 99 | /* Tea_html Not a pure module */ 100 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_location.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function getHref($$location) { 6 | return $$location.href; 7 | } 8 | 9 | function setHref($$location, value) { 10 | $$location.href = value; 11 | return /* () */0; 12 | } 13 | 14 | function getProtocol($$location) { 15 | return $$location.protocol; 16 | } 17 | 18 | function setProtocol($$location, value) { 19 | $$location.protocol = value; 20 | return /* () */0; 21 | } 22 | 23 | function getHost($$location) { 24 | return $$location.host; 25 | } 26 | 27 | function setHost($$location, value) { 28 | $$location.host = value; 29 | return /* () */0; 30 | } 31 | 32 | function getHostname($$location) { 33 | return $$location.hostname; 34 | } 35 | 36 | function setHostname($$location, value) { 37 | $$location.hostname = value; 38 | return /* () */0; 39 | } 40 | 41 | function getPort($$location) { 42 | return $$location.port; 43 | } 44 | 45 | function setPort($$location, value) { 46 | $$location.port = value; 47 | return /* () */0; 48 | } 49 | 50 | function getPathname($$location) { 51 | return $$location.pathname; 52 | } 53 | 54 | function setPathname($$location, value) { 55 | $$location.pathname = value; 56 | return /* () */0; 57 | } 58 | 59 | function getSearch($$location) { 60 | return $$location.search; 61 | } 62 | 63 | function setSearch($$location, value) { 64 | $$location.search = value; 65 | return /* () */0; 66 | } 67 | 68 | function getHash($$location) { 69 | return $$location.hash; 70 | } 71 | 72 | function setHash($$location, value) { 73 | $$location.hash = value; 74 | return /* () */0; 75 | } 76 | 77 | function getUsername($$location) { 78 | return $$location.username; 79 | } 80 | 81 | function setUsername($$location, value) { 82 | $$location.username = value; 83 | return /* () */0; 84 | } 85 | 86 | function getPassword($$location) { 87 | return $$location.password; 88 | } 89 | 90 | function setPassword($$location, value) { 91 | $$location.password = value; 92 | return /* () */0; 93 | } 94 | 95 | function getOrigin($$location) { 96 | return $$location.origin; 97 | } 98 | 99 | function asRecord($$location) { 100 | return { 101 | href: $$location.href, 102 | protocol: $$location.protocol, 103 | host: $$location.host, 104 | hostname: $$location.hostname, 105 | port: $$location.port, 106 | pathname: $$location.pathname, 107 | search: $$location.search, 108 | hash: $$location.hash, 109 | username: $$location.username, 110 | password: $$location.password, 111 | origin: $$location.origin 112 | }; 113 | } 114 | 115 | exports.getHref = getHref; 116 | exports.setHref = setHref; 117 | exports.getProtocol = getProtocol; 118 | exports.setProtocol = setProtocol; 119 | exports.getHost = getHost; 120 | exports.setHost = setHost; 121 | exports.getHostname = getHostname; 122 | exports.setHostname = setHostname; 123 | exports.getPort = getPort; 124 | exports.setPort = setPort; 125 | exports.getPathname = getPathname; 126 | exports.setPathname = setPathname; 127 | exports.getSearch = getSearch; 128 | exports.setSearch = setSearch; 129 | exports.getHash = getHash; 130 | exports.setHash = setHash; 131 | exports.getUsername = getUsername; 132 | exports.setUsername = setUsername; 133 | exports.getPassword = getPassword; 134 | exports.setPassword = setPassword; 135 | exports.getOrigin = getOrigin; 136 | exports.asRecord = asRecord; 137 | /* No side effect */ 138 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/web_node.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | 5 | function style(n) { 6 | return n.style; 7 | } 8 | 9 | function getStyle(n, key) { 10 | return n.style[key]; 11 | } 12 | 13 | function setStyle(n, key, value) { 14 | n.style[key] = value; 15 | return /* () */0; 16 | } 17 | 18 | function setStyleProperty(n, $staropt$star, key, value) { 19 | var priority = $staropt$star !== undefined ? $staropt$star : false; 20 | var style = n.style; 21 | var match = style.setProperty; 22 | if (match !== undefined) { 23 | return style.setProperty(key, value, priority ? "important" : null); 24 | } else { 25 | return setStyle(n, key, value); 26 | } 27 | } 28 | 29 | function childNodes(n) { 30 | return n.childNodes; 31 | } 32 | 33 | function firstChild(n) { 34 | return n.firstChild; 35 | } 36 | 37 | function appendChild(n, child) { 38 | return n.appendChild(child); 39 | } 40 | 41 | function removeChild(n, child) { 42 | return n.removeChild(child); 43 | } 44 | 45 | function insertBefore(n, child, refNode) { 46 | return n.insertBefore(child, refNode); 47 | } 48 | 49 | function remove(n, child) { 50 | return n.remove(child); 51 | } 52 | 53 | function setAttributeNS(n, namespace, key, value) { 54 | return n.setAttributeNS(namespace, key, value); 55 | } 56 | 57 | function setAttribute(n, key, value) { 58 | return n.setAttribute(key, value); 59 | } 60 | 61 | function setAttributeNsOptional(n, namespace, key, value) { 62 | if (namespace === "") { 63 | return n.setAttribute(key, value); 64 | } else { 65 | return n.setAttributeNS(namespace, key, value); 66 | } 67 | } 68 | 69 | function removeAttributeNS(n, namespace, key) { 70 | return n.removeAttributeNS(namespace, key); 71 | } 72 | 73 | function removeAttribute(n, key) { 74 | return n.removeAttribute(key); 75 | } 76 | 77 | function removeAttributeNsOptional(n, namespace, key) { 78 | if (namespace === "") { 79 | return n.removeAttribute(key); 80 | } else { 81 | return n.removeAttributeNS(namespace, key); 82 | } 83 | } 84 | 85 | function addEventListener(n, typ, listener, options) { 86 | return n.addEventListener(typ, listener, options); 87 | } 88 | 89 | function removeEventListener(n, typ, listener, options) { 90 | return n.removeEventListener(typ, listener, options); 91 | } 92 | 93 | function focus(n) { 94 | return n.focus(); 95 | } 96 | 97 | function set_nodeValue(n, text) { 98 | n.nodeValue = text; 99 | return /* () */0; 100 | } 101 | 102 | function get_nodeValue(n) { 103 | return n.nodeValue; 104 | } 105 | 106 | function remove_polyfill(param) { 107 | return (// remove polyfill 108 | (function() { 109 | if (!('remove' in Element.prototype)) { 110 | Element.prototype.remove = function() { 111 | if (this.parentNode) { 112 | this.parentNode.removeChild(this); 113 | } 114 | }; 115 | }; 116 | }())); 117 | } 118 | 119 | exports.style = style; 120 | exports.getStyle = getStyle; 121 | exports.setStyle = setStyle; 122 | exports.setStyleProperty = setStyleProperty; 123 | exports.childNodes = childNodes; 124 | exports.firstChild = firstChild; 125 | exports.appendChild = appendChild; 126 | exports.removeChild = removeChild; 127 | exports.insertBefore = insertBefore; 128 | exports.remove = remove; 129 | exports.setAttributeNS = setAttributeNS; 130 | exports.setAttribute = setAttribute; 131 | exports.setAttributeNsOptional = setAttributeNsOptional; 132 | exports.removeAttributeNS = removeAttributeNS; 133 | exports.removeAttribute = removeAttribute; 134 | exports.removeAttributeNsOptional = removeAttributeNsOptional; 135 | exports.addEventListener = addEventListener; 136 | exports.removeEventListener = removeEventListener; 137 | exports.focus = focus; 138 | exports.set_nodeValue = set_nodeValue; 139 | exports.get_nodeValue = get_nodeValue; 140 | exports.remove_polyfill = remove_polyfill; 141 | /* No side effect */ 142 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_counter_debug_standard.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("../src-ocaml/vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Tea_html = require("../src-ocaml/tea_html.js"); 7 | var Tea_debug = require("../src-ocaml/tea_debug.js"); 8 | 9 | function string_of_msg(param) { 10 | if (typeof param === "number") { 11 | switch (param) { 12 | case /* Increment */0 : 13 | return "Increment"; 14 | case /* Decrement */1 : 15 | return "Decrement"; 16 | case /* Reset */2 : 17 | return "Reset"; 18 | 19 | } 20 | } else { 21 | return "Set"; 22 | } 23 | } 24 | 25 | function init(param) { 26 | return /* tuple */[ 27 | 4, 28 | /* NoCmd */0 29 | ]; 30 | } 31 | 32 | function subscriptions(param) { 33 | return /* NoSub */0; 34 | } 35 | 36 | function update(model, param) { 37 | if (typeof param === "number") { 38 | switch (param) { 39 | case /* Increment */0 : 40 | return /* tuple */[ 41 | model + 1 | 0, 42 | /* NoCmd */0 43 | ]; 44 | case /* Decrement */1 : 45 | return /* tuple */[ 46 | model - 1 | 0, 47 | /* NoCmd */0 48 | ]; 49 | case /* Reset */2 : 50 | return /* tuple */[ 51 | 0, 52 | /* NoCmd */0 53 | ]; 54 | 55 | } 56 | } else { 57 | return /* tuple */[ 58 | param[0], 59 | /* NoCmd */0 60 | ]; 61 | } 62 | } 63 | 64 | function view_button(title, msg) { 65 | return Tea_html.button(undefined, undefined, /* :: */[ 66 | Vdom.onMsg("click", msg), 67 | /* [] */0 68 | ], /* :: */[ 69 | /* Text */Block.__(1, [title]), 70 | /* [] */0 71 | ]); 72 | } 73 | 74 | function view(model) { 75 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 76 | Tea_html.span(undefined, undefined, /* :: */[ 77 | Vdom.style("text-weight", "bold"), 78 | /* [] */0 79 | ], /* :: */[ 80 | /* Text */Block.__(1, [String(model)]), 81 | /* [] */0 82 | ]), 83 | /* :: */[ 84 | Tea_html.br(/* [] */0), 85 | /* :: */[ 86 | view_button("Increment", model >= 3 ? /* Decrement */1 : /* Increment */0), 87 | /* :: */[ 88 | Tea_html.br(/* [] */0), 89 | /* :: */[ 90 | view_button("Decrement", /* Decrement */1), 91 | /* :: */[ 92 | Tea_html.br(/* [] */0), 93 | /* :: */[ 94 | view_button("Set to 42", /* Set */[42]), 95 | /* :: */[ 96 | Tea_html.br(/* [] */0), 97 | /* :: */[ 98 | model !== 0 ? view_button("Reset", /* Reset */2) : Tea_html.noNode, 99 | /* [] */0 100 | ] 101 | ] 102 | ] 103 | ] 104 | ] 105 | ] 106 | ] 107 | ] 108 | ]); 109 | } 110 | 111 | var partial_arg = { 112 | init: init, 113 | update: update, 114 | view: view, 115 | subscriptions: subscriptions 116 | }; 117 | 118 | function main(param, param$1) { 119 | return Tea_debug.standardProgram(partial_arg, string_of_msg, param, param$1); 120 | } 121 | 122 | exports.string_of_msg = string_of_msg; 123 | exports.init = init; 124 | exports.subscriptions = subscriptions; 125 | exports.update = update; 126 | exports.view_button = view_button; 127 | exports.view = view; 128 | exports.main = main; 129 | /* Tea_html Not a pure module */ 130 | -------------------------------------------------------------------------------- /src-ocaml/web_node.ml: -------------------------------------------------------------------------------- 1 | 2 | type style = < 3 | setProperty : Web_json.t Js.undefined [@bs.get]; (* TODO: Revamp this and the next line... *) 4 | setProperty__ : string -> string Js.null -> string Js.null -> unit [@bs.meth]; 5 | > Js.t 6 | 7 | external getStyle : style -> string -> string Js.null = "" [@@bs.get_index] 8 | 9 | external setStyle : style -> string -> string Js.null -> unit = "" [@@bs.set_index] 10 | 11 | type t = < 12 | style : style [@bs.get]; 13 | value : string Js.undefined [@bs.set] [@bs.get]; 14 | checked : bool Js.undefined [@bs.set] [@bs.get]; 15 | childNodes : t Js.Array.t [@bs.get]; 16 | firstChild : t Js.Null.t [@bs.get]; 17 | appendChild : t -> t [@bs.meth]; 18 | removeChild : t -> t [@bs.meth]; 19 | insertBefore : t -> t -> t [@bs.meth]; 20 | remove : unit -> unit [@bs.meth]; 21 | setAttributeNS : string -> string -> string -> unit [@bs.meth]; 22 | setAttribute : string -> string -> unit [@bs.meth]; 23 | removeAttributeNS : string -> string -> unit [@bs.meth]; 24 | removeAttribute : string -> unit [@bs.meth]; 25 | addEventListener : string -> t Web_event.cb -> Web_event.options -> unit [@bs.meth]; 26 | removeEventListener : string -> t Web_event.cb -> Web_event.options -> unit [@bs.meth]; 27 | focus : unit -> unit [@bs.meth]; 28 | (* Text Nodes only *) 29 | nodeValue : string [@bs.set] [@bs.get {null}]; 30 | > Js.t 31 | 32 | external document_node : t = "document" [@@bs.val] 33 | 34 | type event = t Web_event.t 35 | 36 | type event_cb = t Web_event.cb 37 | 38 | 39 | external getProp_asEventListener : t -> 'key -> t Web_event.cb Js.undefined = "" [@@bs.get_index] 40 | 41 | external setProp_asEventListener : t -> 'key -> t Web_event.cb Js.undefined -> unit = "" [@@bs.set_index] 42 | 43 | external getProp : t -> 'key -> 'value = "" [@@bs.get_index] 44 | 45 | external setProp : t -> 'key -> 'value -> unit = "" [@@bs.set_index] 46 | 47 | let style n = n##style 48 | 49 | let getStyle n key = getStyle n##style key 50 | 51 | let setStyle n key value = setStyle n##style key value 52 | 53 | let setStyleProperty n ?(priority=false) key value = 54 | let style = n##style in 55 | match Js.Undefined.toOption style##setProperty with 56 | | None -> setStyle n key value (* TODO: Change this to setAttribute sometime, maybe... *) 57 | | Some _valid -> style##setProperty__ key value (if priority then (Js.Null.return "important") else Js.Null.empty) 58 | 59 | let childNodes n = n##childNodes 60 | 61 | let firstChild n = n##firstChild 62 | 63 | let appendChild n child = n##appendChild child 64 | 65 | let removeChild n child = n##removeChild child 66 | 67 | let insertBefore n child refNode = n##insertBefore child refNode 68 | 69 | let remove n child = n##remove child 70 | 71 | let setAttributeNS n namespace key value = n##setAttributeNS namespace key value 72 | 73 | let setAttribute n key value = n##setAttribute key value 74 | 75 | let setAttributeNsOptional n namespace key value = 76 | match namespace with 77 | | "" -> n##setAttribute key value 78 | | ns -> n##setAttributeNS ns key value 79 | 80 | let removeAttributeNS n namespace key = n##removeAttributeNS namespace key 81 | 82 | let removeAttribute n key = n##removeAttribute key 83 | 84 | let removeAttributeNsOptional n namespace key = 85 | match namespace with 86 | | "" -> n##removeAttribute key 87 | | ns -> n##removeAttributeNS ns key 88 | 89 | let addEventListener n typ listener options = n##addEventListener typ listener options 90 | 91 | let removeEventListener n typ listener options = n##removeEventListener typ listener options 92 | 93 | let focus n = n##focus () 94 | 95 | (* Text Nodes only *) 96 | 97 | let set_nodeValue n text = n##nodeValue #= text 98 | 99 | let get_nodeValue n = n##nodeValue 100 | 101 | 102 | (* Polyfills *) 103 | 104 | let remove_polyfill : unit -> unit = fun () -> 105 | [%bs.raw{| 106 | // remove polyfill 107 | (function() { 108 | if (!('remove' in Element.prototype)) { 109 | Element.prototype.remove = function() { 110 | if (this.parentNode) { 111 | this.parentNode.removeChild(this); 112 | } 113 | }; 114 | }; 115 | }()) 116 | |}] 117 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_ex.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Curry = require("bs-platform/lib/js/curry.js"); 6 | var Tea_sub = require("./tea_sub.js"); 7 | var Tea_task = require("./tea_task.js"); 8 | var Web_window_localstorage = require("./web_window_localstorage.js"); 9 | 10 | function render_event($staropt$star, msg) { 11 | var key = $staropt$star !== undefined ? $staropt$star : ""; 12 | var enableCall = function (callbacks) { 13 | Curry._1(callbacks.on, /* AddRenderMsg */Block.__(0, [msg])); 14 | return (function (param) { 15 | return Curry._1(callbacks.on, /* RemoveRenderMsg */Block.__(1, [msg])); 16 | }); 17 | }; 18 | return Tea_sub.registration(key, enableCall); 19 | } 20 | 21 | var length = /* Task */[(function (cb) { 22 | var match = Web_window_localstorage.length(window); 23 | if (match !== undefined) { 24 | return Curry._1(cb, /* Ok */Block.__(0, [match])); 25 | } else { 26 | return Curry._1(cb, /* Error */Block.__(1, ["localStorage is not available"])); 27 | } 28 | })]; 29 | 30 | var clear = /* Task */[(function (cb) { 31 | var match = Web_window_localstorage.clear(window); 32 | if (match !== undefined) { 33 | return Curry._1(cb, /* Ok */Block.__(0, [match])); 34 | } else { 35 | return Curry._1(cb, /* Error */Block.__(1, ["localStorage is not available"])); 36 | } 37 | })]; 38 | 39 | function clearCmd(param) { 40 | return Tea_task.attemptOpt((function (param) { 41 | return ; 42 | }), clear); 43 | } 44 | 45 | function key(idx) { 46 | return /* Task */[(function (cb) { 47 | var match = Web_window_localstorage.key(window, idx); 48 | if (match !== undefined) { 49 | return Curry._1(cb, /* Ok */Block.__(0, [match])); 50 | } else { 51 | return Curry._1(cb, /* Error */Block.__(1, ["localStorage is not available"])); 52 | } 53 | })]; 54 | } 55 | 56 | function getItem(key) { 57 | return /* Task */[(function (cb) { 58 | var match = Web_window_localstorage.getItem(window, key); 59 | if (match !== undefined) { 60 | return Curry._1(cb, /* Ok */Block.__(0, [match])); 61 | } else { 62 | return Curry._1(cb, /* Error */Block.__(1, ["localStorage is not available"])); 63 | } 64 | })]; 65 | } 66 | 67 | function removeItem(key) { 68 | return /* Task */[(function (cb) { 69 | var match = Web_window_localstorage.removeItem(window, key); 70 | if (match !== undefined) { 71 | return Curry._1(cb, /* Ok */Block.__(0, [match])); 72 | } else { 73 | return Curry._1(cb, /* Error */Block.__(1, ["localStorage is not available"])); 74 | } 75 | })]; 76 | } 77 | 78 | function removeItemCmd(key) { 79 | return Tea_task.attemptOpt((function (param) { 80 | return ; 81 | }), removeItem(key)); 82 | } 83 | 84 | function setItem(key, value) { 85 | return /* Task */[(function (cb) { 86 | var match = Web_window_localstorage.setItem(window, key, value); 87 | if (match !== undefined) { 88 | return Curry._1(cb, /* Ok */Block.__(0, [/* () */0])); 89 | } else { 90 | return Curry._1(cb, /* Error */Block.__(1, ["localStorage is not available"])); 91 | } 92 | })]; 93 | } 94 | 95 | function setItemCmd(key, value) { 96 | return Tea_task.attemptOpt((function (param) { 97 | return ; 98 | }), setItem(key, value)); 99 | } 100 | 101 | var LocalStorage = { 102 | length: length, 103 | clear: clear, 104 | clearCmd: clearCmd, 105 | key: key, 106 | getItem: getItem, 107 | removeItem: removeItem, 108 | removeItemCmd: removeItemCmd, 109 | setItem: setItem, 110 | setItemCmd: setItemCmd 111 | }; 112 | 113 | exports.render_event = render_event; 114 | exports.LocalStorage = LocalStorage; 115 | /* No side effect */ 116 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_navigation.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Curry = require("bs-platform/lib/js/curry.js"); 6 | var Tea_app = require("./tea_app.js"); 7 | var Tea_sub = require("./tea_sub.js"); 8 | var Web_window = require("./web_window.js"); 9 | var Web_location = require("./web_location.js"); 10 | var Web_window_history = require("./web_window_history.js"); 11 | 12 | function getLocation(param) { 13 | return Web_location.asRecord(document.location); 14 | } 15 | 16 | var notifier = { 17 | contents: undefined 18 | }; 19 | 20 | function notifyUrlChange(param) { 21 | var match = notifier.contents; 22 | if (match !== undefined) { 23 | var $$location = Web_location.asRecord(document.location); 24 | Curry._1(match, $$location); 25 | return /* () */0; 26 | } else { 27 | return /* () */0; 28 | } 29 | } 30 | 31 | function subscribe(tagger) { 32 | var enableCall = function (callbacks) { 33 | var notifyHandler = function ($$location) { 34 | return Curry._1(callbacks.enqueue, Curry._1(tagger, $$location)); 35 | }; 36 | notifier.contents = notifyHandler; 37 | var handler = function (_event) { 38 | return notifyUrlChange(/* () */0); 39 | }; 40 | Web_window.addEventListener("popstate", handler, false); 41 | return (function (param) { 42 | return Web_window.removeEventListener("popstate", handler, false); 43 | }); 44 | }; 45 | return Tea_sub.registration("navigation", enableCall); 46 | } 47 | 48 | function replaceState(url) { 49 | Web_window_history.replaceState(window, JSON.parse("{}"), "", url); 50 | return /* () */0; 51 | } 52 | 53 | function pushState(url) { 54 | Web_window_history.pushState(window, JSON.parse("{}"), "", url); 55 | return /* () */0; 56 | } 57 | 58 | function modifyUrl(url) { 59 | return /* EnqueueCall */Block.__(2, [(function (_enqueue) { 60 | replaceState(url); 61 | notifyUrlChange(/* () */0); 62 | return /* () */0; 63 | })]); 64 | } 65 | 66 | function newUrl(url) { 67 | return /* EnqueueCall */Block.__(2, [(function (_enqueue) { 68 | pushState(url); 69 | notifyUrlChange(/* () */0); 70 | return /* () */0; 71 | })]); 72 | } 73 | 74 | function go(step) { 75 | return /* EnqueueCall */Block.__(2, [(function (_enqueue) { 76 | Web_window_history.go(window, step); 77 | notifyUrlChange(/* () */0); 78 | return /* () */0; 79 | })]); 80 | } 81 | 82 | function back(step) { 83 | return go(-step | 0); 84 | } 85 | 86 | var forward = go; 87 | 88 | function navigationProgram(locationToMessage, stuff) { 89 | var init = function (flag) { 90 | return Curry._2(stuff.init, flag, Web_location.asRecord(document.location)); 91 | }; 92 | var subscriptions = function (model) { 93 | return /* Batch */Block.__(0, [/* :: */[ 94 | subscribe(locationToMessage), 95 | /* :: */[ 96 | Curry._1(stuff.subscriptions, model), 97 | /* [] */0 98 | ] 99 | ]]); 100 | }; 101 | var partial_arg_update = stuff.update; 102 | var partial_arg_view = stuff.view; 103 | var partial_arg_shutdown = stuff.shutdown; 104 | var partial_arg = { 105 | init: init, 106 | update: partial_arg_update, 107 | view: partial_arg_view, 108 | subscriptions: subscriptions, 109 | shutdown: partial_arg_shutdown 110 | }; 111 | return (function (param, param$1) { 112 | return Tea_app.program(partial_arg, param, param$1); 113 | }); 114 | } 115 | 116 | exports.getLocation = getLocation; 117 | exports.notifier = notifier; 118 | exports.notifyUrlChange = notifyUrlChange; 119 | exports.subscribe = subscribe; 120 | exports.replaceState = replaceState; 121 | exports.pushState = pushState; 122 | exports.modifyUrl = modifyUrl; 123 | exports.newUrl = newUrl; 124 | exports.go = go; 125 | exports.back = back; 126 | exports.forward = forward; 127 | exports.navigationProgram = navigationProgram; 128 | /* No side effect */ 129 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_counter_debug_program.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Vdom = require("../src-ocaml/vdom.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Tea_html = require("../src-ocaml/tea_html.js"); 7 | var Tea_debug = require("../src-ocaml/tea_debug.js"); 8 | 9 | function string_of_msg(param) { 10 | if (typeof param === "number") { 11 | switch (param) { 12 | case /* Increment */0 : 13 | return "Increment"; 14 | case /* Decrement */1 : 15 | return "Decrement"; 16 | case /* Reset */2 : 17 | return "Reset"; 18 | 19 | } 20 | } else { 21 | return "Set"; 22 | } 23 | } 24 | 25 | function init(param) { 26 | return /* tuple */[ 27 | 4, 28 | /* NoCmd */0 29 | ]; 30 | } 31 | 32 | function subscriptions(param) { 33 | return /* NoSub */0; 34 | } 35 | 36 | function update(model, param) { 37 | if (typeof param === "number") { 38 | switch (param) { 39 | case /* Increment */0 : 40 | return /* tuple */[ 41 | model + 1 | 0, 42 | /* NoCmd */0 43 | ]; 44 | case /* Decrement */1 : 45 | return /* tuple */[ 46 | model - 1 | 0, 47 | /* NoCmd */0 48 | ]; 49 | case /* Reset */2 : 50 | return /* tuple */[ 51 | 0, 52 | /* NoCmd */0 53 | ]; 54 | 55 | } 56 | } else { 57 | return /* tuple */[ 58 | param[0], 59 | /* NoCmd */0 60 | ]; 61 | } 62 | } 63 | 64 | function view_button(title, msg) { 65 | return Tea_html.button(undefined, undefined, /* :: */[ 66 | Vdom.onMsg("click", msg), 67 | /* [] */0 68 | ], /* :: */[ 69 | /* Text */Block.__(1, [title]), 70 | /* [] */0 71 | ]); 72 | } 73 | 74 | function view(model) { 75 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 76 | Tea_html.span(undefined, undefined, /* :: */[ 77 | Vdom.style("text-weight", "bold"), 78 | /* [] */0 79 | ], /* :: */[ 80 | /* Text */Block.__(1, [String(model)]), 81 | /* [] */0 82 | ]), 83 | /* :: */[ 84 | Tea_html.br(/* [] */0), 85 | /* :: */[ 86 | view_button("Increment", model >= 3 ? /* Decrement */1 : /* Increment */0), 87 | /* :: */[ 88 | Tea_html.br(/* [] */0), 89 | /* :: */[ 90 | view_button("Decrement", /* Decrement */1), 91 | /* :: */[ 92 | Tea_html.br(/* [] */0), 93 | /* :: */[ 94 | view_button("Set to 42", /* Set */[42]), 95 | /* :: */[ 96 | Tea_html.br(/* [] */0), 97 | /* :: */[ 98 | model !== 0 ? view_button("Reset", /* Reset */2) : Tea_html.noNode, 99 | /* [] */0 100 | ] 101 | ] 102 | ] 103 | ] 104 | ] 105 | ] 106 | ] 107 | ] 108 | ]); 109 | } 110 | 111 | function partial_arg_shutdown(_model) { 112 | return /* NoCmd */0; 113 | } 114 | 115 | var partial_arg = { 116 | init: init, 117 | update: update, 118 | view: view, 119 | subscriptions: subscriptions, 120 | shutdown: partial_arg_shutdown 121 | }; 122 | 123 | function main(param, param$1) { 124 | return Tea_debug.program(partial_arg, string_of_msg, param, param$1); 125 | } 126 | 127 | exports.string_of_msg = string_of_msg; 128 | exports.init = init; 129 | exports.subscriptions = subscriptions; 130 | exports.update = update; 131 | exports.view_button = view_button; 132 | exports.view = view; 133 | exports.main = main; 134 | /* Tea_html Not a pure module */ 135 | -------------------------------------------------------------------------------- /src-reason/tea_sub.re: -------------------------------------------------------------------------------- 1 | type t('msg) = 2 | | NoSub: t(_) 3 | | Batch(list(t('msg))): t('msg) 4 | | Registration( 5 | string, 6 | (ref(Vdom.applicationCallbacks('msg)), unit) => unit, 7 | ref(option(unit => unit)), 8 | ) 9 | : t('msg) 10 | | Mapper( 11 | ref(Vdom.applicationCallbacks('msg)) => 12 | ref(Vdom.applicationCallbacks('msgB)), 13 | t('msgB), 14 | ) 15 | : t('msg); 16 | 17 | type applicationCallbacks('msg) = Vdom.applicationCallbacks('msg); 18 | 19 | let none = NoSub; 20 | 21 | let batch = subs => Batch(subs); 22 | 23 | let registration = (key, enableCall) => 24 | [@implicit_arity] 25 | Registration(key, callbacks => enableCall(callbacks^), ref(None)); 26 | 27 | let map = (msgMapper, sub) => { 28 | let func = callbacks => Vdom.wrapCallbacks(msgMapper, callbacks); 29 | [@implicit_arity] Mapper(func, sub); 30 | }; 31 | 32 | let mapFunc = (func, sub) => [@implicit_arity] Mapper(func, sub); 33 | 34 | let rec run: 35 | type msgOld msgNew. 36 | ( 37 | ref(Vdom.applicationCallbacks(msgOld)), 38 | ref(Vdom.applicationCallbacks(msgNew)), 39 | t(msgOld), 40 | t(msgNew) 41 | ) => 42 | t(msgNew) = 43 | (oldCallbacks, newCallbacks, oldSub, newSub) => { 44 | let rec enable: 45 | type msg. (ref(Vdom.applicationCallbacks(msg)), t(msg)) => unit = 46 | callbacks => 47 | fun 48 | | NoSub => () 49 | | Batch([]) => () 50 | | Batch(subs) => List.iter(enable(callbacks), subs) 51 | | [@implicit_arity] Mapper(mapper, sub) => { 52 | let subCallbacks = mapper(callbacks); 53 | enable(subCallbacks, sub); 54 | } 55 | | [@implicit_arity] Registration(_key, enCB, diCB) => 56 | diCB := Some(enCB(callbacks)); 57 | 58 | let rec disable: 59 | type msg. (ref(Vdom.applicationCallbacks(msg)), t(msg)) => unit = 60 | callbacks => 61 | fun 62 | | NoSub => () 63 | | Batch([]) => () 64 | | Batch(subs) => List.iter(disable(callbacks), subs) 65 | | [@implicit_arity] Mapper(mapper, sub) => { 66 | let subCallbacks = mapper(callbacks); 67 | disable(subCallbacks, sub); 68 | } 69 | | [@implicit_arity] Registration(_key, _enCB, diCB) => 70 | switch (diCB^) { 71 | | None => () 72 | | Some(cb) => 73 | let () = diCB := None; 74 | cb(); 75 | }; 76 | 77 | [@ocaml.warning "-4"] 78 | ( 79 | switch (oldSub, newSub) { 80 | | (NoSub, NoSub) => newSub 81 | | ( 82 | [@implicit_arity] Registration(oldKey, _oldEnCB, oldDiCB), 83 | [@implicit_arity] Registration(newKey, _newEnCB, newDiCB), 84 | ) 85 | when oldKey == newKey => 86 | let () = newDiCB := oldDiCB^; 87 | newSub; 88 | | ( 89 | [@implicit_arity] Mapper(oldMapper, oldSubSub), 90 | [@implicit_arity] Mapper(newMapper, newSubSub), 91 | ) => 92 | let olderCallbacks = oldMapper(oldCallbacks); /* Resolve the type checker */ 93 | let newerCallbacks = newMapper(newCallbacks); 94 | let _newerSubSub = 95 | run(olderCallbacks, newerCallbacks, oldSubSub, newSubSub); 96 | newSub; 97 | | (Batch(oldSubs), Batch(newSubs)) => 98 | let rec aux = (oldList, newList) => 99 | switch (oldList, newList) { 100 | | ([], []) => () 101 | | ([], [newSubSub, ...newRest]) => 102 | let () = enable(newCallbacks, newSubSub); 103 | aux([], newRest); 104 | | ([oldSubSub, ...oldRest], []) => 105 | let () = disable(oldCallbacks, oldSubSub); 106 | aux(oldRest, []); 107 | | ([oldSubSub, ...oldRest], [newSubSub, ...newRest]) => 108 | let _newerSubSub = 109 | run(oldCallbacks, newCallbacks, oldSubSub, newSubSub); 110 | aux(oldRest, newRest); 111 | }; 112 | let () = aux(oldSubs, newSubs); 113 | newSub; 114 | | (oldS, newS) => 115 | let () = disable(oldCallbacks, oldS); 116 | let () = enable(newCallbacks, newS); 117 | newSub; 118 | } 119 | ); 120 | }; 121 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_attribute_removal.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var List = require("bs-platform/lib/js/list.js"); 5 | var Vdom = require("../src-ocaml/vdom.js"); 6 | var Block = require("bs-platform/lib/js/block.js"); 7 | var Tea_app = require("../src-ocaml/tea_app.js"); 8 | var Tea_html = require("../src-ocaml/tea_html.js"); 9 | var Caml_option = require("bs-platform/lib/js/caml_option.js"); 10 | 11 | function select(param_0) { 12 | return /* Select */[param_0]; 13 | } 14 | 15 | function render_selected(param) { 16 | if (param !== undefined) { 17 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 18 | /* Text */Block.__(1, ["you selected " + param]), 19 | /* :: */[ 20 | Tea_html.div(undefined, undefined, /* :: */[ 21 | Vdom.onMsg("click", /* Delete */0), 22 | /* [] */0 23 | ], /* :: */[ 24 | /* Text */Block.__(1, ["delete selection"]), 25 | /* [] */0 26 | ]), 27 | /* [] */0 28 | ] 29 | ]); 30 | } else { 31 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 32 | /* Text */Block.__(1, ["Nothing selected"]), 33 | /* [] */0 34 | ]); 35 | } 36 | } 37 | 38 | function lang(l, is_selected) { 39 | var msg = /* Select */[l]; 40 | return Tea_html.li(undefined, undefined, /* :: */[ 41 | Vdom.onMsg("click", msg), 42 | /* :: */[ 43 | Vdom.style("color", "blue"), 44 | /* :: */[ 45 | is_selected ? Vdom.style("border", "1px solid black") : Tea_html.noProp, 46 | /* :: */[ 47 | is_selected ? /* Attribute */Block.__(1, [ 48 | "", 49 | "lang", 50 | l 51 | ]) : Tea_html.noProp, 52 | /* [] */0 53 | ] 54 | ] 55 | ] 56 | ], /* :: */[ 57 | /* Text */Block.__(1, [l]), 58 | /* [] */0 59 | ]); 60 | } 61 | 62 | function render_languages(selected, languages) { 63 | var is_selected = function (selected, language) { 64 | if (selected !== undefined) { 65 | return language === Caml_option.valFromOption(selected); 66 | } else { 67 | return false; 68 | } 69 | }; 70 | var rendered = List.map((function (l) { 71 | return lang(l, is_selected(selected, l)); 72 | }), languages); 73 | return Tea_html.ul(undefined, undefined, /* [] */0, rendered); 74 | } 75 | 76 | function update(state, param) { 77 | if (param) { 78 | return { 79 | selected: param[0], 80 | languages: state.languages 81 | }; 82 | } else { 83 | return { 84 | selected: undefined, 85 | languages: state.languages 86 | }; 87 | } 88 | } 89 | 90 | function view(state) { 91 | return Tea_html.div(undefined, undefined, /* [] */0, /* :: */[ 92 | render_selected(state.selected), 93 | /* :: */[ 94 | render_languages(state.selected, state.languages), 95 | /* [] */0 96 | ] 97 | ]); 98 | } 99 | 100 | var partial_arg_model = { 101 | selected: "Erlang", 102 | languages: /* :: */[ 103 | "Erlang", 104 | /* :: */[ 105 | "Ocaml", 106 | /* :: */[ 107 | "Clojure", 108 | /* [] */0 109 | ] 110 | ] 111 | ] 112 | }; 113 | 114 | var partial_arg = { 115 | model: partial_arg_model, 116 | update: update, 117 | view: view 118 | }; 119 | 120 | function main(param, param$1) { 121 | return Tea_app.beginnerProgram(partial_arg, param, param$1); 122 | } 123 | 124 | var $$delete = /* Delete */0; 125 | 126 | exports.select = select; 127 | exports.$$delete = $$delete; 128 | exports.render_selected = render_selected; 129 | exports.lang = lang; 130 | exports.render_languages = render_languages; 131 | exports.update = update; 132 | exports.view = view; 133 | exports.main = main; 134 | /* Tea_html Not a pure module */ 135 | -------------------------------------------------------------------------------- /src-ocaml/tea_sub.ml: -------------------------------------------------------------------------------- 1 | type 'msg t = 2 | | NoSub: _ t 3 | | Batch: 'msg t list -> 'msg t 4 | | Registration: string * 5 | ('msg Vdom.applicationCallbacks ref -> unit -> unit) * (unit -> unit) 6 | option ref -> 'msg t 7 | | Mapper: 8 | ('msg Vdom.applicationCallbacks ref -> 'msgB Vdom.applicationCallbacks ref) 9 | * 'msgB t -> 'msg t 10 | type 'msg applicationCallbacks = 'msg Vdom.applicationCallbacks 11 | let none = NoSub 12 | let batch subs = ((Batch (subs))[@explicit_arity ]) 13 | let registration key enableCall = 14 | ((Registration 15 | (key, (fun callbacks -> enableCall (!callbacks)), (ref None))) 16 | [@implicit_arity ]) 17 | let map msgMapper sub = 18 | let func callbacks = Vdom.wrapCallbacks msgMapper callbacks in 19 | ((Mapper (func, sub))[@implicit_arity ]) 20 | let mapFunc func sub = ((Mapper (func, sub))[@implicit_arity ]) 21 | let rec run : type msgOld msgNew. 22 | msgOld Vdom.applicationCallbacks ref -> 23 | msgNew Vdom.applicationCallbacks ref -> msgOld t -> msgNew t -> msgNew t 24 | = 25 | fun oldCallbacks -> 26 | fun newCallbacks -> 27 | fun oldSub -> 28 | fun newSub -> 29 | let rec enable : type msg. 30 | msg Vdom.applicationCallbacks ref -> msg t -> unit = 31 | fun callbacks -> 32 | function 33 | | NoSub -> () 34 | | ((Batch ([]))[@explicit_arity ]) -> () 35 | | ((Batch (subs))[@explicit_arity ]) -> 36 | List.iter (enable callbacks) subs 37 | | ((Mapper (mapper, sub))[@implicit_arity ]) -> 38 | let subCallbacks = mapper callbacks in 39 | enable subCallbacks sub 40 | | ((Registration (_key, enCB, diCB))[@implicit_arity ]) -> 41 | diCB := ((Some ((enCB callbacks)))[@explicit_arity ]) in 42 | let rec disable : type msg. 43 | msg Vdom.applicationCallbacks ref -> msg t -> unit = 44 | fun callbacks -> 45 | function 46 | | NoSub -> () 47 | | ((Batch ([]))[@explicit_arity ]) -> () 48 | | ((Batch (subs))[@explicit_arity ]) -> 49 | List.iter (disable callbacks) subs 50 | | ((Mapper (mapper, sub))[@implicit_arity ]) -> 51 | let subCallbacks = mapper callbacks in 52 | disable subCallbacks sub 53 | | ((Registration (_key, _enCB, diCB))[@implicit_arity ]) -> 54 | (match !diCB with 55 | | None -> () 56 | | ((Some (cb))[@explicit_arity ]) -> 57 | let () = diCB := None in cb ()) in 58 | ((match (oldSub, newSub) with 59 | | (NoSub, NoSub) -> newSub 60 | | (((Registration 61 | (oldKey, _oldEnCB, oldDiCB))[@implicit_arity ]), 62 | ((Registration 63 | (newKey, _newEnCB, newDiCB))[@implicit_arity ])) when 64 | oldKey = newKey -> let () = newDiCB := (!oldDiCB) in newSub 65 | | (((Mapper (oldMapper, oldSubSub))[@implicit_arity ]), ((Mapper 66 | (newMapper, newSubSub))[@implicit_arity ])) -> 67 | let olderCallbacks = oldMapper oldCallbacks in 68 | let newerCallbacks = newMapper newCallbacks in 69 | let _newerSubSub = 70 | run olderCallbacks newerCallbacks oldSubSub newSubSub in 71 | newSub 72 | | (((Batch (oldSubs))[@explicit_arity ]), ((Batch 73 | (newSubs))[@explicit_arity ])) -> 74 | let rec aux oldList newList = 75 | match (oldList, newList) with 76 | | ([], []) -> () 77 | | ([], newSubSub::newRest) -> 78 | let () = enable newCallbacks newSubSub in 79 | aux [] newRest 80 | | (oldSubSub::oldRest, []) -> 81 | let () = disable oldCallbacks oldSubSub in 82 | aux oldRest [] 83 | | (oldSubSub::oldRest, newSubSub::newRest) -> 84 | let _newerSubSub = 85 | run oldCallbacks newCallbacks oldSubSub newSubSub in 86 | aux oldRest newRest in 87 | let () = aux oldSubs newSubs in newSub 88 | | (oldS, newS) -> 89 | let () = disable oldCallbacks oldS in 90 | let () = enable newCallbacks newS in newSub) 91 | [@ocaml.warning "-4"]) -------------------------------------------------------------------------------- /src-reason/web_node.re: -------------------------------------------------------------------------------- 1 | type style = { 2 | . 3 | [@bs.get] "setProperty": Js.undefined(Web_json.t), /* TODO: Revamp this and the next line... */ 4 | [@bs.meth] 5 | "setProperty__": (string, Js.null(string), Js.null(string)) => unit, 6 | }; 7 | 8 | [@bs.get_index] external getStyle : (style, string) => Js.null(string) = ""; 9 | 10 | [@bs.set_index] 11 | external setStyle : (style, string, Js.null(string)) => unit = ""; 12 | 13 | type t = { 14 | . 15 | [@bs.get] "style": style, 16 | [@bs.set] [@bs.get] "value": Js.undefined(string), 17 | [@bs.set] [@bs.get] "checked": Js.undefined(bool), 18 | [@bs.get] "childNodes": Js.Array.t(t), 19 | [@bs.get] "firstChild": Js.Null.t(t), 20 | [@bs.meth] "appendChild": t => t, 21 | [@bs.meth] "removeChild": t => t, 22 | [@bs.meth] "insertBefore": (t, t) => t, 23 | [@bs.meth] "remove": unit => unit, 24 | [@bs.meth] "setAttributeNS": (string, string, string) => unit, 25 | [@bs.meth] "setAttribute": (string, string) => unit, 26 | [@bs.meth] "removeAttributeNS": (string, string) => unit, 27 | [@bs.meth] "removeAttribute": string => unit, 28 | [@bs.meth] 29 | "addEventListener": (string, Web_event.cb(t), Web_event.options) => unit, 30 | [@bs.meth] 31 | "removeEventListener": (string, Web_event.cb(t), Web_event.options) => unit, 32 | [@bs.meth] "focus": unit => unit, 33 | /* Text Nodes only */ 34 | [@bs.set] [@bs.get {null: null}] "nodeValue": string, 35 | }; 36 | 37 | [@bs.val] external document_node : t = "document"; 38 | 39 | type event = Web_event.t(t); 40 | 41 | type event_cb = Web_event.cb(t); 42 | 43 | [@bs.get_index] 44 | external getProp_asEventListener : (t, 'key) => Js.undefined(Web_event.cb(t)) = 45 | ""; 46 | 47 | [@bs.set_index] 48 | external setProp_asEventListener : 49 | (t, 'key, Js.undefined(Web_event.cb(t))) => unit = 50 | ""; 51 | 52 | [@bs.get_index] external getProp : (t, 'key) => 'value = ""; 53 | 54 | [@bs.set_index] external setProp : (t, 'key, 'value) => unit = ""; 55 | 56 | let style = n => n##style; 57 | 58 | let getStyle = (n, key) => getStyle(n##style, key); 59 | 60 | let setStyle = (n, key, value) => setStyle(n##style, key, value); 61 | 62 | let setStyleProperty = (n, ~priority=false, key, value) => { 63 | let style = n##style; 64 | switch (Js.Undefined.toOption(style##setProperty)) { 65 | | None => setStyle(n, key, value) /* TODO: Change this to setAttribute sometime, maybe... */ 66 | | Some(_valid) => 67 | style##setProperty__( 68 | key, 69 | value, 70 | if (priority) { 71 | Js.Null.return("important"); 72 | } else { 73 | Js.Null.empty; 74 | }, 75 | ) 76 | }; 77 | }; 78 | 79 | let childNodes = n => n##childNodes; 80 | 81 | let firstChild = n => n##firstChild; 82 | 83 | let appendChild = (n, child) => n##appendChild(child); 84 | 85 | let removeChild = (n, child) => n##removeChild(child); 86 | 87 | let insertBefore = (n, child, refNode) => n##insertBefore(child, refNode); 88 | 89 | let remove = (n, child) => n##remove(child); 90 | 91 | let setAttributeNS = (n, namespace, key, value) => 92 | n##setAttributeNS(namespace, key, value); 93 | 94 | let setAttribute = (n, key, value) => n##setAttribute(key, value); 95 | 96 | let setAttributeNsOptional = (n, namespace, key, value) => 97 | switch (namespace) { 98 | | "" => n##setAttribute(key, value) 99 | | ns => n##setAttributeNS(ns, key, value) 100 | }; 101 | 102 | let removeAttributeNS = (n, namespace, key) => 103 | n##removeAttributeNS(namespace, key); 104 | 105 | let removeAttribute = (n, key) => n##removeAttribute(key); 106 | 107 | let removeAttributeNsOptional = (n, namespace, key) => 108 | switch (namespace) { 109 | | "" => n##removeAttribute(key) 110 | | ns => n##removeAttributeNS(ns, key) 111 | }; 112 | 113 | let addEventListener = (n, typ, listener, options) => 114 | n##addEventListener(typ, listener, options); 115 | 116 | let removeEventListener = (n, typ, listener, options) => 117 | n##removeEventListener(typ, listener, options); 118 | 119 | let focus = n => n##focus(); 120 | 121 | /* Text Nodes only */ 122 | let set_nodeValue = (n, text) => n##nodeValue#=text; 123 | 124 | let get_nodeValue = n => n##nodeValue; 125 | 126 | /* Polyfills */ 127 | let remove_polyfill: unit => unit = 128 | () => [%bs.raw 129 | {| 130 | // remove polyfill 131 | (function() { 132 | if (!('remove' in Element.prototype)) { 133 | Element.prototype.remove = function() { 134 | if (this.parentNode) { 135 | this.parentNode.removeChild(this); 136 | } 137 | }; 138 | }; 139 | }()) 140 | |} 141 | ]; 142 | -------------------------------------------------------------------------------- /lib/js/test-ocaml/test_client_on_with_options.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var List = require("bs-platform/lib/js/list.js"); 5 | var Block = require("bs-platform/lib/js/block.js"); 6 | var Curry = require("bs-platform/lib/js/curry.js"); 7 | var Tea_app = require("../src-ocaml/tea_app.js"); 8 | var Tea_json = require("../src-ocaml/tea_json.js"); 9 | var Tea_html2 = require("../src-ocaml/tea_html2.js"); 10 | var Caml_format = require("bs-platform/lib/js/caml_format.js"); 11 | 12 | function set_value(param_0) { 13 | return /* Set_value */[param_0]; 14 | } 15 | 16 | function update(model, param) { 17 | if (param) { 18 | return param[0]; 19 | } else { 20 | return model + 1 | 0; 21 | } 22 | } 23 | 24 | function view(model) { 25 | var clientX = Tea_json.Decoder.field("clientX", Tea_json.Decoder.$$int); 26 | var init = Tea_html2.Events.defaultOptions; 27 | return Tea_html2.div(undefined, undefined, /* [] */0, List.map((function (e) { 28 | return Tea_html2.div(undefined, undefined, /* [] */0, /* :: */[ 29 | e, 30 | /* [] */0 31 | ]); 32 | }), /* :: */[ 33 | /* Text */Block.__(1, [String(model)]), 34 | /* :: */[ 35 | Tea_html2.button(undefined, undefined, /* :: */[ 36 | Tea_html2.Events.onClick(/* Click */0), 37 | /* [] */0 38 | ], /* :: */[ 39 | /* Text */Block.__(1, ["onClick"]), 40 | /* [] */0 41 | ]), 42 | /* :: */[ 43 | Tea_html2.button(undefined, undefined, /* :: */[ 44 | Curry._3(Tea_html2.Events.on, "", "click", Tea_json.Decoder.succeed(/* Click */0)), 45 | /* [] */0 46 | ], /* :: */[ 47 | /* Text */Block.__(1, ["on \"click\""]), 48 | /* [] */0 49 | ]), 50 | /* :: */[ 51 | Tea_html2.a(undefined, undefined, /* :: */[ 52 | Tea_html2.Attributes.href("https://www.google.com"), 53 | /* [] */0 54 | ], /* :: */[ 55 | /* Text */Block.__(1, ["a normal link"]), 56 | /* [] */0 57 | ]), 58 | /* :: */[ 59 | Tea_html2.a(undefined, undefined, /* :: */[ 60 | Tea_html2.Attributes.href("https://www.google.com"), 61 | /* :: */[ 62 | Curry._4(Tea_html2.Events.onWithOptions, "", "click", { 63 | stopPropagation: init.stopPropagation, 64 | preventDefault: true 65 | }, Tea_json.Decoder.succeed(/* Click */0)), 66 | /* [] */0 67 | ] 68 | ], /* :: */[ 69 | /* Text */Block.__(1, ["a link with prevent default"]), 70 | /* [] */0 71 | ]), 72 | /* :: */[ 73 | Tea_html2.button(undefined, undefined, /* :: */[ 74 | Curry._3(Tea_html2.Events.on, "", "click", Tea_json.Decoder.map(set_value, clientX)), 75 | /* [] */0 76 | ], /* :: */[ 77 | /* Text */Block.__(1, ["on \"click\", use clientX value"]), 78 | /* [] */0 79 | ]), 80 | /* :: */[ 81 | Tea_html2.input$prime(undefined, undefined, /* :: */[ 82 | Tea_html2.Attributes.type$prime("text"), 83 | /* :: */[ 84 | Curry._3(Tea_html2.Events.on, "", "input", Tea_json.Decoder.map((function (v) { 85 | return /* Set_value */[Caml_format.caml_int_of_string(v)]; 86 | }), Tea_html2.Events.targetValue)), 87 | /* [] */0 88 | ] 89 | ], /* [] */0), 90 | /* [] */0 91 | ] 92 | ] 93 | ] 94 | ] 95 | ] 96 | ] 97 | ])); 98 | } 99 | 100 | var partial_arg = { 101 | model: 0, 102 | update: update, 103 | view: view 104 | }; 105 | 106 | function main(param, param$1) { 107 | return Tea_app.beginnerProgram(partial_arg, param, param$1); 108 | } 109 | 110 | var click = /* Click */0; 111 | 112 | exports.click = click; 113 | exports.set_value = set_value; 114 | exports.update = update; 115 | exports.view = view; 116 | exports.main = main; 117 | /* Tea_html2 Not a pure module */ 118 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_random.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Curry = require("bs-platform/lib/js/curry.js"); 6 | var Random = require("bs-platform/lib/js/random.js"); 7 | var Pervasives = require("bs-platform/lib/js/pervasives.js"); 8 | 9 | Random.self_init(/* () */0); 10 | 11 | var bool = /* Generator */[(function (state) { 12 | return Random.State.bool(state); 13 | })]; 14 | 15 | function $$int(min, max) { 16 | var match = min < max ? /* tuple */[ 17 | min, 18 | max 19 | ] : /* tuple */[ 20 | max, 21 | min 22 | ]; 23 | var max$1 = match[1]; 24 | var min$1 = match[0]; 25 | return /* Generator */[(function (state) { 26 | return min$1 + Random.State.$$int(state, (max$1 - min$1 | 0) + 1 | 0) | 0; 27 | })]; 28 | } 29 | 30 | function $$float(min, max) { 31 | return /* Generator */[(function (state) { 32 | return min + Random.State.$$float(state, max - min); 33 | })]; 34 | } 35 | 36 | function list(count, param) { 37 | var genCmd = param[0]; 38 | return /* Generator */[(function (state) { 39 | var state$1 = state; 40 | var _i = count; 41 | var _acc = /* [] */0; 42 | while(true) { 43 | var acc = _acc; 44 | var i = _i; 45 | if (i > 0) { 46 | _acc = /* :: */[ 47 | Curry._1(genCmd, state$1), 48 | acc 49 | ]; 50 | _i = i - 1 | 0; 51 | continue ; 52 | } else { 53 | return acc; 54 | } 55 | }; 56 | })]; 57 | } 58 | 59 | function map(func, param) { 60 | var genCmd = param[0]; 61 | return /* Generator */[(function (state) { 62 | return Curry._1(func, Curry._1(genCmd, state)); 63 | })]; 64 | } 65 | 66 | function map2(func, param, param$1) { 67 | var genCmd2 = param$1[0]; 68 | var genCmd1 = param[0]; 69 | return /* Generator */[(function (state) { 70 | var res1 = Curry._1(genCmd1, state); 71 | var res2 = Curry._1(genCmd2, state); 72 | return Curry._2(func, res1, res2); 73 | })]; 74 | } 75 | 76 | function map3(func, param, param$1, param$2) { 77 | var genCmd3 = param$2[0]; 78 | var genCmd2 = param$1[0]; 79 | var genCmd1 = param[0]; 80 | return /* Generator */[(function (state) { 81 | var res1 = Curry._1(genCmd1, state); 82 | var res2 = Curry._1(genCmd2, state); 83 | var res3 = Curry._1(genCmd3, state); 84 | return Curry._3(func, res1, res2, res3); 85 | })]; 86 | } 87 | 88 | function map4(func, param, param$1, param$2, param$3) { 89 | var genCmd4 = param$3[0]; 90 | var genCmd3 = param$2[0]; 91 | var genCmd2 = param$1[0]; 92 | var genCmd1 = param[0]; 93 | return /* Generator */[(function (state) { 94 | var res1 = Curry._1(genCmd1, state); 95 | var res2 = Curry._1(genCmd2, state); 96 | var res3 = Curry._1(genCmd3, state); 97 | var res4 = Curry._1(genCmd4, state); 98 | return Curry._4(func, res1, res2, res3, res4); 99 | })]; 100 | } 101 | 102 | function map5(func, param, param$1, param$2, param$3, param$4) { 103 | var genCmd5 = param$4[0]; 104 | var genCmd4 = param$3[0]; 105 | var genCmd3 = param$2[0]; 106 | var genCmd2 = param$1[0]; 107 | var genCmd1 = param[0]; 108 | return /* Generator */[(function (state) { 109 | var res1 = Curry._1(genCmd1, state); 110 | var res2 = Curry._1(genCmd2, state); 111 | var res3 = Curry._1(genCmd3, state); 112 | var res4 = Curry._1(genCmd4, state); 113 | var res5 = Curry._1(genCmd5, state); 114 | return Curry._5(func, res1, res2, res3, res4, res5); 115 | })]; 116 | } 117 | 118 | function andThen(func, param) { 119 | var genCmd = param[0]; 120 | return /* Generator */[(function (state) { 121 | var res = Curry._1(genCmd, state); 122 | var match = Curry._1(func, res); 123 | return Curry._1(match[0], state); 124 | })]; 125 | } 126 | 127 | function pair(gen1, gen2) { 128 | return map2((function (a, b) { 129 | return /* tuple */[ 130 | a, 131 | b 132 | ]; 133 | }), gen1, gen2); 134 | } 135 | 136 | function generate(tagger, param) { 137 | var genCmd = param[0]; 138 | return /* EnqueueCall */Block.__(2, [(function (callbacks) { 139 | var state = Random.get_state(/* () */0); 140 | var genValue = Curry._1(genCmd, state); 141 | Random.set_state(state); 142 | return Curry._1(callbacks.contents.enqueue, Curry._1(tagger, genValue)); 143 | })]); 144 | } 145 | 146 | function step(param, param$1) { 147 | var newState = Random.State.copy(param$1[0]); 148 | return /* tuple */[ 149 | Curry._1(param[0], newState), 150 | /* Seed */[newState] 151 | ]; 152 | } 153 | 154 | function initialSeed(seed) { 155 | return /* Seed */[Random.State.make([seed])]; 156 | } 157 | 158 | var minInt = Pervasives.min_int; 159 | 160 | var maxInt = Pervasives.max_int; 161 | 162 | var minFloat = Pervasives.min_float; 163 | 164 | var maxFloat = Pervasives.max_float; 165 | 166 | exports.minInt = minInt; 167 | exports.maxInt = maxInt; 168 | exports.minFloat = minFloat; 169 | exports.maxFloat = maxFloat; 170 | exports.bool = bool; 171 | exports.$$int = $$int; 172 | exports.$$float = $$float; 173 | exports.list = list; 174 | exports.map = map; 175 | exports.map2 = map2; 176 | exports.map3 = map3; 177 | exports.map4 = map4; 178 | exports.map5 = map5; 179 | exports.andThen = andThen; 180 | exports.pair = pair; 181 | exports.generate = generate; 182 | exports.step = step; 183 | exports.initialSeed = initialSeed; 184 | /* Not a pure module */ 185 | -------------------------------------------------------------------------------- /lib/js/src-ocaml/tea_sub.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 'use strict'; 3 | 4 | var List = require("bs-platform/lib/js/list.js"); 5 | var Vdom = require("./vdom.js"); 6 | var Block = require("bs-platform/lib/js/block.js"); 7 | var Curry = require("bs-platform/lib/js/curry.js"); 8 | 9 | function batch(subs) { 10 | return /* Batch */Block.__(0, [subs]); 11 | } 12 | 13 | function registration(key, enableCall) { 14 | return /* Registration */Block.__(1, [ 15 | key, 16 | (function (callbacks) { 17 | return Curry._1(enableCall, callbacks.contents); 18 | }), 19 | { 20 | contents: undefined 21 | } 22 | ]); 23 | } 24 | 25 | function map(msgMapper, sub) { 26 | var func = function (callbacks) { 27 | return Vdom.wrapCallbacks(msgMapper, callbacks); 28 | }; 29 | return /* Mapper */Block.__(2, [ 30 | func, 31 | sub 32 | ]); 33 | } 34 | 35 | function mapFunc(func, sub) { 36 | return /* Mapper */Block.__(2, [ 37 | func, 38 | sub 39 | ]); 40 | } 41 | 42 | function run(oldCallbacks, newCallbacks, oldSub, newSub) { 43 | var enable = function (_callbacks, _param) { 44 | while(true) { 45 | var param = _param; 46 | var callbacks = _callbacks; 47 | if (typeof param === "number") { 48 | return /* () */0; 49 | } else { 50 | switch (param.tag | 0) { 51 | case /* Batch */0 : 52 | var subs = param[0]; 53 | if (subs) { 54 | return List.iter((function(callbacks){ 55 | return function (param) { 56 | return enable(callbacks, param); 57 | } 58 | }(callbacks)), subs); 59 | } else { 60 | return /* () */0; 61 | } 62 | case /* Registration */1 : 63 | param[2].contents = Curry._1(param[1], callbacks); 64 | return /* () */0; 65 | case /* Mapper */2 : 66 | var subCallbacks = Curry._1(param[0], callbacks); 67 | _param = param[1]; 68 | _callbacks = subCallbacks; 69 | continue ; 70 | 71 | } 72 | } 73 | }; 74 | }; 75 | var disable = function (_callbacks, _param) { 76 | while(true) { 77 | var param = _param; 78 | var callbacks = _callbacks; 79 | if (typeof param === "number") { 80 | return /* () */0; 81 | } else { 82 | switch (param.tag | 0) { 83 | case /* Batch */0 : 84 | var subs = param[0]; 85 | if (subs) { 86 | return List.iter((function(callbacks){ 87 | return function (param) { 88 | return disable(callbacks, param); 89 | } 90 | }(callbacks)), subs); 91 | } else { 92 | return /* () */0; 93 | } 94 | case /* Registration */1 : 95 | var diCB = param[2]; 96 | var match = diCB.contents; 97 | if (match !== undefined) { 98 | diCB.contents = undefined; 99 | return Curry._1(match, /* () */0); 100 | } else { 101 | return /* () */0; 102 | } 103 | case /* Mapper */2 : 104 | var subCallbacks = Curry._1(param[0], callbacks); 105 | _param = param[1]; 106 | _callbacks = subCallbacks; 107 | continue ; 108 | 109 | } 110 | } 111 | }; 112 | }; 113 | if (typeof oldSub === "number") { 114 | if (typeof newSub === "number") { 115 | return newSub; 116 | } 117 | 118 | } else { 119 | switch (oldSub.tag | 0) { 120 | case /* Batch */0 : 121 | if (typeof newSub !== "number" && !newSub.tag) { 122 | var aux = function (_oldList, _newList) { 123 | while(true) { 124 | var newList = _newList; 125 | var oldList = _oldList; 126 | if (oldList) { 127 | var oldRest = oldList[1]; 128 | var oldSubSub = oldList[0]; 129 | if (newList) { 130 | run(oldCallbacks, newCallbacks, oldSubSub, newList[0]); 131 | _newList = newList[1]; 132 | _oldList = oldRest; 133 | continue ; 134 | } else { 135 | disable(oldCallbacks, oldSubSub); 136 | _newList = /* [] */0; 137 | _oldList = oldRest; 138 | continue ; 139 | } 140 | } else if (newList) { 141 | enable(newCallbacks, newList[0]); 142 | _newList = newList[1]; 143 | _oldList = /* [] */0; 144 | continue ; 145 | } else { 146 | return /* () */0; 147 | } 148 | }; 149 | }; 150 | aux(oldSub[0], newSub[0]); 151 | return newSub; 152 | } 153 | break; 154 | case /* Registration */1 : 155 | if (typeof newSub !== "number" && newSub.tag === /* Registration */1 && oldSub[0] === newSub[0]) { 156 | newSub[2].contents = oldSub[2].contents; 157 | return newSub; 158 | } 159 | break; 160 | case /* Mapper */2 : 161 | if (typeof newSub !== "number" && newSub.tag === /* Mapper */2) { 162 | var olderCallbacks = Curry._1(oldSub[0], oldCallbacks); 163 | var newerCallbacks = Curry._1(newSub[0], newCallbacks); 164 | run(olderCallbacks, newerCallbacks, oldSub[1], newSub[1]); 165 | return newSub; 166 | } 167 | break; 168 | 169 | } 170 | } 171 | disable(oldCallbacks, oldSub); 172 | enable(newCallbacks, newSub); 173 | return newSub; 174 | } 175 | 176 | var none = /* NoSub */0; 177 | 178 | exports.none = none; 179 | exports.batch = batch; 180 | exports.registration = registration; 181 | exports.map = map; 182 | exports.mapFunc = mapFunc; 183 | exports.run = run; 184 | /* No side effect */ 185 | -------------------------------------------------------------------------------- /src-reason/tea_task.re: -------------------------------------------------------------------------------- 1 | type never; 2 | type t('succeed, 'fail) = 3 | | Task((Tea_result.t('succeed, 'fail) => unit) => unit) 4 | : t('succeed, 'fail); 5 | 6 | let nothing = () => (); 7 | 8 | let performOpt = 9 | ( 10 | toOptionalMessage: 'value => option('msg), 11 | Task(task): t('value, never), 12 | ) 13 | : Tea_cmd.t('msg) => 14 | Tea_cmd.call(callbacks => { 15 | open Tea_result; 16 | open Vdom; 17 | let cb = 18 | fun 19 | | Error(_e) => 20 | failwith( 21 | "ERROR: Task perfom returned error of never! Should not happen!", 22 | ) 23 | | Ok(v) => 24 | switch (toOptionalMessage(v)) { 25 | | None => () 26 | | Some(result) => callbacks^.enqueue(result) 27 | }; 28 | task(cb); 29 | }); 30 | let perform = 31 | (toMessage: 'value => 'msg, task: t('value, never)): Tea_cmd.t('msg) => 32 | performOpt(v => Some(toMessage(v)), task); 33 | let attemptOpt = 34 | ( 35 | resultToOptionalMessage: Tea_result.t('succeed, 'fail) => option('msg), 36 | Task(task): t('succeed, 'fail), 37 | ) 38 | : Tea_cmd.t('msg) => 39 | Tea_cmd.call(callbacks => { 40 | open Vdom; 41 | let cb = value => 42 | switch (resultToOptionalMessage(value)) { 43 | | None => () 44 | | Some(result) => callbacks^.enqueue(result) 45 | }; 46 | task(cb); 47 | }); 48 | let attempt = 49 | ( 50 | resultToMessage: Tea_result.t('succeed, 'fail) => 'msg, 51 | task: t('succeed, 'fail), 52 | ) 53 | : Tea_cmd.t('msg) => 54 | attemptOpt(v => Some(resultToMessage(v)), task); 55 | let ignore = task => attemptOpt(_ => None, task); 56 | let succeed = (value: 'v): t('v, 'e) => 57 | Task(cb => cb(Tea_result.Ok(value))); 58 | let fail = (value: 'v): t('e, 'v) => 59 | Task(cb => cb(Tea_result.Error(value))); 60 | let nativeBinding = 61 | (func: (Tea_result.t('succeed, 'fail) => unit) => unit) 62 | : t('succeed, 'fail) => 63 | Task(func); 64 | 65 | let andThen = (fn, Task(task)) => 66 | Tea_result.( 67 | Task( 68 | cb => 69 | task( 70 | fun 71 | | Error(_e) as err => cb(err) 72 | | Ok(v) => { 73 | let Task(nextTask) = fn(v); 74 | nextTask(cb); 75 | }, 76 | ), 77 | ) 78 | ); 79 | let onError = (fn, Task(task)) => 80 | Tea_result.( 81 | Task( 82 | cb => 83 | task( 84 | fun 85 | | Ok(_v) as ok => cb(ok) 86 | | Error(e) => { 87 | let Task(newTask) = fn(e); 88 | newTask(cb); 89 | }, 90 | ), 91 | ) 92 | ); 93 | 94 | let fromResult: Tea_result.t('success, 'failure) => t('success, 'failure) = 95 | fun 96 | | Tea_result.Ok(s) => succeed(s) 97 | | Tea_result.Error(err) => fail(err); 98 | 99 | let mapError = (func, task) => task |> onError(e => fail(func(e))); 100 | 101 | let map = (func, task1) => task1 |> andThen(v1 => succeed(func(v1))); 102 | let map2 = (func, task1, task2) => 103 | task1 |> andThen(v1 => task2 |> andThen(v2 => succeed(func(v1, v2)))); 104 | let map3 = (func, task1, task2, task3) => 105 | task1 106 | |> andThen(v1 => 107 | task2 108 | |> andThen(v2 => task3 |> andThen(v3 => succeed(func(v1, v2, v3)))) 109 | ); 110 | let map4 = (func, task1, task2, task3, task4) => 111 | task1 112 | |> andThen(v1 => 113 | task2 114 | |> andThen(v2 => 115 | task3 116 | |> andThen(v3 => 117 | task4 |> andThen(v4 => succeed(func(v1, v2, v3, v4))) 118 | ) 119 | ) 120 | ); 121 | let map5 = (func, task1, task2, task3, task4, task5) => 122 | task1 123 | |> andThen(v1 => 124 | task2 125 | |> andThen(v2 => 126 | task3 127 | |> andThen(v3 => 128 | task4 129 | |> andThen(v4 => 130 | task5 131 | |> andThen(v5 => succeed(func(v1, v2, v3, v4, v5))) 132 | ) 133 | ) 134 | ) 135 | ); 136 | let map6 = (func, task1, task2, task3, task4, task5, task6) => 137 | task1 138 | |> andThen(v1 => 139 | task2 140 | |> andThen(v2 => 141 | task3 142 | |> andThen(v3 => 143 | task4 144 | |> andThen(v4 => 145 | task5 146 | |> andThen(v5 => 147 | task6 148 | |> andThen(v6 => 149 | succeed(func(v1, v2, v3, v4, v5, v6)) 150 | ) 151 | ) 152 | ) 153 | ) 154 | ) 155 | ); 156 | let rec sequence = 157 | fun 158 | | [] => succeed([]) 159 | | [task, ...remainingTasks] => 160 | map2((l, r) => [l, ...r], task, sequence(remainingTasks)); 161 | 162 | let testing_deop = ref(true); 163 | 164 | let testing = () => { 165 | open Tea_result; 166 | let doTest = (expected, Task(task)) => { 167 | let testAssert = v => 168 | if (v == expected) { 169 | Js.log(("Passed:", expected, v)); 170 | } else { 171 | Js.log(("FAILED:", expected, v)); 172 | }; 173 | task(testAssert); 174 | }; 175 | let s = succeed(42); 176 | let () = doTest(Ok(42), s); 177 | let f = fail(86); 178 | let () = doTest(Error(86), f); 179 | let r = () => 180 | if (testing_deop^) { 181 | succeed(42); 182 | } else { 183 | fail(3.14); 184 | }; 185 | let a1 = succeed(2) |> andThen(n => succeed(n + 2)); 186 | let () = doTest(Ok(4), a1); 187 | let a2 = succeed(2) |> andThen(n => succeed(string_of_int(n))); 188 | let () = doTest(Ok("2"), a2); 189 | let m1 = map(sqrt, succeed(9.)); 190 | let () = doTest(Ok(3.), m1); 191 | let m2 = map2((+), succeed(9), succeed(3)); 192 | let () = doTest(Ok(12), m2); 193 | let m3 = map(string_of_int, succeed(9)); 194 | let () = doTest(Ok("9"), m3); 195 | let s0 = sequence([succeed(1), succeed(2)]); 196 | let () = doTest(Ok([1, 2]), s0); 197 | let s1 = sequence([succeed(1), fail(2.7), r()]); 198 | let () = doTest(Error(2.7), s1); 199 | let e0 = fail("file not found") |> onError(_msg => succeed(42)); 200 | let () = doTest(Ok(42), e0); 201 | let e1 = fail("file not found") |> onError(_msg => fail(42)); 202 | let () = doTest(Error(42), e1); 203 | let n0 = 204 | sequence([ 205 | mapError(string_of_int, fail(42)), 206 | mapError(string_of_float, fail(3.14)), 207 | ]); 208 | let () = doTest(Error("42"), n0); 209 | let n1 = 210 | sequence([ 211 | mapError(string_of_int, succeed(1)), 212 | mapError(string_of_float, fail(3.14)), 213 | ]); 214 | let () = doTest(Error("3.14"), n1); 215 | let n2 = 216 | sequence([ 217 | mapError(string_of_int, succeed(1)), 218 | mapError(string_of_float, succeed(2)), 219 | ]); 220 | let () = doTest(Ok([1, 2]), n2); 221 | let _c0 = perform(_ => 42, succeed(18)); 222 | 223 | let () = doTest(Ok(42), fromResult(Ok(42))); 224 | let () = doTest(Error("failure"), fromResult(Error("failure"))); 225 | 226 | let () = doTest(Ok(None), fail("for some reason") |> toOption); 227 | let () = doTest(Ok(Some(42)), succeed(42) |> toOption); 228 | 229 | (); 230 | }; 231 | --------------------------------------------------------------------------------