Skip to content

The sample application

johnbendi edited this page Aug 11, 2012 · 11 revisions

The Sample Application

The sample application should provide enough information to help you get started building your own applications. It is small enough to comprehend in a short time and large enough to demonstrate how to deal with many issues that will come up in a much larger application.

The application presents a form which takes a name and then displays a greeting message. While performing this simple function, it demonstrates several interesting things about building this kind of application in ClojureScript. Some of these things include:

  • Code organization
  • Using events to decouple components
  • The importance of plain Clojure data
  • Sending Clojure data to the server and back
  • Creating highly interactive user interfaces
  • Keeping application code out of the view

Overview

Although there are things named Model, View and Controller in this application it is not exactly MVC as commonly practiced. The paradigm used in this application is a mix of MVC, Functional reactive programming and Dataflow programming.

The backbone of the application is an event dispatching system. Normally, when something happens, we call a function to do something about it. For some key events in the system, it is better to simply report what has happened and not make a decision about how this event should be dealt with. That decision can be made later, without changing the code which reports the event. In a multi-threaded environment we would use a queue for this. In a single-threaded environment we are essentially using a queue which only holds a single element. The event dispatching system implements the ability to report events and to provide functions which will react to events.

There are many advantages to this kind of decoupling but the one disadvantage is that it can be a bit harder to follow the flow of execution (although doing this is probably the wrong way to think about this type of program). The diagram below shows a typical interaction with the sample application and tracks the flow of execution.

events overview

As you can see, the orange squares down the middle of the diagram show the stream of events that are generated as the application runs. Each event contains data that completely describes that event. Instead of hiding this data away in function calls, we expose it. This stream of events is a collection of Clojure data which represents what has happened as the application runs. This data can be the basis of many interesting features. See the one.sample.logging and one.sample.history namespaces for examples of how useful features can be added without modifying any existing code. If we were to record these events (which could be done without modifying existing code) they could be: queried to find bugs, used to replay user actions to reproduce problems or used to test specific components independent of others.

A quick walk-through

To get a better feel for the sample application, let's walk through what happens from the point where the application starts, to the rendering of the initial view.

init event

The first thing that happens when the application starts is the firing of an :init event. This function is located in the one.sample.core namespace.

(defn ^:export start
  []
  (dispatch/fire :init))

The controller (one.sample.controller) responds to :init, :form and :greeting events by calling the corresponding action multimethod.

(dispatch/react-to #{:init :form :greeting}
                   (fn [t d] (action (assoc d :type t))))

When an :init event is fired, the state atom will be reset to contain {:state :init}.

(defmethod action :init [_]
  (reset! state {:state :init}))

The state atom is being watched, and when it changes, the watcher function will fire a :state-change event.

(def state (atom nil))

(add-watch state :state-change-key
           (fn [k r o n]
             (dispatch/fire :state-change n)))

Views react to :state-change events by rendering a view which displays the current state of the application.

(dispatch/react-to #{:state-change} (fn [_ m] (render m)))

When a :state-change event is fired, and the current state is :init, the render function will initialize the user interface.

(defmethod render :init [_]
  (fx/initialize-views (:form snippets) (:greeting snippets))
  (add-input-event-listeners "name-input")
  (fx/disable-button "greet-button")
  (event/listen (by-id "greet-button")
                "click"
                #(dispatch/fire :greeting
                                {:name (value (by-id "name-input"))})))

This view will add the snippets for the form and greeting page to the DOM, configure event listeners for the name input field and disable the submit button. Finally, an event listener is added to the submit button which will fire a :greeting event which contains the name entered in the input field.

The fx alias above refers to the one.sample.animation namespace where all of the animations are defined.

For more details about the sample application, see Model, View and Controller.

Clone this wiki locally