Chapter 5
Reactive Web Pages in ClojureScript

If you aren't using ClojureScript to make web pages, you are missing out. This style of programming is fast, fun, simple, and rewarding. And when I say fast, I mean really fast. It is a quantum leap from any other style of programming. You really have to experience it to appreciate it.

In this chapter you create a project, build a page, and deploy it. You build a project management tracking web page where users can create stories, change status, and track progress of their project. Then the chapter covers the common libraries used to leverage ClojureScript as a platform for developing useful solutions.

First take a broader look at what ClojureScript has to offer.

CLOJURESCRIPT IS A BIG DEAL

Interest in ClojureScript among web developers is growing strong. The two most watched presentations from the 2015 Conj conference are about ClojureScript. There is good reason for this attention: ClojureScript is eclipsing the current alternatives for building web applications.

ClojureScript is a perfect fit for building reactive web pages. The language idioms are aligned with the philosophy of the successful React framework from Facebook, which is proving to be a winning approach to building rich and robust interfaces. The React philosophy is that there should be a central app-state, functions that render from that state to HTML elements, and a process that automatically propagates change to the browser. Change propagation occurs by calculating the difference between the results of rendering with what is currently on the page. React determines the minimal set of updates required to transition to the new view state and applies those updates. Reactive web pages provide view model separation without the complexity of having to enumerate transitional behaviors. The web pages are fast to build and avoid common errors associated with state management and callback dependencies.

Reactive pages are unlike JQuery style pages. JQuery style pages update elements directly in response to events. You can write ClojureScript in JQuery style, but you will not be getting nearly as much leverage. Having the leverage of a reactive view/model relationship opens new opportunities for creating interactive experiences for your users. When you embrace the React paradigm you get the most out of your code. In the arena of single-page applications, ClojureScript React style dominates Angular, Ember, Backbone, and React because of the expressive power gained by combining Clojure with HTML.

The interactive development story with ClojureScript is especially compelling. Code is reloaded into the browser as soon as you save the file you are working on. This is better than refreshing the browser. Instead of your application being completely reset, you maintain state throughout the development session. This allows better focus on the part that you are currently working on. You no longer need to click through the application to check the change you just made. Even better, you can set up a test page that displays your components bound to many different states, which is perfect for verifying your interface without needing to click around. The combination of fast feedback and multistate testing facilitates an interactive path for development.

ClojureScript has a simple, stable syntax consisting of only data literals, function application, destructuring, special forms, and macros. Expressions are concise, abstractions are functional, and values are immutable.

ClojureScript compiles to JavaScript, has convenient interop capabilities, and you can use existing JavaScript libraries with little ceremony. You have the full reach of JavaScript and can draw upon its vast and rich ecosystem.

The ClojureScript compiler leverages the Google Closure Compiler and Library, which provides convenient access to a high-quality, full-featured, and well-maintained platform. Google Closure handles many recurring concerns well, and it does so in a cross-browser-compatible way. You rarely need to concern yourself about cross-browser behavior when writing ClojureScript, which is fantastic.

When you're developing, source maps link from the minified JavaScript back to the ClojureScript source code. Modern browsers support source maps, so debugging is obvious. From reading the developer console you can see where the error is in the original ClojureScript source code rather than in the compiled JavaScript.

In addition to having access to JavaScript libraries, you also gain access to many Clojure libraries. ClojureScript is not compatible with Clojure at the platform level, but it is compatible at the language level. Hence, many libraries are available in both Clojure and ClojureScript as the same code base, with any platform-specific differences handled by reader conditionals. For example, you can use Instaparse from both Clojure and ClojureScript.

Clojure and ClojureScript have a strong community and ecosystem. There are many simple and powerful libraries to choose from, many tools to help your workflow, and plenty of blogs with helpful tips. StackOverflow answers are quickly answered. The IRC and Slack channels are well populated with helpful and friendly denizens. Furthermore, an increasing number of employers are adopting Clojure/ClojureScript stacks.

One particularly intriguing capability inherited from the Clojure world is the ability to represent communicating sequential processes using the core.async library. Wherever you have a callback, you can put the result on a channel and write your logic as a sequential process consuming from the channel. This style for coordinating asynchronous actions was popularized by the Go language, and it's now possible in the browser.

Defining modules is easily achieved by specifying a namespace to target to an output file. The ClojureScript compiler automatically moves remaining code to a shared module. The Google Closure Compiler ensures the modules get only the code they need. Thus, for very large projects you are able to break up the artifacts as you see fit to provide the best load times for your users.

A typical ClojureScript application is smaller than a typical JavaScript application because of the code shrinking done by advanced compilation. Dead code is removed; live code is minified. Performance is great. The result is efficient code. In JavaScript you have to deal with a quagmire of build tools such as grunt, gulp, node package manager, yeoman, babel, bower, and brunch. The good news is that you need only one easily installed build tool for ClojureScript: Leiningen. You need to install Java, but you never have to directly interact with Java.

ClojureScript has been evolving rapidly over the past several years. Now the core tooling has stabilized to provide a production-ready platform for developing reactive web pages. Meanwhile new libraries continue to push the boundaries of what is possible. This is a great time to be adopting ClojureScript. The JavaScript equivalents require learning and being locked into complex frameworks, whereas ClojureScript libraries are small, simple, and unentangled. ClojureScript is a better way to make interactive interfaces. The idioms, libraries, and tooling are robust.

You are one command line away from a fast and fun coding experience!

A FIRST BRUSH WITH CLOJURESCRIPT

It's time to dive in and build a reactive website. The goal is to write a solution for tracking the status of tasks and assigning people to work on them. You need Java, Leiningen, and your favorite text editor. (We used Java 1.8.0_25 and Leinigen 2.5.3.) If you are new to ClojureScript, stick with your existing code editor. It likely has syntax highlighting, which is all you really need. Don't get hung up on editor integrations or learning a new IDE and key combinations; stick with an editor that is comfortable and familiar. You don't need a REPL or anything fancy. Fast feedback comes directly from the browser itself.

Starting a New ClojureScript Project

Open a terminal and type lein new figwheel whip -- --reagent. Go into the whip project directory cd whip and examine the files created with the tree shown in Figure 5.1.

Schematic of a tree showing a readme file, a project file, a style sheet, index.html and core.cljs source file.

Figure 5.1

You see a readme file, a project file, a style sheet, index.html, and core.cljs source file. Type lein figwheel to start the process that builds and notifies the browser of file changes. Position your browser on the left and navigate to http://localhost:3449. Wait until you see the Successfully Compiled message in the terminal and then reload the browser. You see the Hello World! page shown in Figure 5.2, which indicates that everything is working so far.

Snapshot of Hello World! Page.

Figure 5.2

Getting Fast Feedback with Figwheel

Open the dev console in your browser. Open the core.cljs file from the source directory under whip. Position your text editor on the right so you can see the code and your page side by side. Edit the print string on line 6 of the source code to write a new message. The browser console logs the output immediately (see Figure 5.3).

Snapshot of browser console showing an output.

Figure 5.3

The current state of your application is kept inside a ratom called app-state. This is a Reagent atom, which causes your web page to be re-rendered whenever the app-state changes.

Note that defonce does nothing when evaluated if the var is already defined. So app-state is not re-created on reload. It can only be updated by either code execution or refreshing the browser. Refreshing the browser clears everything.

The on-js-reload function is called whenever new code is loaded from any namespace. You can remove it. Instead, annotate whip.core with ∧:figwheel-always, which causes this namespace to be reloaded when any change is detected, not necessarily in this file. Forcing the main namespace to reload on any change effectively re-renders the interface.

(ns ∧:figwheel-always whip.core
  (:require [reagent.core :as reagent :refer [atom]]))

When we created the project with the lein command, we used the figwheel template. All it did was include the figwheel plug-in in our project definition and enable figwheel in the default build. Figwheel works by establishing a websocket from your page to the build process. When any source or resource files change, the build process detects them. If they are source changes, recompilation occurs, and the new JavaScript code is loaded. If they are resource changes such as CSS, they are also reloaded. One of the great things about figwheel is that you really don't need to know very much about it at all! So long as it is configured in your project, your code and resources load in the browser as you change them.

Creating Components

Reagent components are functions that return HTML in hiccup form. Hiccup looks quite similar to HTML, but it's more compact. Hiccup forms are vectors where the first element is the tag, which may be followed by an optional map of attributes and then any children, which may be hiccup forms or values.

Our generated core.clj contains an example because hello-world is a function that returns a vector representing an <h1> tag containing some text taken from the app-state:

(defn hello-world []
  [:h1 (:text @app-state)])

Edit the example to a complete example of what hiccup can look like:

[:h1
  {:id "stories"
   :class "stories main"
   :style {:font-family "cursive"}}
  "Whip project management tool"]

