├── .gitignore ├── .merlin ├── LICENSE ├── Makefile ├── README.md ├── _oasis ├── _tags ├── configure ├── doc └── style.css ├── myocamlbuild.ml ├── opam ├── descr ├── findlib └── opam ├── reactjs_based_examples ├── Makefile ├── basic-click-counter │ ├── Makefile │ ├── example.ml │ └── index.html ├── basic │ ├── Makefile │ ├── example.ml │ └── index.html ├── quadratic │ ├── Makefile │ ├── example.ml │ └── index.html ├── test_all.sh └── todomvc │ ├── Makefile │ ├── README.md │ ├── base.css │ ├── example.ml │ ├── index.css │ └── index.html ├── setup.ml ├── src ├── META ├── ppx │ └── ppx_reactjs.ml ├── reactjs.ml ├── reactjs.mldylib ├── reactjs.mli └── reactjs.mllib └── static ├── README_base.markdown ├── add_to_read_me.sh ├── base.css ├── basic-click-counter.gif ├── basic.gif ├── quadratic.gif ├── react-dom.js ├── react.js ├── reactjs_doc_pdf.gif ├── reactjs_ocp_browser.gif └── todomvc.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .tern-port 2 | *.annot 3 | *.cmo 4 | *.cma 5 | *.cmi 6 | *.cmt 7 | *.a 8 | *.o 9 | *.cmx 10 | *.cmxs 11 | *.cmxa 12 | 13 | # ocamlbuild working directory 14 | _build/ 15 | 16 | # ocamlbuild targets 17 | *.byte 18 | *.native 19 | 20 | # oasis generated files 21 | setup.data 22 | setup.log 23 | T 24 | node_modules 25 | examples/tutorial_high_level 26 | examples/tutorial_high_level.js 27 | reactjs_based_examples/basic/code 28 | reactjs_based_examples/basic/code.js 29 | scratch.js 30 | code.js 31 | code 32 | api.docdir 33 | react_js.* 34 | reactjs_bindings.pdf -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | S src 2 | B _build/src 3 | PKG js_of_ocaml.ppx js_of_ocaml reactjs 4 | PKG compiler-libs compiler-libs.common 5 | PKG js_of_ocaml.compiler 6 | PKG commonjs commonjs_ppx 7 | PKG ppx_deriving.show ppx_deriving.make 8 | FLG -w +a-4-40..42-44-45-48 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Edgar Aroutiounian 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of ocaml-reactjs nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: a3c674b4239234cbbe53afe090018954) 3 | 4 | SETUP = ocaml setup.ml 5 | 6 | build: setup.data 7 | $(SETUP) -build $(BUILDFLAGS) 8 | 9 | doc: setup.data build 10 | $(SETUP) -doc $(DOCFLAGS) 11 | 12 | test: setup.data build 13 | $(SETUP) -test $(TESTFLAGS) 14 | 15 | all: 16 | $(SETUP) -all $(ALLFLAGS) 17 | 18 | install: setup.data 19 | $(SETUP) -install $(INSTALLFLAGS) 20 | 21 | uninstall: setup.data 22 | $(SETUP) -uninstall $(UNINSTALLFLAGS) 23 | 24 | reinstall: setup.data 25 | $(SETUP) -reinstall $(REINSTALLFLAGS) 26 | 27 | clean: 28 | $(SETUP) -clean $(CLEANFLAGS) 29 | 30 | distclean: 31 | $(SETUP) -distclean $(DISTCLEANFLAGS) 32 | 33 | setup.data: 34 | $(SETUP) -configure $(CONFIGUREFLAGS) 35 | 36 | configure: 37 | $(SETUP) -configure $(CONFIGUREFLAGS) 38 | 39 | .PHONY: build doc test all install uninstall reinstall clean distclean configure 40 | 41 | # OASIS_STOP 42 | .PHONY: readme 43 | 44 | # Add dir name here when example is ready 45 | dirs := basic basic-click-counter quadratic todomvc 46 | files := $(foreach dir,$(dirs),$(wildcard reactjs_based_examples/$(dir)/*.ml)) 47 | 48 | dist_clean:; @rm -f README.md 49 | 50 | readme: dist_clean 51 | @cp static/README_base.markdown README.md 52 | @for file in ${files} ; do \ 53 | bash static/add_to_read_me.sh $$file ; \ 54 | done 55 | 56 | pdf_doc := reactjs_bindings.pdf 57 | 58 | # Install wkhtmltopdf with brew cask install wkhtmltopdf 59 | generate_pdf:doc 60 | @printf '\n\tCreating a PDF of the documentation\n' 61 | @(cd api.docdir; \ 62 | wkhtmltopdf --load-error-handling ignore * ${pdf_doc}) 63 | @mv api.docdir/${pdf_doc} . 64 | @printf '\n\tCreated %s!\n' ${pdf_doc} 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ReactJS bindings in OCaml 2 | ========================= 3 | 4 | These are my bindings to ReactJS, by my count this is the global sixth 5 | attempt at binding to React and my own second attempt, its kind of 6 | hard. 7 | 8 | Installation 9 | ============ 10 | 11 | Right now you can install with: 12 | 13 | ```shell 14 | $ opam install reactjs 15 | ``` 16 | 17 | To get the development version, do: 18 | 19 | ```shell 20 | $ opam pin add -y reactjs git@github.com:fxfactorial/ocaml-reactjs.git 21 | ``` 22 | 23 | The bindings should work on `node` or in the browser, both will assume 24 | that `React`, and `ReactDOM` exist (on node they will do the 25 | appropriate `require`, node side will also try to load the npm package 26 | `react-dom`) 27 | 28 | Documentation 29 | ============= 30 | 31 | See this blog [post](http://hyegar.com/2016/07/17/js-of-ocaml-usage/) 32 | to get a better understanding of OCaml typing of JavaScript objects 33 | and such, (explains the `##` syntax extension). 34 | 35 | The `mli` is commented and the doc strings should come up for you with 36 | `merlin`. I also recommend using `ocp-browser`, this is a program that 37 | is installed via `opam install ocp-index` and it gives you a nice high 38 | level way to see the API: 39 | 40 | ![img](./static/reactjs_ocp_browser.gif) 41 | 42 | You can also do: `make doc` in the repo, that will create a directory 43 | called `api.docdir` and in there you open the `index.html` for pretty 44 | generated documentation. 45 | 46 | If you're okay with doing: `brew cask install wkhtmltopdf`, then you 47 | can get a PDF generated from the OCaml documentation. do 48 | 49 | ```shell 50 | $ make generate_pdf 51 | ``` 52 | 53 | And then the `reactjs_bindings.pdf` will be built in the root of the 54 | directory. It should look like: 55 | 56 | ![img](./static/reactjs_doc_pdf.gif) 57 | 58 | Contributing 59 | ============ 60 | 61 | Contributions of any kind are appreciated. If you're updating this 62 | readme, then be update `static/README_base.markdown` and then run 63 | `make readme`. 64 | 65 | For the source code itself, be aware that it uses some more advanced 66 | features of the type system and can be mental pain. I haven't exposed 67 | everything of `React` yet, and the library can still be made more 68 | strongly typed. 69 | 70 | Right now a `JSX` like ppx is needed since writing out the `Text`, 71 | `Elem` variants can be more strain on the brain and `JSX` lets you see 72 | the structure of the element you're making more easily. 73 | 74 | Before opening a PR, be sure to test all the existing examples. You 75 | can build them all at once from the `reactjs_based_examples` directory 76 | with `make all_examples`, or `make -C reactjs_based_examples 77 | all_examples` from the root directory. 78 | 79 | More examples added are always appreciated and you can do it by: 80 | 81 | ```shell 82 | $ cd reactjs_based_examples 83 | $ cp -R basic your_new_example 84 | ``` 85 | 86 | and add your example's directory name to the `Makefile`'s `dirs` 87 | variable in the root of the project, 88 | 89 | around line 40s, `dirs := basic basic-click-counter quadratic`. 90 | 91 | Examples 92 | ======== 93 | 94 | These examples should be familiar and are autogenerated into this 95 | README from under the `reactjs_based_examples` dir. 96 | 97 | Check the wiki for common FAQs, compile any example with: 98 | 99 | ```shell 100 | $ ocamlfind ocamlc -package reactjs -linkpkg code.ml 101 | $ js_of_ocaml a.out -o code.js 102 | ``` 103 | 104 | Also see 105 | [ocaml-mailing-list](https://github.com/fxfactorial/ocaml-mailing-list) 106 | for more example source code, include how to render on the server with 107 | `nodejs`. 108 | 109 | 110 | [//]: # "Do not write anything below here, the code examples will be appended" 111 | 112 | ![img](./static/basic.gif) 113 | 114 | # reactjs_based_examples/basic 115 | 116 | ```ocaml 117 | let example_application = Reactjs.( 118 | make_class_spec 119 | ~initial_state:(fun ~this -> 120 | print_endline "Initial state called"; 121 | object%js end 122 | ) 123 | ~default_props:(fun ~this -> 124 | print_endline "Default props called"; 125 | object%js end 126 | ) 127 | ~component_will_mount:(fun ~this -> print_endline "Component will mount") 128 | ~component_did_mount:(fun ~this -> print_endline "Component did mount") 129 | ~component_will_receive_props:(fun ~this ~next_prop -> 130 | print_endline "Component will receive props" 131 | ) 132 | ~should_component_update:(fun ~this ~next_prop ~next_state -> 133 | print_endline "Should component update called"; 134 | Js.bool true 135 | ) 136 | ~component_will_update:(fun ~this ~next_prop ~next_state -> 137 | print_endline "Component will update" 138 | ) 139 | ~component_did_update:(fun ~this ~prev_prop ~prev_state -> 140 | print_endline "Component did update" 141 | ) 142 | ~component_will_unmount:(fun ~this -> print_endline "Component about to unmount") 143 | (fun ~this -> 144 | let elapsed = Js.math##round this##.props##.elapsed /. 100.0 in 145 | let seconds = elapsed /. 10.0 in 146 | let message = Printf.sprintf 147 | "React has been successfully running for %f seconds" seconds 148 | in 149 | DOM.make ~tag:`p [Text message] 150 | ) 151 | |> create_class 152 | ) 153 | 154 | let _ = Reactjs.( 155 | let example_app_factory = create_factory example_application in 156 | let start = (new%js Js.date_now)##getTime in 157 | set_interval 158 | ~f:(fun () -> 159 | try 160 | let react_elem = example_app_factory ~props:(object%js 161 | val elapsed = (new%js Js.date_now)##getTime -. start 162 | end) 163 | in 164 | render ~react_elem (get_elem ~id:"container") 165 | (* Get OCaml exception handling! *) 166 | with Js.Error e -> 167 | Firebug.console##log e 168 | ) ~every:100.0 169 | ) 170 | ``` 171 | 172 | ![img](./static/basic-click-counter.gif) 173 | 174 | # reactjs_based_examples/basic-click-counter 175 | 176 | ```ocaml 177 | let counter = Reactjs.( 178 | make_class_spec 179 | ~initial_state:(fun ~this -> (object%js val count = 0 end)) 180 | ~component_will_mount:(fun ~this -> 181 | print_endline "Component about to mount" 182 | ) 183 | (fun ~this -> let open Reactjs.Infix in 184 | let handle_click = !@(fun () -> 185 | this##setState (object%js val count = this##.state##.count + 1 end)) 186 | in 187 | DOM.make 188 | ~elem_spec:(object%js 189 | val onClick = handle_click 190 | end) 191 | ~tag:`button 192 | [Text (Printf.sprintf 193 | "Click me, number of clicks: %d" this##.state##.count)]) 194 | |> create_class 195 | ) 196 | 197 | let () = Reactjs.( 198 | render 199 | ~react_elem:(create_element_from_class counter) 200 | (get_elem ~id:"container") 201 | ) 202 | ``` 203 | 204 | ![img](./static/quadratic.gif) 205 | 206 | # reactjs_based_examples/quadratic 207 | 208 | ```ocaml 209 | open StdLabels 210 | 211 | let quadratic_calculator = Reactjs.( 212 | make_class_spec 213 | ~initial_state:(fun ~this -> object%js 214 | val a = 1.0 val b = 3.0 val c = -3.0 215 | end) 216 | (fun ~this -> let open Infix in 217 | let handle_input_change = 218 | fun ~key event -> 219 | let new_state = 220 | ([(key, 221 | event##.target##.value |> Js.parseFloat |> Js.number_of_float )] >>> 222 | object%js end) 223 | in 224 | this##setState new_state 225 | in 226 | let (a, b, c) = this##.state##.a, this##.state##.b, this##.state##.c in 227 | let root = Js.math##sqrt ((Js.math##pow b 2.0) -. 4.0 *. a *. c) in 228 | let denominator = 2.0 *. a in 229 | let (x1, x2) = (-.b +. root) /. denominator, (-.b -. root) /. denominator in 230 | let input_label ~key init_value = DOM.( 231 | make ~tag:`label 232 | [Text (Printf.sprintf "%s: " key); 233 | Elem (make ~elem_spec:(object%js 234 | val type_ = !*"number" 235 | val value = !^init_value 236 | val onChange = handle_input_change ~key 237 | end) ~tag:`input [])] 238 | ) 239 | in 240 | let label_row l = l |> List.map ~f:(fun (key, value) -> 241 | [Elem (input_label ~key value); Elem (DOM.make ~tag:`br [])] 242 | ) |> List.flatten 243 | in 244 | let equation_row = DOM.( 245 | [Elem (make ~tag:`em [Text "ax"]); Elem (make ~tag:`sup [Text "2"]); 246 | Text " + "; Elem (make ~tag:`em [Text "bx"]); Text " + "; 247 | Elem (make ~tag:`em [Text "c"]); Text " = 0"]) 248 | in 249 | DOM.(make ~tag:`div 250 | [Elem (make ~tag:`strong equation_row ); 251 | Elem (make ~tag:`h4 [Text "Solve for "; 252 | Elem (make ~tag:`em [Text "x"])]); 253 | Elem (make ~tag:`p 254 | (label_row [("a", a); ("b", b); ("c", c)] @ 255 | [Text "x: "; 256 | Elem (make ~tag:`strong 257 | [Text (Printf.sprintf "%f %f" x1 x2)])])) 258 | ])) 259 | |> create_class 260 | ) 261 | 262 | let () = 263 | Reactjs.(render 264 | ~react_elem:(create_element_from_class quadratic_calculator) 265 | (get_elem ~id:"container")) 266 | ``` 267 | 268 | ![img](./static/todomvc.gif) 269 | 270 | # reactjs_based_examples/todomvc 271 | 272 | ```ocaml 273 | type task_id = int 274 | 275 | type task = 276 | { 277 | id: task_id; 278 | label: string; 279 | completed: bool; 280 | } 281 | 282 | type tab = All | Active | Completed 283 | 284 | type state = { 285 | editing: task_id option; 286 | tasks: task list; 287 | tab: tab; 288 | } 289 | 290 | type history = 291 | state list 292 | 293 | let initial_state = 294 | let tasks = [{id = 0; 295 | label = "Initial task"; 296 | completed = false}; 297 | {id = 1; 298 | label = "Completed task"; 299 | completed = true}; 300 | {id = 2; 301 | label = "Final task"; 302 | completed = true};] 303 | in 304 | ref {editing = None; 305 | tasks = tasks; 306 | tab = All} 307 | 308 | let (histories : history ref) = 309 | ref [] 310 | 311 | let observe _old_state new_state = 312 | Printf.printf "Mutation observed: %d\n" (List.length new_state.tasks) 313 | 314 | let observer = 315 | ref (fun _old_state _new_state -> Printf.printf "Placeholder observer used") 316 | 317 | let swap (ref : state ref) f = 318 | let old_state = !ref in 319 | ref := f ref; 320 | let new_state = !ref in 321 | ignore(!observer old_state new_state); 322 | histories := List.append [new_state] !histories; 323 | Printf.printf "History count: %d\n" (List.length !histories); 324 | () 325 | 326 | let set_tab state tab = 327 | {state with tab = tab} 328 | 329 | let random_el arr = 330 | let n = Random.int (Array.length arr) in 331 | Array.get arr n;; 332 | 333 | let colors = 334 | [|"blue"; "green"; "pink"; "purple"; "white"; "gray"|] 335 | 336 | let random_color () = 337 | random_el colors 338 | 339 | let todo_item (task : task) = 340 | let open Reactjs in 341 | let open Infix in 342 | make_class_spec 343 | (fun ~this -> 344 | ignore(this); 345 | DOM.make ~tag:`li 346 | ~elem_spec:(object%js 347 | val key = !*("li-todo-input-" ^ (string_of_int task.id)) 348 | end) 349 | (if (match !initial_state.editing with 350 | | None -> false 351 | | Some id -> id = task.id) then 352 | [Elem (DOM.make ~tag:`input ~elem_spec:(object%js 353 | val key = !*("todo-input-" ^ (string_of_int task.id)) 354 | val type_ = !*"text" 355 | val style = (object%js val display = "block" val backgroundColor = !*("white") end) 356 | val defaultValue = task.label 357 | val onChange = (fun event -> 358 | this##setState event##.target##.value |> Js.to_string 359 | (* swap initial_state (fun state -> *) 360 | (* let new_tasks = List.map (fun t -> *) 361 | (* if t.id = task.id then *) 362 | (* {t with label = event##.target##.value |> Js.to_string } *) 363 | (* else *) 364 | (* t) !state.tasks in *) 365 | (* {!state with tasks = new_tasks}) *)) 366 | val onKeyUp = (fun event -> 367 | Printf.printf "Key: %d\n" event##.which; 368 | match event##.which with 369 | | 13 -> swap initial_state (fun state -> 370 | let new_tasks = List.map (fun t -> 371 | if t.id = task.id then 372 | {t with label = event##.target##.value |> Js.to_string } 373 | else 374 | t) !state.tasks in 375 | let _tt = List.find (fun t -> t.id = task.id) new_tasks in 376 | Printf.printf "Task label after updating: %s\n" _tt.label; 377 | {!state with 378 | tasks = new_tasks; 379 | editing = None}) 380 | | 27 -> swap initial_state (fun state -> {!state with editing = None}) 381 | | _ -> () 382 | 383 | ) 384 | val className = Js.string "edit" 385 | end) 386 | [])] 387 | else 388 | [Elem (DOM.make 389 | ~tag:`div 390 | ~elem_spec:(object%js 391 | val className = "view" 392 | val onDoubleClick = (fun _ -> swap initial_state (fun state -> 393 | Printf.printf "Now editing %d\n"task.id; 394 | {!state with editing = Some task.id} 395 | )) 396 | end) 397 | [Elem (DOM.make ~tag:`input ~elem_spec:(object%js 398 | val type_ = !*"checkbox" 399 | val onClick = (fun _ -> swap initial_state (fun state -> 400 | let new_tasks = List.map (fun t -> 401 | if t.id = task.id then 402 | {t with completed = not t.completed} 403 | else 404 | t 405 | ) !state.tasks in 406 | {!state with tasks = new_tasks} 407 | )) 408 | val checked = Js.string (if task.completed then "checked" else "") 409 | val className = Js.string "toggle" 410 | end) 411 | []); 412 | Elem (DOM.make 413 | ~tag:`label 414 | [Text ((match !initial_state.editing with 415 | | None -> "" 416 | | Some editing -> if editing = task.id then "Editing: " else "") ^ task.label);]); 417 | Elem (DOM.make 418 | ~tag:`button 419 | ~elem_spec:(object%js 420 | val onClick = (fun _ -> swap initial_state (fun state -> 421 | let new_tasks = List.filter (fun t -> 422 | t.id != task.id 423 | ) !state.tasks in 424 | {!state with tasks = new_tasks} 425 | )) 426 | val className = !*"destroy" 427 | end) 428 | [])])])) 429 | |> create_class 430 | 431 | let todo_input () = 432 | let open Reactjs in 433 | let open Infix in 434 | make_class_spec 435 | (fun ~this -> 436 | ignore(this); 437 | DOM.make ~tag:`input 438 | ~elem_spec:(object%js 439 | val className = !*"new-todo" 440 | val placeholder = !*"What needs to be done" 441 | val autofocus = !*"true" 442 | val onKeyDown = (fun event -> 443 | match event##.which with 444 | | 13 -> swap initial_state (fun state -> 445 | let id = Random.int 64000 in 446 | Printf.printf "Updating new task...\n"; 447 | Firebug.console##log event##.target##.value; 448 | let new_tasks = List.append !state.tasks [{id = id; label = event##.target##.value |> Js.to_string; completed = false}] in 449 | Printf.printf "New tasks updated\n"; 450 | {!state with tasks = new_tasks} 451 | ) 452 | | _ -> ()) 453 | 454 | end) 455 | []) 456 | |> create_class 457 | 458 | let root app = 459 | let open Reactjs in 460 | let open Infix in 461 | let tasks = List.filter (fun task -> 462 | Printf.printf "Checking if task matches: %d\n" task.id; 463 | match app.tab with 464 | | All -> true 465 | | Active -> not task.completed 466 | | Completed -> task.completed 467 | ) app.tasks in 468 | make_class_spec 469 | (fun ~this -> 470 | ignore(this); 471 | DOM.make ~tag:`section 472 | ~elem_spec:(object%js 473 | val className = !*"todoapp" 474 | end) 475 | [Elem (DOM.make ~tag:`header 476 | ~elem_spec:(object%js 477 | val className = !*"header" 478 | end) 479 | [Elem (DOM.make ~tag:`h1 [Text "todos"]); 480 | Elem (create_element_from_class (todo_input ()))]); 481 | Elem (DOM.make ~tag:`section 482 | ~elem_spec:(object%js 483 | val className = !*"main" 484 | end) [Elem (DOM.make ~tag:`input 485 | ~elem_spec:([("type", Js.string "checkbox"); 486 | ("className", Js.string "toggle-all")] 487 | >>> object%js end) []); 488 | Elem (DOM.make ~tag:`label 489 | ~elem_spec:([("htmlFor", Js.string "toggle-all");] 490 | >>> object%js end) 491 | [Text "Mark all as complete"]); 492 | Elem (DOM.make ~tag:`ul 493 | ~elem_spec:(object%js 494 | val className = !*"todo-list" 495 | end) 496 | (List.map (fun task -> Elem (create_element_from_class (todo_item task))) tasks))]); 497 | Elem (DOM.make ~tag:`footer 498 | ~elem_spec:(object%js 499 | val className = !*"footer" 500 | end) 501 | [Elem (DOM.make ~tag:`span ~elem_spec:(object%js 502 | val className = !*"todo-count" 503 | end) [Text (string_of_int (List.length tasks))]); 504 | Elem (DOM.make ~tag:`ul ~elem_spec:(object%js 505 | val className = !*"filters" 506 | end) 507 | [Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js 508 | val href = !*"#/" 509 | val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state All)) 510 | val className = (match app.tab with 511 | | All -> !*"selected" 512 | | _ -> !*"") 513 | end) [Text "All"])]); 514 | Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js 515 | val href = !*"#/active" 516 | val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state Active)) 517 | val className = (match app.tab with 518 | | Active -> !*"selected" 519 | | _ -> !*"") 520 | end) [Text "Active"])]); 521 | Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js 522 | val href = !*"#/completed" 523 | val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state Completed)) 524 | val className = (match app.tab with 525 | | Completed -> !*"selected" 526 | | _ -> !*"") 527 | end) [Text "Completed"])])]); 528 | Elem (DOM.make ~tag:`button ~elem_spec:(object%js 529 | val className = !*"clear-completed" 530 | val onClick = (fun _ -> swap initial_state (fun state -> 531 | let new_tasks = List.filter (fun t -> 532 | not t.completed 533 | ) !state.tasks in 534 | {!state with tasks = new_tasks} 535 | )) 536 | end) [Text "Clear completed"]) 537 | ]); 538 | ]) 539 | |> create_class 540 | 541 | let dump_state _ = 542 | List.iter (fun task -> print_endline (task.label ^ " [" ^ (string_of_bool task.completed) ^ "]")) !initial_state.tasks 543 | 544 | let () = 545 | Js.Unsafe.global##.dump_state := dump_state 546 | 547 | let () = 548 | Js.Unsafe.global##.get_state := (fun _ -> !initial_state) 549 | 550 | let first_render = 551 | ref true 552 | 553 | let wrap_root state_ref = 554 | let open Reactjs in 555 | let open Infix in 556 | make_class_spec 557 | (fun ~this -> 558 | ignore(this); 559 | DOM.make ~tag:`div 560 | ~elem_spec:(object%js 561 | val className = !*"overreact" 562 | end) 563 | [Elem (create_element_from_class (root !state_ref)); 564 | ]) 565 | |> create_class 566 | 567 | let render_root state = 568 | let open Reactjs in 569 | (match !state.tab with 570 | | All -> Printf.printf "Current tab: All\n" 571 | | Active -> Printf.printf "Current tab: Active\n" 572 | | Completed -> Printf.printf "Current tab: Completed\n"); 573 | (match !first_render with 574 | | _ -> let root_el_ = render 575 | ~react_elem:(create_element_from_class (wrap_root state)) 576 | (get_elem ~id:"container") in 577 | first_render := false; 578 | Js.Unsafe.global##.example := root_el_; 579 | (* | false -> Js.Unsafe.global##.example##forceUpdate *) 580 | ); 581 | Firebug.console##log Js.Unsafe.global##.example; 582 | () 583 | 584 | let replay_history = 585 | (fun _ -> 586 | let rec render_next_state = (fun states -> 587 | match (List.length states) with 588 | | 0 -> () 589 | | _ -> let next_state = List.hd states in 590 | render_root (ref next_state); 591 | ignore(Dom_html.window##setTimeout (Js.wrap_callback (fun () -> render_next_state (List.tl states); ())) 500.); 592 | ()) in 593 | render_next_state (List.rev !histories) 594 | ) 595 | 596 | let () = 597 | Js.Unsafe.global##.replayHistory := replay_history; 598 | let app_observer = (fun (_old_state : state) (_new_state : state) -> render_root initial_state) in 599 | observer := app_observer; 600 | swap initial_state (fun state -> state := {!state with tab = All}; !state); 601 | ``` 602 | -------------------------------------------------------------------------------- /_oasis: -------------------------------------------------------------------------------- 1 | # -*- conf -*- 2 | OASISFormat: 0.4 3 | Name: reactjs 4 | Version: 0.0.2 5 | Synopsis: js_of_ocaml bindings for Reactjs 6 | Authors: Edgar Aroutiounian 7 | Maintainers: Edgar Aroutiounian 8 | Homepage: https://github.com/fxfactorial/ocaml-reactjs 9 | License: BSD-3-clause 10 | Plugins: META (0.4), DevFiles (0.4) 11 | BuildTools: ocamlbuild, ocamldoc 12 | OCamlVersion: >= 4.02.0 13 | AlphaFeatures: ocamlbuild_more_args 14 | 15 | Description: OCaml bindings to ReactJS 16 | 17 | Library reactjs 18 | Path: src 19 | BuildTools:ocamlbuild 20 | ByteOpt: -g -w +a-4-40..42-44-45-48 21 | Modules : Reactjs 22 | install: true 23 | CompiledObject: byte 24 | BuildDepends: 25 | js_of_ocaml (>= 2.8.1), 26 | js_of_ocaml.ppx, 27 | lwt.ppx, 28 | lwt (>= 2.5.2), 29 | ppx_deriving.show, 30 | ppx_deriving.make, 31 | commonjs, 32 | commonjs_ppx 33 | 34 | Document api 35 | Title: Documentation and API reference for Usbmux 36 | Type: ocamlbuild (0.4) 37 | BuildTools+: ocamldoc 38 | InstallDir: $htmldir/reactjs 39 | PostCommand: cp doc/style.css api.docdir 40 | XOCamlbuildPath: doc 41 | XOCamlbuildModules: src/Reactjs 42 | Install: true 43 | XOCamlbuildExtraArgs: "-docflags '-colorize-code -charset utf-8 -hide Reactjs -hide Pervasives'" 44 | 45 | # Maybe a hero wil come along and write a jsx ppx. 46 | # Library ppx 47 | # FindlibName: ppx 48 | # FindlibParent: reactjs 49 | # Path: src/ppx 50 | # Modules: Ppx_reactjs 51 | # CompiledObject: byte 52 | # BuildDepends: 53 | # compiler-libs, 54 | # compiler-libs.common, 55 | # reactjs 56 | # XMETADescription: New-style (ppx) syntax extension 57 | # XMETARequires: reactjs.high_level 58 | # XMETAExtraLines: ppx = "ppx_reactjs" 59 | 60 | SourceRepository master 61 | Type: git 62 | Location: https://github.com/fxfactorial/ocaml-reactjs.git 63 | Browser: https://github.com/fxfactorial/ocaml-reactjs -------------------------------------------------------------------------------- /_tags: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: a33eabe81f6d7562e06864d629bb48ed) 3 | # Ignore VCS directories, you can use the same kind of rule outside 4 | # OASIS_START/STOP if you want to exclude directories that contains 5 | # useless stuff for the build process 6 | true: annot, bin_annot 7 | <**/.svn>: -traverse 8 | <**/.svn>: not_hygienic 9 | ".bzr": -traverse 10 | ".bzr": not_hygienic 11 | ".hg": -traverse 12 | ".hg": not_hygienic 13 | ".git": -traverse 14 | ".git": not_hygienic 15 | "_darcs": -traverse 16 | "_darcs": not_hygienic 17 | # Library reactjs 18 | "src/reactjs.cmxs": use_reactjs 19 | "src/reactjs.cma": oasis_library_reactjs_byte 20 | : oasis_library_reactjs_byte 21 | : pkg_commonjs_of_ocaml 22 | : pkg_js_of_ocaml 23 | : pkg_js_of_ocaml.ppx 24 | : pkg_lwt 25 | : pkg_lwt.ppx 26 | : pkg_ppx_deriving.make 27 | : pkg_ppx_deriving.show 28 | # OASIS_STOP 29 | "examples": not_hygienic 30 | "reactjs_based_examples": not_hygienic 31 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # OASIS_START 4 | # DO NOT EDIT (digest: dc86c2ad450f91ca10c931b6045d0499) 5 | set -e 6 | 7 | FST=true 8 | for i in "$@"; do 9 | if $FST; then 10 | set -- 11 | FST=false 12 | fi 13 | 14 | case $i in 15 | --*=*) 16 | ARG=${i%%=*} 17 | VAL=${i##*=} 18 | set -- "$@" "$ARG" "$VAL" 19 | ;; 20 | *) 21 | set -- "$@" "$i" 22 | ;; 23 | esac 24 | done 25 | 26 | ocaml setup.ml -configure "$@" 27 | # OASIS_STOP 28 | -------------------------------------------------------------------------------- /doc/style.css: -------------------------------------------------------------------------------- 1 | /* A style for ocamldoc. Daniel C. Buenzli */ 2 | 3 | /* Reset a few things. */ 4 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre, 5 | a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp, 6 | small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset, 7 | form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td 8 | { margin: 0; padding: 0; border: 0 none; outline: 0; font-size: 100%; 9 | font-weight: inherit; font-style:inherit; font-family:inherit; 10 | line-height: inherit; vertical-align: baseline; text-align:inherit; 11 | color:inherit; background: transparent; } 12 | 13 | table { border-collapse: collapse; border-spacing: 0; } 14 | 15 | /* Basic page layout */ 16 | 17 | body { font: normal 10pt/1.375em helvetica, arial, sans-serif; text-align:left; 18 | margin: 1.375em 10%; min-width: 40ex; max-width: 72ex; 19 | color: black; background: white /* url(line-height-22.gif) */; } 20 | 21 | b { font-weight: bold } 22 | em { font-style: italic } 23 | 24 | tt, code, pre { font-family: WorkAroundWebKitAndMozilla, monospace; 25 | font-size: 1em; } 26 | pre code { font-size : inherit; } 27 | .codepre { margin-bottom:1.375em /* after code example we introduce space. */ } 28 | 29 | pre { padding: 5px; } 30 | code.code, pre.codepre, pre.verbatim { background-color: #f7f7f7; border-radius: 3px; } 31 | code.code { font-size: 95%; padding: 0.1em 0.2em; } 32 | pre.codepre code.code { padding: 0; } 33 | 34 | 35 | .superscript,.subscript 36 | { font-size : 0.813em; line-height:0; margin-left:0.4ex;} 37 | .superscript { vertical-align: super; } 38 | .subscript { vertical-align: sub; } 39 | 40 | /* ocamldoc markup workaround hacks */ 41 | 42 | 43 | 44 | hr, hr + br, div + br, center + br, span + br, ul + br, ol + br, pre + br 45 | { display: none } /* annoying */ 46 | 47 | div.info + br { display:block} 48 | 49 | .codepre br + br { display: none } 50 | h1 + pre { margin-bottom:1.375em} /* Toplevel module description */ 51 | 52 | /* Sections and document divisions */ 53 | 54 | /* .navbar { margin-bottom: -1.375em } */ 55 | h1 { font-weight: bold; font-size: 1.5em; /* margin-top:1.833em; */ 56 | margin-top:0.917em; padding-top:0.875em; 57 | border-top-style:solid; border-width:1px; border-color:#AAA; } 58 | h2 { font-weight: bold; font-size: 1.313em; margin-top: 1.048em } 59 | h3 { font-weight: bold; font-size: 1.125em; margin-top: 1.222em } 60 | h3 { font-weight: bold; font-size: 1em; margin-top: 1.375em} 61 | h4 { font-style: italic; } 62 | 63 | /* Used by OCaml's own library documentation. */ 64 | h6 { font-weight: bold; font-size: 1.125em; margin-top: 1.222em } 65 | .h7 { font-weight: bold; font-size: 1em; margin-top: 1.375em } 66 | 67 | p { margin-top: 1.375em } 68 | pre { margin-top: 1.375em } 69 | .info { margin: 0.458em 0em -0.458em 2em;}/* Description of types values etc. */ 70 | td .info { margin:0; padding:0; margin-left: 2em;} /* Description in indexes */ 71 | 72 | ul, ol { margin-top:0.688em; padding-bottom:0.687em; 73 | list-style-position:outside} 74 | ul + p, ol + p { margin-top: 0em } 75 | ul { list-style-type: square } 76 | 77 | 78 | /* h2 + ul, h3 + ul, p + ul { } */ 79 | ul > li { margin-left: 1.375em; } 80 | ol > li { margin-left: 1.7em; } 81 | /* Links */ 82 | 83 | a, a:* { text-decoration: underline } 84 | *:target {background-color: #FFFF99;} /* anchor highlight */ 85 | 86 | /* Code */ 87 | 88 | .keyword { font-weight: bold; } 89 | .comment { color : red } 90 | .constructor { color : green } 91 | .string { color : #957; } 92 | .warning { color : red ; font-weight : bold } 93 | 94 | /* Functors */ 95 | 96 | .paramstable { border-style : hidden ; padding-bottom:1.375em} 97 | .paramstable code { margin-left: 1ex; margin-right: 1ex } 98 | .sig_block {margin-left: 1em} 99 | 100 | /* Images */ 101 | 102 | img { margin-top: 1.375em; display:block } 103 | li img { margin-top: 0em; } 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /myocamlbuild.ml: -------------------------------------------------------------------------------- 1 | (* OASIS_START *) 2 | (* DO NOT EDIT (digest: b3ea4925e4ab39ed08b3e9833f452a52) *) 3 | module OASISGettext = struct 4 | (* # 22 "src/oasis/OASISGettext.ml" *) 5 | 6 | 7 | let ns_ str = 8 | str 9 | 10 | 11 | let s_ str = 12 | str 13 | 14 | 15 | let f_ (str: ('a, 'b, 'c, 'd) format4) = 16 | str 17 | 18 | 19 | let fn_ fmt1 fmt2 n = 20 | if n = 1 then 21 | fmt1^^"" 22 | else 23 | fmt2^^"" 24 | 25 | 26 | let init = 27 | [] 28 | 29 | 30 | end 31 | 32 | module OASISString = struct 33 | (* # 22 "src/oasis/OASISString.ml" *) 34 | 35 | 36 | (** Various string utilities. 37 | 38 | Mostly inspired by extlib and batteries ExtString and BatString libraries. 39 | 40 | @author Sylvain Le Gall 41 | *) 42 | 43 | 44 | let nsplitf str f = 45 | if str = "" then 46 | [] 47 | else 48 | let buf = Buffer.create 13 in 49 | let lst = ref [] in 50 | let push () = 51 | lst := Buffer.contents buf :: !lst; 52 | Buffer.clear buf 53 | in 54 | let str_len = String.length str in 55 | for i = 0 to str_len - 1 do 56 | if f str.[i] then 57 | push () 58 | else 59 | Buffer.add_char buf str.[i] 60 | done; 61 | push (); 62 | List.rev !lst 63 | 64 | 65 | (** [nsplit c s] Split the string [s] at char [c]. It doesn't include the 66 | separator. 67 | *) 68 | let nsplit str c = 69 | nsplitf str ((=) c) 70 | 71 | 72 | let find ~what ?(offset=0) str = 73 | let what_idx = ref 0 in 74 | let str_idx = ref offset in 75 | while !str_idx < String.length str && 76 | !what_idx < String.length what do 77 | if str.[!str_idx] = what.[!what_idx] then 78 | incr what_idx 79 | else 80 | what_idx := 0; 81 | incr str_idx 82 | done; 83 | if !what_idx <> String.length what then 84 | raise Not_found 85 | else 86 | !str_idx - !what_idx 87 | 88 | 89 | let sub_start str len = 90 | let str_len = String.length str in 91 | if len >= str_len then 92 | "" 93 | else 94 | String.sub str len (str_len - len) 95 | 96 | 97 | let sub_end ?(offset=0) str len = 98 | let str_len = String.length str in 99 | if len >= str_len then 100 | "" 101 | else 102 | String.sub str 0 (str_len - len) 103 | 104 | 105 | let starts_with ~what ?(offset=0) str = 106 | let what_idx = ref 0 in 107 | let str_idx = ref offset in 108 | let ok = ref true in 109 | while !ok && 110 | !str_idx < String.length str && 111 | !what_idx < String.length what do 112 | if str.[!str_idx] = what.[!what_idx] then 113 | incr what_idx 114 | else 115 | ok := false; 116 | incr str_idx 117 | done; 118 | if !what_idx = String.length what then 119 | true 120 | else 121 | false 122 | 123 | 124 | let strip_starts_with ~what str = 125 | if starts_with ~what str then 126 | sub_start str (String.length what) 127 | else 128 | raise Not_found 129 | 130 | 131 | let ends_with ~what ?(offset=0) str = 132 | let what_idx = ref ((String.length what) - 1) in 133 | let str_idx = ref ((String.length str) - 1) in 134 | let ok = ref true in 135 | while !ok && 136 | offset <= !str_idx && 137 | 0 <= !what_idx do 138 | if str.[!str_idx] = what.[!what_idx] then 139 | decr what_idx 140 | else 141 | ok := false; 142 | decr str_idx 143 | done; 144 | if !what_idx = -1 then 145 | true 146 | else 147 | false 148 | 149 | 150 | let strip_ends_with ~what str = 151 | if ends_with ~what str then 152 | sub_end str (String.length what) 153 | else 154 | raise Not_found 155 | 156 | 157 | let replace_chars f s = 158 | let buf = Buffer.create (String.length s) in 159 | String.iter (fun c -> Buffer.add_char buf (f c)) s; 160 | Buffer.contents buf 161 | 162 | let lowercase_ascii = 163 | replace_chars 164 | (fun c -> 165 | if (c >= 'A' && c <= 'Z') then 166 | Char.chr (Char.code c + 32) 167 | else 168 | c) 169 | 170 | let uncapitalize_ascii s = 171 | if s <> "" then 172 | (lowercase_ascii (String.sub s 0 1)) ^ (String.sub s 1 ((String.length s) - 1)) 173 | else 174 | s 175 | 176 | let uppercase_ascii = 177 | replace_chars 178 | (fun c -> 179 | if (c >= 'a' && c <= 'z') then 180 | Char.chr (Char.code c - 32) 181 | else 182 | c) 183 | 184 | let capitalize_ascii s = 185 | if s <> "" then 186 | (uppercase_ascii (String.sub s 0 1)) ^ (String.sub s 1 ((String.length s) - 1)) 187 | else 188 | s 189 | 190 | end 191 | 192 | module OASISExpr = struct 193 | (* # 22 "src/oasis/OASISExpr.ml" *) 194 | 195 | 196 | 197 | 198 | 199 | open OASISGettext 200 | 201 | 202 | type test = string 203 | 204 | 205 | type flag = string 206 | 207 | 208 | type t = 209 | | EBool of bool 210 | | ENot of t 211 | | EAnd of t * t 212 | | EOr of t * t 213 | | EFlag of flag 214 | | ETest of test * string 215 | 216 | 217 | 218 | type 'a choices = (t * 'a) list 219 | 220 | 221 | let eval var_get t = 222 | let rec eval' = 223 | function 224 | | EBool b -> 225 | b 226 | 227 | | ENot e -> 228 | not (eval' e) 229 | 230 | | EAnd (e1, e2) -> 231 | (eval' e1) && (eval' e2) 232 | 233 | | EOr (e1, e2) -> 234 | (eval' e1) || (eval' e2) 235 | 236 | | EFlag nm -> 237 | let v = 238 | var_get nm 239 | in 240 | assert(v = "true" || v = "false"); 241 | (v = "true") 242 | 243 | | ETest (nm, vl) -> 244 | let v = 245 | var_get nm 246 | in 247 | (v = vl) 248 | in 249 | eval' t 250 | 251 | 252 | let choose ?printer ?name var_get lst = 253 | let rec choose_aux = 254 | function 255 | | (cond, vl) :: tl -> 256 | if eval var_get cond then 257 | vl 258 | else 259 | choose_aux tl 260 | | [] -> 261 | let str_lst = 262 | if lst = [] then 263 | s_ "" 264 | else 265 | String.concat 266 | (s_ ", ") 267 | (List.map 268 | (fun (cond, vl) -> 269 | match printer with 270 | | Some p -> p vl 271 | | None -> s_ "") 272 | lst) 273 | in 274 | match name with 275 | | Some nm -> 276 | failwith 277 | (Printf.sprintf 278 | (f_ "No result for the choice list '%s': %s") 279 | nm str_lst) 280 | | None -> 281 | failwith 282 | (Printf.sprintf 283 | (f_ "No result for a choice list: %s") 284 | str_lst) 285 | in 286 | choose_aux (List.rev lst) 287 | 288 | 289 | end 290 | 291 | 292 | # 292 "myocamlbuild.ml" 293 | module BaseEnvLight = struct 294 | (* # 22 "src/base/BaseEnvLight.ml" *) 295 | 296 | 297 | module MapString = Map.Make(String) 298 | 299 | 300 | type t = string MapString.t 301 | 302 | 303 | let default_filename = 304 | Filename.concat 305 | (Sys.getcwd ()) 306 | "setup.data" 307 | 308 | 309 | let load ?(allow_empty=false) ?(filename=default_filename) () = 310 | if Sys.file_exists filename then 311 | begin 312 | let chn = 313 | open_in_bin filename 314 | in 315 | let st = 316 | Stream.of_channel chn 317 | in 318 | let line = 319 | ref 1 320 | in 321 | let st_line = 322 | Stream.from 323 | (fun _ -> 324 | try 325 | match Stream.next st with 326 | | '\n' -> incr line; Some '\n' 327 | | c -> Some c 328 | with Stream.Failure -> None) 329 | in 330 | let lexer = 331 | Genlex.make_lexer ["="] st_line 332 | in 333 | let rec read_file mp = 334 | match Stream.npeek 3 lexer with 335 | | [Genlex.Ident nm; Genlex.Kwd "="; Genlex.String value] -> 336 | Stream.junk lexer; 337 | Stream.junk lexer; 338 | Stream.junk lexer; 339 | read_file (MapString.add nm value mp) 340 | | [] -> 341 | mp 342 | | _ -> 343 | failwith 344 | (Printf.sprintf 345 | "Malformed data file '%s' line %d" 346 | filename !line) 347 | in 348 | let mp = 349 | read_file MapString.empty 350 | in 351 | close_in chn; 352 | mp 353 | end 354 | else if allow_empty then 355 | begin 356 | MapString.empty 357 | end 358 | else 359 | begin 360 | failwith 361 | (Printf.sprintf 362 | "Unable to load environment, the file '%s' doesn't exist." 363 | filename) 364 | end 365 | 366 | 367 | let rec var_expand str env = 368 | let buff = 369 | Buffer.create ((String.length str) * 2) 370 | in 371 | Buffer.add_substitute 372 | buff 373 | (fun var -> 374 | try 375 | var_expand (MapString.find var env) env 376 | with Not_found -> 377 | failwith 378 | (Printf.sprintf 379 | "No variable %s defined when trying to expand %S." 380 | var 381 | str)) 382 | str; 383 | Buffer.contents buff 384 | 385 | 386 | let var_get name env = 387 | var_expand (MapString.find name env) env 388 | 389 | 390 | let var_choose lst env = 391 | OASISExpr.choose 392 | (fun nm -> var_get nm env) 393 | lst 394 | end 395 | 396 | 397 | # 397 "myocamlbuild.ml" 398 | module MyOCamlbuildFindlib = struct 399 | (* # 22 "src/plugins/ocamlbuild/MyOCamlbuildFindlib.ml" *) 400 | 401 | 402 | (** OCamlbuild extension, copied from 403 | * http://brion.inria.fr/gallium/index.php/Using_ocamlfind_with_ocamlbuild 404 | * by N. Pouillard and others 405 | * 406 | * Updated on 2009/02/28 407 | * 408 | * Modified by Sylvain Le Gall 409 | *) 410 | open Ocamlbuild_plugin 411 | 412 | type conf = 413 | { no_automatic_syntax: bool; 414 | } 415 | 416 | (* these functions are not really officially exported *) 417 | let run_and_read = 418 | Ocamlbuild_pack.My_unix.run_and_read 419 | 420 | 421 | let blank_sep_strings = 422 | Ocamlbuild_pack.Lexers.blank_sep_strings 423 | 424 | 425 | let exec_from_conf exec = 426 | let exec = 427 | let env_filename = Pathname.basename BaseEnvLight.default_filename in 428 | let env = BaseEnvLight.load ~filename:env_filename ~allow_empty:true () in 429 | try 430 | BaseEnvLight.var_get exec env 431 | with Not_found -> 432 | Printf.eprintf "W: Cannot get variable %s\n" exec; 433 | exec 434 | in 435 | let fix_win32 str = 436 | if Sys.os_type = "Win32" then begin 437 | let buff = Buffer.create (String.length str) in 438 | (* Adapt for windowsi, ocamlbuild + win32 has a hard time to handle '\\'. 439 | *) 440 | String.iter 441 | (fun c -> Buffer.add_char buff (if c = '\\' then '/' else c)) 442 | str; 443 | Buffer.contents buff 444 | end else begin 445 | str 446 | end 447 | in 448 | fix_win32 exec 449 | 450 | let split s ch = 451 | let buf = Buffer.create 13 in 452 | let x = ref [] in 453 | let flush () = 454 | x := (Buffer.contents buf) :: !x; 455 | Buffer.clear buf 456 | in 457 | String.iter 458 | (fun c -> 459 | if c = ch then 460 | flush () 461 | else 462 | Buffer.add_char buf c) 463 | s; 464 | flush (); 465 | List.rev !x 466 | 467 | 468 | let split_nl s = split s '\n' 469 | 470 | 471 | let before_space s = 472 | try 473 | String.before s (String.index s ' ') 474 | with Not_found -> s 475 | 476 | (* ocamlfind command *) 477 | let ocamlfind x = S[Sh (exec_from_conf "ocamlfind"); x] 478 | 479 | (* This lists all supported packages. *) 480 | let find_packages () = 481 | List.map before_space (split_nl & run_and_read (exec_from_conf "ocamlfind" ^ " list")) 482 | 483 | 484 | (* Mock to list available syntaxes. *) 485 | let find_syntaxes () = ["camlp4o"; "camlp4r"] 486 | 487 | 488 | let well_known_syntax = [ 489 | "camlp4.quotations.o"; 490 | "camlp4.quotations.r"; 491 | "camlp4.exceptiontracer"; 492 | "camlp4.extend"; 493 | "camlp4.foldgenerator"; 494 | "camlp4.listcomprehension"; 495 | "camlp4.locationstripper"; 496 | "camlp4.macro"; 497 | "camlp4.mapgenerator"; 498 | "camlp4.metagenerator"; 499 | "camlp4.profiler"; 500 | "camlp4.tracer" 501 | ] 502 | 503 | 504 | let dispatch conf = 505 | function 506 | | After_options -> 507 | (* By using Before_options one let command line options have an higher 508 | * priority on the contrary using After_options will guarantee to have 509 | * the higher priority override default commands by ocamlfind ones *) 510 | Options.ocamlc := ocamlfind & A"ocamlc"; 511 | Options.ocamlopt := ocamlfind & A"ocamlopt"; 512 | Options.ocamldep := ocamlfind & A"ocamldep"; 513 | Options.ocamldoc := ocamlfind & A"ocamldoc"; 514 | Options.ocamlmktop := ocamlfind & A"ocamlmktop"; 515 | Options.ocamlmklib := ocamlfind & A"ocamlmklib" 516 | 517 | | After_rules -> 518 | 519 | (* When one link an OCaml library/binary/package, one should use 520 | * -linkpkg *) 521 | flag ["ocaml"; "link"; "program"] & A"-linkpkg"; 522 | 523 | if not (conf.no_automatic_syntax) then begin 524 | (* For each ocamlfind package one inject the -package option when 525 | * compiling, computing dependencies, generating documentation and 526 | * linking. *) 527 | List.iter 528 | begin fun pkg -> 529 | let base_args = [A"-package"; A pkg] in 530 | (* TODO: consider how to really choose camlp4o or camlp4r. *) 531 | let syn_args = [A"-syntax"; A "camlp4o"] in 532 | let (args, pargs) = 533 | (* Heuristic to identify syntax extensions: whether they end in 534 | ".syntax"; some might not. 535 | *) 536 | if Filename.check_suffix pkg "syntax" || 537 | List.mem pkg well_known_syntax then 538 | (syn_args @ base_args, syn_args) 539 | else 540 | (base_args, []) 541 | in 542 | flag ["ocaml"; "compile"; "pkg_"^pkg] & S args; 543 | flag ["ocaml"; "ocamldep"; "pkg_"^pkg] & S args; 544 | flag ["ocaml"; "doc"; "pkg_"^pkg] & S args; 545 | flag ["ocaml"; "link"; "pkg_"^pkg] & S base_args; 546 | flag ["ocaml"; "infer_interface"; "pkg_"^pkg] & S args; 547 | 548 | (* TODO: Check if this is allowed for OCaml < 3.12.1 *) 549 | flag ["ocaml"; "compile"; "package("^pkg^")"] & S pargs; 550 | flag ["ocaml"; "ocamldep"; "package("^pkg^")"] & S pargs; 551 | flag ["ocaml"; "doc"; "package("^pkg^")"] & S pargs; 552 | flag ["ocaml"; "infer_interface"; "package("^pkg^")"] & S pargs; 553 | end 554 | (find_packages ()); 555 | end; 556 | 557 | (* Like -package but for extensions syntax. Morover -syntax is useless 558 | * when linking. *) 559 | List.iter begin fun syntax -> 560 | flag ["ocaml"; "compile"; "syntax_"^syntax] & S[A"-syntax"; A syntax]; 561 | flag ["ocaml"; "ocamldep"; "syntax_"^syntax] & S[A"-syntax"; A syntax]; 562 | flag ["ocaml"; "doc"; "syntax_"^syntax] & S[A"-syntax"; A syntax]; 563 | flag ["ocaml"; "infer_interface"; "syntax_"^syntax] & 564 | S[A"-syntax"; A syntax]; 565 | end (find_syntaxes ()); 566 | 567 | (* The default "thread" tag is not compatible with ocamlfind. 568 | * Indeed, the default rules add the "threads.cma" or "threads.cmxa" 569 | * options when using this tag. When using the "-linkpkg" option with 570 | * ocamlfind, this module will then be added twice on the command line. 571 | * 572 | * To solve this, one approach is to add the "-thread" option when using 573 | * the "threads" package using the previous plugin. 574 | *) 575 | flag ["ocaml"; "pkg_threads"; "compile"] (S[A "-thread"]); 576 | flag ["ocaml"; "pkg_threads"; "doc"] (S[A "-I"; A "+threads"]); 577 | flag ["ocaml"; "pkg_threads"; "link"] (S[A "-thread"]); 578 | flag ["ocaml"; "pkg_threads"; "infer_interface"] (S[A "-thread"]); 579 | flag ["ocaml"; "package(threads)"; "compile"] (S[A "-thread"]); 580 | flag ["ocaml"; "package(threads)"; "doc"] (S[A "-I"; A "+threads"]); 581 | flag ["ocaml"; "package(threads)"; "link"] (S[A "-thread"]); 582 | flag ["ocaml"; "package(threads)"; "infer_interface"] (S[A "-thread"]); 583 | 584 | | _ -> 585 | () 586 | end 587 | 588 | module MyOCamlbuildBase = struct 589 | (* # 22 "src/plugins/ocamlbuild/MyOCamlbuildBase.ml" *) 590 | 591 | 592 | (** Base functions for writing myocamlbuild.ml 593 | @author Sylvain Le Gall 594 | *) 595 | 596 | 597 | 598 | 599 | 600 | open Ocamlbuild_plugin 601 | module OC = Ocamlbuild_pack.Ocaml_compiler 602 | 603 | 604 | type dir = string 605 | type file = string 606 | type name = string 607 | type tag = string 608 | 609 | 610 | (* # 62 "src/plugins/ocamlbuild/MyOCamlbuildBase.ml" *) 611 | 612 | 613 | type t = 614 | { 615 | lib_ocaml: (name * dir list * string list) list; 616 | lib_c: (name * dir * file list) list; 617 | flags: (tag list * (spec OASISExpr.choices)) list; 618 | (* Replace the 'dir: include' from _tags by a precise interdepends in 619 | * directory. 620 | *) 621 | includes: (dir * dir list) list; 622 | } 623 | 624 | 625 | let env_filename = 626 | Pathname.basename 627 | BaseEnvLight.default_filename 628 | 629 | 630 | let dispatch_combine lst = 631 | fun e -> 632 | List.iter 633 | (fun dispatch -> dispatch e) 634 | lst 635 | 636 | 637 | let tag_libstubs nm = 638 | "use_lib"^nm^"_stubs" 639 | 640 | 641 | let nm_libstubs nm = 642 | nm^"_stubs" 643 | 644 | 645 | let dispatch t e = 646 | let env = 647 | BaseEnvLight.load 648 | ~filename:env_filename 649 | ~allow_empty:true 650 | () 651 | in 652 | match e with 653 | | Before_options -> 654 | let no_trailing_dot s = 655 | if String.length s >= 1 && s.[0] = '.' then 656 | String.sub s 1 ((String.length s) - 1) 657 | else 658 | s 659 | in 660 | List.iter 661 | (fun (opt, var) -> 662 | try 663 | opt := no_trailing_dot (BaseEnvLight.var_get var env) 664 | with Not_found -> 665 | Printf.eprintf "W: Cannot get variable %s\n" var) 666 | [ 667 | Options.ext_obj, "ext_obj"; 668 | Options.ext_lib, "ext_lib"; 669 | Options.ext_dll, "ext_dll"; 670 | ] 671 | 672 | | After_rules -> 673 | (* Declare OCaml libraries *) 674 | List.iter 675 | (function 676 | | nm, [], intf_modules -> 677 | ocaml_lib nm; 678 | let cmis = 679 | List.map (fun m -> (OASISString.uncapitalize_ascii m) ^ ".cmi") 680 | intf_modules in 681 | dep ["ocaml"; "link"; "library"; "file:"^nm^".cma"] cmis 682 | | nm, dir :: tl, intf_modules -> 683 | ocaml_lib ~dir:dir (dir^"/"^nm); 684 | List.iter 685 | (fun dir -> 686 | List.iter 687 | (fun str -> 688 | flag ["ocaml"; "use_"^nm; str] (S[A"-I"; P dir])) 689 | ["compile"; "infer_interface"; "doc"]) 690 | tl; 691 | let cmis = 692 | List.map (fun m -> dir^"/"^(OASISString.uncapitalize_ascii m)^".cmi") 693 | intf_modules in 694 | dep ["ocaml"; "link"; "library"; "file:"^dir^"/"^nm^".cma"] 695 | cmis) 696 | t.lib_ocaml; 697 | 698 | (* Declare directories dependencies, replace "include" in _tags. *) 699 | List.iter 700 | (fun (dir, include_dirs) -> 701 | Pathname.define_context dir include_dirs) 702 | t.includes; 703 | 704 | (* Declare C libraries *) 705 | List.iter 706 | (fun (lib, dir, headers) -> 707 | (* Handle C part of library *) 708 | flag ["link"; "library"; "ocaml"; "byte"; tag_libstubs lib] 709 | (S[A"-dllib"; A("-l"^(nm_libstubs lib)); A"-cclib"; 710 | A("-l"^(nm_libstubs lib))]); 711 | 712 | flag ["link"; "library"; "ocaml"; "native"; tag_libstubs lib] 713 | (S[A"-cclib"; A("-l"^(nm_libstubs lib))]); 714 | 715 | flag ["link"; "program"; "ocaml"; "byte"; tag_libstubs lib] 716 | (S[A"-dllib"; A("dll"^(nm_libstubs lib))]); 717 | 718 | (* When ocaml link something that use the C library, then one 719 | need that file to be up to date. 720 | This holds both for programs and for libraries. 721 | *) 722 | dep ["link"; "ocaml"; tag_libstubs lib] 723 | [dir/"lib"^(nm_libstubs lib)^"."^(!Options.ext_lib)]; 724 | 725 | dep ["compile"; "ocaml"; tag_libstubs lib] 726 | [dir/"lib"^(nm_libstubs lib)^"."^(!Options.ext_lib)]; 727 | 728 | (* TODO: be more specific about what depends on headers *) 729 | (* Depends on .h files *) 730 | dep ["compile"; "c"] 731 | headers; 732 | 733 | (* Setup search path for lib *) 734 | flag ["link"; "ocaml"; "use_"^lib] 735 | (S[A"-I"; P(dir)]); 736 | ) 737 | t.lib_c; 738 | 739 | (* Add flags *) 740 | List.iter 741 | (fun (tags, cond_specs) -> 742 | let spec = BaseEnvLight.var_choose cond_specs env in 743 | let rec eval_specs = 744 | function 745 | | S lst -> S (List.map eval_specs lst) 746 | | A str -> A (BaseEnvLight.var_expand str env) 747 | | spec -> spec 748 | in 749 | flag tags & (eval_specs spec)) 750 | t.flags 751 | | _ -> 752 | () 753 | 754 | 755 | let dispatch_default conf t = 756 | dispatch_combine 757 | [ 758 | dispatch t; 759 | MyOCamlbuildFindlib.dispatch conf; 760 | ] 761 | 762 | 763 | end 764 | 765 | 766 | # 766 "myocamlbuild.ml" 767 | open Ocamlbuild_plugin;; 768 | let package_default = 769 | { 770 | MyOCamlbuildBase.lib_ocaml = [("reactjs", ["src"], [])]; 771 | lib_c = []; 772 | flags = 773 | [ 774 | (["oasis_library_reactjs_byte"; "ocaml"; "link"; "byte"], 775 | [ 776 | (OASISExpr.EBool true, 777 | S [A "-g"; A "-w"; A "+a-4-40..42-44-45-48"]) 778 | ]); 779 | (["oasis_library_reactjs_byte"; "ocaml"; "ocamldep"; "byte"], 780 | [ 781 | (OASISExpr.EBool true, 782 | S [A "-g"; A "-w"; A "+a-4-40..42-44-45-48"]) 783 | ]); 784 | (["oasis_library_reactjs_byte"; "ocaml"; "compile"; "byte"], 785 | [ 786 | (OASISExpr.EBool true, 787 | S [A "-g"; A "-w"; A "+a-4-40..42-44-45-48"]) 788 | ]) 789 | ]; 790 | includes = [] 791 | } 792 | ;; 793 | 794 | let conf = {MyOCamlbuildFindlib.no_automatic_syntax = false} 795 | 796 | let dispatch_default = MyOCamlbuildBase.dispatch_default conf package_default;; 797 | 798 | # 799 "myocamlbuild.ml" 799 | (* OASIS_STOP *) 800 | Ocamlbuild_plugin.dispatch dispatch_default;; 801 | -------------------------------------------------------------------------------- /opam/descr: -------------------------------------------------------------------------------- 1 | OCaml bindings to ReactJS 2 | 3 | These are OCaml bindings to ReactJS. This means that you write OCaml 4 | and compile to JavaScript using the js_of_ocaml compiler. 5 | 6 | with just: 7 | 8 | $ ocamlfind ocamlc -package reactjs -linkpkg code.ml 9 | $ js_of_ocaml a.out -o from_ocaml.js 10 | 11 | See the github homepage for working code examples and check the 12 | wiki for common questions. 13 | 14 | Also check https://github.com/fxfactorial/ocaml-mailing-list for 15 | a self contained and working example of rendering ReactJS on node 16 | by also using OCaml bindings to nodejs. 17 | -------------------------------------------------------------------------------- /opam/findlib: -------------------------------------------------------------------------------- 1 | reactjs 2 | -------------------------------------------------------------------------------- /opam/opam: -------------------------------------------------------------------------------- 1 | # -*- conf -*- 2 | opam-version: "1.2" 3 | name: "reactjs" 4 | version: "0.0.2" 5 | maintainer: "Edgar Aroutiounian " 6 | authors: [ "Edgar Aroutiounian " ] 7 | license: "BSD-3-clause" 8 | homepage: "https://github.com/fxfactorial/ocaml-reactjs" 9 | dev-repo: "https://github.com/fxfactorial/ocaml-reactjs.git" 10 | bug-reports: "https://github.com/fxfactorial/ocaml-reactjs/issues" 11 | build: [ 12 | ["oasis" "setup"] 13 | ["ocaml" "setup.ml" "-configure" "--prefix" prefix] 14 | ["ocaml" "setup.ml" "-build"] 15 | ] 16 | install: ["ocaml" "setup.ml" "-install"] 17 | available: [ ocaml-version >= "4.02.0"] 18 | build-doc: [ "ocaml" "setup.ml" "-doc" ] 19 | remove: [ 20 | ["ocamlfind" "remove" "reactjs"] 21 | ] 22 | build-test: [ 23 | ["oasis" "setup"] 24 | ["ocaml" "setup.ml" "-configure" "--enable-tests"] 25 | ["ocaml" "setup.ml" "-build"] 26 | ["ocaml" "setup.ml" "-test"] 27 | ] 28 | depends: [ 29 | "js_of_ocaml" {>= "2.8.1"} 30 | "lwt" {>= "2.5.2"} 31 | "commonjs_of_ocaml" {>= "0.1.0"} 32 | "ppx_deriving" {>= "4.0"} 33 | "oasis" {build & >= "0.4"} 34 | "ocamlbuild" {build} 35 | "ocamlfind" {build} 36 | ] 37 | messages:[ 38 | "typesafe, mostly, ReactJS in OCaml!" 39 | ] 40 | post-messages:[ 41 | "Now you can write ReactJS in OCaml" 42 | "and compile to JavaScript runnable on either " 43 | "nodejs or the browser with just" 44 | "" 45 | "$ ocamlfind ocamlc -package reactjs -linkpkg code.ml" 46 | "$ js_of_ocaml a.out -o from_ocaml.js" 47 | "" 48 | "Check github for several working examples and wiki for FAQs" 49 | "Also check https://github.com/fxfactorial/ocaml-mailing-list for " 50 | "a self contained and working example of rendering ReactJS " 51 | "on node by also using OCaml bindings to nodejs" 52 | ] -------------------------------------------------------------------------------- /reactjs_based_examples/Makefile: -------------------------------------------------------------------------------- 1 | pkgs := reactjs 2 | # js_debug := --debug-info --no-inline \ 3 | # --pretty --linkall --source-map-inline --source-map \ 4 | # --disable=optcall --disable=share --disable=shortvar \ 5 | # --disable=deadcode --disable=genprim 6 | 7 | js_debug := \ 8 | --enable=debuginfo --disable=inline --enable=pretty \ 9 | #--source-map-inline --source-map \ 10 | --disable=shortvar 11 | 12 | code:example.ml 13 | ocamlfind ocamlc -g -package ${pkgs} -linkpkg $< -o $@ 14 | js_of_ocaml ${js_debug} $@ -o $@.js 15 | 16 | clean:;@rm -f *.cmi *.cmo *.cmt a.out *.js code 17 | 18 | all_examples:; -@bash test_all.sh 19 | 20 | .PHONY:clean code all 21 | -------------------------------------------------------------------------------- /reactjs_based_examples/basic-click-counter/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /reactjs_based_examples/basic-click-counter/example.ml: -------------------------------------------------------------------------------- 1 | let counter = Reactjs.( 2 | make_class_spec 3 | ~initial_state:(fun ~this -> (object%js val count = 0 end)) 4 | ~component_will_mount:(fun ~this -> 5 | print_endline "Component about to mount" 6 | ) 7 | (fun ~this -> let open Reactjs.Infix in 8 | let handle_click = !@(fun () -> 9 | this##setState (object%js val count = this##.state##.count + 1 end)) 10 | in 11 | DOM.make 12 | ~elem_spec:(object%js 13 | val onClick = handle_click 14 | end) 15 | ~tag:`button 16 | [Text (Printf.sprintf 17 | "Click me, number of clicks: %d" this##.state##.count)]) 18 | |> create_class 19 | ) 20 | 21 | let () = Reactjs.( 22 | render 23 | ~react_elem:(create_element_from_class counter) 24 | (get_elem ~id:"container") 25 | ) 26 | -------------------------------------------------------------------------------- /reactjs_based_examples/basic-click-counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Basic Click Counter Example 6 | 7 | 8 | 9 |

