-
Notifications
You must be signed in to change notification settings - Fork 0
View
The file src/app/cljs/one/sample/view.cljs
contains the one.sample.view
namespace which is responsible for rendering views in the sample application.
A "view" is anything that changes the user interface. This could mean manipulating the DOM, changing a CSS class on an element or running an animation.
View rendering actions occur in a reactive way based on changing states over time in the model. Views are only changed when the application state changes. When this happens, Clojure provides both the old and new value. This allows us to easily render different views based on both the current and previous state.
All views react to either a :state-change
or :form-change
event. :state-change
events are fired when the state of the application changes. :form-change
events are fired when the state of the form changes.
There are three functions in one.sample.view
which are responsible for rendering views: render
, render-form-field
and render-button
. Each of these is implemented as a multimethod that dispatches based on some function of the current state.
The easiest of the three functions to understand is render
because it dispatches on the value associated with the :state
key in the application state. To follow along, start a ClojureScript REPL and enter the one.sample.view
namespace.
(in-ns 'one.sample.view)
When the application starts, :state
is set to :init
. A reaction is defined that "reacts" to a :state-change
event by calling the render
function, like so:
(dispatch/react-to #{:state-change} (fn [_ m] (render m)))
;=> {:max-count nil, :event-pred #{:state-change}, :reactor #<function ...>}
From the REPL, we can see what happens when the render function is called with this state.
(render {:state :init})
This view will remove everything from the DOM and then re-add the elements required by the application.
The form for this application is an example of how dynamic a user interface in a JavaScript application can be. The idea behind this form is that it is validated while it is being edited and that the form cannot be submitted until it contains valid data.
The function add-input-event-listeners
, adds listeners to the input field that will fire events to indicate that the field is being edited or that the field's value has changed. The model is listening for these events and will validate the input and update the form state accordingly.
The view reacts to :form-change
events which are fired when the form's state changes. When a :form-change
is received, the data associated with the event contains both the old and new state of the form. This data is transformed before calling the render-form-field
and render-button
functions.
The render-form-field
function receives a map with :transition
and :id
keys and will run the correct animation depending on the state transition which has just occurred.
(render-form-field {:transition [:editing :empty] :id "name-input"})
;=> true
The transition above means that the field is no longer being edited and is empty..
The render-button
function receives a vector of two elements which represents the state transition of the entire form.
(render-button [:editing :finished])
The Vector [:editing :finished]
means that the form has transitioned from being edited to being finished and can be submitted.
Go ahead and try calling the render function with other valid states.
(render {:state :greeting :name "James"})
(render {:state :form})
(render {:state :init})
The first definition in the one.sample.view
namespace looks like this:
(def snippets (snippets/snippets))
We can inspect the contents of snippets from the REPL.
snippets
;=> {:form "<div id=....>" :greeting "<div id=...>"}
It contains a map of keywords to strings of HTML. Were do these strings come from?
snippets
contains the value returned from calling (snippets/snippets)
. This is a call to the snippets
macro in the one.sample.snippets
namespace.
(defn- snippet [file id]
(render (html/select (html/html-resource file) id)))
(defmacro snippets
"Expands to a map of HTML snippets which are extracted from the
design templates."
[]
{:form (snippet "form.html" [:div#form])
:greeting (snippet "greeting.html" [:div#greeting])})
The macro uses Enlive to extract the required HTML from the HTML templates in the templates
directory and return them in a map. Enlive does not work in ClojureScript but does work from Clojure. Macros in ClojureScript are just regular Clojure macros.
Each time the ClojureScript application is compiled, the templates defined in the snippets
function will be pulled into the ClojureScript application. This scenario allows the separation of the development activities from the design activities.
Because of the way reloading is implemented, if you edit a template and then refresh the page, the changes to the template will be visible immediately.