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:
Gered 2014-12-26 22:39:37 -05:00
parent 7c2350f136
commit a91876b7da
4 changed files with 110 additions and 56 deletions

View file

@ -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

View 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)))))

View file

@ -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))

View 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))