• Stars
    star
    118
  • Rank 290,174 (Top 6 %)
  • Language
    OCaml
  • License
    BSD 3-Clause "New...
  • Created over 8 years ago
  • Updated almost 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Write OCaml, use ReactJS. : DEPRECATED: USE REASONREACT

ReactJS bindings in OCaml

These are my bindings to ReactJS, by my count this is the global sixth attempt at binding to React and my own second attempt, its kind of hard.

Installation

Right now you can install with:

$ opam install reactjs

To get the development version, do:

$ opam pin add -y reactjs [email protected]:fxfactorial/ocaml-reactjs.git

The bindings should work on node or in the browser, both will assume that React, and ReactDOM exist (on node they will do the appropriate require, node side will also try to load the npm package react-dom)

Documentation

See this blog post to get a better understanding of OCaml typing of JavaScript objects and such, (explains the ## syntax extension).

The mli is commented and the doc strings should come up for you with merlin. I also recommend using ocp-browser, this is a program that is installed via opam install ocp-index and it gives you a nice high level way to see the API:

img

You can also do: make doc in the repo, that will create a directory called api.docdir and in there you open the index.html for pretty generated documentation.

If you're okay with doing: brew cask install wkhtmltopdf, then you can get a PDF generated from the OCaml documentation. do

$ make generate_pdf

And then the reactjs_bindings.pdf will be built in the root of the directory. It should look like:

img

Contributing

Contributions of any kind are appreciated. If you're updating this readme, then be update static/README_base.markdown and then run make readme.

For the source code itself, be aware that it uses some more advanced features of the type system and can be mental pain. I haven't exposed everything of React yet, and the library can still be made more strongly typed.

Right now a JSX like ppx is needed since writing out the Text, Elem variants can be more strain on the brain and JSX lets you see the structure of the element you're making more easily.

Before opening a PR, be sure to test all the existing examples. You can build them all at once from the reactjs_based_examples directory with make all_examples, or make -C reactjs_based_examples all_examples from the root directory.

More examples added are always appreciated and you can do it by:

$ cd reactjs_based_examples
$ cp -R basic your_new_example

and add your example's directory name to the Makefile's dirs variable in the root of the project,

around line 40s, dirs := basic basic-click-counter quadratic.

Examples

These examples should be familiar and are autogenerated into this README from under the reactjs_based_examples dir.

Check the wiki for common FAQs, compile any example with:

$ ocamlfind ocamlc -package reactjs -linkpkg code.ml
$ js_of_ocaml a.out -o code.js

Also see ocaml-mailing-list for more example source code, include how to render on the server with nodejs.

img

reactjs_based_examples/basic

let example_application = Reactjs.(
    make_class_spec
      ~initial_state:(fun ~this ->
          print_endline "Initial state called";
          object%js end
        )
      ~default_props:(fun ~this ->
          print_endline "Default props called";
          object%js end
        )
      ~component_will_mount:(fun ~this -> print_endline "Component will mount")
      ~component_did_mount:(fun ~this -> print_endline "Component did mount")
      ~component_will_receive_props:(fun ~this ~next_prop ->
          print_endline "Component will receive props"
        )
      ~should_component_update:(fun ~this ~next_prop ~next_state ->
          print_endline "Should component update called";
          Js.bool true
        )
      ~component_will_update:(fun ~this ~next_prop ~next_state ->
          print_endline "Component will update"
        )
      ~component_did_update:(fun ~this ~prev_prop ~prev_state ->
          print_endline "Component did update"
        )
      ~component_will_unmount:(fun ~this -> print_endline "Component about to unmount")
      (fun ~this ->
         let elapsed = Js.math##round this##.props##.elapsed /. 100.0 in
         let seconds = elapsed /. 10.0 in
         let message = Printf.sprintf
             "React has been successfully running for %f seconds" seconds
         in
         DOM.make ~tag:`p [Text message]
      )
    |> create_class
  )

let _ = Reactjs.(
    let example_app_factory = create_factory example_application in
    let start = (new%js Js.date_now)##getTime in
    set_interval
      ~f:(fun () ->
          try
            let react_elem = example_app_factory ~props:(object%js
                val elapsed = (new%js Js.date_now)##getTime -. start
              end)
            in
            render ~react_elem (get_elem ~id:"container")
          (* Get OCaml exception handling! *)
          with Js.Error e ->
            Firebug.console##log e
        ) ~every:100.0
  )

img

reactjs_based_examples/basic-click-counter

let counter = Reactjs.(
    make_class_spec
      ~initial_state:(fun ~this -> (object%js val count = 0 end))
      ~component_will_mount:(fun ~this ->
          print_endline "Component about to mount"
        )
      (fun ~this -> let open Reactjs.Infix in
        let handle_click = !@(fun () ->
            this##setState (object%js val count = this##.state##.count + 1 end))
        in
         DOM.make
           ~elem_spec:(object%js
             val onClick = handle_click
           end)
           ~tag:`button
           [Text (Printf.sprintf
                    "Click me, number of clicks: %d" this##.state##.count)])
    |> create_class
  )

let () = Reactjs.(
    render
      ~react_elem:(create_element_from_class counter)
      (get_elem ~id:"container")
  )

img

reactjs_based_examples/quadratic

open StdLabels

let quadratic_calculator = Reactjs.(
    make_class_spec
      ~initial_state:(fun ~this -> object%js
                       val a = 1.0 val b = 3.0 val c = -3.0
                     end)
      (fun ~this -> let open Infix in
        let handle_input_change =
          fun ~key event ->
            let new_state =
              ([(key,
                 event##.target##.value |> Js.parseFloat |> Js.number_of_float )] >>>
               object%js end)
            in
            this##setState new_state
        in
        let (a, b, c) = this##.state##.a, this##.state##.b, this##.state##.c in
        let root = Js.math##sqrt ((Js.math##pow b 2.0) -. 4.0 *. a *. c) in
        let denominator = 2.0 *. a in
        let (x1, x2) = (-.b +. root) /. denominator, (-.b -. root) /. denominator in
        let input_label ~key init_value = DOM.(
            make ~tag:`label
              [Text (Printf.sprintf "%s: " key);
               Elem (make ~elem_spec:(object%js
                       val type_ = !*"number"
                       val value = !^init_value
                       val onChange = handle_input_change ~key
                     end) ~tag:`input [])]
          )
        in
        let label_row l = l |> List.map ~f:(fun (key, value) ->
            [Elem (input_label ~key value); Elem (DOM.make ~tag:`br [])]
          ) |> List.flatten
        in
        let equation_row = DOM.(
            [Elem (make ~tag:`em [Text "ax"]); Elem (make ~tag:`sup [Text "2"]);
             Text " + "; Elem (make ~tag:`em [Text "bx"]); Text " + ";
             Elem (make ~tag:`em [Text "c"]); Text " = 0"])
        in
        DOM.(make ~tag:`div
               [Elem (make ~tag:`strong equation_row );
                Elem (make ~tag:`h4 [Text "Solve for ";
                                     Elem (make ~tag:`em [Text "x"])]);
                Elem (make ~tag:`p
                        (label_row [("a", a); ("b", b); ("c", c)] @
                         [Text "x: ";
                          Elem (make ~tag:`strong
                                  [Text (Printf.sprintf "%f %f" x1 x2)])]))
               ]))
    |> create_class
  )

let () =
  Reactjs.(render
             ~react_elem:(create_element_from_class quadratic_calculator)
             (get_elem ~id:"container"))

img

reactjs_based_examples/todomvc

type task_id = int

type task =
  {
    id: task_id;
    label: string;
    completed: bool;
  }

type tab = All | Active | Completed
  
type state = {
  editing: task_id option;
  tasks: task list;
  tab: tab;
}

type history =
  state list

let initial_state =
  let tasks = [{id = 0;
                label = "Initial task";
                completed = false};
               {id = 1;
                label = "Completed task";
                completed = true};
               {id = 2;
                label = "Final task";
                completed = true};]
  in
  ref {editing = None;
       tasks = tasks;
       tab = All}

let (histories : history ref) =
  ref []

let observe _old_state new_state =
  Printf.printf "Mutation observed: %d\n" (List.length new_state.tasks)
  
let observer =
  ref (fun _old_state _new_state -> Printf.printf "Placeholder observer used")
           
let swap (ref : state ref) f =
  let old_state = !ref in
  ref := f ref;
  let new_state = !ref in
  ignore(!observer old_state new_state);
  histories := List.append [new_state] !histories;
  Printf.printf "History count: %d\n" (List.length !histories);
  ()

let set_tab state tab =
  {state with tab = tab}

let random_el arr =
    let n = Random.int (Array.length arr) in
    Array.get arr n;;

let colors =
  [|"blue"; "green"; "pink"; "purple"; "white"; "gray"|]

let random_color () =
  random_el colors

let todo_item (task : task) =
  let open Reactjs in
  let open Infix in
  make_class_spec
    (fun ~this ->
       ignore(this);
       DOM.make ~tag:`li
         ~elem_spec:(object%js
           val key = !*("li-todo-input-" ^ (string_of_int task.id))
       end)
         (if (match !initial_state.editing with
              | None -> false
              | Some id -> id = task.id) then
            [Elem (DOM.make ~tag:`input ~elem_spec:(object%js
                     val key = !*("todo-input-" ^ (string_of_int task.id))
                     val type_ = !*"text"
                     val style = (object%js val display = "block" val backgroundColor = !*("white") end)
                     val defaultValue = task.label
                     val onChange = (fun event ->
                         this##setState event##.target##.value |> Js.to_string
                         (* swap initial_state (fun state -> *)
                         (* let new_tasks = List.map (fun t -> *)
                         (*     if t.id = task.id then *)
                         (*       {t with label = event##.target##.value |> Js.to_string } *)
                         (*     else *)
                         (*       t) !state.tasks in *)
                         (* {!state with tasks = new_tasks}) *))
                     val onKeyUp = (fun event ->
                         Printf.printf "Key: %d\n" event##.which;
                         match event##.which with
                         | 13 -> swap initial_state (fun state -> 
                         let new_tasks = List.map (fun t ->
                             if t.id = task.id then
                               {t with label = event##.target##.value |> Js.to_string }
                             else
                               t) !state.tasks in
                           let _tt = List.find (fun t -> t.id = task.id) new_tasks in
                           Printf.printf "Task label after updating: %s\n" _tt.label;
                           {!state with 
                            tasks = new_tasks;
                            editing = None})
                         | 27 -> swap initial_state (fun state -> {!state with editing = None})
                         | _ ->  () 

                       )
                     val className = Js.string "edit"
                   end)
                     [])]
          else 
            [Elem (DOM.make 
                     ~tag:`div
                     ~elem_spec:(object%js
                       val className = "view"
                       val onDoubleClick = (fun _ -> swap initial_state (fun state ->
                           Printf.printf "Now editing %d\n"task.id;
                           {!state with editing = Some task.id}
                         ))
                     end)
                     [Elem (DOM.make ~tag:`input ~elem_spec:(object%js
                              val type_ = !*"checkbox"
                              val onClick = (fun _ -> swap initial_state (fun state ->
                                  let new_tasks = List.map (fun t -> 
                                      if t.id = task.id then
                                        {t with completed = not t.completed}
                                      else
                                        t
                                    ) !state.tasks in
                                  {!state with tasks = new_tasks}
                                ))
                              val checked = Js.string (if task.completed then "checked" else "")
                              val className = Js.string "toggle"
                            end)
                              []);
                      Elem (DOM.make 
                              ~tag:`label
                              [Text ((match !initial_state.editing with
                                   | None -> ""
                                   | Some editing -> if editing = task.id then "Editing: " else "") ^  task.label);]);
                      Elem (DOM.make 
                              ~tag:`button
                              ~elem_spec:(object%js
                                val onClick = (fun _ -> swap initial_state (fun state ->
                                    let new_tasks = List.filter (fun t -> 
                                        t.id != task.id
                                      ) !state.tasks in
                                    {!state with tasks = new_tasks}
                                  ))
                                val className = !*"destroy"
                              end)
                              [])])]))
  |> create_class

let todo_input () =
  let open Reactjs in
  let open Infix in
  make_class_spec
    (fun ~this ->
       ignore(this);
       DOM.make ~tag:`input
         ~elem_spec:(object%js
           val className = !*"new-todo"
           val placeholder = !*"What needs to be done"
           val autofocus = !*"true"
           val onKeyDown = (fun event ->
               match event##.which with
               | 13 -> swap initial_state (fun state ->
                   let id = Random.int 64000 in
                   Printf.printf "Updating new task...\n";
                   Firebug.console##log event##.target##.value;
                   let new_tasks = List.append !state.tasks [{id = id; label = event##.target##.value |> Js.to_string; completed = false}] in
                   Printf.printf "New tasks updated\n";
                   {!state with tasks = new_tasks}
                 )
               | _ -> ())

         end)
         [])
  |> create_class

let root app =
  let open Reactjs in
  let open Infix in
  let tasks = List.filter (fun task ->
      Printf.printf "Checking if task matches: %d\n" task.id;
      match app.tab with
      | All -> true
      | Active -> not task.completed
      | Completed -> task.completed
    ) app.tasks in
  make_class_spec
    (fun ~this ->
       ignore(this);
       DOM.make ~tag:`section
         ~elem_spec:(object%js
           val className = !*"todoapp"
         end)
         [Elem (DOM.make ~tag:`header
                  ~elem_spec:(object%js
                    val className = !*"header"
                  end)
                  [Elem (DOM.make ~tag:`h1 [Text "todos"]);
                   Elem (create_element_from_class (todo_input ()))]);
          Elem (DOM.make ~tag:`section
                  ~elem_spec:(object%js
                    val className = !*"main"
                  end) [Elem (DOM.make ~tag:`input
                                ~elem_spec:([("type", Js.string "checkbox");
                                             ("className", Js.string "toggle-all")]
                                            >>> object%js end) []);
                        Elem (DOM.make ~tag:`label
                                ~elem_spec:([("htmlFor", Js.string "toggle-all");]
                                            >>> object%js end)
                                [Text "Mark all as complete"]);
                        Elem (DOM.make ~tag:`ul
                                ~elem_spec:(object%js
                                  val className = !*"todo-list"
                                end)
                                (List.map (fun task -> Elem (create_element_from_class (todo_item task))) tasks))]);
          Elem (DOM.make ~tag:`footer
                  ~elem_spec:(object%js
                    val className = !*"footer"
                  end)
                  [Elem (DOM.make ~tag:`span ~elem_spec:(object%js
                           val className = !*"todo-count"
                         end) [Text (string_of_int (List.length tasks))]);
                   Elem (DOM.make ~tag:`ul ~elem_spec:(object%js
                           val className = !*"filters"
                         end) 
                           [Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js
                                                             val href = !*"#/"
                                                             val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state All))
                                                             val className = (match app.tab with
                                                                 | All -> !*"selected"
                                                                 | _ -> !*"")
                                                           end) [Text "All"])]);
                            Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js
                                                             val href = !*"#/active"
                                                             val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state Active))
                                                             val className = (match app.tab with
                                                                 | Active -> !*"selected"
                                                                 | _ -> !*"")
                                                           end) [Text "Active"])]);
                            Elem (DOM.make ~tag:`li [Elem (DOM.make ~tag:`a ~elem_spec:(object%js
                                                             val href = !*"#/completed"
                                                             val onClick = (fun _ -> swap initial_state (fun state -> set_tab !state Completed))
                                                             val className = (match app.tab with
                                                                 | Completed -> !*"selected"
                                                                 | _ -> !*"")
                                                           end) [Text "Completed"])])]);
                   Elem (DOM.make ~tag:`button ~elem_spec:(object%js
                           val className = !*"clear-completed"
                           val onClick = (fun _ -> swap initial_state (fun state ->
                               let new_tasks = List.filter (fun t -> 
                                   not t.completed
                                 ) !state.tasks in
                               {!state with tasks = new_tasks}
                             ))
                         end) [Text "Clear completed"])
                  ]);
         ])
  |> create_class

let dump_state _ =
  List.iter (fun task -> print_endline (task.label ^ " [" ^ (string_of_bool task.completed) ^ "]")) !initial_state.tasks

let () =
  Js.Unsafe.global##.dump_state := dump_state

let () =
  Js.Unsafe.global##.get_state := (fun _ -> !initial_state)

let first_render =
  ref true

let wrap_root state_ref =
  let open Reactjs in
  let open Infix in
  make_class_spec
    (fun ~this ->
       ignore(this);
       DOM.make ~tag:`div
         ~elem_spec:(object%js
           val className = !*"overreact"
         end)
         [Elem (create_element_from_class (root !state_ref));
         ])
  |> create_class

let render_root state =
  let open Reactjs in
  (match !state.tab with
   | All -> Printf.printf "Current tab: All\n"
   | Active -> Printf.printf "Current tab: Active\n"
   | Completed -> Printf.printf "Current tab: Completed\n");
  (match !first_render with
   | _ -> let root_el_ = render
                 ~react_elem:(create_element_from_class (wrap_root state))
                 (get_elem ~id:"container") in
     first_render := false;
     Js.Unsafe.global##.example := root_el_;
   (* | false -> Js.Unsafe.global##.example##forceUpdate *)
  );
  Firebug.console##log Js.Unsafe.global##.example;
  ()

let replay_history =
  (fun _ ->
      let rec render_next_state = (fun states ->
          match (List.length states) with
          | 0 -> ()
          | _ -> let next_state = List.hd states in
            render_root (ref next_state);
            ignore(Dom_html.window##setTimeout (Js.wrap_callback (fun () -> render_next_state (List.tl states); ())) 500.);
        ()) in
      render_next_state (List.rev !histories)
    )

let () =
  Js.Unsafe.global##.replayHistory := replay_history;
  let app_observer = (fun (_old_state : state) (_new_state : state) -> render_root initial_state) in
  observer := app_observer;
  swap initial_state (fun state -> state := {!state with tab = All}; !state);

More Repositories

1

liquidation-bot-fall-2020

My old liquidation bot - doesn't compile, just learn from it
Go
347
star
2

an-ocaml-adoption-manifesto

Getting Widespread OCaml Adoption
86
star
3

ocaml-nodejs

🆒 Write OCaml, run on node.
OCaml
82
star
4

cheap-name-

find a cheap selector name, save gas on external calls
Go
78
star
5

ocaml-electron

🔦 Write OCaml, run on Electron.
OCaml
78
star
6

opam-ios

OCaml cross-compiler for iOS
78
star
7

podge

💰 A Hodgepodge of functions for living in the OCaml ecosystem
OCaml
61
star
8

defi-abigen

already abigen code for main defi projects
Go
59
star
9

run-evm-code

run ABI encoded data against the ethereum blockchain
Go
57
star
10

wooyun.github.io

HTML
48
star
11

brozip

⚡ CLI to concurrently compress, decompress using Brotli algorithm
OCaml
40
star
12

bs-expo

ReasonML bindings to the Expo SDK
OCaml
38
star
13

bsc-evmone

evmone inside bsc, start from here and adapt
Go
34
star
14

ocaml-javascriptcore

Create, Control, Execute JavaScript in OCaml. Think of the possibilities.
C++
30
star
15

ocaml-graphql

OCaml implementation of Facebook's graphql, library and server.
OCaml
21
star
16

ocaml-libgit2

🔨 OCaml Bindings to libgit2 based on Ctypes
OCaml
20
star
17

ethereum-re

Access the ethereum network with pure ReasonML, use with native code or with type safe JavaScript
OCaml
20
star
18

ocaml-emoji

Emojis in OCaml, use them in your CLIs or whatever
OCaml
20
star
19

ocaml-starterkit

Prep a new switch instantly.
20
star
20

valentine

HTML validation on command line
OCaml
18
star
21

lambda-invaders

👽 OCaml based version of space-invaders
OCaml
14
star
22

ocaml-libssh

💻 OCaml bindings to libssh
C
14
star
23

hayots

OCaml/Reason native IDE on OS X
Objective-C
12
star
24

woof-new-contract

Go
11
star
25

ocaml-stripe

OCaml Library for talking to Stripe
OCaml
10
star
26

eth-peers-tui

Go
10
star
27

ocaml-via-node

Install OCaml and opam on your machine via npm
JavaScript
10
star
28

ocaml-plist

Create, parse Apple Plists, plays nicely with Yojson
Objective-C
10
star
29

silicondzor

The Armenian tech community, tech commentary, bug bounty, jobs, calendar.
JavaScript
10
star
30

hump

Get an OCaml project up and running instantly.
C++
10
star
31

go-to-cuda

starter repo to do golang with cuda and C++
Cuda
10
star
32

ocaml-qjsengine

Evaluate and control JavaScript values seamlessly from within OCaml
C++
9
star
33

tv_js

OCaml bindings to TVML, aka Apple TV JavaScript
CSS
9
star
34

bs-solc

Compile solidity with Reason
OCaml
8
star
35

contract-eval-web

Go
8
star
36

ocaml-objc

Play with Objective-C in OCaml
Objective-C
7
star
37

tallgeese

🌊 Native Cocoa GUI for SSH sessions with OCaml powered backend
Objective-C
7
star
38

runa

js_of_ocaml utility library, common functionality for your js_of_ocaml programs.
OCaml
7
star
39

JavaScriptCore-cpp

An attempt at a C++ interface to JavaScriptCore
C++
7
star
40

ocaml-corecount

Simple way to get a count of cores of the machine and expose to OCaml via C++
OCaml
7
star
41

ocaml-maxminddb

🌏 OCaml Bindings to libmaxminddb, think GeoIP2
OCaml
7
star
42

ocaml-radare2

OCaml interface to radare2, use r2 from within OCaml, much more fun
OCaml
7
star
43

re-closure

Some wacky shit yo, calling google closure compiler via Java & C++ and getting in Reason to optimize JavaScript
Java
6
star
44

ocaml-express

OCaml implementation of express
6
star
45

ocaml-mailing-list

Program that generates OCaml mailing list
OCaml
6
star
46

bs-web3

bucklescript bindings to web3 IGNORE THIS: USE https://github.com/fxfactorial/ethereum-re
OCaml
5
star
47

ml-js-repl

A REPL for both OCaml and JavaScript
OCaml
5
star
48

react-server-side

JavaScript
4
star
49

fxfactorial.github.io

Source code for my website, emacs all the things.
HTML
4
star
50

accumulator

RSA accumulator in go lang
Go
4
star
51

silicondzor-mobile

Expo app for silicondzor mobile, one stop Armenian tech central
JavaScript
4
star
52

lets_chat

OCaml
3
star
53

ocaml-java-scriptengine

Evaluate JavaScript code in OCaml by using Java
C++
3
star
54

berber

Command line tool to view OCaml documentation
OCaml
3
star
55

visor-core

The DeFi protocol for Active Liquidity Management. Building on Uniswap v3.
Solidity
3
star
56

ez-lectures

Easily make pretty lecture notes/Presentations, especially for coding. Just provide a JS file and get a self contained HTML file
JavaScript
3
star
57

react-example-with-async-await-babel

JavaScript
2
star
58

emacsd

my emacs .d directory
Emacs Lisp
2
star
59

ocaml_on_android

Working beginning for starting OCaml on Android
Makefile
2
star
60

sprung

JavaScript
2
star
61

ios-hacker-buildkit

A better way to build for jailbroken iPhones
C++
2
star
62

delegation

JavaScript
2
star
63

evm-dev-station

Hands on Blockchain
2
star
64

just-chat

Terminal Chatting with anyone on Local network.
OCaml
1
star
65

ocamlxarm

1
star
66

dga-padcrypt-ocaml

OCaml implementation of DGA Padcrypt
OCaml
1
star
67

ent

a JavaScript node like thing but for iOS
OCaml
1
star
68

bs-react-navigation

1
star
69

kernel_dev

Miscellaneous Kernel-Dev Stuff for OS X / iOS
C++
1
star
70

ece4305-project

Project files for ECE 4305, software-defined radio.
MATLAB
1
star
71

my-resume

OCaml
1
star
72

stay_aware

OCaml/Objective-C application that lets you know who else is on the network with you.
C++
1
star
73

sdefl

Small/Simple inflate/deflate implementation in ~300 LoC of C
C
1
star
74

validator-mgmt

Rust
1
star
75

bls-key-agg

Example usage of BLS aggregate keys in the harmony blockchain
Go
1
star
76

hye-life

Source code of the Arts and Culture calendar of Armenia
JavaScript
1
star
77

ocamlios64

OCaml
1
star