My approach to this stuff is to think in terms of state and derived state.
I have a few helpers to facilitate implementing that which amounts to
- Use an atom for state
- Use a function to derive state
- Use a simple cache to reduce load of derivation in render loop
- Hack deref to make atom pass back derived-state
If you're new to OM I'd encourage you not to get carried away with picking
up dodgy bits of code like the ones I'm sharing here until you're confident
you're making best use of OM as it comes out of the box.
That being said...
Here's my derived-atom! function.
(defn derived-atom!
"
This allows us to access derived values associated with atom state.
The implementation of atom does not use deref so we aren't interfering
with how state transitions, they use .-state to access the pure attribute
value.
The implementation of om ref cursors use deref to resolve the current
value
of state so we are able to apply our logic and have the result available
via (value c) which means reference cursors will trigger updates based
on changes to derived data too.
"
[iref derived-fn]
(specify! iref
IDeref (-deref [_] (derived-fn (.-state iref))))
iref)
This is how i used it as my om app-state
(defonce app-state (derived-atom! (atom {}) (memoize-last derived-state)))
memoize-last is a cutdown version of memoize which just remembers the last
version. That essentially means that we only calculate derived-state once
for each OM state
(defn memoize-last
"Returns a memoized version of a referentially transparent function.
The memoized version of the function keeps a cache of the *most recent
call*
from arguments to result.
"
[f]
(let [mem (atom {})
lookup-sentinel (js-obj)]
(fn [& args]
(let [v (get @mem args lookup-sentinel)]
(if (identical? v lookup-sentinel)
(let [ret (apply f args)]
(reset! mem {args ret})
ret)
v)))))
Finally, here's me using derived state logic to do things like lookups,
validation and other things. It's all very app specific but amounts to
doing (assoc-in state [:a :b] :something) a lot. At this risk of confusing
things here's my generalised required field validation logic which is
implemented as derived state.
(def empty-values #{nil "" [] {} #{}})
(defn validate-required-field [field]
(let [{:keys [required value errors]} field]
(if (and required (contains? empty-values value))
(assoc field :errors (conj errors "This field is required"))
field)))
(defn is-required-field?
"Identifies walker nodes which are fields relevant to require logic"
[m]
(and (map? m)
(contains? m :required)
(contains? m :value)))
(defn validate-required-fields
"Derive errors associated with missing required fields"
[state]
(postwalk
#(if (is-required-field? %) (validate-required-field %) %)
state))
(defn derived-state
"Used to include derived state for use by components."
[state] (-> state
;derive-vertical-required
;end-position-logic
;maint-freq-logic
validate-required-fields))
On Mon, Feb 16, 2015 at 4:17 AM, Leon Grapenthin <[email protected]>
wrote:
> If they were changing, you could implement your own reference system,
> where you have one stateful lookup map that you make globally available via
> a ref cursor. Instead of the actual changing values, you'd use lookup keys
> in your rendered cursors. Changes to values in the stateful lookup map
> would would reflect everywhere they are looked up.
>
> On Sunday, February 15, 2015 at 3:54:36 PM UTC+1, Scott Nelson wrote:
> > I’m working on an Om application that includes a music browser and
> player with a simple artist -> albums -> songs hierarchy. When a user is
> viewing an artist section I layout all the artist’s albums and songs
> hierarchically and this works great since there is basically a 1-to-1
> correspondence between the component hierarchy and the data hierarchy.
> >
> > Here’s a basic example of what a piece of the application state might
> look like under some sort of :current-artist key:
> >
> > {:name “The Beatles”
> > :albums [{:name “Abbey Road”,
> > :songs [{:name “Come Together”}
> > {:name “Something”}]}
> > {:name "Rubber Soul",
> > :songs [{:name "Drive My Car"}
> > {:name "Norwegian Wood"}]}]}
> >
> > When a user plays one of these songs I build an ordered playlist of all
> the artist’s songs. There is a player component that plays the playlist
> (even if the user navigates away from the current artist section) and
> displays the currently playing artist, album and song name. To achieve
> this I ended up duplicating a bunch of artist/album/song data elsewhere in
> the application state.
> >
> > Here’s what the :playlist path of the application state might look like
> after a user clicked the play button next to “Drive My Car”:
> >
> > {:play-index 2
> > :songs [{:name “Come Together”
> > :album {:name “Abbey Road”
> > :artist {:name "The Beatles"}}}
> > {:name “Something”
> > :album {:name “Abbey Road”
> > :artist {:name "The Beatles"}}}
> > {:name “Drive My Car”
> > :album {:name “Rubber Soul”
> > :artist {:name "The Beatles"}}}
> > {:name “Norwegian Wood”
> > :album {:name "Rubber Soul"
> > :artist {:name "The Beatles"}}}]}
> >
> > I think this sort of denormalization works fine in my case since the
> artist/album/song data is not changing but I'm wondering if there is a
> better way to accomplish this. If the data were changing then I would
> imagine it could become a challenge to keep the playlist data in sync.
>
> --
> Note that posts from new members are moderated - please be patient with
> your first post.
> ---
> You received this message because you are subscribed to the Google Groups
> "ClojureScript" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To post to this group, send email to [email protected].
> Visit this group at http://groups.google.com/group/clojurescript.
>
--
Oliver George
Director, Condense
0428 740 978
--
Note that posts from new members are moderated - please be patient with your
first post.
---
You received this message because you are subscribed to the Google Groups
"ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/clojurescript.