├── .gitignore ├── LICENSE ├── README.md └── recipes ├── animator ├── .gitignore ├── README.md ├── index.html ├── project.clj └── src │ └── animator │ ├── animator.cljs │ ├── clock.cljs │ └── core.cljs ├── auto-complete ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ ├── clj │ │ │ └── auto_complete │ │ │ │ └── dev.clj │ │ └── cljs │ │ │ └── auto_complete │ │ │ └── main.cljs │ ├── prod │ │ ├── clj │ │ │ └── auto_complete │ │ │ │ └── dev.clj │ │ └── cljs │ │ │ └── auto_complete │ │ │ └── main.cljs │ └── test │ │ ├── js │ │ ├── polyfill.js │ │ └── unit-test.js │ │ └── unit-test.html ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── auto_complete │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── auto_complete │ │ └── core.cljs └── system.properties ├── boot-setup ├── .gitignore ├── README.md ├── boot.properties ├── build.boot ├── resources │ └── public │ │ └── index.html └── src │ ├── boot_setup │ └── core.cljs │ └── public │ └── main.cljs.edn ├── datascript-query-browser ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── datascript_query_browser │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── datascript_query_browser │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── datascript_query_browser │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── datascript_query_browser │ │ └── core.cljs └── system.properties ├── dev-setup ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── dev_setup │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── dev_setup │ │ └── prod.cljs ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── dev_setup │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── dev_setup │ │ └── core.cljs └── system.properties ├── dimple-bar-chart ├── .gitignore ├── README.md ├── index.html ├── project.clj └── src │ └── dimple_bar_chart │ └── core.cljs ├── example ├── LICENSE ├── README.md ├── project.clj ├── resources │ └── index.html └── src │ ├── clj │ └── example │ │ └── server.clj │ └── cljs │ └── example │ └── core.cljs ├── input-validation ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── input_validation │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── input_validation │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── input_validation │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── input_validation │ │ └── core.cljs └── system.properties ├── kioo-blog ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── kioo_blog │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── kioo_blog │ │ └── prod.cljs ├── project.clj ├── resources │ ├── blog.html │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── kioo_blog │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── kioo_blog │ │ └── core.cljs └── system.properties ├── knob ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── knob │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── knob │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── knob │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── knob │ │ └── core.cljs └── system.properties ├── local-state ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── local_state │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── local_state │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── local_state │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── local_state │ │ └── core.cljs └── system.properties ├── routing-with-secretary ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── routing_with_secretary │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── routing_with_secretary │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── routing_with_secretary │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── routing_with_secretary │ │ └── core.cljs └── system.properties ├── scrollbar ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── scrollbar │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── scrollbar │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── scrollbar │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── scrollbar │ │ └── core.cljs └── system.properties ├── sortable-table ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── sortable_table │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── sortable_table │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── sortable_table │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── sortable_table │ │ └── core.cljs └── system.properties ├── toggle-button ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── env │ ├── dev │ │ └── cljs │ │ │ └── toggle_button │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── toggle_button │ │ └── prod.cljs ├── preview.gif ├── project.clj ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css ├── src │ ├── clj │ │ └── toggle_button │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── toggle_button │ │ └── core.cljs └── system.properties └── views ├── .gitignore ├── README.md ├── index.html ├── project.clj └── src └── views └── core.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .DS_Store 9 | .lein-deps-sum 10 | .lein-repl-history 11 | .lein-plugins/ 12 | .lein-failures 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | om-cookbook 2 | =========== 3 | 4 | Home of Om recipes 5 | 6 | ## Contributions 7 | 8 | Contributions welcomed! Please fork, branch, and submit a PR. If you'd 9 | like to help out with maintenance, drop me a message. 10 | 11 | ## Recipes 12 | 13 | 1. [Dev Setup](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/dev-setup) 14 | Basic dev environment setup with chestnut that includes Figwheel, Weasel and 15 | ring server. 16 | 2. [Boot Setup](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/boot-setup) 17 | How to start a new Om project with boot-cljs. 18 | 3. [Dimple Bar Chart](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/dimple-bar-chart) 19 | Simple vertical bar chart created with dimple.js 20 | 4. [Views](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/views) 21 | Multiple views of a single data source (using multimethods). 22 | 5. [Components Using Local State](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/local-state) 23 | Making components with local state. 24 | 6. [Color Coded Input Validation](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/input-validation) 25 | 7. [Input Auto-completion](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/auto-complete) 26 | 8. [Single Attribute Sortable Table](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/sortable-table) 27 | 9. [Secretary Routing with Om Components](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/routing-with-secretary) 28 | 10. [Custom Toggle Buttons](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/toggle-button) 29 | 11. [DataScript Query Browser](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/datascript-query-browser) 30 | 12. [Custom Scrolling Bar](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/scrollbar) 31 | 13. [Circle Rotation Knobs](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/knob) 32 | 14. [Canvas Animation](https://github.com/om-cookbook/om-cookbook/tree/master/recipes/animator) 33 | 15. [Kioo Example - Enlive/Enfocus Style Templates](https://github.com/omcljs/om-cookbook/tree/master/recipes/kioo-blog) 34 | 35 | -------------------------------------------------------------------------------- /recipes/animator/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | -------------------------------------------------------------------------------- /recipes/animator/README.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | You want to do scripted HTML Canvas animations. 4 | 5 | # Solution 6 | 7 | 1. Create a project structure using Leiningen template 8 | [mies](https://github.com/swannodette/mies). 9 | 10 | ``` 11 | $ lein new mies animator 12 | ``` 13 | 14 | 2. Add om, sablono, and core.async dependencies to project.clj. 15 | 16 | ```clojure 17 | :dependencies [[org.clojure/clojure "1.6.0"] 18 | [org.clojure/clojurescript "0.0-2371"] 19 | 20 | [om "0.8.0-alpha2"] 21 | [sablono "0.2.20"] 22 | [org.clojure/core.async "0.1.346.0-17112a-alpha"]] 23 | ``` 24 | 25 | 3. Add react to index.html, as well as a div to hold our animation. 26 | 27 | ```html 28 | 29 | 30 | Hello, Animator! 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | 4. Copy animator.cljs and clock.cljs into your project. These 42 | act as a library that you will use, but they are very small, and you might 43 | want to customize them. 44 | 45 | 5. Now we start working with our own code, in core.cljs. 46 | Create a function that draws something in a canvas. 47 | It should take a drawing-context argument and whatever else it needs. 48 | 49 | ```clojure 50 | (defn draw-circle 51 | [context center radius line-width scale {:keys [r g b a] 52 | :or {a 1.0}}] 53 | (let [h (.-height (.-canvas context)) 54 | center-x (* scale (:x center)) 55 | center-y (- h (* scale (:y center))) 56 | radius (* scale radius)] 57 | (set! (. context -strokeStyle) (str "rgba(" r "," g "," b "," a ")")) 58 | (set! (. context -lineWidth) line-width) 59 | (.beginPath context) 60 | ;; x y radius startAngle endAngle counterClockwise?: 61 | (.arc context center-x center-y radius 0 (* 2 Math/PI) false) 62 | (.stroke context))) 63 | ``` 64 | 65 | 6. Create a function that calculates some property based upon the elapsed time. 66 | It should take elapsed-time and whatever else it needs. In this case we are returning 67 | an alpha of 1 (opaque) at the time the animation is to start, and values 68 | linearly decreasing to 0 (transparent) at the time the animation is to end. 69 | 70 | ```clojure 71 | (defn alpha 72 | [elapsed-time delay duration] 73 | (* .001 (- (+ delay duration) elapsed-time))) 74 | ``` 75 | 76 | 7. Now create our update function using the two functions we have already defined. 77 | It must take elapsed-time and canvas, followed by a map containing all the parameters. 78 | 79 | ```clojure 80 | (defn fading-circle-update 81 | [elapsed-time canvas {:keys [center radius line-width scale color delay duration] :as opts}] 82 | (let [context (.getContext canvas "2d") 83 | a (alpha elapsed-time delay duration)] 84 | (when (>= elapsed-time delay) 85 | (.clearRect context 0 0 (.-width canvas) (.-height canvas)) 86 | (draw-circle context center radius line-width scale (merge color {:a a}))))) 87 | ``` 88 | 89 | 8. Define a vector of your own animations. 90 | Each animation is a map containing an update function 91 | and all the parameters that the update function needs. 92 | Each animation will run in its own canvas element, 93 | with the later ones in the vector rendering in front of the earlier ones. 94 | 95 | ```clojure 96 | (defonce animations [{:update fading-circle-update 97 | :center {:x 400 :y 200} 98 | :radius 75 99 | :line-width 2 100 | :scale 1 101 | :color {:r 0 :g 255 :b 0} 102 | :delay 0 103 | :duration 2000} 104 | 105 | {:update fading-circle-update 106 | :center {:x 450 :y 250} 107 | :radius 50 108 | :line-width 2 109 | :scale 1 110 | :color {:r 255 :g 0 :b 0} 111 | :delay 250 112 | :duration 1250} 113 | 114 | {:update fading-circle-update 115 | :center {:x 200 :y 340} 116 | :radius 25 117 | :line-width 2 118 | :scale 1 119 | :color {:r 255 :g 255 :b 0} 120 | :delay 750 121 | :duration 1250} 122 | 123 | {:update fading-circle-update 124 | :center {:x 100 :y 100} 125 | :radius 50 126 | :line-width 2 127 | :scale 1 128 | :color {:r 255 :g 0 :b 0} 129 | :delay 1000 130 | :duration 1000}]) 131 | ``` 132 | 133 | 9. Now let's create a component that builds an animator instance 134 | and passes it the required start-time parameter: 135 | 136 | ```clojure 137 | (defn example 138 | [cursor owner opts] 139 | (reify 140 | om/IRender 141 | (render 142 | [_] 143 | (dom/div #js {:width "800px" :height "500px"} 144 | 145 | (om/build animator 146 | cursor 147 | {;; You must pass a start-time in the state map. 148 | ;; In this case we want the animation timeline 149 | ;; to begin right away, so we use the current time 150 | ;; as reported by the browser: 151 | :state {:start-time (.now (.-performance js/window))} 152 | 153 | ;; You may pass a clock-interval in milliseconds 154 | ;; in the opts map (default is 10): 155 | :opts {:clock-interval 20}}))))) 156 | 157 | ``` 158 | 159 | 6. That's it. Run ``` lein cljsbuild once ``` in 160 | ```/recipes/animator``` and open ```index.html``` in a 161 | browser of your choice. You should see the animations. 162 | -------------------------------------------------------------------------------- /recipes/animator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, Animator! 4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /recipes/animator/project.clj: -------------------------------------------------------------------------------- 1 | (defproject animator "0.1.0-SNAPSHOT" 2 | :description "FIXME: write this!" 3 | :url "http://example.com/FIXME" 4 | 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [org.clojure/clojurescript "0.0-2371"] 7 | 8 | [om "0.8.0-alpha2"] 9 | [sablono "0.2.20"] 10 | [org.clojure/core.async "0.1.346.0-17112a-alpha"]] 11 | 12 | :plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"]] 13 | 14 | :source-paths ["src"] 15 | 16 | :cljsbuild { 17 | :builds [{:id "animator" 18 | :source-paths ["src"] 19 | :compiler { 20 | :output-to "animator.js" 21 | :output-dir "out" 22 | :optimizations :none 23 | :source-map true}}]}) 24 | -------------------------------------------------------------------------------- /recipes/animator/src/animator/animator.cljs: -------------------------------------------------------------------------------- 1 | (ns animator.animator 2 | (:require [om.core :as om :include-macros true] 3 | [sablono.core :refer-macros [html]] 4 | [cljs.core.async :refer [! out now))) 18 | (condp = ch 19 | stop (recur false) 20 | t (recur running?) 21 | start (recur true)))) 22 | [out start stop])) 23 | -------------------------------------------------------------------------------- /recipes/animator/src/animator/core.cljs: -------------------------------------------------------------------------------- 1 | (ns animator.core 2 | (:require [om.core :as om :include-macros true] 3 | [om.dom :as dom :include-macros true] 4 | [animator.animator :refer [animator]])) 5 | 6 | (enable-console-print!) 7 | 8 | 9 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 10 | ;; Define your own update function that draws something 11 | ;; into a canvas based on the elapsed time. 12 | ;; The animator knows nothing of its internals. 13 | 14 | 15 | ;; First, two helper functions: 16 | 17 | (defn draw-circle 18 | [context center radius line-width scale {:keys [r g b a] 19 | :or {a 1.0}}] 20 | (let [h (.-height (.-canvas context)) 21 | center-x (* scale (:x center)) 22 | center-y (- h (* scale (:y center))) 23 | radius (* scale radius)] 24 | (set! (. context -strokeStyle) (str "rgba(" r "," g "," b "," a ")")) 25 | (set! (. context -lineWidth) line-width) 26 | (.beginPath context) 27 | ;; x y radius startAngle endAngle counterClockwise?: 28 | (.arc context center-x center-y radius 0 (* 2 Math/PI) false) 29 | (.stroke context))) 30 | 31 | (defn alpha 32 | [elapsed-time delay duration] 33 | (* .001 (- (+ delay duration) elapsed-time))) 34 | 35 | ;; Here's the example update function. 36 | ;; elapsed-time and canvas are provided by the animator; 37 | ;; everything in the opts map must come from your animations: 38 | 39 | (defn fading-circle-update 40 | [elapsed-time canvas {:keys [center radius line-width scale color delay duration] :as opts}] 41 | (let [context (.getContext canvas "2d") 42 | a (alpha elapsed-time delay duration)] 43 | (when (>= elapsed-time delay) 44 | (.clearRect context 0 0 (.-width canvas) (.-height canvas)) 45 | (draw-circle context center radius line-width scale (merge color {:a a}))))) 46 | ;; 47 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 48 | 49 | 50 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 51 | ;; Define a vector of your own animations. 52 | ;; Each animation is a map containing an update function 53 | ;; and all the parameters that the update function needs. 54 | ;; Each animation will run in its own canvas element, 55 | ;; with the later ones in the vector rendering in front of the earlier ones. 56 | 57 | (defonce animations [{:update fading-circle-update 58 | :center {:x 400 :y 200} 59 | :radius 75 60 | :line-width 2 61 | :scale 1 62 | :color {:r 0 :g 255 :b 0} 63 | :delay 0 64 | :duration 2000} 65 | 66 | {:update fading-circle-update 67 | :center {:x 450 :y 250} 68 | :radius 50 69 | :line-width 2 70 | :scale 1 71 | :color {:r 255 :g 0 :b 0} 72 | :delay 250 73 | :duration 1250} 74 | 75 | {:update fading-circle-update 76 | :center {:x 200 :y 340} 77 | :radius 25 78 | :line-width 2 79 | :scale 1 80 | :color {:r 255 :g 255 :b 0} 81 | :delay 750 82 | :duration 1250} 83 | 84 | {:update fading-circle-update 85 | :center {:x 100 :y 100} 86 | :radius 50 87 | :line-width 2 88 | :scale 1 89 | :color {:r 255 :g 0 :b 0} 90 | :delay 1000 91 | :duration 1000}]) 92 | ;; 93 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 94 | 95 | 96 | ;; Create the example component. 97 | ;; Its render method will build an animator instance 98 | ;; and pass it the required start-time parameter: 99 | 100 | (defn example 101 | [cursor owner opts] 102 | (reify 103 | om/IRender 104 | (render 105 | [_] 106 | (dom/div #js {:width "800px" :height "500px"} 107 | 108 | (om/build animator 109 | cursor 110 | {;; You must pass a start-time in the state map. 111 | ;; In this case we want the animation timeline 112 | ;; to begin right away, so we use the current time 113 | ;; as reported by the browser: 114 | :state {:start-time (.now (.-performance js/window))} 115 | 116 | ;; You may pass a clock-interval in milliseconds 117 | ;; in the opts map (default is 10): 118 | :opts {:clock-interval 20}}))))) 119 | 120 | 121 | ;; I could have passed the animations vector as another member of 122 | ;; the opts map, and this example would work identically. 123 | ;; Passing it as app-state, as I do below, 124 | ;; lets me imagine user interactions modifying the animations 125 | ;; and resetting the start-time to run them again... 126 | 127 | (om/root example 128 | (atom animations) 129 | {:target (. js/document (getElementById "anim"))}) 130 | 131 | -------------------------------------------------------------------------------- /recipes/auto-complete/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/auto-complete/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/auto-complete.jar clojure.main -m auto-complete.server 2 | -------------------------------------------------------------------------------- /recipes/auto-complete/README.md: -------------------------------------------------------------------------------- 1 | # Input Auto-Complete 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut auto-complete -- --om-tools --http-kit 9 | cd auto-complete 10 | ``` 11 | 12 | 2) Use your editor of choice to open the file `src/cljs/auto_complete/core.cljs` 13 | 14 | 15 | 3) For this demo keep namespace as is. 16 | 17 | 18 | 4) Replace `app-state` with the following: 19 | 20 | ```clojure 21 | (defonce app-state 22 | (atom {:user-input "" 23 | :things ["Apple" 24 | "ant" 25 | "bird" 26 | "CAR, Audi" 27 | "crayon" 28 | "cooler" 29 | "DVD" 30 | "Dirt" 31 | "DOG, Beagle"]})) 32 | ``` 33 | 34 | 35 | 5) Create function for handling auto-complete logic. 36 | 37 | ```clojure 38 | (defn partial-complete 39 | "Takes a needle and looks for it in the haystack. 40 | Return all matches in a set or nil on empty match set. 41 | Empty needle causes empty match set. 42 | The needle will match any part of the string." 43 | [needle haystack] 44 | (when-not (empty? needle) 45 | (let [pattern (js/RegExp. (str ".* " needle ".*|" needle ".*") "i")] 46 | (not-empty (set (keep #(re-matches pattern %) haystack)))))) 47 | ``` 48 | 49 | 50 | 6) Create a view that will display the results of the completion function. 51 | 52 | ```clojure 53 | (defcomponent partial-complete-view [app _] 54 | (render [_])) 55 | ``` 56 | 57 | 58 | 7) Add the completion function to the view. 59 | 60 | ```clojure 61 | (defcomponent partial-complete-view [app _] 62 | (render 63 | [_] 64 | (dom/div 65 | (if-let [completions (partial-complete (:user-input app) (:things app))] 66 | (for [thing completions] 67 | (dom/div {} thing)) 68 | "No Completions.")))) 69 | ``` 70 | 71 | 72 | 8) Add user input field and apply onChange JavaScript event. 73 | This event should update the application state with `om/update!`. 74 | 75 | ```clojure 76 | (defcomponent user-input [app _] 77 | (render 78 | [_] 79 | (dom/input {:value (:user-input app) 80 | :on-change #(om/update! app :user-input (.. % -target -value))}))) 81 | ``` 82 | 83 | 84 | 9) Make a component view that places the user input view above the partial completion view. 85 | 86 | ```clojure 87 | (defcomponent auto-complete-view [app _] 88 | (render 89 | [_] 90 | (dom/div 91 | (om/build user-input app) 92 | (dom/br {}) 93 | (om/build partial-complete-view app)))) 94 | ``` 95 | 96 | 97 | 10) Replace or alter your main function to display the validation box view. 98 | 99 | ```clojure 100 | (defn main [] 101 | (om/root 102 | auto-complete-view 103 | app-state 104 | {:target (. js/document (getElementById "app"))})) 105 | ``` 106 | 107 | 108 | 11) Start a REPL with `lein repl` 109 | 110 | ``` 111 | nREPL server started on port 52193 on host 127.0.0.1 - nrepl://127.0.0.1:52193 112 | REPL-y 0.3.5, nREPL 0.2.6 113 | Clojure 1.6.0 114 | OpenJDK 64-Bit Server VM 1.8.0_31-b13 115 | Docs: (doc function-name-here) 116 | (find-doc "part-of-name-here") 117 | Source: (source function-name-here) 118 | Javadoc: (javadoc java-object-or-class-here) 119 | Exit: Control+D or (exit) or (quit) 120 | Results: Stored in vars *1, *2, *3, an exception in *e 121 | ``` 122 | 123 | 12) Call `run` to start the back end and compile your ClojureScript. 124 | 125 | ``` 126 | auto-complete.server=> (run) 127 | Figwheel: Starting server at http://localhost:3449 128 | Figwheel: Serving files from '(resources)/public' 129 | Starting web server on port 10555 . 130 | # 131 | auto-complete.server=> Compiling "resources/public/js/app.js" from ["env/dev/cljs" "src/cljs"]... 132 | WARNING: No such namespace: auto-complete.core at line 1 env/dev/cljs/auto_complete/main.cljs 133 | Successfully compiled "resources/public/js/app.js" in 20.965 seconds. 134 | notifying browser that file changed: /js/app.js 135 | notifying browser that file changed: /js/out/goog/deps.js 136 | notifying browser that file changed: /js/out/auto_complete/core.js 137 | notifying browser that file changed: /js/out/auto_complete/main.js 138 | ``` 139 | 140 | 13) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 141 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/dev/clj/auto_complete/dev.clj: -------------------------------------------------------------------------------- 1 | (ns auto-complete.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [figwheel-sidecar.auto-builder :as fig-auto] 7 | [figwheel-sidecar.core :as fig] 8 | [clojurescript-build.auto :as auto] 9 | [clojure.java.shell :refer [sh]])) 10 | 11 | (def is-dev? (env :is-dev)) 12 | 13 | (def inject-devmode-html 14 | (comp 15 | (set-attr :class "is-dev") 16 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 17 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 18 | (append (html [:script {:type "text/javascript"} "goog.require('auto_complete.main')"])))) 19 | 20 | (defn browser-repl [] 21 | (let [repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001)] 22 | (piggieback/cljs-repl :repl-env repl-env) 23 | (piggieback/cljs-eval repl-env '(in-ns 'auto-complete.core) {}))) 24 | 25 | (defn start-figwheel [] 26 | (let [server (fig/start-server { :css-dirs ["resources/public/css"] }) 27 | config {:builds [{:id "dev" 28 | :source-paths ["env/dev/cljs" "src/cljs"] 29 | :compiler {:output-to "resources/public/js/app.js" 30 | :output-dir "resources/public/js/out" 31 | :source-map "resources/public/js/out.js.map" 32 | :source-map-timestamp true 33 | :preamble ["react/react.min.js"]}}] 34 | :figwheel-server server}] 35 | (fig-auto/autobuild* config))) 36 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/dev/cljs/auto_complete/main.cljs: -------------------------------------------------------------------------------- 1 | (ns auto-complete.main 2 | (:require [auto-complete.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] 12 | (core/main))) 13 | 14 | (weasel/connect "ws://localhost:9001" :verbose true :print #{:repl :console}) 15 | 16 | (core/main) 17 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/prod/clj/auto_complete/dev.clj: -------------------------------------------------------------------------------- 1 | (ns auto-complete.dev 2 | (:require [environ.core :refer [env]])) 3 | 4 | (if (env :is-dev) 5 | (throw (Exception. (str "Production environment code is being loaded while the dev environment is active. " 6 | "You likely have compiled class files lying around from an uberjar build. " 7 | "Remove the target/ directory and try again.")))) 8 | 9 | (def is-dev? false) 10 | (def inject-devmode-html identity) 11 | (defn browser-repl [] 12 | (throw (Exception. "Browser connected REPL is not available in prod mode"))) 13 | (defn start-figwheel [] 14 | (throw (Exception. "Figwheel is not available in prod mode"))) 15 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/prod/cljs/auto_complete/main.cljs: -------------------------------------------------------------------------------- 1 | (ns auto-complete.main 2 | (:require [auto-complete.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/test/js/polyfill.js: -------------------------------------------------------------------------------- 1 | if (!Function.prototype.bind) { 2 | Function.prototype.bind = function(oThis) { 3 | if (typeof this !== 'function') { 4 | // closest thing possible to the ECMAScript 5 5 | // internal IsCallable function 6 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 7 | } 8 | 9 | var aArgs = Array.prototype.slice.call(arguments, 1), 10 | fToBind = this, 11 | fNOP = function() {}, 12 | fBound = function() { 13 | return fToBind.apply(this instanceof fNOP && oThis 14 | ? this 15 | : oThis, 16 | aArgs.concat(Array.prototype.slice.call(arguments))); 17 | }; 18 | 19 | fNOP.prototype = this.prototype; 20 | fBound.prototype = new fNOP(); 21 | 22 | return fBound; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/test/js/unit-test.js: -------------------------------------------------------------------------------- 1 | var page = require('webpage').create(); 2 | var url = phantom.args[0]; 3 | 4 | page.onConsoleMessage = function (message) { 5 | console.log(message); 6 | }; 7 | 8 | function exit(code) { 9 | setTimeout(function(){ phantom.exit(code); }, 0); 10 | phantom.onError = function(){}; 11 | } 12 | 13 | console.log("Loading URL: " + url); 14 | 15 | page.open(url, function (status) { 16 | if (status != "success") { 17 | console.log('Failed to open ' + url); 18 | phantom.exit(1); 19 | } 20 | 21 | console.log("Running test."); 22 | 23 | var result = page.evaluate(function() { 24 | return auto_complete.test_runner.runner(); 25 | }); 26 | 27 | if (result != 0) { 28 | console.log("*** Test failed! ***"); 29 | exit(1); 30 | } 31 | else { 32 | console.log("Test succeeded."); 33 | exit(0); 34 | } 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /recipes/auto-complete/env/test/unit-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /recipes/auto-complete/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/auto-complete/preview.gif -------------------------------------------------------------------------------- /recipes/auto-complete/project.clj: -------------------------------------------------------------------------------- 1 | (defproject auto-complete "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj"] 8 | :repl-options {:timeout 200000} ;; Defaults to 30000 (30 seconds) 9 | 10 | :test-paths ["spec/clj"] 11 | 12 | :dependencies [[org.clojure/clojure "1.6.0"] 13 | [org.clojure/clojurescript "0.0-2511" :scope "provided"] 14 | [ring "1.3.2"] 15 | [ring/ring-defaults "0.1.3"] 16 | [compojure "1.3.1"] 17 | [enlive "1.1.5"] 18 | [om "0.8.0-rc1"] 19 | [environ "1.0.0"] 20 | [http-kit "2.1.19"] 21 | [prismatic/om-tools "0.3.10"]] 22 | 23 | :plugins [[lein-cljsbuild "1.0.3"] 24 | [lein-environ "1.0.0"]] 25 | 26 | :min-lein-version "2.5.0" 27 | 28 | :uberjar-name "auto-complete.jar" 29 | 30 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 31 | :compiler {:output-to "resources/public/js/app.js" 32 | :output-dir "resources/public/js/out" 33 | :source-map "resources/public/js/out.js.map" 34 | :preamble ["react/react.min.js"] 35 | :optimizations :none 36 | :pretty-print true}}}} 37 | 38 | :profiles {:dev {:source-paths ["env/dev/clj"] 39 | :test-paths ["test/clj"] 40 | 41 | :dependencies [[figwheel "0.2.1-SNAPSHOT"] 42 | [figwheel-sidecar "0.2.1-SNAPSHOT"] 43 | [com.cemerick/piggieback "0.1.3"] 44 | [weasel "0.4.2"]] 45 | 46 | :repl-options {:init-ns auto-complete.server 47 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 48 | 49 | :plugins [[lein-figwheel "0.2.1-SNAPSHOT"]] 50 | 51 | :figwheel {:http-server-root "public" 52 | :server-port 3449 53 | :css-dirs ["resources/public/css"]} 54 | 55 | :env {:is-dev true} 56 | 57 | :cljsbuild {:test-commands { "test" ["phantomjs" "env/test/js/unit-test.js" "env/test/unit-test.html"] } 58 | :builds {:app {:source-paths ["env/dev/cljs"]} 59 | :test {:source-paths ["src/cljs" "test/cljs"] 60 | :compiler {:output-to "resources/public/js/app_test.js" 61 | :output-dir "resources/public/js/test" 62 | :source-map "resources/public/js/test.js.map" 63 | :preamble ["react/react.min.js"] 64 | :optimizations :whitespace 65 | :pretty-print false}}}}} 66 | 67 | :uberjar {:source-paths ["env/prod/clj"] 68 | :hooks [leiningen.cljsbuild] 69 | :env {:production true} 70 | :omit-source true 71 | :aot :all 72 | :cljsbuild {:builds {:app 73 | {:source-paths ["env/prod/cljs"] 74 | :compiler 75 | {:optimizations :advanced 76 | :pretty-print false}}}}}}) 77 | -------------------------------------------------------------------------------- /recipes/auto-complete/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/auto-complete/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/auto-complete/src/clj/auto_complete/dev.clj: -------------------------------------------------------------------------------- 1 | (ns auto-complete.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('auto_complete.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/auto-complete/src/clj/auto_complete/server.clj: -------------------------------------------------------------------------------- 1 | (ns auto-complete.server 2 | (:require [clojure.java.io :as io] 3 | [auto-complete.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [net.cgrand.enlive-html :refer [deftemplate]] 7 | [net.cgrand.reload :refer [auto-reload]] 8 | [ring.middleware.reload :as reload] 9 | [ring.middleware.defaults :refer [wrap-defaults api-defaults]] 10 | [environ.core :refer [env]] 11 | [org.httpkit.server :refer [run-server]])) 12 | 13 | (deftemplate page (io/resource "index.html") [] 14 | [:body] (if is-dev? inject-devmode-html identity)) 15 | 16 | (defroutes routes 17 | (resources "/") 18 | (resources "/react" {:root "react"}) 19 | (GET "/*" req (page))) 20 | 21 | (def http-handler 22 | (if is-dev? 23 | (reload/wrap-reload (wrap-defaults #'routes api-defaults)) 24 | (wrap-defaults routes api-defaults))) 25 | 26 | (defn run-web-server [& [port]] 27 | (let [port (Integer. (or port (env :port) 10555))] 28 | (print "Starting web server on port" port ".\n") 29 | (run-server http-handler {:port port :join? false}))) 30 | 31 | (defn run-auto-reload [& [port]] 32 | (auto-reload *ns*) 33 | (start-figwheel)) 34 | 35 | (defn run [& [port]] 36 | (when is-dev? 37 | (run-auto-reload)) 38 | (run-web-server port)) 39 | 40 | (defn -main [& [port]] 41 | (run port)) 42 | -------------------------------------------------------------------------------- /recipes/auto-complete/src/cljs/auto_complete/core.cljs: -------------------------------------------------------------------------------- 1 | (ns auto-complete.core 2 | (:require [om.core :as om :include-macros true] 3 | [om-tools.dom :as dom :include-macros true] 4 | [om-tools.core :refer-macros [defcomponent]])) 5 | 6 | (defonce app-state 7 | (atom {:user-input "" 8 | :things ["Apple" 9 | "ant" 10 | "bird" 11 | "CAR, Audi" 12 | "crayon" 13 | "cooler" 14 | "DVD" 15 | "Dirt" 16 | "DOG, Beagle"]})) 17 | 18 | (defn partial-complete 19 | "Takes a needle and looks for it in the haystack. 20 | Return all matches in a set or nil on empty match set. 21 | Empty needle causes empty match set." 22 | [needle haystack] 23 | (when-not (empty? needle) 24 | (let [pattern (js/RegExp. (str ".* " needle ".*|" needle ".*") "i")] 25 | (not-empty (set (keep #(re-matches pattern %) haystack)))))) 26 | 27 | (defcomponent partial-complete-view [app _] 28 | (render 29 | [_] 30 | (dom/div 31 | (if-let [completions (partial-complete (:user-input app) (:things app))] 32 | (for [thing completions] 33 | (dom/div {} thing)) 34 | "No Completions.")))) 35 | 36 | 37 | (defcomponent user-input [app owner] 38 | (render 39 | [_] 40 | (dom/input {:value (:user-input app) 41 | :on-change #(om/update! app :user-input (.. % -target -value))}))) 42 | 43 | (defcomponent auto-complete-view [app _] 44 | (render 45 | [_] 46 | (dom/div 47 | (om/build user-input app) 48 | (dom/br {}) 49 | (om/build partial-complete-view app)))) 50 | 51 | (defn main [] 52 | (om/root 53 | auto-complete-view 54 | app-state 55 | {:target (. js/document (getElementById "app"))})) 56 | -------------------------------------------------------------------------------- /recipes/auto-complete/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/boot-setup/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.boot 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | /.nrepl-history 12 | /resources/public/js 13 | /out 14 | /.repl 15 | -------------------------------------------------------------------------------- /recipes/boot-setup/README.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | You want to start a new om project with boot-cljs but don't know how. 4 | 5 | # Solution 6 | 7 | Read [introductory blog post][blog-post] on boot-cljs. 8 | 9 | ## Prepare 10 | 11 | [Install boot][installboot]. Then, in a terminal: 12 | 13 | ```bash 14 | boot -u 15 | ``` 16 | 17 | This will update boot to the latest stable release version. 18 | 19 | ## Use 20 | 21 | To serve the project at http://localhost:3000, watch the sources, 22 | build and reload your project, run: 23 | 24 | ```bash 25 | boot dev 26 | ``` 27 | 28 | The `dev` task as well as project dependencies are defined in 29 | [build.boot](./build.boot)-file. The `dev` task uses multiple other boot tasks: 30 | 31 | - [boot-cljs] to compile Cljs sources 32 | - [boot-cljs-repl] to provide nrepl server with Cljs middlewares 33 | - [boot-http] to serve http and js files 34 | - [boot-reload] to reload changed files 35 | 36 | You should read the `build.boot` to see how the tasks are combined. The task 37 | options should be documented on the file. 38 | 39 | Now open http://localhost:3000 and try changing something in core.cljs! 40 | To use cljs-repl in another terminal do: 41 | 42 | ``` 43 | boot repl --client 44 | boot.user=> (start-repl) 45 | ; Refresh the page 46 | boot.user=> (js/console.log "Hello!") 47 | boot.user=> (in-ns 'boot-setup.core) 48 | boot-setup.core=> (swap! app-state assoc :cong "Profit!") 49 | boot-setup.core=> (another-root) 50 | boot-setup.core=> (main) 51 | ``` 52 | 53 | ## More resources 54 | 55 | - More example projects 56 | - [boot-cljs-example] 57 | - [Saapas][saapas] 58 | - Best places to ask for help are `#hoplon` on Freenode IRC and `#boot` on 59 | [Clojurians Slack][clojurians] 60 | 61 | [installboot]: https://github.com/boot-clj/boot#install 62 | [blog-post]: http://adzerk.com/blog/2014/11/clojurescript-builds-rebooted/ 63 | [boot]: https://github.com/boot-clj/boot 64 | [boot-cljs-example]: https://github.com/adzerk/boot-cljs-example 65 | [boot-cljs]: https://github.com/adzerk-oss/boot-cljs 66 | [boot-cljs-repl]: https://github.com/adzerk-oss/boot-cljs-repl 67 | [boot-reload]: https://github.com/adzerk-oss/boot-reload 68 | [boot-http]: https://github.com/pandeiro/boot-http 69 | [saapas]: https://github.com/Deraen/saapas 70 | [clojurians]: https://clojurians.slack.com/ 71 | -------------------------------------------------------------------------------- /recipes/boot-setup/boot.properties: -------------------------------------------------------------------------------- 1 | #https://github.com/boot-clj/boot 2 | #Mon Jul 13 11:46:35 EEST 2015 3 | BOOT_CLOJURE_VERSION=1.7.0 4 | BOOT_VERSION=2.1.2 5 | -------------------------------------------------------------------------------- /recipes/boot-setup/build.boot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env boot 2 | 3 | (set-env! 4 | :source-paths #{"src"} 5 | :resource-paths #{"resources"} 6 | :dependencies '[[org.clojure/clojure "1.7.0"] 7 | [org.clojure/clojurescript "0.0-3308"] 8 | [adzerk/boot-cljs "0.0-3308-0" :scope "test"] 9 | [adzerk/boot-cljs-repl "0.1.10-SNAPSHOT" :scope "test"] 10 | [adzerk/boot-reload "0.3.1" :scope "test"] 11 | [pandeiro/boot-http "0.6.2" :scope "test"] 12 | [org.omcljs/om "0.8.8"]]) 13 | 14 | (task-options! 15 | pom {:project 'boot-setup 16 | :version "0.1.0-SNAPSHOT"}) 17 | 18 | (require '[adzerk.boot-cljs :refer [cljs]] 19 | '[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]] 20 | '[adzerk.boot-reload :refer [reload]] 21 | '[pandeiro.boot-http :refer [serve]]) 22 | 23 | (deftask dev 24 | "Development environment" 25 | [] 26 | ; Check `boot --help` for more documentation on the task options. 27 | (comp 28 | ; Serve the files directly from classpath. The files to serve located 29 | ; inside public/ prefix. This way e.g. the source code or other things 30 | ; in classpath are not available through http. 31 | (serve :resource-root "public") 32 | (watch) 33 | ; Starts a nrepl server with cljs middlewares. 34 | (cljs-repl) 35 | ; The interesting files in fileset/classpath and inside public prefix. 36 | ; The asset-path option removes given path from urls being reloaded, 37 | ; so when "public/index.html" changes, browser is told to reload 38 | ; "index.html". 39 | (reload :asset-path "public") 40 | ; Compiles the cljs. This searches for *.cljs.edn files and finds 41 | ; src/public/main.cljs.edn, from the relative path of the file inside 42 | ; fileset (public/main.cljs.edn) it sets the output-to and output-path 43 | ; for cljs compiler, and thus the resulting js is written to 44 | ; "public/main.js" and the rest of files to "public/out/". 45 | ; To make main.js refer to other files without public/ prefix, asset-path 46 | ; option is set manually. 47 | (cljs :source-map true :compiler-options {:asset-path "out"}))) 48 | -------------------------------------------------------------------------------- /recipes/boot-setup/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | my first boot-setup 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /recipes/boot-setup/src/boot_setup/core.cljs: -------------------------------------------------------------------------------- 1 | (ns boot-setup.core 2 | (:require [om.core :as om] 3 | [om.dom :as dom])) 4 | 5 | (def app-state (atom {:cong "Very much congratulated!"})) 6 | 7 | (defn main 8 | [] 9 | (om/root 10 | (fn [app owner] 11 | (reify 12 | om/IRender 13 | (render [_] 14 | (dom/div nil 15 | (dom/h1 nil "Yay!") 16 | (dom/p nil "I did it!!!") 17 | (dom/p nil (:cong app)))))) 18 | app-state 19 | {:target js/document.body})) 20 | 21 | (defn another-root 22 | [] 23 | (om/root 24 | (fn [app owner] 25 | (reify 26 | om/IRender 27 | (render [_] 28 | (dom/div nil 29 | (dom/h1 nil "Hello from prototype!"))))) 30 | app-state 31 | {:target js/document.body})) 32 | 33 | (set! (.-onload js/window) main) 34 | -------------------------------------------------------------------------------- /recipes/boot-setup/src/public/main.cljs.edn: -------------------------------------------------------------------------------- 1 | {:require [boot-setup.core] 2 | :init-fns [boot-setup.core/main]} 3 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/datascript-query-browser.jar clojure.main -m datascript-query-browser.server 2 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/README.md: -------------------------------------------------------------------------------- 1 | # DataScript Query Browser 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut datascript-query-browser -- --om-tools --http-kit 9 | ``` 10 | 11 | 2) Use your editor of choice to open the file `datascript-query-browser/src/cljs/core.cljs` 12 | 13 | 3) Your initial app-state should look like this. 14 | 15 | ```clojure 16 | (ns datascript-query-browser.core 17 | (:require [om.core :as om :include-macros true] 18 | [om-tools.dom :as dom :include-macros true] 19 | [om-tools.core :refer-macros [defcomponent]] 20 | [datascript :as d])) 21 | 22 | (def conn (d/create-conn {})) 23 | 24 | (defonce app-state 25 | (atom 26 | {:viewing-query 0 27 | :queries [] 28 | :query-results []})) 29 | ``` 30 | 31 | 4) This demo application requires dummy data called fake-people. 32 | 33 | ```clojure 34 | (def fake-people 35 | [{:db/id -1 :name/last "Larson" :name/first "Andrew" :amount 10242.94 } 36 | {:db/id -1 :name/last "Richardson" :name/first "Sarah" :amount 26327.99 } 37 | {:db/id -1 :name/last "Rice" :name/first "Emily" :amount 35799.79 } 38 | {:db/id -1 :name/last "Williams" :name/first "Cheryl" :amount 42043.31 } 39 | {:db/id -1 :name/last "Boyd" :name/first "Alan" :amount 18659.8 } 40 | {:db/id -1 :name/last "Ryan" :name/first "Donna" :amount 24686.99 } 41 | {:db/id -1 :name/last "Hayes" :name/first "Jane" :amount 38395.31 } 42 | {:db/id -1 :name/last "Lynch" :name/first "Kathleen" :amount 40031.52 } 43 | {:db/id -1 :name/last "White" :name/first "Jane" :amount 16953.81 } 44 | {:db/id -1 :name/last "Morgan" :name/first "Joyce" :amount 25542.66 } 45 | {:db/id -1 :name/last "Oliver" :name/first "Susan" :amount 35623.38 } 46 | {:db/id -1 :name/last "Bradley" :name/first "Jacqueline" :amount 4191.39 } 47 | {:db/id -1 :name/last "Gardner" :name/first "Steve" :amount 58599.19 } 48 | {:db/id -1 :name/last "Clark" :name/first "Dorothy" :amount 59395.47 } 49 | {:db/id -1 :name/last "Roberts" :name/first "Terry" :amount 55777.04 } 50 | {:db/id -1 :name/last "Dunn" :name/first "Jeremy" :amount 26784.03 } 51 | {:db/id -1 :name/last "Little" :name/first "Robin" :amount 13619.52 } 52 | {:db/id -1 :name/last "Fowler" :name/first "Harry" :amount 21089.65 } 53 | {:db/id -1 :name/last "Mitchell" :name/first "David" :amount 18071.86 } 54 | {:db/id -1 :name/last "Olson" :name/first "Jonathan" :amount 23951.22 } 55 | {:db/id -1 :name/last "Hunter" :name/first "Nicholas" :amount 35798.73 } 56 | {:db/id -1 :name/last "Lawson" :name/first "Norma" :amount 35146.94 } 57 | {:db/id -1 :name/last "Allen" :name/first "William" :amount 6512.05 }]) 58 | ``` 59 | 5) Create a function to add the fake people to the db once the main function is loaded. 60 | 61 | ```clojure 62 | (defn add-fake-people! [] 63 | (doseq [person fake-people] 64 | (d/transact! conn [person]))) 65 | ``` 66 | 67 | 6) The same can be done to make dummy queries. 68 | 69 | ```clojure 70 | (defn query-amount-minimum [n] 71 | (into '[:find ?a ?ln ?fn 72 | :where 73 | [?e :amount ?a] 74 | [?e :name/last ?ln] 75 | [?e :name/first ?fn]] 76 | [[(list '<= n '?a)]])) 77 | 78 | (def queries 79 | [{:title "Over 10k" :query (query-amount-minimum 10000)} 80 | {:title "Over 20k" :query (query-amount-minimum 20000)} 81 | {:title "Over 30k" :query (query-amount-minimum 30000)} 82 | {:title "Over 40k" :query (query-amount-minimum 40000)} 83 | {:title "Over 50k" :query (query-amount-minimum 50000)}]) 84 | 85 | (defn add-queries! [cursor] 86 | (om/update! cursor [:queries] queries)) 87 | ``` 88 | 7) Create a function to load both the dummy data and queries. 89 | 90 | ```clojure 91 | (defn add-demo-data! [cursor] 92 | (add-fake-people!) 93 | (add-queries! cursor)) 94 | ``` 95 | 96 | 8) Create a function that can cycle through all available queries. 97 | 98 | ```clojure 99 | (defn next-query! [cursor] 100 | (let [vqk :viewing-query 101 | qk :queries 102 | state cursor 103 | qc (-> state qk count dec) 104 | vq (-> state vqk inc) 105 | vq (if (> vq qc) 0 vq)] 106 | (om/update! cursor [vqk] vq))) 107 | ``` 108 | 109 | 9) Create a function that can fetch the query results. 110 | 111 | ```clojure 112 | (defn query-results! [cursor] 113 | (let [{:keys [queries viewing-query]} cursor 114 | current-query (nth queries viewing-query) 115 | {:keys [query]} current-query 116 | results (d/q query @conn)] 117 | (om/update! cursor [:query-results] results))) 118 | ``` 119 | 120 | 10) Create a query view component. 121 | 122 | ```clojure 123 | (defcomponent query-view [cursor _] 124 | (will-mount 125 | [_] 126 | (add-demo-data! cursor) 127 | (query-results! cursor)) 128 | (render 129 | [_] 130 | (let [{:keys [queries viewing-query]} cursor 131 | current-query (nth queries viewing-query) 132 | {:keys [title query]} current-query] 133 | (dom/div 134 | (dom/h1 "Query Viewer") 135 | (dom/h3 title) 136 | (dom/div {:style {:font-size "16px" 137 | :background-color "hsl(150,80%,95%)" 138 | :width "400px"}} 139 | (dom/pre {:style {:white-space "pre-wrap" 140 | :padding "20px"}} 141 | (let [r clojure.string/replace] 142 | (-> query 143 | pr-str 144 | (r ":find" "\n:find") 145 | (r ":in" "\n:in") 146 | (r ":where" "\n:where\t") 147 | (r "]" "]\n\t")))) 148 | (dom/button {:on-click (fn [_] 149 | (next-query! cursor) 150 | (query-results! cursor))} 151 | "Next Query")))))) 152 | 153 | ``` 154 | 155 | 11) Create a query results component. 156 | 157 | ```clojure 158 | (defcomponent query-results-view [cursor _] 159 | (render 160 | [_] 161 | (dom/ul 162 | (for [qr (:query-results cursor)] 163 | (dom/li 164 | (dom/table 165 | (dom/tr 166 | (for [x qr] 167 | (dom/td {:style {:padding "10px;"}} x))))))))) 168 | ``` 169 | 170 | 12) Create a main view component. 171 | 172 | ```clojure 173 | (defcomponent main-view [app _] 174 | (render 175 | [_] 176 | (dom/div 177 | (om/build query-view app) 178 | (om/build query-results-view app)))) 179 | ``` 180 | 181 | 13) Alter or replace your main function to display the main view component. 182 | 183 | ```clojure 184 | (defn main [] 185 | (om/root 186 | main-view 187 | app-state 188 | {:target (. js/document (getElementById "app"))})) 189 | ``` 190 | 191 | 14) Start a REPL with `lein repl` 192 | 193 | ``` 194 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 195 | REPL-y 0.3.5, nREPL 0.2.6 196 | Clojure 1.6.0 197 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 198 | Docs: (doc function-name-here) 199 | (find-doc "part-of-name-here") 200 | Source: (source function-name-here) 201 | Javadoc: (javadoc java-object-or-class-here) 202 | Exit: Control+D or (exit) or (quit) 203 | Results: Stored in vars *1, *2, *3, an exception in *e 204 | ``` 205 | 206 | 15) Call `run` to start the back end and compile your ClojureScript. 207 | 208 | ``` 209 | datascript-query-browser.server=> (run) 210 | Starting figwheel. 211 | Starting web server on port 10555 . 212 | # 213 | datascript-query-browser.server=> Compiling ClojureScript. 214 | Figwheel: Starting server at http://localhost:3449 215 | Figwheel: Serving files from '(dev-resources|resources)/public' 216 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 217 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 218 | notifying browser that file changed: /js/out/local_state/core.js 219 | ``` 220 | 221 | 16) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 222 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/env/dev/cljs/datascript_query_browser/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns datascript-query-browser.dev 2 | (:require [datascript-query-browser.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/env/prod/cljs/datascript_query_browser/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns datascript-query-browser.prod 2 | (:require [datascript-query-browser.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/datascript-query-browser/preview.gif -------------------------------------------------------------------------------- /recipes/datascript-query-browser/project.clj: -------------------------------------------------------------------------------- 1 | (defproject datascript-query-browser "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"] 20 | [http-kit "2.1.19"] 21 | [prismatic/om-tools "0.3.3"] 22 | [datascript "0.6.0"]] 23 | 24 | :plugins [[lein-cljsbuild "1.0.3"] 25 | [lein-environ "1.0.0"]] 26 | 27 | :min-lein-version "2.5.0" 28 | 29 | :uberjar-name "datascript-query-browser.jar" 30 | 31 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 32 | :compiler {:output-to "resources/public/js/app.js" 33 | :output-dir "resources/public/js/out" 34 | :source-map "resources/public/js/out.js.map" 35 | :preamble ["react/react.min.js"] 36 | :externs ["react/externs/react.js"] 37 | :optimizations :none 38 | :pretty-print true}}}} 39 | 40 | :profiles {:dev {:repl-options {:init-ns datascript-query-browser.server 41 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 42 | 43 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 44 | 45 | :figwheel {:http-server-root "public" 46 | :port 3449 47 | :css-dirs ["resources/public/css"]} 48 | 49 | :env {:is-dev true} 50 | 51 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 52 | 53 | :uberjar {:hooks [leiningen.cljsbuild] 54 | :env {:production true} 55 | :omit-source true 56 | :aot :all 57 | :cljsbuild {:builds {:app 58 | {:source-paths ["env/prod/cljs"] 59 | :compiler 60 | {:optimizations :advanced 61 | :pretty-print false}}}}}}) 62 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/src/clj/datascript_query_browser/dev.clj: -------------------------------------------------------------------------------- 1 | (ns datascript-query-browser.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('datascript_query_browser.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/src/clj/datascript_query_browser/server.clj: -------------------------------------------------------------------------------- 1 | (ns datascript-query-browser.server 2 | (:require [clojure.java.io :as io] 3 | [datascript-query-browser.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/src/cljs/datascript_query_browser/core.cljs: -------------------------------------------------------------------------------- 1 | (ns datascript-query-browser.core 2 | (:require [om.core :as om :include-macros true] 3 | [om-tools.dom :as dom :include-macros true] 4 | [om-tools.core :refer-macros [defcomponent]] 5 | [datascript :as d])) 6 | 7 | (def conn (d/create-conn {})) 8 | 9 | (defonce app-state 10 | (atom 11 | {:viewing-query 0 12 | :queries [] 13 | :query-results []})) 14 | 15 | (def fake-people 16 | [{:db/id -1 :name/last "Larson" :name/first "Andrew" :amount 10242.94 } 17 | {:db/id -1 :name/last "Richardson" :name/first "Sarah" :amount 26327.99 } 18 | {:db/id -1 :name/last "Rice" :name/first "Emily" :amount 35799.79 } 19 | {:db/id -1 :name/last "Williams" :name/first "Cheryl" :amount 42043.31 } 20 | {:db/id -1 :name/last "Boyd" :name/first "Alan" :amount 18659.8 } 21 | {:db/id -1 :name/last "Ryan" :name/first "Donna" :amount 24686.99 } 22 | {:db/id -1 :name/last "Hayes" :name/first "Jane" :amount 38395.31 } 23 | {:db/id -1 :name/last "Lynch" :name/first "Kathleen" :amount 40031.52 } 24 | {:db/id -1 :name/last "White" :name/first "Jane" :amount 16953.81 } 25 | {:db/id -1 :name/last "Morgan" :name/first "Joyce" :amount 25542.66 } 26 | {:db/id -1 :name/last "Oliver" :name/first "Susan" :amount 35623.38 } 27 | {:db/id -1 :name/last "Bradley" :name/first "Jacqueline" :amount 4191.39 } 28 | {:db/id -1 :name/last "Gardner" :name/first "Steve" :amount 58599.19 } 29 | {:db/id -1 :name/last "Clark" :name/first "Dorothy" :amount 59395.47 } 30 | {:db/id -1 :name/last "Roberts" :name/first "Terry" :amount 55777.04 } 31 | {:db/id -1 :name/last "Dunn" :name/first "Jeremy" :amount 26784.03 } 32 | {:db/id -1 :name/last "Little" :name/first "Robin" :amount 13619.52 } 33 | {:db/id -1 :name/last "Fowler" :name/first "Harry" :amount 21089.65 } 34 | {:db/id -1 :name/last "Mitchell" :name/first "David" :amount 18071.86 } 35 | {:db/id -1 :name/last "Olson" :name/first "Jonathan" :amount 23951.22 } 36 | {:db/id -1 :name/last "Hunter" :name/first "Nicholas" :amount 35798.73 } 37 | {:db/id -1 :name/last "Lawson" :name/first "Norma" :amount 35146.94 } 38 | {:db/id -1 :name/last "Allen" :name/first "William" :amount 6512.05 }]) 39 | 40 | (defn add-fake-people! [] 41 | (doseq [person fake-people] 42 | (d/transact! conn [person]))) 43 | 44 | (defn query-amount-minimum [n] 45 | (into '[:find ?a ?ln ?fn 46 | :where 47 | [?e :amount ?a] 48 | [?e :name/last ?ln] 49 | [?e :name/first ?fn]] 50 | [[(list '<= n '?a)]])) 51 | 52 | (def queries 53 | [{:title "Over 10k" :query (query-amount-minimum 10000)} 54 | {:title "Over 20k" :query (query-amount-minimum 20000)} 55 | {:title "Over 30k" :query (query-amount-minimum 30000)} 56 | {:title "Over 40k" :query (query-amount-minimum 40000)} 57 | {:title "Over 50k" :query (query-amount-minimum 50000)}]) 58 | 59 | (defn add-queries! [cursor] 60 | (om/update! cursor [:queries] queries)) 61 | 62 | (defn add-demo-data! [cursor] 63 | (add-fake-people!) 64 | (add-queries! cursor)) 65 | 66 | (defn next-query! [cursor] 67 | (let [vqk :viewing-query 68 | qk :queries 69 | state cursor 70 | qc (-> state qk count dec) 71 | vq (-> state vqk inc) 72 | vq (if (> vq qc) 0 vq)] 73 | (om/update! cursor [vqk] vq))) 74 | 75 | (defn query-results! [cursor] 76 | (let [{:keys [queries viewing-query]} cursor 77 | current-query (nth queries viewing-query) 78 | {:keys [query]} current-query 79 | results (d/q query @conn)] 80 | (om/update! cursor [:query-results] results))) 81 | 82 | (defcomponent query-view [cursor _] 83 | (will-mount 84 | [_] 85 | (add-demo-data! cursor) 86 | (query-results! cursor)) 87 | (render 88 | [_] 89 | (let [{:keys [queries viewing-query]} cursor 90 | current-query (nth queries viewing-query) 91 | {:keys [title query]} current-query] 92 | (dom/div 93 | (dom/h1 "Query Viewer") 94 | (dom/h3 title) 95 | (dom/div {:style {:font-size "16px" 96 | :background-color "hsl(150,80%,95%)" 97 | :width "400px"}} 98 | (dom/pre {:style {:white-space "pre-wrap" 99 | :padding "20px"}} 100 | (let [r clojure.string/replace] 101 | (-> query 102 | pr-str 103 | (r ":find" "\n:find") 104 | (r ":in" "\n:in") 105 | (r ":where" "\n:where\t") 106 | (r "]" "]\n\t")))) 107 | (dom/button {:on-click (fn [_] 108 | (next-query! cursor) 109 | (query-results! cursor))} 110 | "Next Query")))))) 111 | 112 | 113 | (defcomponent query-results-view [cursor _] 114 | (render 115 | [_] 116 | (dom/ul 117 | (for [qr (:query-results cursor)] 118 | (dom/li 119 | (dom/table 120 | (dom/tr 121 | (for [x qr] 122 | (dom/td {:style {:padding "10px;"}} x))))))))) 123 | 124 | 125 | (defcomponent main-view [app _] 126 | (render 127 | [_] 128 | (dom/div 129 | (om/build query-view app) 130 | (om/build query-results-view app)))) 131 | 132 | (defn main [] 133 | (om/root 134 | main-view 135 | app-state 136 | {:target (. js/document (getElementById "app"))})) 137 | -------------------------------------------------------------------------------- /recipes/datascript-query-browser/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/dev-setup/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/dev-setup/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/dev-setup.jar clojure.main -m dev-setup.server 2 | -------------------------------------------------------------------------------- /recipes/dev-setup/README.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | You'd like to start a new Om project that includes 4 | [Figwheel](https://github.com/bhauman/lein-figwheel) and 5 | REPL-connected browser 6 | 7 | # Solution 8 | 9 | Use [chestnut](https://github.com/plexus/chestnut) template: 10 | 11 | ```clojure 12 | $ lein new chestnut 13 | ``` 14 | 15 | This creates project structure, adds needed dependencies to your 16 | `project.clj`. If you open up `README.md` that was created, 17 | you'll see step by step instructions of how to proceed further. 18 | 19 | But the gist is: 20 | 21 | 1. Start the REPL 22 | 2. Do: 23 | 24 | ```clojure 25 | (run) 26 | ``` 27 | This will start the webserver at the port 10555 and Figweheel server 28 | at port 3449, as specified in your `project.clj`: 29 | 30 | ```clojure 31 | :figwheel {:http-server-root "public" 32 | :port 3449 33 | :css-dirs ["resources/public/css"]} 34 | ``` 35 | Your files will compile to `public` directory, and this is where 36 | you should put your CSS files too. You can modify it as needed. 37 | First time you exectute `run` it will take some time. Wait until 38 | files are compiled and you see `Successfully compiled 39 | "resources/public/app.js"`. 40 | You can open up the index page by going to `http://localhost:10555` 41 | in your browser. 42 | 43 | 3. Run `(browser-repl)` to start the Weasel REPL server 44 | This will take you to ClojureScript REPL. Make sure you have the 45 | page opened in a browser. You can evaluate expressions here and 46 | see the resoluts in your browser. 47 | 48 | 4. Whenever you modify your CSS or cljs files, you'll see your page 49 | being updated instantly. 50 | 51 | 5. Go to [Chestnut documention](http://plexus.github.io/chestnut/) for 52 | more information. 53 | -------------------------------------------------------------------------------- /recipes/dev-setup/env/dev/cljs/dev_setup/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns dev-setup.dev 2 | (:require [dev-setup.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/dev-setup/env/prod/cljs/dev_setup/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns dev-setup.prod 2 | (:require [dev-setup.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/dev-setup/project.clj: -------------------------------------------------------------------------------- 1 | (defproject dev-setup "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"]] 20 | 21 | :plugins [[lein-cljsbuild "1.0.3"] 22 | [lein-environ "1.0.0"]] 23 | 24 | :min-lein-version "2.5.0" 25 | 26 | :uberjar-name "dev-setup.jar" 27 | 28 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 29 | :compiler {:output-to "resources/public/js/app.js" 30 | :output-dir "resources/public/js/out" 31 | :source-map "resources/public/js/out.js.map" 32 | :preamble ["react/react.min.js"] 33 | :externs ["react/externs/react.js"] 34 | :optimizations :none 35 | :pretty-print true}}}} 36 | 37 | :profiles {:dev {:repl-options {:init-ns dev-setup.server 38 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 39 | 40 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 41 | 42 | :figwheel {:http-server-root "public" 43 | :port 3449 44 | :css-dirs ["resources/public/css"]} 45 | 46 | :env {:is-dev true} 47 | 48 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 49 | 50 | :uberjar {:hooks [leiningen.cljsbuild] 51 | :env {:production true} 52 | :omit-source true 53 | :aot :all 54 | :cljsbuild {:builds {:app 55 | {:source-paths ["env/prod/cljs"] 56 | :compiler 57 | {:optimizations :advanced 58 | :pretty-print false}}}}}}) 59 | -------------------------------------------------------------------------------- /recipes/dev-setup/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/dev-setup/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/dev-setup/src/clj/dev_setup/dev.clj: -------------------------------------------------------------------------------- 1 | (ns dev-setup.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('dev_setup.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/dev-setup/src/clj/dev_setup/server.clj: -------------------------------------------------------------------------------- 1 | (ns dev-setup.server 2 | (:require [clojure.java.io :as io] 3 | [dev-setup.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [ring.adapter.jetty :refer [run-jetty]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-jetty http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/dev-setup/src/cljs/dev_setup/core.cljs: -------------------------------------------------------------------------------- 1 | (ns dev-setup.core 2 | (:require [om.core :as om :include-macros true] 3 | [om.dom :as dom :include-macros true])) 4 | 5 | (defonce app-state (atom {:text "Hello Chestnut!"})) 6 | 7 | (defn main [] 8 | (om/root 9 | (fn [app owner] 10 | (reify 11 | om/IRender 12 | (render [_] 13 | (dom/h1 nil (:text app))))) 14 | app-state 15 | {:target (. js/document (getElementById "app"))})) 16 | -------------------------------------------------------------------------------- /recipes/dev-setup/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/dimple-bar-chart/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | -------------------------------------------------------------------------------- /recipes/dimple-bar-chart/README.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | You want to create a simple bar chart using [dimple.js](http://dimplejs.org/). 4 | 5 | # Solution 6 | 7 | 1. Create a project structure using Leiningen template 8 | [mies](https://github.com/swannodette/mies) 9 | 10 | ``` 11 | $ lein new mies dimple-bar-chart 12 | ``` 13 | 14 | 2. Add om and [sablono](https://github.com/r0man/sablono) to dependencies 15 | 16 | ```clojure 17 | :dependencies [[org.clojure/clojure "1.6.0"] 18 | [org.clojure/clojurescript "0.0-2371"] 19 | 20 | [om "0.8.0-alpha2"] 21 | ;; Hiccup style templating for React in ClojureScript 22 | [sablono "0.2.20"]] 23 | ``` 24 | 25 | 3. Add react, d3 and dimple to index.html 26 | 27 | ```html 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 4. Code to create a vertical bar chart using JavaScript looks like 41 | this: 42 | 43 | ```JavaScript 44 |
45 | 60 |
61 | ``` 62 | 63 | This creates a very simple bar chart. We're going to use interop to do 64 | the same in ClojureScript: 65 | 66 | ```Clojure 67 | (defn- draw-chart [data div {:keys [id chart]}] 68 | (let [{:keys [width height]} div 69 | {:keys [bounds plot 70 | x-axis y-axis]} chart 71 | Chart (.-chart js/dimple) 72 | svg (.newSvg js/dimple (str "#" id) width height) 73 | dimple-chart (.setBounds (Chart. svg) (:x bounds) (:y bounds) (:width bounds) (:height bounds)) 74 | x (.addCategoryAxis dimple-chart "x" x-axis) 75 | y (.addMeasureAxis dimple-chart "y" y-axis) 76 | s (.addSeries dimple-chart nil plot (clj->js [x y]))] 77 | (aset s "data" (clj->js data)) 78 | (.addLegend dimple-chart "5%" "10%" "20%" "10%" "right") 79 | (.draw dimple-chart))) 80 | ``` 81 | 82 | We've provided some options around the height, width and margins 83 | making the chart a little bit more robust. 84 | 85 | 5. Now let's create a component that builds this chart: 86 | 87 | We want to generate an empty div that d3/dimple will use for the 88 | SVG. Width and height are stored in our application state: 89 | 90 | ```Clojure 91 | om/IRender 92 | (render [_] 93 | (let [{:keys [width height]} (:div cursor)] 94 | (html 95 | [:div {:id id :width width :height height}]))) 96 | ``` 97 | Once the component is mounted, we can tell dimple to draw the chart: 98 | 99 | ```clojure 100 | om/IDidMount 101 | (did-mount [_] 102 | (when-let [data (seq (:data cursor))] 103 | (draw-chart data (:div cursor) opts))) 104 | ``` 105 | 106 | But we also want to update the chart whenever the data changes, so we 107 | want to do this: 108 | 109 | ```clojure 110 | om/IDidUpdate 111 | (did-update [_ _ _] 112 | (let [{:keys [width height]} (:div cursor)] 113 | (let [n (.getElementById js/document id)] 114 | (while (.hasChildNodes n) 115 | (.removeChild n (.-lastChild n)))) 116 | (when-let [data (seq (:data cursor))] 117 | (draw-chart data (:div cursor) opts)))) 118 | ``` 119 | We need to remove the old chart and let dimple create a new one. 120 | 121 | One last step: when we resize the window, we'd like the chart to 122 | resize as well. This can be done by adding an event listener: 123 | 124 | ```clojure 125 | om/IWillMount 126 | (will-mount [_] 127 | (.addEventListener js/window 128 | "resize" (fn [] 129 | (let [{:keys [width height]} (get-div-dimensions id)] 130 | (om/update! cursor :div {:width width :height height}))))) 131 | ``` 132 | 6. That's it. Run ``` lein cljsbuild once ``` in 133 | ```/recipes/dimple-bar-chart``` and open ```index.html``` in a 134 | browser of your choice. You should see the chart. 135 | -------------------------------------------------------------------------------- /recipes/dimple-bar-chart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /recipes/dimple-bar-chart/project.clj: -------------------------------------------------------------------------------- 1 | (defproject dimple-bar-chart "0.1.0-SNAPSHOT" 2 | :description "FIXME: write this!" 3 | :url "http://example.com/FIXME" 4 | 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [org.clojure/clojurescript "0.0-2371"] 7 | 8 | [om "0.8.0-alpha2"] 9 | ;; Hiccup style templating for React in ClojureScript 10 | [sablono "0.2.20"]] 11 | 12 | :plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"]] 13 | 14 | :source-paths ["src"] 15 | 16 | :cljsbuild { 17 | :builds [{:id "dimple-bar-chart" 18 | :source-paths ["src"] 19 | :compiler { 20 | :output-to "dimple_bar_chart.js" 21 | :output-dir "out" 22 | :optimizations :none 23 | :source-map true}}]}) 24 | -------------------------------------------------------------------------------- /recipes/dimple-bar-chart/src/dimple_bar_chart/core.cljs: -------------------------------------------------------------------------------- 1 | (ns dimple-bar-chart.core 2 | (:require [om.core :as om :include-macros true] 3 | [sablono.core :as html :refer-macros [html]])) 4 | 5 | (enable-console-print!) 6 | 7 | (defonce app-state (atom {:chart {:div {:width "100%" :height 600} 8 | :data [{:value 240000 :timestamp "2014-01-01"} 9 | {:value 260000 :timestamp "2014-02-01"} 10 | {:value 290000 :timestamp "2014-03-01"} 11 | {:value 70000 :timestamp "2014-04-01"} 12 | {:value 100000 :timestamp "2014-05-01"} 13 | {:value 120000 :timestamp "2014-06-01"} 14 | {:value 240000 :timestamp "2014-07-01"} 15 | {:value 220000 :timestamp "2014-08-01"} 16 | {:value 360000 :timestamp "2014-09-01"} 17 | {:value 260000 :timestamp "2014-10-01"} 18 | {:value 250000 :timestamp "2014-11-01"} 19 | {:value 190000 :timestamp "2014-12-01"}]}})) 20 | 21 | (defn get-div-dimensions 22 | "Get width and height of a div with a specified id." 23 | [id] 24 | (let [e (.getElementById js/document id) 25 | x (.-clientWidth e) 26 | y (.-clientHeight e)] 27 | {:width x :height y})) 28 | 29 | (defn- draw-chart [data div {:keys [id chart]}] 30 | (let [{:keys [width height]} div 31 | {:keys [bounds plot 32 | x-axis y-axis]} chart 33 | Chart (.-chart js/dimple) 34 | svg (.newSvg js/dimple (str "#" id) width height) 35 | dimple-chart (.setBounds (Chart. svg) (:x bounds) (:y bounds) (:width bounds) (:height bounds)) 36 | x (.addCategoryAxis dimple-chart "x" x-axis) 37 | y (.addMeasureAxis dimple-chart "y" y-axis) 38 | s (.addSeries dimple-chart nil plot (clj->js [x y]))] 39 | (aset s "data" (clj->js data)) 40 | (.addLegend dimple-chart "5%" "10%" "20%" "10%" "right") 41 | (.draw dimple-chart))) 42 | 43 | (defn bar-chart [cursor owner {:keys [id chart] :as opts}] 44 | (reify 45 | om/IWillMount 46 | (will-mount [_] 47 | ;; Add event listener that will update width & height when window is resized 48 | (.addEventListener js/window 49 | "resize" (fn [] 50 | (let [{:keys [width height]} (get-div-dimensions id)] 51 | (om/update! cursor :div {:width width :height height}))))) 52 | om/IRender 53 | (render [_] 54 | (let [{:keys [width height]} (:div cursor)] 55 | (html 56 | [:div {:id id :width width :height height}]))) 57 | om/IDidMount 58 | (did-mount [_] 59 | (when-let [data (seq (:data cursor))] 60 | (draw-chart data (:div cursor) opts))) 61 | om/IDidUpdate 62 | (did-update [_ _ _] 63 | (let [{:keys [width height]} (:div cursor)] 64 | (let [n (.getElementById js/document id)] 65 | (while (.hasChildNodes n) 66 | (.removeChild n (.-lastChild n)))) 67 | (when-let [data (seq (:data cursor))] 68 | (draw-chart data (:div cursor) opts)))))) 69 | 70 | (om/root 71 | (fn [app owner] 72 | (reify 73 | om/IRender 74 | (render [_] 75 | (html 76 | [:div 77 | [:h1 "Dimple vertical bar chart"] 78 | (om/build bar-chart (:chart app) {:opts {:id "chart" 79 | :chart {:bounds {:x "5%" 80 | :y "15%" 81 | :width "80%" 82 | :height "60%"} 83 | :plot js/dimple.plot.bar 84 | :x-axis "timestamp" 85 | :y-axis "value"}}})])))) 86 | app-state {:target (. js/document (getElementById "app"))}) 87 | -------------------------------------------------------------------------------- /recipes/example/README.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | Description of the problem. 4 | 5 | # Solution 6 | 7 | Description (walk-through) of the solution. 8 | -------------------------------------------------------------------------------- /recipes/example/project.clj: -------------------------------------------------------------------------------- 1 | (defproject example "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [om "0.7.3"] 12 | [sablono "0.2.22"]] 13 | 14 | :plugins [[lein-cljsbuild "1.0.3"] 15 | [lein-environ "1.0.0"]] 16 | 17 | :min-lein-version "2.5.0" 18 | 19 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 20 | :compiler {:output-to "resources/public/js/app.js" 21 | :output-dir "resources/public/js/out" 22 | :source-map "resources/public/js/out.js.map" 23 | :preamble ["react/react.min.js"] 24 | :externs ["react/externs/react.js"] 25 | :optimizations :none 26 | :pretty-print true}}}} 27 | 28 | :profiles {:dev {:cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}}}) 29 | -------------------------------------------------------------------------------- /recipes/example/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/example/src/clj/example/server.clj: -------------------------------------------------------------------------------- 1 | (ns example.server) 2 | -------------------------------------------------------------------------------- /recipes/example/src/cljs/example/core.cljs: -------------------------------------------------------------------------------- 1 | (ns example.core) 2 | -------------------------------------------------------------------------------- /recipes/input-validation/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/input-validation/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/input-validation.jar clojure.main -m input-validation.server 2 | -------------------------------------------------------------------------------- /recipes/input-validation/README.md: -------------------------------------------------------------------------------- 1 | # Color Coded Input Validation 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut input-validation -- --om-tools --http-kit 9 | ``` 10 | 11 | 2) Use your editor of choice to open the file `input-validation/src/cljs/core.cljs` 12 | 13 | 14 | 3) Your initial app-state should look like this. 15 | 16 | ```clojure 17 | (defonce app-state (atom {:user-input "some value"})) 18 | ``` 19 | 4) Create a user input component 20 | 21 | ```clojure 22 | (defcomponent user-input-view [app owner] 23 | (render [_])) 24 | ``` 25 | 26 | 5) Make it render an Input element 27 | 28 | ```clojure 29 | (defcomponent user-input-view [app owner] 30 | (render 31 | [_] 32 | (let [ref "user-input" 33 | k :user-input] 34 | (dom/input {:ref ref 35 | :value (k app)})))) 36 | ``` 37 | 38 | 6) Add an onChange JavaScript event handler. This handler should get the Input element's value and update the application state using `om/update!` 39 | 40 | ```clojure 41 | (defcomponent user-input-view [app owner] 42 | (render 43 | [_] 44 | (let [ref "user-input" 45 | k :user-input] 46 | (dom/input {:ref ref 47 | :value (k app) 48 | :on-change (fn [_] 49 | (let [this (om/get-node owner ref)] 50 | (om/update! app [k] (.-value this))))})))) 51 | ``` 52 | 53 | 7) Create a new function to validate user input. 54 | 55 | ```clojure 56 | (defn valid-input? [x] 57 | (> 10 (count x))) 58 | ``` 59 | 60 | 8) Create a new component that will display a valid or invalid state for the user input. 61 | 62 | ```clojure 63 | (defcomponent validation-box-view [app _] 64 | (render [_])) 65 | ``` 66 | 67 | 9) The user input view should be parented by the validation view. 68 | 69 | ```clojure 70 | (defcomponent validation-box-view [app _] 71 | (render 72 | [_] 73 | (dom/div 74 | (om/build user-input-view app)))) 75 | ``` 76 | 77 | 10) Add validation logic and styles. 78 | 79 | ```clojure 80 | (defcomponent validation-box-view [app _] 81 | (render 82 | [_] 83 | (let [valid-color "green" 84 | invalid-color "red" 85 | color (if (valid-input? (:user-input app)) 86 | valid-color 87 | invalid-color)] 88 | (dom/div {:style {:padding "10px;" 89 | :background-color color}} 90 | (om/build user-input-view app))))) 91 | ``` 92 | 93 | 11) Replace or alter your main function to display the validation box view. 94 | 95 | ```clojure 96 | (defn main [] 97 | (om/root 98 | validation-box-view 99 | app-state 100 | {:target (. js/document (getElementById "app"))})) 101 | ``` 102 | 12) Start a REPL with `lein repl` 103 | 104 | ``` 105 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 106 | REPL-y 0.3.5, nREPL 0.2.6 107 | Clojure 1.6.0 108 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 109 | Docs: (doc function-name-here) 110 | (find-doc "part-of-name-here") 111 | Source: (source function-name-here) 112 | Javadoc: (javadoc java-object-or-class-here) 113 | Exit: Control+D or (exit) or (quit) 114 | Results: Stored in vars *1, *2, *3, an exception in *e 115 | ``` 116 | 117 | 13) Call `run` to start the back end and compile your ClojureScript. 118 | 119 | ``` 120 | input-validation.server=> (run) 121 | Starting figwheel. 122 | Starting web server on port 10555 . 123 | # 124 | input-validation.server=> Compiling ClojureScript. 125 | Figwheel: Starting server at http://localhost:3449 126 | Figwheel: Serving files from '(dev-resources|resources)/public' 127 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 128 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 129 | notifying browser that file changed: /js/out/local_state/core.js 130 | ``` 131 | 132 | 14) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 133 | 134 | 135 | -------------------------------------------------------------------------------- /recipes/input-validation/env/dev/cljs/input_validation/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns input-validation.dev 2 | (:require [input-validation.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/input-validation/env/prod/cljs/input_validation/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns input-validation.prod 2 | (:require [input-validation.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/input-validation/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/input-validation/preview.gif -------------------------------------------------------------------------------- /recipes/input-validation/project.clj: -------------------------------------------------------------------------------- 1 | (defproject input-validation "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"] 20 | [http-kit "2.1.19"] 21 | [prismatic/om-tools "0.3.3"]] 22 | 23 | :plugins [[lein-cljsbuild "1.0.3"] 24 | [lein-environ "1.0.0"]] 25 | 26 | :min-lein-version "2.5.0" 27 | 28 | :uberjar-name "input-validation.jar" 29 | 30 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 31 | :compiler {:output-to "resources/public/js/app.js" 32 | :output-dir "resources/public/js/out" 33 | :source-map "resources/public/js/out.js.map" 34 | :preamble ["react/react.min.js"] 35 | :externs ["react/externs/react.js"] 36 | :optimizations :none 37 | :pretty-print true}}}} 38 | 39 | :profiles {:dev {:repl-options {:init-ns input-validation.server 40 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 41 | 42 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 43 | 44 | :figwheel {:http-server-root "public" 45 | :port 3449 46 | :css-dirs ["resources/public/css"]} 47 | 48 | :env {:is-dev true} 49 | 50 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 51 | 52 | :uberjar {:hooks [leiningen.cljsbuild] 53 | :env {:production true} 54 | :omit-source true 55 | :aot :all 56 | :cljsbuild {:builds {:app 57 | {:source-paths ["env/prod/cljs"] 58 | :compiler 59 | {:optimizations :advanced 60 | :pretty-print false}}}}}}) 61 | -------------------------------------------------------------------------------- /recipes/input-validation/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/input-validation/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/input-validation/src/clj/input_validation/dev.clj: -------------------------------------------------------------------------------- 1 | (ns input-validation.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('input_validation.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/input-validation/src/clj/input_validation/server.clj: -------------------------------------------------------------------------------- 1 | (ns input-validation.server 2 | (:require [clojure.java.io :as io] 3 | [input-validation.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/input-validation/src/cljs/input_validation/core.cljs: -------------------------------------------------------------------------------- 1 | (ns input-validation.core 2 | (:require [om.core :as om :include-macros true] 3 | [om-tools.dom :as dom :include-macros true] 4 | [om-tools.core :refer-macros [defcomponent]])) 5 | 6 | (defonce app-state (atom {:user-input "some value"})) 7 | 8 | (defcomponent user-input-view [app owner] 9 | (render 10 | [_] 11 | (let [ref "user-input" 12 | k :user-input] 13 | (dom/input {:ref ref 14 | :value (k app) 15 | :on-change (fn [_] 16 | (let [this (om/get-node owner ref)] 17 | (om/update! app [k] (.-value this))))})))) 18 | 19 | 20 | (defn valid-input? [x] 21 | (> 10 (count x))) 22 | 23 | (defcomponent validation-box-view [app _] 24 | (render 25 | [_] 26 | (let [valid-color "green" 27 | invalid-color "red" 28 | color (if (valid-input? (:user-input app)) 29 | valid-color 30 | invalid-color)] 31 | (dom/div {:style {:padding "10px;" 32 | :background-color color}} 33 | (om/build user-input-view app))))) 34 | 35 | (defn main [] 36 | (om/root 37 | validation-box-view 38 | app-state 39 | {:target (. js/document (getElementById "app"))})) 40 | -------------------------------------------------------------------------------- /recipes/input-validation/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/kioo-blog/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/kioo-blog/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/kioo-blog.jar clojure.main -m kioo-blog.server 2 | -------------------------------------------------------------------------------- /recipes/kioo-blog/README.md: -------------------------------------------------------------------------------- 1 | # Om with Kioo Templating 2 | 3 | Kioo brings Enlive/Enfocus style templates to React/Om. Instead of building the dom with om/om-tools/sablono, you accept a static html and replace its dom with css-like selectors. Kioo snippets and templates are components that return React/om components, and as such, you'll be working with kioo's api to 'transform' the dom. 4 | 5 | For further info on a list of transformations, checkout [kioo](https://github.com/ckirkendall/kioo). 6 | 7 | 1) Create a new Om project using Chestnut 8 | 9 | ```bash 10 | lein new chestnut kioo-blog 11 | ``` 12 | 13 | 2) Use your editor of choice to open the file `kioo-blog/src/cljs/core.cljs` 14 | 15 | 3) Add kioo to your project file 16 | 17 | ```clojure 18 | [kioo "0.4.0" :exclusions [com.facebook/react 19 | om]] 20 | ``` 21 | 22 | 4) Add Kioo to your project requirements 23 | 24 | ```clojure 25 | (ns kioo-blog.core 26 | (:require [kioo.om :refer [set-style set-attr do-> substitute listen] :as kio] 27 | [kioo.core :refer [handle-wrapper]] 28 | [om.core :as om :include-macros true] 29 | [om.dom :as dom :include-macros true]) 30 | (:require-macros [kioo.om :refer [defsnippet deftemplate]])) 31 | ``` 32 | 33 | 6) Add an initial app-state. 34 | 35 | ```clojure 36 | (def app-state (atom {:heading "My programming blog" 37 | :contents [[:100 "Are We There Yet?"] 38 | [:200 "Simple Made Easy."] 39 | [:300 "The Value of Values"]] 40 | :navigation [["home" #(.log js/console %)] 41 | ["blog" #(.log js/console %)]] 42 | :footer "Eclipse Public License (c)"})) 43 | ``` 44 | 45 | 7) Initialize a template and replace header/footer 46 | 47 | Study `resources/blog.html`. Let's replace menu and header. 48 | 49 | ```clojure 50 | 51 | (defsnippet menu-view "blog.html" [:.nav-item] 52 | [[caption func]] 53 | {[:a] (do-> (kio/content caption) 54 | (listen :onClick #(func caption)))}) 55 | 56 | (defsnippet header-view "blog.html" [:header] 57 | [{:keys [heading navigation]}] 58 | {[:h1] (kio/content heading) 59 | [:ul] (kio/content (map menu-view navigation))}) 60 | 61 | (deftemplate blog-tmpl "blog.html" 62 | [data] 63 | {[:header] (substitute (header-view data))}) 64 | 65 | ``` 66 | 67 | What just happened: `header-snippet` read the header tag of `blog.html`. After that, it transformed inidividual selectors, h1, ul, menu with content passed in (from the app-state cursor). Note how transformations can call other snippets, like `menu-view`. 68 | 69 | 8) Continue transforming content. 70 | 71 | ```clojure 72 | (defsnippet content-view "blog.html" [:section#blog] 73 | [content] 74 | {[:article.content] (kio/content (second content))}) 75 | 76 | (defsnippet footer-view "blog.html" [:footer] 77 | [{:keys [footer]}] 78 | {[:h4] (kio/content footer)}) 79 | 80 | (deftemplate blog-tmpl "blog.html" 81 | [data] 82 | {[:header] (substitute (header-view data)) 83 | [:section#blog] (substitute (map content-view (:contents data))) 84 | [:footer] (substitute (footer-view data))}) 85 | ``` 86 | 87 | 9) Start a REPL with `lein repl` 88 | 89 | Call `run` to start the back end and compile your ClojureScript. Point your browser to http://localhost:port. 90 | 91 | You can find the port in the REPL message output. 92 | 93 | 94 | -------------------------------------------------------------------------------- /recipes/kioo-blog/env/dev/cljs/kioo_blog/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns kioo-blog.dev 2 | (:require [kioo-blog.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/kioo-blog/env/prod/cljs/kioo_blog/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns kioo-blog.prod 2 | (:require [kioo-blog.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/kioo-blog/project.clj: -------------------------------------------------------------------------------- 1 | (defproject kioo-blog "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [kioo "0.4.0" :exclusions [com.facebook/react 16 | om]] 17 | [figwheel "0.1.4-SNAPSHOT"] 18 | [environ "1.0.0"] 19 | [com.cemerick/piggieback "0.1.3"] 20 | [weasel "0.4.0-SNAPSHOT"] 21 | [leiningen "2.5.0"]] 22 | 23 | :plugins [[lein-cljsbuild "1.0.3"] 24 | [lein-environ "1.0.0"]] 25 | 26 | :min-lein-version "2.5.0" 27 | 28 | :uberjar-name "kioo-blog.jar" 29 | 30 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 31 | :compiler {:output-to "resources/public/js/app.js" 32 | :output-dir "resources/public/js/out" 33 | :source-map "resources/public/js/out.js.map" 34 | :preamble ["react/react.min.js"] 35 | :externs ["react/externs/react.js"] 36 | :optimizations :none 37 | :pretty-print true}}}} 38 | 39 | :profiles {:dev {:repl-options {:init-ns kioo-blog.server 40 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 41 | 42 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 43 | 44 | :figwheel {:http-server-root "public" 45 | :port 3449 46 | :css-dirs ["resources/public/css"]} 47 | 48 | :env {:is-dev true} 49 | 50 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 51 | 52 | :uberjar {:hooks [leiningen.cljsbuild] 53 | :env {:production true} 54 | :omit-source true 55 | :aot :all 56 | :cljsbuild {:builds {:app 57 | {:source-paths ["env/prod/cljs"] 58 | :compiler 59 | {:optimizations :advanced 60 | :pretty-print false}}}}}}) 61 | -------------------------------------------------------------------------------- /recipes/kioo-blog/resources/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Header

6 | 9 |
10 |
11 |
12 |

content 1

13 |
14 |
15 |
16 |

footer

17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /recipes/kioo-blog/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /recipes/kioo-blog/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/kioo-blog/src/clj/kioo_blog/dev.clj: -------------------------------------------------------------------------------- 1 | (ns kioo-blog.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('kioo_blog.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/kioo-blog/src/clj/kioo_blog/server.clj: -------------------------------------------------------------------------------- 1 | (ns kioo-blog.server 2 | (:require [clojure.java.io :as io] 3 | [kioo-blog.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [ring.adapter.jetty :refer [run-jetty]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-jetty http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/kioo-blog/src/cljs/kioo_blog/core.cljs: -------------------------------------------------------------------------------- 1 | (ns kioo-blog.core 2 | (:require [kioo.om :refer [set-style set-attr do-> substitute listen] :as kio] 3 | [kioo.core :refer [handle-wrapper]] 4 | [om.core :as om :include-macros true] 5 | [om.dom :as dom :include-macros true]) 6 | (:require-macros [kioo.om :refer [defsnippet deftemplate]])) 7 | 8 | (def app-state (atom {:heading "My programming blog" 9 | :contents [[:100 "Are We There Yet?"] 10 | [:200 "Simple Made Easy."] 11 | [:300 "The Value of Values"]] 12 | :navigation [["home" #(.log js/console %)] 13 | ["blog" #(.log js/console %)]] 14 | :footer "Eclipse Public License (c)"})) 15 | 16 | (defsnippet menu-view "blog.html" [:.nav-item] 17 | [[caption func]] 18 | {[:a] (do-> (kio/content caption) 19 | (listen :onClick #(func caption)))}) 20 | 21 | (defsnippet header-view "blog.html" [:header] 22 | [{:keys [heading navigation]}] 23 | {[:h1] (kio/content heading) 24 | [:ul] (kio/content (map menu-view navigation))}) 25 | 26 | (defsnippet content-view "blog.html" [:section#blog] 27 | [content] 28 | {[:article.content] (kio/content (second content))}) 29 | 30 | (defsnippet footer-view "blog.html" [:footer] 31 | [{:keys [footer]}] 32 | {[:h4] (kio/content footer)}) 33 | 34 | (deftemplate blog-tmpl "blog.html" 35 | [data] 36 | {[:header] (substitute (header-view data)) 37 | [:section#blog] (substitute (map content-view (:contents data))) 38 | [:footer] (substitute (footer-view data))}) 39 | 40 | (defn template [data] (om/component (blog-tmpl data))) 41 | 42 | (defn main [] 43 | (om/root 44 | template 45 | app-state 46 | {:target (.-body js/document)})) 47 | -------------------------------------------------------------------------------- /recipes/kioo-blog/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/knob/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/knob/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/knob.jar clojure.main -m knob.server 2 | -------------------------------------------------------------------------------- /recipes/knob/env/dev/cljs/knob/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns knob.dev 2 | (:require [knob.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/knob/env/prod/cljs/knob/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns knob.prod 2 | (:require [knob.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/knob/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/knob/preview.gif -------------------------------------------------------------------------------- /recipes/knob/project.clj: -------------------------------------------------------------------------------- 1 | (defproject knob "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 12 | [ring "1.3.1"] 13 | [compojure "1.2.0"] 14 | [enlive "1.1.5"] 15 | [om "0.7.3"] 16 | [figwheel "0.1.4-SNAPSHOT"] 17 | [environ "1.0.0"] 18 | [com.cemerick/piggieback "0.1.3"] 19 | [weasel "0.4.0-SNAPSHOT"] 20 | [leiningen "2.5.0"] 21 | [http-kit "2.1.19"] 22 | [prismatic/om-tools "0.3.3" :exclusions [org.clojure/clojure]]] 23 | 24 | :plugins [[lein-cljsbuild "1.0.3"] 25 | [lein-environ "1.0.0"]] 26 | 27 | :min-lein-version "2.5.0" 28 | 29 | :uberjar-name "knob.jar" 30 | 31 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 32 | :compiler {:output-to "resources/public/js/app.js" 33 | :output-dir "resources/public/js/out" 34 | :source-map "resources/public/js/out.js.map" 35 | :preamble ["react/react.min.js"] 36 | :externs ["react/externs/react.js"] 37 | :optimizations :none 38 | :pretty-print true}}}} 39 | 40 | :profiles {:dev {:repl-options {:init-ns knob.server 41 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 42 | 43 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 44 | 45 | :figwheel {:http-server-root "public" 46 | :port 3449 47 | :css-dirs ["resources/public/css"]} 48 | 49 | :env {:is-dev true} 50 | 51 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 52 | 53 | :uberjar {:hooks [leiningen.cljsbuild] 54 | :env {:production true} 55 | :omit-source true 56 | :aot :all 57 | :cljsbuild {:builds {:app 58 | {:source-paths ["env/prod/cljs"] 59 | :compiler 60 | {:optimizations :advanced 61 | :pretty-print false}}}}}}) 62 | -------------------------------------------------------------------------------- /recipes/knob/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/knob/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/knob/src/clj/knob/dev.clj: -------------------------------------------------------------------------------- 1 | (ns knob.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('knob.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/knob/src/clj/knob/server.clj: -------------------------------------------------------------------------------- 1 | (ns knob.server 2 | (:require [clojure.java.io :as io] 3 | [knob.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/knob/src/cljs/knob/core.cljs: -------------------------------------------------------------------------------- 1 | (ns knob.core 2 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]) 3 | (:require [om.core :as om :include-macros true] 4 | [om-tools.dom :as dom :include-macros true] 5 | [om-tools.core :refer-macros [defcomponent]])) 6 | 7 | (defonce app-state 8 | (atom 9 | {:panel {:bg-color "#22313F" 10 | :font-color "white"} 11 | :knobs [{:id "a" :deg 0 :size 10 :color "#34495E" :border "#22A7F0"} 12 | {:id "b" :deg 80 :size 20 :color "#22313F" :border "#22A7F0"} 13 | {:id "c" :deg 180 :size 30 :color "#4B77BE" :border "#22A7F0"} 14 | {:id "d" :deg 350 :size 40 :color "#446CB3" :border "#22A7F0"}]})) 15 | 16 | (defn quadrant [y x rect] 17 | (let [half #(-> % (* 0.5) int) 18 | top (.-top rect) 19 | bottom (.-bottom rect) 20 | left (.-left rect) 21 | right (.-right rect) 22 | x-half (half (- right left)) 23 | x (- x left) 24 | y-half (half (- bottom top)) 25 | y (- y top) 26 | y-pos (if (<= y y-half) :up :down) 27 | x-pos (if (>= x x-half) :right :left)] 28 | [y-pos x-pos])) 29 | 30 | (defn direction 31 | [cursor y quadrant] 32 | (let [state @cursor 33 | {:keys [last-quadrant last-y]} state 34 | up? (< y last-y) 35 | direction [up? last-quadrant quadrant]] 36 | 37 | (condp = direction 38 | 39 | ;; increase top left to right 40 | [true [:up :left] [:up :left]] inc 41 | [false [:up :left] [:up :right]] inc 42 | [true [:up :left] [:up :right]] inc 43 | [false [:up :right] [:up :right]] inc 44 | [false [:up :right] [:down :right]] inc 45 | 46 | ;; increase bottom right to left 47 | [false [:down :right] [:down :right]] inc 48 | [false [:down :right] [:down :left]] inc 49 | [true [:down :left] [:down :left]] inc 50 | [false [:down :left] [:down :left]] dec 51 | 52 | ;; decrease bottom left to right 53 | [false [:down :left] [:down :right]] dec 54 | [true [:down :right] [:down :right]] dec 55 | [true [:down :right] [:up :right]] dec 56 | 57 | ;; decrease up right to left 58 | [true [:up :right] [:up :right]] dec 59 | [true [:up :right] [:up :left]] dec 60 | [false [:up :left] [:up :left]] dec 61 | 62 | identity))) 63 | 64 | (defn mouse-move! [cursor evt] 65 | (go-loop [state @cursor] 66 | (when (:mouse-down? state) 67 | (let [dk :deg 68 | yk :last-y 69 | lqk :last-quadrant 70 | rect (-> js/document 71 | (.getElementById (:id state)) 72 | .getBoundingClientRect) 73 | y (.-clientY evt) 74 | x (.-clientX evt) 75 | q (quadrant y x rect) 76 | f (direction cursor y q)] 77 | (om/transact! cursor 78 | (fn [state] 79 | (-> state 80 | (update-in [dk] 81 | (fn [deg] 82 | (if (and (<= deg 350) 83 | (>= deg 0)) 84 | (f deg)))) 85 | (assoc-in [lqk] q) 86 | (assoc-in [yk] y)))))))) 87 | 88 | (defn mouse-down! [cursor evt] 89 | (go 90 | (om/update! cursor [:mouse-down?] (-> evt .-button zero?)))) 91 | 92 | (defn mouse-up! [cursor evt] 93 | (go 94 | (om/update! cursor [:mouse-down?] false))) 95 | 96 | (defcomponent knob-view [cursor _] 97 | (did-mount 98 | [_] 99 | (let [node (-> js/document 100 | (.getElementById (:id cursor)))] 101 | (-> node .-onmousemove (set! #(mouse-move! cursor %))) 102 | (-> node .-onmousedown (set! #(mouse-down! cursor %))) 103 | (-> node .-onmouseup (set! #(mouse-up! cursor %))))) 104 | (render 105 | [_] 106 | (let [{:keys [id deg size color border]} cursor 107 | rotate (str "rotate(" deg "deg)") 108 | width (* 1.5 size) 109 | height width 110 | padding (* size 0.30) 111 | stick-width (* width 0.35) 112 | stick-margin-top (* padding 2) 113 | stick-margin-left (str "-" (* padding 0.5)) 114 | percent (-> (/ deg 350) (* 100) int)] 115 | (dom/div 116 | (dom/h2 (str percent "%")) 117 | (dom/div {:id id 118 | :style {:margin "20px" 119 | :border-color border 120 | :border-style "solid" 121 | :border-width "5px" 122 | :padding padding 123 | :border-radius "50%" 124 | :transform rotate 125 | :width width 126 | :height height 127 | :background-color color}} 128 | (dom/div {:style {:margin-top stick-margin-top 129 | :margin-left stick-margin-left 130 | :padding-right stick-width 131 | :padding-left stick-width 132 | :height "15%" 133 | :border-radius "35%" 134 | :background-color "white" 135 | :border-color "black" 136 | :border-style "solid" 137 | :border-width "1px" 138 | :float "left"}})))))) 139 | 140 | (defcomponent main-view [app owner] 141 | (render 142 | [_] 143 | (dom/div {:style {:background-color (-> app :panel :bg-color) 144 | :color (-> app :panel :font-color) 145 | :padding "10px"}} 146 | (om/build-all knob-view (:knobs app))))) 147 | 148 | (defn main [] 149 | (om/root 150 | main-view 151 | app-state 152 | {:target (. js/document (getElementById "app"))})) 153 | -------------------------------------------------------------------------------- /recipes/knob/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/local-state/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/local-state/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/local-state.jar clojure.main -m local-state.server 2 | -------------------------------------------------------------------------------- /recipes/local-state/README.md: -------------------------------------------------------------------------------- 1 | # Local State 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut local-state -- --om-tools --http-kit 9 | ``` 10 | 11 | 2) Use your editor of choice to open the file `local-state/src/cljs/core.cljs` 12 | 13 | 3) You should remove `(defonce app-state ...)` because it's not needed for this example. 14 | 15 | 4) Create an Om component with initial state 16 | 17 | ```clojure 18 | (defcomponent local-state-counter-view [_ owner] 19 | (init-state [_]) 20 | (render-state [_ state])) 21 | ``` 22 | 23 | 5) Set your initial state values 24 | 25 | ```clojure 26 | (defcomponent local-state-counter-view [_ owner] 27 | (init-state 28 | [_] 29 | {:button-presses 1}) 30 | (render-state [_ state])) 31 | ``` 32 | 33 | 6) Create a render state so the initial state can be displayed 34 | 35 | ```clojure 36 | (defcomponent local-state-counter-view [_ owner] 37 | (init-state 38 | [_] 39 | {:button-presses 1}) 40 | (render-state 41 | [_ state] 42 | (dom/div 43 | (dom/button {} "Click Me") 44 | (dom/br {}) 45 | (str "Button Presses: " 46 | (:button-presses state))))) 47 | 48 | ``` 49 | 50 | 7) Add an onClick JavaScript event to the "Click Me" button. This event should update the local state using `om/update-state!`. 51 | 52 | ```clojure 53 | (defcomponent local-state-counter-view [_ owner] 54 | (init-state 55 | [_] 56 | {:button-presses 1}) 57 | (render-state 58 | [_ state] 59 | (dom/div 60 | (dom/button 61 | {:on-click (fn [_] 62 | (om/update-state! owner 63 | [:button-presses] 64 | inc))} 65 | 66 | "Click Me") 67 | (dom/br {}) 68 | (str "Button Presses: " 69 | (:button-presses state))))) 70 | 71 | ``` 72 | 73 | 8) Alter or replace the `(defn main [] ...)` function so that the local state component is displayed on the webpage. 74 | 75 | ```clojure 76 | (defn main [] 77 | (om/root 78 | local-state-counter-view 79 | {} 80 | {:target (. js/document (getElementById "app"))})) 81 | ``` 82 | 83 | 9) Start a REPL with `lein repl` 84 | 85 | ``` 86 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 87 | REPL-y 0.3.5, nREPL 0.2.6 88 | Clojure 1.6.0 89 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 90 | Docs: (doc function-name-here) 91 | (find-doc "part-of-name-here") 92 | Source: (source function-name-here) 93 | Javadoc: (javadoc java-object-or-class-here) 94 | Exit: Control+D or (exit) or (quit) 95 | Results: Stored in vars *1, *2, *3, an exception in *e 96 | ``` 97 | 98 | 10) Call `run` to start the back end and compile your ClojureScript. 99 | 100 | ``` 101 | local-state.server=> (run) 102 | Starting figwheel. 103 | Starting web server on port 10555 . 104 | # 105 | local-state.server=> Compiling ClojureScript. 106 | Figwheel: Starting server at http://localhost:3449 107 | Figwheel: Serving files from '(dev-resources|resources)/public' 108 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 109 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 110 | notifying browser that file changed: /js/out/local_state/core.js 111 | ``` 112 | 113 | 11) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 114 | -------------------------------------------------------------------------------- /recipes/local-state/env/dev/cljs/local_state/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns local-state.dev 2 | (:require [local-state.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/local-state/env/prod/cljs/local_state/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns local-state.prod 2 | (:require [local-state.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/local-state/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/local-state/preview.gif -------------------------------------------------------------------------------- /recipes/local-state/project.clj: -------------------------------------------------------------------------------- 1 | (defproject local-state "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"] 20 | [http-kit "2.1.19"] 21 | [prismatic/om-tools "0.3.3" :exclusions [org.clojure/clojure]]] 22 | 23 | :plugins [[lein-cljsbuild "1.0.3"] 24 | [lein-environ "1.0.0"]] 25 | 26 | :min-lein-version "2.5.0" 27 | 28 | :uberjar-name "local-state.jar" 29 | 30 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 31 | :compiler {:output-to "resources/public/js/app.js" 32 | :output-dir "resources/public/js/out" 33 | :source-map "resources/public/js/out.js.map" 34 | :preamble ["react/react.min.js"] 35 | :externs ["react/externs/react.js"] 36 | :optimizations :none 37 | :pretty-print true}}}} 38 | 39 | :profiles {:dev {:repl-options {:init-ns local-state.server 40 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 41 | 42 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 43 | 44 | :figwheel {:http-server-root "public" 45 | :port 3449 46 | :css-dirs ["resources/public/css"]} 47 | 48 | :env {:is-dev true} 49 | 50 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 51 | 52 | :uberjar {:hooks [leiningen.cljsbuild] 53 | :env {:production true} 54 | :omit-source true 55 | :aot :all 56 | :cljsbuild {:builds {:app 57 | {:source-paths ["env/prod/cljs"] 58 | :compiler 59 | {:optimizations :advanced 60 | :pretty-print false}}}}}}) 61 | -------------------------------------------------------------------------------- /recipes/local-state/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/local-state/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/local-state/src/clj/local_state/dev.clj: -------------------------------------------------------------------------------- 1 | (ns local-state.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('local_state.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/local-state/src/clj/local_state/server.clj: -------------------------------------------------------------------------------- 1 | (ns local-state.server 2 | (:require [clojure.java.io :as io] 3 | [local-state.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/local-state/src/cljs/local_state/core.cljs: -------------------------------------------------------------------------------- 1 | (ns local-state.core 2 | (:require [om.core :as om :include-macros true] 3 | [om-tools.dom :as dom :include-macros true] 4 | [om-tools.core :refer-macros [defcomponent]])) 5 | 6 | (defcomponent local-state-counter-view [_ owner] 7 | (init-state 8 | [_] 9 | {:button-presses 1}) 10 | (render-state 11 | [_ state] 12 | (dom/div 13 | (dom/button 14 | {:on-click (fn [_] 15 | (om/update-state! owner 16 | [:button-presses] 17 | inc))} 18 | 19 | "Click Me") 20 | (dom/br {}) 21 | (str "Button Presses: " 22 | (:button-presses state))))) 23 | 24 | (defn main [] 25 | (om/root 26 | local-state-counter-view 27 | {} 28 | {:target (. js/document (getElementById "app"))})) 29 | -------------------------------------------------------------------------------- /recipes/local-state/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/routing-with-secretary.jar clojure.main -m routing-with-secretary.server 2 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/README.md: -------------------------------------------------------------------------------- 1 | # Single Page Routing With Secretary 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut routing-with-secretary -- --om-tools --http-kit 9 | ``` 10 | 11 | 2) Use your editor of choice to open the file `routing-with-secretary/src/cljs/core.cljs` 12 | 13 | 3) Add Secretary to your project file 14 | 15 | ```clojure 16 | [secretary "1.2.1"] 17 | ``` 18 | 19 | 4) Add Secretary to your project requirements 20 | 21 | ```clojure 22 | (ns routing-with-secretary.core 23 | (:require [om.core :as om :include-macros true] 24 | [om-tools.dom :as dom :include-macros true] 25 | [om-tools.core :refer-macros [defcomponent]] 26 | [secretary.core :as sec 27 | :include-macros true])) 28 | ``` 29 | 30 | 5) Add Google Closure History related requirements and imports. 31 | 32 | ```clojure 33 | (ns routing-with-secretary.core 34 | (:require [om.core :as om :include-macros true] 35 | [om-tools.dom :as dom :include-macros true] 36 | [om-tools.core :refer-macros [defcomponent]] 37 | [secretary.core :as sec 38 | :include-macros true] 39 | [goog.events :as events] 40 | [goog.history.EventType :as EventType]) 41 | (:import goog.History)) 42 | ``` 43 | 6) Configure Secretary to have a fallback for browsers that don't support HTML5 history. 44 | 45 | ```clojure 46 | (sec/set-config! :prefix "#") 47 | ``` 48 | 49 | 7) Enable HTML5 history support. 50 | 51 | ```clojure 52 | (let [history (History.) 53 | navigation EventType/NAVIGATE] 54 | (goog.events/listen history 55 | navigation 56 | #(-> % .-token sec/dispatch!)) 57 | (doto history (.setEnabled true))) 58 | ``` 59 | 60 | 8) Create a navigation component. 61 | 62 | ```clojure 63 | (defcomponent navigation-view [_ _] 64 | (render 65 | [_] 66 | (let [style {:style {:margin "10px;"}}] 67 | (dom/div style 68 | (dom/a (assoc style :href "#/") 69 | "Home") 70 | (dom/a (assoc style :href "#/something") 71 | "Something") 72 | (dom/a (assoc style :href "#/about") 73 | "About"))))) 74 | ``` 75 | 76 | 9) Create a view component and a Secretary route for the index page. 77 | 78 | ```clojure 79 | (defcomponent index-page-view [_ _] 80 | (render 81 | [_] 82 | (dom/div 83 | (om/build navigation-view {}) 84 | (dom/h1 "Index Page")))) 85 | 86 | (sec/defroute index-page "/" [] 87 | (om/root index-page-view 88 | {} 89 | {:target (. js/document (getElementById "app"))})) 90 | ``` 91 | 92 | 10) Create a view component and a Secretary route for the something page. 93 | 94 | ```clojure 95 | (defcomponent something-page-view [_ _] 96 | (render 97 | [_] 98 | (dom/div 99 | (om/build navigation-view {}) 100 | (dom/h1 "Something Page")))) 101 | 102 | (sec/defroute something-page "/something" [] 103 | (om/root something-page-view 104 | {} 105 | {:target (. js/document (getElementById "app"))})) 106 | ``` 107 | 11) Create a view component and a Secretary route for the about page. 108 | 109 | ```clojure 110 | (defcomponent about-page-view [_ _] 111 | (render 112 | [_] 113 | (dom/div 114 | (om/build navigation-view {}) 115 | (dom/h1 "About Page")))) 116 | 117 | (sec/defroute about-page "/about" [] 118 | (om/root about-page-view 119 | {} 120 | {:target (. js/document (getElementById "app"))})) 121 | ``` 122 | 123 | 12) Alter or replace your main function to display the index page view when the page is loaded. 124 | 125 | ```clojure 126 | (defn main [] 127 | (-> js/document 128 | .-location 129 | (set! "#/"))) 130 | ``` 131 | 13) Start a REPL with `lein repl` 132 | 133 | ``` 134 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 135 | REPL-y 0.3.5, nREPL 0.2.6 136 | Clojure 1.6.0 137 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 138 | Docs: (doc function-name-here) 139 | (find-doc "part-of-name-here") 140 | Source: (source function-name-here) 141 | Javadoc: (javadoc java-object-or-class-here) 142 | Exit: Control+D or (exit) or (quit) 143 | Results: Stored in vars *1, *2, *3, an exception in *e 144 | ``` 145 | 146 | 14) Call `run` to start the back end and compile your ClojureScript. 147 | 148 | ``` 149 | routing-with-secretary.server=> (run) 150 | Starting figwheel. 151 | Starting web server on port 10555 . 152 | # 153 | routing-with-secretary.server=> Compiling ClojureScript. 154 | Figwheel: Starting server at http://localhost:3449 155 | Figwheel: Serving files from '(dev-resources|resources)/public' 156 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 157 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 158 | notifying browser that file changed: /js/out/local_state/core.js 159 | ``` 160 | 161 | 15) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 162 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/env/dev/cljs/routing_with_secretary/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns routing-with-secretary.dev 2 | (:require [routing-with-secretary.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/env/prod/cljs/routing_with_secretary/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns routing-with-secretary.prod 2 | (:require [routing-with-secretary.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/routing-with-secretary/preview.gif -------------------------------------------------------------------------------- /recipes/routing-with-secretary/project.clj: -------------------------------------------------------------------------------- 1 | (defproject routing-with-secretary "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"] 20 | [http-kit "2.1.19"] 21 | [secretary "1.2.1"] 22 | [prismatic/om-tools "0.3.3" :exclusions [org.clojure/clojure]]] 23 | 24 | :plugins [[lein-cljsbuild "1.0.3"] 25 | [lein-environ "1.0.0"]] 26 | 27 | :min-lein-version "2.5.0" 28 | 29 | :uberjar-name "routing-with-secretary.jar" 30 | 31 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 32 | :compiler {:output-to "resources/public/js/app.js" 33 | :output-dir "resources/public/js/out" 34 | :source-map "resources/public/js/out.js.map" 35 | :preamble ["react/react.min.js"] 36 | :externs ["react/externs/react.js"] 37 | :optimizations :none 38 | :pretty-print true}}}} 39 | 40 | :profiles {:dev {:repl-options {:init-ns routing-with-secretary.server 41 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 42 | 43 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 44 | 45 | :figwheel {:http-server-root "public" 46 | :port 3449 47 | :css-dirs ["resources/public/css"]} 48 | 49 | :env {:is-dev true} 50 | 51 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 52 | 53 | :uberjar {:hooks [leiningen.cljsbuild] 54 | :env {:production true} 55 | :omit-source true 56 | :aot :all 57 | :cljsbuild {:builds {:app 58 | {:source-paths ["env/prod/cljs"] 59 | :compiler 60 | {:optimizations :advanced 61 | :pretty-print false}}}}}}) 62 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/src/clj/routing_with_secretary/dev.clj: -------------------------------------------------------------------------------- 1 | (ns routing-with-secretary.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('routing_with_secretary.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/src/clj/routing_with_secretary/server.clj: -------------------------------------------------------------------------------- 1 | (ns routing-with-secretary.server 2 | (:require [clojure.java.io :as io] 3 | [routing-with-secretary.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/src/cljs/routing_with_secretary/core.cljs: -------------------------------------------------------------------------------- 1 | (ns routing-with-secretary.core 2 | (:require [om.core :as om :include-macros true] 3 | [om-tools.dom :as dom :include-macros true] 4 | [om-tools.core :refer-macros [defcomponent]] 5 | [secretary.core :as sec 6 | :include-macros true] 7 | [goog.events :as events] 8 | [goog.history.EventType :as EventType]) 9 | (:import goog.History)) 10 | 11 | (sec/set-config! :prefix "#") 12 | 13 | (let [history (History.) 14 | navigation EventType/NAVIGATE] 15 | (goog.events/listen history 16 | navigation 17 | #(-> % .-token sec/dispatch!)) 18 | (doto history (.setEnabled true))) 19 | 20 | (defcomponent navigation-view [_ _] 21 | (render 22 | [_] 23 | (let [style {:style {:margin "10px;"}}] 24 | (dom/div style 25 | (dom/a (assoc style :href "#/") 26 | "Home") 27 | (dom/a (assoc style :href "#/something") 28 | "Something") 29 | (dom/a (assoc style :href "#/about") 30 | "About"))))) 31 | 32 | (defcomponent index-page-view [_ _] 33 | (render 34 | [_] 35 | (dom/div 36 | (om/build navigation-view {}) 37 | (dom/h1 "Index Page")))) 38 | 39 | (sec/defroute index-page "/" [] 40 | (om/root index-page-view 41 | {} 42 | {:target (. js/document (getElementById "app"))})) 43 | 44 | (defcomponent something-page-view [_ _] 45 | (render 46 | [_] 47 | (dom/div 48 | (om/build navigation-view {}) 49 | (dom/h1 "Something Page")))) 50 | 51 | (sec/defroute something-page "/something" [] 52 | (om/root something-page-view 53 | {} 54 | {:target (. js/document (getElementById "app"))})) 55 | 56 | (defcomponent about-page-view [_ _] 57 | (render 58 | [_] 59 | (dom/div 60 | (om/build navigation-view {}) 61 | (dom/h1 "About Page")))) 62 | 63 | (sec/defroute about-page "/about" [] 64 | (om/root about-page-view 65 | {} 66 | {:target (. js/document (getElementById "app"))})) 67 | 68 | (defn main [] 69 | (-> js/document 70 | .-location 71 | (set! "#/"))) 72 | -------------------------------------------------------------------------------- /recipes/routing-with-secretary/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/scrollbar/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/scrollbar/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/scrollbar.jar clojure.main -m scrollbar.server 2 | -------------------------------------------------------------------------------- /recipes/scrollbar/README.md: -------------------------------------------------------------------------------- 1 | # Custom Scrolling Bar 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut scrollbar -- --om-tools --http-kit 9 | ``` 10 | 11 | 2) Use your editor of choice to open the file `scrollbar/src/cljs/core.cljs` 12 | 13 | 14 | 3) Your initial app-state should look like this. 15 | 16 | ```clojure 17 | (ns scrollbar.core 18 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]) 19 | (:require [om.core :as om :include-macros true] 20 | [om-tools.dom :as dom :include-macros true] 21 | [om-tools.core :refer-macros [defcomponent]])) 22 | 23 | 24 | (def app-state 25 | (atom {:scrollbars []})) 26 | ``` 27 | 4) Create a function that generates a scrollbar. 28 | 29 | ```clojure 30 | (defn add-sample-scrollbar! [cursor] 31 | (let [id (gensym) 32 | sb {:scroller-id (str "scroller" id) 33 | :slider-track-id (str "slider-track" id) 34 | :scrollbar-id (str "scrollbar" id) 35 | :scrollbar-height (rand-nth (range 40 200)) 36 | :scrollbar 100 37 | :scrolling false}] 38 | (om/transact! cursor 39 | [:scrollbars] 40 | (fn [coll] 41 | (conj coll sb))))) 42 | ``` 43 | 5) Create mouse up/down event handlers 44 | 45 | ```clojure 46 | (defn scrollbar-mouse-down! [cursor] 47 | (go 48 | (om/update! cursor [:scrolling] true))) 49 | 50 | (defn scrollbar-mouse-up! [cursor] 51 | (go 52 | (om/update! cursor [:scrolling] false))) 53 | ``` 54 | 55 | 6) Create a function to change the scroll percent. 56 | 57 | ```clojure 58 | (defn scrollbar-percent! [cursor percent] 59 | (om/update! cursor [:scrollbar] percent)) 60 | ``` 61 | 62 | 7) The mouse move event handler should update the scroller percent value and positon when `(:scrolling state)`. 63 | 64 | ```clojure 65 | (defn scrollbar-mouse-move! [evt cursor] 66 | (go-loop [state @cursor 67 | scroller-node (-> js/document 68 | (.getElementById (:scroller-id state))) 69 | slider-track (-> js/document 70 | (.getElementById (:slider-track-id state))) 71 | y (- (.-clientY evt) 8) 72 | yp (str y "px") 73 | true-offset (-> slider-track .getBoundingClientRect .-top) 74 | true-y (- y true-offset) 75 | full (:scrollbar-height state) 76 | percent (->> (/ true-y full) 77 | (* 100) 78 | (- 100) 79 | int)] 80 | (when (and (:scrolling state) 81 | (>= 100 percent) 82 | (>= percent 0)) 83 | (-> scroller-node 84 | .-style.top 85 | (set! yp)) 86 | (scrollbar-percent! cursor percent)))) 87 | ``` 88 | 89 | 8) Add the event handlers to the scroll bar view 90 | 91 | ```clojure 92 | (defcomponent scrollbar-view [cursor _] 93 | (did-mount 94 | [_] 95 | (let [scroller-node (-> js/document 96 | (.getElementById (:scroller-id cursor))) 97 | scrollbar-node (-> js/document 98 | (.getElementById (:scrollbar-id cursor)))] 99 | ;; set scrolling 100 | (doseq [node [scroller-node 101 | scrollbar-node]] 102 | (-> node 103 | .-onmousedown 104 | (set! (fn [_] (scrollbar-mouse-down! cursor))))) 105 | 106 | ;; set not scrolling 107 | (doseq [node [scroller-node 108 | scrollbar-node]] 109 | (-> node 110 | .-onmouseup 111 | (set! (fn [_] (scrollbar-mouse-up! cursor))))) 112 | 113 | ;; set movement listener 114 | (doseq [node [scroller-node 115 | scrollbar-node]] 116 | (-> node 117 | .-onmousemove 118 | (set! (fn [evt] (scrollbar-mouse-move! evt cursor))))))) 119 | (render 120 | [_] 121 | (let [height-px (str (:scrollbar-height cursor) "px")] 122 | (dom/span 123 | (dom/b (str (:scrollbar cursor) "%")) 124 | (dom/div {:id (:scrollbar-id cursor) 125 | :style {:width "20px" 126 | :height height-px 127 | :padding "8px 2px 8px 2px" 128 | :border-width "1px" 129 | :border-color "#000" 130 | :border-style "solid" 131 | :background-color "hsl(190,15%,90%)"}} 132 | (dom/div {:id (:slider-track-id cursor) 133 | :style {:float "left" 134 | :padding "1px" 135 | :height height-px 136 | :margin-left "9px" 137 | :background-color "#000"}}) 138 | (dom/div {:id (:scroller-id cursor) 139 | :style {:width "18px" 140 | :height "10px" 141 | :position "absolute" 142 | :background-color "hsl(195,100%,50%)" 143 | :border-width "1px" 144 | :border-style "solid" 145 | :border-color "#000"}})))))) 146 | ``` 147 | 148 | 9) The main view should generate 10 scrollbars with each being of a random height. 149 | 150 | ```clojure 151 | (defcomponent main-view [app owner] 152 | (will-mount 153 | [_] 154 | (dotimes [_ 10] 155 | (add-sample-scrollbar! app))) 156 | (render 157 | [_] 158 | (dom/table 159 | (dom/tr 160 | (for [sb (:scrollbars app)] 161 | (dom/td {:style {:padding 30}} 162 | (om/build scrollbar-view sb))))))) 163 | ``` 164 | 165 | 10) Replace or alter your main function to display the main view 166 | 167 | ```clojure 168 | (defn main [] 169 | (om/root 170 | main-view 171 | app-state 172 | {:target (. js/document (getElementById "app"))})) 173 | ``` 174 | 11) Start a REPL with `lein repl` 175 | 176 | ``` 177 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 178 | REPL-y 0.3.5, nREPL 0.2.6 179 | Clojure 1.6.0 180 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 181 | Docs: (doc function-name-here) 182 | (find-doc "part-of-name-here") 183 | Source: (source function-name-here) 184 | Javadoc: (javadoc java-object-or-class-here) 185 | Exit: Control+D or (exit) or (quit) 186 | Results: Stored in vars *1, *2, *3, an exception in *e 187 | ``` 188 | 189 | 12) Call `run` to start the back end and compile your ClojureScript. 190 | 191 | ``` 192 | scrollbar.server=> (run) 193 | Starting figwheel. 194 | Starting web server on port 10555 . 195 | # 196 | scrollbar.server=> Compiling ClojureScript. 197 | Figwheel: Starting server at http://localhost:3449 198 | Figwheel: Serving files from '(dev-resources|resources)/public' 199 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 200 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 201 | notifying browser that file changed: /js/out/local_state/core.js 202 | ``` 203 | 204 | 13) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 205 | -------------------------------------------------------------------------------- /recipes/scrollbar/env/dev/cljs/scrollbar/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns scrollbar.dev 2 | (:require [scrollbar.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/scrollbar/env/prod/cljs/scrollbar/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns scrollbar.prod 2 | (:require [scrollbar.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/scrollbar/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/scrollbar/preview.gif -------------------------------------------------------------------------------- /recipes/scrollbar/project.clj: -------------------------------------------------------------------------------- 1 | (defproject scrollbar "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 12 | [ring "1.3.1"] 13 | [compojure "1.2.0"] 14 | [enlive "1.1.5"] 15 | [om "0.7.3"] 16 | [figwheel "0.1.4-SNAPSHOT"] 17 | [environ "1.0.0"] 18 | [com.cemerick/piggieback "0.1.3"] 19 | [weasel "0.4.0-SNAPSHOT"] 20 | [leiningen "2.5.0"] 21 | [http-kit "2.1.19"] 22 | [prismatic/om-tools "0.3.3" 23 | :exclusions [org.clojure/clojure]]] 24 | 25 | :plugins [[lein-cljsbuild "1.0.3"] 26 | [lein-environ "1.0.0"]] 27 | 28 | :min-lein-version "2.5.0" 29 | 30 | :uberjar-name "scrollbar.jar" 31 | 32 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 33 | :compiler {:output-to "resources/public/js/app.js" 34 | :output-dir "resources/public/js/out" 35 | :source-map "resources/public/js/out.js.map" 36 | :preamble ["react/react.min.js"] 37 | :externs ["react/externs/react.js"] 38 | :optimizations :none 39 | :pretty-print true}}}} 40 | 41 | :profiles {:dev {:repl-options {:init-ns scrollbar.server 42 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 43 | 44 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 45 | 46 | :figwheel {:http-server-root "public" 47 | :port 3449 48 | :css-dirs ["resources/public/css"]} 49 | 50 | :env {:is-dev true} 51 | 52 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 53 | 54 | :uberjar {:hooks [leiningen.cljsbuild] 55 | :env {:production true} 56 | :omit-source true 57 | :aot :all 58 | :cljsbuild {:builds {:app 59 | {:source-paths ["env/prod/cljs"] 60 | :compiler 61 | {:optimizations :advanced 62 | :pretty-print false}}}}}}) 63 | -------------------------------------------------------------------------------- /recipes/scrollbar/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/scrollbar/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/scrollbar/src/clj/scrollbar/dev.clj: -------------------------------------------------------------------------------- 1 | (ns scrollbar.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('scrollbar.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/scrollbar/src/clj/scrollbar/server.clj: -------------------------------------------------------------------------------- 1 | (ns scrollbar.server 2 | (:require [clojure.java.io :as io] 3 | [scrollbar.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/scrollbar/src/cljs/scrollbar/core.cljs: -------------------------------------------------------------------------------- 1 | (ns scrollbar.core 2 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]) 3 | (:require [om.core :as om :include-macros true] 4 | [om-tools.dom :as dom :include-macros true] 5 | [om-tools.core :refer-macros [defcomponent]])) 6 | 7 | 8 | (def app-state 9 | (atom {:scrollbars []})) 10 | 11 | (defn add-sample-scrollbar! [cursor] 12 | (let [id (gensym) 13 | sb {:scroller-id (str "scroller" id) 14 | :slider-track-id (str "slider-track" id) 15 | :scrollbar-id (str "scrollbar" id) 16 | :scrollbar-height (rand-nth (range 40 200)) 17 | :scrollbar 100 18 | :scrolling false}] 19 | (om/transact! cursor 20 | [:scrollbars] 21 | (fn [coll] 22 | (conj coll sb))))) 23 | 24 | (defn scrollbar-mouse-down! [cursor] 25 | (go 26 | (om/update! cursor [:scrolling] true))) 27 | 28 | (defn scrollbar-mouse-up! [cursor] 29 | (go 30 | (om/update! cursor [:scrolling] false))) 31 | 32 | (defn scrollbar-percent! [cursor percent] 33 | (om/update! cursor [:scrollbar] percent)) 34 | 35 | (defn scrollbar-mouse-move! [evt cursor] 36 | (go-loop [state @cursor 37 | scroller-node (-> js/document 38 | (.getElementById (:scroller-id state))) 39 | slider-track (-> js/document 40 | (.getElementById (:slider-track-id state))) 41 | y (- (.-clientY evt) 8) 42 | yp (str y "px") 43 | true-offset (-> slider-track .getBoundingClientRect .-top) 44 | true-y (- y true-offset) 45 | full (:scrollbar-height state) 46 | percent (->> (/ true-y full) 47 | (* 100) 48 | (- 100) 49 | int)] 50 | (when (and (:scrolling state) 51 | (>= 100 percent) 52 | (>= percent 0)) 53 | (-> scroller-node 54 | .-style.top 55 | (set! yp)) 56 | (scrollbar-percent! cursor percent)))) 57 | 58 | (defcomponent scrollbar-view [cursor _] 59 | (did-mount 60 | [_] 61 | (let [scroller-node (-> js/document 62 | (.getElementById (:scroller-id cursor))) 63 | scrollbar-node (-> js/document 64 | (.getElementById (:scrollbar-id cursor)))] 65 | ;; set scrolling 66 | (doseq [node [scroller-node 67 | scrollbar-node]] 68 | (-> node 69 | .-onmousedown 70 | (set! (fn [_] (scrollbar-mouse-down! cursor))))) 71 | 72 | ;; set not scrolling 73 | (doseq [node [scroller-node 74 | scrollbar-node]] 75 | (-> node 76 | .-onmouseup 77 | (set! (fn [_] (scrollbar-mouse-up! cursor))))) 78 | 79 | ;; set movement listener 80 | (doseq [node [scroller-node 81 | scrollbar-node]] 82 | (-> node 83 | .-onmousemove 84 | (set! (fn [evt] (scrollbar-mouse-move! evt cursor))))))) 85 | (render 86 | [_] 87 | (let [height-px (str (:scrollbar-height cursor) "px")] 88 | (dom/span 89 | (dom/b (str (:scrollbar cursor) "%")) 90 | (dom/div {:id (:scrollbar-id cursor) 91 | :style {:width "20px" 92 | :height height-px 93 | :padding "8px 2px 8px 2px" 94 | :border-width "1px" 95 | :border-color "#000" 96 | :border-style "solid" 97 | :background-color "hsl(190,15%,90%)"}} 98 | (dom/div {:id (:slider-track-id cursor) 99 | :style {:float "left" 100 | :padding "1px" 101 | :height height-px 102 | :margin-left "9px" 103 | :background-color "#000"}}) 104 | (dom/div {:id (:scroller-id cursor) 105 | :style {:width "18px" 106 | :height "10px" 107 | :position "absolute" 108 | :background-color "hsl(195,100%,50%)" 109 | :border-width "1px" 110 | :border-style "solid" 111 | :border-color "#000"}})))))) 112 | 113 | (defcomponent main-view [app owner] 114 | (will-mount 115 | [_] 116 | (dotimes [_ 10] 117 | (add-sample-scrollbar! app))) 118 | (render 119 | [_] 120 | (dom/table 121 | (dom/tr 122 | (for [sb (:scrollbars app)] 123 | (dom/td {:style {:padding 30}} 124 | (om/build scrollbar-view sb))))))) 125 | 126 | (defn main [] 127 | (om/root 128 | main-view 129 | app-state 130 | {:target (. js/document (getElementById "app"))})) 131 | -------------------------------------------------------------------------------- /recipes/scrollbar/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/sortable-table/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/sortable-table/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/sortable-table.jar clojure.main -m sortable-table.server 2 | -------------------------------------------------------------------------------- /recipes/sortable-table/README.md: -------------------------------------------------------------------------------- 1 | # Single Attribute Sortable Table 2 | 3 | ![alt text](preview.gif "Preview Image GIF") 4 | 5 | 1) Create a new Om project using Chestnut 6 | 7 | ```bash 8 | lein new chestnut sortable-table -- --om-tools --http-kit 9 | ``` 10 | 11 | 2) Use your editor of choice to open the file `sortable-table/src/cljs/core.cljs` 12 | 13 | 14 | 3) Your initial app-state should look like this. 15 | 16 | ```clojure 17 | (defonce app-state 18 | (atom 19 | {:table [{:gender "M" :name "person1" :age 100} 20 | {:gender "F" :name "person2" :age 50} 21 | {:gender "N/A" :name "person3" :age 10} 22 | {:gender "F" :name "person4" :age 20} 23 | {:gender "F" :name "person5" :age 30} 24 | {:gender "M" :name "person6" :age 80} 25 | {:gender "N/A" :name "person7" :age 80}]})) 26 | ``` 27 | 28 | 4) Define state in your local component to store sort settings 29 | 30 | ```clojure 31 | (init-state [_] {:sort :nil 32 | :sort-dir nil}) 33 | ``` 34 | 35 | 5) Define a way to change the sort order and key 36 | 37 | ``` clojure 38 | (defn change-sorting [owner key] 39 | ;; invert direction on second click 40 | (when (= (om/get-state owner :sort) key) 41 | (om/set-state! owner :sort-dir (not (om/get-state owner :sort-dir)))) 42 | (om/set-state! owner :sort key)) 43 | ``` 44 | 45 | 6) Set up the function to sort the data 46 | 47 | ``` clojure 48 | (defn sorted-data [key direction data] 49 | (if key 50 | ((if direction reverse identity) 51 | (sort-by key data)) 52 | data)) 53 | ``` 54 | 55 | 7) Create a table view header component 56 | 57 | ```clojure 58 | (defcomponent table-view-header [{:keys [header]} _] 59 | (render [_])) 60 | ``` 61 | 62 | 8) Add header element with a fn that changes the sort setting 63 | 64 | ```clojure 65 | (defcomponent table-view-header [{:keys [header sort-fn]} _] 66 | (render 67 | [_] 68 | (let [header-name (name header) 69 | header-kw (keyword header-name) 70 | attr {:href "javascript:void(0);" 71 | :style {:margin "5px;" 72 | :font-size "30px;" 73 | :text-decoration "none;"} 74 | :on-click sort-fn} 75 | header-attr {:style {:padding-right "20px;" 76 | :font-size "25px;"}}] 77 | (dom/th header-attr 78 | (clojure.string/capitalize header-name) 79 | ;; UTF-8 Up/Down Arrow 80 | (dom/a attr "⬍"))))) 81 | ``` 82 | 83 | 8) Create a table row component 84 | 85 | ```clojure 86 | (defcomponent table-row [row owner] 87 | (render 88 | [_] 89 | (dom/tr 90 | (for [value (map val row)] 91 | (dom/td value))))) 92 | ``` 93 | 94 | 9) Define a function that gets the table headers and adds the sort key 95 | 96 | ```clojure 97 | (defn header-types [data sort-fn] 98 | (map #(hash-map :header % :sort-fn (partial sort-fn %)) 99 | (-> data 100 | first 101 | keys))) 102 | ``` 103 | 104 | 10) Create a component that builds the table's header and rows. 105 | This table component holds the local sort state. 106 | The partial is being used to draft a function that is called with the "owern" from this scope and the header key from the header-types scope 107 | 108 | ```clojure 109 | (defcomponent table-view [data owner] 110 | (init-state [_] {:sort :nil 111 | :sort-dir nil}) 112 | (render-state 113 | [_ state] 114 | (dom/table 115 | (dom/tr 116 | (om/build-all table-view-header (header-types data (partial change-sorting owner)))) 117 | (om/build-all table-row 118 | (sorted-data (:sort state) 119 | (:sort-dir state) 120 | data))))) 121 | ``` 122 | 123 | 11) Define the view that holds the table, and hand it the app state cursor 124 | 125 | (defcomponent main-view [app-state] 126 | (render [_] 127 | (om/build table-view (:table app-state)))) 128 | 129 | 12) Display the main view 130 | 131 | ```clojure 132 | (defn main [] 133 | (om/root 134 | main-view 135 | app-state 136 | {:target (. js/document (getElementById "app"))})) 137 | ``` 138 | 139 | 13) Start a REPL with `lein repl` 140 | 141 | ``` 142 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 143 | REPL-y 0.3.5, nREPL 0.2.6 144 | Clojure 1.6.0 145 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 146 | Docs: (doc function-name-here) 147 | (find-doc "part-of-name-here") 148 | Source: (source function-name-here) 149 | Javadoc: (javadoc java-object-or-class-here) 150 | Exit: Control+D or (exit) or (quit) 151 | Results: Stored in vars *1, *2, *3, an exception in *e 152 | ``` 153 | 154 | 14) Call `run` to start the back end and compile your ClojureScript. 155 | 156 | ``` 157 | sortable-table.server=> (run) 158 | Starting figwheel. 159 | Starting web server on port 10555 . 160 | # 161 | sortable-table.server=> Compiling ClojureScript. 162 | Figwheel: Starting server at http://localhost:3449 163 | Figwheel: Serving files from '(dev-resources|resources)/public' 164 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 165 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 166 | notifying browser that file changed: /js/out/local_state/core.js 167 | ``` 168 | 169 | 15) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 170 | 171 | -------------------------------------------------------------------------------- /recipes/sortable-table/env/dev/cljs/sortable_table/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns sortable-table.dev 2 | (:require [sortable-table.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/sortable-table/env/prod/cljs/sortable_table/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns sortable-table.prod 2 | (:require [sortable-table.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/sortable-table/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/sortable-table/preview.gif -------------------------------------------------------------------------------- /recipes/sortable-table/project.clj: -------------------------------------------------------------------------------- 1 | (defproject sortable-table "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"] 20 | [http-kit "2.1.19"] 21 | [prismatic/om-tools "0.3.3"]] 22 | 23 | :plugins [[lein-cljsbuild "1.0.3"] 24 | [lein-environ "1.0.0"]] 25 | 26 | :min-lein-version "2.5.0" 27 | 28 | :uberjar-name "sortable-table.jar" 29 | 30 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 31 | :compiler {:output-to "resources/public/js/app.js" 32 | :output-dir "resources/public/js/out" 33 | :source-map "resources/public/js/out.js.map" 34 | :preamble ["react/react.min.js"] 35 | :externs ["react/externs/react.js"] 36 | :optimizations :none 37 | :pretty-print true}}}} 38 | 39 | :profiles {:dev {:repl-options {:init-ns sortable-table.server 40 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 41 | 42 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 43 | 44 | :figwheel {:http-server-root "public" 45 | :port 3449 46 | :css-dirs ["resources/public/css"]} 47 | 48 | :env {:is-dev true} 49 | 50 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 51 | 52 | :uberjar {:hooks [leiningen.cljsbuild] 53 | :env {:production true} 54 | :omit-source true 55 | :aot :all 56 | :cljsbuild {:builds {:app 57 | {:source-paths ["env/prod/cljs"] 58 | :compiler 59 | {:optimizations :advanced 60 | :pretty-print false}}}}}}) 61 | -------------------------------------------------------------------------------- /recipes/sortable-table/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/sortable-table/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/sortable-table/src/clj/sortable_table/dev.clj: -------------------------------------------------------------------------------- 1 | (ns sortable-table.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('sortable_table.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/sortable-table/src/clj/sortable_table/server.clj: -------------------------------------------------------------------------------- 1 | (ns sortable-table.server 2 | (:require [clojure.java.io :as io] 3 | [sortable-table.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10559))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/sortable-table/src/cljs/sortable_table/core.cljs: -------------------------------------------------------------------------------- 1 | (ns sortable-table.core 2 | (:require 3 | [om.core :as om :include-macros true] 4 | [om-tools.dom :as dom :include-macros true] 5 | [om-tools.core :refer-macros [defcomponent]])) 6 | 7 | (def app-state 8 | (atom 9 | {:table [{:gender "M" :name "person1" :age 100} 10 | {:gender "F" :name "person2" :age 50} 11 | {:gender "N/A" :name "person3" :age 10} 12 | {:gender "F" :name "person4" :age 20} 13 | {:gender "F" :name "person5" :age 30} 14 | {:gender "M" :name "person6" :age 80} 15 | {:gender "N/A" :name "person7" :age 80}]})) 16 | 17 | (defcomponent table-view-header [{:keys [header sort-fn]} _] 18 | (render 19 | [_] 20 | (let [header-name (name header) 21 | header-kw (keyword header-name) 22 | attr {:href "javascript:void(0);" 23 | :style {:margin "5px;" 24 | :font-size "30px;" 25 | :text-decoration "none;"} 26 | :on-click sort-fn} 27 | header-attr {:style {:padding-right "20px;" 28 | :font-size "25px;"}}] 29 | (dom/th header-attr 30 | (clojure.string/capitalize header-name) 31 | ;; UTF-8 Up/Down Arrow 32 | (dom/a attr "⬍"))))) 33 | 34 | (defcomponent table-row [row owner] 35 | (render 36 | [_] 37 | (dom/tr 38 | (for [value (map val row)] 39 | (dom/td value))))) 40 | 41 | (defn sorted-data [key direction data] 42 | (if key 43 | ((if direction reverse identity) 44 | (sort-by key data)) 45 | data)) 46 | 47 | (defn change-sorting [owner key] 48 | ;; invert direction on second click 49 | (when (= (om/get-state owner :sort) key) 50 | (om/set-state! owner :sort-dir (not (om/get-state owner :sort-dir)))) 51 | (om/set-state! owner :sort key)) 52 | 53 | (defn header-types [data sort-fn] 54 | (map #(hash-map :header % :sort-fn (partial sort-fn %)) 55 | (-> data 56 | first 57 | keys))) 58 | 59 | (defcomponent table-view [data owner] 60 | ;; Our local state stores the sort key and the sort direction. 61 | ;; The model data will never be modified, it contains the truth. 62 | ;; Sorting is just a ephemeral view setting, which is also stored in said view. 63 | (init-state [_] {:sort :nil 64 | :sort-dir nil}) 65 | (render-state 66 | [_ state] 67 | (dom/table 68 | (dom/tr 69 | (om/build-all table-view-header (header-types data (partial change-sorting owner)))) 70 | (om/build-all table-row 71 | (sorted-data (:sort state) 72 | (:sort-dir state) 73 | data))))) 74 | 75 | 76 | (defcomponent main-view [app-state] 77 | (render [_] 78 | (om/build table-view (:table app-state)))) 79 | 80 | (defn main [] 81 | (om/root 82 | main-view 83 | app-state 84 | {:target (. js/document (getElementById "app"))})) 85 | -------------------------------------------------------------------------------- /recipes/sortable-table/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/toggle-button/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /recipes/toggle-button/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/toggle-button.jar clojure.main -m toggle-button.server 2 | -------------------------------------------------------------------------------- /recipes/toggle-button/README.md: -------------------------------------------------------------------------------- 1 | # Om Toggle Button 2 | 3 | Preview Image: 4 | ![alt text](preview.gif "Preview Animation Image GIF") 5 | 6 | 1) Create a new Om project using Chestnut 7 | 8 | ```bash 9 | lein new chestnut toggle-button -- --om-tools --http-kit 10 | ``` 11 | 12 | 2) Use your editor of choice to open the file `toggle-button/src/cljs/core.cljs` 13 | 14 | 15 | 3) Your initial app-state should look like this. 16 | 17 | ```clojure 18 | (defonce app-state (atom {:toggle true})) 19 | ``` 20 | 21 | 4) Create a toggle button component 22 | 23 | ```clojure 24 | (defcomponent toggle-button-view [cursor _] 25 | (render 26 | [_] 27 | (let [on? (:toggle cursor)] 28 | (dom/div 29 | {:class "can-toggle"} 30 | (dom/span "ON") 31 | (dom/a 32 | {:href "javascript:void(0)"} 33 | (dom/span 34 | {:class "can-toggle-switch"})) 35 | (dom/span "OFF"))))) 36 | ``` 37 | 38 | 5) Add button view styles 39 | 40 | ```clojure 41 | (defcomponent toggle-button-view [cursor _] 42 | (render 43 | [_] 44 | (let [on? (:toggle cursor) 45 | [on off] (if on? 46 | ["inline" "none"] 47 | ["none" "inline"]) 48 | padding "2px 20px 2px 20px;" 49 | on-attr {:background-color "hsl(100,90%,60%)" 50 | :color "hsl(0,90%,60%)" 51 | :font-weight "bold" 52 | :font-size "20px" 53 | :padding padding 54 | :display on} 55 | off-attr {:background-color "hsl(0,90%,60%)" 56 | :color "hsl(110,50%,50%)" 57 | :font-weight "bold" 58 | :font-size "20px" 59 | :padding padding 60 | :display off} 61 | toggle-attr {:width "5px;" 62 | :height "5px;" 63 | :padding "5px;" 64 | :border-width "1px;" 65 | :border-style "solid;" 66 | :border-color "black"}] 67 | (dom/div 68 | {:class "can-toggle" 69 | :style {:width "55px;"}} 70 | (dom/span {:style on-attr} "ON") 71 | (dom/a 72 | {:href "javascript:void(0)"} 73 | (dom/span 74 | {:class "can-toggle-switch" 75 | :style toggle-attr})) 76 | (dom/span {:style off-attr} "OFF"))))) 77 | ``` 78 | 79 | 6) Add click logic to toggle using `om/update!` 80 | 81 | ```clojure 82 | (defcomponent toggle-button-view [cursor _] 83 | (render 84 | [_] 85 | (let [on? (:toggle cursor) 86 | [on off] (if on? 87 | ["inline" "none"] 88 | ["none" "inline"]) 89 | padding "2px 20px 2px 20px;" 90 | on-attr {:background-color "hsl(100,90%,60%)" 91 | :color "hsl(0,90%,60%)" 92 | :font-weight "bold" 93 | :font-size "20px" 94 | :padding padding 95 | :display on} 96 | off-attr {:background-color "hsl(0,90%,60%)" 97 | :color "hsl(110,50%,50%)" 98 | :font-weight "bold" 99 | :font-size "20px" 100 | :padding padding 101 | :display off} 102 | toggle-attr {:width "5px;" 103 | :height "5px;" 104 | :padding "5px;" 105 | :border-width "1px;" 106 | :border-style "solid;" 107 | :border-color "black"}] 108 | (dom/div 109 | {:class "can-toggle" 110 | :style {:width "55px;"}} 111 | (dom/span {:style on-attr} "ON") 112 | (dom/a 113 | {:href "javascript:void(0)" 114 | :on-click (fn [_] 115 | (om/update! cursor 116 | [:toggle] 117 | (not on?)))} 118 | (dom/span 119 | {:class "can-toggle-switch" 120 | :style toggle-attr})) 121 | (dom/span {:style off-attr} "OFF"))))) 122 | ``` 123 | 7) Replace or alter your main function to display the validation box view. 124 | 125 | ```clojure 126 | (defn main [] 127 | (om/root 128 | toggle-button-view 129 | app-state 130 | {:target (. js/document (getElementById "app"))})) 131 | ``` 132 | 8) Start a REPL with `lein repl` 133 | 134 | ``` 135 | nREPL server started on port 54879 on host 127.0.0.1 - nrepl://127.0.0.1:54879 136 | REPL-y 0.3.5, nREPL 0.2.6 137 | Clojure 1.6.0 138 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13 139 | Docs: (doc function-name-here) 140 | (find-doc "part-of-name-here") 141 | Source: (source function-name-here) 142 | Javadoc: (javadoc java-object-or-class-here) 143 | Exit: Control+D or (exit) or (quit) 144 | Results: Stored in vars *1, *2, *3, an exception in *e 145 | ``` 146 | 147 | 9) Call `run` to start the back end and compile your ClojureScript. 148 | 149 | ``` 150 | toggle-button.server=> (run) 151 | Starting figwheel. 152 | Starting web server on port 10555 . 153 | # 154 | toggle-button.server=> Compiling ClojureScript. 155 | Figwheel: Starting server at http://localhost:3449 156 | Figwheel: Serving files from '(dev-resources|resources)/public' 157 | Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")... 158 | Successfully compiled "resources/public/js/app.js" in 18.01 seconds. 159 | notifying browser that file changed: /js/out/local_state/core.js 160 | ``` 161 | 162 | 10) Point your browser to http://localhost:port. You can find the port in the REPL message output => `Starting web server on port ...` 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /recipes/toggle-button/env/dev/cljs/toggle_button/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns toggle-button.dev 2 | (:require [toggle-button.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] (core/main))) 12 | 13 | (weasel/connect "ws://localhost:9001" :verbose true) 14 | 15 | (core/main) 16 | -------------------------------------------------------------------------------- /recipes/toggle-button/env/prod/cljs/toggle_button/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns toggle-button.prod 2 | (:require [toggle-button.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /recipes/toggle-button/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omcljs/om-cookbook/a44bb8d8a3330bae7bc000830a28bae4567ea830/recipes/toggle-button/preview.gif -------------------------------------------------------------------------------- /recipes/toggle-button/project.clj: -------------------------------------------------------------------------------- 1 | (defproject toggle-button "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"] 20 | [http-kit "2.1.19"] 21 | [prismatic/om-tools "0.3.3"]] 22 | 23 | :plugins [[lein-cljsbuild "1.0.3"] 24 | [lein-environ "1.0.0"]] 25 | 26 | :min-lein-version "2.5.0" 27 | 28 | :uberjar-name "toggle-button.jar" 29 | 30 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 31 | :compiler {:output-to "resources/public/js/app.js" 32 | :output-dir "resources/public/js/out" 33 | :source-map "resources/public/js/out.js.map" 34 | :preamble ["react/react.min.js"] 35 | :externs ["react/externs/react.js"] 36 | :optimizations :none 37 | :pretty-print true}}}} 38 | 39 | :profiles {:dev {:repl-options {:init-ns toggle-button.server 40 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 41 | 42 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 43 | 44 | :figwheel {:http-server-root "public" 45 | :port 3449 46 | :css-dirs ["resources/public/css"]} 47 | 48 | :env {:is-dev true} 49 | 50 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 51 | 52 | :uberjar {:hooks [leiningen.cljsbuild] 53 | :env {:production true} 54 | :omit-source true 55 | :aot :all 56 | :cljsbuild {:builds {:app 57 | {:source-paths ["env/prod/cljs"] 58 | :compiler 59 | {:optimizations :advanced 60 | :pretty-print false}}}}}}) 61 | -------------------------------------------------------------------------------- /recipes/toggle-button/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /recipes/toggle-button/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /recipes/toggle-button/src/clj/toggle_button/dev.clj: -------------------------------------------------------------------------------- 1 | (ns toggle-button.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('toggle_button.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /recipes/toggle-button/src/clj/toggle_button/server.clj: -------------------------------------------------------------------------------- 1 | (ns toggle-button.server 2 | (:require [clojure.java.io :as io] 3 | [toggle-button.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [org.httpkit.server :refer [run-server]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-server http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /recipes/toggle-button/src/cljs/toggle_button/core.cljs: -------------------------------------------------------------------------------- 1 | (ns toggle-button.core 2 | (:require [om.core :as om :include-macros true] 3 | [om-tools.dom :as dom :include-macros true] 4 | [om-tools.core :refer-macros [defcomponent]])) 5 | 6 | 7 | (defonce app-state (atom {:toggle true})) 8 | 9 | (defcomponent toggle-button-view [cursor _] 10 | (render 11 | [_] 12 | (let [on? (:toggle cursor) 13 | [on off] (if on? 14 | ["inline" "none"] 15 | ["none" "inline"]) 16 | padding "2px 20px 2px 20px;" 17 | on-attr {:background-color "hsl(100,90%,60%)" 18 | :color "hsl(0,90%,60%)" 19 | :font-weight "bold" 20 | :font-size "20px" 21 | :padding padding 22 | :display on} 23 | off-attr {:background-color "hsl(0,90%,60%)" 24 | :color "hsl(110,50%,50%)" 25 | :font-weight "bold" 26 | :font-size "20px" 27 | :padding padding 28 | :display off} 29 | toggle-attr {:width "5px;" 30 | :height "5px;" 31 | :padding "5px;" 32 | :border-width "1px;" 33 | :border-style "solid;" 34 | :border-color "black"}] 35 | (dom/div 36 | {:class "can-toggle" 37 | :style {:width "55px;"}} 38 | (dom/span {:style on-attr} "ON") 39 | (dom/a 40 | {:href "javascript:void(0)" 41 | :on-click (fn [_] 42 | (om/update! cursor 43 | [:toggle] 44 | (not on?)))} 45 | (dom/span 46 | {:class "can-toggle-switch" 47 | :style toggle-attr})) 48 | (dom/span {:style off-attr} "OFF"))))) 49 | 50 | (defn main [] 51 | (om/root 52 | toggle-button-view 53 | app-state 54 | {:target (. js/document (getElementById "app"))})) 55 | -------------------------------------------------------------------------------- /recipes/toggle-button/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /recipes/views/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | -------------------------------------------------------------------------------- /recipes/views/README.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | You want create different views for the same data and toggle them 4 | depending on user's selection. 5 | 6 | # Solution 7 | 8 | 1. Create a project structure using Leiningen template 9 | [mies](https://github.com/swannodette/mies) 10 | 11 | ``` 12 | $ lein new mies views 13 | ``` 14 | 15 | 2. We will use [om-boostrap](http://om-bootstrap.herokuapp.com/) to 16 | create table and panels. Add we'll also add Om and 17 | [sablono](https://github.com/r0man/sablono) to our 18 | dependencies: 19 | 20 | ```clojure 21 | :dependencies [[org.clojure/clojure "1.6.0"] 22 | [org.clojure/clojurescript "0.0-2371"] 23 | 24 | [racehub/om-bootstrap "0.3.1"] 25 | [om "0.8.0-alpha2"] 26 | [sablono "0.2.20"]] 27 | ``` 28 | 29 | 3. Let's add React and Boostrap CSS to our html file: 30 | 31 | ``` 32 | 33 | 34 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | 4. We're going to use a toolbar with two buttons as our view toggle: 48 | 49 | ```clojure 50 | (defn toggle [cursor owner opts] 51 | (om/component 52 | (b/toolbar {} 53 | (b/button {:active? (when (= :table (-> cursor :selected)) true) 54 | :on-click (fn [_] (om/update! cursor :selected :table))} 55 | "Table") 56 | (b/button {:active? (when (= :panels (-> cursor :selected)) true) 57 | :on-click (fn [_] (om/update! cursor :selected :panels))} 58 | "Panels")))) 59 | ``` 60 | 61 | 5. Depending on the view selected we'll want to build a different 62 | component. We can use a multimethod for that: 63 | 64 | ```clojure 65 | (defmulti view (fn [cursor owner opts] (-> cursor :view :selected))) 66 | ``` 67 | 68 | If the view selected is a table, we'll build this: 69 | 70 | ```clojure 71 | (defmethod view :table [cursor owner opts] 72 | (om/component 73 | (table {:striped? true :bordered? true :hover? true} 74 | (d/thead 75 | (d/tr 76 | (d/th "Timestamp") 77 | (d/th "Value"))) 78 | (d/tbody 79 | (for [v (:data cursor)] 80 | (d/tr 81 | (d/td (:timestamp v)) 82 | (d/td (:value v)))))))) 83 | ``` 84 | 85 | Or if it's panels, then we'll build this: 86 | 87 | ```clojure 88 | (defmethod view :panels [cursor owner opts] 89 | (om/component 90 | (d/div 91 | (for [v (:data cursor)] 92 | (p/panel {} 93 | (d/p 94 | (d/p (str "Timestamp: " (:timestamp v))) 95 | (d/p (str "Value: " (:value v))))))))) 96 | ``` 97 | 98 | 6. That's it. Run `lein cljsbuild once`, open up `index.html` in 99 | your browser and try out selecting different views. 100 | 101 | Multimethods are a great way to build different components based on 102 | some criteria. 103 | -------------------------------------------------------------------------------- /recipes/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /recipes/views/project.clj: -------------------------------------------------------------------------------- 1 | (defproject views "0.1.0-SNAPSHOT" 2 | :description "FIXME: write this!" 3 | :url "http://example.com/FIXME" 4 | 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [org.clojure/clojurescript "0.0-2371"] 7 | 8 | [racehub/om-bootstrap "0.3.1"] 9 | [om "0.8.0-alpha2"] 10 | [sablono "0.2.20"]] 11 | 12 | :plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"]] 13 | 14 | :source-paths ["src"] 15 | 16 | :cljsbuild { 17 | :builds [{:id "views" 18 | :source-paths ["src"] 19 | :compiler { 20 | :output-to "views.js" 21 | :output-dir "out" 22 | :optimizations :none 23 | :source-map true}}]}) 24 | -------------------------------------------------------------------------------- /recipes/views/src/views/core.cljs: -------------------------------------------------------------------------------- 1 | (ns views.core 2 | (:require [om-bootstrap.button :as b] 3 | [om-bootstrap.table :refer [table]] 4 | [om-bootstrap.panel :as p] 5 | [om-tools.dom :as d :include-macros true] 6 | [om.core :as om :include-macros true] 7 | [sablono.core :as html :refer-macros [html]])) 8 | 9 | (enable-console-print!) 10 | 11 | (defonce app-state (atom {:view {:selected :table} 12 | :data [{:value 240000 :timestamp "2014-01-01"} 13 | {:value 260000 :timestamp "2014-02-01"} 14 | {:value 290000 :timestamp "2014-03-01"} 15 | {:value 70000 :timestamp "2014-04-01"} 16 | {:value 100000 :timestamp "2014-05-01"} 17 | {:value 120000 :timestamp "2014-06-01"} 18 | {:value 240000 :timestamp "2014-07-01"} 19 | {:value 220000 :timestamp "2014-08-01"} 20 | {:value 360000 :timestamp "2014-09-01"} 21 | {:value 260000 :timestamp "2014-10-01"} 22 | {:value 250000 :timestamp "2014-11-01"} 23 | {:value 190000 :timestamp "2014-12-01"}]})) 24 | 25 | (defmulti view (fn [cursor owner opts] (-> cursor :view :selected))) 26 | 27 | (defmethod view :table [cursor owner opts] 28 | (om/component 29 | (table {:striped? true :bordered? true :hover? true} 30 | (d/thead 31 | (d/tr 32 | (d/th "Timestamp") 33 | (d/th "Value"))) 34 | (d/tbody 35 | (for [v (:data cursor)] 36 | (d/tr 37 | (d/td (:timestamp v)) 38 | (d/td (:value v)))))))) 39 | 40 | (defmethod view :panels [cursor owner opts] 41 | (om/component 42 | (d/div 43 | (for [v (:data cursor)] 44 | (p/panel {} 45 | (d/p 46 | (d/p (str "Timestamp: " (:timestamp v))) 47 | (d/p (str "Value: " (:value v))))))))) 48 | 49 | (defn toggle [cursor owner opts] 50 | (om/component 51 | (b/toolbar {} 52 | (b/button {:active? (when (= :table (-> cursor :selected)) true) 53 | :on-click (fn [_] (om/update! cursor :selected :table))} 54 | "Table") 55 | (b/button {:active? (when (= :panels (-> cursor :selected)) true) 56 | :on-click (fn [_] (om/update! cursor :selected :panels))} 57 | "Panels")))) 58 | 59 | (om/root 60 | (fn [app owner] 61 | (reify 62 | om/IRender 63 | (render [_] 64 | (html 65 | [:div.col-md-6 66 | [:h1 "Views"] 67 | [:div 68 | [:div {:style {:padding-bottom "3px"}} 69 | ;; Button to toggle the view 70 | (om/build toggle (:view app))] 71 | ;; View component 72 | (om/build view app)]])))) 73 | app-state {:target (. js/document (getElementById "app"))}) 74 | --------------------------------------------------------------------------------