Login

ClojureScript

A compiler for Clojure that targets JavaScript.

Testing Clojurescript - A simple approach

Article Summary
This article is part of a series. We use the "book club" project to explore various programming languages and frameworks. Details of the book club's business and data requirements are detailed in a prior article, "Leveraging Ruby on Rails and ClojureScript.".

This article details authomated testing og our Clojurescript client. Please see the corresponding article ClojureScript - Single Page Application - A simple approach for details on business requirements and implementation details.

We employ a stack of Java libraries and tools. Source code and build files are located on GitHub, at the book-site-clojurescript repository.

In this article we'll detail automated testing of our Clojurescript client. I'm using a port of clojure.test. cemerick/clojurescript.test. I am using phantomjs as a test runner (container).

Managing the Project with Leiningen
We are using the Leiningen to manage the Clojurescript project. Settings for Leiningen are placed in project.clj (located at the root directory of your project).

We need to add a contributed library to support automated tests for Clojurescript. We are using cemerick /clojurescript.test, a port of clojure.test to ClojureScript.

We make the following changes to the project.clj file.

We add the test library to the list dependencies and plugins:

:dependencies [[org.clojure/clojure "1.5.1"] [org.clojure/clojurescript "0.0-1859"] 
                      [com.cemerick/clojurescript.test "0.2.1"]]
:plugins [[lein-cljsbuild "0.3.3"] [com.cemerick/clojurescript.test "0.2.1"]
                      [marginalia "0.7.1"] [lein-marginalia "0.7.1"]]

Adding a new test target for the compiler.
Next we instruct the Clojurescript compiler to build 2 seperate targets. The first target includes our unit tests. The second target is what we'll use when deploying on the server (and ultimately delivered to the web browser client).

:cljsbuild
  { :builds [ { :source-paths ["src/cljs" "test" ]
                :compiler {:output-to "target/cljs/wstestable.js"
                         :optimizations :whitespace :pretty-print true}}
              {:id "prod" :source-paths ["src/cljs"]
                :compiler {:output-to "target/cljs/books_cljs.js"
                         :optimizations :whitespace :pretty-print true}}]
     :test-commands { "phantom-ws" [ "target/cljs/wstestable.js"]}
  }

Compiling Multiple Targets
With the above configuration we can compile just "prod" (production) target with the following command line:

$dev> lein cljsbuild once prod

Or, if we want our code compiled each time we save changes to disk. we substitute "once" with "auto":

$dev> lein cljsbuild auto prod

If we want to build both targets, just remove "prod" from the command line. For example to compile both targets just once:

$dev> lein cljsbuild once

Writing the tests.
In general these tests are much closer to the definition of "unit tests". The functions we are testing a very small. For example, a test of addAuthor[]. Let's look at the addAuthor source code:

(defn addAuthor
  ([id first_name last_name ]  
    (swap! AuthorList conj (Author. id first_name last_name )))
  ([jsonObj] (swap! AuthorList conj (jsonToAuthor jsonObj)) )
)

The above function can be called 2 different ways:

  1. With 3 parameters, an id, author's first name. author's last name
  2. WIth 1 parameter, a JSON object

ClojureScript - Single Page Application - A simple approach

Article Summary
This article is part of a series. We use the "book club" project to explore various programming languages and frameworks. Details of the book club's business and data requirements are detailed in a prior article, "Leveraging Ruby on Rails and ClojureScript.".

This article details enhancement to our client tier implementation using ClojureScript. Clojurescript is a compiler which takes Clojure source code and emits (generates) JavaScript. Please see our prior article Leveraging Clojurescript for details on Clojurescript, as well as details on the client's business requirements.

In this installment, we add create,update and delete (CRUD) functionality to our client.

Source code and build files are located on GitHub, at the book-site-clojurescript repository.

Single Page Application (SPA)

The JavaScript client dynamically renders web pages in the web browser. The JavaScript client is referred to as a "single page app".

To the user, the application appears to be displaying multiple web pages. However, technically, the web browser has loaded one single html document (and JavaScript engine instance).

