App Structure
A Smaller App¶
For simpler apps, you should put code for each layer into separate files:
src ├── core.cljs <--- entry point, plus history, routing, etc ├── db.cljs <--- schema, validation, etc (data layer) ├── views.cljs <--- reagent views (view layer) ├── events.cljs <--- event handlers (control/update layer) └── subs.cljs <--- subscription handlers (query layer)
For a living example of this approach, look at the todomvc example.
The Gotcha¶
If you adopt this structure, there's a gotcha.
events.cljs
and subs.cljs
will never be required
by any other
namespaces. To the Google Closure dependency mechanism, it appears as
if these two namespaces are not needed and it doesn't load them.
And, if the namespaces are not loaded, the registrations in these namespaces will never happen. And, then you'll be staring at your running app very puzzled about why none of your events handlers are registered.
Once you twig to what's going on, the solution is easy. You must
explicitly require
both namespaces, events
and subs
, in your core
namespace. Then they'll be loaded and the registrations (reg-sub
, reg-event-fx
,
etc) will occur as that loading happens.
Larger Apps¶
Assuming your larger apps have multiple "panels" (or "views") which are relatively independent, you might use this structure:
src ├── core.cljs <--- entry point, plus history, routing, etc ├── panel-1 │ ├── db.cljs <--- schema, validation, etc (data layer) │ ├── subs.cljs <--- subscription handlers (query layer) │ ├── views.cljs <--- reagent components (view layer) │ └── events.cljs <--- event handlers (control/update layer) ├── panel-2 │ ├── db.cljs <--- schema, validation. etc (data layer) │ ├── subs.cljs <--- subscription handlers (query layer) │ ├── views.cljs <--- reagent components (view layer) │ └── events.cljs <--- event handlers (control/update layer) . . └── panel-n
Namespaced Ids¶
As an app gets bigger, you'll tend to get clashes on ids - event-ids, or query-ids (subscriptions), etc.
One panel will need to dispatch
an :edit
event and so will
another, but the two panels will have different handlers.
So, how do you avoid a clash? How do you distinguish between
one :edit
event and another?
Your goal should be to use event-ids which encode both the event
itself (:edit
?) and the context (:panel1
or :panel2
?).
Luckily, ClojureScript provides a nice easy solution: use keywords
with a synthetic namespace. Perhaps something like :panel1/edit
and :panel2/edit
.
You see, ClojureScript allows the namespace in a keyword to be a total
fiction. I can have the keyword :panel1/edit
even though
panel1.cljs
doesn't exist.
Naturally, you'll take advantage of this by using keyword namespaces which are both unique and descriptive.
Navigation¶
How do I switch between different panels of a larger app?
Your app-db
could have an :active-panel
key containing an id for the panel being displayed.
When the user does something navigation-ish (selects a tab, a dropdown or something which changes the active panel), then the associated event and dispatch look like this:
(re-frame/reg-event-db
:set-active-panel
(fn [db [_ value]]
(assoc db :active-panel value)))
(re-frame/dispatch
[:set-active-panel :panel1])
A high level reagent view has a subscription to :active-panel and will switch to the associated panel.
(re-frame/reg-sub
:active-panel
(fn [db _]
(:active-panel db)))
(defn panel1
[]
[:div {:on-click #(re-frame/dispatch [:set-active-panel :panel2])}
"Here" ])
(defn panel2
[]
[:div "There"])
(defn high-level-view
[]
(let [active (re-frame/subscribe [:active-panel])]
(fn []
[:div
[:div.title "Heading"]
(condp = @active ;; or you could look up in a map
:panel1 [panel1]
:panel2 [panel2])])))