├── .gitignore
├── LICENSE
├── README.md
├── basics
├── basic-component
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ │ └── public
│ │ │ ├── index.html
│ │ │ └── js
│ │ │ └── compiled
│ │ │ └── app.js
│ └── src
│ │ └── cljs
│ │ └── basic_component
│ │ └── core.cljs
├── component-level-state
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ │ └── public
│ │ │ ├── index.html
│ │ │ └── js
│ │ │ └── compiled
│ │ │ └── app.js
│ └── src
│ │ └── cljs
│ │ └── component_level_state
│ │ └── core.cljs
└── cursors
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ └── public
│ │ ├── index.html
│ │ └── js
│ │ └── compiled
│ │ └── app.js
│ └── src
│ └── cljs
│ └── cursors
│ └── core.cljs
├── logo-rounded.jpg
├── old-recipes
├── mermaid
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ └── src
│ │ ├── clj
│ │ └── mermaid
│ │ │ └── handler.clj
│ │ └── cljs
│ │ └── mermaid
│ │ └── core.cljs
├── nvd3
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ └── src
│ │ ├── clj
│ │ └── nvd3
│ │ │ └── handler.clj
│ │ └── cljs
│ │ └── nvd3
│ │ └── core.cljs
├── test-example-with-ReactTestUtils
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ ├── src
│ │ └── cljs
│ │ │ └── test_example
│ │ │ └── core.cljs
│ └── test
│ │ └── cljs
│ │ └── test_example
│ │ ├── core_test.cljs
│ │ └── runner.cljs
├── test-example-with-test-check
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ ├── src
│ │ └── cljs
│ │ │ └── test_example
│ │ │ └── core.cljs
│ └── test
│ │ └── cljs
│ │ └── test_example
│ │ ├── core_test.cljs
│ │ └── runner.cljs
└── test-example
│ ├── README.md
│ ├── project.clj
│ ├── resources
│ └── public
│ │ └── index.html
│ ├── src
│ └── cljs
│ │ └── test_example
│ │ └── core.cljs
│ └── test
│ └── cljs
│ └── test_example
│ ├── core_test.cljs
│ └── runner.cljs
└── recipes
├── ReactCSSTransitionGroup
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── animation
│ └── core.cljs
├── add-routing
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── add_routing
│ └── core.cljs
├── autocomplete
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── autocomplete
│ └── core.cljs
├── bootstrap-datepicker
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── bootstrap_datepicker
│ └── core.cljs
├── bootstrap-modal
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── bootstrap_modal
│ └── core.cljs
├── canvas-fills-div
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── canvas_fills_div
│ └── core.cljs
├── compare-argv
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── compare_argv
│ └── core.cljs
├── data-tables
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── data_tables
│ └── core.cljs
├── draggable
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── draggable
│ └── core.cljs
├── droppable
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── droppable
│ └── core.cljs
├── editable-label
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── editable_label
│ └── core.cljs
├── file-upload
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── file_upload
│ └── core.cljs
├── filter-table
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── filter_table
│ └── core.cljs
├── google-maps
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── google_maps
│ └── core.cljs
├── google-street-view
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── google_street_view
│ └── core.cljs
├── highcharts
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── highcharts
│ └── core.cljs
├── input-validation
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── input_validation
│ └── core.cljs
├── leaflet
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── leaflet
│ └── core.cljs
├── local-storage
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── local_storage
│ └── core.cljs
├── markdown-editor
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── markdown_editor
│ └── core.cljs
├── mojs-animation
├── README.md
├── project.clj
├── resources
│ └── public
│ │ ├── css
│ │ └── animation.css
│ │ ├── index.html
│ │ └── vendor
│ │ └── js
│ │ └── mo.min.js
└── src
│ └── cljs
│ └── mojs-animation
│ └── core.cljs
├── morris
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── morris
│ └── core.cljs
├── reagent-server-rendering
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── css
│ │ └── site.css
└── src
│ ├── clj
│ └── reagent_server_rendering
│ │ └── handler.clj
│ └── cljs
│ └── reagent_server_rendering
│ └── core.cljs
├── simple-sidebar
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ ├── css
│ │ └── simple-sidebar.css
│ │ └── index.html
└── src
│ └── cljs
│ └── simple_sidebar
│ └── core.cljs
├── sort-table
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── sort_table
│ └── core.cljs
├── sortable-portlets
├── README.md
├── externs.js
├── project.clj
├── resources
│ └── public
│ │ ├── css
│ │ └── screen.css
│ │ └── index.html
└── src
│ └── cljs
│ └── sortable_portlets
│ └── core.cljs
├── toggle-class
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── toggle_class
│ └── core.cljs
├── typeaheadjs
├── README.md
├── project.clj
├── resources
│ └── public
│ │ └── index.html
└── src
│ └── cljs
│ └── typeahead
│ └── core.cljs
└── undo
├── README.md
├── project.clj
├── resources
└── public
│ └── index.html
└── src
└── cljs
└── undo
└── core.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | out
3 | *.*~
4 | js
5 |
6 | .vscode
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Matthew Jaoudi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/basics/basic-component/README.md:
--------------------------------------------------------------------------------
1 | # Creating a Basic Reagent Component
2 |
3 | First things first, what is a component? Let's look at the React.js [documentation](https://facebook.github.io/react/docs/component-specs.html).
4 |
5 | ```
6 | When creating a component class by invoking `React.createClass()`,
7 | you should provide a specification object that contains a
8 | *render* method and can optionally contain other lifecycle methods
9 | described here.
10 | ```
11 |
12 | Alright, so a component is created by using the `React.createClass()` method. At a minimum, it requires a render function. In [Reagent](https://github.com/reagent-project/reagent/), there is a wrapper for the `React.createClass()` method called `create-class`. The `create-class` function takes a map of lifecycle methods (`:reagent-render`, `:component-will-mount`, `:component-did-mount`, etc.), see [here](https://github.com/reagent-project/reagent/blob/e53a5c2b1357c0560f0c4c15b28f00d09e27237b/src/reagent/core.cljs#L110). *Note: (React.js calls it `render`, Reagent calls it `:reagent-render`)*
13 |
14 | So if we want to make a bare-bones component, we could do the following.
15 |
16 | ```clojure
17 | ;; Form-3 Component
18 | (defn foo []
19 | (reagent/create-class {:reagent-render (fn [] [:div "Hello, world!"])}))
20 | ```
21 |
22 | However, creating a component that only defines the `:reagent-render` lifecycle method is very common. Reagent provides a shorthand for this - a function returning hiccup.
23 |
24 | ```clojure
25 | ;; Form-1 Component
26 | (defn bar []
27 | [:div "Hello, world!"])
28 | ```
29 |
30 | Normally, you'd call a function by wrapping it in `( )`. However, reagent components, which produce html, are called by wrapping it in `[ ]` instead. When called, both `foo` and `bar` will invoke `React.createClass()` with the same render method defined. In other words, both `foo` and `bar` are effectively the same reagent component, and, when called, will produce the same html.
31 |
32 | But wait, there is yet another way to create a reagent component with just a `render` lifecycle method - a function returning a function that returns hiccup.
33 |
34 | ```clojure
35 | ;; Form-2 Component
36 | (defn baz []
37 | (fn []
38 | [:div "Hello, world!"]))
39 | ```
40 |
41 | In the end, `foo` `bar` and `baz` all produce the same reagent component! For more detail, please read [creating reagent components](https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md).
42 |
43 | ---
44 |
45 | Let's create a project and try this out.
46 |
47 | ```
48 | $ lein new rc basic-component
49 | ```
50 |
51 | Navigate to `src/cljs/basic_component/core.cljs` and make it look like this.
52 |
53 | ```clojure
54 | (ns basic-component.core
55 | (:require [reagent.core :as reagent]
56 | [reagent.dom :as rdom]))
57 |
58 | ;; Form-3 Component
59 | (defn foo []
60 | (reagent/create-class {:reagent-render (fn [] [:div "Hello, world!"])}))
61 |
62 | ;; Form-1 Component
63 | (defn bar []
64 | [:div "Hello, world!"])
65 |
66 | ;; Form-2 Component
67 | (defn baz []
68 | (fn []
69 | [:div "Hello, world!"]))
70 |
71 | (defn home []
72 | [:div
73 | [foo]
74 | [bar]
75 | [baz]
76 | ])
77 |
78 | (defn ^:export main []
79 | (rdom/render [home]
80 | (.getElementById js/document "app")))
81 | ```
82 |
83 | Compile cljs files.
84 |
85 | ```
86 | $ lein clean
87 | $ lein cljsbuild once prod
88 | ```
89 |
90 | Open `resources/public/index.html`.
91 |
--------------------------------------------------------------------------------
/basics/basic-component/project.clj:
--------------------------------------------------------------------------------
1 | (defproject basic-component "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/basics/basic-component/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/basics/basic-component/src/cljs/basic_component/core.cljs:
--------------------------------------------------------------------------------
1 | (ns basic-component.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | ;; Form-3 Component
6 | (defn foo []
7 | (reagent/create-class {:reagent-render (fn [] [:div "Hello, world!"])}))
8 |
9 | ;; Form-1 Component
10 | (defn bar []
11 | [:div "Hello, world!"])
12 |
13 | ;; Form-2 Component
14 | (defn baz []
15 | (fn []
16 | [:div "Hello, world!"]))
17 |
18 | (defn home []
19 | [:div
20 | [foo]
21 | [bar]
22 | [baz]
23 | ])
24 |
25 | (defn ^:export main []
26 | (rdom/render [home]
27 | (.getElementById js/document "app")))
28 |
29 |
--------------------------------------------------------------------------------
/basics/component-level-state/project.clj:
--------------------------------------------------------------------------------
1 | (defproject component-level-state "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/basics/component-level-state/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/basics/component-level-state/src/cljs/component_level_state/core.cljs:
--------------------------------------------------------------------------------
1 | (ns component-level-state.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | ;; Do this
6 | (defn foo [] ;; A function
7 | (let [component-state (reagent/atom {:count 0})] ;; <-- not included in `render`
8 | (fn [] ;; That returns a function <-- `render` is from here down
9 | [:div ;; That returns hiccup
10 | [:p "Current count is: " (get @component-state :count)]
11 | [:button {:on-click #(swap! component-state update-in [:count] inc)} "Increment"]])))
12 |
13 | ;; Don't do this
14 | (defn foo-mistake []
15 | (let [component-state (reagent/atom {:count 0})]
16 | [:div
17 | [:p "Current count is: " (get @component-state :count)]
18 | [:button {:on-click #(swap! component-state update-in [:count] inc)} "Increment"]]))
19 |
20 | ;; Don't do this
21 | (defn foo-mistake2 []
22 | (let [component-state (reagent/atom {:count 0})]
23 | [:div
24 | [:p "Current count is: " (get @component-state :count)] ;; <-- This deref is causing the re-render
25 | (.log js/console (str "Foo Mistake 2 is being rendered")) ;; <- will print this on-click
26 | [:button {:on-click #(swap! component-state update-in [:count] inc)} "Increment"]]))
27 |
28 | ;; Do this
29 | (defn foo-inner-let []
30 | (let [component-state (reagent/atom {:count 0})]
31 | (fn [] ;; <-- `render` is from here down
32 | (let [count (get @component-state :count)] ;; let block is inside `render`
33 | [:div
34 | [:p "Current count is: " count]
35 | [:button {:on-click #(swap! component-state update-in [:count] inc)} "Increment"]]))))
36 |
37 | (defn home []
38 | [:div
39 | [:h1 "Foo"]
40 | [foo]
41 | [:h1 "Foo Mistake"]
42 | [foo-mistake]
43 | [:h1 "Foo Mistake 2"]
44 | [foo-mistake2]
45 | [:h1 "Foo Inner Let"]
46 | [foo-inner-let]
47 | ])
48 |
49 | (defn ^:export main []
50 | (rdom/render [home]
51 | (.getElementById js/document "app")))
52 |
--------------------------------------------------------------------------------
/basics/cursors/README.md:
--------------------------------------------------------------------------------
1 | # Reagent Cursors
2 |
3 | In Reagent, it is a common pattern to create a single reagent atom (often called `app-state` or `app-db`) that contains all of your application's state. [Cursors](https://github.com/reagent-project/reagent/blob/e53a5c2b1357c0560f0c4c15b28f00d09e27237b/src/reagent/core.cljs#L248) are an effective way to get a pointer inside a larger reagent atom, such as `app-state`.
4 |
5 | Let's start by making a reagent atom.
6 |
7 | ```clojure
8 | (def app-state (reagent/atom {:foo {:bar "Hello, world!"
9 | :baz {:quux "Woot"}}}))
10 | ```
11 |
12 | To see what is inside `app-state` we could create a reagent component that dereferences the atom and converts it to a string.
13 |
14 | ```clojure
15 | (defn inside-app-state []
16 | [:div (str "Inside app-state: " @app-state)])
17 |
18 | ;; Inside app-state: {:foo {:bar "Hello, world!", :baz {:quux "Woot"}}}
19 | ```
20 |
21 | Next, let's create a cursor and a reagent component to look inside.
22 |
23 | ```clojure
24 | (def foo-cursor (reagent/cursor app-state [:foo]))
25 |
26 | (defn inside-foo-cursor []
27 | [:div (str "Inside foo-cursor: " @foo-cursor)])
28 |
29 | ;; Inside foo-cursor: {:bar "Hello, world!", :baz {:quux "Woot"}}
30 | ```
31 |
32 | The `reagent/cursor` function takes a reagent atom and a path inside that reagent atom. The path we provided was `[:foo]`. Notice how it is wrapped in `[ ]`, this is just a convenient way to traverse a nested map structure. Let's make a few more cursors to understand this a little better.
33 |
34 | ```clojure
35 | (def foobar-cursor (reagent/cursor app-state [:foo :bar]))
36 |
37 | (defn inside-foobar-cursor []
38 | [:div (str "Inside foobar-cursor: " @foobar-cursor)])
39 |
40 | ;; Inside foobar-cursor: Hello, world!
41 |
42 |
43 | (def foobaz-cursor (reagent/cursor app-state [:foo :baz]))
44 |
45 | (defn inside-foobaz-cursor []
46 | [:div (str "Inside foobaz-cursor: " @foobaz-cursor)])
47 |
48 | ;; Inside foobaz-cursor: {:quux "Woot"}
49 |
50 |
51 | (def foobazquux-cursor (reagent/cursor app-state [:foo :baz :quux]))
52 |
53 | (defn inside-foobazquux-cursor []
54 | [:div (str "Inside foobazquux-cursor: " @foobazquux-cursor)])
55 |
56 | ;; Inside foobazquux-cursor: Woot
57 | ```
58 |
59 | Take a look at the foobazquux-cursor, the path is `[:foo :baz :quux]` and we can see it will return "Woot". If you look back at `app-state` you should have a sense of how this path thing works now.
60 |
61 | ---
62 |
63 | Let's create a project and try this out.
64 |
65 | ```
66 | $ lein new rc cursors
67 | ```
68 |
69 | Navigate to `src/cljs/cursors/core.cljs` and make it look like this.
70 |
71 | ```clojure
72 | (ns cursors.core
73 | (:require [reagent.core :as reagent]))
74 |
75 | (def app-state (reagent/atom {:foo {:bar "Hello, world!"
76 | :baz {:quux "Woot"}}}))
77 |
78 | (defn inside-app-state []
79 | [:div (str "Inside app-state: " @app-state)])
80 |
81 | (def foo-cursor (reagent/cursor app-state [:foo]))
82 |
83 | (defn inside-foo-cursor []
84 | [:div (str "Inside foo-cursor: " @foo-cursor)])
85 |
86 | (def foobar-cursor (reagent/cursor app-state [:foo :bar]))
87 |
88 | (defn inside-foobar-cursor []
89 | [:div (str "Inside foobar-cursor: " @foobar-cursor)])
90 |
91 | (def foobaz-cursor (reagent/cursor app-state [:foo :baz]))
92 |
93 | (defn inside-foobaz-cursor []
94 | [:div (str "Inside foobaz-cursor: " @foobaz-cursor)])
95 |
96 | (def foobazquux-cursor (reagent/cursor app-state [:foo :baz :quux]))
97 |
98 | (defn inside-foobazquux-cursor []
99 | [:div (str "Inside foobazquux-cursor: " @foobazquux-cursor)])
100 |
101 | (defn home []
102 | [:div
103 | [inside-app-state]
104 | [inside-foo-cursor]
105 | [inside-foobar-cursor]
106 | [inside-foobaz-cursor]
107 | [inside-foobazquux-cursor]
108 | ])
109 |
110 | (defn ^:export main []
111 | (rdom/render [home]
112 | (.getElementById js/document "app")))
113 | ```
114 |
115 | Compile cljs files.
116 |
117 | ```
118 | $ lein clean
119 | $ lein cljsbuild once prod
120 | ```
121 |
122 | Open `resources/public/index.html`.
123 |
--------------------------------------------------------------------------------
/basics/cursors/project.clj:
--------------------------------------------------------------------------------
1 | (defproject cursors "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/basics/cursors/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/basics/cursors/src/cljs/cursors/core.cljs:
--------------------------------------------------------------------------------
1 | (ns cursors.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | (def app-state (reagent/atom {:foo {:bar "Hello, world!"
6 | :baz {:quux "Woot"}}}))
7 |
8 | (defn inside-app-state []
9 | [:div (str "Inside app-state: " @app-state)])
10 |
11 | (def foo-cursor (reagent/cursor app-state [:foo]))
12 |
13 | (defn inside-foo-cursor []
14 | [:div (str "Inside foo-cursor: " @foo-cursor)])
15 |
16 | (def foobar-cursor (reagent/cursor app-state [:foo :bar]))
17 |
18 | (defn inside-foobar-cursor []
19 | [:div (str "Inside foobar-cursor: " @foobar-cursor)])
20 |
21 | (def foobaz-cursor (reagent/cursor app-state [:foo :baz]))
22 |
23 | (defn inside-foobaz-cursor []
24 | [:div (str "Inside foobaz-cursor: " @foobaz-cursor)])
25 |
26 | (def foobazquux-cursor (reagent/cursor app-state [:foo :baz :quux]))
27 |
28 | (defn inside-foobazquux-cursor []
29 | [:div (str "Inside foobazquux-cursor: " @foobazquux-cursor)])
30 |
31 | (defn home []
32 | [:div
33 | [inside-app-state]
34 | [inside-foo-cursor]
35 | [inside-foobar-cursor]
36 | [inside-foobaz-cursor]
37 | [inside-foobazquux-cursor]
38 | ])
39 |
40 | (defn ^:export main []
41 | (rdom/render [home]
42 | (.getElementById js/document "app")))
43 |
44 |
--------------------------------------------------------------------------------
/logo-rounded.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reagent-project/reagent-cookbook/296cf62f59af64f172e33bde2338ec8f3043225b/logo-rounded.jpg
--------------------------------------------------------------------------------
/old-recipes/mermaid/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to use [mermaid](https://github.com/knsv/mermaid) (a d3 charting/diagramming library from markdown-like syntax) in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | [Demo](http://rc-mermaid.s3-website-us-west-1.amazonaws.com/)
8 |
9 | We are going to follow the example in mermaid's [README](https://github.com/knsv/mermaid/blob/master/README.md).
10 |
11 | *Steps*
12 |
13 | 1. Create a new project
14 | 2. Add necessary items to `resources/public/index.html`
15 | 3. Add div with the class of _mermaid_ to `home`, along with the text for a diagram
16 | 4. Call mermaid's `init` method in a *did-mount* function
17 | 5. Use `home` and `home-did-mount` to create a reagent component called `home-component`
18 | 6. Change the initially rendered component from `home` to `home-component`
19 |
20 | #### Step 1: Create a new project
21 |
22 | ```
23 | $ lein new rc mermaid
24 | ```
25 |
26 | #### Step 2: Add necessary items to `resources/public/index.html`
27 |
28 | ```html
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Loading...
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
45 |
46 | #### Step 3: Add div with the class of _mermaid_ to `home`, along with the text for a diagram
47 |
48 | ```clojure
49 | (defn home []
50 | [:div [:h1 "Welcome to Reagent Cookbook!"]
51 | [:div.mermaid "sequenceDiagram
52 | participant Alice
53 | participant Bob
54 | Alice->>John: Hello John, how are you?
55 | loop Healthcheck
56 | John->>John: Fight against hypochondria
57 | end
58 | Note right of John: Rational thoughts
prevail...
59 | John-->>Alice: Great!
60 | John->>Bob: How about you?
61 | Bob-->>John: Jolly good!"]
62 | ])
63 | ```
64 |
65 | #### Step 4: Call mermaid's `init` method in a *did-mount* function
66 |
67 | ```clojure
68 | (defn home-did-mount []
69 | (.init js/mermaid))
70 | ```
71 |
72 | #### Step 5: Use `home` and `home-did-mount` to create a reagent component called `home-component`
73 |
74 | ```clojure
75 | (defn home-component []
76 | (reagent/create-class {:reagent-render home
77 | :component-did-mount home-did-mount}))
78 | ```
79 |
80 | #### Step 6: Change the initially rendered component from `home` to `home-component`
81 |
82 | ```clojure
83 | (reagent/render-component [home-component]
84 | (.getElementById js/document "app"))
85 | ```
86 |
87 | # Usage
88 |
89 | Compile cljs files.
90 |
91 | ```
92 | $ lein cljsbuild once
93 | ```
94 |
95 | Start a server.
96 |
97 | ```
98 | $ lein ring server
99 | ```
100 |
--------------------------------------------------------------------------------
/old-recipes/mermaid/project.clj:
--------------------------------------------------------------------------------
1 | (defproject mermaid "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [ring "1.8.2"]
5 | [ring/ring-defaults "0.3.2"]
6 | [compojure "1.3.2"]
7 | [prone "0.8.0"]
8 | [reagent "1.0.0"]]
9 |
10 | :min-lein-version "2.5.0"
11 |
12 | :plugins [[lein-ring "0.12.5"]
13 | [lein-cljsbuild "1.1.8"]]
14 |
15 | :source-paths ["src/clj"]
16 |
17 | :ring {:handler mermaid.handler/app}
18 |
19 | :cljsbuild {:builds [{:source-paths ["src/cljs"]
20 | :compiler {:output-to "resources/public/js/app.js"}}]})
21 |
--------------------------------------------------------------------------------
/old-recipes/mermaid/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Loading...
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/old-recipes/mermaid/src/clj/mermaid/handler.clj:
--------------------------------------------------------------------------------
1 | (ns mermaid.handler
2 | (:require [clojure.java.io :as io]
3 | [compojure.core :refer [GET defroutes]]
4 | [compojure.route :refer [not-found resources]]
5 | [ring.middleware.defaults :refer [site-defaults wrap-defaults]]
6 | [prone.middleware :refer [wrap-exceptions]]))
7 |
8 | (defroutes routes
9 | (GET "/" [] (slurp (io/resource "public/index.html")))
10 | (resources "/")
11 | (not-found "Not Found"))
12 |
13 | (def app
14 | (let [handler (wrap-defaults routes site-defaults)]
15 | (wrap-exceptions handler) handler))
16 |
--------------------------------------------------------------------------------
/old-recipes/mermaid/src/cljs/mermaid/core.cljs:
--------------------------------------------------------------------------------
1 | (ns mermaid.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | (defn home []
6 | [:div [:h1 "Welcome to Reagent Cookbook!"]
7 | [:div.mermaid "sequenceDiagram
8 | participant Alice
9 | participant Bob
10 | Alice->>John: Hello John, how are you?
11 | loop Healthcheck
12 | John->>John: Fight against hypochondria
13 | end
14 | Note right of John: Rational thoughts
prevail...
15 | John-->>Alice: Great!
16 | John->>Bob: How about you?
17 | Bob-->>John: Jolly good!"]
18 | ])
19 |
20 | (defn home-did-mount []
21 | (.init js/mermaid))
22 |
23 | (defn home-component []
24 | (reagent/create-class {:reagent-render home
25 | :component-did-mount home-did-mount}))
26 |
27 | (rdom/render [home-component]
28 | (.getElementById js/document "app"))
29 |
--------------------------------------------------------------------------------
/old-recipes/nvd3/project.clj:
--------------------------------------------------------------------------------
1 | (defproject nvd3 "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.6.0"]
3 | [org.clojure/clojurescript "0.0-3058" :scope "provided"]
4 | [ring "1.3.2"]
5 | [ring/ring-defaults "0.1.3"]
6 | [compojure "1.3.2"]
7 | [prone "0.8.0"]
8 | [reagent "0.5.0"]]
9 |
10 | :min-lein-version "2.5.0"
11 |
12 | :plugins [[lein-ring "0.9.1"]
13 | [lein-cljsbuild "1.0.4"]]
14 |
15 | :source-paths ["src/clj"]
16 |
17 | :ring {:handler nvd3.handler/app}
18 |
19 | :cljsbuild {:builds [{:source-paths ["src/cljs"]
20 | :compiler {:output-to "resources/public/js/app.js"}}]})
21 |
--------------------------------------------------------------------------------
/old-recipes/nvd3/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Loading...
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/old-recipes/nvd3/src/clj/nvd3/handler.clj:
--------------------------------------------------------------------------------
1 | (ns nvd3.handler
2 | (:require [clojure.java.io :as io]
3 | [compojure.core :refer [GET defroutes]]
4 | [compojure.route :refer [not-found resources]]
5 | [ring.middleware.defaults :refer [site-defaults wrap-defaults]]
6 | [prone.middleware :refer [wrap-exceptions]]))
7 |
8 | (defroutes routes
9 | (GET "/" [] (slurp (io/resource "public/index.html")))
10 | (resources "/")
11 | (not-found "Not Found"))
12 |
13 | (def app
14 | (let [handler (wrap-defaults routes site-defaults)]
15 | (wrap-exceptions handler) handler))
16 |
--------------------------------------------------------------------------------
/old-recipes/nvd3/src/cljs/nvd3/core.cljs:
--------------------------------------------------------------------------------
1 | (ns nvd3.core
2 | (:require [reagent.core :as reagent]))
3 |
4 | (defn home []
5 | [:div [:h1 "Welcome to Reagent Cookbook!"]
6 | [:div#d3-node {:style {:width "750" :height "420"}} [:svg ]]
7 | ])
8 |
9 | (defn home-did-mount []
10 | (.addGraph js/nv (fn []
11 | (let [chart (.. js/nv -models lineChart
12 | (margin #js {:left 100})
13 | (useInteractiveGuideline true)
14 | (transitionDuration 350)
15 | (showLegend true)
16 | (showYAxis true)
17 | (showXAxis true))]
18 | (.. chart -xAxis
19 | (axisLabel "x-axis")
20 | (tickFormat (.format js/d3 ",r")))
21 | (.. chart -yAxis
22 | (axisLabel "y-axis")
23 | (tickFormat (.format js/d3 ",r")))
24 |
25 | (let [my-data [{:x 1 :y 5} {:x 2 :y 3} {:x 3 :y 4} {:x 4 :y 1} {:x 5 :y 2}]]
26 |
27 | (.. js/d3 (select "#d3-node svg")
28 | (datum (clj->js [{:values my-data
29 | :key "my-red-line"
30 | :color "red"
31 | }]))
32 | (call chart)))))))
33 |
34 | (defn home-component []
35 | (reagent/create-class {:reagent-render home
36 | :component-did-mount home-did-mount}))
37 |
38 | (reagent/render-component [home-component]
39 | (.getElementById js/document "app"))
40 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-ReactTestUtils/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [cljs.test](https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/test.cljs), [doo](https://github.com/bensu/doo), and [ReactTestUtils](https://facebook.github.io/react/docs/test-utils.html) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Follow all of the instructions in the [test-example](https://github.com/reagent-project/reagent-cookbook/tree/master/recipes/test-example) recipe.
10 | 2. Add [cljs-react-test](https://github.com/bensu/cljs-react-test) and [dommy](https://github.com/Prismatic/dommy) to `project.clj` :dependencies vector, as well as change react to react-with-addons
11 | 3. Add an increment button to `core.cljs`
12 | 4. Add a test that will simulate the on-click event
13 |
14 | #### Step 1: Follow all of the instructions in the [test-example](https://github.com/reagent-project/reagent-cookbook/tree/master/recipes/test-example) recipe.
15 |
16 | #### Step 2: Add [cljs-react-test](https://github.com/bensu/cljs-react-test) and [dommy](https://github.com/Prismatic/dommy) to `project.clj` :dependencies vector, as well change react to react-with-addons
17 |
18 | ```clojure
19 | :dependencies [...
20 | [reagent "0.5.1":exclusions [cljsjs/react]]
21 | [cljsjs/react-with-addons "0.13.3-0"]
22 | [cljs-react-test "0.1.3-SNAPSHOT"]
23 | [prismatic/dommy "1.1.0"]]
24 | ```
25 |
26 | #### Step 3: Add an increment button to `core.cljs`
27 |
28 | Navigate to `src/cljs/test_example/core.cljs` and add the following button.
29 |
30 | ```clojure
31 | (ns test-example.core
32 | (:require [reagent.core :as reagent]))
33 |
34 | ;; ATTENTION \/
35 | (defonce app-state (reagent/atom {:count 0}))
36 |
37 | (defn handle-on-click [app-state]
38 | (swap! app-state update-in [:count] inc))
39 |
40 | (defn increment-button [app-state]
41 | [:button {:on-click #(handle-on-click app-state)}
42 | "Increment"])
43 |
44 | (defn home []
45 | [:div
46 | [increment-button app-state]
47 | [:div "Current count: " (@app-state :count)]
48 | ])
49 | ;; ATTENTION /\
50 |
51 | (defn ^:export main []
52 | (reagent/render [home]
53 | (.getElementById js/document "app")))
54 | ```
55 |
56 | #### Step 4: Add a test that will simulate the on-click event
57 |
58 | Navigate to `test/cljs/test_example/core_test.cljs` and make it look like the following.
59 |
60 | For this single `deftest`, the use of `use-fixtures` isn't necessary; however, you will likely find this pattern useful when defining multiple `deftest`s.
61 |
62 | ```clojure
63 | (ns test-example.core-test
64 | (:require [cljs.test :refer-macros [deftest testing is use-fixtures]]
65 | [cljs-react-test.utils :as tu]
66 | [cljs-react-test.simulate :as sim]
67 | [dommy.core :as dommy :refer-macros [sel1]]
68 | [reagent.core :as reagent]
69 | [test-example.core :as core]))
70 |
71 | (def ^:dynamic c)
72 |
73 | (use-fixtures :each (fn [test-fn]
74 | (binding [c (tu/new-container!)]
75 | (test-fn)
76 | (tu/unmount! c))))
77 |
78 | (deftest increment-button
79 | (testing "on-click"
80 | (let [app-state (reagent/atom {:count 0})
81 | _ (reagent/render [core/increment-button app-state] c)
82 | node (sel1 c [:button])]
83 | (is (= 0 (:count @app-state)))
84 | (sim/click node nil)
85 | (is (= 1 (:count @app-state))))))
86 | ```
87 |
88 | # Usage
89 |
90 | Run the tests.
91 |
92 | ```
93 | $ lein clean
94 | $ lein doo phantom test once
95 | ```
96 |
97 | The above command assumes that you have [phantomjs](https://www.npmjs.com/package/phantomjs) installed. However, please note that [doo](https://github.com/bensu/doo) can be configured to run cljs.test in many other JS environments (chrome, ie, safari, opera, slimer, node, rhino, or nashorn).
98 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-ReactTestUtils/project.clj:
--------------------------------------------------------------------------------
1 | (defproject test-example "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.7.0"]
3 | [org.clojure/clojurescript "1.7.122"]
4 | [reagent "0.5.1":exclusions [cljsjs/react]]
5 | [cljsjs/react-with-addons "0.13.3-0"]
6 | [cljs-react-test "0.1.3-SNAPSHOT"]
7 | [prismatic/dommy "1.1.0"]]
8 |
9 | :source-paths ["src/clj"]
10 |
11 | :plugins [[lein-cljsbuild "1.0.6"]
12 | [lein-doo "0.1.6"]]
13 |
14 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
15 |
16 | :cljsbuild {:builds [{:id "prod"
17 | :source-paths ["src/cljs"]
18 | :compiler {:output-to "resources/public/js/compiled/app.js"
19 | :optimizations :advanced
20 | :pretty-print false}}
21 |
22 | {:id "test"
23 | :source-paths ["src/cljs" "test/cljs"]
24 | :compiler {:output-to "resources/public/js/compiled/test.js"
25 | :main test-example.runner
26 | :optimizations :none}}
27 | ]})
28 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-ReactTestUtils/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-ReactTestUtils/src/cljs/test_example/core.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.core
2 | (:require [reagent.core :as reagent]))
3 |
4 | (defonce app-state (reagent/atom {:count 0}))
5 |
6 | (defn handle-on-click [app-state]
7 | (swap! app-state update-in [:count] inc))
8 |
9 | (defn increment-button [app-state]
10 | [:button {:on-click #(handle-on-click app-state)}
11 | "Increment"])
12 |
13 | (defn home []
14 | [:div
15 | [increment-button app-state]
16 | [:div "Current count: " (@app-state :count)]
17 | ])
18 |
19 | (defn ^:export main []
20 | (reagent/render [home]
21 | (.getElementById js/document "app")))
22 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-ReactTestUtils/test/cljs/test_example/core_test.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.core-test
2 | (:require [cljs.test :refer-macros [deftest testing is use-fixtures]]
3 | [cljs-react-test.utils :as tu]
4 | [cljs-react-test.simulate :as sim]
5 | [dommy.core :as dommy :refer-macros [sel1]]
6 | [reagent.core :as reagent]
7 | [test-example.core :as core]))
8 |
9 | (def ^:dynamic c)
10 |
11 | (use-fixtures :each (fn [test-fn]
12 | (binding [c (tu/new-container!)]
13 | (test-fn)
14 | (tu/unmount! c))))
15 |
16 | (deftest increment-button
17 | (testing "on-click"
18 | (let [app-state (reagent/atom {:count 0})
19 | _ (reagent/render [core/increment-button app-state] c)
20 | node (sel1 c [:button])]
21 | (is (= 0 (:count @app-state)))
22 | (sim/click node nil)
23 | (is (= 1 (:count @app-state))))))
24 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-ReactTestUtils/test/cljs/test_example/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.runner
2 | (:require [doo.runner :refer-macros [doo-tests]]
3 | [test-example.core-test]))
4 |
5 | (doo-tests 'test-example.core-test)
6 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-test-check/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [cljs.test](https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/test.cljs), [doo](https://github.com/bensu/doo), and [test.check](https://github.com/clojure/test.check) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Follow all of the instructions in the [test-example](https://github.com/reagent-project/reagent-cookbook/tree/master/recipes/test-example) recipe.
10 | 2. Add [test.check](https://github.com/clojure/test.check) to `project.clj` :dependencies vector
11 | 3. Add a test that will use test.check's `defspec` macro
12 |
13 | #### Step 1: Follow all of the instructions in the [test-example](https://github.com/reagent-project/reagent-cookbook/tree/master/recipes/test-example) recipe.
14 |
15 | #### Step 2: Add [test.check](https://github.com/clojure/test.check) to `project.clj` :dependencies vector
16 |
17 | ```clojure
18 | [org.clojure/test.check "0.9.0"]
19 | ```
20 |
21 | #### Step 3: Add a test that will use test.check's `defspec` macro
22 |
23 | Navigate to `test/cljs/test_example/core_test.cljs` and make it look like the following.
24 |
25 | ```clojure
26 | (ns test-example.core-test
27 | (:require [cljs.test :refer-macros [deftest testing is]]
28 | [clojure.test.check :as tc]
29 | [clojure.test.check.generators :as gen]
30 | [clojure.test.check.properties :as prop :include-macros true]
31 | [clojure.test.check.clojure-test :refer-macros [defspec]]))
32 |
33 | (defspec first-element-is-min-after-sorting ;; the name of the test
34 | 100 ;; the number of iterations for test.check to test
35 | (prop/for-all [v (gen/not-empty (gen/vector gen/int))]
36 | (= (apply min v)
37 | (first (sort v)))))
38 | ```
39 |
40 | # Usage
41 |
42 | Run the tests.
43 |
44 | ```
45 | $ lein clean
46 | $ lein doo phantom test once
47 | ```
48 |
49 | The above command assumes that you have [phantomjs](https://www.npmjs.com/package/phantomjs) installed. However, please note that [doo](https://github.com/bensu/doo) can be configured to run cljs.test in many other JS environments (chrome, ie, safari, opera, slimer, node, rhino, or nashorn).
50 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-test-check/project.clj:
--------------------------------------------------------------------------------
1 | (defproject test-example "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.7.0"]
3 | [org.clojure/clojurescript "1.7.122"]
4 | [reagent "0.5.1"]
5 | [org.clojure/test.check "0.9.0"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.0.6"]
10 | [lein-doo "0.1.6"]]
11 |
12 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
13 |
14 | :cljsbuild {:builds [{:id "prod"
15 | :source-paths ["src/cljs"]
16 | :compiler {:output-to "resources/public/js/compiled/app.js"
17 | :optimizations :advanced
18 | :pretty-print false}}
19 |
20 | {:id "test"
21 | :source-paths ["src/cljs" "test/cljs"]
22 | :compiler {:output-to "resources/public/js/compiled/test.js"
23 | :main test-example.runner
24 | :optimizations :none}}
25 | ]})
26 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-test-check/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-test-check/src/cljs/test_example/core.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.core
2 | (:require [reagent.core :as reagent]))
3 |
4 | (defn home []
5 | [:div
6 |
7 | ])
8 |
9 | (defn ^:export main []
10 | (reagent/render [home]
11 | (.getElementById js/document "app")))
12 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-test-check/test/cljs/test_example/core_test.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.core-test
2 | (:require [cljs.test :refer-macros [deftest testing is]]
3 | [clojure.test.check :as tc]
4 | [clojure.test.check.generators :as gen]
5 | [clojure.test.check.properties :as prop :include-macros true]
6 | [clojure.test.check.clojure-test :refer-macros [defspec]]))
7 |
8 | (defspec first-element-is-min-after-sorting ;; the name of the test
9 | 100 ;; the number of iterations for test.check to test
10 | (prop/for-all [v (gen/not-empty (gen/vector gen/int))]
11 | (= (apply min v)
12 | (first (sort v)))))
13 |
--------------------------------------------------------------------------------
/old-recipes/test-example-with-test-check/test/cljs/test_example/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.runner
2 | (:require [doo.runner :refer-macros [doo-tests]]
3 | [test-example.core-test]))
4 |
5 | (doo-tests 'test-example.core-test)
6 |
--------------------------------------------------------------------------------
/old-recipes/test-example/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [cljs.test](https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/test.cljs) and [doo](https://github.com/bensu/doo) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add lein-doo to `project.clj` :plugins vector
11 | 3. Add `test` build to `project.clj`
12 | 4. Add a test file with a `deftest`
13 | 5. Add a test runner
14 |
15 | #### Step 1: Create a new project
16 |
17 | ```
18 | $ lein new rc test-example
19 | ```
20 |
21 | #### Step 2: Add lein-doo to `project.clj` :plugins vector
22 |
23 | ```clojure
24 | [lein-doo "0.1.6"]
25 | ```
26 |
27 | #### Step 3: Add `test` build to `project.clj
28 |
29 | ```clojure
30 | {:id "test"
31 | :source-paths ["src/cljs" "test/cljs"]
32 | :compiler {:output-to "resources/public/js/compiled/test.js"
33 | :main test-example.runner
34 | :optimizations :none}}
35 | ```
36 |
37 | #### Step 4: Add a test file with a `deftest`
38 |
39 | Create the file *test/cljs/test\_example/core\_test.cljs* and add the following:
40 |
41 | ```clojure
42 | (ns test-example.core-test
43 | (:require [cljs.test :refer-macros [deftest testing is]]))
44 |
45 | (deftest fake-test
46 | (testing "fake description"
47 | (is (= 1 2))))
48 | ```
49 |
50 | #### Step 5: Add a test runner
51 |
52 | Create the file *test/cljs/test_example/runner.cljs* and add the following:
53 |
54 | ```clojure
55 | (ns test-example.runner
56 | (:require [doo.runner :refer-macros [doo-tests]]
57 | [test-example.core-test]))
58 |
59 | (doo-tests 'test-example.core-test)
60 | ```
61 |
62 | # Usage
63 |
64 | Run the tests.
65 |
66 | ```
67 | $ lein clean
68 | $ lein doo phantom test once
69 | ```
70 |
71 | The above command assumes that you have [phantomjs](https://www.npmjs.com/package/phantomjs) installed. However, please note that [doo](https://github.com/bensu/doo) can be configured to run cljs.test in many other JS environments (chrome, ie, safari, opera, slimer, node, rhino, or nashorn).
72 |
--------------------------------------------------------------------------------
/old-recipes/test-example/project.clj:
--------------------------------------------------------------------------------
1 | (defproject test-example "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.7.0"]
3 | [org.clojure/clojurescript "1.7.122"]
4 | [reagent "0.5.1"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.0.6"]
9 | [lein-doo "0.1.6"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false}}
18 |
19 | {:id "test"
20 | :source-paths ["src/cljs" "test/cljs"]
21 | :compiler {:output-to "resources/public/js/compiled/test.js"
22 | :main test-example.runner
23 | :optimizations :none}}
24 | ]})
25 |
--------------------------------------------------------------------------------
/old-recipes/test-example/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/old-recipes/test-example/src/cljs/test_example/core.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.core
2 | (:require [reagent.core :as reagent]))
3 |
4 | (defn home []
5 | [:div
6 |
7 | ])
8 |
9 | (defn ^:export main []
10 | (reagent/render [home]
11 | (.getElementById js/document "app")))
12 |
--------------------------------------------------------------------------------
/old-recipes/test-example/test/cljs/test_example/core_test.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.core-test
2 | (:require [cljs.test :refer-macros [deftest testing is]]))
3 |
4 | (deftest fake-test
5 | (testing "fake description"
6 | (is (= 1 2))))
7 |
--------------------------------------------------------------------------------
/old-recipes/test-example/test/cljs/test_example/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns test-example.runner
2 | (:require [doo.runner :refer-macros [doo-tests]]
3 | [test-example.core-test]))
4 |
5 | (doo-tests 'test-example.core-test)
6 |
--------------------------------------------------------------------------------
/recipes/ReactCSSTransitionGroup/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to use [ReactCSSTransitionGroup](https://facebook.github.io/react/docs/animation.html) in your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | (Note: This recipe is based on @jcreedcmu gist in this reagent [issue](https://github.com/reagent-project/reagent/issues/55).)
6 |
7 | # Solution
8 |
9 | In this example, we will be making a list of items that have an animated transition when they are created and removed.
10 |
11 | *Steps*
12 |
13 | 1. Create a new project
14 | 2. Add react-with-addons to dependecies vector in `project.clj`
15 | 3. Navigate to `src/cljs/animation/core.cljs` and adapt react's CSSTransitionGroup addon to reagent
16 | 4. Create the style for your transition
17 | 5. Create the initial app-state
18 | 6. Create an `add-item` function
19 | 7. Create a `delete-item` function
20 | 8. Create a reagent component that can add items, delete items, and displays the items
21 |
22 | #### Step 1: Create a new project
23 |
24 | ```
25 | $ lein new rc animation
26 | ```
27 |
28 | #### Step 2: Exclude react from reagent and add react-with-addons to dependecies vector in `project.clj`
29 |
30 | ```clojure
31 | [reagent "0.5.1" :exclusions [cljsjs/react]]
32 | [cljsjs/react-with-addons "0.13.3-0"]
33 | ```
34 |
35 | #### Step 3: Navigate to `src/cljs/animation/core.cljs` and adapt react's CSSTransitionGroup addon to reagent
36 |
37 | ```clojure
38 | (def css-transition-group
39 | (reagent/adapt-react-class js/React.addons.CSSTransitionGroup))
40 | ```
41 |
42 | #### Step 4: Create the style for your transition
43 |
44 | Notice that we are using `foo` as our transition name.
45 |
46 | ```clojure
47 | (def style
48 | "li {
49 | background-color: #44ee22; padding: 10px; margin: 1px; width: 150px;
50 | border-radius: 5px;
51 | font-size: 24px;
52 | text-align: center;
53 | list-style: none;
54 | color: #fff;
55 | height: 2em;
56 | line-height: 2em;
57 | padding: 0 0.5em;
58 | overflow: hidden;
59 | }
60 |
61 | .foo-enter {
62 | height: 0;
63 | transition: height 0.27s ease-out;
64 | }
65 |
66 | .foo-leave {
67 | height: 0;
68 | transition: height 0.27s ease-out;
69 | }
70 |
71 | .foo-enter-active {
72 | height: 2em;
73 | opacity: 1;
74 | }")
75 | ```
76 |
77 | #### Step 5: Create the initial app-state
78 |
79 | ```clojure
80 | (def app-state
81 | (reagent/atom {:items []
82 | :items-counter 0}))
83 | ```
84 |
85 | #### Step 6: Create an `add-item` function
86 |
87 | We are doing two things here: 1) keeping track of the running total of items, and 2) adding the new item to the list of items stored in `app-state`.
88 |
89 | ```clojure
90 | (defn add-item []
91 | (let [items (:items @app-state)]
92 | (swap! app-state update-in [:items-counter] inc)
93 | (swap! app-state assoc :items (conj items (:items-counter @app-state)))))
94 | ```
95 |
96 | #### Step 7: Create a `delete-item` function
97 |
98 | Here we are are deleting the last item added to the list of items that are stored in `app-state`
99 |
100 | ```clojure
101 | (defn delete-item []
102 | (let [items (:items @app-state)]
103 | (swap! app-state assoc :items (vec (butlast items)))))
104 | ```
105 |
106 | #### Step 8: Create a reagent component that can add items, delete items, and displays the items
107 |
108 | Notice the use of the `css-transition-group` from step 3, and the use of the name `"foo"` from step 4.
109 |
110 | ```clojure
111 | (defn home []
112 | [:div
113 | [:div (str "Total list items to date: " (:items-counter @app-state))]
114 | [:button {:on-click #(add-item)} "add"]
115 | [:button {:on-click #(delete-item)} "delete"]
116 | [:style style]
117 | [:ul
118 | [css-transition-group {:transition-name "foo"}
119 | (map-indexed (fn [i x]
120 | ^{:key i} [:li (str "List Item " x)])
121 | (:items @app-state))]]
122 | ])
123 | ```
124 |
125 | # Usage
126 |
127 | Compile cljs files.
128 |
129 | ```
130 | $ lein clean
131 | $ lein cljsbuild once prod
132 | ```
133 |
134 | Open `resources/public/index.html`.
135 |
--------------------------------------------------------------------------------
/recipes/ReactCSSTransitionGroup/project.clj:
--------------------------------------------------------------------------------
1 | (defproject animation "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0" :exclusions [cljsjs/react]]
5 | [cljsjs/react-with-addons "15.6.1-0"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.1.8"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false}}]})
18 |
--------------------------------------------------------------------------------
/recipes/ReactCSSTransitionGroup/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/recipes/ReactCSSTransitionGroup/src/cljs/animation/core.cljs:
--------------------------------------------------------------------------------
1 | (ns animation.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (def css-transition-group
6 | (reagent/adapt-react-class js/React.addons.CSSTransitionGroup))
7 |
8 | (def style
9 | "li {
10 | background-color: #44ee22; padding: 10px; margin: 1px; width: 150px;
11 | border-radius: 5px;
12 | font-size: 24px;
13 | text-align: center;
14 | list-style: none;
15 | color: #fff;
16 | height: 2em;
17 | line-height: 2em;
18 | padding: 0 0.5em;
19 | overflow: hidden;
20 | }
21 |
22 | .foo-enter {
23 | height: 0;
24 | transition: height 0.27s ease-out;
25 | }
26 |
27 | .foo-leave {
28 | height: 0;
29 | transition: height 0.27s ease-out;
30 | }
31 |
32 | .foo-enter-active {
33 | height: 2em;
34 | opacity: 1;
35 | }")
36 |
37 | (def app-state
38 | (reagent/atom {:items []
39 | :items-counter 0}))
40 |
41 | (defn add-item []
42 | (let [items (:items @app-state)]
43 | (swap! app-state update-in [:items-counter] inc)
44 | (swap! app-state assoc :items (conj items (:items-counter @app-state)))))
45 |
46 | (defn delete-item []
47 | (let [items (:items @app-state)]
48 | (swap! app-state assoc :items (vec (butlast items)))))
49 |
50 | (defn home []
51 | [:div
52 | [:div (str "Total list items to date: " (:items-counter @app-state))]
53 | [:button {:on-click #(add-item)} "add"]
54 | [:button {:on-click #(delete-item)} "delete"]
55 | [:style style]
56 | [:ul
57 | [css-transition-group {:transition-name "foo"}
58 | (map-indexed (fn [i x]
59 | ^{:key i} [:li (str "List Item " x)])
60 | (:items @app-state))]]
61 | ])
62 |
63 | (defn ^:export main []
64 | (rdom/render [home]
65 | (.getElementById js/document "app")))
66 |
67 |
--------------------------------------------------------------------------------
/recipes/add-routing/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add routing using [Secretary](https://github.com/gf3/secretary) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add Secretary to `project.clj` :dependencies vector
11 | 3. Add a reagent atom called `app-state`
12 | 4. Add Secretary to `core.cljs` namespace
13 | 5. Add browser history
14 | 6. Create routes
15 | 7. Create a `home` and `about` pages that link to each other
16 | 8. Create a `current-page` multimethod that will return which page component to display based on `app-state`
17 | 9. Update `main` to include `app-routes` and to render `current-page`
18 |
19 | #### Step 1: Create a new project
20 |
21 | ```
22 | $ lein new rc add-routing
23 | ```
24 |
25 | #### Step 2: Add Secretary to `project.clj` :dependencies vector
26 |
27 | ```clojure
28 | [secretary "1.2.3"]
29 | ```
30 |
31 | #### Step 3: Add a reagent atom called `app-state`
32 |
33 | ```clojure
34 | (def app-state (reagent/atom {}))
35 | ```
36 |
37 | #### Step 4: Add Secretary to `core.cljs` namespace
38 |
39 | Navigate to `src/cljs/add_routing/core.cljs` and update the `ns` to the following.
40 |
41 | ```clojure
42 | (ns add-routing.core
43 | (:require-macros [secretary.core :refer [defroute]])
44 | (:import goog.history.Html5History)
45 | (:require [secretary.core :as secretary]
46 | [goog.events :as events]
47 | [goog.history.EventType :as EventType]
48 | [reagent.core :as reagent]))
49 | ```
50 |
51 | #### Step 5: Add browser history
52 |
53 | Add browser history.
54 |
55 | ```clojure
56 | (defn hook-browser-navigation! []
57 | (doto (Html5History.)
58 | (events/listen
59 | EventType/NAVIGATE
60 | (fn [event]
61 | (secretary/dispatch! (.-token event))))
62 | (.setEnabled true)))
63 | ```
64 |
65 | #### Step 6: Create routes
66 |
67 | Secretary is a client-side router for clojurscript. We are going to create two routes and prefix them with `#`. When a user hits the `#/` route, then the key `:page` in `app-state` will get the value of `:home`. When a user hits the `#/about` route, then the key `:page` in `app-state` will get the value of `:about`. Later we will create a function that will dispatch to the correct reagent component based on the keys `:home` and `:about`.
68 |
69 | ```clojure
70 | (defn app-routes []
71 | (secretary/set-config! :prefix "#")
72 |
73 | (defroute "/" []
74 | (swap! app-state assoc :page :home))
75 |
76 | (defroute "/about" []
77 | (swap! app-state assoc :page :about))
78 |
79 | (hook-browser-navigation!))
80 | ```
81 |
82 | #### Step 7: Create a `home` and `about` pages that link to each other
83 |
84 | ```clojure
85 | (defn home []
86 | [:div [:h1 "Home Page"]
87 | [:a {:href "#/about"} "about page"]])
88 |
89 | (defn about []
90 | [:div [:h1 "About Page"]
91 | [:a {:href "#/"} "home page"]])
92 | ```
93 |
94 | #### Step 8: Create a `current-page` multimethod that will return which page component to display based on `app-state`
95 |
96 | ```clojure
97 | (defmulti current-page #(@app-state :page))
98 | (defmethod current-page :home []
99 | [home])
100 | (defmethod current-page :about []
101 | [about])
102 | (defmethod current-page :default []
103 | [:div ])
104 | ```
105 |
106 | #### Step 9: Update `main` to include `app-routes` and to render `current-page`
107 |
108 | ```clojure
109 | (defn ^:export main []
110 | (app-routes)
111 | (reagent/render [current-page]
112 | (.getElementById js/document "app")))
113 | ```
114 |
115 | # Usage
116 |
117 | Compile cljs files.
118 |
119 | ```
120 | $ lein clean
121 | $ lein cljsbuild once prod
122 | ```
123 |
124 | Open `resources/public/index.html`.
125 |
--------------------------------------------------------------------------------
/recipes/add-routing/project.clj:
--------------------------------------------------------------------------------
1 | (defproject add-routing "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [secretary "1.2.3"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.1.8"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false}}]})
18 |
--------------------------------------------------------------------------------
/recipes/add-routing/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/recipes/add-routing/src/cljs/add_routing/core.cljs:
--------------------------------------------------------------------------------
1 | (ns add-routing.core
2 | (:require-macros [secretary.core :refer [defroute]])
3 | (:import goog.history.Html5History)
4 | (:require [secretary.core :as secretary]
5 | [goog.events :as events]
6 | [goog.history.EventType :as EventType]
7 | [reagent.dom :as rdom]
8 | [reagent.core :as reagent]))
9 |
10 | (def app-state (reagent/atom {}))
11 |
12 | (defn hook-browser-navigation! []
13 | (doto (Html5History.)
14 | (events/listen
15 | EventType/NAVIGATE
16 | (fn [event]
17 | (secretary/dispatch! (.-token event))))
18 | (.setEnabled true)))
19 |
20 | (defn app-routes []
21 | (secretary/set-config! :prefix "#")
22 |
23 | (defroute "/" []
24 | (swap! app-state assoc :page :home))
25 |
26 | (defroute "/about" []
27 | (swap! app-state assoc :page :about))
28 |
29 | (hook-browser-navigation!))
30 |
31 | (defn home []
32 | [:div [:h1 "Home Page"]
33 | [:a {:href "#/about"} "about page"]])
34 |
35 | (defn about []
36 | [:div [:h1 "About Page"]
37 | [:a {:href "#/"} "home page"]])
38 |
39 | (defmulti current-page #(@app-state :page))
40 | (defmethod current-page :home []
41 | [home])
42 | (defmethod current-page :about []
43 | [about])
44 | (defmethod current-page :default []
45 | [:div ])
46 |
47 | (defn ^:export main []
48 | (app-routes)
49 | (rdom/render [current-page]
50 | (.getElementById js/document "app")))
51 |
52 |
--------------------------------------------------------------------------------
/recipes/autocomplete/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [jQuery UI's](http://jqueryui.com/) autocomplete to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | We are going to follow this [example](http://jqueryui.com/autocomplete/).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Add autocomplete element to `home-render`
14 | 4. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
15 | 5. Use `home-render` and `home-did-mount` to create a reagent component called `home`
16 |
17 | #### Step 1: Create a new project
18 |
19 | ```
20 | $ lein new rc autocomplete
21 | ```
22 |
23 | #### Step 2: Add necessary items to `resources/public/index.html`
24 |
25 | ```html
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | ```
42 |
43 | #### Step 3: Add autocomplete element to `home-render`
44 |
45 | Navigate to `src/cljs/autocomplete/core.cljs`. This is the html we need.
46 |
47 | ```html
48 |
49 |
50 |
51 |
52 | ```
53 |
54 | Let's convert this and put in a function called `home-render`.
55 |
56 | ```clojure
57 | (defn home-render []
58 | [:div.ui-widget
59 | [:label {:for "tags"} "Programming Languages: "]
60 | [:input#tags]])
61 | ```
62 |
63 | #### Step 4: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
64 |
65 | This is the javascript we need.
66 |
67 | ```javascript
68 | $(function() {
69 | var availableTags = [
70 | "ActionScript",
71 | "AppleScript",
72 | "Asp",
73 | "BASIC",
74 | "C",
75 | "C++",
76 | "Clojure",
77 | "COBOL",
78 | "ColdFusion",
79 | "Erlang",
80 | "Fortran",
81 | "Groovy",
82 | "Haskell",
83 | "Java",
84 | "JavaScript",
85 | "Lisp",
86 | "Perl",
87 | "PHP",
88 | "Python",
89 | "Ruby",
90 | "Scala",
91 | "Scheme"
92 | ];
93 | $( "#tags" ).autocomplete({
94 | source: availableTags
95 | });
96 | });
97 | ```
98 |
99 | Let's convert this to clojurescript and place in `home-did-mount`
100 |
101 | ```clojure
102 | (def tags
103 | ["ActionScript"
104 | "AppleScript"
105 | "Asp"
106 | "BASIC"
107 | "C"
108 | "C++"
109 | "Clojure"
110 | "COBOL"
111 | "ColdFusion"
112 | "Erlang"
113 | "Fortran"
114 | "Groovy"
115 | "Haskell"
116 | "Java"
117 | "JavaScript"
118 | "Lisp"
119 | "Perl"
120 | "PHP"
121 | "Python"
122 | "Ruby"
123 | "Scala"
124 | "Scheme"])
125 |
126 | (defn home-did-mount []
127 | (js/$ (fn []
128 | (.autocomplete (js/$ "#tags")
129 | (clj->js {:source tags})))))
130 | ```
131 |
132 | #### Step 5: Use `home-render` and `home-did-mount` to create a reagent component called `home`
133 |
134 | ```clojure
135 | (defn home []
136 | (reagent/create-class {:reagent-render home-render
137 | :component-did-mount home-did-mount}))
138 | ```
139 |
140 | # Usage
141 |
142 | Compile cljs files.
143 |
144 | ```
145 | $ lein clean
146 | $ lein cljsbuild once prod
147 | ```
148 |
149 | Open `resources/public/index.html`.
150 |
--------------------------------------------------------------------------------
/recipes/autocomplete/project.clj:
--------------------------------------------------------------------------------
1 | (defproject autocomplete "0.2.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/autocomplete/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/autocomplete/src/cljs/autocomplete/core.cljs:
--------------------------------------------------------------------------------
1 | (ns autocomplete.core
2 | (:require
3 | [reagent.core :as r]
4 | [reagent.dom :as rdom]))
5 |
6 | (defn home-render []
7 | [:div.ui-widget
8 | [:label {:for "tags"} "Programming Languages: "]
9 | [:input#tags]])
10 |
11 | (def tags
12 | ["ActionScript"
13 | "AppleScript"
14 | "Asp"
15 | "BASIC"
16 | "C"
17 | "C++"
18 | "Clojure"
19 | "COBOL"
20 | "ColdFusion"
21 | "Erlang"
22 | "Fortran"
23 | "Groovy"
24 | "Haskell"
25 | "Java"
26 | "JavaScript"
27 | "Lisp"
28 | "Perl"
29 | "PHP"
30 | "Python"
31 | "Ruby"
32 | "Scala"
33 | "Scheme"])
34 |
35 | (defn home-did-mount []
36 | (js/$ (fn []
37 | (.autocomplete (js/$ "#tags")
38 | (clj->js {:source tags})))))
39 |
40 | (defn home []
41 | (r/create-class {:reagent-render home-render
42 | :component-did-mount home-did-mount}))
43 |
44 | (defn ^:export main []
45 | (rdom/render [home]
46 | (.getElementById js/document "app")))
47 |
48 |
--------------------------------------------------------------------------------
/recipes/bootstrap-datepicker/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [bootstrap-datepicker](https://github.com/eternicode/bootstrap-datepicker) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | We are going to follow this [example](http://runnable.com/UmOlOZbXvZRqAABU/bootstrap-datepicker-example-text-input-with-specifying-date-format2).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Add text input to `home-render`
14 | 4. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
15 | 5. Use `home-render` and `home-did-mount` to create a reagent component called `home`
16 | 6. Add externs
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc bootstrap-datepicker
22 | ```
23 |
24 | #### Step 2: Add necessary items to `resources/public/index.html`
25 |
26 | ```html
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ```
48 |
49 | #### Step 3: Add text input to `home-render`
50 |
51 | Navigate to `src/cljs/bootstrap_datepicker/core.cljs`.
52 |
53 | ```clojure
54 | (defn home-render []
55 | [:input {:type "text" :placeholder "click to show datepicker"}])
56 | ```
57 |
58 | #### Step 4: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
59 |
60 | This is the javascript we need.
61 |
62 | ```javascript
63 | $(document).ready(function () {
64 | $('#example1').datepicker({
65 | format: "dd/mm/yyyy"
66 | });
67 | });
68 | ```
69 |
70 | Let's convert this to clojurescript and place in `home-did-mount`
71 |
72 | ```clojure
73 | (defn home-did-mount []
74 | (.ready (js/$ js/document)
75 | (fn [] (.datepicker (js/$ "#example1") (clj->js {:format "dd/mm/yyyy"})))))
76 | ```
77 |
78 | The `.ready` method is used to assure that the DOM node exists on the page before executing the `.datepicker` method. However, since we are tapping into the did-mount lifecycle of the component, we are already assured that the component will exist. In addition, rather than using jQuery to select the element by id, we can get the DOM node directly using React/Reagent. Let's refactor the code as follows:
79 |
80 | ```clojure
81 | (defn home-did-mount [this]
82 | (.datepicker (js/$ (reagent/dom-node this)) (clj->js {:format "dd/mm/yyyy"})))
83 | ```
84 |
85 | #### Step 5: Use `home-render` and `home-did-mount` to create a reagent component called `home`
86 |
87 | ```clojure
88 | (defn home []
89 | (reagent/create-class {:reagent-render home-render
90 | :component-did-mount home-did-mount}))
91 | ```
92 |
93 | #### Step 6: Add externs
94 |
95 | For advanced compilation, we need to protect `$.datepicker` from getting renamed. Add an `externs.js` file.
96 |
97 | ```js
98 | var $ = function(){};
99 | $.datepicker = function(){};
100 | ```
101 |
102 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
103 |
104 | ```clojure
105 | :externs ["externs.js"]
106 | ```
107 |
108 | # Usage
109 |
110 | Compile cljs files.
111 |
112 | ```
113 | $ lein clean
114 | $ lein cljsbuild once prod
115 | ```
116 |
117 | Open `resources/public/index.html`.
118 |
--------------------------------------------------------------------------------
/recipes/bootstrap-datepicker/externs.js:
--------------------------------------------------------------------------------
1 | var $ = function(){};
2 | $.datepicker = function(){};
3 |
--------------------------------------------------------------------------------
/recipes/bootstrap-datepicker/project.clj:
--------------------------------------------------------------------------------
1 | (defproject bootstrap-datepicker "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/bootstrap-datepicker/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/recipes/bootstrap-datepicker/src/cljs/bootstrap_datepicker/core.cljs:
--------------------------------------------------------------------------------
1 | (ns bootstrap-datepicker.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (defn home-render []
7 | [:input {:type "text" :placeholder "click to show datepicker"}])
8 |
9 | (defn home-did-mount [this]
10 | (.datepicker (js/$ (rdom/dom-node this)) (clj->js {:format "dd/mm/yyyy"})))
11 |
12 | (defn home []
13 | (reagent/create-class {:reagent-render home-render
14 | :component-did-mount home-did-mount}))
15 |
16 | (defn ^:export main []
17 | (rdom/render [home]
18 | (.getElementById js/document "app")))
19 |
--------------------------------------------------------------------------------
/recipes/bootstrap-modal/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add a [modal window](http://getbootstrap.com/javascript/) to your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to use the [reagent-modals](https://github.com/Frozenlock/reagent-modals) library.
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Add reagent-modals to `project.clj`
14 | 4. Add reagent-modals to `src/cljs/bootstrap_modal/core.cljs` namespace
15 | 5. Add modal-window to `home`
16 | 6. Create a button to bring up the modal window
17 | 7. Add `modal-window-button` to `home`
18 |
19 | #### Step 1: Create a new project
20 |
21 | ```
22 | $ lein new rc bootstrap-modal
23 | ```
24 |
25 | #### Step 2: Add necessary items to `resources/public/index.html`
26 |
27 | ```html
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ```
46 |
47 | #### Step 3: Add reagent-modals to `project.clj`
48 |
49 | Add the following to the `:dependencies` vector.
50 |
51 | ```clojure
52 | [org.clojars.frozenlock/reagent-modals "0.2.3"]
53 | ```
54 |
55 | #### Step 4: Add reagent-modals to `src/cljs/bootstrap_modal/core.cljs` namespace
56 |
57 | ```clojure
58 | (ns bootstrap-modal.core
59 | (:require [reagent.core :as reagent]
60 | [reagent-modals.modals :as reagent-modals]))
61 | ```
62 |
63 | #### Step 5: Add modal-window to `home`
64 |
65 | ```clojure
66 | (defn home []
67 | [:div
68 | [reagent-modals/modal-window]
69 | ])
70 | ```
71 | #### Step 6: Create a button to bring up the modal window
72 |
73 | ```clojure
74 | (defn modal-window-button []
75 | [:div.btn.btn-primary
76 | {:on-click #(reagent-modals/modal! [:div "some message to the user!"])}
77 | "My Modal"])
78 | ```
79 |
80 | #### Step 7: Add `modal-window-button` to `home`
81 |
82 | ```clojure
83 | (defn home []
84 | [:div
85 | [reagent-modals/modal-window]
86 | ;; ATTNETION \/
87 | [modal-window-button]
88 | ;; ATTENTION /\
89 | ])
90 | ```
91 |
92 | # Usage
93 |
94 | Compile cljs files.
95 |
96 | ```
97 | $ lein clean
98 | $ lein cljsbuild once prod
99 | ```
100 |
101 | Open `resources/public/index.html`.
102 |
--------------------------------------------------------------------------------
/recipes/bootstrap-modal/project.clj:
--------------------------------------------------------------------------------
1 | (defproject bootstrap-modal "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [org.clojars.frozenlock/reagent-modals "0.2.8"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.1.8"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false}}]})
18 |
--------------------------------------------------------------------------------
/recipes/bootstrap-modal/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/recipes/bootstrap-modal/src/cljs/bootstrap_modal/core.cljs:
--------------------------------------------------------------------------------
1 | (ns bootstrap-modal.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]
5 | [reagent-modals.modals :as reagent-modals]))
6 |
7 | (defn modal-window-button []
8 | [:div.btn.btn-primary
9 | {:on-click #(reagent-modals/modal! [:div "some message to the user!"])}
10 | "My Modal"])
11 |
12 | (defn home []
13 | [:div
14 | [reagent-modals/modal-window]
15 | ;; ATTENTION \/
16 | [modal-window-button]
17 | ;; ATTENTION /\
18 | ])
19 |
20 | (defn ^:export main []
21 | (rdom/render [home]
22 | (.getElementById js/document "app")))
23 |
24 |
--------------------------------------------------------------------------------
/recipes/canvas-fills-div/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want a canvas that fills a div in at least one dimension,
4 | as the browser window is resized.
5 |
6 | # Solution
7 |
8 | We are going to use `create-class` and a Reagent `atom` to listen for
9 | events necessary to ensure that the canvas within a div fills the
10 | containing div. As the window changes size,
11 |
12 | *Steps*
13 |
14 | 1. Create a new project
15 | 2. Add CSS to `resources/public/index.html`
16 | 3. Add an atom and event handler for tracking the size of the window
17 | 4. Create a `div-with-canvas` component
18 | 5. Create a function to draw the contents of the canvas
19 | 6. Refer to the `div-with-canvas` component in the `home` definition.
20 |
21 | #### Step 1: Create a new project
22 |
23 | ```
24 | $ lein new rc canvas-fills-div
25 | ```
26 |
27 | #### Step 2: Add CSS to `resources/public/index.html`
28 |
29 | Add some simple css styling to get a div that fills the window. Most
30 | of this is here to ensure that the `with-canvas` div fills all
31 | available vertical space. If all you need is a div/canvas that fills
32 | the width, all but the `display: block` can be removed.
33 |
34 |
35 | ```html
36 |
37 |
60 |
61 | ```
62 |
63 | #### Step 3: Add an atom and event handler for tracking the size of the window
64 |
65 | Define a Reagent atom to track the current width of the window. The
66 | canvas component will refer to this atom so that it's re-rendered as the
67 | window size changes.
68 |
69 | ```clojure
70 | (def window-width (reagent/atom nil))
71 | ```
72 |
73 | Define an event handler to update `window-width` when the window
74 | resizes. The size of the canvas is taken from the enclosing div, so
75 | the main point of this is that the atom is updated rather than the
76 | specific value.
77 |
78 | ```clojure
79 | (defn on-window-resize [ evt ]
80 | (reset! window-width (.-innerWidth js/window)))
81 | ```
82 |
83 | Add `on-window-resize` as a window event listener for resize events.
84 |
85 | ```clojure
86 | (defn ^:export main []
87 | (reagent/render [home]
88 | (.getElementById js/document "app"))
89 | (.addEventListener js/window "resize" on-window-resize))
90 | ```
91 |
92 | #### Step 4: Create a `div-with-canvas` component
93 |
94 | Navigate to `src/cljs/div_fills_canvasc/core.cljs`. This uses
95 | `create-class` because we need to capture the DOM node at the time the
96 | component is mounted as well as render into the canvas when the
97 | component updates.
98 |
99 | ```clojure
100 | (defn div-with-canvas [ ]
101 | (let [dom-node (reagent/atom nil)]
102 | (reagent/create-class
103 | {:component-did-update
104 | (fn [ this ]
105 | (draw-canvas-contents (.-firstChild @dom-node)))
106 |
107 | :component-did-mount
108 | (fn [ this ]
109 | (reset! dom-node (reagent/dom-node this)))
110 |
111 | :reagent-render
112 | (fn [ ]
113 | @window-width ;; Trigger re-render on window resizes
114 | [:div.with-canvas
115 | ;; reagent-render is called before the compoment mounts, so
116 | ;; protect against the null dom-node that occurs on the first
117 | ;; render
118 | [:canvas (if-let [ node @dom-node ]
119 | {:width (.-clientWidth node)
120 | :height (.-clientHeight node)})]])})))
121 | ```
122 |
123 | #### Step 5: Create a function to draw the contents of the canvas
124 |
125 | ```clojure
126 | (defn draw-canvas-contents [ canvas ]
127 | (let [ ctx (.getContext canvas "2d")
128 | w (.-clientWidth canvas)
129 | h (.-clientHeight canvas)]
130 | (.beginPath ctx)
131 | (.moveTo ctx 0 0)
132 | (.lineTo ctx w h)
133 | (.moveTo ctx w 0)
134 | (.lineTo ctx 0 h)
135 | (.stroke ctx)))
136 | ```
137 |
138 | #### Step 6: Refer to the `div-with-canvas` component in the `home` definition.
139 |
140 | ```clojure
141 | (defn home []
142 | [div-with-canvas])
143 | ```
144 |
145 | # Usage
146 |
147 | Compile cljs files.
148 |
149 | ```
150 | $ lein clean
151 | $ lein cljsbuild once prod
152 | ```
153 |
154 | Open `resources/public/index.html`.
155 |
--------------------------------------------------------------------------------
/recipes/canvas-fills-div/project.clj:
--------------------------------------------------------------------------------
1 | (defproject canvas-fills-div "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/canvas-fills-div/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/recipes/canvas-fills-div/src/cljs/canvas_fills_div/core.cljs:
--------------------------------------------------------------------------------
1 | (ns canvas-fills-div.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (def window-width (reagent/atom nil))
7 |
8 | (defn draw-canvas-contents [ canvas ]
9 | (let [ ctx (.getContext canvas "2d")
10 | w (.-clientWidth canvas)
11 | h (.-clientHeight canvas)]
12 | (.beginPath ctx)
13 | (.moveTo ctx 0 0)
14 | (.lineTo ctx w h)
15 | (.moveTo ctx w 0)
16 | (.lineTo ctx 0 h)
17 | (.stroke ctx)))
18 |
19 | (defn div-with-canvas [ ]
20 | (let [dom-node (reagent/atom nil)]
21 | (reagent/create-class
22 | {:component-did-update
23 | (fn [ this ]
24 | (draw-canvas-contents (.-firstChild @dom-node)))
25 |
26 | :component-did-mount
27 | (fn [ this ]
28 | (reset! dom-node (rdom/dom-node this)))
29 |
30 | :reagent-render
31 | (fn [ ]
32 | @window-width
33 | [:div.with-canvas
34 | [:canvas (if-let [ node @dom-node ]
35 | {:width (.-clientWidth node)
36 | :height (.-clientHeight node)})]])})))
37 |
38 | (defn home []
39 | [div-with-canvas])
40 |
41 | (defn on-window-resize [ evt ]
42 | (reset! window-width (.-innerWidth js/window)))
43 |
44 | (defn ^:export main []
45 | (rdom/render [home]
46 | (.getElementById js/document "app"))
47 | (.addEventListener js/window "resize" on-window-resize))
48 |
49 |
--------------------------------------------------------------------------------
/recipes/compare-argv/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to identify which argument(s) triggered the `:component-will-update` method in your [Reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | The `:component-will-update` handler provides the new values of the component's parameters via `new-argv`.
8 | Using the Reagent api, we can also extract the prior values of the component arguments.
9 | By comparing the new values with the old, we can identify which arguments triggered the update.
10 |
11 | *Steps*
12 |
13 | 1. Create a new project
14 | 2. Add css to `resources/public/index.html`
15 | 3. Create a component which compares prior vs current arguments
16 | 4. Update the `home` component to have two atoms and buttons to increment each one
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc compare-argv
22 | ```
23 |
24 | #### Step 2: Add css to `resources/public/index.html`
25 |
26 | Add some simple css styling which will be used to highlight the most recently updated atom.
27 | Add the following html:
28 |
29 | ```html
30 |
31 |
34 |
35 | ```
36 |
37 | #### Step 3: Create a component which compares prior vs current arguments
38 |
39 | Navigate to `src/cljs/compare_argv/core.cljs`.
40 | Create the following component. In the `component-will-update` function, we call `(reagent/argv this)` which
41 | will return the list of current values of the component arguments. These are compared with new-argv. Depending on
42 | which argument changed, the `last-updated` atom will be updated to reflect which changed.
43 |
44 | ```clojure
45 | (defn update-highlighter
46 | "Displays two vals and highlights the most recently updated one"
47 | [val1 val2]
48 | (let [last-updated (reagent/atom 0)]
49 | (reagent/create-class
50 | {:component-will-update (fn [this new-argv]
51 | (let [[_ old-v1 old-v2] (reagent/argv this)
52 | [_ new-v1 new-v2] new-argv]
53 | (reset! last-updated
54 | (cond
55 | (> new-v1 old-v1) 1
56 | (> new-v2 old-v2) 2
57 | :else 0))))
58 | :reagent-render (fn [val1 val2]
59 | [:div
60 | ; display the current value of each,
61 | ; and highlight which was last updated
62 | [:div (when (= 1 @last-updated) {:class "selectedRow"})
63 | (str "val1 = " val1)]
64 | [:div (when (= 2 @last-updated) {:class "selectedRow"})
65 | (str "val2 = " val2)]])})))
66 | ```
67 |
68 | #### Step 4: Update the `home` component to have two atoms and buttons to increment each one
69 |
70 |
71 | ```clojure
72 | (defn home []
73 | (let [v1 (reagent/atom 0)
74 | v2 (reagent/atom 0)]
75 | (fn []
76 | [:div
77 | [:button {:on-click #(swap! v1 inc)} "inc val1"]
78 | [:button {:on-click #(swap! v2 inc)} "inc val2"]
79 | [update-highlighter @v1 @v2]])))
80 | ```
81 |
82 |
83 | # Usage
84 |
85 | Compile cljs files.
86 |
87 | ```
88 | $ lein clean
89 | $ lein cljsbuild once prod
90 | ```
91 |
92 | Open `resources/public/index.html`.
93 |
--------------------------------------------------------------------------------
/recipes/compare-argv/project.clj:
--------------------------------------------------------------------------------
1 | (defproject compare-argv "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/compare-argv/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/compare-argv/src/cljs/compare_argv/core.cljs:
--------------------------------------------------------------------------------
1 | (ns compare-argv.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 |
7 |
8 | (defn update-highlighter
9 | "Displays two vals and highlights the most recently updated one"
10 | [val1 val2]
11 | (let [last-updated (reagent/atom 0)]
12 | (reagent/create-class
13 | {:component-will-update (fn [this new-argv]
14 | (let [[_ old-v1 old-v2] (reagent/argv this)
15 | [_ new-v1 new-v2] new-argv]
16 | (reset! last-updated
17 | (cond
18 | (> new-v1 old-v1) 1
19 | (> new-v2 old-v2) 2
20 | :else 0))))
21 | :reagent-render (fn [val1 val2]
22 | [:div
23 | ; display the current value of each,
24 | ; and highlight which was last updated
25 | [:div (when (= 1 @last-updated) {:class "selectedRow"})
26 | (str "val1 = " val1)]
27 | [:div (when (= 2 @last-updated) {:class "selectedRow"})
28 | (str "val2 = " val2)]])})))
29 |
30 |
31 | (defn home []
32 | (let [v1 (reagent/atom 0)
33 | v2 (reagent/atom 0)]
34 | (fn []
35 | [:div
36 | [:button {:on-click #(swap! v1 inc)} "inc val1"]
37 | [:button {:on-click #(swap! v2 inc)} "inc val2"]
38 | [update-highlighter @v1 @v2]])))
39 |
40 |
41 | (defn ^:export main []
42 | (rdom/render [home]
43 | (.getElementById js/document "app")))
44 |
45 |
--------------------------------------------------------------------------------
/recipes/data-tables/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [DataTables](http://www.datatables.net/) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | We are going to follow this [example](http://www.datatables.net/examples/basic_init/zero_configuration.html).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Add table element with unique id to `home-render`
14 | 4. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
15 | 5. Use `home-render` and `home-did-mount` to create a reagent component called `home`
16 | 6. Add externs
17 |
18 | ## NB This is for static-data only
19 |
20 | DataTables will only work so long as React never has to update the DOM after it's been modified by jQuery. If
21 | you intend to modify the content of the table from within Reagent then DataTables will stop working predictably.
22 |
23 | #### Step 1: Create a new project
24 |
25 | ```
26 | $ lein new rc data-tables
27 | ```
28 |
29 | #### Step 2: Add necessary items to `resources/public/index.html`
30 |
31 | ```html
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ```
54 |
55 | #### Step 3: Add table element with unique id to `home-render`
56 |
57 | Navigate to `src/cljs/data_tables/core.cljs`.
58 |
59 | ```clojure
60 | (defn home-render []
61 | [:table.table.table-striped.table-bordered
62 | {:cell-spacing "0" :width "100%"}
63 |
64 | [:thead>tr
65 | [:th "Name"]
66 | [:th "Age"]]
67 |
68 | [:tbody
69 | [:tr
70 | [:td "Matthew"]
71 | [:td "26"]]
72 |
73 | [:tr
74 | [:td "Anna"]
75 | [:td "24"]]
76 |
77 | [:tr
78 | [:td "Michelle"]
79 | [:td "42"]]
80 |
81 | [:tr
82 | [:td "Frank"]
83 | [:td "46"]]
84 | ]])
85 | ```
86 |
87 | #### Step 4: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
88 |
89 | This is the javascript we need.
90 |
91 | ```javascript
92 | $(document).ready(function() {
93 | $('#example').DataTable();
94 | } );
95 | ```
96 |
97 | Let's convert this to clojurescript and place in `home-did-mount`
98 |
99 | ```clojure
100 | (defn home-did-mount []
101 | (.ready (js/$ js/document)
102 | (fn []
103 | (.DataTable (js/$ "#example")))))
104 | ```
105 |
106 | The `.ready` method is used to assure that the DOM node exists on the page before executing the `.DataTable` method. However, since we are tapping into the did-mount lifecycle of the component, we are already assured that the component will exist. In addition, rather than using jQuery to select the element by id, we can get the DOM node directly using React/Reagent. Let's refactor the code as follows:
107 |
108 | ```clojure
109 | (defn home-did-mount [this]
110 | (.DataTable (js/$ (reagent/dom-node this))))
111 | ```
112 |
113 |
114 | #### Step 5: Use `home-render` and `home-did-mount` to create a reagent component called `home`
115 |
116 | ```clojure
117 | (defn home []
118 | (reagent/create-class {:reagent-render home-render
119 | :component-did-mount home-did-mount}))
120 | ```
121 |
122 | #### Step 6: Add externs
123 |
124 | For advanced compilation, we need to protect `$.DataTable` from getting renamed. Add an `externs.js` file.
125 |
126 | ```js
127 | var $ = function(){};
128 | $.DataTable = function(){};
129 | ```
130 |
131 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
132 |
133 | ```clojure
134 | :externs ["externs.js"]
135 | ```
136 |
137 | # Usage
138 |
139 | Compile cljs files.
140 |
141 | ```
142 | $ lein clean
143 | $ lein cljsbuild once prod
144 | ```
145 |
146 | Open `resources/public/index.html`.
147 |
--------------------------------------------------------------------------------
/recipes/data-tables/externs.js:
--------------------------------------------------------------------------------
1 | var $ = function(){};
2 | $.DataTable = function(){};
3 |
--------------------------------------------------------------------------------
/recipes/data-tables/project.clj:
--------------------------------------------------------------------------------
1 | (defproject data-tables "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/data-tables/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/recipes/data-tables/src/cljs/data_tables/core.cljs:
--------------------------------------------------------------------------------
1 | (ns data-tables.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (defn home-render []
7 | [:table.table.table-striped.table-bordered
8 | {:cell-spacing "0" :width "100%"}
9 |
10 | [:thead>tr
11 | [:th "Name"]
12 | [:th "Age"]]
13 |
14 | [:tbody
15 | [:tr
16 | [:td "Matthew"]
17 | [:td "26"]]
18 |
19 | [:tr
20 | [:td "Anna"]
21 | [:td "24"]]
22 |
23 | [:tr
24 | [:td "Michelle"]
25 | [:td "42"]]
26 |
27 | [:tr
28 | [:td "Frank"]
29 | [:td "46"]]
30 | ]])
31 |
32 | (defn home-did-mount [this]
33 | (.DataTable (js/$ (rdom/dom-node this))))
34 |
35 |
36 | (defn home []
37 | (reagent/create-class {:reagent-render home-render
38 | :component-did-mount home-did-mount}))
39 |
40 | (defn ^:export main []
41 | (rdom/render [home]
42 | (.getElementById js/document "app")))
43 |
44 |
--------------------------------------------------------------------------------
/recipes/draggable/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add a [draggable](http://jqueryui.com/draggable/) element in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to follow this [example](http://jqueryui.com/draggable/).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Create div that will contain the draggable element in `home-render`
14 | 4. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
15 | 5. Use `home-render` and `home-did-mount` to create a reagent component called `home`
16 |
17 | #### Step 1: Create a new project
18 |
19 | ```
20 | $ lein new rc draggable
21 | ```
22 |
23 | #### Step 2: Add necessary items to `resources/public/index.html`
24 |
25 | ```html
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | ```
42 |
43 | #### Step 3: Create div that will contain the draggable element in `home-render`
44 |
45 | Navigate to `src/cljs/draggable/core.cljs`. To add a draggable element, we need the following:
46 |
47 | * parent div with a class of "ui-widget-content".
48 | * nested element inside div to be dragged
49 |
50 | ```clojure
51 | (defn home-render []
52 | [:div.ui-widget-content {:style {:width "150px"
53 | :height "150px"
54 | :padding "0.5em"}}
55 | [:p "Drag me around"]])
56 | ```
57 |
58 | #### Step 4: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
59 |
60 | This is the javascript we need.
61 |
62 | ```javascript
63 | $(function() {
64 | $( "#draggable" ).draggable();
65 | });
66 | ```
67 |
68 | Let's convert this to clojurescript and place in `home-did-mount`
69 |
70 | ```clojure
71 | (defn home-did-mount []
72 | (js/$ (fn []
73 | (.draggable (js/$ "#draggable")))))
74 | ```
75 |
76 | Rather than using jQuery to select the element by id, we can get the DOM node directly using React/Reagent. Let's refactor the code as follows:
77 |
78 | ```clojure
79 | (defn home-did-mount [this]
80 | (.draggable (js/$ (reagent/dom-node this))))
81 | ```
82 |
83 |
84 | #### Step 5: Use `home-render` and `home-did-mount` to create a reagent component called `home`
85 |
86 | ```clojure
87 | (defn home []
88 | (reagent/create-class {:reagent-render home-render
89 | :component-did-mount home-did-mount}))
90 | ```
91 |
92 | # Usage
93 |
94 | Compile cljs files.
95 |
96 | ```
97 | $ lein clean
98 | $ lein cljsbuild once prod
99 | ```
100 |
101 | Open `resources/public/index.html`.
102 |
--------------------------------------------------------------------------------
/recipes/draggable/project.clj:
--------------------------------------------------------------------------------
1 | (defproject draggable "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/draggable/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/draggable/src/cljs/draggable/core.cljs:
--------------------------------------------------------------------------------
1 | (ns draggable.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (defn home-render []
6 | [:div.ui-widget-content {:style {:width "150px"
7 | :height "150px"
8 | :padding "0.5em"}}
9 | [:p "Drag me around"]])
10 |
11 | (defn home-did-mount [this]
12 | (.draggable (js/$ (rdom/dom-node this))))
13 |
14 | (defn home []
15 | (reagent/create-class {:reagent-render home-render
16 | :component-did-mount home-did-mount}))
17 |
18 | (defn ^:export main []
19 | (rdom/render [home]
20 | (.getElementById js/document "app")))
21 |
22 |
--------------------------------------------------------------------------------
/recipes/droppable/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add a [droppable](http://jqueryui.com/droppable/) element in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to take inspiration from this [example](http://jqueryui.com/droppable/), but do it reagent-style.
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Create a `draggable` component
14 | 4. Create a `drop-area` component
15 | 5. Add `draggable` and `drop-area` to `home`
16 | 6. Add externs
17 |
18 | Prerequisite Recipes:
19 |
20 | * [draggable](https://github.com/reagent-project/reagent-cookbook/tree/master/recipes/draggable)
21 |
22 | #### Step 1: Create a new project
23 |
24 | ```
25 | $ lein new rc droppable
26 | ```
27 |
28 | #### Step 2: Add necessary items to `resources/public/index.html`
29 |
30 | ```html
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | ```
47 |
48 | #### Step 3: Create a `draggable` component
49 |
50 | ```clojure
51 | (defn draggable-render []
52 | [:div.ui-widget-content {:style {:width "100px"
53 | :height "100px"
54 | :padding "0.5em"
55 | :float "left"
56 | :margin "10px 10px 10px 0"}}
57 | [:p "Drag me to my target"]])
58 |
59 | (defn draggable-did-mount [this]
60 | (.draggable (js/$ (reagent/dom-node this))))
61 |
62 | (defn draggable []
63 | (reagent/create-class {:reagent-render draggable-render
64 | :component-did-mount draggable-did-mount}))
65 | ```
66 |
67 | #### Step 4: Create a `drop-area` component
68 |
69 | Let's create a reagent atom called `app-state` that will store the class and text of our drop-area component. Next, let's create the render function for our drop-area component, called `drop-area-render`, that listens to changes in `app-state`.
70 |
71 |
72 | ```clojure
73 | (def app-state (reagent/atom {:drop-area {:class "ui-widget-header"
74 | :text "Drop here"}}))
75 |
76 | (defn drop-area-render []
77 | (let [class (get-in @app-state [:drop-area :class])
78 | text (get-in @app-state [:drop-area :text])]
79 | [:div {:class class
80 | :style {:width "150px"
81 | :height "150px"
82 | :padding "0.5em"
83 | :float "left"
84 | :margin "10px"}}
85 | [:p text]]))
86 | ```
87 |
88 | We need to use jQuery UI's `.droppable` method on our drop-area component. On the `drop` event, we can pass a handler that will update our `app-state` with a new class and text.
89 |
90 | ```clojure
91 | (defn drop-area-did-mount [this]
92 | (.droppable (js/$ (reagent/dom-node this))
93 | #js {:drop (fn []
94 | (swap! app-state assoc-in [:drop-area :class] "ui-widget-header ui-state-highlight")
95 | (swap! app-state assoc-in [:drop-area :text] "Dropped!"))}))
96 | ```
97 |
98 | Finally, let's create our `drop-area` component by combining `drop-area-did-mount` with `drop-area-render`.
99 |
100 | ```clojure
101 | (defn drop-area []
102 | (reagent/create-class {:reagent-render drop-area-render
103 | :component-did-mount drop-area-did-mount}))
104 | ```
105 |
106 | #### Step 5: Add `draggable` and `drop-area` to `home`
107 |
108 | ```clojure
109 | (defn home []
110 | [:div
111 | [draggable]
112 | [drop-area]])
113 | ```
114 |
115 | #### Step 6: Add externs
116 |
117 | For advanced compilation, we need to protect `$.draggable` and `$.droppable` from getting renamed. Add an `externs.js` file.
118 |
119 | ```js
120 | var $ = function(){};
121 | $.draggable = function(){};
122 | $.droppable = function(){};
123 | ```
124 |
125 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
126 |
127 | ```clojure
128 | :externs ["externs.js"]
129 | ```
130 |
131 | # Usage
132 |
133 | Compile cljs files.
134 |
135 | ```
136 | $ lein clean
137 | $ lein cljsbuild once prod
138 | ```
139 |
140 | Open `resources/public/index.html`.
141 |
142 |
--------------------------------------------------------------------------------
/recipes/droppable/externs.js:
--------------------------------------------------------------------------------
1 | var $ = function(){};
2 | $.draggable = function(){};
3 | $.droppable = function(){};
4 |
--------------------------------------------------------------------------------
/recipes/droppable/project.clj:
--------------------------------------------------------------------------------
1 | (defproject droppable "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/droppable/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/droppable/src/cljs/droppable/core.cljs:
--------------------------------------------------------------------------------
1 | (ns droppable.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (def app-state (reagent/atom {:drop-area {:class "ui-widget-header"
7 | :text "Drop here"}}))
8 |
9 | (defn draggable-render []
10 | [:div.ui-widget-content {:style {:width "100px"
11 | :height "100px"
12 | :padding "0.5em"
13 | :float "left"
14 | :margin "10px 10px 10px 0"}}
15 | [:p "Drag me to my target"]])
16 |
17 | (defn draggable-did-mount [this]
18 | (.draggable (js/$ (rdom/dom-node this))))
19 |
20 | (defn draggable []
21 | (reagent/create-class {:reagent-render draggable-render
22 | :component-did-mount draggable-did-mount}))
23 |
24 | (defn drop-area-render []
25 | (let [class (get-in @app-state [:drop-area :class])
26 | text (get-in @app-state [:drop-area :text])]
27 | [:div {:class class
28 | :style {:width "150px"
29 | :height "150px"
30 | :padding "0.5em"
31 | :float "left"
32 | :margin "10px"}}
33 | [:p text]]))
34 |
35 | (defn drop-area-did-mount [this]
36 | (.droppable (js/$ (rdom/dom-node this))
37 | #js {:drop (fn []
38 | (swap! app-state assoc-in [:drop-area :class] "ui-widget-header ui-state-highlight")
39 | (swap! app-state assoc-in [:drop-area :text] "Dropped!"))}))
40 |
41 | (defn drop-area []
42 | (reagent/create-class {:reagent-render drop-area-render
43 | :component-did-mount drop-area-did-mount}))
44 |
45 | (defn home []
46 | [:div
47 | [draggable]
48 | [drop-area]])
49 |
50 | (defn ^:export main []
51 | (rdom/render [home]
52 | (.getElementById js/document "app")))
53 |
54 |
--------------------------------------------------------------------------------
/recipes/editable-label/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to edit a label.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add necessary items to `resources/public/index.html`
11 | 3. Create 'label-edit' component
12 | 4. Call the 'label-edit' component with the starting label name
13 |
14 | #### Step 1: Create a new project
15 |
16 | ```
17 | $ lein new rc editable-label
18 | ```
19 |
20 | #### Step 2: Add necessary items to `resources/public/index.html`
21 |
22 | ```html
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ```
37 |
38 | From here on out we will be working in `src/cljs/filter_table/core.cljs`
39 |
40 | #### Step 3: Create 'label-edit' component
41 |
42 | Double-clicking will change the 'label' to a 'input.text' field, hitting 'ENTER'
43 | or moving your mouse out of the element will save the inputed text and convert
44 | the 'input.text' back into a 'label'
45 |
46 | ```clojure
47 | (defn label-edit [item]
48 | (let [form (reagent/atom item)
49 | edit? (reagent/atom false)
50 | input-component (fn []
51 | [:input.text
52 | {:value @form
53 | :on-change #(reset! form (-> % .-target .-value))
54 | :on-key-press (fn [e]
55 | (let [enter? (= 13 (.-charCode e))]
56 | (when enter?
57 | (reset! edit? false)
58 | (reset! form @form))))
59 | :on-mouse-leave #(do (reset! edit? false)
60 | (reset! form @form))}])]
61 | (fn [item]
62 | [:div
63 | (if-not @edit?
64 | [:label {:on-double-click #(reset! edit? true)} @form]
65 | [input-component])])))
66 | ```
67 | #### Step 4: Call 'label-edit' component with the initial label name
68 |
69 | ```clojure
70 | (defn home []
71 | [:div.container
72 | [:h1 "Editing Labels"]
73 | [label-edit (str "Double-click to edit")]
74 | [label-edit (str "Edit me!!")]])
75 | ```
76 |
77 | # Usage
78 |
79 | Compile cljs files.
80 |
81 | ```
82 | $ lein clean
83 | $ lein cljsbuild once prod
84 | ```
85 |
86 | Open `resources/public/index.html`.
87 |
--------------------------------------------------------------------------------
/recipes/editable-label/project.clj:
--------------------------------------------------------------------------------
1 | (defproject editable-label "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/editable-label/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/editable-label/src/cljs/editable_label/core.cljs:
--------------------------------------------------------------------------------
1 | (ns editable-label.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (defn label-edit [item]
6 | (let [form (reagent/atom item)
7 | edit? (reagent/atom false)
8 | input-component (fn []
9 | [:input.text
10 | {:value @form
11 | :on-change #(reset! form (-> % .-target .-value))
12 | :on-key-press (fn [e]
13 | (let [enter? (= 13 (.-charCode e))]
14 | (when enter?
15 | (reset! edit? false)
16 | (reset! form @form))))
17 | :on-mouse-leave #(do (reset! edit? false)
18 | (reset! form @form))}])]
19 | (fn [item]
20 | [:div
21 | (if-not @edit?
22 | [:label {:on-double-click #(reset! edit? true)} @form]
23 | [input-component])])))
24 |
25 | (defn home []
26 | [:div.container
27 | [:h1 "Editing Labels"]
28 | [label-edit (str "Double-click to edit")]
29 | [label-edit (str "Edit me!!")]])
30 |
31 | (defn ^:export main []
32 | (rdom/render [home]
33 | (.getElementById js/document "app")))
34 |
--------------------------------------------------------------------------------
/recipes/file-upload/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to be able to upload files.
4 |
5 | # Solution
6 |
7 | We are going to use the [filestack](https://www.filestack.com/) service.
8 |
9 | *Steps*
10 |
11 | 1. Sign up and get a free api key
12 | 2. Create a new project
13 | 3. Add filestack to `resources/public/index.html`
14 | 4. Add promesa to `project.clj` :dependencies vector
15 | 5. Add promesa to `core.cljs` namespace
16 | 6. Create a button to upload an image, `btn-upload-image`
17 | 7. Add `btn-upload-image` to `home`
18 | 8. Add externs
19 |
20 | #### Step 1: Sign up and get a free api key
21 |
22 | Go to [filestack](https://www.filestack.com/) and create a free account. You will need to create a 'New application' to get an 'API Key'.
23 |
24 | #### Step 2: Create a new project
25 |
26 | ```
27 | $ lein new rc file-upload
28 | ```
29 |
30 | #### Step 3: Add filestack to `resources/public/index.html`
31 |
32 | ```html
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | ```
47 |
48 | #### Step 4: Add promesa to `project.clj` :dependencies vector
49 |
50 | ```clojure
51 | [funcool/promesa "1.8.1"]
52 | ```
53 |
54 | #### Step 5: Add promesa to `core.cljs` namespace
55 |
56 | Navigate to `src/cljs/file_upload/core.cljs` and update the `ns` to the following.
57 |
58 | ```clojure
59 | (ns file-upload.core
60 | (:require
61 | [reagent.core :as reagent]
62 | [promesa.core :as p]))
63 | ```
64 |
65 | #### Step 6: Create button to upload an image, `btn-upload-image`
66 |
67 | The example pick code looks like this:
68 |
69 | ```html
70 | var client = filestack.init('yourApiKey');
71 | client.pick(pickerOptions);
72 |
73 | client.pick({
74 | accept: 'image/*',
75 | maxFiles: 5
76 | }).then(function(result) {
77 | console.log(JSON.stringify(result.filesUploaded))
78 | })
79 | ```
80 |
81 | Let's convert this to clojurescript - be sure to use the API Key that you got from step 1.
82 |
83 | ```clojure
84 | (defn btn-upload-image []
85 | ;; TODO: update XXXX with your api key
86 | (let [client (.init js/filestack "XXXX")]
87 | [:button
88 | {:on-click (fn []
89 | (-> (p/promise
90 | (.pick client (clj->js {:accept "image/*"
91 | :maxFiles 5})))
92 | (p/then #(let [files-uploaded (.-filesUploaded %)
93 | file (aget files-uploaded 0)
94 | file-url (.-url file)]
95 | (js/console.log "URL of file:" file-url)))))}
96 | "Upload Image"]))
97 | ```
98 |
99 | #### Step 7: Add `btn-upload-image` to `home`
100 |
101 | ```clojure
102 | (defn home []
103 | [:div
104 | [btn-upload-image]
105 | ])
106 | ```
107 |
108 | #### Step 8: Add externs
109 |
110 | For advanced compilation, we need to protect names used for interop from getting renamed. Add an `externs.js` file.
111 |
112 | ```js
113 | var TopLevel = {
114 | "filestack" : function () {},
115 | "filesUploaded" : function () {},
116 | "init" : function () {},
117 | "log" : function () {},
118 | "pick" : function () {},
119 | "url" : function () {}
120 | }
121 | ```
122 |
123 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
124 |
125 | ```clojure
126 | :externs ["externs.js"]
127 | ```
128 |
129 | # Usage
130 |
131 | Compile cljs files.
132 |
133 | ```
134 | $ lein clean
135 | $ lein cljsbuild once prod
136 | ```
137 |
138 | Open `resources/public/index.html`.
139 |
140 | Upload a couple files, then navigate to your app on filestack. You can see the uploaded files under the *Assets* section.
141 |
--------------------------------------------------------------------------------
/recipes/file-upload/externs.js:
--------------------------------------------------------------------------------
1 | var TopLevel = {
2 | "filestack" : function () {},
3 | "filesUploaded" : function () {},
4 | "init" : function () {},
5 | "log" : function () {},
6 | "pick" : function () {},
7 | "url" : function () {}
8 | }
9 |
--------------------------------------------------------------------------------
/recipes/file-upload/project.clj:
--------------------------------------------------------------------------------
1 | (defproject file-upload "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [funcool/promesa "6.0.0"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.1.8"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false
18 | :externs ["externs.js"]}}]})
19 |
--------------------------------------------------------------------------------
/recipes/file-upload/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/recipes/file-upload/src/cljs/file_upload/core.cljs:
--------------------------------------------------------------------------------
1 | (ns file-upload.core
2 | (:require
3 | [reagent.core :as reagent]
4 | [reagent.dom :as rdom]
5 | [promesa.core :as p]))
6 |
7 |
8 | (defn btn-upload-image []
9 | ;; TODO: update XXXX with your api key
10 | (let [client (.init js/filestack "XXXX")]
11 | [:button
12 | {:on-click (fn []
13 | (-> (p/promise
14 | (.pick client (clj->js {:accept "image/*"
15 | :maxFiles 5})))
16 | (p/then #(let [files-uploaded (.-filesUploaded %)
17 | file (aget files-uploaded 0)
18 | file-url (.-url file)]
19 | (js/console.log "URL of file:" file-url)))))}
20 | "Upload Image"]))
21 |
22 | (defn home []
23 | [:div
24 | [btn-upload-image]
25 | ])
26 |
27 | (defn ^:export main []
28 | (rdom/render [home]
29 | (.getElementById js/document "app")))
30 |
--------------------------------------------------------------------------------
/recipes/filter-table/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to filter a table based on user input.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add necessary items to `resources/public/index.html`
11 | 3. Include `clojure.string`
12 | 4. Create a reagent atom called `app-state` holds the table data
13 | 5. Setup filter-content
14 | 6. Create the `table` component
15 | 7. Create the `search-table` component
16 | 8. Add `search-table` to home
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc filter-table
22 | ```
23 |
24 | #### Step 2: Add necessary items to `resources/public/index.html`
25 |
26 | ```html
27 |
28 |
29 |
30 |
31 |
32 |
33 | Filter Table
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | ```
47 |
48 | From here on out we will be working in `src/cljs/filter_table/core.cljs`
49 |
50 | #### Step 3: Include `clojure.string`
51 |
52 | We will use [clojure.string] to convert user input and the first-name to upper-case
53 | this will ensure we can search the entire field.
54 |
55 | ```clojure
56 | (ns filter-table.core
57 | (:require [reagent.core :as reagent]
58 | [clojure.string :as string]))
59 | ```
60 |
61 | #### Step 4: Create a reagent atom called `app-state`
62 |
63 |
64 | We store our table content in a reagent atom.
65 |
66 | ```clojure
67 | (def app-state
68 | (reagent/atom [{:id 1 :first-name "Jason" :last-name "Yates" :age "34"}
69 | {:id 2 :first-name "Chris" :last-name "Wilson" :age "33"}
70 | {:id 3 :first-name "John" :last-name "Lawrence" :age"32"}
71 | {:id 4 :first-name "Albert" :last-name "Voxel" :age "67"}
72 | {:id 5 :first-name "Zemby" :last-name "Alcoe" :age "495"}]))
73 | ```
74 |
75 | #### Step 5: Setup the filter-content
76 |
77 | The filter-content function will take a filterstring and return the filtered app-state
78 |
79 | ```clojure
80 | (defn filter-content
81 | [filterstring]
82 | (filter #(re-find (->> (str filterstring)
83 | (string/upper-case)
84 | (re-pattern))
85 | (string/upper-case (:first-name %)))
86 | @app-state))
87 | ```
88 |
89 | #### Step 6: Create the `table` component
90 |
91 | The table component takes the filter provided by the search-table component
92 | and returns the filtered app-state in a table.
93 |
94 | ```clojure
95 | (defn table
96 | [myfilter]
97 | [:table {:class "table table-condensed"}
98 | [:thead
99 | [:tr
100 | [:th "First Name"]
101 | [:th "Last Name"]
102 | [:th "Age"]]]
103 | [:tbody
104 | (for [{:keys [id
105 | first-name
106 | last-name
107 | age]} (filter-content myfilter)]
108 | ^{:key id}
109 | [:tr
110 | [:td first-name]
111 | [:td last-name]
112 | [:td age]])]])
113 | ```
114 |
115 | #### Step 7: Create the `search-table` component
116 |
117 | The search-table component gives us a text input which get's sent to the
118 | table component to filter the table.
119 |
120 | ```clojure
121 | (defn search-table
122 | []
123 | (let [filter-value (reagent/atom nil)]
124 | (fn []
125 | [:div
126 | [:input {:type "text" :value @filter-value
127 | :on-change #(reset! filter-value (-> % .-target .-value))}]
128 | [table @filter-value]])))
129 | ```
130 |
131 | #### Step 8: Add `search-table` to home
132 |
133 | ```clojure
134 | (defn home []
135 | [:div.container
136 | [search-table]])
137 | ```
138 |
139 | # Usage
140 |
141 | Compile cljs files.
142 |
143 | ```
144 | $ lein clean
145 | $ lein cljsbuild once prod
146 | ```
147 |
148 | Open `resources/public/index.html`.
149 |
--------------------------------------------------------------------------------
/recipes/filter-table/project.clj:
--------------------------------------------------------------------------------
1 | (defproject filter-table "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8" :exclusions [[org.clojure/clojure]]]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/filter-table/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Filter Table
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/recipes/filter-table/src/cljs/filter_table/core.cljs:
--------------------------------------------------------------------------------
1 | (ns filter-table.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]
4 | [clojure.string :as string]))
5 |
6 | (def app-state
7 | (reagent/atom [{:id 1 :first-name "Jason" :last-name "Yates" :age "34"}
8 | {:id 2 :first-name "Chris" :last-name "Wilson" :age "33"}
9 | {:id 3 :first-name "John" :last-name "Lawrence" :age "32"}
10 | {:id 4 :first-name "Albert" :last-name "Voxel" :age "67"}
11 | {:id 5 :first-name "Zemby" :last-name "Alcoe" :age "495"}]))
12 |
13 | (defn filter-content
14 | [filterstring]
15 | (filter #(re-find (->> (str filterstring)
16 | (string/upper-case)
17 | (re-pattern))
18 | (string/upper-case (:first-name %)))
19 | @app-state))
20 |
21 | (defn table
22 | [myfilter]
23 | [:table {:class "table table-condensed"}
24 | [:thead
25 | [:tr
26 | [:th "First Name"]
27 | [:th "Last Name"]
28 | [:th "Age"]]]
29 | [:tbody
30 | (for [{:keys [id
31 | first-name
32 | last-name
33 | age]} (filter-content myfilter)]
34 | ^{:key id}
35 | [:tr
36 | [:td first-name]
37 | [:td last-name]
38 | [:td age]])]])
39 |
40 | (defn search-table
41 | []
42 | (let [filter-value (reagent/atom nil)]
43 | (fn []
44 | [:div
45 | [:input {:type "text" :value @filter-value
46 | :on-change #(reset! filter-value (-> % .-target .-value))}]
47 | [table @filter-value]])))
48 |
49 | (defn home []
50 | [:div.container
51 | [search-table]])
52 |
53 | (defn ^:export main []
54 | (rdom/render [home]
55 | (.getElementById js/document "app")))
56 |
--------------------------------------------------------------------------------
/recipes/google-maps/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [Google Maps](https://developers.google.com/maps/documentation/javascript/) to your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to follow this [example](https://developers.google.com/maps/documentation/javascript/tutorial#HelloWorld).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Obtain a Google Maps API Key
13 | 3. Add necessary items to `resources/public/index.html`
14 | 4. Create a function called `home-render` with an empty div
15 | 5. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
16 | 6. Use `home-render` and `home-did-mount` to create a reagent component called `home`
17 | 7. Add externs
18 |
19 | #### Step 1: Create a new project
20 |
21 | ```
22 | $ lein new rc google-maps
23 | ```
24 |
25 | #### Step 2: Obtain a Google Maps API Key
26 |
27 | In order to use the Google Maps API, we must first obtain an API Key. Follow the steps [here](https://developers.google.com/maps/documentation/javascript/tutorial#api_key), then copy the API key.
28 |
29 | #### Step 3: Add necessary items to `resources/public/index.html`
30 |
31 | Add the script tag below, but replace `API_KEY` with you *actual* API key.
32 |
33 | ```html
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ```
50 |
51 | #### Step 4: Create a function called `home-render` with an empty div
52 |
53 | Navigate to `src/cljs/google_maps/core.cljs`.
54 |
55 | ```clojure
56 | (defn home-render []
57 | [:div {:style {:height "300px"}}
58 | ])
59 | ```
60 |
61 | #### Step 5: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
62 |
63 | To center a map around Sydney, this is the javascript we need.
64 |
65 | ```javascript
66 | var mapOptions = {
67 | center: { lat: -34.397, lng: 150.644},
68 | zoom: 8
69 | };
70 |
71 | var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
72 | ```
73 |
74 | Let's convert this to clojurescript and place in `home-did-mount`
75 |
76 | ```clojure
77 | (defn home-did-mount [this]
78 | (let [map-canvas (reagent/dom-node this)
79 | map-options (clj->js {"center" (js/google.maps.LatLng. -34.397, 150.644)
80 | "zoom" 8})]
81 | (js/google.maps.Map. map-canvas map-options)))
82 | ```
83 |
84 | #### Step 6: Use `home-render` and `home-did-mount` to create a reagent component called `home`
85 |
86 | ```clojure
87 | (defn home []
88 | (reagent/create-class {:reagent-render home-render
89 | :component-did-mount home-did-mount}))
90 | ```
91 |
92 | #### Step 7: Add externs
93 |
94 | For [advanced compilation](https://clojurescript.org/reference/advanced-compilation), we need to protect `google.maps.Map` and `google.maps.LatLng` from getting renamed. Add a dependency on the [cljsjs google-maps package](https://github.com/cljsjs/packages/tree/master/google-maps), which provides externs for the Google Maps API:
95 |
96 | ```clojure
97 | :dependencies [cljsjs/google-maps "3.18-1"]
98 | ```
99 |
100 | Alternatively [provide externs](https://clojurescript.org/reference/advanced-compilation#providing-externs) by creating an `externs.js` file:
101 |
102 | ```js
103 | google.maps = {};
104 | google.maps.Map = function(){};
105 | google.maps.LatLng = function() {};
106 | ```
107 |
108 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
109 |
110 | ```clojure
111 | :externs ["externs.js"]
112 | ```
113 |
114 | # Usage
115 |
116 | Compile cljs files.
117 |
118 | ```
119 | $ lein clean
120 | $ lein cljsbuild once prod
121 | ```
122 |
123 | Open `resources/public/index.html`.
124 |
--------------------------------------------------------------------------------
/recipes/google-maps/externs.js:
--------------------------------------------------------------------------------
1 | google.maps = {};
2 | google.maps.Map = function() {};
3 | google.maps.LatLng = function() {};
4 |
--------------------------------------------------------------------------------
/recipes/google-maps/project.clj:
--------------------------------------------------------------------------------
1 | (defproject google-maps "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/google-maps/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/google-maps/src/cljs/google_maps/core.cljs:
--------------------------------------------------------------------------------
1 | (ns google-maps.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (defn home-render []
7 | [:div {:style {:height "300px"}}
8 | ])
9 |
10 | (defn home-did-mount [this]
11 | (let [map-canvas (rdom/dom-node this)
12 | map-options (clj->js {"center" (js/google.maps.LatLng. -34.397, 150.644)
13 | "zoom" 8})]
14 | (js/google.maps.Map. map-canvas map-options)))
15 |
16 | (defn home []
17 | (reagent/create-class {:reagent-render home-render
18 | :component-did-mount home-did-mount}))
19 |
20 | (defn ^:export main []
21 | (rdom/render [home]
22 | (.getElementById js/document "app")))
23 |
--------------------------------------------------------------------------------
/recipes/google-street-view/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add a Google Street View image to your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to loosely follow this [example](https://www.udacity.com/course/viewer#!/c-ud110/l-3310298553/e-3180658599/m-3180658600) provided by [Udacity](https://www.udacity.com/).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Create the base part of the url for the street view image request
13 | 3. Create the address part of the url for the street view image request
14 | 4. Create a function that makes the full url for the street view image request
15 | 5. Create a reagent atom called `app-state` to store the street and city values
16 | 6. Create an `input` component where a user can change the value of a key in `app-state`
17 | 7. Create an `home` component that displays an image based on the street and city supplied by a user
18 |
19 | #### Step 1: Create a new project
20 |
21 | ```
22 | $ lein new rc google-street-view
23 | ```
24 |
25 | To request a google street view image with a url, you need two parts:
26 |
27 | 1. The "base" part
28 | 2. The address part
29 |
30 | #### Step 2: Create the base part of the url for the street view image request
31 |
32 | Navigate to `src/cljs/google_street_view/core.cljs`.
33 |
34 | ```clojure
35 | (def base-url
36 | "http://maps.googleapis.com/maps/api/streetview?size=600x400&location=")
37 | ```
38 |
39 | #### Step 3: Create the address part of the url for the street view image request
40 |
41 | ```clojure
42 | (defn address-url [street city]
43 | (str street ", " city))
44 | ```
45 |
46 | #### Step 4: Create a function that makes the full url for the street view image request
47 |
48 | ```clojure
49 | (defn street-view-url [street city]
50 | (str base-url
51 | (address-url street city)))
52 | ```
53 |
54 | #### Step 5: Create a reagent atom called `app-state` to store the street and city values
55 |
56 | ```clojure
57 | (def app-state (reagent/atom {:street "24 Willie Mays Plaza" :city "San Francisco"}))
58 | ```
59 |
60 | #### Step 6: Create an `input` component where a user can change the value of a key in `app-state`
61 |
62 | ```clojure
63 | (defn input [k]
64 | [:input {:value (@app-state k)
65 | :on-change #(swap! app-state assoc k (-> % .-target .-value))}])
66 | ```
67 |
68 | #### Step 7: Create an `home` component that displays an image based on the street and city supplied by a user
69 |
70 | ```clojure
71 | (defn home []
72 | [:div
73 | [:p "Street: " [input :street]]
74 | [:p "City: " [input :city]]
75 | [:img {:src (street-view-url (@app-state :street) (@app-state :city))}]])
76 | ```
77 |
78 | # Usage
79 |
80 | Compile cljs files.
81 |
82 | ```
83 | $ lein clean
84 | $ lein cljsbuild once prod
85 | ```
86 |
87 | Open `resources/public/index.html`.
88 |
89 |
90 |
--------------------------------------------------------------------------------
/recipes/google-street-view/project.clj:
--------------------------------------------------------------------------------
1 | (defproject google-street-view "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/google-street-view/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/recipes/google-street-view/src/cljs/google_street_view/core.cljs:
--------------------------------------------------------------------------------
1 | (ns google-street-view.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (def base-url
7 | "http://maps.googleapis.com/maps/api/streetview?size=600x400&location=")
8 |
9 | (defn address-url [street city]
10 | (str street ", " city))
11 |
12 | (defn street-view-url [street city]
13 | (str base-url
14 | (address-url street city)))
15 |
16 | (def app-state (reagent/atom {:street "24 Willie Mays Plaza" :city "San Francisco"}))
17 |
18 | (defn input [k]
19 | [:input {:value (@app-state k)
20 | :on-change #(swap! app-state assoc k (-> % .-target .-value))}])
21 |
22 | (defn home []
23 | [:div
24 | [:p "Street: " [input :street]]
25 | [:p "City: " [input :city]]
26 | [:img {:src (street-view-url (@app-state :street) (@app-state :city))}]])
27 |
28 | (defn ^:export main []
29 | (rdom/render [home]
30 | (.getElementById js/document "app")))
31 |
32 |
--------------------------------------------------------------------------------
/recipes/highcharts/externs.js:
--------------------------------------------------------------------------------
1 | var Highcharts;
2 | Highcharts.Chart = function(){};
3 |
--------------------------------------------------------------------------------
/recipes/highcharts/project.clj:
--------------------------------------------------------------------------------
1 | (defproject highcharts "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/highcharts/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/recipes/highcharts/src/cljs/highcharts/core.cljs:
--------------------------------------------------------------------------------
1 | (ns highcharts.core
2 | (:require
3 | [reagent.dom :as rdom]
4 | [reagent.core :as reagent]))
5 |
6 | (def chart-config
7 | {:chart {:type "bar"}
8 | :title {:text "Historic World Population by Region"}
9 | :subtitle {:text "Source: Wikipedia.org"}
10 | :xAxis {:categories ["Africa" "America" "Asia" "Europe" "Oceania"]
11 | :title {:text nil}}
12 | :yAxis {:min 0
13 | :title {:text "Population (millions)"
14 | :align "high"}
15 | :labels {:overflow "justify"}}
16 | :tooltip {:valueSuffix " millions"}
17 | :plotOptions {:bar {:dataLabels {:enabled true}}}
18 | :legend {:layout "vertical"
19 | :align "right"
20 | :verticalAlign "top"
21 | :x -40
22 | :y 100
23 | :floating true
24 | :borderWidth 1
25 | :shadow true}
26 | :credits {:enabled false}
27 | :series [{:name "Year 1800"
28 | :data [107 31 635 203 2]}
29 | {:name "Year 1900"
30 | :data [133 156 947 408 6]}
31 | {:name "Year 2008"
32 | :data [973 914 4054 732 34]}]
33 | })
34 |
35 | (defn home-render []
36 | [:div {:style {:min-width "310px" :max-width "800px"
37 | :height "400px" :margin "0 auto"}}])
38 |
39 | (defn home-did-mount [this]
40 | (js/Highcharts.Chart. (rdom/dom-node this)
41 | (clj->js chart-config)))
42 |
43 | (defn home []
44 | (reagent/create-class {:reagent-render home-render
45 | :component-did-mount home-did-mount}))
46 |
47 | (defn ^:export main []
48 | (rdom/render [home]
49 | (.getElementById js/document "app")))
50 |
--------------------------------------------------------------------------------
/recipes/input-validation/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to create color-coded input validation in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to validate that a password is at least 6 characters long.
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Create an `password-valid?` predicate function
13 | 3. Create `password-color` function that changes whether or not the password is valid
14 | 4. Create a reagent atom, `app-state`, to store the password
15 | 5. Create a `password` component that updates `app-state` on-change
16 | 6. Create a `home` component and add the `password` component inside of it
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc input-validation
22 | ```
23 |
24 | #### Step 2: Create a `password-valid?` predicate function
25 |
26 | ```clojure
27 | (defn password-valid?
28 | "Valid if password is greater than 5 characters"
29 | [password]
30 | (> (count password) 5))
31 | ```
32 |
33 | #### Step 3: Create `password-color` function that changes whether or not the input is valid
34 |
35 | ```clojure
36 | (defn password-color [password]
37 | (let [valid-color "green"
38 | invalid-color "red"]
39 | (if (password-valid? password)
40 | valid-color
41 | invalid-color)))
42 | ```
43 |
44 | #### Step 4: Create a reagent atom, `app-state`, to store the password
45 |
46 | ```clojure
47 | (def app-state (reagent/atom {:password nil}))
48 | ```
49 |
50 | #### Step 5: Create a `password` component that updates `app-state` on-change
51 |
52 | ```clojure
53 | (defn password []
54 | [:input {:type "password"
55 | :on-change #(swap! app-state assoc :password (-> % .-target .-value))}])
56 | ```
57 |
58 | #### Step 6: Create a `home` component and add the `password` component inside of it
59 |
60 | Add the `password` component and wrap it in a span. Give the span a border-color based on the password length by using `password-color`.
61 |
62 | ```clojure
63 | (defn home []
64 | [:div {:style {:margin-top "30px"}}
65 | "Please enter a password greater than 5 characters. "
66 | [:span {:style {:padding "20px"
67 | :background-color (password-color (@app-state :password))}}
68 | [password]
69 | ]])
70 | ```
71 |
--------------------------------------------------------------------------------
/recipes/input-validation/project.clj:
--------------------------------------------------------------------------------
1 | (defproject input-validation "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/input-validation/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/recipes/input-validation/src/cljs/input_validation/core.cljs:
--------------------------------------------------------------------------------
1 | (ns input-validation.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (defn password-valid?
6 | "Valid if password is greater than 5 characters"
7 | [password]
8 | (> (count password) 5))
9 |
10 | (defn password-color [password]
11 | (let [valid-color "green"
12 | invalid-color "red"]
13 | (if (password-valid? password)
14 | valid-color
15 | invalid-color)))
16 |
17 | (def app-state (reagent/atom {:password nil}))
18 |
19 | (defn password []
20 | [:input {:type "password"
21 | :on-change #(swap! app-state assoc :password (-> % .-target .-value))}])
22 |
23 | (defn home []
24 | [:div {:style {:margin-top "30px"}}
25 | "Please enter a password greater than 5 characters. "
26 | [:span {:style {:padding "20px"
27 | :background-color (password-color (@app-state :password))}}
28 | [password]
29 | ]])
30 |
31 | (defn ^:export main []
32 | (rdom/render [home]
33 | (.getElementById js/document "app")))
34 |
35 |
--------------------------------------------------------------------------------
/recipes/leaflet/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [leaflet](http://leafletjs.com/) to your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to follow the first part of this [example](http://leafletjs.com/examples/quick-start.html).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Create div with a unique id in `home-render`
14 | 4. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
15 | 5. Use `home-render` and `home-did-mount` to create a reagent component called `home`
16 | 6. Add externs
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc leaflet
22 | ```
23 |
24 | #### Step 2: Add necessary items to `resources/public/index.html`
25 |
26 | ```html
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | #### Step 3: Create div with a unique id in `home-render`
45 |
46 | Navigate to `src/cljs/leaflet/core.cljs`.
47 |
48 | ```clojure
49 | (defn home-render []
50 | [:div#map {:style {:height "360px"}}])
51 | ```
52 |
53 | #### Step 4: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
54 |
55 | To center a map around London, this is the javascript we need.
56 |
57 | ```javascript
58 | var map = L.map('map').setView([51.505, -0.09], 13);
59 |
60 | L.tileLayer('http://{s}.tiles.mapbox.com/v3/MapID/{z}/{x}/{y}.png', {
61 | attribution: 'Map data © [...]',
62 | maxZoom: 18
63 | }).addTo(map);
64 | ```
65 |
66 | Let's convert this to clojurescript and place in `home-did-mount`. You will need to sign up with [Mapbox](https://www.mapbox.com/), get a [mapID](https://www.mapbox.com/help/define-map-id/) and replace the `FIXME` below with your mapID. *Note: the mapID is different from the API key*
67 |
68 | ```clojure
69 | (defn home-did-mount []
70 | (let [map (.setView (.map js/L "map") #js [51.505 -0.09] 13)]
71 |
72 | ;; NEED TO REPLACE FIXME with your mapID!
73 | (.addTo (.tileLayer js/L "http://{s}.tiles.mapbox.com/v3/FIXME/{z}/{x}/{y}.png"
74 | (clj->js {:attribution "Map data © [...]"
75 | :maxZoom 18}))
76 | map)))
77 | ```
78 |
79 | #### Step 5: Use `home-render` and `home-did-mount` to create a reagent component called `home`
80 |
81 | ```clojure
82 | (defn home []
83 | (reagent/create-class {:reagent-render home-render
84 | :component-did-mount home-did-mount}))
85 | ```
86 |
87 | #### Step 6: Add externs
88 |
89 | For advanced compilation, we need to protect `L.map.setView` and `L.tileLayer.addTo` from getting renamed. Add an `externs.js` file.
90 |
91 | ```js
92 | var L = {
93 | "map": {
94 | "setView": function(){}
95 | },
96 | "tileLayer": {
97 | "addTo": function(){}
98 | }
99 | };
100 | ```
101 |
102 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
103 |
104 | ```clojure
105 | :externs ["externs.js"]
106 | ```
107 |
108 | # Usage
109 |
110 | Compile cljs files.
111 |
112 | ```
113 | $ lein clean
114 | $ lein cljsbuild once prod
115 | ```
116 |
117 | Open `resources/public/index.html`.
118 |
--------------------------------------------------------------------------------
/recipes/leaflet/externs.js:
--------------------------------------------------------------------------------
1 | var L = {
2 | "map": {
3 | "setView": function(){}
4 | },
5 | "tileLayer": {
6 | "addTo": function(){}
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/recipes/leaflet/project.clj:
--------------------------------------------------------------------------------
1 | (defproject leaflet "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/leaflet/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/leaflet/src/cljs/leaflet/core.cljs:
--------------------------------------------------------------------------------
1 | (ns leaflet.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (defn home-render []
6 | [:div#map {:style {:height "360px"}}])
7 |
8 | (defn home-did-mount []
9 | (let [map (.setView (.map js/L "map") #js [51.505 -0.09] 13)]
10 |
11 | ;; NEED TO UPDATE with your mapID
12 | (.addTo (.tileLayer js/L "http://{s}.tiles.mapbox.com/v3/thedon73v.k3goc602/{z}/{x}/{y}.png"
13 | (clj->js {:attribution "Map data © [...]"
14 | :maxZoom 18}))
15 | map)))
16 |
17 | (defn home []
18 | (reagent/create-class {:reagent-render home-render
19 | :component-did-mount home-did-mount}))
20 |
21 | (defn ^:export main []
22 | (rdom/render [home]
23 | (.getElementById js/document "app")))
24 |
25 |
--------------------------------------------------------------------------------
/recipes/local-storage/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add local storage via [storage-atom](https://github.com/alandipert/storage-atom) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add storage-atom to `project.clj` :dependencies vector
11 | 3. Add storage-atom to `core.cljs` namespace
12 | 4. Add a reagent atom called `app-state` and wrap it in the `local-storage` function
13 | 6. Add an increment button to `home`
14 |
15 | #### Step 1: Create a new project
16 |
17 | ```
18 | $ lein new rc local-storage
19 | ```
20 |
21 | #### Step 2: Add storage-atom to `project.clj` :dependencies vector
22 |
23 | ```clojure
24 | [alandipert/storage-atom "1.2.4"]
25 | ```
26 |
27 | #### Step 3: Add storage-atom to `core.cljs` namespace
28 |
29 | Navigate to `src/cljs/local_storage/core.cljs` and update the `ns` to the following.
30 |
31 | ```clojure
32 | (ns local-storage.core
33 | (:require [reagent.core :as reagent]
34 | [alandipert.storage-atom :refer [local-storage]]))
35 | ```
36 |
37 | #### Step 4: Add a reagent atom called `app-state` and wrap it in the `local-storage` function
38 |
39 | ```clojure
40 | (def app-state (local-storage
41 | (reagent/atom {:counter 0})
42 | :app-state))
43 | ```
44 |
45 | #### Step 6: Add an increment button to `home`
46 |
47 | ```clojure
48 | (defn home []
49 | [:div
50 | [:div "Current count: " (@app-state :counter)]
51 | [:button {:on-click #(swap! app-state update-in [:counter] inc)}
52 | "Increment"]])
53 | ```
54 |
55 | # Usage
56 |
57 | Compile cljs files.
58 |
59 | ```
60 | $ lein clean
61 | $ lein cljsbuild once prod
62 | ```
63 |
64 | Open `resources/public/index.html`.
65 |
66 | Click on the button, increment the counter, close the page and reopen it ... the current count should persist via local-storate.
67 |
--------------------------------------------------------------------------------
/recipes/local-storage/project.clj:
--------------------------------------------------------------------------------
1 | (defproject local-storage "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [alandipert/storage-atom "2.0.1"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.1.8"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false}}]})
18 |
--------------------------------------------------------------------------------
/recipes/local-storage/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/recipes/local-storage/src/cljs/local_storage/core.cljs:
--------------------------------------------------------------------------------
1 | (ns local-storage.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]
4 | [alandipert.storage-atom :refer [local-storage]]))
5 |
6 | (def app-state (local-storage
7 | (reagent/atom {:counter 0})
8 | :app-state))
9 |
10 | (defn home []
11 | [:div
12 | [:div "Current count: " (@app-state :counter)]
13 | [:button {:on-click #(swap! app-state update-in [:counter] inc)}
14 | "Increment"]])
15 |
16 | (defn ^:export main []
17 | (rdom/render [home]
18 | (.getElementById js/document "app")))
19 |
20 |
--------------------------------------------------------------------------------
/recipes/markdown-editor/externs.js:
--------------------------------------------------------------------------------
1 | var hljs = {};
2 | hljs.highlightBlock = function(){};
3 | var marked = function(){};
4 |
--------------------------------------------------------------------------------
/recipes/markdown-editor/project.clj:
--------------------------------------------------------------------------------
1 | (defproject markdown-editor "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/markdown-editor/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/recipes/markdown-editor/src/cljs/markdown_editor/core.cljs:
--------------------------------------------------------------------------------
1 | (ns markdown-editor.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 |
6 | (defn editor [content]
7 | [:textarea.form-control
8 | {:value @content
9 | :on-change #(reset! content (-> % .-target .-value))}])
10 |
11 | (defn highlight-code [html-node]
12 | (let [nodes (.querySelectorAll html-node "pre code")]
13 | (loop [i (.-length nodes)]
14 | (when-not (neg? i)
15 | (when-let [item (.item nodes i)]
16 | (.highlightBlock js/hljs item))
17 | (recur (dec i))))))
18 |
19 | (defn markdown-render [content]
20 | [:div {:dangerouslySetInnerHTML
21 | {:__html (-> content str js/marked)}}])
22 |
23 | (defn markdown-did-mount [this]
24 | (let [node (rdom/dom-node this)]
25 | (highlight-code node)))
26 |
27 | (defn markdown-component [content]
28 | (reagent/create-class
29 | {:reagent-render markdown-render
30 | :component-did-mount markdown-did-mount}))
31 |
32 | (defn preview [content]
33 | (when (not-empty @content)
34 | [markdown-component @content]))
35 |
36 | (defn home []
37 | (let [content (reagent/atom nil)]
38 | (fn []
39 | [:div
40 | [:h1 "Live Markdown Editor"]
41 | [:div.container-fluid
42 | [:div.row
43 | [:div.col-sm-6
44 | [:h3 "Editor"]
45 | [editor content]]
46 |
47 | [:div.col-sm-6
48 | [:h3 "Preview"]
49 | [preview content]]
50 |
51 | ]]])))
52 |
53 | (defn ^:export main []
54 | (rdom/render [home]
55 | (.getElementById js/document "app")))
56 |
--------------------------------------------------------------------------------
/recipes/mojs-animation/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [mo · js](https://github.com/legomushroom/mojs) animations to your [Reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add the `mo . js` library to `resources/public/vendor/js`
11 | 3. Add necessary items to `resources/public/index.html`
12 | 4. Add some css to `resources/public/css/animation.css`
13 | 5. Add `animation-did-mount` function to render a Tween using the DOM node
14 | 6. Add a `translate-y` function to update the node during animation
15 | 7. Create an `animation` component that will render a `div` element
16 | 8. Add externs in `project.clj` using the `mo . js` library
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc mojs-animation
22 | ```
23 |
24 | #### Step 2: Add the `mo . js` library to `resources/public/vendor/js`
25 |
26 | ```
27 | wget -O resources/public/vendor/js/mo.min.js http://cdn.jsdelivr.net/mojs/latest/mo.min.js
28 | ```
29 |
30 | #### Step 3: Add necessary items to `resources/public/index.html`
31 |
32 | ```html
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
45 |
46 | #### Step 4: Add some css to `resources/public/css/animation.css`
47 |
48 | ```css
49 | body {
50 | max-width: 600px;
51 | margin: 0 auto;
52 | padding-top: 72px;
53 | }
54 |
55 | .square {
56 | width: 50px;
57 | height: 50px;
58 | background: #F64040;
59 | position: absolute;
60 | top: 10px;
61 | left: 50%;
62 | margin-left: -25px;
63 | margin-top: -25px;
64 | }
65 | ```
66 |
67 | #### Step 5: Add `animation-did-mount` function to render a Tween using the DOM node
68 |
69 | ```clojure
70 | (defn animation-did-mount [this]
71 | (.run
72 | (js/mojs.Tween.
73 | (clj->js
74 | {:repeat 999
75 | :delay 2000
76 | :onUpdate (translate-y (reagent/dom-node this))}))))
77 | ```
78 |
79 | #### Step 6: Add a `translate-y` function to update the node during animation
80 |
81 | ```clojure
82 | (defn translate-y [node]
83 | (fn [progress]
84 | (set! (-> node .-style .-transform)
85 | (str "translateY(" (* 200 progress) "px)"))))
86 | ```
87 |
88 | #### Step 7: Create an `animation` component that will render a `div` element
89 |
90 | The component will call the external Js code when the node is mounted in the browser DOM. This has to be done in the `component-did-mount` state.
91 |
92 | ```clojure
93 | (defn animation []
94 | (reagent/create-class {:render (fn [] [:div.square])
95 | :component-did-mount animation-did-mount}))
96 |
97 | (defn ^:export main []
98 | (reagent/render [animation]
99 | (.getElementById js/document "app")))
100 |
101 | ```
102 |
103 | #### Step 8: Add externs
104 |
105 | For advanced compilation, we need to protect `mojs` namespaced functions from getting renamed. We'll open `project.clj` and add a reference to the externs in the compiler portion under the `:cljsbuild` key. The `:closure-warnings` key will suppress the warnings when parsing externs from the library.
106 |
107 | ```clojure
108 | :externs ["resources/public/vendor/js/mo.min.js"]
109 | :closure-warnings {:externs-validation :off
110 | :non-standard-jsdoc :off}
111 | ```
112 |
113 | # Usage
114 |
115 | Compile cljs files.
116 |
117 | ```
118 | $ lein clean
119 | $ lein cljsbuild once
120 | ```
121 |
122 | Open `resources/public/index.html`.
123 |
--------------------------------------------------------------------------------
/recipes/mojs-animation/project.clj:
--------------------------------------------------------------------------------
1 | (defproject mojs-animation "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["resources/public/vendor/js/mo.min.js"]
18 | :closure-warnings {:externs-validation :off
19 | :non-standard-jsdoc :off}}}]})
20 |
--------------------------------------------------------------------------------
/recipes/mojs-animation/resources/public/css/animation.css:
--------------------------------------------------------------------------------
1 | body {
2 | max-width: 600px;
3 | margin: 0 auto;
4 | padding-top: 72px;
5 | }
6 |
7 | .square {
8 | width: 50px;
9 | height: 50px;
10 | background: #F64040;
11 | position: absolute;
12 | top: 10px;
13 | left: 50%;
14 | margin-left: -25px;
15 | margin-top: -25px;
16 | }
17 |
--------------------------------------------------------------------------------
/recipes/mojs-animation/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/recipes/mojs-animation/src/cljs/mojs-animation/core.cljs:
--------------------------------------------------------------------------------
1 | (ns mojs-animation.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.rdom :as rdom]))
4 |
5 | (defn translate-y [node]
6 | (fn [progress]
7 | (set! (-> node .-style .-transform)
8 | (str "translateY(" (* 200 progress) "px)"))))
9 |
10 | (defn animation-did-mount [this]
11 | (.run
12 | (js/mojs.Tween.
13 | (clj->js
14 | {:repeat 999
15 | :delay 2000
16 | :onUpdate (translate-y (rdom/dom-node this))}))))
17 |
18 | (defn animation []
19 | (reagent/create-class {:render (fn [] [:div.square])
20 | :component-did-mount animation-did-mount}))
21 |
22 | (defn ^:export main []
23 | (rdom/render [animation]
24 | (.getElementById js/document "app")))
25 |
26 |
--------------------------------------------------------------------------------
/recipes/morris/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to use [morris](http://morrisjs.github.io/morris.js/) charts to display data in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to create a donut chart and follow this [example](http://jsbin.com/ukaxod/144/embed?html,js,output).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Add div with unique id to `home-render`
14 | 4. Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
15 | 5. Use `home-render` and `home-did-mount` to create a reagent component called `home`
16 | 6. Add externs
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc morris
22 | ```
23 |
24 | #### Step 2: Add necessary items to `resources/public/index.html`
25 |
26 | ```html
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
45 |
46 | #### Step 3: Add div with unique id to `home-render`
47 |
48 | Navigate to `src/cljs/morris/core.cljs`.
49 |
50 | ```clojure
51 | (defn home-render []
52 | [:div#donut-example ])
53 | ```
54 |
55 | #### Step 4: Convert javascript to clojurescript and put inside a *did-mount* function called `home-did-mount`
56 |
57 | This is the javascript we need.
58 |
59 | ```javascript
60 | Morris.Donut({
61 | element: 'donut-example',
62 | data: [
63 | {label: "Download Sales", value: 12},
64 | {label: "In-Store Sales", value: 30},
65 | {label: "Mail-Order Sales", value: 20}
66 | ]
67 | });
68 | ```
69 |
70 | Let's convert this to clojurescript and place in `home-did-mount`
71 |
72 | ```clojure
73 | (defn home-did-mount []
74 | (.Donut js/Morris (clj->js {:element "donut-example"
75 | :data [{:label "Download Sales" :value 12}
76 | {:label "In-Store Sales" :value 30}
77 | {:label "Mail-Order Sales" :value 20}]})))
78 | ```
79 |
80 | #### Step 5: Use `home-render` and `home-did-mount` to create a reagent component called `home`
81 |
82 | ```clojure
83 | (defn home []
84 | (reagent/create-class {:reagent-render home-render
85 | :component-did-mount home-did-mount}))
86 | ```
87 |
88 | #### Step 6: Add externs
89 |
90 | For advanced compilation, we need to protect `Morris.donut` from getting renamed. Add an `externs.js` file.
91 |
92 | ```js
93 | var Morris = function(){};
94 | Morris.Donut = function(){};
95 | ```
96 |
97 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
98 |
99 | ```clojure
100 | :externs ["externs.js"]
101 | ```
102 |
103 | # Usage
104 |
105 | Compile cljs files.
106 |
107 | ```
108 | $ lein clean
109 | $ lein cljsbuild once prod
110 | ```
111 |
112 | Open `resources/public/index.html`.
113 |
--------------------------------------------------------------------------------
/recipes/morris/externs.js:
--------------------------------------------------------------------------------
1 | var Morris = function(){};
2 | Morris.Donut = function(){};
3 |
--------------------------------------------------------------------------------
/recipes/morris/project.clj:
--------------------------------------------------------------------------------
1 | (defproject morris "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/morris/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/recipes/morris/src/cljs/morris/core.cljs:
--------------------------------------------------------------------------------
1 | (ns morris.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (defn home-render []
6 | [:div#donut-example ])
7 |
8 | (defn home-did-mount []
9 | (.Donut js/Morris (clj->js {:element "donut-example"
10 | :data [{:label "Download Sales" :value 12}
11 | {:label "In-Store Sales" :value 30}
12 | {:label "Mail-Order Sales" :value 20}]})))
13 |
14 | (defn home []
15 | (reagent/create-class {:reagent-render home-render
16 | :component-did-mount home-did-mount}))
17 |
18 | (defn ^:export main []
19 | (rdom/render [home]
20 | (.getElementById js/document "app")))
21 |
22 |
--------------------------------------------------------------------------------
/recipes/reagent-server-rendering/project.clj:
--------------------------------------------------------------------------------
1 | (defproject reagent-server-rendering "0.1.0"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [ring "1.8.2"]
6 | [ring/ring-defaults "0.3.2"]
7 | [hiccup "2.0.0-alpha2"]
8 | [aleph "0.4.7-alpha7"]]
9 |
10 | :source-paths ["src/clj"]
11 |
12 | :plugins [[lein-cljsbuild "1.1.8"]
13 | [lein-ring "0.12.5"]]
14 |
15 | :ring {:handler reagent-server-rendering.handler/app}
16 |
17 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
18 |
19 | :cljsbuild {:builds [{:id "prod"
20 | :source-paths ["src/cljs"]
21 | :compiler {:output-to "resources/public/js/compiled/app.js"
22 | :optimizations :advanced
23 | :pretty-print false}}]})
24 |
--------------------------------------------------------------------------------
/recipes/reagent-server-rendering/resources/public/css/site.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Verdana, Helvetica, Arial, sans-serif;
3 | max-width: 600px;
4 | margin: 0 auto;
5 | padding-top: 72px;
6 | -webkit-font-smoothing: antialiased;
7 | font-size: 1.125em;
8 | color: #333;
9 | line-height: 1.5em;
10 | }
11 |
12 | h1, h2, h3 {
13 | color: #000;
14 | }
15 | h1 {
16 | font-size: 2.5em
17 | }
18 |
19 | h2 {
20 | font-size: 2em
21 | }
22 |
23 | h3 {
24 | font-size: 1.5em
25 | }
26 |
27 | a {
28 | text-decoration: none;
29 | color: #09f;
30 | }
31 |
32 | a:hover {
33 | text-decoration: underline;
34 | }
35 |
--------------------------------------------------------------------------------
/recipes/reagent-server-rendering/src/clj/reagent_server_rendering/handler.clj:
--------------------------------------------------------------------------------
1 | (ns reagent-server-rendering.handler
2 | (:require [aleph.flow :as flow]
3 | [clojure.java.io :as io]
4 | [hiccup.core :refer [html]]
5 | [hiccup.page :refer [include-js include-css]]
6 | [compojure.core :refer [GET defroutes]]
7 | [compojure.route :refer [not-found resources]]
8 | [ring.middleware.defaults :refer [site-defaults wrap-defaults]])
9 | (:import [io.aleph.dirigiste Pools]
10 | [javax.script ScriptEngineManager Invocable]))
11 |
12 | (defn- create-js-engine []
13 | (doto (.getEngineByName (ScriptEngineManager.) "nashorn")
14 | ; React requires either "window" or "global" to be defined.
15 | (.eval "var global = this")
16 | (.eval (-> "public/js/compiled/app.js"
17 | io/resource
18 | io/reader))))
19 |
20 | ; We have one and only one key in the pool, it's a constant.
21 | (def ^:private js-engine-key "js-engine")
22 | (def ^:private js-engine-pool
23 | (flow/instrumented-pool
24 | {:generate (fn [_] (create-js-engine))
25 | :controller (Pools/utilizationController 0.9 10000 10000)}))
26 |
27 | (defn- render-page [page-id]
28 | (let [js-engine @(flow/acquire js-engine-pool js-engine-key)]
29 | (try (.invokeMethod
30 | ^Invocable js-engine
31 | (.eval js-engine "reagent_server_rendering.core")
32 | "render_page" (object-array [page-id]))
33 | (finally (flow/release js-engine-pool js-engine-key js-engine)))))
34 |
35 | (defn page [page-id]
36 | (html
37 | [:html
38 | [:head
39 | [:meta {:charset "utf-8"}]
40 | [:meta {:name "viewport"
41 | :content "width=device-width, initial-scale=1"}]
42 | (include-css "css/site.css")]
43 | [:body
44 | [:div#app
45 | (render-page page-id)]
46 | (include-js "js/compiled/app.js")
47 | [:script {:type "text/javascript"}
48 | (str "reagent_server_rendering.core.main('" page-id "');")]]]))
49 |
50 | (defroutes app-routes
51 | (GET "/" [] (page "home"))
52 | (GET "/about" [] (page "about"))
53 | (resources "/")
54 | (not-found "Not Found"))
55 |
56 | (def app (wrap-defaults app-routes site-defaults))
57 |
--------------------------------------------------------------------------------
/recipes/reagent-server-rendering/src/cljs/reagent_server_rendering/core.cljs:
--------------------------------------------------------------------------------
1 | (ns reagent-server-rendering.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]
4 | [reagent.dom.server :as reagent-server]))
5 |
6 | (defn home-page []
7 | [:div [:h2 "Welcome to reagent-server-rendering"]
8 | [:div [:a {:href "/about"} "go to about page"]]])
9 |
10 | (defn about-page []
11 | [:div [:h2 "About reagent-server-rendering"]
12 | [:div [:a {:href "/"} "go to the home page"]]])
13 |
14 | (def pages
15 | {"home" home-page
16 | "about" about-page})
17 |
18 | (defn ^:export render-page [page-id]
19 | (reagent-server/render-to-string [(get pages page-id)]))
20 |
21 | (defn ^:export main [page-id]
22 | (rdom/render [(get pages page-id)] (.getElementById js/document "app")))
23 |
--------------------------------------------------------------------------------
/recipes/simple-sidebar/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to include a bootstrap-themed sidebar in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to use [simple-sidebar](http://startbootstrap.com/template-overviews/simple-sidebar/).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Download Simple Sidebar CSS
13 | 3. Add necessary items to `resources/public/index.html`
14 | 4. Create a `sidebar` component
15 | 5. Create a `menu-toggle` button
16 | 6. Add `sidebar` and `menu-toggle` to `home`
17 | 7. Add externs
18 |
19 | #### Step 1: Create a new project
20 |
21 | ```
22 | $ lein new rc simple-sidebar
23 | ```
24 |
25 | #### Step 2: Download Simple Sidebar CSS
26 |
27 | * Open your new project and create the following folder `resources/public/css`
28 | * Go [here](http://startbootstrap.com/template-overviews/simple-sidebar/) and hit the "Download" button
29 | * Extract the zip file and navigate to the `css` folder
30 | * Copy `simple-sidebar.css` and place it into your project in the `resources/public/css` folder that you just made earlier
31 |
32 | #### Step 3: Add necessary items to `resources/public/index.html`
33 |
34 | ```html
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ```
60 |
61 | #### Step 4: Create a `sidebar` component
62 |
63 | Navigate to `src/cljs/simple_sidebar/core.cljs`.
64 |
65 | ```clojure
66 | (defn sidebar []
67 | [:div#sidebar-wrapper
68 | [:ul.sidebar-nav
69 | [:li.sidebar-brand>a {:href "#"} "Simple Sidebar"]
70 | [:li>a {:href "#"} "Page 1"]
71 | [:li>a {:href "#"} "Page 2"]
72 | [:li>a {:href "#"} "Page 3"]]])
73 | ```
74 |
75 | The classes `sidebar-wrapper` and `sidebar-nav` are from Simple Sidebar. You can add real links in the href, but for now we are just going to use the `#` as a placeholder.
76 |
77 | #### Step 5: Create a menu toggle button
78 |
79 | ```clojure
80 | (defn menu-toggle-render []
81 | [:div.btn.btn-default "Toggle Menu"])
82 |
83 | (defn menu-toggle-did-mount [this]
84 | (.click (js/$ (rdom/dom-node this))
85 | (fn [e]
86 | (.preventDefault e)
87 | (.toggleClass (js/$ "#wrapper") "toggled") ;#wrapper will be the id of a div in our home component
88 | )))
89 |
90 | (defn menu-toggle []
91 | (reagent/create-class {:reagent-render menu-toggle-render
92 | :component-did-mount menu-toggle-did-mount}))
93 | ```
94 |
95 | For your reference, this is the javascript we are converting to clojurescript in `menu-toggle-did-mount`.
96 |
97 | ```javascript
98 | $("#menu-toggle").click(function(e) {
99 | e.preventDefault();
100 | $("#wrapper").toggleClass("toggled");
101 | });
102 | ```
103 |
104 | #### Step 6: Add `sidebar` and `menu-toggle` to `home`
105 |
106 | ```clojure
107 | (defn home []
108 | [:div#wrapper
109 | [sidebar]
110 | [:div.page-content-wrapper>div.container-fluid>div.row>div.col-lg-12
111 | [menu-toggle]]])
112 | ```
113 |
114 | #### Step 7: Add externs
115 |
116 | For advanced compilation, we need to protect `$.click` and `$.toggleClass` from getting renamed. Add an `externs.js` file.
117 |
118 | ```js
119 | var $ = function(){};
120 | $.click = function(){};
121 | $.toggleClass = function(){};
122 | ```
123 |
124 | Open `project.clj` and add a reference to the externs in the cljsbuild portion.
125 |
126 | ```clojure
127 | :externs ["externs.js"]
128 | ```
129 |
130 | # Usage
131 |
132 | Compile cljs files.
133 |
134 | ```
135 | $ lein clean
136 | $ lein cljsbuild once prod
137 | ```
138 |
139 | Open `resources/public/index.html`.
140 |
--------------------------------------------------------------------------------
/recipes/simple-sidebar/externs.js:
--------------------------------------------------------------------------------
1 | var $ = function(){};
2 | $.click = function(){};
3 | $.toggleClass = function(){};
4 |
--------------------------------------------------------------------------------
/recipes/simple-sidebar/project.clj:
--------------------------------------------------------------------------------
1 | (defproject simple-sidebar "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/simple-sidebar/resources/public/css/simple-sidebar.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - Simple Sidebar HTML Template (http://startbootstrap.com)
3 | * Code licensed under the Apache License v2.0.
4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0.
5 | */
6 |
7 | /* Toggle Styles */
8 |
9 | #wrapper {
10 | padding-left: 0;
11 | -webkit-transition: all 0.5s ease;
12 | -moz-transition: all 0.5s ease;
13 | -o-transition: all 0.5s ease;
14 | transition: all 0.5s ease;
15 | }
16 |
17 | #wrapper.toggled {
18 | padding-left: 250px;
19 | }
20 |
21 | #sidebar-wrapper {
22 | z-index: 1000;
23 | position: fixed;
24 | left: 250px;
25 | width: 0;
26 | height: 100%;
27 | margin-left: -250px;
28 | overflow-y: auto;
29 | background: #000;
30 | -webkit-transition: all 0.5s ease;
31 | -moz-transition: all 0.5s ease;
32 | -o-transition: all 0.5s ease;
33 | transition: all 0.5s ease;
34 | }
35 |
36 | #wrapper.toggled #sidebar-wrapper {
37 | width: 250px;
38 | }
39 |
40 | #page-content-wrapper {
41 | width: 100%;
42 | position: absolute;
43 | padding: 15px;
44 | }
45 |
46 | #wrapper.toggled #page-content-wrapper {
47 | position: absolute;
48 | margin-right: -250px;
49 | }
50 |
51 | /* Sidebar Styles */
52 |
53 | .sidebar-nav {
54 | position: absolute;
55 | top: 0;
56 | width: 250px;
57 | margin: 0;
58 | padding: 0;
59 | list-style: none;
60 | }
61 |
62 | .sidebar-nav li {
63 | text-indent: 20px;
64 | line-height: 40px;
65 | }
66 |
67 | .sidebar-nav li a {
68 | display: block;
69 | text-decoration: none;
70 | color: #999999;
71 | }
72 |
73 | .sidebar-nav li a:hover {
74 | text-decoration: none;
75 | color: #fff;
76 | background: rgba(255,255,255,0.2);
77 | }
78 |
79 | .sidebar-nav li a:active,
80 | .sidebar-nav li a:focus {
81 | text-decoration: none;
82 | }
83 |
84 | .sidebar-nav > .sidebar-brand {
85 | height: 65px;
86 | font-size: 18px;
87 | line-height: 60px;
88 | }
89 |
90 | .sidebar-nav > .sidebar-brand a {
91 | color: #999999;
92 | }
93 |
94 | .sidebar-nav > .sidebar-brand a:hover {
95 | color: #fff;
96 | background: none;
97 | }
98 |
99 | @media(min-width:768px) {
100 | #wrapper {
101 | padding-left: 250px;
102 | }
103 |
104 | #wrapper.toggled {
105 | padding-left: 0;
106 | }
107 |
108 | #sidebar-wrapper {
109 | width: 250px;
110 | }
111 |
112 | #wrapper.toggled #sidebar-wrapper {
113 | width: 0;
114 | }
115 |
116 | #page-content-wrapper {
117 | padding: 20px;
118 | position: relative;
119 | }
120 |
121 | #wrapper.toggled #page-content-wrapper {
122 | position: relative;
123 | margin-right: 0;
124 | }
125 | }
--------------------------------------------------------------------------------
/recipes/simple-sidebar/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/recipes/simple-sidebar/src/cljs/simple_sidebar/core.cljs:
--------------------------------------------------------------------------------
1 | (ns simple-sidebar.core
2 | (:require [reagent.dom :as rdom]
3 | [reagent.core :as reagent]))
4 |
5 | (defn sidebar []
6 | [:div#sidebar-wrapper
7 | [:ul.sidebar-nav
8 | [:li.sidebar-brand>a {:href "#"} "Simple Sidebar"]
9 | [:li>a {:href "#"} "Page 1"]
10 | [:li>a {:href "#"} "Page 2"]
11 | [:li>a {:href "#"} "Page 3"]]])
12 |
13 | (defn menu-toggle-render []
14 | [:div.btn.btn-default "Toggle Menu"])
15 |
16 | (defn menu-toggle-did-mount [this]
17 | (.click (js/$ (rdom/dom-node this))
18 | (fn [e]
19 | (.preventDefault e)
20 | (.toggleClass (js/$ "#wrapper") "toggled") ;#wrapper will be the id of a div in our home component
21 | )))
22 |
23 | (defn menu-toggle []
24 | (reagent/create-class {:reagent-render menu-toggle-render
25 | :component-did-mount menu-toggle-did-mount}))
26 |
27 | (defn home []
28 | [:div#wrapper
29 | [sidebar]
30 | [:div.page-content-wrapper>div.container-fluid>div.row>div.col-lg-12
31 | [menu-toggle]]])
32 |
33 | (defn ^:export main []
34 | (rdom/render [home]
35 | (.getElementById js/document "app")))
36 |
37 |
--------------------------------------------------------------------------------
/recipes/sort-table/project.clj:
--------------------------------------------------------------------------------
1 | (defproject sort-table "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/sort-table/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/recipes/sort-table/src/cljs/sort_table/core.cljs:
--------------------------------------------------------------------------------
1 | (ns sort-table.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | (def app-state (reagent/atom {:sort-val :first-name :ascending true}))
6 |
7 | (def table-contents
8 | [{:id 1 :first-name "Bram" :last-name "Moolenaar" :known-for "Vim"}
9 | {:id 2 :first-name "Richard" :last-name "Stallman" :known-for "GNU"}
10 | {:id 3 :first-name "Dennis" :last-name "Ritchie" :known-for "C"}
11 | {:id 4 :first-name "Rich" :last-name "Hickey" :known-for "Clojure"}
12 | {:id 5 :first-name "Guido" :last-name "Van Rossum" :known-for "Python"}
13 | {:id 6 :first-name "Linus" :last-name "Torvalds" :known-for "Linux"}
14 | {:id 7 :first-name "Yehuda" :last-name "Katz" :known-for "Ember"}])
15 |
16 | (defn update-sort-value [new-val]
17 | (if (= new-val (:sort-val @app-state))
18 | (swap! app-state update-in [:ascending] not)
19 | (swap! app-state assoc :ascending true))
20 | (swap! app-state assoc :sort-val new-val))
21 |
22 | (defn sorted-contents []
23 | (let [sorted-contents (sort-by (:sort-val @app-state) table-contents)]
24 | (if (:ascending @app-state)
25 | sorted-contents
26 | (rseq sorted-contents))))
27 |
28 | (defn table []
29 | [:table
30 | [:thead
31 | [:tr
32 | [:th {:width "200" :on-click #(update-sort-value :first-name)} "First Name"]
33 | [:th {:width "200" :on-click #(update-sort-value :last-name) } "Last Name"]
34 | [:th {:width "200" :on-click #(update-sort-value :known-for) } "Known For"]]]
35 | [:tbody
36 | (for [person (sorted-contents)]
37 | ^{:key (:id person)}
38 | [:tr [:td (:first-name person)]
39 | [:td (:last-name person)]
40 | [:td (:known-for person)]])]])
41 |
42 | (defn home []
43 | [:div {:style {:margin "auto"
44 | :padding-top "30px"
45 | :width "600px"}}
46 | [table]])
47 |
48 | (defn ^:export main []
49 | (rdom/render [home]
50 | (.getElementById js/document "app")))
51 |
52 |
--------------------------------------------------------------------------------
/recipes/sortable-portlets/externs.js:
--------------------------------------------------------------------------------
1 | var $ = function(){};
2 | $.addClass = function(){};
3 | $.click = function(){};
4 | $.closest.find.toggle = function(){};
5 | $.draggable = function(){};
6 | $.droppable = function(){};
7 | $.find = function(){};
8 | $.prepend = function(){};
9 | $.sortable = function(){};
10 | $.toggleClass = function(){};
11 |
--------------------------------------------------------------------------------
/recipes/sortable-portlets/project.clj:
--------------------------------------------------------------------------------
1 | (defproject sortable-portlets "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false
17 | :externs ["externs.js"]}}]})
18 |
--------------------------------------------------------------------------------
/recipes/sortable-portlets/resources/public/css/screen.css:
--------------------------------------------------------------------------------
1 | body {
2 | min-width: 520px;
3 | }
4 | .column {
5 | width: 170px;
6 | float: left;
7 | padding-bottom: 100px;
8 | }
9 | .portlet {
10 | margin: 0 1em 1em 0;
11 | padding: 0.3em;
12 | }
13 | .portlet-header {
14 | padding: 0.2em 0.3em;
15 | margin-bottom: 0.5em;
16 | position: relative;
17 | }
18 | .portlet-toggle {
19 | position: absolute;
20 | top: 50%;
21 | right: 0;
22 | margin-top: -8px;
23 | }
24 | .portlet-content {
25 | padding: 0.4em;
26 | }
27 | .portlet-placeholder {
28 | border: 1px dotted black;
29 | margin: 0 1em 1em 0;
30 | height: 50px;
31 | }
--------------------------------------------------------------------------------
/recipes/sortable-portlets/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/recipes/sortable-portlets/src/cljs/sortable_portlets/core.cljs:
--------------------------------------------------------------------------------
1 | (ns sortable-portlets.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | (defn home-render []
6 | [:div
7 | [:div.column
8 | [:div.portlet
9 | [:div.portlet-header "Feeds"]
10 | [:div.portlet-content "Lorem ipsum dolor sit amet, consectetuer adipiscing elit"]]
11 | [:div.portlet
12 | [:div.portlet-header "News"]
13 | [:div.portlet-content "Lorem ipsum dolor sit amet, consectetuer adipiscing elit"]] ]
14 |
15 | [:div.column
16 | [:div.portlet
17 | [:div.portlet-header "Shopping"]
18 | [:div.portlet-content "Lorem ipsum dolor sit amet, consectetuer adipiscing elit"]] ]
19 |
20 | [:div.column
21 | [:div.portlet
22 | [:div.portlet-header "Links"]
23 | [:div.portlet-content "Lorem ipsum dolor sit amet, consectetuer adipiscing elit"]]
24 | [:div.portlet
25 | [:div.portlet-header "Images"]
26 | [:div.portlet-content "Lorem ipsum dolor sit amet, consectetuer adipiscing elit"]] ]
27 | ])
28 |
29 | (defn home-did-mount []
30 | (js/$ (fn []
31 | (.sortable (js/$ ".column") (clj->js {:connectWith ".column"
32 | :handle ".portlet-header"
33 | :cancel ".portlet-toggle"
34 | :placeholder "portlet-placeholder ui-corner-all"}))
35 | (.. (js/$ ".portlet")
36 | (addClass "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all")
37 | (find ".portlet-header")
38 | (addClass "ui-widget-header ui-corner-all")
39 | (prepend ""))
40 |
41 | (.click (js/$ ".portlet-toggle")
42 | (fn []
43 | (this-as this
44 | (let [icon (js/$ this)]
45 | (.toggleClass icon "ui-icon-minusthick ui-icon-plusthick")
46 | (.toggle (.find (.closest icon ".portlet") ".portlet-content")))))))))
47 |
48 | (defn home []
49 | (reagent/create-class {:reagent-render home-render
50 | :component-did-mount home-did-mount}))
51 |
52 | (defn ^:export main []
53 | (rdom/render [home]
54 | (.getElementById js/document "app")))
55 |
56 |
--------------------------------------------------------------------------------
/recipes/toggle-class/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to toggle the class of an element (without using jQuery) in your [reagent](https://github.com/reagent-project/reagent) webapp.
4 |
5 | # Solution
6 |
7 | We are going to store the class of an element in a reagent atom, and then toggle the class when a button is clicked.
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary items to `resources/public/index.html`
13 | 3. Create a `toggle-class` function
14 | 4. Create [form-2](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-2--a-function-returning-a-function) component with a reagent atom called `local-state`
15 | 5. Add a button to `home` and uses the `toggle-class` function on-click
16 |
17 | #### Step 1: Create a new project
18 |
19 | ```
20 | $ lein new rc toggle-class
21 | ```
22 |
23 | #### Step 2: Add necessary items to `resources/public/index.html`
24 |
25 | Let's add Bootstrap css so we can use their button classes in our toggle button.
26 |
27 | ```html
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | #### Step 3: Create a `toggle-class` function
45 |
46 | Navigate to `src/cljs/toggle_class/core.cljs`.
47 |
48 | ```clojure
49 | (defn toggle-class [a k class1 class2]
50 | (if (= (@a k) class1)
51 | (swap! a assoc k class2)
52 | (swap! a assoc k class1)))
53 | ```
54 |
55 | #### Step 4: Create a [form-2](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-2--a-function-returning-a-function) component with a reagent atom called `local-state`
56 |
57 | ```clojure
58 | (defn home []
59 | (let [local-state (reagent/atom {:btn-class "btn btn-default"})]
60 | (fn []
61 | [:div ])))
62 | ```
63 |
64 | #### Step 5: Add a button to `home` and uses the `toggle-class` function on-click
65 |
66 | ```clojure
67 | (defn home []
68 | (let [local-state (reagent/atom {:btn-class "btn btn-default"})]
69 | (fn []
70 | ;; ATTENTION \/
71 | [:div {:class (@local-state :btn-class)
72 | :on-click #(toggle-class local-state :btn-class "btn btn-default" "btn btn-danger")}
73 | "Click me"]
74 | ;; ATTENTION /\
75 | )))
76 | ```
77 |
78 | # Usage
79 |
80 | Compile cljs files.
81 |
82 | ```
83 | $ lein clean
84 | $ lein cljsbuild once prod
85 | ```
86 |
87 | Open `resources/public/index.html`.
88 |
--------------------------------------------------------------------------------
/recipes/toggle-class/project.clj:
--------------------------------------------------------------------------------
1 | (defproject toggle-class "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]]
5 |
6 | :source-paths ["src/clj"]
7 |
8 | :plugins [[lein-cljsbuild "1.1.8"]]
9 |
10 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
11 |
12 | :cljsbuild {:builds [{:id "prod"
13 | :source-paths ["src/cljs"]
14 | :compiler {:output-to "resources/public/js/compiled/app.js"
15 | :optimizations :advanced
16 | :pretty-print false}}]})
17 |
--------------------------------------------------------------------------------
/recipes/toggle-class/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/recipes/toggle-class/src/cljs/toggle_class/core.cljs:
--------------------------------------------------------------------------------
1 | (ns toggle-class.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]))
4 |
5 | (defn toggle-class [a k class1 class2]
6 | (if (= (@a k) class1)
7 | (swap! a assoc k class2)
8 | (swap! a assoc k class1)))
9 |
10 | (defn home []
11 | (let [local-state (reagent/atom {:btn-class "btn btn-default"})]
12 | (fn []
13 | [:div {:class (@local-state :btn-class)
14 | :on-click #(toggle-class local-state :btn-class "btn btn-default" "btn btn-danger")}
15 | "Click me"])))
16 |
17 | (defn ^:export main []
18 | (rdom/render [home]
19 | (.getElementById js/document "app")))
20 |
21 |
--------------------------------------------------------------------------------
/recipes/typeaheadjs/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add [typeahead.js](https://twitter.github.io/typeahead.js/) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | We are going to follow this [example](https://twitter.github.io/typeahead.js/examples/).
8 |
9 | *Steps*
10 |
11 | 1. Create a new project
12 | 2. Add necessary dependencies
13 | 3. Add necessary items to `resources/public/index.html`
14 | 4. Create the typeahead component
15 | 5. Add typeahead element to `home-render`
16 |
17 |
18 | #### Step 1: Create a new project
19 |
20 | ```
21 | $ lein new rc typeahead
22 | ```
23 |
24 | #### Step 2: Add necessary dependencies
25 |
26 | These dependnecies will provide the necessary externs for the advanced compilation:
27 |
28 | ```clojure
29 | [cljsjs/jquery "2.2.2-0"]
30 | [cljsjs/typeahead-bundle "0.11.1-1"]
31 | ```
32 |
33 | #### Step 3: Add necessary items to `resources/public/index.html`
34 |
35 | ```html
36 |
37 |
38 | ```
39 |
40 | #### Step 4: Create a typeahead element
41 |
42 | Navigate to `src/cljs/autocomplete/core.cljs`.
43 |
44 | We'll start by creating a vector of states and the matcher function:
45 |
46 | ```clojure
47 | (def states
48 | ["Alabama" "Alaska" "Arizona" "Arkansas" "California"
49 | "Colorado" "Connecticut" "Delaware" "Florida" "Georgia" "Hawaii"
50 | "Idaho" "Illinois" "Indiana" "Iowa" "Kansas" "Kentucky" "Louisiana"
51 | "Maine" "Maryland" "Massachusetts" "Michigan" "Minnesota"
52 | "Mississippi" "Missouri" "Montana" "Nebraska" "Nevada" "New Hampshire"
53 | "New Jersey" "New Mexico" "New York" "North Carolina" "North Dakota"
54 | "Ohio" "Oklahoma" "Oregon" "Pennsylvania" "Rhode Island"
55 | "South Carolina" "South Dakota" "Tennessee" "Texas" "Utah" "Vermont"
56 | "Virginia" "Washington" "West Virginia" "Wisconsin" "Wyoming"])
57 |
58 | (defn matcher [strs]
59 | (fn [text callback]
60 | (->> strs
61 | (filter #(s/includes? % text))
62 | (clj->js)
63 | (callback))))
64 | ```
65 |
66 | Next, we'll write the wrapper for the typeahead component. This will be called when the typeahead is mounted in the DOM:
67 |
68 | ```clojure
69 | (defn typeahead-mounted [this]
70 | (.typeahead (js/$ (reagent/dom-node this))
71 | (clj->js {:hint true
72 | :highlight true
73 | :minLength 1})
74 | (clj->js {:name "states"
75 | :source (matcher states)})))
76 | ```
77 |
78 | We'll now add an atom to hold the state of the typeahead and the *render* function:
79 |
80 | ```clojure
81 | (def typeahead-value (reagent/atom nil))
82 |
83 | (defn render-typeahead []
84 | [:input.typeahead
85 | {:type :text
86 | :on-select #(reset! typeahead-value (-> % .-target .-value))
87 | :placeholder "Programming Languages"}])
88 | ```
89 |
90 | Finally, we'll write the typeahead component:
91 |
92 | ```clojure
93 | (defn typeahead []
94 | (reagent/create-class
95 | {:component-did-mount typeahead-mounted
96 | :reagent-render render-typeahead}))
97 | ```
98 |
99 | #### Step 5: Add typeahead element to `home-render`
100 |
101 | ```clojure
102 | (defn home []
103 | [:div.ui-widget
104 | (when-let [language @typeahead-value]
105 | [:label "selected: " language])
106 | [typeahead]])
107 | ```
108 |
109 | # Usage
110 |
111 | Compile cljs files.
112 |
113 | ```
114 | $ lein clean
115 | $ lein cljsbuild once prod
116 | ```
117 |
118 | Open `resources/public/index.html`.
119 |
--------------------------------------------------------------------------------
/recipes/typeaheadjs/project.clj:
--------------------------------------------------------------------------------
1 | (defproject autocomplete "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [cljsjs/jquery "3.4.0-0"]
6 | [cljsjs/typeahead-bundle "0.11.1-2"]]
7 |
8 | :source-paths ["src/clj"]
9 |
10 | :plugins [[lein-cljsbuild "1.1.8"]]
11 |
12 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
13 |
14 | :cljsbuild {:builds [{:id "dev"
15 | :source-paths ["src/cljs"]
16 | :compiler {:main "typeahead.core"
17 | :output-to "resources/public/js/compiled/app.js"
18 | :optimizations :none
19 | :pretty-print true}}
20 | {:id "prod"
21 | :source-paths ["src/cljs"]
22 | :compiler {:output-to "resources/public/js/compiled/app.js"
23 | :optimizations :advanced
24 | :pretty-print false}}]})
25 |
--------------------------------------------------------------------------------
/recipes/typeaheadjs/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/recipes/typeaheadjs/src/cljs/typeahead/core.cljs:
--------------------------------------------------------------------------------
1 | (ns typeahead.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]
4 | [clojure.string :as s]))
5 |
6 | (def states
7 | ["Alabama" "Alaska" "Arizona" "Arkansas" "California"
8 | "Colorado" "Connecticut" "Delaware" "Florida" "Georgia" "Hawaii"
9 | "Idaho" "Illinois" "Indiana" "Iowa" "Kansas" "Kentucky" "Louisiana"
10 | "Maine" "Maryland" "Massachusetts" "Michigan" "Minnesota"
11 | "Mississippi" "Missouri" "Montana" "Nebraska" "Nevada" "New Hampshire"
12 | "New Jersey" "New Mexico" "New York" "North Carolina" "North Dakota"
13 | "Ohio" "Oklahoma" "Oregon" "Pennsylvania" "Rhode Island"
14 | "South Carolina" "South Dakota" "Tennessee" "Texas" "Utah" "Vermont"
15 | "Virginia" "Washington" "West Virginia" "Wisconsin" "Wyoming"])
16 |
17 | (defn matcher [strs]
18 | (fn [text callback]
19 | (->> strs
20 | (filter #(s/includes? % text))
21 | (clj->js)
22 | (callback))))
23 |
24 | (defn typeahead-mounted [this]
25 | (.typeahead (js/$ (rdom/dom-node this))
26 | (clj->js {:hint true
27 | :highlight true
28 | :minLength 1})
29 | (clj->js {:name "states"
30 | :source (matcher states)})))
31 |
32 | (def typeahead-value (reagent/atom nil))
33 |
34 | (defn render-typeahead []
35 | [:input.typeahead
36 | {:type :text
37 | :on-select #(reset! typeahead-value (-> % .-target .-value))
38 | :placeholder "States of USA"}])
39 |
40 | (defn typeahead []
41 | (reagent/create-class
42 | {:component-did-mount typeahead-mounted
43 | :reagent-render render-typeahead}))
44 |
45 | (defn home []
46 | [:div.ui-widget
47 | [:p "selected state: " @typeahead-value]
48 | [typeahead]])
49 |
50 | (defn ^:export main []
51 | (rdom/render [home]
52 | (.getElementById js/document "app")))
53 |
54 |
55 |
--------------------------------------------------------------------------------
/recipes/undo/README.md:
--------------------------------------------------------------------------------
1 | # Problem
2 |
3 | You want to add *undo* via [historian](https://github.com/reagent-project/historian) to your [reagent](https://github.com/reagent-project/reagent) web application.
4 |
5 | # Solution
6 |
7 | *Steps*
8 |
9 | 1. Create a new project
10 | 2. Add Historian to `project.clj` :dependencies vector
11 | 3. Add Historian to `core.cljs` namespace
12 | 4. Add a reagent atom called `app-state`
13 | 5. Have Historian record changes in `app-state`
14 | 6. Add an increment and undo button to `home`
15 |
16 | #### Step 1: Create a new project
17 |
18 | ```
19 | $ lein new rc undo
20 | ```
21 |
22 | #### Step 2: Add Historian to `project.clj` :dependencies vector
23 |
24 | ```clojure
25 | [historian "1.1.0"]
26 | ```
27 |
28 | #### Step 3: Add Historian to `core.cljs` namespace
29 |
30 | Navigate to `src/cljs/undo/core.cljs` and update the `ns` to the following.
31 |
32 | ```clojure
33 | (ns undo.core
34 | (:require [reagent.core :as reagent]
35 | [historian.core :as hist]))
36 | ```
37 |
38 | #### Step 4: Add a reagent atom called `app-state`
39 |
40 | ```clojure
41 | (def app-state (reagent/atom {:counter 0}))
42 | ```
43 |
44 | #### Step 5: Have Historian record changes in `app-state`
45 |
46 | ```clojure
47 | (hist/record! app-state :app-state)
48 | ```
49 |
50 | #### Step 6: Add an increment and undo button to `home`
51 |
52 | ```clojure
53 | (defn home []
54 | [:div
55 | [:div "Current count: " (@app-state :counter)]
56 | [:button {:on-click #(swap! app-state update-in [:counter] inc)} "Increment"]
57 | [:button {:on-click #(hist/undo!)} "Undo"] ])
58 | ```
59 |
60 | # Usage
61 |
62 | Compile cljs files.
63 |
64 | ```
65 | $ lein clean
66 | $ lein cljsbuild once prod
67 | ```
68 |
69 | Open `resources/public/index.html`.
70 |
--------------------------------------------------------------------------------
/recipes/undo/project.clj:
--------------------------------------------------------------------------------
1 | (defproject undo "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.1"]
3 | [org.clojure/clojurescript "1.10.773"]
4 | [reagent "1.0.0"]
5 | [historian "1.2.2"]]
6 |
7 | :source-paths ["src/clj"]
8 |
9 | :plugins [[lein-cljsbuild "1.1.8"]]
10 |
11 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "test/js"]
12 |
13 | :cljsbuild {:builds [{:id "prod"
14 | :source-paths ["src/cljs"]
15 | :compiler {:output-to "resources/public/js/compiled/app.js"
16 | :optimizations :advanced
17 | :pretty-print false}}]})
18 |
--------------------------------------------------------------------------------
/recipes/undo/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/recipes/undo/src/cljs/undo/core.cljs:
--------------------------------------------------------------------------------
1 | (ns undo.core
2 | (:require [reagent.core :as reagent]
3 | [reagent.dom :as rdom]
4 | [historian.core :as hist]))
5 |
6 | (def app-state (reagent/atom {:counter 0}))
7 |
8 | (hist/record! app-state :app-state)
9 |
10 | (defn home []
11 | [:div
12 | [:div "Current count: " (@app-state :counter)]
13 | [:button {:on-click #(swap! app-state update-in [:counter] inc)} "Increment"]
14 | [:button {:on-click #(hist/undo!)} "Undo"] ])
15 |
16 | (defn ^:export main []
17 | (rdom/render [home]
18 | (.getElementById js/document "app")))
19 |
20 |
--------------------------------------------------------------------------------