Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
ClojureScript One Execution Flow

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, processing templates/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 in one.sample.controller.
  • The :init event handler calls (action :init) which triggers the :init case of the action multimethod.
  • (action :init) resets the #'one.sample.model/state atom to {:state :init}
  • Note that in one.sample.model, a watch is added to the state 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 of render 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? In one.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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment