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-routesis invoked by Compojure. This in turn calls#'one.sample.dev-server/make-host-page, passing in the Ring request map. make-host-pageimmediately calls#'one.host-page/application-host, passing in the app configuration map in#'one.sample.config/configand:developmentfor the environment setting.- At this point,
application-hostdoes some Enlive magic, processingtemplates/application.htmland then inserting<script>tags for each value in the:dev-jskey of the config map. This basically loads all the cljs code and then calls#'one.sample.core/startwhich 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
startis called.startis basically the entry point, or "main", for the ClojureScript app that runs on the client. It just fires an:initevent on the dispatch event bus. - The
:initevent is caught by a(react-to)handler registered inone.sample.controller. - The
:initevent handler calls(action :init)which triggers the:initcase of theactionmultimethod. (action :init)resets the#'one.sample.model/stateatom to{:state :init}- Note that in
one.sample.model, a watch is added to thestateatom. So when it's reset, a:state-changeevent 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.viewthere a(react-to)handler registered for the:state-changeevent which simply calls#'one.sample.view/renderwith the new state map. - The
rendermultimethod is dispatched off the:stateentry of the state map. So, in this case the:initcase ofrenderis 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
#formcome from in the URL? Inone.sample.history, another event handler is set up which messes with the browser history. When it sees the:initevent, it sets the history token to:form, which modifies the URL to show#form.
That's it.
Cheers,
Dave