Basic Click Counter Example

10 |
11 |

12 | To install React, follow the instructions on 13 | GitHub. 14 |

15 |

16 | If you can see this, React is not working right. 17 |

18 |
19 |

Example Details

20 |

This example uses events and state to track clicks and update the rendered output.

21 |

22 | Learn more about React at 23 | facebook.github.io/react. 24 |

25 | 26 | 27 | 28 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /reactjs_based_examples/basic/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /reactjs_based_examples/basic/example.ml: -------------------------------------------------------------------------------- 1 | let example_application = Reactjs.( 2 | make_class_spec 3 | ~initial_state:(fun ~this -> 4 | print_endline "Initial state called"; 5 | object%js end 6 | ) 7 | ~default_props:(fun ~this -> 8 | print_endline "Default props called"; 9 | object%js end 10 | ) 11 | ~component_will_mount:(fun ~this -> print_endline "Component will mount") 12 | ~component_did_mount:(fun ~this -> print_endline "Component did mount") 13 | ~component_will_receive_props:(fun ~this ~next_prop -> 14 | print_endline "Component will receive props" 15 | ) 16 | ~should_component_update:(fun ~this ~next_prop ~next_state -> 17 | print_endline "Should component update called"; 18 | Js.bool true 19 | ) 20 | ~component_will_update:(fun ~this ~next_prop ~next_state -> 21 | print_endline "Component will update" 22 | ) 23 | ~component_did_update:(fun ~this ~prev_prop ~prev_state -> 24 | print_endline "Component did update" 25 | ) 26 | ~component_will_unmount:(fun ~this -> print_endline "Component about to unmount") 27 | (fun ~this -> 28 | let elapsed = Js.math##round this##.props##.elapsed /. 100.0 in 29 | let seconds = elapsed /. 10.0 in 30 | let message = Printf.sprintf 31 | "React has been successfully running for %f seconds" seconds 32 | in 33 | DOM.make ~tag:`p [Text message] 34 | ) 35 | |> create_class 36 | ) 37 | 38 | let _ = Reactjs.( 39 | let example_app_factory = create_factory example_application in 40 | let start = (new%js Js.date_now)##getTime in 41 | set_interval 42 | ~f:(fun () -> 43 | try 44 | let react_elem = example_app_factory ~props:(object%js 45 | val elapsed = (new%js Js.date_now)##getTime -. start 46 | end) 47 | in 48 | render ~react_elem (get_elem ~id:"container") 49 | (* Get OCaml exception handling! *) 50 | with Js.Error e -> 51 | Firebug.console##log e 52 | ) ~every:100.0 53 | ) 54 | -------------------------------------------------------------------------------- /reactjs_based_examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Basic Example 6 | 7 | 8 | 9 |

Basic Example

10 |
11 |

12 | To install React, follow the instructions on 13 | GitHub. 14 |

15 |

16 | If you can see this, React is not working right. 17 |

18 |
19 |

Example Details

20 |

21 | This is written in vanilla JavaScript (without JSX) 22 | and transformed in the browser. 23 |

24 |

25 | Learn more about React at 26 | facebook.github.io/react. 28 |

29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /reactjs_based_examples/quadratic/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /reactjs_based_examples/quadratic/example.ml: -------------------------------------------------------------------------------- 1 | open StdLabels 2 | 3 | let quadratic_calculator = Reactjs.( 4 | make_class_spec 5 | ~initial_state:(fun ~this -> object%js 6 | val a = 1.0 val b = 3.0 val c = -3.0 7 | end) 8 | (fun ~this -> let open Infix in 9 | let handle_input_change = 10 | fun ~key event -> 11 | let new_state = 12 | ([(key, 13 | event##.target##.value |> Js.parseFloat |> Js.number_of_float )] >>> 14 | object%js end) 15 | in 16 | this##setState new_state 17 | in 18 | let (a, b, c) = this##.state##.a, this##.state##.b, this##.state##.c in 19 | let root = Js.math##sqrt ((Js.math##pow b 2.0) -. 4.0 *. a *. c) in 20 | let denominator = 2.0 *. a in 21 | let (x1, x2) = (-.b +. root) /. denominator, (-.b -. root) /. denominator in 22 | let input_label ~key init_value = DOM.( 23 | make ~tag:`label 24 | [Text (Printf.sprintf "%s: " key); 25 | Elem (make ~elem_spec:(object%js 26 | val type_ = !*"number" 27 | val value = !^init_value 28 | val onChange = handle_input_change ~key 29 | end) ~tag:`input [])] 30 | ) 31 | in 32 | let label_row l = l |> List.map ~f:(fun (key, value) -> 33 | [Elem (input_label ~key value); Elem (DOM.make ~tag:`br [])] 34 | ) |> List.flatten 35 | in 36 | let equation_row = DOM.( 37 | [Elem (make ~tag:`em [Text "ax"]); Elem (make ~tag:`sup [Text "2"]); 38 | Text " + "; Elem (make ~tag:`em [Text "bx"]); Text " + "; 39 | Elem (make ~tag:`em [Text "c"]); Text " = 0"]) 40 | in 41 | DOM.(make ~tag:`div 42 | [Elem (make ~tag:`strong equation_row ); 43 | Elem (make ~tag:`h4 [Text "Solve for "; 44 | Elem (make ~tag:`em [Text "x"])]); 45 | Elem (make ~tag:`p 46 | (label_row [("a", a); ("b", b); ("c", c)] @ 47 | [Text "x: "; 48 | Elem (make ~tag:`strong 49 | [Text (Printf.sprintf "%f %f" x1 x2)])])) 50 | ])) 51 | |> create_class 52 | ) 53 | 54 | let () = 55 | Reactjs.(render 56 | ~react_elem:(create_element_from_class quadratic_calculator) 57 | (get_elem ~id:"container")) 58 | -------------------------------------------------------------------------------- /reactjs_based_examples/quadratic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Basic Click Counter Example 6 | 7 | 8 | 9 |

Quadratic Calculator

10 |
11 |

12 | To install React, follow the instructions on 13 | GitHub. 14 |

15 |

16 | If you can see this, React is not working right. 17 |

18 |
19 |

Example Details

20 |

This example uses events and state to track clicks and update the rendered output.

21 |

22 | Learn more about React at 23 | facebook.github.io/react. 24 |

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /reactjs_based_examples/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | do_all() { 6 | for i in 'basic' 'basic-click-counter' 'quadratic'; do 7 | printf '\tBuilding example: %s\n\n' ${i} 8 | make -C ${i} 9 | done 10 | } 11 | 12 | do_all 13 | -------------------------------------------------------------------------------- /reactjs_based_examples/todomvc/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile 2 | -------------------------------------------------------------------------------- /reactjs_based_examples/todomvc/README.md: -------------------------------------------------------------------------------- 1 | ## Compile 2 | `ocamlfind ocamlc -g -package reactjs -linkpkg main.ml -o code && js_of_ocaml --enable=debuginfo --disable=inline --enable=pretty code -o code.js` 3 | -------------------------------------------------------------------------------- /reactjs_based_examples/todomvc/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /reactjs_based_examples/todomvc/example.ml: -------------------------------------------------------------------------------- 1 | type task_id = int 2 | 3 | type task = 4 | { 5 | id: task_id; 6 | label: string; 7 | completed: bool; 8 | } 9 | 10 | type tab = All | Active | Completed 11 | 12 | type state = { 13 | editing: task_id option; 14 | tasks: task list; 15 | tab: tab; 16 | } 17 | 18 | type history = 19 | state list 20 | 21 | let initial_state = 22 | let tasks = [{id = 0; 23 | label = "Initial task"; 24 | completed = false}; 25 | {id = 1; 26 | label = "Completed task"; 27 | completed = true}; 28 | {id = 2; 29 | label = "Final task"; 30 | completed = true};] 31 | in 32 | ref {editing = None; 33 | tasks = tasks; 34 | tab = All} 35 | 36 | let (histories : history ref) = 37 | ref [] 38 | 39 | let observe _old_state new_state = 40 | Printf.printf "Mutation observed: %d\n" (List.length new_state.tasks) 41 | 42 | let observer = 43 | ref (fun _old_state _new_state -> Printf.printf "Placeholder observer used") 44 | 45 | let swap (ref : state ref) f = 46 | let old_state = !ref in 47 | ref := f ref; 48 | let new_state = !ref in 49 | ignore(!observer old_state new_state); 50 | histories := List.append [new_state] !histories; 51 | Printf.printf "History count: %d\n" (List.length !histories); 52 | () 53 | 54 | let set_tab state tab = 55 | {state with tab = tab} 56 | 57 | let random_el arr = 58 | let n = Random.int (Array.length arr) in 59 | Array.get arr n;; 60 | 61 | let colors = 62 | [|"blue"; "green"; "pink"; "purple"; "white"; "gray"|] 63 | 64 | let random_color () = 65 | random_el colors 66 | 67 | let todo_item (task : task) = 68 | let open Reactjs in 69 | let open Infix in 70 | make_class_spec 71 | (fun ~this -> 72 | ignore(this); 73 | DOM.make ~tag:`li 74 | ~elem_spec:(object%js 75 | val key = !*("li-todo-input-" ^ (string_of_int task.id)) 76 | end) 77 | (if (match !initial_state.editing with 78 | | None -> false 79 | | Some id -> id = task.id) then 80 | [Elem (DOM.make ~tag:`input ~elem_spec:(object%js 81 | val key = !*("todo-input-" ^ (string_of_int task.id)) 82 | val type_ = !*"text" 83 | val style = (object%js val display = "block" val backgroundColor = !*("white") end) 84 | val defaultValue = task.label 85 | val onChange = (fun event -> 86 | this##setState event##.target##.value |> Js.to_string 87 | (* swap initial_state (fun state -> *) 88 | (* let new_tasks = List.map (fun t -> *) 89 | (* if t.id = task.id then *) 90 | (* {t with label = event##.target##.value |> Js.to_string } *) 91 | (* else *) 92 | (* t) !state.tasks in *) 93 | (* {!state with tasks = new_tasks}) *)) 94 | val onKeyUp = (fun event -> 95 | Printf.printf "Key: %d\n" event##.which; 96 | match event##.which with 97 | | 13 -> swap initial_state (fun state -> 98 | let new_tasks = List.map (fun t -> 99 | if t.id = task.id then 100 | {t with label = event##.target##.value |> Js.to_string } 101 | else 102 | t) !state.tasks in 103 | let _tt = List.find (fun t -> t.id = task.id) new_tasks in 104 | Printf.printf "Task label after updating: %s\n" _tt.label; 105 | {!state with 106 | tasks = new_tasks; 107 | editing = None}) 108 | | 27 -> swap initial_state (fun state -> {!state with editing = None}) 109 | | _ -> () 110 | 111 | ) 112 | val className = Js.string "edit" 113 | end) 114 | [])] 115 | else 116 | [Elem (DOM.make 117 | ~tag:`div 118 | ~elem_spec:(object%js 119 | val className = "view" 120 | val onDoubleClick = (fun _ -> swap initial_state (fun state -> 121 | Printf.printf "Now editing %d\n"task.id; 122 | {!state with editing = Some task.id} 123 | )) 124 | end) 125 | [Elem (DOM.make ~tag:`input ~elem_spec:(object%js 126 | val type_ = !*"checkbox" 127 | val onClick = (fun _ -> swap initial_state (fun state -> 128 | let new_tasks = List.map (fun t -> 129 | if t.id = task.id then 130 | {t with completed = not t.completed} 131 | else 132 | t 133 | ) !state.tasks in 134 | {!state with tasks = new_tasks} 135 | )) 136 | val checked = Js.string (if task.completed then "checked" else "") 137 | val className = Js.string "toggle" 138 | end) 139 | []); 140 | Elem (DOM.make 141 | ~tag:`label 142 | [Text ((match !initial_state.editing with 143 | | None -> "" 144 | | Some editing -> if editing = task.id then "Editing: " else "") ^ task.label);]); 145 | Elem (DOM.make 146 | ~tag:`button 147 | ~elem_spec:(object%js 148 | val onClick = (fun _ -> swap initial_state (fun state -> 149 | let new_tasks = List.filter (fun t -> 150 | t.id != task.id 151 | ) !state.tasks in 152 | {!state with tasks = new_tasks} 153 | )) 154 | val className = !*"destroy" 155 | end) 156 | [])])])) 157 | |> create_class 158 | 159 | let todo_input () = 160 | let open Reactjs in 161 | let open Infix in 162 | make_class_spec 163 | (fun ~this -> 164 | ignore(this); 165 | DOM.make ~tag:`input 166 | ~elem_spec:(object%js 167 | val className = !*"new-todo" 168 | val placeholder = !*"What needs to be done" 169 | val autofocus = !*"true" 170 | val onKeyDown = (fun event -> 171 | match event##.which with 172 | | 13 -> swap initial_state (fun state -> 173 | let id = Random.int 64000 in 174 | Printf.printf "Updating new task...\n"; 175 | Firebug.console##log event##.target##.value; 176 | let new_tasks = List.append !state.tasks [{id = id; label = event##.target##.value |> Js.to_string; completed = false}] in 177 | Printf.printf "New tasks updated\n"; 178 | {!state with tasks = new_tasks} 179 | ) 180 | | _ -> ()) 181 | 182 | end) 183 | []) 184 | |> create_class 185 | 186 | let root app = 187 | let open Reactjs in 188 | let open Infix in 189 | let tasks = List.filter (fun task -> 190 | Printf.printf "Checking if task matches: %d\n" task.id; 191 | match app.tab with 192 | | All -> true 193 | | Active -> not task.completed 194 | | Completed -> task.completed 195 | ) app.tasks in 196 | make_class_spec 197 | (fun ~this -> 198 | ignore(this); 199 | DOM.make ~tag:`section 200 | ~elem_spec:(object%js 201 | val className = !*"todoapp" 202 | end) 203 | [Elem (DOM.make ~tag:`header 204 | ~elem_spec:(object%js 205 | val className = !*"header" 206 | end) 207 | [Elem (DOM.make ~tag:`h1 [Text "todos"]); 208 | Elem (create_element_from_class (todo_input ()))]); 209 | Elem (DOM.make ~tag:`section 210 | ~elem_spec:(object%js 211 | val className = !*"main" 212 | end) [Elem (DOM.make ~tag:`input 213 | ~elem_spec:([("type", Js.string "checkbox"); 214 | ("className", Js.string "toggle-all")] 215 | >>> object%js end) []); 216 | Elem (DOM.make ~tag:`label 217 | ~elem_spec:([("htmlFor", Js.string "toggle-all");] 218 | >>> object%js end) 219 | [Text "Mark all as complete"]); 220 | Elem (DOM.make ~tag:`ul 221 | ~elem_spec:(object%js 222 | val className = !*"todo-list" 223 | end) 224 | (List.map (fun task -> Elem (create_element_from_class (todo_item task))) tasks))]); 225 | Elem (DOM.make ~tag:`footer 226 | ~elem_spec:(object%js 227 | val className = !*"footer" 228 | end) 229 | [Elem (DOM.make ~tag:`span ~elem_spec:(object%js 230 | val className = !*"todo-count" 231 | end) [Text (string_of_int (List.length tasks))]); 232 | Elem (DOM.make ~tag:`ul ~elem_spec:(object%js 233 | val className = !*"filters" 234 | end) 235 | [Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js 236 | val href = !*"#/" 237 | val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state All)) 238 | val className = (match app.tab with 239 | | All -> !*"selected" 240 | | _ -> !*"") 241 | end) [Text "All"])]); 242 | Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js 243 | val href = !*"#/active" 244 | val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state Active)) 245 | val className = (match app.tab with 246 | | Active -> !*"selected" 247 | | _ -> !*"") 248 | end) [Text "Active"])]); 249 | Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js 250 | val href = !*"#/completed" 251 | val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state Completed)) 252 | val className = (match app.tab with 253 | | Completed -> !*"selected" 254 | | _ -> !*"") 255 | end) [Text "Completed"])])]); 256 | Elem (DOM.make ~tag:`button ~elem_spec:(object%js 257 | val className = !*"clear-completed" 258 | val onClick = (fun _ -> swap initial_state (fun state -> 259 | let new_tasks = List.filter (fun t -> 260 | not t.completed 261 | ) !state.tasks in 262 | {!state with tasks = new_tasks} 263 | )) 264 | end) [Text "Clear completed"]) 265 | ]); 266 | ]) 267 | |> create_class 268 | 269 | let dump_state _ = 270 | List.iter (fun task -> print_endline (task.label ^ " [" ^ (string_of_bool task.completed) ^ "]")) !initial_state.tasks 271 | 272 | let () = 273 | Js.Unsafe.global##.dump_state := dump_state 274 | 275 | let () = 276 | Js.Unsafe.global##.get_state := (fun _ -> !initial_state) 277 | 278 | let first_render = 279 | ref true 280 | 281 | let wrap_root state_ref = 282 | let open Reactjs in 283 | let open Infix in 284 | make_class_spec 285 | (fun ~this -> 286 | ignore(this); 287 | DOM.make ~tag:`div 288 | ~elem_spec:(object%js 289 | val className = !*"overreact" 290 | end) 291 | [Elem (create_element_from_class (root !state_ref)); 292 | ]) 293 | |> create_class 294 | 295 | let render_root state = 296 | let open Reactjs in 297 | (match !state.tab with 298 | | All -> Printf.printf "Current tab: All\n" 299 | | Active -> Printf.printf "Current tab: Active\n" 300 | | Completed -> Printf.printf "Current tab: Completed\n"); 301 | (match !first_render with 302 | | _ -> let root_el_ = render 303 | ~react_elem:(create_element_from_class (wrap_root state)) 304 | (get_elem ~id:"container") in 305 | first_render := false; 306 | Js.Unsafe.global##.example := root_el_; 307 | (* | false -> Js.Unsafe.global##.example##forceUpdate *) 308 | ); 309 | Firebug.console##log Js.Unsafe.global##.example; 310 | () 311 | 312 | let replay_history = 313 | (fun _ -> 314 | let rec render_next_state = (fun states -> 315 | match (List.length states) with 316 | | 0 -> () 317 | | _ -> let next_state = List.hd states in 318 | render_root (ref next_state); 319 | ignore(Dom_html.window##setTimeout (Js.wrap_callback (fun () -> render_next_state (List.tl states); ())) 500.); 320 | ()) in 321 | render_next_state (List.rev !histories) 322 | ) 323 | 324 | let () = 325 | Js.Unsafe.global##.replayHistory := replay_history; 326 | let app_observer = (fun (_old_state : state) (_new_state : state) -> render_root initial_state) in 327 | observer := app_observer; 328 | swap initial_state (fun state -> state := {!state with tab = All}; !state); 329 | -------------------------------------------------------------------------------- /reactjs_based_examples/todomvc/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre-line; 201 | word-break: break-all; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /reactjs_based_examples/todomvc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rerere • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /setup.ml: -------------------------------------------------------------------------------- 1 | (* setup.ml generated for the first time by OASIS v0.4.6 *) 2 | 3 | (* OASIS_START *) 4 | (* DO NOT EDIT (digest: 9852805d5c19ca1cb6abefde2dcea323) *) 5 | (******************************************************************************) 6 | (* OASIS: architecture for building OCaml libraries and applications *) 7 | (* *) 8 | (* Copyright (C) 2011-2013, Sylvain Le Gall *) 9 | (* Copyright (C) 2008-2011, OCamlCore SARL *) 10 | (* *) 11 | (* This library is free software; you can redistribute it and/or modify it *) 12 | (* under the terms of the GNU Lesser General Public License as published by *) 13 | (* the Free Software Foundation; either version 2.1 of the License, or (at *) 14 | (* your option) any later version, with the OCaml static compilation *) 15 | (* exception. *) 16 | (* *) 17 | (* This library is distributed in the hope that it will be useful, but *) 18 | (* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *) 19 | (* or FITNESS FOR A PARTICULAR PURPOSE. See the file COPYING for more *) 20 | (* details. *) 21 | (* *) 22 | (* You should have received a copy of the GNU Lesser General Public License *) 23 | (* along with this library; if not, write to the Free Software Foundation, *) 24 | (* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *) 25 | (******************************************************************************) 26 | 27 | let () = 28 | try 29 | Topdirs.dir_directory (Sys.getenv "OCAML_TOPLEVEL_PATH") 30 | with Not_found -> () 31 | ;; 32 | #use "topfind";; 33 | #require "oasis.dynrun";; 34 | open OASISDynRun;; 35 | 36 | (* OASIS_STOP *) 37 | let () = setup ();; 38 | -------------------------------------------------------------------------------- /src/META: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: f5a20f1a39359a54d1753d00ca66ee3b) 3 | version = "0.0.1" 4 | description = "js_of_ocaml bindings for Reactjs" 5 | requires = 6 | "js_of_ocaml js_of_ocaml.ppx lwt.ppx lwt ppx_deriving.show ppx_deriving.make commonjs_of_ocaml" 7 | archive(byte) = "reactjs.cma" 8 | archive(byte, plugin) = "reactjs.cma" 9 | exists_if = "reactjs.cma" 10 | # OASIS_STOP 11 | 12 | -------------------------------------------------------------------------------- /src/ppx/ppx_reactjs.ml: -------------------------------------------------------------------------------- 1 | (* open Ast_mapper *) 2 | (* open Ast_helper *) 3 | (* open Asttypes *) 4 | (* open Parsetree *) 5 | (* open Longident *) 6 | 7 | (* open Reactjs_high_level *) 8 | 9 | (* let getenv s = try Sys.getenv s with Not_found -> "" *) 10 | 11 | (* let getenv_mapper argv = *) 12 | (* (\* Our getenv_mapper only overrides the handling of expressions in the default mapper. *\) *) 13 | (* { default_mapper with *) 14 | (* expr = fun mapper expr -> *) 15 | (* match expr with *) 16 | (* (\* Is this an extension node? *\) *) 17 | (* | { pexp_desc = *) 18 | (* (\* Should have name "getenv". *\) *) 19 | (* Pexp_extension ({ txt = "elem"; loc }, pstr)} -> *) 20 | (* begin match pstr with *) 21 | (* | (\* Should have a single structure item, which is evaluation of a constant string. *\) *) 22 | (* PStr [{ pstr_desc = *) 23 | (* Pstr_eval ({ pexp_loc = loc; *) 24 | (* pexp_desc = Pexp_constant (Parsetree.con (sym, None))}, _)}] -> *) 25 | (* (\* Replace with a constant string with the value from the environment. *\) *) 26 | (* Exp.constant ~loc (Pconst_string (getenv sym, None)) *) 27 | (* | _ -> *) 28 | (* raise (Location.Error ( *) 29 | (* Location.error ~loc "[%getenv] accepts a string, e.g. [%getenv \"USER\"]")) *) 30 | (* end *) 31 | (* (\* Delegate to the default mapper. *\) *) 32 | (* | x -> default_mapper.expr mapper x; *) 33 | (* } *) 34 | 35 | (* let () = register "elem" getenv_mapper *) 36 | -------------------------------------------------------------------------------- /src/reactjs.ml: -------------------------------------------------------------------------------- 1 | open StdLabels 2 | 3 | type 'a javascript_object = 'a Js.t 4 | 5 | type 'a component_api = ( as 'a) Js.t 6 | 7 | module Infix = struct 8 | 9 | let ( !@ ) = Js.wrap_meth_callback 10 | 11 | let ( !^ ) = Js.Unsafe.inject 12 | 13 | let ( ) (obj : 'a Js.t) field = Js.Unsafe.get obj (Js.string field) 14 | 15 | let ( ) (obj : 'a Js.t) field = 16 | Js.Opt.(obj field |> return |> to_option) 17 | 18 | let ( <@> ) (obj : 'a Js.t) field = 19 | Js.Optdef.(obj field |> return |> to_option) 20 | 21 | (* merge *) 22 | let ( <+> ) (a :'a Js.t) (b : 'b Js.t) : < .. > Js.t = 23 | Js.Unsafe.global##.Object##assign a b 24 | 25 | let ( !$ ) (o : 'a Js.t) = Js._JSON##stringify o |> Js.to_string 26 | 27 | let ( !* ) = Js.string 28 | 29 | let ( !& ) = Js.to_string 30 | 31 | let ( $> ) g = 32 | g |> Js.str_array |> Js.to_array 33 | |> Array.map ~f:Js.to_string |> Array.to_list 34 | 35 | let ( <$ ) g = 36 | g |> Array.of_list |> Array.map ~f:Js.string |> Js.array 37 | 38 | let ( >>> ) 39 | (data : (string * 'a Js.t) list) (obj : 'b Js.t) : 'b Js.t = 40 | data 41 | |> List.iter ~f:(fun (key, o) -> Js.Unsafe.set obj (Js.string key) o); 42 | obj 43 | 44 | end 45 | 46 | module Helpers = struct 47 | let set_interval ~f ~every = 48 | Dom_html.window##setInterval (Js.wrap_callback f) every 49 | let get_elem ~id = Dom_html.getElementById id 50 | let debug thing field = 51 | Firebug.console##log 52 | (Js.Unsafe.(meth_call (get thing field) "toString" [||])) 53 | let real_type_name (o : 'a Js.t) = 54 | Infix.(!&((o "constructor") "name")) 55 | 56 | end 57 | 58 | include Helpers 59 | 60 | module Low_level_bindings = struct 61 | 62 | class type react_dom_server = object 63 | method renderToString : 64 | react_element Js.t -> Js.js_string Js.t Js.meth 65 | method renderToStaticMarkup : 66 | react_element Js.t -> Js.js_string Js.t Js.meth 67 | end 68 | 69 | and react_dom = object 70 | method render : 71 | react_element Js.t -> #Dom_html.element Js.t -> unit Js.meth 72 | (* method render_WithCallback : *) 73 | (* react_element Js.t -> #Dom_html.element Js.t -> unit Js.meth *) 74 | 75 | method unmountComponentAtNode : 76 | #Dom_html.element Js.t -> bool Js.t Js.meth 77 | 78 | (* method findDOMNode : *) 79 | (* Js.t -> #Dom_html.element Js.t Js.meth *) 80 | 81 | end 82 | 83 | and ['this] react = object 84 | 85 | constraint 'this = _ component_api 86 | 87 | method createElement_withString : 88 | Js.js_string Js.t -> react_element Js.t Js.meth 89 | 90 | method createElement_withPropsAndSingleText : 91 | Js.js_string Js.t -> 92 | Js.t -> 93 | Js.js_string Js.t -> 94 | react_element Js.t Js.meth 95 | 96 | method createElement_WithReactClass : 97 | react_class Js.t -> _ Js.Opt.t -> react_element Js.t Js.meth 98 | method cloneElement : react_element Js.t -> react_element Js.t Js.meth 99 | method isValidElement : 'a Js.t -> bool Js.t Js.meth 100 | 101 | method createClass : 102 | < 103 | render : 104 | ('this, react_element Js.t) Js.meth_callback Js.readonly_prop; 105 | getInitialState : 106 | ('this, 'b Js.t) Js.meth_callback Js.Optdef.t Js.readonly_prop; 107 | getDefaultProps : 108 | ('this, 'default_props Js.t ) 109 | Js.meth_callback Js.Optdef.t Js.readonly_prop; 110 | propTypes : 'props_validator Js.t Js.Optdef.t Js.readonly_prop; 111 | mixins : 'mixin Js.t Js.js_array Js.t Js.Optdef.t Js.readonly_prop; 112 | statics : 'static_functions Js.t Js.Optdef.t Js.readonly_prop; 113 | displayName : Js.js_string Js.t Js.Optdef.t Js.readonly_prop; 114 | (* Lifecycle Methods *) 115 | componentWillMount : 116 | ('this, unit) Js.meth_callback Js.Optdef.t Js.readonly_prop; 117 | componentDidMount : 118 | ('this, unit) Js.meth_callback Js.Optdef.t Js.readonly_prop; 119 | componentWillReceiveProps : 120 | ('this, 121 | 'next_props Js.t -> unit) 122 | Js.meth_callback Js.Optdef.t Js.readonly_prop; 123 | shouldComponentUpdate : 124 | ('this, 'next_props Js.t -> 'next_state Js.t -> bool Js.t) 125 | Js.meth_callback Js.Optdef.t Js.readonly_prop; 126 | componentWillUpdate : 127 | ('this, 'next_prop Js.t -> 'next_state Js.t -> unit) 128 | Js.meth_callback Js.Optdef.t Js.readonly_prop; 129 | componentDidUpdate : 130 | ('this, 131 | 'prev_prop Js.t -> 'prev_state Js.t -> unit) 132 | Js.meth_callback Js.Optdef.t Js.readonly_prop; 133 | componentWillUnmount : 134 | ('this, unit) 135 | Js.meth_callback Js.Optdef.t Js.readonly_prop; 136 | > Js.t -> 137 | react_class Js.t Js.meth 138 | 139 | method createFactory : 140 | react_class Js.t -> 141 | (prop:'new_prop Js.t -> react_element Js.t) Js.meth 142 | 143 | method version : Js.js_string Js.t Js.readonly_prop 144 | (* method __spread *) 145 | method _DOM : 'a Js.t Js.readonly_prop 146 | end 147 | 148 | and react_element = object 149 | 150 | method type_ : Js.js_string Js.t Js.readonly_prop 151 | method key : 'a Js.t Js.Opt.t Js.prop 152 | (* method ref : react_element_ref Js.t Js.Opt.t Js.prop *) 153 | 154 | end 155 | 156 | and react_class = object 157 | 158 | end 159 | 160 | let (__react, __reactDOM, __reactDOMServer) : 161 | 'react Js.t * 'react_dom Js.t * react_dom_server Js.t 162 | = Js.Unsafe.( 163 | [%require_or_default "react" global##.React], 164 | [%require_or_default "react-dom" global##.ReactDOM], 165 | [%require_or_default "react-dom/server" global##.dummy] 166 | ) 167 | 168 | let react : 169 | 'this . 170 | (< isMounted : bool Js.t Js.meth; .. > as 'this) 171 | component_api react Js.t 172 | = __react 173 | 174 | let react_dom = __reactDOM 175 | 176 | (* Only makes sense on the server, hence the unit *) 177 | let react_dom_server : unit -> react_dom_server Js.t = 178 | fun () -> __reactDOMServer 179 | 180 | end 181 | 182 | type element_spec = { class_name: string option; } [@@deriving make] 183 | 184 | type react_node = 185 | | Text of string 186 | | Elem of Low_level_bindings.react_element Js.t 187 | (* | Fragment : _ react_node list -> _ react_node *) 188 | 189 | type tree = react_node list 190 | 191 | type ('this, 192 | 'initial_state, 193 | 'default_props, 194 | 'prop_types, 195 | 'static_functions, 196 | 'next_props, 197 | 'next_state, 198 | 'prev_props, 199 | 'prev_state, 200 | 'props, 201 | 'mixin) class_spec = 202 | { render: 203 | this:'this component_api -> 204 | Low_level_bindings.react_element Js.t; [@main] 205 | initial_state : (this:'this component_api -> 'initial_state Js.t) option; 206 | default_props : (this:'this component_api -> 'default_props Js.t) option; 207 | prop_types : 'prop_types Js.t option; 208 | mixins : 'mixin Js.t list option; 209 | statics : 'static_functions Js.t option; 210 | display_name : string option; 211 | component_will_mount : (this:'this Js.t -> unit) option; 212 | component_did_mount : (this:'this Js.t -> unit) option; 213 | component_will_receive_props : 214 | (this:'this Js.t -> next_prop:'next_props Js.t -> unit) option; 215 | should_component_update : 216 | (this:'this Js.t -> 217 | next_prop:'next_props Js.t -> 218 | next_state:'next_state Js.t -> bool Js.t) option; 219 | component_will_update : 220 | (this:'this Js.t -> 221 | next_prop:'next_props Js.t -> 222 | next_state:'next_state Js.t -> unit) option; 223 | component_did_update : 224 | (this:'this Js.t -> 225 | prev_prop:'prev_props Js.t -> 226 | prev_state:'prev_state Js.t -> unit) option; 227 | component_will_unmount : (this:'this Js.t -> unit) option; 228 | } [@@deriving make] 229 | 230 | let create_element : 231 | ?element_opts:element_spec -> 232 | string -> 233 | tree -> 234 | Low_level_bindings.react_element Js.t 235 | = fun ?element_opts elem_name children -> Js.Unsafe.( 236 | let g = 237 | children |> List.map ~f:(function 238 | | Elem e -> inject e 239 | | Text s -> Js.string s |> inject 240 | (* | Fragment l -> *) 241 | ) 242 | in 243 | [ 244 | [| 245 | inject (Js.string elem_name); 246 | match element_opts with 247 | None -> Js.null |> inject 248 | | Some spec -> 249 | inject (object%js(self) 250 | val className = 251 | Js.Opt.(map (option spec.class_name) Js.string) 252 | end) 253 | |]; 254 | Array.of_list g 255 | ] 256 | |> Array.concat 257 | |> Js.Unsafe.meth_call Low_level_bindings.__react "createElement" 258 | ) 259 | 260 | let create_element_from_class class_ = 261 | Low_level_bindings.react##createElement_WithReactClass class_ Js.null 262 | 263 | let create_factory : 264 | Low_level_bindings.react_class Js.t -> 265 | (props:'a Js.t -> Low_level_bindings.react_element Js.t) = fun c -> 266 | let call_me = Low_level_bindings.react##createFactory c in 267 | fun ~props -> Js.Unsafe.fun_call call_me [|Js.Unsafe.inject props|] 268 | 269 | let create_class class_opts = let open Js.Optdef in 270 | let comp = (object%js 271 | (* Component Specifications *) 272 | val render = Js.wrap_meth_callback (fun this -> class_opts.render ~this) 273 | val getInitialState = 274 | (fun f -> Js.wrap_meth_callback (fun this -> f ~this)) 275 | |> map (option class_opts.initial_state) 276 | val getDefaultProps = 277 | (fun f -> Js.wrap_meth_callback (fun this -> f ~this)) 278 | |> map (option class_opts.default_props) 279 | val propTypes = option class_opts.prop_types 280 | val mixins = map (option class_opts.mixins) (fun m -> Array.of_list m |> Js.array) 281 | val statics = option class_opts.statics 282 | val displayName = map (option class_opts.display_name) Js.string 283 | (* Lifecycle Methods *) 284 | val componentWillMount = 285 | (fun f -> Js.wrap_meth_callback (fun this -> f ~this)) 286 | |> map (option class_opts.component_will_mount) 287 | val componentDidMount = 288 | (fun f -> Js.wrap_meth_callback (fun this -> f ~this)) 289 | |> map (option class_opts.component_did_mount) 290 | val componentWillReceiveProps = 291 | (fun f -> Js.wrap_meth_callback (fun this next_prop -> f ~this ~next_prop)) 292 | |> map (option class_opts.component_will_receive_props) 293 | val shouldComponentUpdate = 294 | (fun f -> Js.wrap_meth_callback 295 | (fun this next_prop next_state -> f ~this ~next_prop ~next_state)) 296 | |> map (option class_opts.should_component_update) 297 | val componentWillUpdate = 298 | (fun f -> Js.wrap_meth_callback 299 | (fun this next_prop next_state -> f ~this ~next_prop ~next_state)) 300 | |> map (option class_opts.component_will_update) 301 | val componentDidUpdate = 302 | (fun f -> Js.wrap_meth_callback 303 | (fun this prev_prop prev_state -> f ~this ~prev_prop ~prev_state)) 304 | |> map (option class_opts.component_did_update) 305 | val componentWillUnmount = 306 | (fun f -> Js.wrap_meth_callback (fun this -> f ~this)) 307 | |> map (option class_opts.component_will_unmount) 308 | end) 309 | in 310 | Low_level_bindings.react##createClass comp 311 | 312 | let elem_from_spec spec = create_element_from_class (create_class spec) 313 | 314 | let render ~react_elem dom_elem = 315 | Low_level_bindings.react_dom##render react_elem dom_elem 316 | 317 | 318 | let (react, react_dom, react_dom_server) = 319 | Low_level_bindings.react, 320 | Low_level_bindings.react_dom, 321 | Low_level_bindings.react_dom_server 322 | 323 | module DOM = struct 324 | 325 | type tag = [`a | `abbr | `address | `area | `article | `aside | `audio | 326 | `b | `base | `bdi | `bdo | `big | `blockquote | `body | 327 | `br | `button | `canvas | `caption | `cite | `code | 328 | `col | `colgroup | `data | `datalist | `dd | `del | 329 | `details | `dfn | `dialog | `div | `dl | `dt | `em | 330 | `emded | `fieldset | `figcaption | `figure | `footer | 331 | `form | `h1 | `h2 | `h3 | `h4 | `h5 | `h6 | `head | `header | 332 | `hgroup | `hr | `html | `i | `iframe | `img | `input | 333 | `ins | `kbd | `keygen | `label | `legend | `li | `link | 334 | `main | `map | `mark | `menu | `menuitem | `meta | `meter | 335 | `nav | `noscript | 336 | `object_ [@printer fun fmt -> fprintf fmt "`object"] | 337 | `ol | `optgroup | `option | `output | `p | `param | `picture | 338 | `pre | `progress | `q | `rp | `rt | `ruby | `s | `samp | 339 | `script | `section | `select | `small | `source | `span | 340 | `strong | `style | `sub | `summary | `sup | `table | 341 | `tbody | `td | `textarea | `tfoot | `th | `thead | 342 | `time | `title | `tr | `track | `u | `ul | `var | `video | 343 | `wbr | `circle | `clipPath | `defs | `ellipse | `g | 344 | `image | `line | `linearGradient | `mask | `path | 345 | `pattern | `polygon | `polyline | `radialGradient | 346 | `rect | `stop | `svg | `text | `tspan ] [@@deriving show] 347 | 348 | let string_of_tag tag = 349 | (show_tag tag |> Js.string)##substring_toEnd 1 |> Js.to_string 350 | 351 | type 'a elem_spec = 352 | ( as 'a ) Js.t 353 | 354 | let make : 355 | ?elem_spec : 'a javascript_object -> 356 | ?class_name : string -> 357 | ?tag:tag -> 358 | tree -> 359 | Low_level_bindings.react_element Js.t 360 | = fun ?elem_spec ?class_name ?(tag=`div) children -> Js.Unsafe.(Infix.( 361 | let elem_name = tag |> string_of_tag in 362 | let args = children |> List.map ~f:(function 363 | | Elem e -> inject e 364 | | Text s -> Js.string s |> inject 365 | (* | Fragment l -> *) 366 | ) 367 | in 368 | match elem_spec, class_name with 369 | | None, None -> 370 | meth_call 371 | Low_level_bindings.react##._DOM 372 | elem_name 373 | (Array.of_list (inject Js.null :: args)) 374 | | (Some e_spec, None) -> 375 | meth_call 376 | Low_level_bindings.react##._DOM 377 | elem_name 378 | (Array.of_list ((inject e_spec) :: args)) 379 | | (Some e_spec, Some c_name) -> 380 | (* Mutates the elem_spec by making className take 381 | precedence *) 382 | meth_call 383 | Low_level_bindings.react##._DOM 384 | elem_name 385 | (Array.of_list ((inject ([("className", !*c_name)] >>> e_spec)) :: args)) 386 | | (None, Some c_name) -> 387 | meth_call 388 | Low_level_bindings.react##._DOM 389 | elem_name 390 | (Array.of_list ((inject (object%js val className = !*c_name end)) :: args)) 391 | )) 392 | 393 | end 394 | 395 | module Common_components = struct 396 | 397 | let stylesheet ?custom_opts ~href () = Infix.( 398 | let attrs = object%js 399 | val ref = !*"stylesheet" 400 | val href = !*href 401 | end 402 | in 403 | match custom_opts with 404 | None -> 405 | Elem (DOM.make ~elem_spec:attrs ~tag:`link []) 406 | | Some custom -> 407 | Elem (DOM.make ~elem_spec:(attrs <+> custom) ~tag:`link []) 408 | ) 409 | 410 | let ahref ?(href="") txt = Infix.( 411 | Elem (DOM.make 412 | ~elem_spec:(object%js val href = !*href end) 413 | ~tag:`a 414 | [Text txt]) 415 | ) 416 | 417 | end 418 | -------------------------------------------------------------------------------- /src/reactjs.mldylib: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: fce39bef5e4bd9f96c8757917a003a35) 3 | Reactjs 4 | # OASIS_STOP 5 | -------------------------------------------------------------------------------- /src/reactjs.mli: -------------------------------------------------------------------------------- 1 | (** OCaml bindings to ReactJS, interact with the function provided by 2 | the top level modules {b Infix}, {b Helpers}, and {b Reactjs} *) 3 | 4 | type 'a javascript_object = 'a Js.t 5 | (** A JavaScript object made via: 6 | (object%js 7 | val foo = Js.string "Hello" 8 | end) *) 9 | 10 | (** Signature of a ReactComponent *) 11 | type 'a component_api = 'a Js.t 12 | constraint 'a = < isMounted : bool Js.t Js.meth; .. > 13 | (** This constraint lets us guarantee what fields/methods will exist 14 | on the `this` object given to component lifecycle methods *) 15 | 16 | (** Various helper functions that deal with the DOM API *) 17 | module Helpers : 18 | sig 19 | val set_interval : 20 | f:(unit -> unit) -> every:float -> Dom_html.interval_id 21 | 22 | (** alias of document.getElementById *) 23 | val get_elem : id:string -> Dom_html.element Js.t 24 | 25 | val debug : 'a -> 'b -> unit 26 | 27 | (** Calls constructor.name on any object *) 28 | val real_type_name : < .. > Js.t -> string 29 | end 30 | 31 | (* Nice trick to avoid having to type all those signatures again *) 32 | include (module type of Helpers) 33 | 34 | (** Infix and prefix functions for easier go between OCaml and 35 | JavaScript values *) 36 | module Infix : 37 | sig 38 | (** Wrap an OCaml function for JavaScript to call. The first 39 | argument is the `this` context, the rest are whatever arguments 40 | the JavaScript side will call with. Example: 41 | 42 | !@(fun this -> print_endline *!this##.name) 43 | *) 44 | val ( !@ ) : ('a -> 'b) -> ('a, 'b) Js.meth_callback 45 | 46 | (** Inject any value as an any *) 47 | val ( !^ ) : 'a -> Js.Unsafe.any 48 | 49 | (** Get whatever is at the object, unsafe *) 50 | val ( ) : 'a Js.t -> string -> 'b 51 | 52 | (** Get whatever is at the potentially null field *) 53 | val ( ) : 'a Js.t -> string -> 'b option 54 | 55 | (** Get whatever is at the potentially undefined field *) 56 | val ( <@> ) : 'a Js.t -> string -> 'b option 57 | 58 | (** Merge two objects together *) 59 | val ( <+> ) : 'a Js.t -> 'b Js.t -> < .. > Js.t 60 | 61 | (** Call stringify on any Object *) 62 | val ( !$ ) : 'a Js.t -> string 63 | 64 | (** Shorthand for {b Js.string} *) 65 | val ( !* ) : string -> Js.js_string Js.t 66 | 67 | (** Shorthand for {b Js.to_string} *) 68 | val ( !& ) : Js.js_string Js.t -> string 69 | 70 | (** Convert a JavaScript string array into an Ocaml list of strings *) 71 | val ( $> ) : Js.string_array Js.t -> string list 72 | 73 | (** Convert an OCaml list of strings into a JavaScript array of 74 | JavaScript strings *) 75 | val ( <$ ) : string list -> Js.js_string Js.t Js.js_array Js.t 76 | 77 | (** Mutate the object on the right hand side with whatever alist is 78 | on the left hand side, useful for making an object with key name 79 | conflicts with OCaml's reserved keywords, example: 80 | 81 | [("val", !*"black")] >>> (object%js end) 82 | *) 83 | val ( >>> ) : (string * 'a Js.t) list -> 'b Js.t -> 'b Js.t 84 | 85 | end 86 | 87 | (** These are the low level bindings, often times you don't need to 88 | interact with them. They follow the same API as presented from 89 | ReactJS itself at the JavaScript level *) 90 | module Low_level_bindings : sig 91 | 92 | (** Only makes sense on nodejs, ReactDOMServer *) 93 | class type react_dom_server = 94 | object 95 | method renderToStaticMarkup : 96 | react_element Js.t -> Js.js_string Js.t Js.meth 97 | method renderToString : 98 | react_element Js.t -> Js.js_string Js.t Js.meth 99 | end 100 | 101 | (** ReactDOM *) 102 | and react_dom = 103 | object 104 | method render : 105 | react_element Js.t -> #Dom_html.element Js.t -> unit Js.meth 106 | method unmountComponentAtNode : 107 | #Dom_html.element Js.t -> bool Js.t Js.meth 108 | end 109 | 110 | (** React *) 111 | and ['c] react = 112 | object 113 | constraint 'c = 114 | (< isMounted : bool Js.t Js.meth; .. > as 'd) component_api 115 | method _DOM : 'a Js.t Js.readonly_prop 116 | method cloneElement : 117 | react_element Js.t -> react_element Js.t Js.meth 118 | method createClass : 119 | < componentDidMount : ('c, unit) Js.meth_callback Js.Optdef.t 120 | Js.readonly_prop; 121 | componentDidUpdate : ('c, 122 | 'prev_prop Js.t -> 'prev_state Js.t -> unit) 123 | Js.meth_callback Js.Optdef.t 124 | Js.readonly_prop; 125 | componentWillMount : ('c, unit) Js.meth_callback Js.Optdef.t 126 | Js.readonly_prop; 127 | componentWillReceiveProps : ('c, 'next_props Js.t -> unit) 128 | Js.meth_callback Js.Optdef.t 129 | Js.readonly_prop; 130 | componentWillUnmount : ('c, unit) Js.meth_callback Js.Optdef.t 131 | Js.readonly_prop; 132 | componentWillUpdate : ('c, 133 | 'next_prop Js.t -> 134 | 'next_state Js.t -> unit) 135 | Js.meth_callback Js.Optdef.t 136 | Js.readonly_prop; 137 | displayName : Js.js_string Js.t Js.Optdef.t Js.readonly_prop; 138 | getDefaultProps : ('c, 'default_props Js.t) Js.meth_callback 139 | Js.Optdef.t Js.readonly_prop; 140 | getInitialState : ('c, 'b Js.t) Js.meth_callback Js.Optdef.t 141 | Js.readonly_prop; 142 | mixins : 'mixin Js.t Js.js_array Js.t Js.Optdef.t 143 | Js.readonly_prop; 144 | propTypes : 'props_validator Js.t Js.Optdef.t Js.readonly_prop; 145 | render : ('c, react_element Js.t) Js.meth_callback 146 | Js.readonly_prop; 147 | shouldComponentUpdate : ('c, 148 | 'next_props Js.t -> 149 | 'next_state Js.t -> bool Js.t) 150 | Js.meth_callback Js.Optdef.t 151 | Js.readonly_prop; 152 | statics : 'static_functions Js.t Js.Optdef.t Js.readonly_prop > 153 | Js.t -> react_class Js.t Js.meth 154 | method createElement_WithReactClass : 155 | react_class Js.t -> 'e Js.Opt.t -> react_element Js.t Js.meth 156 | method createElement_withPropsAndSingleText : 157 | Js.js_string Js.t -> 158 | < className : Js.js_string Js.t Js.readonly_prop > Js.t -> 159 | Js.js_string Js.t -> react_element Js.t Js.meth 160 | method createElement_withString : 161 | Js.js_string Js.t -> react_element Js.t Js.meth 162 | method createFactory : 163 | react_class Js.t -> 164 | (prop:'new_prop Js.t -> react_element Js.t) Js.meth 165 | method isValidElement : 'a Js.t -> bool Js.t Js.meth 166 | method version : Js.js_string Js.t Js.readonly_prop 167 | end 168 | and react_element = 169 | object 170 | method key : 'a Js.t Js.Opt.t Js.prop 171 | method type_ : Js.js_string Js.t Js.readonly_prop 172 | end 173 | and react_class = object end 174 | 175 | (** Handle on the React object *) 176 | val react : 177 | < isMounted : bool Js.t Js.meth; .. > component_api react Js.t 178 | 179 | (** Handle on ReactDOM *) 180 | val react_dom : 'a Js.t 181 | val react_dom_server : unit -> react_dom_server Js.t 182 | end 183 | 184 | type element_spec = { class_name : string option; } 185 | 186 | val make_element_spec : ?class_name:string -> unit -> element_spec 187 | 188 | (** A React node is one of these items *) 189 | type react_node = 190 | Text of string 191 | (** A Plain text, this doesn't create a react element *) 192 | | Elem of Low_level_bindings.react_element Js.t 193 | (** An arbitrary ReactElement *) 194 | 195 | (** Simple alias of a list of react_nodes *) 196 | type tree = react_node list 197 | 198 | (** Create and pass one of these records to {b create_class}, this is 199 | how you provide functions for a component lifecycle, props, state and 200 | other things. *) 201 | type ('a, 'initial_state, 'default_props, 'prop_types, 'static_functions, 202 | 'next_props, 'next_state, 'prev_props, 'prev_state, 'props, 'mixin) 203 | class_spec = { 204 | render : this:'a component_api -> Low_level_bindings.react_element Js.t; 205 | initial_state : (this:'a component_api -> 'initial_state Js.t) option; 206 | default_props : (this:'a component_api -> 'default_props Js.t) option; 207 | prop_types : 'prop_types Js.t option; 208 | mixins : 'mixin Js.t list option; 209 | statics : 'static_functions Js.t option; 210 | display_name : string option; 211 | component_will_mount : (this:'a Js.t -> unit) option; 212 | component_did_mount : (this:'a Js.t -> unit) option; 213 | component_will_receive_props : 214 | (this:'a Js.t -> next_prop:'next_props Js.t -> unit) option; 215 | should_component_update : 216 | (this:'a Js.t -> 217 | next_prop:'next_props Js.t -> next_state:'next_state Js.t -> bool Js.t) 218 | option; 219 | component_will_update : 220 | (this:'a Js.t -> 221 | next_prop:'next_props Js.t -> next_state:'next_state Js.t -> unit) 222 | option; 223 | component_did_update : 224 | (this:'a Js.t -> 225 | prev_prop:'prev_props Js.t -> prev_state:'prev_state Js.t -> unit) 226 | option; 227 | component_will_unmount : (this:'a Js.t -> unit) option; 228 | } constraint 'a = < isMounted : bool Js.t Js.meth; .. > 229 | 230 | (** Creates a class spec record for you to pass to 231 | {b create_class}. Only requires the last argument, which is your 232 | render function. To provide a function for a lifecycle method, 233 | simply provide a function to whatever optional function you 234 | want. *) 235 | val make_class_spec : 236 | ?initial_state:(this:(< isMounted : bool Js.t Js.meth; .. > as 'a) 237 | component_api -> 238 | 'b Js.t) -> 239 | ?default_props:(this:'a component_api -> 'c Js.t) -> 240 | ?prop_types:'d Js.t -> 241 | ?mixins:'e Js.t list -> 242 | ?statics:'f Js.t -> 243 | ?display_name:string -> 244 | ?component_will_mount:(this:'a Js.t -> unit) -> 245 | ?component_did_mount:(this:'a Js.t -> unit) -> 246 | ?component_will_receive_props:(this:'a Js.t -> next_prop:'g Js.t -> unit) -> 247 | ?should_component_update:(this:'a Js.t -> 248 | next_prop:'g Js.t -> 249 | next_state:'h Js.t -> bool Js.t) -> 250 | ?component_will_update:(this:'a Js.t -> 251 | next_prop:'g Js.t -> next_state:'h Js.t -> unit) -> 252 | ?component_did_update:(this:'a Js.t -> 253 | prev_prop:'i Js.t -> prev_state:'j Js.t -> unit) -> 254 | ?component_will_unmount:(this:'a Js.t -> unit) -> 255 | (this:'a component_api -> Low_level_bindings.react_element Js.t) -> 256 | ('a, 'b, 'c, 'd, 'f, 'g, 'h, 'i, 'j, 'k, 'e) class_spec 257 | 258 | (** Create a ReactElement directly where you specify the element 259 | for the position argument *) 260 | val create_element : 261 | ?element_opts:element_spec -> 262 | string -> tree -> Low_level_bindings.react_element Js.t 263 | 264 | (** Create a ReactElement given a ReactClass *) 265 | val create_element_from_class : 266 | Low_level_bindings.react_class Js.t -> 267 | Low_level_bindings.react_element Js.t 268 | 269 | (** Create a React factory function *) 270 | val create_factory : 271 | Low_level_bindings.react_class Js.t -> 272 | props:'a Js.t -> Low_level_bindings.react_element Js.t 273 | 274 | (** Create a ReactClass, pass result of make_class_spec to this 275 | function *) 276 | val create_class : 277 | (< isMounted : bool Js.t Js.meth; .. >, 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 278 | 'j) 279 | class_spec -> Low_level_bindings.react_class Js.t 280 | 281 | (** Short cut for making a ReactElement given a {b class_spec} *) 282 | val elem_from_spec : 283 | (< isMounted : bool Js.t Js.meth; .. >, 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 284 | 'j) 285 | class_spec -> Low_level_bindings.react_element Js.t 286 | 287 | (** ReactDOM.render, pass the ReactElement and a Dom node to render in *) 288 | val render : 289 | react_elem:Low_level_bindings.react_element Js.t -> 290 | #Dom_html.element Js.t -> 'a 291 | 292 | (** Top level handle on React *) 293 | val react : 294 | < isMounted : bool Js.t Js.meth; .. > component_api 295 | Low_level_bindings.react Js.t 296 | 297 | (** Top level handle on ReactDOM *) 298 | val react_dom : 'a Js.t 299 | 300 | (** Top level handle on react server, a function call since this only 301 | makes sense on nodejs. *) 302 | val react_dom_server : unit -> Low_level_bindings.react_dom_server Js.t 303 | 304 | (** React.DOM *) 305 | module DOM : 306 | sig 307 | (** All the tags recognized by ReactJS *) 308 | type tag = [`a | `abbr | `address | `area | `article | `aside | `audio | 309 | `b | `base | `bdi | `bdo | `big | `blockquote | `body | 310 | `br | `button | `canvas | `caption | `cite | `code | 311 | `col | `colgroup | `data | `datalist | `dd | `del | 312 | `details | `dfn | `dialog | `div | `dl | `dt | `em | 313 | `emded | `fieldset | `figcaption | `figure | `footer | 314 | `form | `h1 | `h2 | `h3 | `h4 | `h5 | `h6 | `head | `header | 315 | `hgroup | `hr | `html | `i | `iframe | `img | `input | 316 | `ins | `kbd | `keygen | `label | `legend | `li | `link | 317 | `main | `map | `mark | `menu | `menuitem | `meta | `meter | 318 | `nav | `noscript | `object_ | `ol | `optgroup | `option | `output 319 | | `p | `param | `picture |`pre | `progress | `q | `rp | `rt 320 | | `ruby | `s | `samp | `script | `section | `select | 321 | `small | `source | `span |`strong | `style | `sub | 322 | `summary | `sup | `table | `tbody | `td | `textarea | 323 | `tfoot | `th | `thead | `time | `title | `tr | `track | 324 | `u | `ul | `var | `video | `wbr | `circle | `clipPath | 325 | `defs | `ellipse | `g | `image | `line | `linearGradient 326 | | `mask | `path | `pattern | `polygon | `polyline | 327 | `radialGradient | `rect | `stop | `svg | `text | `tspan ] 328 | 329 | val string_of_tag : tag -> string 330 | type 'a elem_spec = 'a Js.t 331 | constraint 'a = < className : Js.js_string Js.t Js.readonly_prop; .. > 332 | 333 | (** Create your ReactElement using this helper function. Since 334 | className is such a common operation, you can provide a 335 | short cut for it with ~class_name instead of object%js val 336 | className = !*"foo" end. If elem_spec and class_name are 337 | both provided then class_name will mutate elem_spec with 338 | className := class_name 339 | 340 | If no tag is provided then defaults to making a `div. 341 | *) 342 | val make : 343 | ?elem_spec:'a javascript_object -> 344 | ?class_name:string -> 345 | ?tag:tag -> 346 | tree -> 347 | Low_level_bindings.react_element Js.t 348 | end 349 | 350 | (** Helper functions to create commonly needed components *) 351 | module Common_components : sig 352 | 353 | (** Creates a `link element, offers you a chance to add other 354 | attributes to the stylesheet. *) 355 | val stylesheet : ?custom_opts:'a Js.t -> href:string -> unit -> react_node 356 | 357 | (** Create a ahrefs component, href is optional and defaults to "" 358 | {b ahref "Some content"} *) 359 | val ahref : ?href:string -> string -> react_node 360 | 361 | end 362 | -------------------------------------------------------------------------------- /src/reactjs.mllib: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: fce39bef5e4bd9f96c8757917a003a35) 3 | Reactjs 4 | # OASIS_STOP 5 | -------------------------------------------------------------------------------- /static/README_base.markdown: -------------------------------------------------------------------------------- 1 | ReactJS bindings in OCaml 2 | ========================= 3 | 4 | These are my bindings to ReactJS, by my count this is the global sixth 5 | attempt at binding to React and my own second attempt, its kind of 6 | hard. 7 | 8 | Installation 9 | ============ 10 | 11 | Right now you can install with: 12 | 13 | ```shell 14 | $ opam install reactjs 15 | ``` 16 | 17 | To get the development version, do: 18 | 19 | ```shell 20 | $ opam pin add -y reactjs git@github.com:fxfactorial/ocaml-reactjs.git 21 | ``` 22 | 23 | The bindings should work on `node` or in the browser, both will assume 24 | that `React`, and `ReactDOM` exist (on node they will do the 25 | appropriate `require`, node side will also try to load the npm package 26 | `react-dom`) 27 | 28 | Documentation 29 | ============= 30 | 31 | See this blog [post](http://hyegar.com/2016/07/17/js-of-ocaml-usage/) 32 | to get a better understanding of OCaml typing of JavaScript objects 33 | and such, (explains the `##` syntax extension). 34 | 35 | The `mli` is commented and the doc strings should come up for you with 36 | `merlin`. I also recommend using `ocp-browser`, this is a program that 37 | is installed via `opam install ocp-index` and it gives you a nice high 38 | level way to see the API: 39 | 40 | ![img](./static/reactjs_ocp_browser.gif) 41 | 42 | You can also do: `make doc` in the repo, that will create a directory 43 | called `api.docdir` and in there you open the `index.html` for pretty 44 | generated documentation. 45 | 46 | If you're okay with doing: `brew cask install wkhtmltopdf`, then you 47 | can get a PDF generated from the OCaml documentation. do 48 | 49 | ```shell 50 | $ make generate_pdf 51 | ``` 52 | 53 | And then the `reactjs_bindings.pdf` will be built in the root of the 54 | directory. It should look like: 55 | 56 | ![img](./static/reactjs_doc_pdf.gif) 57 | 58 | Contributing 59 | ============ 60 | 61 | Contributions of any kind are appreciated. If you're updating this 62 | readme, then be update `static/README_base.markdown` and then run 63 | `make readme`. 64 | 65 | For the source code itself, be aware that it uses some more advanced 66 | features of the type system and can be mental pain. I haven't exposed 67 | everything of `React` yet, and the library can still be made more 68 | strongly typed. 69 | 70 | Right now a `JSX` like ppx is needed since writing out the `Text`, 71 | `Elem` variants can be more strain on the brain and `JSX` lets you see 72 | the structure of the element you're making more easily. 73 | 74 | Before opening a PR, be sure to test all the existing examples. You 75 | can build them all at once from the `reactjs_based_examples` directory 76 | with `make all_examples`, or `make -C reactjs_based_examples 77 | all_examples` from the root directory. 78 | 79 | More examples added are always appreciated and you can do it by: 80 | 81 | ```shell 82 | $ cd reactjs_based_examples 83 | $ cp -R basic your_new_example 84 | ``` 85 | 86 | and add your example's directory name to the `Makefile`'s `dirs` 87 | variable in the root of the project, 88 | 89 | around line 40s, `dirs := basic basic-click-counter quadratic`. 90 | 91 | Examples 92 | ======== 93 | 94 | These examples should be familiar and are autogenerated into this 95 | README from under the `reactjs_based_examples` dir. 96 | 97 | Check the wiki for common FAQs, compile any example with: 98 | 99 | ```shell 100 | $ ocamlfind ocamlc -package reactjs -linkpkg code.ml 101 | $ js_of_ocaml a.out -o code.js 102 | ``` 103 | 104 | Also see 105 | [ocaml-mailing-list](https://github.com/fxfactorial/ocaml-mailing-list) 106 | for more example source code, include how to render on the server with 107 | `nodejs`. 108 | 109 | 110 | [//]: # "Do not write anything below here, the code examples will be appended" 111 | -------------------------------------------------------------------------------- /static/add_to_read_me.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | example_to_readme () { 6 | local read_me=README.md 7 | printf '\n![img](./static/%s.gif)\n' $(basename $(dirname $1)) >> $read_me 8 | printf '\n# %s\n' $(dirname $1) >> $read_me 9 | printf '\n```ocaml\n' >> $read_me 10 | cat $1 >> $read_me 11 | printf '```\n' >> $read_me 12 | } 13 | 14 | example_to_readme "$1" 15 | -------------------------------------------------------------------------------- /static/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | font-size: 15px; 5 | line-height: 1.7; 6 | margin: 0; 7 | padding: 30px; 8 | } 9 | 10 | a { 11 | color: #4183c4; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | 19 | code { 20 | background-color: #f8f8f8; 21 | border: 1px solid #ddd; 22 | border-radius: 3px; 23 | font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; 24 | font-size: 12px; 25 | margin: 0 2px; 26 | padding: 0px 5px; 27 | } 28 | 29 | h1, h2, h3, h4 { 30 | font-weight: bold; 31 | margin: 0 0 15px; 32 | padding: 0; 33 | } 34 | 35 | h1 { 36 | border-bottom: 1px solid #ddd; 37 | font-size: 2.5em; 38 | font-weight: bold; 39 | margin: 0 0 15px; 40 | padding: 0; 41 | } 42 | 43 | h2 { 44 | border-bottom: 1px solid #eee; 45 | font-size: 2em; 46 | } 47 | 48 | h3 { 49 | font-size: 1.5em; 50 | } 51 | 52 | h4 { 53 | font-size: 1.2em; 54 | } 55 | 56 | p, ul { 57 | margin: 15px 0; 58 | } 59 | 60 | ul { 61 | padding-left: 30px; 62 | } 63 | -------------------------------------------------------------------------------- /static/basic-click-counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxfactorial/ocaml-reactjs/908377849d813f55df9f14715b50d2bc45a8304a/static/basic-click-counter.gif -------------------------------------------------------------------------------- /static/basic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxfactorial/ocaml-reactjs/908377849d813f55df9f14715b50d2bc45a8304a/static/basic.gif -------------------------------------------------------------------------------- /static/quadratic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxfactorial/ocaml-reactjs/908377849d813f55df9f14715b50d2bc45a8304a/static/quadratic.gif -------------------------------------------------------------------------------- /static/react-dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ReactDOM v15.0.2 3 | * 4 | * Copyright 2013-present, Facebook, Inc. 5 | * All rights reserved. 6 | * 7 | * This source code is licensed under the BSD-style license found in the 8 | * LICENSE file in the root directory of this source tree. An additional grant 9 | * of patent rights can be found in the PATENTS file in the same directory. 10 | * 11 | */ 12 | // Based off https://github.com/ForbesLindesay/umd/blob/master/template.js 13 | ;(function(f) { 14 | // CommonJS 15 | if (typeof exports === "object" && typeof module !== "undefined") { 16 | module.exports = f(require('react')); 17 | 18 | // RequireJS 19 | } else if (typeof define === "function" && define.amd) { 20 | define(['react'], f); 21 | 22 | //