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)
|
||||
|
||||
(defmacro def-views-component
|
||||
(defmacro defvc
|
||||
[component-name args view-sigs & body]
|
||||
`(defn ~component-name ~args
|
||||
(let [gen-view-sigs# (fn ~args ~view-sigs)
|
||||
view-sigs-atom# (atom nil)]
|
||||
(let [gen-view-sigs# (fn ~args ~view-sigs)
|
||||
get-current-view-sigs# (fn [this#]
|
||||
(apply gen-view-sigs# (rest (reagent.core/argv this#))))]
|
||||
(reagent.core/create-class
|
||||
{:component-will-mount
|
||||
(fn [this#]
|
||||
(reset! view-sigs-atom# (apply gen-view-sigs# (rest (reagent.core/argv this#))))
|
||||
(reagent-data-views.client.core/subscribe! (deref view-sigs-atom#)))
|
||||
(let [current-view-sigs# (get-current-view-sigs# this#)]
|
||||
(reagent-data-views.client.component/subscribe! this# current-view-sigs#)))
|
||||
|
||||
:component-will-unmount
|
||||
(fn [this#]
|
||||
(reagent-data-views.client.core/unsubscribe! (deref view-sigs-atom#)))
|
||||
(reagent-data-views.client.component/unsubscribe-all! this#))
|
||||
|
||||
:component-did-update
|
||||
(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
|
||||
(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
|
||||
(:require
|
||||
[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:
|
||||
;; 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 {}))
|
||||
|
||||
; return items in b that don't exist in a
|
||||
(defn- diff [a b]
|
||||
(vec
|
||||
(reduce
|
||||
(fn [item-a item-b]
|
||||
(remove #(= % item-b) item-a))
|
||||
a b)))
|
||||
(defn view-sig-cursor
|
||||
"Returns a Reagent cursor that can be used to access the data for the view
|
||||
corresponding with the view-sig.
|
||||
|
||||
(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
|
||||
"Not intended to be used outside of the def-view-component macro's
|
||||
internal functionality."
|
||||
[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."
|
||||
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-sig]
|
||||
(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]
|
||||
(let [cursor (get-view-sig-cursor view-sig)]
|
||||
(let [cursor (view-sig-cursor view-sig)]
|
||||
(reset! cursor data)))
|
||||
|
||||
(defn- remove-view-data! [view-sig]
|
||||
|
@ -83,7 +47,7 @@
|
|||
(concat existing-data insert-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]
|
||||
(if refresh-set (reset! cursor refresh-set))
|
||||
(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