|
24 | 24 |
|
25 | 25 | (set! *warn-on-infer* false) |
26 | 26 |
|
27 | | -(defn dispatch-up! [{::comp/keys [^not-native parent] :as env} ev-map] |
| 27 | +(defn dispatch-up! |
| 28 | + "Use within a component event handler to propagate the event `ev-map` up the |
| 29 | + component tree. `env` is the environment map available in event handlers." |
| 30 | + [{::comp/keys [^not-native parent] :as env} ev-map] |
28 | 31 | {:pre [(map? env) |
29 | 32 | (map? ev-map) |
30 | 33 | (qualified-keyword? (:e ev-map))]} |
31 | 34 | ;; FIXME: should schedule properly when it isn't in event handler already |
32 | 35 | (gp/handle-event! parent ev-map nil env)) |
33 | 36 |
|
34 | 37 | (defn query-ident |
| 38 | + "Queries the db starting from `ident`. |
| 39 | + * `query` - Optional. EQL query. Defaults to ident lookup if not provided. |
| 40 | + * `config` - Optional. Any kind of config that may come up. |
| 41 | + Changes to idents accessed by the query (including inside `eql/attr`) during |
| 42 | + transactions will cause the query to re-un. |
| 43 | + --- |
| 44 | + Example |
| 45 | + ```clojure |
| 46 | + (defmethod eql/attr ::contains |
| 47 | + [env db {:dir/keys [files dirs] :as current} query-part params] |
| 48 | + (cond->> (concat dirs files) |
| 49 | + (not (::show-hidden? db)) |
| 50 | + (filterv (fn [ident] (not (::hidden? (get db ident))))))) |
| 51 | + (bind {:as query-result |
| 52 | + :dir/keys [name open?] |
| 53 | + ::keys [contains]} |
| 54 | + (sg/query-ident ident [:dir/name :dir/open? ::contains])) |
| 55 | + ```" |
35 | 56 | ;; shortcut for ident lookups that can skip EQL queries |
36 | 57 | ([ident] |
37 | 58 | (impl/hook-query ident nil {})) |
|
42 | 63 | (impl/hook-query ident query config))) |
43 | 64 |
|
44 | 65 | (defn query-root |
| 66 | + "Queries from the root of the db. |
| 67 | + * `query` - EQL query. |
| 68 | + * `config` - Optional. Any kind of config that may come up. |
| 69 | + Changes to idents accessed by the query (including inside `eql/attr`) during |
| 70 | + transactions will cause the query to re-un. |
| 71 | + --- |
| 72 | + Example |
| 73 | + ```clojure |
| 74 | + (defmethod eql/attr :products-in-stock [env db _ _] |
| 75 | + (->> (db/all-of :product) |
| 76 | + (filter #(pos? (:stock %))) |
| 77 | + (mapv :db/ident))) |
| 78 | + (defc ui-homepage [] |
| 79 | + (bind {:keys [products-in-stock a-root-key]} |
| 80 | + (sg/query-root [:products-in-stock :a-root-key])) |
| 81 | + ```" |
45 | 82 | ([query] |
46 | 83 | (impl/hook-query nil query {})) |
47 | 84 | ([query config] |
48 | 85 | (impl/hook-query nil query config))) |
49 | 86 |
|
50 | 87 | (defn run-tx |
| 88 | + "Use inside a component event handler. Runs transaction `tx`, e.g. |
| 89 | + `{:e ::some-event :data ...}`. `env` is the component environment map |
| 90 | + available in event handlers. |
| 91 | + --- |
| 92 | + Example |
| 93 | + ```clojure |
| 94 | + (event :hide! [env ev-map event] |
| 95 | + (when (.-ctrlKey event) |
| 96 | + (sg/run-tx env ev-map))) |
| 97 | + ```" |
51 | 98 | [{::rt/keys [runtime-ref] :as env} tx] |
52 | 99 | (impl/process-event runtime-ref tx env)) |
53 | 100 |
|
54 | | -(defn run-tx! [runtime-ref tx] |
| 101 | +(defn run-tx! |
| 102 | + "Runs the transaction `tx`, e.g. `{:e ::some-event :data ...}`, outside of the |
| 103 | + component context." |
| 104 | + [runtime-ref tx] |
55 | 105 | (assert (rt/ref? runtime-ref) "expected runtime ref?") |
56 | 106 | (let [{::rt/keys [scheduler]} @runtime-ref] |
57 | 107 | (gp/run-now! scheduler #(impl/process-event runtime-ref tx nil) ::run-tx!))) |
|
63 | 113 | (js-delete root-el "sg$env"))) |
64 | 114 |
|
65 | 115 | (defn watch |
66 | | - "hook that watches an atom and triggers an update on change |
67 | | - accepts an optional path-or-fn arg that can be used for quick diffs |
| 116 | + "Hook that watches `the-atom` and updates when the atom's value changes. |
68 | 117 |
|
| 118 | + Accepts an optional `path-or-fn` arg that can be used to 'watch' a portion of |
| 119 | + `the-atom`, enabling quick diffs. |
| 120 | + * 'path' – as in `(get-in @the-atom path)` |
| 121 | + * 'fn' - similar to above, defines how to access the relevant parts of |
| 122 | + `the-atom`. Takes [old-state new-state] of `the-atom` and returns the actual |
| 123 | + value stored in the hook. Example: `(fn [_ new] (get-in new [:id :name]))`. |
| 124 | +
|
| 125 | + **Use strongly discouraged** in favor of the normalized central db. |
| 126 | +
|
| 127 | + --- |
| 128 | + Examples |
| 129 | + ```clojure |
69 | 130 | (watch the-atom [:foo]) |
70 | | - (watch the-atom (fn [old new] ...))" |
| 131 | + (watch the-atom (fn [old new] ...)) |
| 132 | + ```" |
71 | 133 | ([the-atom] |
72 | 134 | (watch the-atom (fn [old new] new))) |
73 | 135 | ([the-atom path-or-fn] |
|
76 | 138 | (atoms/AtomWatch. the-atom path-or-fn nil nil)))) |
77 | 139 |
|
78 | 140 | (defn env-watch |
| 141 | + "Similar to [[watch]], but for atoms inside the component env." |
79 | 142 | ([key-to-atom] |
80 | 143 | (env-watch key-to-atom [] nil)) |
81 | 144 | ([key-to-atom path] |
|
85 | 148 | (vector? path)]} |
86 | 149 | (atoms/EnvWatch. key-to-atom path default nil nil nil))) |
87 | 150 |
|
88 | | -(defn suspense [opts vnode] |
| 151 | +(defn suspense |
| 152 | + "See [docs](https://github.com/thheller/shadow-experiments/blob/master/doc/async.md)." |
| 153 | + [opts vnode] |
89 | 154 | (suspense/SuspenseInit. opts vnode)) |
90 | 155 |
|
91 | | -(defn simple-seq [coll render-fn] |
| 156 | +(defn simple-seq |
| 157 | + "Creates a collection of DOM elements by applying `render-fn` to each item |
| 158 | + in `coll`. `render-fn` can be a function or component. |
| 159 | +
|
| 160 | + Makes no attempts to minimize DOM operations required for updates. Efficient |
| 161 | + with colls which change infrequently or colls updated at the tail. Otherwise, |
| 162 | + consider using [[keyed-seq]]. |
| 163 | + --- |
| 164 | + Example: |
| 165 | + ```clojure |
| 166 | + (sg/simple-seq |
| 167 | + (range 5) |
| 168 | + (fn [num] |
| 169 | + (<< [:div \"inline-item: \" num]))) |
| 170 | + ```" |
| 171 | + [coll render-fn] |
92 | 172 | (sc/simple-seq coll render-fn)) |
93 | 173 |
|
94 | | -(defn keyed-seq [coll key-fn render-fn] |
| 174 | +(defn keyed-seq |
| 175 | + "Creates a keyed collection of DOM elements by applying `render-fn` to each |
| 176 | + item in `coll`. |
| 177 | + * `key-fn` is used to extract a unique key from items in `coll`. |
| 178 | + * `render-fn` can be a function or component. |
| 179 | + Uses the key to minimize DOM updates. Consider using instead of (the more |
| 180 | + lightweight) [[simple-seq]] when `coll` changes frequently. |
| 181 | +
|
| 182 | + --- |
| 183 | + Examples: |
| 184 | + ```clojure |
| 185 | + ;; ident used as key |
| 186 | + (keyed-seq [[::ident 1] ...] identity component) |
| 187 | + (keyed-seq [{:id 1 :data ...} ...] :id |
| 188 | + (fn [item] (<< [:div.id (:data item) ...]))) |
| 189 | + ```" |
| 190 | + [coll key-fn render-fn] |
95 | 191 | (sc/keyed-seq coll key-fn render-fn)) |
96 | 192 |
|
97 | 193 | (deftype TrackChange |
|
132 | 228 | (volatile! nil)) |
133 | 229 |
|
134 | 230 | (defn effect |
135 | | - "calls (callback env) after render when provided deps argument changes |
136 | | - callback can return a function which will be called if cleanup is required" |
| 231 | + "Calls `(callback env)` after render when `deps` changes. (*Note*: will be |
| 232 | + called on mount too.) `callback` may return a cleanup function which is |
| 233 | + called on component unmount *and* just before whenever callback would be |
| 234 | + called." |
137 | 235 | [deps callback] |
138 | 236 | {:pre [(fn? callback)]} |
139 | 237 | (comp/EffectHook. deps callback nil true nil)) |
140 | 238 |
|
141 | 239 | (defn render-effect |
142 | | - "call (callback env) after every render" |
| 240 | + "Calls `(callback env)` after every render. `callback` may return a cleanup |
| 241 | + function which is called on component unmount *and* after each render before |
| 242 | + callback." |
143 | 243 | [callback] |
144 | 244 | {:pre [(fn? callback)]} |
145 | 245 | (comp/EffectHook. :render callback nil true nil)) |
146 | 246 |
|
147 | 247 | (defn mount-effect |
148 | | - "call (callback env) on mount once" |
| 248 | + "Calls `(callback env)` on mount. `callback` may return a cleanup function |
| 249 | + which is called on unmount." |
149 | 250 | [callback] |
150 | 251 | {:pre [(fn? callback)]} |
151 | 252 | (comp/EffectHook. :mount callback nil true nil)) |
|
220 | 321 | (set! (.-sg$env root-el) new-env) |
221 | 322 | ::started))) |
222 | 323 |
|
223 | | -(defn render [rt-ref ^js root-el root-node] |
| 324 | +(defn render |
| 325 | + "Renders the UI root. Call on init and `^:dev/after-load`. |
| 326 | + * `rt-ref` – runtime atom |
| 327 | + * `root-el` – DOM element, e.g. `(js/document.getElementById \"app\")`. |
| 328 | + * `root-node` – root fn/component (e.g. defined with `defc`)." |
| 329 | + [rt-ref ^js root-el root-node] |
224 | 330 | {:pre [(rt/ref? rt-ref)]} |
225 | 331 | (gp/run-now! ^not-native (::rt/scheduler @rt-ref) #(render* rt-ref root-el root-node) ::render)) |
226 | 332 |
|
|
320 | 426 | (goog-define TRACE false) |
321 | 427 |
|
322 | 428 | (defn prepare |
| 429 | + "Initialises the runtime atom. |
| 430 | + * `init` – Optional. A map. |
| 431 | + * `data-ref` – Ref to the grove db atom. |
| 432 | + * `runtime-id` |
| 433 | +
|
| 434 | + --- |
| 435 | + Example: |
| 436 | +
|
| 437 | + ```clojure |
| 438 | + (defonce rt-ref |
| 439 | + (-> {::rt/tx-reporter (fn [report] (tap> report))} |
| 440 | + (rt/prepare data-ref ::my-rt))) |
| 441 | + ```" |
323 | 442 | ([data-ref runtime-id] |
324 | 443 | (prepare {} data-ref runtime-id)) |
325 | 444 | ([init data-ref runtime-id] |
|
357 | 476 | (defn vec-conj [x y] |
358 | 477 | (if (nil? x) [y] (conj x y))) |
359 | 478 |
|
360 | | -(defn queue-fx [env fx-id fx-val] |
| 479 | +(defn queue-fx |
| 480 | + "Used inside an event handler, it queues up the registered handler of `fx-id` |
| 481 | + to run at the end of the transaction. The handler will be called with |
| 482 | + `fx-val`. |
| 483 | + --- |
| 484 | + Example: |
| 485 | + ```clojure |
| 486 | + (sg/reg-event rt-ref ::toggle-show-hidden! |
| 487 | + (fn [tx-env {:keys [show?] :as event}] |
| 488 | + (-> tx-env |
| 489 | + (assoc-in [:db ::show-hidden?] show?) ;; modify the db |
| 490 | + (sg/queue-fx ::alert! event)))) ;; schedule an fx |
| 491 | + (sg/reg-fx rt-ref ::alert! |
| 492 | + (fn [fx-env {:keys [show?] :as fx-val}] |
| 493 | + (js/alert (str \"Will \" (when-not show? \"not\") \" show hidden files.\")))) |
| 494 | + ```" |
| 495 | + [env fx-id fx-val] |
361 | 496 | (update env ::rt/fx vec-conj [fx-id fx-val])) |
362 | 497 |
|
363 | | -(defn reg-event [rt-ref ev-id handler-fn] |
| 498 | +(defn reg-event |
| 499 | + "Registers the `handler-fn` for event `ev-id`. `handler-fn` will be called |
| 500 | + with `{:as tx-env :keys [db]} event-map` and should return the modified |
| 501 | + `tx-env`. |
| 502 | +
|
| 503 | + There is an alternative approach to registering event handlers, see examples. |
| 504 | +
|
| 505 | + --- |
| 506 | + Example: |
| 507 | + ```clojure |
| 508 | + (sg/reg-event rt-ref ::complete! |
| 509 | + (fn [tx-env {:keys [checked ident] :as ev}] |
| 510 | + (assoc-in tx-env [:db ident :completed?] checked))) |
| 511 | +
|
| 512 | + ;; metadata approach |
| 513 | + (defn complete! {::ev/handle ::complete!} |
| 514 | + [tx-env {:keys [checked ident] :as ev}] |
| 515 | + (assoc-in tx-env [:db ident :completed?] checked)) |
| 516 | +
|
| 517 | + ;; use `{:dev/always true}` in namespaces utilising the metadata approach. |
| 518 | + ```" |
| 519 | + [rt-ref ev-id handler-fn] |
| 520 | + |
364 | 521 | {:pre [(keyword? ev-id) |
365 | 522 | (ifn? handler-fn)]} |
366 | 523 | (swap! rt-ref assoc-in [::rt/event-config ev-id] handler-fn) |
367 | 524 | rt-ref) |
368 | 525 |
|
369 | | -(defn reg-fx [rt-ref fx-id handler-fn] |
| 526 | +(defn reg-fx |
| 527 | + "Registers the `handler-fn` for fx `fx-id`. fx is used for side effects, so |
| 528 | + `handler-fn` shouldn't modify the db. |
| 529 | +
|
| 530 | + --- |
| 531 | + Examples: |
| 532 | + ```clojure |
| 533 | + (sg/reg-event rt-ref ::toggle-show-hidden! |
| 534 | + (fn [tx-env {:keys [show?] :as event}] |
| 535 | + (-> tx-env |
| 536 | + (assoc-in [:db ::show-hidden?] show?) ;; modify the db |
| 537 | + (sg/queue-fx ::alert! event)))) ;; schedule an fx |
| 538 | +
|
| 539 | + (sg/reg-fx rt-ref ::alert! |
| 540 | + (fn [fx-env {:keys [show?] :as fx-data}] |
| 541 | + (js/alert (str \"Will \" (when-not show? \"not\") \" show hidden files.\")))) |
| 542 | + ``` |
| 543 | +
|
| 544 | + `(:transact! fx-env)` allows fx to schedule another transaction, but it |
| 545 | + should be an async call: |
| 546 | + ```clojure |
| 547 | + (sg/reg-fx rt-ref :ui/redirect! |
| 548 | + (fn [{:keys [transact!] :as env} {:keys [token title]}] |
| 549 | + (let [tokens (str/split (subs token 1) #\"/\")] |
| 550 | + ;; forcing the transaction to be async |
| 551 | + (js/setTimeout #(transact! {:e :ui/route! :token token :tokens tokens}) 0)))) |
| 552 | + ```" |
| 553 | + [rt-ref fx-id handler-fn] |
370 | 554 | (swap! rt-ref assoc-in [::rt/fx-config fx-id] handler-fn) |
371 | 555 | rt-ref) |
0 commit comments