├── .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 | --------------------------------------------------------------------------------