Here you can see several attributes have been set: the ID, class, and style. As these are common attributes, there is a shorthand way for adding them that expands to the same HTML elements.

[:h1#stories.stories.main
  {:style {:font-family "cursive"}}
  "Whip project management tool"]

You see that the tag can be suffixed with an optional single ID preceded by a hash and any number of classes preceded by periods. If an ID is specified it must come as the first suffix. Unlike jQuery where IDs are commonly used to find and modify elements, React style components rarely need an ID because they are functions of state. Note that styles expand according to CSS syntax. So the example expands to

<h1 id="stories" class="stories main" style="font-family:cursive">
  Whip project management tool
</h1>

In your browser, right-click the text and select Inspect Element. The code has produced DOM elements, just like normal HTML.

Can you express the hiccup syntax equivalent to draw the circle on your page? https://developer.mozilla.org/en-US/docs/Web/SVG/Element has a reference of shapes you might like to use. Making games and visualizations is very convenient because you can build up SVG elements with conditional code.

Modeling the Data

Now you have the basics in place to render your page. How are you going to model the project management tracking solution? Before you worry too much about how to organize the code for the project, instead focus on what data your application will be dealing with. A project will consist of stories. Stories have a status, which will be visualized as a lane. Users can be assigned to stories. Stories can be moved between lanes to indicate status changes (see Figure 5.4).

Illustration depicting project that consists of stories.

Figure 5.4

You need to be able to add stories to your app-state. It's useful to have some example data to work with, so create some data. There are many options available for modeling data. We suggest that you work with nested maps where possible.

 (defonce app-state
  (reagent/atom
    {:projects
     {"aaa"
      {:title "Build Whip"
       :stories
       {1 {:title "Design a data model for projects and stories"
           :status "done"
           :order 1}
        2 {:title "Create a story title entry form"
           :order 2}
        3 {:title "Implement a way to finish stories"
           :order 3}}}}}))

You need to reload the browser for this change to take effect because of the defonce. Take some time to think about why this is the case. It is important to be mindful of how code reloading interacts with existing data. The code reloading model is more powerful than simply refreshing the page. Code reloading causes execution without re-creating the entire app-state. When you want to re-create the entire app-state, reloading the page is the way to do it.

With this data in place you can spend some time drawing a basic list of the stories that are in the project. Create a project-board component that can be an unordered list of stories:

(defn project-board [app-state project-id]
  (into [:ul]
   (for [[id {:keys [title]}] (get-in @app-state [:projects project-id :stories])]
     [:li title])])

This for expression calculates a sequence of list item elements, which are then put into the parent unordered list vector.

Inside the whip-main component, nest the project-board component. To nest a component, place it in a vector as though it was a regular function call surrounded by square brackets instead of parenthesis:

(defn whip-main [app-state]
  [:div
    [:h1 "Whip project management tool"]
    [whip-content app-state]])

Great! Now you can see your stories in your example project.

Responding to Events and Handling State Change

It's time to make the user interface more interactive. You want to be able to complete these stories, so add a button to do that. You attach an event handler function to the on-click attribute. In the project-board function, in the list item form, append the hiccup to create a button:

[:button
  {:on-click
   (fn done-click [e]
     (println "You clicked me!"))}
  "done"]

It is helpful to start with the handler function printing to the console so you can confirm events are occurring as you expect. Click the Done button and check the console for the expected message. Now that you know your clicks are registered, update the app-state instead of printing to the console.

(swap! app-state assoc-in [:projects "aaa" :stories id :status] "done")

Now conditionally render the list item based upon its status. If the status is done, show the title in a <del> tag, which is HTML for strikethrough. Otherwise, show the title and the button to complete the task. The list item contains quite a lot of code now, so pull it out into a component called story-card.

 (defn story-card [app-state story-id {:keys [title status]}]
  [:li.card
    (if (= status "done")
      [:del title]
      [:span
       title
       " "
       [:button
        {:on-click
         (fn done-click [e]
           (swap! app-state assoc-in [:projects "aaa" :stories id :status]
                  "done"))}
        "done"]])])

Notice that you don't update the DOM directly. Instead you are updating the app-state, and the interface changes flow out from your functional definitions of how to render app-state. This feels a lot like templating, but without moustaches {{}} and with a more Clojure-like syntax. The components are mostly data with some code for conditionals and transformations. Why not use a templating approach based upon HTML instead of hiccup? There are options to do that, but we urge you to persist with hiccup because it provides an excellent way to mix code and data. Hiccup forms as data are much more amenable to data transformations (see Figure 5.5).

Snapshot of Whip project management tool.

Figure 5.5

Understanding Errors and Warnings

ClojureScript has useful error and warning messages. As a general approach to locating the source of errors you will be well served by following a scientific approach. You can make a hypothesis, do an experiment, refine to a more precise experiment by removing possible causes, and iterate until you find the actual cause, or move on to a new hypothesis when all possibilities are exhausted. This section covers how to get notified of problems, and how to read common notifications.

Let's step through some common debugging scenarios. At the top of core.cljs, add an obvious error (+ "hello" 1) and save the file. You will immediately see in the browser a yellow popup showing the warning. figwheel declines to load code that has warnings; it just displays what went wrong (see Figure 5.6).

Snapshot of a popup showing compile warning.

Figure 5.6

The source code file and line number are displayed in an overlay, and you can see the warning in the console as well. Usually the warning message, filename, and line number are enough to identify the problem. In this case it is clear that you should be passing numerical arguments to the addition function.

Let's try something a little more egregious, (defn (inc 1)). This gives you a popup indicating the problem (see Figure 5.7). The code cannot be compiled, so is treated as an error.

Snapshot of a popup showing compile error.

Figure 5.7

If you like to see warnings and errors in your editor, CursiveClojure provides inline error/warning detection and highlighting.

Now let's look at an error that is not detected at compile time.

(rand-nth (range 0))

Here you can choose a random element from an empty sequence. The code compiles just fine because there is no non-empty type to enforce that the sequence argument to rand-nth contains a value. So figwheel will load the code, but when it is running you get a runtime error because there are no items to choose from. Expand the stacktrace by clicking the triangle at the start of the error message (see Figure 5.8).

Illustration depicting the expansion of the stacktrace by clicking the triangle at the start of the error message.

Figure 5.8

The important thing to note about the stacktrace is that again you have the filename and line number to guide you, but you have to do a little more work to identify the root cause. One confusing element is that there are four lines that occur in core.cljs; however, these are actually different files. The first three lines are in ClojureScript itself, and only the last line is from the source file. You can mouse over the filenames to see the full path. We recommend keeping very little code in your core.cljs namespace, so that you can mentally ignore most lines that contain core.cljs. In this case those lines do provide valuable hints; that the error occurred while calling rand-nth, trying to get an element from range.

We highly recommend renaming core.cljs to main.cljs. You will save yourself a world of confusion when looking at stacktraces, and it really is the main entry point for the browser.

Because of JavaScript restrictions, the compiled code namespaces contain $ and _ where you might expect . or / and -, but the names make it fairly obvious what is going on. However, the fourth line containing the problem in the code is quite opaque. Say instead that you put the code in a function (see Figure 5.9).

Snapshot of a code put in a function.

Figure 5.9

(defn choose []
  (rand-nth (range 0)))
(choose)

Now you can see that the problem is in the whip.core/choose function. The function names are quite useful when debugging, so you should always give functions names, even when they don't require them. For example, when creating event handlers on a component, instead of coding them as (fn [e] ...), write (fn click-ok [e] ...) and then if an error is thrown you will have a far more readable stacktrace.

The details on where the error occurs are only meaningful if you are building source maps. In the current project.clj cljsbuild configuration, the “dev” build creates a source map, because it is not an optimized build. But the “min” build is set to :optimizations :advanced, so no source map is created. This is a sensible default, avoiding any source level identifiers being revealed in production releases. You can enable source maps for advanced compilation as well. Indeed it is sometimes necessary to create a source map enabled minified build, since it is pretty much the only way to pinpoint a missing extern, thus causing the advanced mode build to throw an error.

To enable source maps for the “min” build, add the configuration :source-map “resources/public/js/compiled/whip.js.map” :output-dir “resources/public/js/compiled/min.”

To run the minified build, go to the terminal and cancel the current build process, then run lein do clean, cljsbuild once to run the advanced mode compilation. Then you need to open resources/public/index.html (see Figure 5.10).

Snapshot of open resources/public/index.html.

Figure 5.10

The minified build is much slower than the dev build, so cancel the minified build and return to using lein figwheel.