In a traditional web application, the server generates html pages. Each time the web browser requests a new "page", the web browser loads a new instance of the JavaScript loader. On a desktop web browser, the traditional architecture works fine.

SPA and Mobile Devices

On a mobile web browser loading a new instance of the JavaScript engine is not a trivial task. SPA loads a single JavaScript engine (once). Thus, SPA is beneficial on a mobile web browser.

In addition, a SPA requires less traffic going across the network.

On a mobile client, both of these optimizations (reduced network traffic, a single javascript engine instance) are worth considering.

HTML Document Structure

Our html layout is set with a mobile display in mind. We are using the Pure CSS framework to provide a responsive design.

We added a new area of our UI (at the top portion of the display screen). I called the new area (a div) "entity-menu". Entity menu contains a child "ul", identified as "entity-menu-details". The application changes the contents of "entity-menu-details" as the user navigates through the application.

<body>
  <header id="header-container" class="header" style="text-align:center;">
    <h4 id="menu-header>"</h4>
      <div class="pure-menu pure-menu-open pure-menu-horizontal" id="entity-menu">
         <ul id="entity-menu-details">
         </ul>
      </div>
    </header>
</pre>
</div>

DOM Event Listeners

As in the prior version of the client we load the event listeners once. The event listeners target a container element. The container element and the corresponding listener are both maintained throughout the application's life-cycle.

The strategy is to keep the task on managing listeners simple. The number of listeners should remain constant.

We load all of the event listeners when the application starts. In a web browser, the "document ready" event signals the application start. The following snippet demonstrates.

(defn doc-ready-handler []
  (let[ ready-state (. js/document -readyState)]
    (if (= "complete" ready-state)
      (do
        (add-router)
        (do-ajax "GET" "../rest/export/book/all" ajax-response-handler)
        (do-ajax "GET" "../rest/export/authors/all" ajax-response-handler)
        (do-ajax "GET" "../rest/export/review/all" ajax-response-handler)
        (do-ajax "GET" "../rest/export/category/all" ajax-response-handler)
        (do-ajax "GET" "../rest/export/book_category/all" ajax-response-handler)
      ))))

(defn on-doc-ready []
  (aset js/document "onreadystatechange" doc-ready-handler ))

(on-doc-ready)

The last statement (on-doc-ready) is our application entry point.

(on-doc-ready) invokes the on-doc-ready[] function. The on-doc-ready function simply assigns the doc-read-handler[] function as the DOM listener for "ready state change".

Once the Document's ready state is "complete", the add-router[] function is called.

(defn add-router []
  (let [ report (by-id "report")
          menu (by-id "footer-menu")
          entity-menu-details (by-id "entity-menu-details")]
          (.addEventListener report "click" list-click-listener true)
          (.addEventListener menu "click" menu-listener)
          (.addEventListener entity-menu-details "click" entity-menu-listener) ))

The add-router[] function loads our event listeners.

Leveraging ClojureScript.

This article is part of a series. The previous article Leverage Ruby on Rails and ClojureScript detailed the project's business requirements. In this article I'll detail the application's mobile client implementation. The mobile client implementation is written in ClojureScript.

Complete source code is available on my GitHub repository here.

What is ClojureScript?

Clojure is a Lisp dialect. Clojure's default implementations run on the Java Virtual Machine (JVM) and Microsoft's CLR.

ClojureScript is a compiler, written in Clojure. The ClojureScript compiler emits (generates) JavaScript.

The programmer writes client code in ClojureScript. The programmer compiles the Clojurescript. The ClojureScript compiler generates JavaScript.

Why ClojureScript

There is a popular theory that holds the less source code statements, the lesser chance for bugs, the greatest number of functions can be provided (AKA -- an application can be more complex.) Clojure and ClojureScript expressions are very short and concise. Let's look at an example.

The following code adds a HTML document ready listener. The ready listener then sends a set asynchronous HTTP requests to our server. Each request to the server is assigned a separate "handler function". The handler functions (not shown here), parse the server's response.

