├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── project.clj └── src └── reframe_utils └── core.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.idea 11 | *.iml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | ## 0.2.2 - 2019-11-01 3 | ### Changes 4 | - Make `add-or-update-by-id-event` public 5 | 6 | ## 0.2.1-1 - 2018-05-04 7 | ### Added 8 | - Error handler to each of the existing ajax request sugar functions 9 | 10 | ### Fixed 11 | - Error-handler accidentally firing 12 | 13 | ## 0.2.1 - 2018-05-03 14 | ### Added 15 | - Allow on-error and error-handler params to ajax requests 16 | - Allow handler param to ajax requests (alias for on-success) 17 | ### Changed 18 | - Handle fn or dispatcher for ajax requests 19 | 20 | ## 0.2.0 - 2018-?-? 21 | ### Added 22 | - reg-ajax-delete-event 23 | - Ability to alter on-success keys for ajax events, as well as manipulate DB with a DB fn 24 | - sort-fn arg to reg-basic-sub (3-arity) 25 | 26 | ### Changed 27 | - Bump all dependencies (breaking changes to cljs-ajax) 28 | 29 | ### Fixed 30 | - Nested get for subscriptions not working 31 | 32 | ## 0.1.4 - 2016-07-07 33 | ### Added 34 | - reg-sub-by-id 35 | 36 | ## 0.1.3 - 2016-11-03 37 | ### Fixed 38 | - Data not returning following error being thrown 39 | 40 | ## 0.1.2 - 2016-11-01 41 | ### Added 42 | - reg-update-by-id-event 43 | - reg-add-or-update-by-id-event 44 | - reg-ajax-post-event 45 | - reg-ajax-put-event 46 | 47 | ### Changed 48 | - Allow for reframe-utils/http effect to take a variable number of parameters for any custom parameters passed to the ajax.core requests 49 | - Bump clojurescript to 1.9.293 50 | 51 | ## 0.1.1 - 2016-09-21 52 | ### Added 53 | - Prototype reg-ajax-get-event 54 | 55 | ### Changed 56 | - Bump clojurescript to 1.9.229 57 | - Add docstrings 58 | 59 | ## 0.1.0 - 2016-09-06 60 | ### Added 61 | - reg-basic-sub 62 | - reg-set-event 63 | - reg-add-event 64 | - reg-update-event 65 | - reg-remove-event 66 | - multi-generation 67 | 68 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Nikola Peric 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reframe-utils 2 | 3 | [![Clojars Project](http://clojars.org/reframe-utils/latest-version.svg)](https://clojars.org/reframe-utils) 4 | 5 | A collection of commonly used utility functions/helper functions for re-frame. 6 | 7 | ## Usage 8 | 9 | If you've been using the magical [reagent](https://github.com/reagent-project/reagent) and [re-frame](https://github.com/Day8/re-frame) libraries, and are anything like me, you will have implemented a bunch of helper functions that manage common subscription and event/handler registration events that you create. After copy and pasting these like a pleb from project to project it seemed like about time to put together a library that has commonly used ones. The purpose of this library is to add syntactic sugar to help you reduce the lines of code you need to write when using re-frame. 10 | 11 | If you have any additional helper functions you wish to submit or suggest, please make a pull request or add an issue! 12 | 13 | To begin using the library ensure you have the latest reframe-utils included in your leiningen or boot dependencies and add the following to any namespaces you wish to use the utilities with. 14 | 15 | It's worth noting that all of the utilities in this library can handle namespaced keywords, e.g. :library/books, intelligently. 16 | 17 | `(require [reframe-utils.core :as rf-utils])` 18 | 19 | ### Subscription utilities 20 | 21 | #### `reg-basic-sub` #### 22 | Used to register a basic get query from the database 23 | 24 | ```clojure 25 | (reg-basic-sub :common/active-page) 26 | ;; Equivalent to 27 | (reg-basic-sub :common/active-page :common/active-page) 28 | ;; Equivalent to 29 | (reg-sub 30 | :common/active-page 31 | (fn [db _] 32 | (:common/active-page db))) 33 | ``` 34 | 35 | #### `reg-sub-by-id` #### 36 | ```clojure 37 | (reg-sub-by-id :common/pages) 38 | ;; Equivalent to 39 | (reg-sub-by-id :common/pages :common/pages) 40 | ;; Equivalent to 41 | (reg-sub-by-id :common/pages :common/pages :id) 42 | ;; Equivalent to 43 | (reg-sub 44 | :common/pages 45 | (fn [db [_ id]] 46 | (->> :common/pages 47 | (get db) 48 | (filter 49 | (fn [item] 50 | (= (get item :id) id))) 51 | first))) 52 | ``` 53 | 54 | ### Event/handler utilities 55 | 56 | #### `reg-set-event` #### 57 | Used to register a basic associative set to a keyworded value in the database 58 | 59 | ```clojure 60 | (reg-set-event :active-page) 61 | ;; Equivalent to 62 | (reg-set-event :set-active-page :active-page) 63 | ;; Equivalent to 64 | (reg-event-db 65 | :set-active-page 66 | (fn [db [_ page]] 67 | (assoc db :active-page page))) 68 | ``` 69 | 70 | Unless specified otherwise, event/handler utilities you can do crazy stuff like this which lets you update/change values of nested keywords 71 | ```clojure 72 | (reg-set-event :set-deep-deep-value [:deep :super-deep :super-duper-deep]) 73 | ;; Equivalent to 74 | (reg-event-db 75 | :set-deep-deep-value 76 | (fn [db [_ page]] 77 | (assoc-in db [:deep :super-deep :super-duper-deep] page))) 78 | ``` 79 | 80 | #### `reg-add-event` #### 81 | Used to register a basic conj update to a keyworded value in the database 82 | 83 | ```clojure 84 | (reg-add-event :cases/add-case :cases/case) 85 | ;; Equivalent to 86 | (reg-event-db 87 | :cases/add-case 88 | (fn [db [_ case]] 89 | (update db :cases/case conj case))) 90 | ``` 91 | 92 | #### `reg-update-event` #### 93 | Used to register a basic replace update to a keyworded value in the database. Requires a given old and new value. 94 | 95 | ```clojure 96 | (reg-update-event :cases/update-case :cases/case) 97 | ;; Equivalent to 98 | (reg-event-db 99 | :cases/update-case 100 | (fn [db [_ old-case new-case]] 101 | (update db :cases/case #(replace {old new} %)))) 102 | ``` 103 | 104 | #### `reg-remove-event` #### 105 | Used to register a basic remove update to a keyworded value in the database. Removes the exact value that is passed through. 106 | 107 | ```clojure 108 | (reg-remove-event :cases/delete-case :cases/case) 109 | ;; Equivalent to 110 | (reg-event-db 111 | :cases/delete-case 112 | (fn [db [_ case]] 113 | (update db :cases/case 114 | (fn [cases] 115 | (remove #(= % case) cases))))) 116 | ``` 117 | 118 | #### `reg-update-by-id-event` #### 119 | View source for usage details. More docs to come eventually... 120 | 121 | #### `reg-add-or-update-by-id-event` #### 122 | View source for usage details. More docs to come eventually... 123 | 124 | ### General utilities 125 | 126 | #### `multi-generation` #### 127 | Used to generate multiple events or subscriptions at one go 128 | ```clojure 129 | (multi-generation reg-basic-sub 130 | :active-page 131 | [:active-cow :cow]) 132 | ;; will generate two subscriptions, active-page and active-cow 133 | ``` 134 | 135 | ### AJAX utilities 136 | 137 | #### `reg-ajax-get-event` #### 138 | ```clojure 139 | (reg-ajax-get-event "/api/request-call" :data) 140 | ;; Equivalent to 141 | (reg-ajax-get-event "/api/request-call" :get-data :data) 142 | 143 | ;; You would then dispatch the event as follows 144 | (dispatch [:get-data]) 145 | 146 | ;; If there are variable uri params to pass through you can register an event as follows 147 | (reg-ajax-get-event "/api/items/%s" :item) 148 | ;; And then you would dispatch it like so 149 | (dispatch [:get-item 1]) ;; => call GET on "/api/items/1" and assoc-in the response to :item 150 | ``` 151 | 152 | #### `reg-ajax-post-event` #### 153 | View source for usage details. More docs to come eventually... 154 | 155 | #### `reg-ajax-put-event` #### 156 | View source for usage details. More docs to come eventually... 157 | 158 | ## License 159 | 160 | Copyright © 2016 Nikola Peric 161 | 162 | Distributed under the MIT License 163 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject reframe-utils "0.2.2" 2 | :description "Utility/helper functions for use with re-frame" 3 | :url "https://github.com/nikolap/reframe-utils" 4 | :license {:name "The MIT License (MIT)" 5 | :url "https://opensource.org/licenses/MIT"} 6 | :dependencies [[org.clojure/clojure "1.9.0"] 7 | [org.clojure/clojurescript "1.9.946"] 8 | [re-frame "0.10.2"] 9 | [cljs-ajax "0.7.3"]]) 10 | -------------------------------------------------------------------------------- /src/reframe_utils/core.cljs: -------------------------------------------------------------------------------- 1 | (ns reframe-utils.core 2 | (:require 3 | [re-frame.core :refer [reg-sub reg-event-db reg-event-fx reg-fx dispatch]] 4 | [ajax.core :refer [GET HEAD POST PUT DELETE OPTIONS TRACE PATCH]] 5 | [goog.string :as gstring])) 6 | 7 | (def ^:private filter-first (comp first filter)) 8 | 9 | (defn- collify 10 | "Given an item, returns the item if it is a coll, 11 | otherwise returns it wrapped in a vector" 12 | [item] 13 | (if (coll? item) 14 | item 15 | [item])) 16 | 17 | (defn- kw-prefix 18 | "Takes a keyword and prefix. Appends the prefix to the keyword, 19 | taking into account namespacing" 20 | [kw prefix] 21 | (let [split-kw (clojure.string/split (subs (str kw) 1) #"/")] 22 | (keyword 23 | (if (= (count split-kw) 1) 24 | (str prefix (first split-kw)) 25 | (let [[kw-ns kw-name] split-kw] 26 | (str kw-ns "/" prefix kw-name)))))) 27 | 28 | (defn- remove-when 29 | "Removes an item from a collection" 30 | [coll item] 31 | (remove #(= % item) coll)) 32 | 33 | (defn- error-and-return [msg return] 34 | (.error js/console msg) 35 | return) 36 | 37 | (defn add-or-update-by-id-event 38 | [kw id-fn add-alt? db [_ item]] 39 | (if-let [id (id-fn item)] 40 | (update-in db kw 41 | (fn [items] 42 | (if-let [old-item (filter-first #(= (id-fn %) id) items)] 43 | (replace {old-item item} items) 44 | (if add-alt? 45 | (conj items item) 46 | (error-and-return (str "No existing item found to replace: " (pr-str item)) items))))) 47 | 48 | (error-and-return (str "No id found in: " (pr-str item)) db))) 49 | 50 | ;; SUBSCRIPTION UTILITIES 51 | 52 | (defn reg-basic-sub 53 | "Registers a 'get' subscription from the re-frame db. 54 | 55 | (reg-basic-sub :sub-name :kw-to-get) 56 | (reg-basic-sub :sub-name [:kw-to-get1 :kw-to-get2]) 57 | 58 | Using the one-arity version of this function will append 'get' 59 | as a prefix to the keyword for the name of the sub. For example 60 | (reg-basic-sub :best-ns/my-sub) registers a subscription named 61 | :best-ns/get-my-sub and returns :best-ns/my-sub from the db" 62 | ([name k sort-fn] 63 | (reg-sub 64 | name 65 | (fn [db _] 66 | (->> k 67 | collify 68 | (get-in db) 69 | (sort-by sort-fn))))) 70 | ([name k] 71 | (reg-sub 72 | name 73 | (fn [db _] 74 | (get-in db (collify k))))) 75 | ([k] 76 | (reg-basic-sub k k))) 77 | 78 | (defn reg-sub-by-id 79 | ([name k id-key] 80 | (reg-sub 81 | name 82 | (fn [db [_ id]] 83 | (->> k 84 | (get db) 85 | (filter 86 | (fn [item] 87 | (= (get item id-key) id))) 88 | first)))) 89 | ([name k] 90 | (reg-sub-by-id name k :id)) 91 | ([k] 92 | (reg-sub-by-id k k :id))) 93 | 94 | ;; EVENT/HANDLER UTILITIES 95 | 96 | (defn reg-set-event 97 | "Registers a 'set' associative event. 98 | 99 | (reg-set-event :event-name :kw-to-set) 100 | (reg-set-event :event-name [:kw-to-set1 :kw-to-set2]) 101 | 102 | Using the one-arity version of this function will append 'set' 103 | as a prefix to the keyword for the name of the revent. For example 104 | (reg-set-event :best-ns/my-kw) registers an event named 105 | :best-ns/set-my-kw that, when called, associates a value to 106 | :best-ns/my-kw db. 107 | 108 | As with other re-frame events you would call these through any form 109 | of the dispatch, e.g. (dispatch [:best-ns/set-my-kw [1 2 3 4]])" 110 | ([event-kw kw] 111 | (let [kw (collify kw)] 112 | (reg-event-db 113 | event-kw 114 | (fn [db [_ v]] 115 | (assoc-in db kw v))))) 116 | ([k] 117 | (assert (keyword? k) "1-arity reg-set-event must pass a keyword as the k, not a collection") 118 | (reg-set-event (kw-prefix k "set-") k))) 119 | 120 | (defn reg-add-event 121 | "Registers an update event to the db that preforms a conj 122 | 123 | (reg-add-event :add-kw :kw) 124 | (reg-add-event :add-kw [:kw1 :kw2]) 125 | 126 | You would call the event as follows, passing through one item to conj 127 | 128 | (dispatch [:add-kw my-item]) 129 | 130 | Note: there is no one-arity version of this function." 131 | [event-kw kw] 132 | (let [kw (collify kw)] 133 | (reg-event-db 134 | event-kw 135 | (fn [db [_ item]] 136 | (update-in db kw conj item))))) 137 | 138 | (defn reg-update-event 139 | "Registers an update event to the db that preforms a replace 140 | 141 | (reg-update-event :update-kw :kw) 142 | (reg-update-event :update-kw [:kw1 :kw2]) 143 | 144 | You would call the event as follows, passing through two items, 145 | the old item to replace, and the new one to replace it with 146 | 147 | (dispatch [:update-kw old-item new-item]) 148 | 149 | Note: there is no one-arity version of this function." 150 | [event-kw kw] 151 | (let [kw (collify kw)] 152 | (reg-event-db 153 | event-kw 154 | (fn [db [_ old new]] 155 | (update-in db kw #(replace {old new} %)))))) 156 | 157 | (defn reg-update-by-id-event 158 | "Registers an update event to the db that performs a replace with 159 | assumptions that each item in the collection operated on has an 160 | id and that it will find/update the one item whose unique id matches. 161 | 162 | (reg-update-by-id-event :update-kw :kw) 163 | (reg-update-by-id-event :id :update-kw :kw) 164 | => Both of the above examples assume the :kw collection has items 165 | that all contain the :id identifier 166 | 167 | You would call the event with the new item passed through, e.g. 168 | 169 | (dispatch [:update-kw new-item])" 170 | ([id-fn event-kw kw] 171 | (let [kw (collify kw)] 172 | (reg-event-db 173 | event-kw 174 | (partial add-or-update-by-id-event kw id-fn false)))) 175 | ([event-kw kw] 176 | (reg-update-by-id-event :id event-kw kw))) 177 | 178 | (defn reg-add-or-update-by-id-event 179 | "Registers an update event to the db that performs a conj or replace 180 | depending on whether an item can be found in the collection. Makes 181 | assumptions on being able to the find the id of each item in the 182 | collection. 183 | 184 | (reg-add-or-update-by-id-event :add-or-update-kw :kw) 185 | (reg-add-or-update-by-id-event :id :add-or-update-kw :kw) 186 | => Both of the above examples assume the :kw collection has items 187 | that all contain the :id identifier 188 | 189 | You would call the event with the item passed through, e.g. 190 | 191 | (dispatch [:add-or-update-kw item])" 192 | ([id-fn event-kw kw] 193 | (let [kw (collify kw)] 194 | (reg-event-db 195 | event-kw 196 | (partial add-or-update-by-id-event kw id-fn true)))) 197 | ([event-kw kw] 198 | (reg-add-or-update-by-id-event :id event-kw kw))) 199 | 200 | (defn reg-remove-event 201 | "Registers an update event to the db that performs a remove-when. 202 | 203 | (reg-remove-event :remove-kw :kw) 204 | (reg-remove-event :remove-kw [:kw1 :kw2]) 205 | 206 | Remove-when is a helper function that calls remove on a collection 207 | and is true when any item in the collection equals the item to test 208 | against. 209 | 210 | You would call the event as follows, passing through one item, 211 | the one that you are removing from the collection. 212 | 213 | (dispatch [:remove-kw item]) 214 | 215 | Note: there is no one-arity version of this function." 216 | [event-kw kw] 217 | (let [kw (collify kw)] 218 | (reg-event-db 219 | event-kw 220 | (fn [db [_ item]] 221 | (update-in db kw remove-when item))))) 222 | 223 | ;; AJAX UTILITIES 224 | 225 | (reg-event-db 226 | :reframe-utils/basic-get-success 227 | (fn [db [_ k resp]] 228 | (assoc-in db (collify k) resp))) 229 | 230 | (reg-event-db 231 | :reframe-utils/basic-add-success 232 | (fn [db [_ k resp]] 233 | (update-in db k conj resp))) 234 | 235 | (reg-event-db 236 | :reframe-utils/basic-update-success 237 | (fn [db [_ k id-fn resp]] 238 | (add-or-update-by-id-event k id-fn false db [nil resp]))) 239 | 240 | (reg-event-db 241 | :reframe-utils/basic-delete-success 242 | (fn [db [_ k id-fn resp]] 243 | (update-in db k #(remove (fn [item] (= resp (id-fn item))) %)))) 244 | 245 | (defn- dispatch-or-fn [f] 246 | (cond 247 | (fn? f) #(f %) 248 | (sequential? f) #(dispatch (conj f %)) 249 | :else (error-and-return (str "Invalid arguments passed. Must either be a function or a sequential collection, not " f) 250 | #()))) 251 | 252 | (reg-fx 253 | :reframe-utils/http 254 | (fn [{:keys [method uri on-success handler on-error error-handler] :as params}] 255 | (let [req-fn (case method 256 | :get GET 257 | :head HEAD 258 | :post POST 259 | :put PUT 260 | :delete DELETE 261 | :options OPTIONS 262 | :trace TRACE 263 | :patch PATCH 264 | (throw (js/Error. (str "Unrecognized ajax request method: " method))))] 265 | (req-fn uri 266 | (merge {:handler (dispatch-or-fn (or on-success handler))} 267 | (when (or on-error error-handler) {:error-handler (dispatch-or-fn (or on-error error-handler))}) 268 | (dissoc params :method :uri :on-success)))))) 269 | 270 | (defn- default-db-handler [db _] db) 271 | 272 | (defn reg-ajax-get-event 273 | "Registers an ajax get event that assoc-in the result to the db. 274 | If no get- keyword passed, appends get- to the keyword. 275 | 276 | (reg-ajax-get-event \"/api/request-call\" :data) 277 | (reg-ajax-get-event \"/api/request-call\" :get-data :data)" 278 | ([uri get-event-kw kw & [{:keys [on-success db-fn on-error] 279 | :or {on-success :reframe-utils/basic-get-success 280 | db-fn default-db-handler}}]] 281 | (reg-event-fx 282 | get-event-kw 283 | (fn [{:keys [db]} & [params]] 284 | {:db db 285 | :reframe-utils/http {:method :get 286 | :uri (apply gstring/subs uri (rest params)) 287 | :on-success [:reframe-utils/basic-get-success kw] 288 | :on-error on-error}}))) 289 | ([uri kw] 290 | (reg-ajax-get-event uri (kw-prefix kw "get-") kw))) 291 | 292 | (defn reg-ajax-post-event 293 | "Registers an ajax post event that applies an update-in and conj 294 | of the result to the db" 295 | [uri post-event-kw kw & [{:keys [on-success db-fn on-error] 296 | :or {on-success :reframe-utils/basic-add-success 297 | db-fn default-db-handler}}]] 298 | (reg-event-fx 299 | post-event-kw 300 | (fn [{:keys [db]} [_ params & [uri-strs]]] 301 | {:db db 302 | :reframe-utils/http {:method :post 303 | :uri (apply gstring/subs uri uri-strs) 304 | :params params 305 | :on-success [:reframe-utils/basic-add-success kw] 306 | :on-error on-error}}))) 307 | 308 | (defn reg-ajax-put-event 309 | "Registers an ajax put event that applies an update-in and update 310 | of the result to the db. Assumes there is a unique identifier for 311 | items manipulated here" 312 | [uri put-event-kw kw id-fn & [{:keys [on-success db-fn on-error] 313 | :or {on-success :reframe-utils/basic-update-success 314 | db-fn default-db-handler}}]] 315 | (reg-event-fx 316 | put-event-kw 317 | (fn [{:keys [db]} [_ params & [uri-strs]]] 318 | {:db db 319 | :reframe-utils/http {:method :put 320 | :uri (apply gstring/subs uri uri-strs) 321 | :params params 322 | :on-success [:reframe-utils/basic-update-success kw id-fn] 323 | :on-error on-error}}))) 324 | 325 | (defn reg-ajax-delete-event 326 | [uri event-kw kw id-fn & [{:keys [on-success db-fn on-error] 327 | :or {on-success :reframe-utils/basic-delete-success 328 | db-fn default-db-handler}}]] 329 | (reg-event-fx 330 | event-kw 331 | (fn [{:keys [db]} [_ params & [uri-strs]]] 332 | {:db (db-fn db params) 333 | :reframe-utils/http {:method :delete 334 | :uri (apply gstring/subs uri uri-strs) 335 | :params params 336 | :on-success [on-success kw id-fn] 337 | :on-error on-error}}))) 338 | 339 | 340 | ;; GENERAL UTILITIES 341 | 342 | (defn multi-generation 343 | "Applies a generation function to each parameter passed through, 344 | up to a variable amount. 345 | 346 | (multi-generation reg-basic-sub :cow :wolf :dog :cat) 347 | ; => registers four subscriptions, :get-cow, :get-wolf, :get-dog :get-cat" 348 | [gen-fn & params] 349 | (doseq [p params] 350 | (if (coll? p) 351 | (apply gen-fn p) 352 | (gen-fn p)))) --------------------------------------------------------------------------------