And now that I've written this, I see that Brenton already documented pretty much the same thing here. Way it goes. Figuring it out myself is better than reading about it anyway :)
ClojureScript One is pretty nice and very well-documented. Despite that, it still wasn't clear to me exactly how we got from a browser request to stuff on the screen. CS1 is built around an event bus model which makes it beautifully decoupled, but hides the execution flow. Add multimethods to the mix and it's definitely a little confusing for a newcomer.
So, tonight, as a newcomer, I worked through the control flow for the initial (development) request from the browser all the way until stuff is displayed on the screen and ready for input. I found it to be an extremely enlightening exercise. The results are below, hopefully someone will find it useful.
- When the browser requests "/development", the "/development" route from
#'one.sample.dev-server/app-routes
is invoked by Compojure. This in turn calls#'one.sample.dev-server/make-host-page
, passing in the Ring request map. make-host-page
immediately calls#'one.host-page/application-host
, passing in the app configuration map in#'one.sample.config/config
and:development
for the environment setting.- At this point,
application-host
does some Enlive magic, processingtemplates/application.html
and then inserting<script>
tags for each value in the:dev-js
key of the config map. This basically loads all the cljs code and then calls#'one.sample.core/start
which is an exported, i.e. JS-accessible, ClojureScript function.
This is where we make the transition from server-side to client-side code. The line's a little blurry, isn't it?
- So, the page loads and
start
is called.start
is basically the entry point, or "main", for the ClojureScript app that runs on the client. It just fires an:init
event on the dispatch event bus. - The
:init
event is caught by a(react-to)
handler registered inone.sample.controller
. - The
:init
event handler calls(action :init)
which triggers the:init
case of theaction
multimethod. (action :init)
resets the#'one.sample.model/state
atom to{:state :init}
- Note that in
one.sample.model
, a watch is added to thestate
atom. So when it's reset, a:state-change
event is fired, passing the new value of the atom (the state map) as a parameter.
We've gotten all the way down to the model level with controller events. We'll bubble back up to the view with model events now.
- In
one.sample.view
there a(react-to)
handler registered for the:state-change
event which simply calls#'one.sample.view/render
with the new state map. - The
render
multimethod is dispatched off the:state
entry of the state map. So, in this case the:init
case ofrender
is called since the map is just{:state :init}
. (render :init)
initializes the views (causes the form view to slide in) and adds event listeners for UI stuff. At this point the page is initialized and ready for action.- One final thing. Where does
#form
come from in the URL? Inone.sample.history
, another event handler is set up which messes with the browser history. When it sees the:init
event, it sets the history token to:form
, which modifies the URL to show#form
.
That's it.
Cheers,
Dave