There is plenty of feedback about what went wrong, but how do you fix it? Print debugging is very convenient when you have code reloading on your side. As soon as you save code to print a value, you will see it in the console. Your code needs to explicitly enable printing to the console by calling (enable-console-print!), which we already do in core.cljs. However, let's move that code to a-init.cljs and require it from core.cljs. So long as it is the first require, other namespaces will be able to print as well. This is convenient because you may want to drop print statements around while debugging. A-init should stay at the top, even after alphabetically sorting. You can also set up the build to later remove a-init.clj from the production build. All of the print functions of Clojure will now work.

Let's add (prn "RANGE:" (range 0)) to the source code. Upon saving you will see RANGE: (), indicating an empty sequence. Typical errors arise from misunderstanding the shape of data. For example, you might use the wrong keyword, or expect a map but get a sequence. So simply printing out the data that is being passed through to failing functions is often useful.

Keep in mind that (doto x (prn "*****")) is a handy way to spy on expressions. It will return the value of x after printing it with some stars to draw your attention. You can wrap (doto ... (prn ...)) around existing expressions to see them as the code executes, and remove the doto later by raising the expression using structural editing in Cursive or paredit in Emacs.

If you are working with JavaScript objects, it can be convenient to use JavaScript's built in (js/console.log x) function because it will render a nice object explorer in the console. If you are working with large nested maps, use (cljs.pprint/pprint m) to see a nicely formatted output on the console.

The ClojureScript compiler's designation of what is a warning and what is an error is somewhat arbitrary, and has a permissive nature inherited from JavaScript. As such, it is important to be attentive to warnings, which will be your main source of clues as to what is going wrong when debugging is required. We encourage you to treat warnings as errors, because in ClojureScript, warnings are almost always errors.

To treat warnings as errors in the build, you need the build process to return an error code when there are warnings. To do this you can add a custom warning-handler to the project.cljs file under the :cljsbuild section. Now the build system will be able to recognize and stop problems before they make it into releasable artifacts.

:warning-handlers
[(fn treat-warnings-as errors [warning-type env extra]
   (when-let [s (cljs.analyzer/error-message warning-type extra)]
     (binding [*out* *err*]
       (println "WARNING:" (cljs.analyzer/message env s)))
     (System/exit 1)))]

Namespace Layout

ClojureScript and libraries like Reagent do not prescribe any particular namespace layout for your code. However, some structure is useful for navigating your source code and isolating areas of responsibility.

Conceptually you have a global app-state, view components, and state transition functions. Let's put the component rendering code in a “view” folder. You'll have separate namespaces for the navbar, project, projects list, and story views. The app-state and state transition functions go in the model namespace, and be sure to leave the route dispatching in main.

A reagent component is a function that returns a hiccup vector. So let's make a separate function for each of those three components. Reagent components are nested in vectors. So instead of calling the components directly by placing them inside parentheses, place them inside square brackets.

All of the namespaces are a nice comprehensible size; around 50 lines of code. It is obvious where the rendering code is, and how it relates to the user interface. And the state manipulation is isolated away from the rendering. Main contains the bare bones required for stitching together the views, model, and routing.

Styling

To style markup you can use CSS or inline styles. Inline styles are standard attributes of HTML.

Where you might write an inline style in HTML as:

<span style="font-family:fantasy"> Whip</span>

You express this in hiccup as:

[:span {:style {:font-family "fantasy"}} " Whip"]

Let's create a navbar and apply some styling.

(defn navbar [app-state]
  [:nav
   [:ul.nav-list
    [:li
     [:a
      {:href "#/"}
      [:img
       {:src "img/logo.png"}]
      [:span
       {:style {:font-family "fantasy"}}
       " Whip"]]]
    [:ul.nav-list
     {:style {:float "right"}}
     [:li
      [:a {:href "#/"} "About"]]
     [:li
      (if-let [username (:username @app-state)]
        [:a {:href "#/settings"} username]
        [:a {:href "#/login"} "Login"])]]]])

Here you see a mix of both HTML and hiccup. The ul elements have a nav-list class on them that comes from style.css, which is included from index.html.

.nav-list {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #383838;
}

In contrast, the span containing the application named “Whip” has an inline style specifying a font-family “fantasy.” It is up to you how much you want to rely on classes or inline styles. A good rule of thumb is to build up with inline styles and convert them to classes as you discover good abstractions for what they are trying to accomplish. It is rare that you know what the nesting and abstractions will be in advance. You can choose to use an established set of CSS classes. For instance you might include bootstrap and leverage their many styles (see Figure 5.11).

Snapshot of Build a project management tool that includes bootstrap and leverage their many styles.

Figure 5.11

Form Inputs and Form Handling

Forms and inputs are represented in hiccup in the same way as static HTML elements. The difference is that you can use the values that the user provides to the input elements. There are two approaches to getting the values of input elements:

  1. Listen to the form submit event. The event target is the form itself, which contains the input elements and their current values.
  2. Listen to an element's on-change event. The event target contains the element, which has the new value of the input.

Before examining the approaches in detail, let's think about state management. For the Whip tool, you want the capability to add stories. You will have a form that allows users to edit the title, description, and add members. You should use the first approach and simply act on the data when the user submits their changes. There is no use for the intermediary values as they are typed, so you should leave their state management entirely to the input elements themselves. The sample application code will not track the form state at all. The global state will be updated once when the submit event is processed.

Now, consider adding a search filter. In this case you want to provide immediate feedback to the user as they type their search query. So you should use the second approach and listen for changes to the search box input, instead of waiting for a submit event. The global state will be kept in sync with whatever the user types in the search box. We describe this state as global, because the filtering will be done above the search box's scope. The code that renders the stories we are filtering must know the current search term.

Finally, let's think about form validation. Say you want to enforce some constraints such as “two stories cannot have the same title.” And you also want to highlight in red the title when it breaks this rule, even before the submit button is pressed. This rule can be contained inside the scope of the form as local state. You don't need this information in other parts of the application. In Reagent, a common way to handle local state is to close over a ratom (see the second form in the Reagent section of this chapter). Track the current rule state for every rule, and highlight elements that have broken rules. The submit button can be activated if there are no broken rules. To achieve this you need to listen to the on-change events to apply the rules, and to the submit event to update the application with the story. Keep in mind that HTML has input attributes to restrict user input, which for basic use cases are often preferable to custom rules.

Let's build an add story form. You need an event handler that can gather the current values in all the input elements in the form when a user clicks “submit.” Attach to the form on-submit event, rather than a button on-click event, because the on-submit event target is the entire form, whereas the button's on-click event target is just the button itself.

[:form {:on-submit (fn [e])}
  [:input {:type "submit"}]]

You will want to prevent the form from performing a POST directly to the server. If you allow the normal submit process to POST, the form to the server will cause the result page to be loaded. Instead, let's control the view. To prevent a form POST, call preventDefault on the event when handling it in the on-submit handler.

 (defn add-story-form [app-state project-id status]
  [:form
   {:on-submit
    (fn add-story-submit [e]
      (.preventDefault e)
      (model/add-story!
        app-state
        project-id
        (forms/getValueByName (.-target e) "story-title")
        status))}
   [:input
    {:type "text"
     :name "story-title"}]
   [:input
    {:type "submit"
     :value "Add story"}]])

To get the current values from the inputs, use the getValueByName helper method from goog.dom.forms in the Closure Library. There is also a toMap function, which is convenient if you have a large form that you would like to interpret as data. The target of the event is the form itself, so you don't need any other external references to the form or its elements to look up their values. Setting the name attribute on inputs allows you to access their values. You can get values by the id or the name, but bear in mind that ids need to be unique to the entire page, whereas names only need to be unique within the form. Using names is usually the right choice.

When the user is typing changes to a title, do you want the new title to be instantly reflected in the global app-state? No, you want to follow the idiom of current edits being transitory until such time as they are submitted. So there is no need to listen to change events, and you can focus on just the form submit event.

What about if you want to edit an existing story instead? You would want to pre-populate the form with the current values. It may seem tempting to provide the values when rendering:

[:input {:type "text" :name "title" :value "Some story title"}]

But this has a severe limitation; the user can't change the text in the input anymore! The user is typing, but as soon as the text changes, the difference is detected, and the DOM gets reset back to the value specified by the render function. You can close over a local atom to store the transient title. The atom can be updated by the on-change event, and thus rendered with the new value, which matches the current input. This works, but is completely unnecessary. Instead, you should use the defaultValue attribute.

[:input {:default-value "Some story title"}]

This is a standard HTML input attribute that provides the initial value for an input. It will populate the value of the element when it is mounted into the DOM. Be sure to only render the input when you know what the default value should be. You want to avoid rendering an element with a blank defaultValue, then updating it, because this will have no effect. For checkboxes use defaultChecked. For options within a select element, use defaultSelected.

