view components now maintain their own list of subscribed view-sigs
- includes some related refactorings - getting a view cursor by view name (not sig) now is limited to a view-sig lookup within the list of the component's own subscribed view-sigs instead of the global subscribed view-sig list
This commit is contained in:
parent
7c2350f136
commit
a91876b7da
|
@ -1,23 +1,25 @@
|
||||||
(ns reagent-data-views.client.component)
|
(ns reagent-data-views.client.component)
|
||||||
|
|
||||||
(defmacro def-views-component
|
(defmacro defvc
|
||||||
[component-name args view-sigs & body]
|
[component-name args view-sigs & body]
|
||||||
`(defn ~component-name ~args
|
`(defn ~component-name ~args
|
||||||
(let [gen-view-sigs# (fn ~args ~view-sigs)
|
(let [gen-view-sigs# (fn ~args ~view-sigs)
|
||||||
view-sigs-atom# (atom nil)]
|
get-current-view-sigs# (fn [this#]
|
||||||
|
(apply gen-view-sigs# (rest (reagent.core/argv this#))))]
|
||||||
(reagent.core/create-class
|
(reagent.core/create-class
|
||||||
{:component-will-mount
|
{:component-will-mount
|
||||||
(fn [this#]
|
(fn [this#]
|
||||||
(reset! view-sigs-atom# (apply gen-view-sigs# (rest (reagent.core/argv this#))))
|
(let [current-view-sigs# (get-current-view-sigs# this#)]
|
||||||
(reagent-data-views.client.core/subscribe! (deref view-sigs-atom#)))
|
(reagent-data-views.client.component/subscribe! this# current-view-sigs#)))
|
||||||
|
|
||||||
:component-will-unmount
|
:component-will-unmount
|
||||||
(fn [this#]
|
(fn [this#]
|
||||||
(reagent-data-views.client.core/unsubscribe! (deref view-sigs-atom#)))
|
(reagent-data-views.client.component/unsubscribe-all! this#))
|
||||||
|
|
||||||
:component-did-update
|
:component-did-update
|
||||||
(fn [this# old-argv#]
|
(fn [this# old-argv#]
|
||||||
(reagent-data-views.client.core/update-view-component-sigs this# gen-view-sigs# view-sigs-atom#))
|
(let [new-view-sigs# (get-current-view-sigs# this#)]
|
||||||
|
(reagent-data-views.client.component/update-subscriptions! this# new-view-sigs#)))
|
||||||
|
|
||||||
:component-function
|
:component-function
|
||||||
(fn ~args
|
(fn ~args
|
||||||
|
|
61
src/cljs/reagent_data_views/client/component.cljs
Normal file
61
src/cljs/reagent_data_views/client/component.cljs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
(ns reagent-data-views.client.component
|
||||||
|
(:require
|
||||||
|
[reagent.core :as r]
|
||||||
|
[reagent.impl.util :refer [reagent-component?]]
|
||||||
|
[reagent-data-views.client.core :as views]
|
||||||
|
[reagent-data-views.client.utils :refer [diff update-component-state!]]))
|
||||||
|
|
||||||
|
(defn subscribe!
|
||||||
|
"Subscribes a component to the given view-sigs.
|
||||||
|
NOTE: this function is only intended to be used internally by defvc."
|
||||||
|
[this view-sigs]
|
||||||
|
(assert (reagent-component? this))
|
||||||
|
(r/set-state this {:view-sigs view-sigs})
|
||||||
|
(views/subscribe! view-sigs))
|
||||||
|
|
||||||
|
(defn unsubscribe-all!
|
||||||
|
"Unsubscribes a component from all it's current view subscriptions.
|
||||||
|
NOTE: this function is only intended to be used internally by defvc."
|
||||||
|
[this]
|
||||||
|
(assert (reagent-component? this))
|
||||||
|
(when-let [view-sigs (:view-sigs (r/state this))]
|
||||||
|
(update-component-state! this #(dissoc % :view-sigs))
|
||||||
|
(views/unsubscribe! view-sigs)))
|
||||||
|
|
||||||
|
(defn update-subscriptions!
|
||||||
|
"Updates a component's view subscriptions to match the new full list
|
||||||
|
of view-sigs. Only changed view-sigs will cause a view subscription
|
||||||
|
change to occur.
|
||||||
|
NOTE: this function is only intended to be used internally by defvc."
|
||||||
|
[this new-view-sigs]
|
||||||
|
(assert (reagent-component? this))
|
||||||
|
(let [current-view-sigs (:view-sigs (r/state this))]
|
||||||
|
(when (not= current-view-sigs new-view-sigs)
|
||||||
|
(let [sigs-to-sub (diff new-view-sigs current-view-sigs)
|
||||||
|
sigs-to-unsub (diff current-view-sigs new-view-sigs)]
|
||||||
|
(update-component-state! this #(assoc % :view-sigs new-view-sigs))
|
||||||
|
(views/update-subscriptions! sigs-to-sub sigs-to-unsub)))))
|
||||||
|
|
||||||
|
(defn- get-views-by-name [view-name view-sigs]
|
||||||
|
(filter #(= view-name (first %)) view-sigs))
|
||||||
|
|
||||||
|
(defn view-cursor
|
||||||
|
"Returns a Reagent cursor that can be used to access the data for a view by
|
||||||
|
looking up the corresponding view-sig by name in the current component's
|
||||||
|
list of view subscriptions.
|
||||||
|
|
||||||
|
This function can only be used within the component's render function.
|
||||||
|
|
||||||
|
If there are currently multiple subscriptions to views with the same name
|
||||||
|
(using different arguments) an error is thrown.
|
||||||
|
|
||||||
|
NOTE: This function is intended to be used in a read-only manner. Using
|
||||||
|
this cursor to change the data will *not* propagate to the server or
|
||||||
|
any other clients currently subscribed to this view."
|
||||||
|
[view-name]
|
||||||
|
(assert (not (nil? (r/current-component))))
|
||||||
|
(let [view-sigs (->> (r/current-component) (r/state) :view-sigs)
|
||||||
|
match (get-views-by-name view-name view-sigs)]
|
||||||
|
(if (> (count match) 1)
|
||||||
|
(throw (str "More then one view signature by the name \"" view-name "\" found."))
|
||||||
|
(views/view-sig-cursor (first match)))))
|
|
@ -1,7 +1,8 @@
|
||||||
(ns reagent-data-views.client.core
|
(ns reagent-data-views.client.core
|
||||||
(:require
|
(:require
|
||||||
[reagent.core :as r]
|
[reagent.core :as r]
|
||||||
[clj-browserchannel-messaging.client :as browserchannel]))
|
[clj-browserchannel-messaging.client :as browserchannel]
|
||||||
|
[reagent-data-views.client.utils :refer [diff]]))
|
||||||
|
|
||||||
;; IMPORTANT NOTE:
|
;; IMPORTANT NOTE:
|
||||||
;; We are using Reagent's built-in RCursor instead of the one provided by reagent-cursor
|
;; We are using Reagent's built-in RCursor instead of the one provided by reagent-cursor
|
||||||
|
@ -14,59 +15,22 @@
|
||||||
|
|
||||||
(defonce view-data (r/atom {}))
|
(defonce view-data (r/atom {}))
|
||||||
|
|
||||||
; return items in b that don't exist in a
|
(defn view-sig-cursor
|
||||||
(defn- diff [a b]
|
"Returns a Reagent cursor that can be used to access the data for the view
|
||||||
(vec
|
corresponding with the view-sig.
|
||||||
(reduce
|
|
||||||
(fn [item-a item-b]
|
|
||||||
(remove #(= % item-b) item-a))
|
|
||||||
a b)))
|
|
||||||
|
|
||||||
(declare update-subscriptions!)
|
Generally, for code in a component's render function, you should use
|
||||||
|
reagent-data-views.client.component/view-cursor instead of using this
|
||||||
|
function directly.
|
||||||
|
|
||||||
(defn update-view-component-sigs
|
NOTE: This function is intended to be used in a read-only manner. Using
|
||||||
"Not intended to be used outside of the def-view-component macro's
|
this cursor to change the data will *not* propagate to the server or
|
||||||
internal functionality."
|
any other clients currently subscribed to this view."
|
||||||
[owner view-sig-gen-fn view-sigs-atom]
|
|
||||||
(let [new-args (rest (r/argv owner))
|
|
||||||
old-sigs @view-sigs-atom
|
|
||||||
new-sigs (apply view-sig-gen-fn new-args)]
|
|
||||||
(when (not= old-sigs new-sigs)
|
|
||||||
(let [sigs-to-sub (diff new-sigs old-sigs)
|
|
||||||
sigs-to-unsub (diff old-sigs new-sigs)]
|
|
||||||
(update-subscriptions! sigs-to-sub sigs-to-unsub)
|
|
||||||
(if (not= old-sigs new-sigs)
|
|
||||||
(reset! view-sigs-atom new-sigs))))))
|
|
||||||
|
|
||||||
(defn get-view-sig-cursor
|
|
||||||
"Returns a Reagent cursor that can be used to access the data for this view.
|
|
||||||
NOTE: This is intended to be used in a read-only manner. Using this cursor
|
|
||||||
to change the data will *not* propagate to the server or any other
|
|
||||||
clients currently subscribed to this view."
|
|
||||||
[view-sig]
|
[view-sig]
|
||||||
(r/cursor [view-sig] view-data))
|
(r/cursor [view-sig] view-data))
|
||||||
|
|
||||||
(defn- get-views-by-name [view-name]
|
|
||||||
(filter
|
|
||||||
(fn [[view-sig _]]
|
|
||||||
(= view-name (first view-sig)))
|
|
||||||
@view-data))
|
|
||||||
|
|
||||||
(defn get-view-cursor
|
|
||||||
"Returns a Reagent cursor that can be used to access the data for the view-sig
|
|
||||||
with the specified name. If there is currently multiple subscriptions to views
|
|
||||||
with the same name (but different arguments), this will throw an error.
|
|
||||||
NOTE: This is intended to be used in a read-only manner. Using this cursor
|
|
||||||
to change the data will *not* propagate to the server or any other
|
|
||||||
clients currently subscribed to this view."
|
|
||||||
[view-name]
|
|
||||||
(let [view-sig (get-views-by-name view-name)]
|
|
||||||
(if (> (count view-sig) 1)
|
|
||||||
(throw (str "More then one view signature by the name \"" view-name "\" found."))
|
|
||||||
(get-view-sig-cursor (ffirst view-sig)))))
|
|
||||||
|
|
||||||
(defn- add-initial-view-data! [view-sig data]
|
(defn- add-initial-view-data! [view-sig data]
|
||||||
(let [cursor (get-view-sig-cursor view-sig)]
|
(let [cursor (view-sig-cursor view-sig)]
|
||||||
(reset! cursor data)))
|
(reset! cursor data)))
|
||||||
|
|
||||||
(defn- remove-view-data! [view-sig]
|
(defn- remove-view-data! [view-sig]
|
||||||
|
@ -83,7 +47,7 @@
|
||||||
(concat existing-data insert-deltas))
|
(concat existing-data insert-deltas))
|
||||||
|
|
||||||
(defn- apply-deltas! [view-sig deltas]
|
(defn- apply-deltas! [view-sig deltas]
|
||||||
(let [cursor (get-view-sig-cursor view-sig)]
|
(let [cursor (view-sig-cursor view-sig)]
|
||||||
(doseq [{:keys [refresh-set insert-deltas delete-deltas]} deltas]
|
(doseq [{:keys [refresh-set insert-deltas delete-deltas]} deltas]
|
||||||
(if refresh-set (reset! cursor refresh-set))
|
(if refresh-set (reset! cursor refresh-set))
|
||||||
(if (seq delete-deltas) (swap! cursor apply-delete-deltas delete-deltas))
|
(if (seq delete-deltas) (swap! cursor apply-delete-deltas delete-deltas))
|
||||||
|
|
27
src/cljs/reagent_data_views/client/utils.cljs
Normal file
27
src/cljs/reagent_data_views/client/utils.cljs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
(ns reagent-data-views.client.utils
|
||||||
|
(:require
|
||||||
|
[reagent.core :as r]
|
||||||
|
[reagent.impl.component :as rcomp]
|
||||||
|
[reagent.impl.util :refer [reagent-component?]]))
|
||||||
|
|
||||||
|
(defn diff
|
||||||
|
"Given two vectors a and b, returns a vector that contains only the
|
||||||
|
items from a that do not also exist in b."
|
||||||
|
[a b]
|
||||||
|
(->> b
|
||||||
|
(reduce
|
||||||
|
(fn [item-a item-b]
|
||||||
|
(remove #(= % item-b) item-a))
|
||||||
|
a)
|
||||||
|
(vec)))
|
||||||
|
|
||||||
|
; TODO: relies on internal Reagent functionality. state-atom is not officially
|
||||||
|
; part of the public Reagent API yet, but will probably be part of it in
|
||||||
|
; the future. this function may need to be updated at that time.
|
||||||
|
; https://github.com/reagent-project/reagent/issues/80#issuecomment-67302125
|
||||||
|
(defn update-component-state!
|
||||||
|
"Updates the Reagent component's internal state atom by swap!-ing in the value
|
||||||
|
returned by the function f (which receives the current state atom's value)."
|
||||||
|
[owner f]
|
||||||
|
(assert (reagent-component? owner))
|
||||||
|
(swap! (rcomp/state-atom owner) f))
|
Loading…
Reference in a new issue