(defn doc-ready-handler []
  (let[ ready-state (. js/document -readyState)]
    (if (= "complete" ready-state)
      (do
        (add-router)
        (doget "GET" "/books" ajax-response-handler)
        (doget "GET" "/authors" ajax-response-handler)
        (doget "GET" "/reviews" ajax-response-handler)
        (doget "GET" "/categories" ajax-response-handler)
        (doget "GET" "/book_categories" ajax-response-handler)
      ))))

(defn on-doc-ready []
  (aset  js/document "onreadystatechange" doc-ready-handler ))

(on-doc-ready)

The first thing you might note is, I am using vanilla JavaScript services. There are many high level JavaScript libraries which ease the development process. Libraries such as JQuery and BackBone have plenty of use cases. The ClojureScript community has already contributed libraries that provide calls the JQuery via ClojureScript.

One of the main focuses of this project is a learning experience. I am not adverse to high level libraries. High level libraries like JQuery and Backbone have very good use cases. I'm using vanilla JavaScript here to follow the "walk then run" philosophy. Much of this project is simply a learning device.

Translation Please
Let's translate the above ClojureScript code.

  • "defn" defines a function. We defined 2 functions, doc-ready-handler and on-doc-ready.
  • "aset" assigns a value to a an application object. In the function "on-doc-ready", we assign a "ready state" listener to our document.
  • "js/document" is the connotation for our document. "js/" designates name space. In this case, the name space is js (vanilla JavaScript). We are instructing ClojureScript to issue a vanilla JavaScript instruction.
  • "doget" is a call to custom function (shown below)

The "doc-ready-handler" function is called by our document several times as the document is loaded in to the web browser. The "doc-ready-handler" function reads the "readyState" document property to determine whether the document is "ready". In other words, this is the same functionality as JQuery's ready() function.

The last expression, (on-doc-ready), runs the function "on-doc-ready".

We are calling a function "doget" several times. That function sends an asynchronous HTTP GET request to the server. The function sets the GET request headers so that the server responds with a JavaScript Object Notation (JSON) response. Finally, the function assigns a listener callback function ("handler-function"). "handler-function" processed the server's response when it arrives. Here is my implementation:

(defn doget [request-type url handler-function ]
  (let [x  (js/XMLHttpRequest.)  ]
    (aset  x "onreadystatechange" handler-function )
    (.open x request-type url)
    (.setRequestHeader x "Content-Type" "application/json" )
    (.setRequestHeader x "Accept" "application/json" )
    (.send x)))

Leveraging Ruby on Rails and ClojureScript.

Our upcoming sample project focuses on application frameworks and compilers.

Ruby on Rails (ROR) is an application framework written in Ruby.

ClojureScript is a compiler which emits (generates) JavaScript. The ClojureScript compiler is written in Clojure.

Our project will focus on a couple of customizations.

  1. We'll define database integrity rules in the database server (versus the application code).
  2. We'll verify our JavaScript client code, marks nodes, removed from the document, as available for deletion. In other words, we will verify that after we remove an html element from the document, it's underlying object is empty. We will verify the web browser's garbage collector removes the nodes from memory.

As with projects other projects in this series, this project is primarily a learning device. We will build a useful application. However, the main benefit is, to learn technology.

Business Requirements

One of my hobbies is visiting libraries, used book stores, and reading. I want to carry a list of books, authors and categories (E.G. science fiction) with me on my mobile phone. I'd also like to refer to book reviews. Many folks who work in libraries or book stores are great sources for book and author recommendations. Communicating your prior reviews helps folks recommend new books and authors.

Application Architecture

Server will run on a Local Area Network (LAN). That means, access is provided by my LAN. We won't implement application layer authentication and authorization for the initial release.

Our application provides two user interfaces (UI and user experience (UX)).

  1. Full size web browser. Supports create, report, update, and delete (CRUD).
  2. Mobile web browser. Supports reports.
Syndicate content