Now let's look at the case where you do want to modify global app-state as the user types. Let's implement a search box. The main difference is that you don't need a submit button or a form at all, you just need an input. Bear in mind that HTML is very permissive, so while your page will work without a form, strictly valid inputs should occur inside a form. Feel free to wrap this input in an empty form, but if you do, you will still need to handle and suppress the submit event as the user can press enter to submit a form without a submit button.

[:input {:type "text" :on-change (fn [e] (prn (.. e –target –value)))}]

When you type in the search input box, the console prints out the value of the input element, which is the target of the event. You can see the search term printed as soon as you type it. Instead of printing it, let's update app-state instead:

(swap! app-state assoc :search-term (.. e –target –value))

Now when you render the stories, you can filter by the search term:

(defn filter-stories [stories search-term project-id status]
  (filter
    (fn filter-match [[id story]]
      (and (= (:project-id story) project-id)
           (= (:status story) status)
           (story-search-match search-term story)))
    stories))

State management is not limited to forms, but it is something you do need to think about carefully when working with user inputs. How you manage state can greatly affect the user's experience. Losing state unnecessarily, or not following the edit/apply paradigm, can be very frustrating to users.

The definitions of global and local state are ambiguous, but the terms come up often when discussing how and where components get and store data. Data can be held in HTML elements, in React properties, passed in to the render function, and closed over or read from a global def. That is a lot of places! The distinctions about how you access them are subtle, so it is helpful to be able to relate state management concepts to the concrete examples presented. Understanding them enables you to reason about when to use which approach, and the behavior it will produce in your user interaction. As much as possible, leave state in the HTML elements and rely on data passed in to the render function.

Avoid making assumptions about global state in your components; instead contain interactions with global state in model centric namespace. This is the flexible and isolated approach. It is simple in that components do not directly interact with each other, but can still effect coordinated state change. Keeping global state interaction out of the components allows you to replace your data representation without rewiring all your components. It may initially seem like a bit of a chore to write a model function that just does a swap!, but as your data model matures and changes you will find having all data shape related code co-located makes understanding your application much easier.

Navigation and Routes

The sample Whip application has three main views: the list of projects, a project's stories, and a story details view. In order to render different pages dependent on the state, you need to update the whip-content function to conditionally render the relevant view. You can choose to set a view value in the app-state and have a case statement to choose which component to render. Setting the view value will cause a different view to show.

Browser navigation is based around URLs and back/forward. In order for users to be able to have a comfortable navigation experience, you need to hook into the browser's navigation mechanism. Fortunately this is easily accomplished, thanks to the Closure library.

(doto (History.)
  (events/listen EventType/NAVIGATE model/navigation)
  (.setEnabled true))

The goog.History class allows you to listen to navigation events.

(defn navigation [event]
  (swap! app-state assoc :route (.-token event)))

The listener needs to record the token of the event. The token is the text after the # anchor in a URL, which can be used for linking.

You can dispatch to the appropriate component directly based upon the string. This indeed works fine for basic routes, but you can access some very useful features by using a routing library to parse an anchor # tag appended to the site URL. For example, let's consider the story-details view. We want to pass a story-id to determine which story to show. You can write some parsing code to figure out the values that you need from the URL string, but it would be better to leverage a library that provides a well thought out abstraction for routes.

There are many good libraries to help route a string, but let's use Bidi. Bidi defines routes as data, works in both Clojure and ClojureScript, and is bidirectional (you can build routes as well as parse them). The important thing to keep in mind is that this type of routing is exactly the same sort of string pattern matching that occurs when you write a server side route, but the big difference is that we will be dispatching on the string after the # anchor.

There is nothing magical about the # in a URL, which is a standard HTML mechanism for linking to an element inside a page. It is called the fragment identifier and tells a browser to open a page, but moves the view to the element with the id specified after the # symbol. This is how you link to a specific paragraph on a web page. However it is a common technique in single page applications to use the fragment identifier for routing instead of providing an element with the id. It is convenient because you can use it to link to a different state of the same page. Page fragment identifiers are never passed to the server on page requests; they exist only in the browser. So the server will always serve the same page, regardless of what follows the # symbol.

Bidi routes are expressed as nested vectors. You can use maps instead, but we encourage you to use the vector notation. The map version does not guarantee the order in which the rules will be applied. The syntax is [pattern matched], where matched can be a result, or recur into another vector.

 (def routes
  ["/"
   [["projects" projects/projects-list]
    [["project/" :project-id] project/project-board]
    [["story/" :story-id] story/story-details]]])

The routing root context is /. You will match against “projects” and return the projects-list component. You will next match against “project/project-id” and return the project-board component. Finally, you can match against “story/story-id” and return the story-detail component. To use these routes call (bidi/match-route routes route), where route is the string you are matching, and routes is the data defined above. The result of match-route is a map containing a handler and route-params. The handler will be the last value in the route vectors, which is a component function. The route-params are the captured key value pairs containing project-id or story-id.

(defn whip-content [app-state]
  (let [route (:route @app-state)
        {:keys [handler route-params]} (bidi/match-route routes route)]
    [(or handler projects/projects-list) app-state route-params]))

We match against the routes, and then render whichever component matches. You can have a default in case the user types an unknown URL. The routed components get passed the route-params as a map containing the additional id they need to render a specific story or project. As you can see there is a regular structure here that extends nicely to handling multiple arguments and optional arguments as well.

Routing is very powerful and concise, so it warrants contemplation. But the principles are very simple:

  1. Hook up a function to the browser navigate event.
  2. Record the token (string after the # fragment identifier) of the URL.
  3. Dispatch to the appropriate component function on the token.
  4. Use a routing library to succinctly define string patterns to match to component functions.

HTTP Calls: Talking to a Server

The goal is for teams of people to interact with the sample project tracking system and see each other's changes. In order to accomplish this you need central storage of the data. Thus, you need a web app to communicate with a backend server. This can be done with HTTP requests or WebSockets.

An important restriction on HTTP requests made from JavaScript in a browser is that the browser can only communicate with the same host that served the page. This is called the same-origin policy. A resource may only be accessed by the same scheme (http or https), from the same hostname (domain where the page is hosted) on the same port. Your page can only make requests to the server hosting it, so you can proxy your page requests. When your page requests resources from your host, they can be forwarded to the third party.

There is a standard way to allow access called Cross-Origin Resource Sharing (CORS), where the server adds a header explicitly allowing an origin access. This means you can allow access from JavaScript on a page that you host from one server to another service running on a different port or host that you control. But you can't grant your JavaScript access to third party hosts. It is possible to provide a wildcard to allow everyone access to your service, but popular service providers rarely do. In practical terms, this means that calling the Twitter API directly from JavaScript hosted on the Whip website is not possible.

It is a common mistake to build a ClojureScript project and host it on localhost:3449 by running figwheel, and then run a Ring service in a separate project hosted on localhost:8080 and try to make calls to this service. This will not work, because of the same-origin policy. So the easiest approach when starting out is to host the service code in the same project. This is easily achieved by supplying a ring handler to the figwheel configuration, so that the page, the JavaScript, and the services are all hosted from the same port.

To integrate with third party APIs, you can proxy a call to the server to the third party server. In such a scenario, the browser loads a page from the Whip website, which runs JavaScript that sends requests to the Whip host, which then makes a request to the third party server, and passes the result back to the browser when it arrives.

Another approach to getting around the same-origin policy is JSONP, which is a common way to include a script from a third party site, which will deliver a JSON object. This relies on the third party exposing this method. For example, the Gravatar API allows JSONP requests, so you can make them from ClojureScript using goog.net.Jsonp from the Closure Library.

(defn member-details [email]
  (doto (Jsonp. (str "//www.gravatar.com/" (md5-hash email) ".json"))
    (.send
      nil
      (fn profile-success [result]
        (model/set-member! model/app-state email
          (with-out-str (pprint/pprint ((js->clj result) "entry")))))
      (fn profile-error [error]
        (model/set-member! model/app-state email "No profile")))))

The Gravatar API allows you to look up profile information of users by making a request to https://www.gravatar.com/HASH.json, where HASH is calculated by taking an email address, trimming surrounding whitespace, forcing it to lower-case, and taking the md5 hash. You can use the result in the “title” of the member component to make a tooltip that shows more information about the user (see Figure 5.12).

Snapshot of a tooltip that shows more information about the user.

Figure 5.12

For now, you can call a static endpoint served from a file to avoid the need to create and host a server process. Create a file data.edn under resources/public and put some data in it:

{:foo "bar"}

Check that the file is being served by navigating to http://localhost:3449/data.edn. Let's add some code to make the request. Add [goog.net.XhrIo :as xhr] to the namespace :require form, then use xhr/send to make the request.

(xhr/send "data.edn"
  (fn [e]
    (prn (.. e -target getResponseText))))

The most common way to make requests is cljs-http. Add [cljs-http "0.1.39"] to the dependencies in project.clj and restart the figwheel process in the terminal to pick up the new dependency.

(:require
    [cljs.core.async :refer [<!]]
    [cljs-http.client :as http])
  (:require-macros
    [cljs.core.async.macros :refer [go]])
(go (let [response (<! (http/get "data.edn"))]
      (prn (:status response))
      (prn (:body response))))

Cljs-http is a nice way to manage HTTP requests. It uses core.async channels to deliver its results. For now, all you need to focus on is that http/get and http/post calls should occur inside a go form, and the result is a channel that can have its result read with <!.

When it comes to WebSockets, Sente is a fantastic library to establish a persistent bi-directional link between the client and server. An example is beyond the scope of this book, but we highly recommend using it when you need to stream information. The abstraction fits very well with reactive web pages. It is very convenient to be able to push patches of a data model over the wire and apply it at the other end.

Drag and Drop

You can attach drag and drop handlers by adding attributes to the story card.

{:draggable "true"
 :on-drag-start
 (fn card-drag-start [e]
   (.setData (.-dataTransfer e) "text/plain" story-id)
   (set! (.. e -dataTransfer -dropEffect) "move"))}

And attributes to the columns.

{:on-drop
 (fn drop-on-column [e]
   (.preventDefault e)
   (let [story-id (.getData (.-dataTransfer e) "text")]
     (model/set-story-status! app-state story-id status)))
 :on-drag-over
 (fn drag-over-column [e]
   (.preventDefault e)
   (set! (.. e -dataTransfer -dropEffect) "move"))}

Publishing

It is time to publish the web page. Github Pages is an easy way to get code online. Create a new Github repository, “git init” for the project, and commit and push all of the files. To publish the built version you need to create a branch called gh-pages. Anything in this branch will be hosted at <username>.github.io/<project>, so it should contain an index.html and the compiled JavaScript.

This script creates a clean minified build, and then makes a fresh gh-pages branch containing only the files in resources/public, where the index.html and compiled code are. Replace <username> and <project> with your Github username and project name. Put these steps into a deploy.sh, and use this script to deploy.

#!/bin/bash
set -e
lein do clean, cljsbuild once min
cd resources/public
git init
git add .
git commit -m "Deploy to GitHub Pages"
git push --force --quiet "[email protected]:<username>/<project>.git" master:gh-pages
rm -fr resources/public/.git

Navigate to <username>.github.io/<project> to see your page. The deployed version from the book is located at http://timothypratley.github.io/whip.

There are many good hosting options besides Github, and the steps to deploy are always straightforward. Push the resource/public directory containing the HTML, styles, images, and compiled JavaScript to the hosting platform.

When deploying with a backend you may choose to structure your project such that the backend and frontend code live in the same project. For example, you might have the Clojure Ring server code side-by-side with ClojureScript user interface code. This can be quite convenient for development. For deployment, the resources get can be wrapped up in the same WAR file with the service code. There is one easy step to push the war to a Tomcat container or something similar.

For large applications consider separating the ClojureScript project from the backend service. The advantages of having separate projects are subtle but substantial. Dependency conflicts are avoided. Building the browser artifacts separately opens up more options for server configuration. Testing is often easier to reason about where a clear division has been made between the client project and the service project.

One option to consider is presenting a consistent routing layer using Nginx to avoid the CORS situation, and a reverse proxy to underlying services running on different hosts and ports. In this way you present a single hosting entry point, but have control over how the services are configured internally. Only embark upon fine-grain routing if you truly need many services.

It is good to be aware of the ring-cors middleware library, which allows you to very easily wrap a handler with the necessary OPTIONS request handling to allow cross-origin requests. Using this library you can use multiple hosts without a routing layer, which can be handy for projects where co-hosting the service and HTML artifacts is not desirable due to hosting cost structures, or the desire to use a CDN for HTML artifacts.

Ultimately your project structure will be guided by your desired deployment architecture. Clojure and ClojureScript work very well with many popular hosting options and architecture configurations. There is plenty of flexibility in the deployment unit choice and how deployment units should communicate.

REAGENT IN DEPTH

At this point you have at your disposal all of the tools necessary to build and deploy a fully featured interactive webpage. It is time to expand your horizons and examine some more advanced abstractions that are available. Let's start with a deeper dive into Reagent's capabilities.

Break down exactly what is happening in a Reagent application:

  1. Create global state as a ratom:
    (def app-state (reagent/atom {:title "An example story title"}))
  2. Create component functions:
    (defn story-card [title]
        [:div "Story: " title])
  3. Define a main component that uses global state and passes down values to children states:
    (defn main-view []
        [story-card (:title @app-state)])
  4. Hook into the React system to make changes to the UI:
    (reagent/render-component [main-view] (dom/getElement "app"))

Reagent components are functions. You write functions to render a view from an app state. React renders to a virtual DOM, diffs with the existing DOM, and updates only what has changed into the live DOM. Components that deref a reagent atom (ratom) will be called when the ratom changes. The component functions produce new elements, which are compared with the existing DOM. A set of minimal changes will be calculated and applied to the live DOM to make it match exactly the result of component rendering.

Reagent components can be written in three forms: a function that returns a vector, a function that returns a component, or a function that returns a React class.

Form 1: A Function That Returns a Vector

The first element in the vector is either an HTML tag or a component. Components are followed by arguments to pass to the component when it is invoked. Refer to the “Creating Components” section for the full description of what Hiccup can represent.

(defn story-card [title]
  [:div "Story " title])
(defn main []
  [story-card "world"])

This first form is the most common way to define a component. It is concise and elegant, and the syntax is regular ClojureScript. We aren't using any libraries at all. Reagent can consume this data to create components and hook them into the React framework.

Hiccup is expressed as data, which introduces no new words to learn in order to create elements, call components, format strings, and so forth. Data has better editor support than string templating, making it easier to type and format. Also, it is impossible to miss a closing HTML tag in Hiccup.

The result vector must contain a first element that is either a tag or component. Beware that returning a vector of several divs is not a valid Reagent component.

(defn bad []
  [[:div] [:div]])

But nesting several divs inside a parent div is valid.

(defn good []
  [:div [:div] [:div]])

Why put components in vectors instead of calling them directly? In fact calling them by using parentheses instead of braces works just fine. The problem with direct function calls is that we don't want every view recalculated during the render phase, only views that depend on state that has changed. Reagent takes care of this. When you use a vector instead of a function call, it allows Reagent to only render sub components in response to changes in ratoms that they depend on, because immediate execution has not been forced.

Form 2: A Function That Returns a Component

Consider a situation where you need to perform some initialization on a component. Perhaps you want to be able to add stories directly from the project board view into any of the columns representing status. Having an add story form at the bottom of every column would be visually noisy. Instead, the add story form could be brought up by clicking a button (see Figure 5.13).

Illustration depicting the addition of a story by clicking a button.

Figure 5.13

A component can be defined as a function that returns a function. Use this form to close over a ratom flag that indicates whether the user is adding a story or not. The flag will be initialized once. The component will re-render when the flag changes.

(defn maybe-add-story-form [app-state project-id status]
  (let [adding? (reagent/atom false)]
    (fn a-maybe-add-story-form [app-state project-id status]
      (if @adding?
        [add-story-form app-state project-id status adding?]
        [:button
         {:on-click
          (fn add-story-click [e]
            (swap! adding? not)
            nil)}
         "Add a story"]))))

The component here is returning a function, not a vector. The outer function is called once when the component is first created. The inner function is called whenever the arguments or a ratom that it depends on changes. The inner function returns a Hiccup vector.

Be extra careful to ensure that the inner function has exactly the same parameters as the outer function. It is easy to forget an argument or typo it, causing the outer functions argument to be used in the inner function. Such an error can be confusing, because the outer argument is never updated. Keep this in mind when refactoring.

Form 2 is useful when you need local state, because you can use the “let over lambda” style close over a local ratom instead of relying on global state. The beauty of a local ratom is that each component has its own state encapsulated. However, you can instead decide that you only want one add story form active at any one time. In that case you need a shared ratom that indicates which column the form is currently in. We might choose to put that state in the global app-state, or in the project-board component.

Making a strong distinction between local and global state is somewhat illusory. Don't fret too much about trying to localize state. But do be careful to separate display concerns from data model concerns such as transformation, as this has a direct impact on the flexibility of your code.

Form 3: A Function That Returns a Class

Reagent provides the create-class function to expose the underlying React lifecycle methods.

As a motivating example of how this can be useful, consider a case where you need to perform some task after the element is mounted. You want to make the project title editable, so you can use a similar approach to the add story form; however, there is a wrinkle. When you click to edit the project title, autofocus puts the cursor at the start of the input box. For editing we prefer the cursor to go to the end. In order to achieve this you need to call setSelectionRange after the input is mounted into the DOM.

(defn project-title-input [title]
  (reagent/create-class
    {:display-name "project-title"
     :component-did-mount
     (fn project-title-input-did-mount [this]
       (doto (.getDOMNode this)
         (.setSelectionRange 10000 10000)))
     :reagent-render
     (fn project-title-input-render [title]
       [:input
        {:default-value title
         :required "required"
         :auto-focus "autofocus"
         :name "project-title"}])}))

Pass a map to create-class that contains the lifecycle methods you wish to specify. Now, when the component is mounted into the DOM, you call setSelectionRange on the DOM node. Note that you do not need to rely on an element id lookup, as the component-did-mount lifecycle method receives the component as the first argument.

The lifecycle methods are useful for interop with JavaScript libraries that need to interact with the element directly (threejs, google charts, select2). For example, you can subscribe to a window resize event in componentDidMount and unsubscribe in componentWillUnmount. The resize event handler can change the renderer for a threejs webgl scene to account for a new canvas size. You can read about other lifecycle methods in the React documentation; they are rarely useful for a Reagent application and should be avoided.

There is an alternate style of form 3, which is to attach the map of lifecycle methods as metadata to a function instead of calling create-class. Avoid the metadata style, as metadata provides no syntactic advantage. It is common to attach metadata to a var in Clojure, but Reagent looks for the metadata on the function itself. Avoid this confusing behavior. When you need to use a class, create it explicitly.

Sequences and Keys

Up to this point we have been creating sequences of elements by putting them into a parent vector. But this is not always the best approach. Consider a list of story cards.

(into
  [:ul]
  (for [[story-id story] stories]
    [story-card app-state story-id story]))

The DOM elements get created with four stories.

<ul>
  <li><a href="#/story/3">Implement a way to finish stories</a></li>
  <li><a href="#/story/4">Build a todo list</a></li>
  <li><a href="#/story/5">Draw cards to represent stories</a></li>
  <li><a href="#/story/6">Create lanes to represent story status</a></li>
</ul>

And then the app-state is updated such that the stories are reversed in order.

<ul>
  <li><a href="#/story/6">Create lanes to represent story status</a></li>
  <li><a href="#/story/5">Draw cards to represent stories</a></li>
  <li><a href="#/story/4">Build a todo list</a></li>
  <li><a href="#/story/3">Implement a way to finish stories</a></li>
</ul>

In the absence of any special information, the number of DOM updates required to transition between the forward list and reverse list is relatively large. Every <li> element would have its contents replaced. However it is obvious to us that nothing really changed except the order. The most efficient update is to re-order the elements, not to re-create their contents.

For React to be able to re-order elements instead of blindly updating them it needs some concept of identity. We assign a unique key to each <li> element, and React uses this to do re-ordering efficiently.

(doall
  (for [[story-id story] stories]
    ∧{:key story-id}
    [story-card app-state story-id story]))

Now the <li> elements are re-ordered instead of being changed in place. The same end state is achieved with fewer updates to the DOM. Note that doall forces a lazy sequence evaluation. Lazy sequences that deref ratoms are not allowed.

If the list of stories is short and the story HTML is small, then the difference in performance will not be noticeable, as only a few DOM updates occur either way. But what if we encounter a very long list of stories, and each story has several detailed fields associated with it like the description, owner, and links to other parts of the system? Imagine an update that reverses this large list of detailed stories. In such a scenario, the performance difference will be significant.

To encourage keying sequences React warns you in the console about unkeyed arrays of elements.

Unkeyed:     [:ul [[:li] [:li] [:li] [:li]]]

It is good practice to explicitly choose to represent a sequence either as children, or as an array of elements with an identifying key. The latter will be more efficient for reordering, so if you have uniquely identifiable elements in your sequence, you should go ahead and assign a key.

Children:    [:ul [:li] [:li] [:li] [:li]]
Keyed:       [:ul [∧3[:li] ∧4[:li] ∧5[:li] ∧6[:li]]]

You can represent sequences in three slightly different ways. The resulting DOM elements are indistinguishable. The difference is in how React will update the elements in response to changes.

Custom Markup

If you pass properties to native HTML elements that do not exist in the HTML specification, React will not render them. If you want to use a custom attribute, you need to prefix it with data.

[:span {:awesome "maximus"}]    ;; will not have the awesome attribute
[:span {:data-awesome "maximus"}]    ;; will have the data-awesome attribute

The support for standard tags and attributes is comprehensive, but there is also an escape hatch. If you need some custom markup that is not recognized by React as standard, you can dangerously set HTML. As the name suggests, there is a danger involved with supplying HTML. The main problem to avoid is accidentally forgetting to close a tag, or in some way creating invalid HTML.

An example of where this can be useful is when dealing with SVG. Say you want to create a simple burn-down chart component for the projects. You can build an SVG image with lines representing the remaining tasks over time (see Figure 5.14).

Illustration depicting the burn-down chart.

Figure 5.14

(defn burndown-chart []
  (into
    [:svg
     {:width 300
      :height :200
      :style {:border "1px solid lightgrey"}}
     [:text {:x 200 :y 30} "Burn Down"]]
    (for [i (range 9)]
      [:line
       {:x1 (* (inc i) 30)
        :y1 180
        :x2 (* (inc i) 30)
        :y2 (+ (* 15 i) (rand-int 50))
        :stroke "green"
        :stroke-width 15}])))

To add a logo image to the chart, try an SVG image tag.

[:image {:hlink:href "img/logo.png" :x 170 :y 15 :height 20 :width 20}]

But you now discover that there is no attribute mapping for the xlink:href attribute. The logo does not show up. Instead you can specify the markup as a string.

[:g {:dangerouslySetInnerHTML
     {:_____html "<image xlink:href=img/logo.png x=170
             y=15 height=20 width=20 /> "}}]]

Be very careful to close your tags when using this escape hatch! An unclosed tag will result in strange behavior. Also note that the coverage of mapped tags and attributes is growing. When Reagent 6 is released, you will be able to use :xlink-href, because the underlying React 14 now supports this and many more SVG related attribute mappings.

Reactions

Reagent knows when to update React by observing ratoms. Ratoms behave similarly to atoms in that you can watch, deref, swap!, and reset! them. Additionally, ratoms trigger rendering when they change. If you use a regular atom instead of a ratom, your interface will still render initially, but rendering upon change will not be triggered. So be careful to always define state using the implementation of atom from reagent.core, not a regular atom.

To create a reaction, write an expression inside a (reaction ...) macro form. Reaction expressions are re-evaluated when any ratom or reaction it derefs change. Components containing derefed reactions will be re-rendered with the new result. In this way you can build up a signal graph where changes propagate in a controlled manner.

(ns whip.view.reactions
  (:require [reagent.core :as reagent]
            [devcards.core :refer-macros [defcard-rg deftest]])
  (:require-macros [reagent.ratom :refer [reaction]]))
(def a (reagent/atom {:x 100 :y 200}))
(def b (reaction (:x @a)))
(def c (reaction (+ @b 10)))
(defn view-c []
  (prn "Rendering view-c")
  [:div
   [:div @c]
   [:button {:on-click (fn [e] (swap! a update :x inc))} "inc x"]
   [:button {:on-click (fn [e] (swap! a update :y inc))} "inc y"]])
(defcard-rg reaction-example
  [view-c])

Reactions are a very concise way to express data flow. Here you start with a ratom containing x and y values. You then build a reaction b that only observes the x value. Next, introduce another reaction c that observes the sum of b and 10. Then create a component that renders c reactively. Observe that when you click the “inc x” button, the view is updated with the result of applying the expressions. When you click the “inc y” button, nothing happens. Check the console to confirm that the “Rendering view-c” message is only printed when clicking “inc x.” This is a very good thing, because the view does not depend on y in any way. If you were to deref a instead of c in the view, it would be re-rendered even if y changed.

Reagent reacts to reactions and ratoms via requestAnimationFrame. So, changing many ratoms and reactions that depend on them only results in one render phase.

Currently, when you render a project-board, you render multiple columns. For every column you are filtering, all of the stories in app-state are sorted by order. Any time anything at all changes in app-state, everything is recalculated. If you want to isolate the effects of changing app-state, you can introduce some reactions.

When defining reactions, be careful to think about when they will really change. Reactions use identical? instead of = to test whether the computation should be re-evaluated. Identical means the same object.

For the view-c example identical? is perfect. But, in the case of filtering stories, the calculation produces results that are equal but not identical. You can introduce equality semantics by remembering the last calculated value in a plain atom a, and then only by returning newly calculated results when they are not equal to a.

(defn stories-by-project-status-reaction [app-state project-id status]
  (let [search-term (reaction (:search-term @app-state))
        all-stories (reaction (:stories @app-state))
        a (atom nil)
        filtered-stories
        (reaction
          (let [b (filter-stories @all-stories @search-term project-id status)]
            (if (= b @a)
              @a
              (reset! a b))))]
    (reaction (sort-by (comp :order second) @filtered-stories))))

Reactions provide efficient data views and change propagation, which are powerful features. But as the application grows you need to be careful to keep the data and data access organized. The Reframe library can help with this.

One final thing to think about is that if you had chosen a different model to store the stories, you could have avoided the need to calculate derivative values. Instead of storing all of the stories in one map by id that is filtered by project and column, you could have chosen to store the stories exactly how the view would consume them. You could have created something perfectly tailored to the view you are building.

{:projects {"project-id" {:status "in progress" [{:story :data}}}}}

If you chose to use a nested structure like this that has stories organized by project, then status, and already sorted in a vector, then you don't need to do any transformations at all. But you have given up the ability to find stories by their id. Clearly there are tradeoffs involved in choosing what data is appropriate to send to the client. Often there are multiple views that overlap in the data they consume. DataScript and Om are libraries that can help with this. DataScript is an in browser database, giving the ultimate of flexible data models. Om provides a component query and reconciler abstraction ideal for defining the shape of the data that the view needs from a server.

A Note on Style

Be sure you take new lines liberally.

[:button
  {:on-click
   (fn done-click [e]
     (task-done id))}
  "done"]

You could collapse this expression down to one line. However, there is a tendency for the attribute map to grow. Also, nested vectors lend themselves to “hangers.” Git diffs are easier to understand when you take more vertical space. You'll find it easier to be consistent if you regularly take a new line for attribute maps and event functions.

Don't feel the need to push all of the style code into your styles.css file. Inline styles are convenient when you are building out a new interface. You already have view model separation by isolating your component functions in a view namespace away from your state modification code. So inline styling in the view code is not a bad thing. Create classes for common styles as you discover them and move them to your styles.css to avoid repeating the same styling throughout your code.

Give event handler functions a name. It communicates intent, and error stacktraces are clearer. When returning to the code, the name is very helpful as a summary of what the function is for.

TESTING COMPONENTS WITH DEVCARDS

Figwheel frees you from having to click through your application while you are working on a particular feature. But isn't it annoying now when you have to click through the application to see if everything else is still working? Tests and examples are a great way to drive development toward a goal and clarify design decisions. Testing user interfaces has traditionally been considered difficult. Automation is tedious and brittle, while unit tests rarely capture the look and behavior. Wouldn't it be great if you could see the entire application without having to step through every path? Devcards is a library for showcasing components in various states. You can display several states right next to each other. Devcards is perfect for examples and testing in a web application.

So, before proceeding on to more features for the whip project management tracker, let's add testing feedback to the workflow. Don't worry, this won't be the boring style of testing you may be expecting.

Edit the project.clj file. In the dependencies section add [devcards "0.2.1"]. In the cljsbuild builds section, copy the dev build to a devcards build. Specify :devcards true in the new devcards build's :figwheel section. Change :compiler :output-dir to “resources/public/js/compiled/devcards,” so that the intermediary compiled files do not collide with other builds. Change :asset-path to “js/compiled/devcards”. Change :compiler :output-to to “resources/public/js/compiled/devcards.js,” so that the final artifact does not collide with other builds.

    {:id "devcards"
     :source-paths ["src"]
     :figwheel {:devcards true}
     :compiler {:main whip.core
                :asset-path "js/compiled/devcards"
                :output-to "resources/public/js/compiled/devcards.js"
                :output-dir "resources/public/js/compiled/devcards"
                :source-map-timestamp true}}

You don't want to try and render the entire app when looking at the devcards, so modify core.cljs to only conditionally render the entire app:

(ns whip.main (:require [goog.dom :as dom]))
(when-let [app (dom/getElement "app")]
  (reagent/render-component [whip-main] app))

In the project's resources/public directory, copy the index.html file to devcards.html. Edit devcards.html. Remove the <div id=app>...</div> element. Replace whip.js with devcards.js in the <script> tag.

Start both the dev build and the new devcards build configured by running in the terminal: lein figwheel devcards dev. Check that the app still works at http://localhost:3449 and then open a new tab and navigate to http://localhost:3449/devcards.html. Seeing this page in Figure 5.15 indicates that everything is working so far.

Snapshot of a page devcards dev.

Figure 5.15

In the view.cljs source file, add devcards to the namespace require. Refer the defcard macro [devcards.core :refer-macros [defcard-rg deftest]]. Instead of defcard, use a more specific version, defcard-rg, which renders reagent components. You can define a card that shows the project-board.

(defcard-rg project-board-example
  [project-board model/app-state "aaa"])

Now you can isolate a single component to work on, instead of having to observe the entire application.

The big idea with Devcards is that because components are functions of state, you can view them in isolation. Imagine how you might approach this without any library support. You might create a page and mount your component with various states. In fact, that is pretty much how you develop any page, except that you throw away all of the intermediary code and only keep the final application. Devcards provides structure for this intuitive approach. The key concept is a macro to create a “card.” Devcards collects cards by namespace into a navigable page showing all of the cards. Thanks to dead code removal, you can conveniently intermingle example cards with your application code without worrying about bloat. The cards will not be present in your production build.

Notice how we started with some example data that you were going to throw away? Now you have a way to preserve the hard work and get the benefit of being able to show and test the UI in the states you have designed it for (see Figure 5.16).

Illustration depicting the testing of the UI in the states it was designed for.

Figure 5.16

(defn projects-list [app-state]
  [:div "Project List"])
(defcard-rg projects-list-example
  [projects-list model/app-state])

A string can optionally be specified in a card as the first argument. The string is rendered as markdown, so you can have richly formatted text and even format code.

(defcard test-card
  "=Hello hello"
  [hello])

Refer deftest from devcards instead of test.

(ns example
  (:require [devcards :refer [deftest]])

Write a test, and see how the test is rendered into the page. Failing tests are colored red. Add an on-jsload hook to report a test summary to the console while developing.

Notice that you are passing the app-state around everywhere. You can instead rely on a global app-state; but we recommend against it. Passing dependencies tends to make code more testable, in the same way that backend functions might pass a database parameter down instead of relying on a global variable. Depending on what you are testing, it might be convenient to share a single state for your cards, or completely isolate them with their own state. For example, say you want to have an example of a story card that does not change the state of the other cards. In that case, you might do something like so:

(defn story-card-example-component []
  (let [app-state (reagent/atom example-projects)]
    (fn a-story-card-example-component []
      [story-card app-state "aaa" 2
                  (get-in @app-state [:projects "aaa" :stories 2])])))
(defcard-rg story-card-example
  [story-card-example-component])

Let's make a card with a complete board (see Figure 5.17).

Snapshot of a card with a complete board.

Figure 5.17

Devcards presents cards and tests that occur in namespaces, so you can see what the components will look like in predefined states. You are presented with a visual history and specification of how everything should look and behave.

INTEROP WITH JAVASCRIPT

Interop with JavaScript from ClojureScript is similar to interop with Java from Clojure.

(.method object arg1 arg2)
(. object method arg1 arg2)
(.. object method1 (method2 arg1 arg2))

JavaScript has a global root object. To access the global root in ClojureScript, use the special js prefix.

js/window
(.setEventListener js/window (fn [e] (prn "hi")))
(js/window.setEventListener (fn ...)))

To access properties, ClojureScript differs from Clojure slightly in that you must use a dash to indicate property access. JavaScript does not distinguish properties from methods, so the dash is required to explicitly designate property access.

(.-property object)
(. object -property)
(.. object -property1 -property2)
(set! (.-property object) value)

You may notice that properties can be accessed another way. The below form is a quirk and will fail advanced compilation, so avoid using it.

object.property          ;; bad, don't do this
object/property          ;; bad, don't do this

Array access is done with aget and aset.

(aget array index)
(aget array index1 index2 index3)
(aset array index value)
(aset array index1 index2 index3 value)

Note that you can chain access forms together.

(set! (.-foo (aget array index1)) value)

Object constructors are the same as Clojure, but note that you can use constructors from the global JavaScript root.

(js/THREE.Vector3. 0 0 0)
(new js/THREE.Vector3 0 0 0)

Modules can be referenced and imported from the Closure Library.

(ns example
  (:import [goog.event KeyCode]))

Creating JavaScript objects can be done with js-obj or by using the special #js reader literal. These forms are not recursive. So for nested objects, be sure that the children are specified as literals also.

(js-obj ...)
#js {"key" "value"}

Be cognizant about when to use JavaScript objects and when to use ClojureScript data structures. We recommend using ClojureScript data literals as much as possible, and only using JavaScript objects for interop with third party libraries.

There are transformation functions to convert from ClojureScript data structures to JavaScript objects. These are both recursive.

(clj->js)
(js->clj)

You can optionally specify :keywordize-keys true, but keep in mind that not all strings can be keywords.

There is a penultimate escape hatch form js* that takes raw JavaScript.

(js* "some(javascript);")

JavaScript allows anything to be thrown, so the ClojureScript catch form has a special term :default, which will match anything. Use this to catch unexpected values being thrown.

(try
  (/ :1 2)
  (catch :default ex))

You can annotate your functions with :export to prevent them being name-mangled during advanced compilation. They will then be easily consumable from JavaScript.

(defn ∧:export f []
  (+ 1 2))

To take advantage of advanced compilation, most popular JavaScript libraries have wrappers available. Wrapping additional libraries is not difficult. Wrapped libraries are easy to depend on, and you benefit from code shrinking. The best source of pre-wrapped libraries is CLJS (http://cljsjs.github.io), which also provides instructions on how to create your own wrappers.

ONE LANGUAGE, ONE IDIOM, MANY PLATFORMS

Clojure/ClojureScript realizes the dream of being able to write your frontend code and backend code in the same language, using the same idioms, data structures, and abstractions. Staying in the same language is good, and it goes beyond syntax.

You can even share code directly. Files with the cljc extension are used in any platform. You can specify platform specific code with #clj+.

#?(:clj     Double/NaN
   :cljs    js/NaN
   :default nil)
(defn build-list []
  (list #?@(:clj  [5 6 7 8]
            :cljs [1 2 3 4])))

THINGS TO KNOW ABOUT THE CLOSURE COMPILER AND LIBRARY

ClojureScript uses the Closure Compiler to produce JavaScript. In the same way that Clojure programming is augmented by some knowledge of the Java ecosystem, it is useful to know a little bit about the Closure Library. There are many opportunities to leverage the platform.

The most impactful feature of the Closure Compiler is that it can do advanced compilation. Advanced mode compilation does aggressive renaming, dead code removal, and global inlining, resulting in highly compressed JavaScript. This power comes with some restrictions. Advanced compilation mode makes assumptions about the code that it optimizes, and breaking these assumptions will result in errors. For the most part you are one layer away from those assumptions, until it comes to interop with JavaScript libraries. JavaScript libraries must be accompanied by extern definitions, or the aggressive renaming will not be matched to your interop calls. Fortunately, many popular libraries are readily available from CLJSJS with extern definitions as a packaged dependency.

Most project templates configure a development build using no optimization, and a production build using advanced mode compilation. Development tools like figwheel can only be used with no optimizations, and these builds are very fast (typically under one second). Advanced mode production builds on the other hand are too slow for an interactive development cycle (typically between 15 seconds to a full minute).

The Closure Library is a rich source of helper code. The library is rigorously cross browser compatible. This is a big win. You took advantage of this when you added the history listener in the navigation and routing section of this chapter. Instead of relying on browser specific code, use goog.History instead. As a rule of thumb, if you find yourself accessing browser features directly via interop, consider looking for an abstraction for it in the Closure Library documentation. There are helpers for the DOM, files, events, keyboard handling, form handling, components such as date pickers, and many more. The library is comprehensive, and well worth perusing to familiarize yourself with what is available.

MODELING STATE WITH DATASCRIPT

DataScript is a database implementation in ClojureScript designed to be a replacement for the app-state of your application. It goes beyond storing your application state and stores the history of changes to the state as well. Having a database as your app-state centralizes state access and modification around a powerful abstraction. You can create queries to view the data in different ways. DataScript is built on the same principles as Datomic, which is explored in depth in Chapter 6. Datascript stores data as datom tuples (entity, relationship, entity, time), which provide a history of all changes. The main advantage of Datascript comes from flexible Datalog queries.

Queries are expressed as data.

(def q-player
  '[:find ?s ?attrs (pull ?e [*])
    :where [?a :coach "Coach"]
    [?a :player "William"]
    [?a :attrs ?attrs]
    [?e :name "William"]
    [?e :somedata ?s]])

To use DataScript in a reactive website, you can take advantage of the ability to watch transactions. You use the listen! function to be notified of transactions. Inside the handler you respond to changes in the database. Re-query the database whenever the data changes:

(defn bind
  ([conn q & args]
   (let [k (uuid/make-random)
         ratom (reagent/atom (apply d/q q @conn args))]
     (d/listen! conn k (fn [tx-report]
                         (reset! ratom (apply d/q q (:db-after tx-report)
                           args))))
     (set! (.-_____key ratom) k)
     ratom)))

Here bind creates a ratom that the application can use for reactive rendering. When the database changes, the ratom is updated, and when the ratom is updated the component re-renders, and when the component re-renders the DOM diff occurs to update the browser elements.

(defn player []
  (bind conn q-player))

One major difference from Datomic is that DataScript does not require an upfront schema. This makes it very approachable, because you can dive right into transactions and queries without defining your attributes. However, a schema is necessary in order to nominate non-defaults such as the cardinality of one to many.

GO ROUTINES IN YOUR BROWSER WITH CORE.ASYNC

Core.async is your get out of callback hell card. Let's take a look at the standard example of callback hell; a nested chain of asynchronous function calls.

getData(function(a){
    getMoreData(a, function(b){
        getMoreData(b, function(c){ 

This situation arises frequently in JavaScript applications because JavaScript is single threaded and avoids blocking for operations like remote service calls. The common interface presented by the browser for blocking actions is to return from the function call immediately, while the browser manages the request. When the request is fulfilled there is no way to return control to the original call site. So when a blocking action is requested, the caller must specify a callback function to be invoked when the action is completed. This allows you to get around the single threaded model with asynchronous calls. But, as the number of callbacks increases, it quickly becomes difficult to reason about the order of execution of our code, and how failures should be treated.

Callback hell is the eventual state of any complex callback oriented asynchronous program where the management of callbacks gets so hard that your code becomes extremely hard to understand, reason about, and maintain.

The problem is more than just having nested function definitions. The real rub comes when changing application state inside those callbacks. Consider a text input that you want to populate with a list of completions. Fire off a request to the server with some user input, but by the time the result arrives, it may not be relevant anymore, because the user has since replaced the text in the input. The difficulty using an asynchronous expression goes beyond syntax. How can you express state and error handling in a way that is clear, logical, and testable?

Callbacks close over state and raise events, which are consumed from a queue. The state is in the processing code. To handle all the possible interactions of the callbacks, build a state machine that encompasses all of the possible state transitions.

Core.async provides you with an option to trade where the state lives. It presents a model of short-lived variables in a sequential process, and allows concurrent sequential processes to communicate with each other. The implementation of core.async is actually a bunch of sophisticated macros that build state machines to provide the execution models written as sequential processes. While it isn't important to understand the implementation details, it helps to remember that in ClojureScript these expressions are executing in a single thread in an interleaved fashion controlled by a generated state machine.

Let's look at a basic example where you make a web request.

 (let [ch (http/get "//whip/projects")]
  (go (hello (<! ch))))

Here http/get returns a channel. The go block defines a concurrent process that will pull the result of the web request from the channel and use it. Blocking can only occur inside a concurrent process defined by a go block. The result of a go block is a channel, which will deliver the last value in the go block. In this example the result is ignored. Channels can accept and deliver values. <! is the operator to take a value from a channel. Notice that there are no locks, no condition variables, and no callbacks required for these kinds of expressions. They look like a thread that can block while other threads continue.

Core.async plays well with existing JavaScript libraries. If you have an interface that requires a callback, just pass a callback that only puts the arguments onto a channel, and you can reframe your logic as a sequential process consuming from the channel.

SUMMARY

ClojureScript has many compelling features that position it as the leading option for reactive webpage development. Interface definitions are concise, the reactive model is robust, there are solid abstractions to build on, and there are many helpful libraries to leverage.

This chapter stepped through all of the important aspects of building a ClojureScript application, developing an interactive project tracking webpage, and using several useful libraries. You saw how ClojureScript has a unique and compelling development workflow.

A lot of ground was covered, from getting started through to advanced techniques. You may be feeling a little overwhelmed by the breadth of topics covered in this chapter. The good news is that this knowledge is widely applicable. ClojureScript opens up fundamental computation abstractions that provide the leverage necessary to build large and robust web pages. But you don't need to use all of ClojureScript's special powers. You can start small and build up with the confidence that those special powers are there when you need to reach for them.

You now have a template for building out a ClojureScript project. With this knowledge you are ready to build any interface that a browser can support, using comfortable idioms and powerful abstractions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset