ClojureScript is a compiler for Clojure that targets JavaScript. It is designed to emit JavaScript code which is compatible with the advanced compilation mode of the Google Closure optimizing compiler.
Why ClojureScript?
- Allows to use functional programming.
- Uses Immutable data structures. (Immutable.js library is available to enable use of immutable data structures with JavaScript. Watch Christian Johansen’s talk on Immutable JavaScript)
- Uses Google Closure Compiler : advanced code compiling with dead code elimination
- core.async: makes ajax asynchronous call look like synchronous without the use of callbacks
- Om/React : React provides a functional approach for rendering DOM. Using ClojureScript and React makes some tasks , for example undo quite simple
A great tutorial to follow to learn ClojureScript is http://clojure.org/clojurescript which covers the basics of ClojureScript, environment set up with the use of Leiningen project, DOM interaction, routing,AJAX call, core.async, Om/React, browser enabled REPL and more.
The difference between JavaScript and ClojureScript is described in Compare ClojureScript with JavaScript.
Below is an example how to create an object in JavaScript and ClojureScript.
Create object in JavaScript
var foo = {foo: "bar"};
Create objects in ClojureScript
;; Create JavaScript objects with `js-obj`.
(def foo (js-obj "foo" "bar"))
;; ClojureScript supports JavaScript data literals via the `#js` reader literal.
(def foo #js {"foo" "bar"})
;; It’s important to note that `#js` is shallow, the contents of
;;`#js` will be ClojureScript data unless preceded by `#js`.
;; This is a mutable JavaScript object with an immutable
;; ClojureScript object inside.
(def foo #js {"foo" "bar"})
Since I am using LightTable as the IDE, an introduction to clojureScript to LightTable users is useful. If you are using a Windows machine, use ctrl + enter instead of command +ENTER to evaluate each form.
I got hooked up with the use of React with ClojureScript. The following interfaces are available to integrate React
- OM – I found the basic tutorial to OM useful to understand the syntax of OM/React. It also describes how to use Figwheel, a tool which reloads code in the browser without refreshing the page.
- Quiescent
- Reagent
The advantages and disadvantages of each of the above interface are described in “How to use React in ClojureScript, and why”
Furthermore external JavaScript libraries can be integrated with ClojureScript with the use of :foreign-libs option.
Building Single Page App “SimpleCalculator” with Reagent, Secretary and Accountant
I followed the figwheel tutorial to create a project based on figwheel leiningen template.
lein new figwheel SimpleCalculator
It automatically creates a directory “SimpleCalculator”. Open the folder using Light Table. It has the following structure.

Open project.clj and add the following dependencies
[reagent "0.5.1"
:exclusions [org.clojure/tools.reader]]
[reagent-forms "0.5.13"]
[reagent-utils "0.1.7"]
[secretary "1.2.3"]
[venantius/accountant "0.1.6"
:exclusions [org.clojure/tools.reader]]
The dependencies tag look like

Reagent is the interface for React in ClojureScript. Secretary is client side router for ClojureScript. Accountant makes navigation simple by triggering dispatches to Secretary defined routers and update browser’s path without a page reload.
Open Index.html file. Add the h3 tag above the “app” div.
<h3> Welcome to calculators</h3>
The Index.html will look like

Open core.cljs and copy and paste the code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (ns simplecalculator.core | |
| (:require [reagent.core :as r :refer [atom]] | |
| [reagent.session :as session] | |
| [secretary.core :as secretary :include-macros true] | |
| [accountant.core :as accountant] | |
| )) | |
| (enable-console-print!) | |
| ;;pension age calculator implementation | |
| ;;atom to store the state data for the pension age component | |
| ;;defonce is used instead of def to prevent the atom from resetting on each save of the file. Only on refresh of the page, the atom is reset. | |
| (defonce pension-age (r/atom {:dob nil :pensiondate 0 :age 0})) | |
| ;;JavaScript objects can be referenced using the js prefix | |
| ;;function to check whether object is a JavaScript date | |
| (defn date? [x] | |
| (= (type x) js/Date)) | |
| ;; using Javascript date function toLocaleDateString to convert date into local date format if date object is provided else return "unselected" | |
| (defn get-date! [date] | |
| (if (date? date) | |
| (.toLocaleDateString date "en" "%d-%b-%Y") | |
| "unselected")) | |
| ;;function to calculate pension age based on date of birth entered from the UI | |
| ;;destructuring is used on the pension-age atom object to map each value (dob, pensiondate and age) to a variable which can be used for manipulation | |
| (defn calc-pensionage [] | |
| (let [{:keys [dob pensiondate age]} @pension-age ] | |
| ;;js/Date. is used to get a date object from string | |
| ;;if dob is between "1950-04-06" and "1953-04-05" then state pension age is 63 | |
| (if(and (<(js/Date. "1950-04-06") dob) (> (js/Date. "1953-04-05") dob)) | |
| ;;age to get state pension is 63 | |
| ((swap! pension-age assoc :age 63) | |
| ;;add 63 years to dob | |
| ;;JavaScript Date function getFullYear is used to get the year from dob date object and then add the number of years | |
| (swap! pension-age assoc :pensiondate (js/Date. (+ (.getFullYear dob) 63) (.getMonth dob) (.getDate dob))))) | |
| ;;if dob is between "1953-04-06" and "1953-12-05" then state pension age is 65 | |
| (if(and (<(js/Date. "1953-04-06") dob ) (>(js/Date. "1953-12-05") dob)) | |
| ;;age to get state pension is 65 | |
| ((swap! pension-age assoc :age 65) | |
| ;;add 65 to dob | |
| (swap! pension-age assoc :pensiondate (js/Date. (+ (.getFullYear dob) 65) (.getMonth dob) (.getDate dob))))) | |
| ;;if dob is between "1953-12-06" and "1960-04-05" then state pension age is 66 | |
| (if(and (<(js/Date. "1953-12-06") dob ) (>(js/Date. "1960-04-05") dob)) | |
| ;;age to get state pension is 66 | |
| ((swap! pension-age assoc :age 66) | |
| ;;add 66 to dob | |
| (swap! pension-age assoc :pensiondate (js/Date. (+ (.getFullYear dob) 66) (.getMonth dob) (.getDate dob)))) | |
| ) | |
| ;;if dob is between "1960-04-06" and "1977-04-05" then state pension age is 67 | |
| (if(and (<(js/Date. "1960-04-06") dob ) (>(js/Date. "1977-04-05") dob)) | |
| ;;age to get state pension is 67 | |
| ((swap! pension-age assoc :age 67) | |
| ;;add 67 to dob | |
| (swap! pension-age assoc :pensiondate (js/Date. (+ (.getFullYear dob) 67) (.getMonth dob) (.getDate dob)))) | |
| ) | |
| ;;if dob is greater than "1977-04-06", then state pension age is 68 | |
| (if(<(js/Date. "1977-04-06") dob ) | |
| ;;age to get state pension is 68 | |
| ((swap! pension-age assoc :age 68) | |
| ;;add 68 to dob | |
| (swap! pension-age assoc :pensiondate (js/Date. (+ (.getFullYear dob) 68) (.getMonth dob) (.getDate dob)))) | |
| ) | |
| ) | |
| ) | |
| ;;maths calculator component | |
| ;;atom storing the data state of the maths calc component | |
| (defonce result-data (r/atom {:number 0 :result nil :operator nil})) | |
| ;;function to calculate result based on operator and number | |
| ;;using destructuring to map each value from the atom to a variable | |
| (defn calc-result [] | |
| (let [{:keys [number result operator]} @result-data] | |
| ;; multiplying the result and number by 1 to force cast to number before adding the numbers, otherwise the numbers were concatenated | |
| (if (= operator "+") (swap! result-data assoc :result (+ (* 1 result) (* 1 number)))) | |
| ;;if substract sign is selected, the number is substracted from the result | |
| (if (= operator "-") (swap! result-data assoc :result (- result number ))) | |
| ;;if multiply sign is selected, the number is multipled with the result | |
| (if (= operator "*") (swap! result-data assoc :result (* result number ))) | |
| ;;if divide sign is selected, the result is divided with the number | |
| (if (= operator "/") (swap! result-data assoc :result (/ result number ))) | |
| (swap! result-data assoc :number 0) | |
| )) | |
| ;;BMI Calculator components | |
| (defonce bmi-data (r/atom {:height 180 :weight 80})) | |
| ;;function to calculate the bmi | |
| ;;destructuring used to map each atom value to a variable | |
| ;;in addition, the variable h is set to height/100 | |
| (defn calc-bmi [] | |
| (let [{:keys [height weight bmi] :as data} @bmi-data | |
| h (/ height 100)] | |
| (if (nil? bmi) | |
| ;;update the key bmi value with weight/ (h*h) is bmi is nil | |
| (assoc data :bmi (/ weight (* h h))) | |
| ;;if bmi is not nil, update the key weight with bmi * h * h | |
| (assoc data :weight (* bmi h h))))) | |
| ;;slider child component which can be referenced in other components | |
| (defn slider [param value min max] | |
| [:input {:type "range" :value value :min min :max max | |
| :style {:width "100%"} | |
| :on-change (fn [e] | |
| ;;swap! is used to update the atom object | |
| (swap! bmi-data assoc param (.-target.value e)) | |
| ;;if bmi has not been updated set it to nil in the atom object | |
| (when (not= param :bmi) | |
| (swap! bmi-data assoc :bmi nil)))}]) | |
| ;; ————————- | |
| ;; Views | |
| ;;navigation view | |
| ;;navigation-view will be shared by three calculator components; Maths, BMI and Pension Age | |
| (defn navigation-view [] | |
| [:div | |
| [:a {:href "/"} "Maths "] | |
| [:a {:href "/BMI"} " BMI"] | |
| [:a {:href "/Pensionage"} " PensionAge"] | |
| ]) | |
| ;;Home Page is the Maths Calculator | |
| ;; using the atom object to display the data on the UI | |
| (defn home-page [] | |
| (let [{:keys [number result operator]} @result-data] | |
| ;;Reagent uses the hiccup style to write the virtual DOM | |
| [:div | |
| ;;navigation component | |
| [navigation-view] | |
| [:h4 "Maths Calculator"] | |
| ;;input control allowing only numbers to be typed in | |
| [:input {:type "number" | |
| :value number | |
| :on-change (fn [e] | |
| (swap! result-data assoc :number (.-target.value e)) | |
| ) | |
| }] | |
| [:div | |
| [:input {:type "button" | |
| :value "+" | |
| :on-click (fn [e] | |
| (swap! result-data assoc :operator (.-target.value e)) | |
| (if(nil? result) | |
| ((swap! result-data assoc :result number) | |
| (swap! result-data assoc :number 0))) | |
| ) | |
| }] | |
| [:input {:type "button" | |
| :value "-" | |
| :on-click (fn [e] | |
| (swap! result-data assoc :operator (.-target.value e)) | |
| (if(nil? result) | |
| ((swap! result-data assoc :result number) | |
| (swap! result-data assoc :number 0))) | |
| ) | |
| }] | |
| [:input {:type "button" | |
| :value "/" | |
| :on-click (fn [e] | |
| (swap! result-data assoc :operator (.-target.value e)) | |
| (if(nil? result) | |
| ((swap! result-data assoc :result number) | |
| (swap! result-data assoc :number 0))) | |
| ) | |
| }] | |
| [:input {:type "button" | |
| :value "*" | |
| :on-click (fn [e] | |
| (swap! result-data assoc :operator (.-target.value e)) | |
| (if(nil? result) | |
| ((swap! result-data assoc :result number) | |
| (swap! result-data assoc :number 0))) | |
| ) | |
| }] | |
| [:input {:type "button" | |
| :value "=" | |
| :on-click calc-result | |
| }] | |
| ] | |
| [:p result]])) | |
| ;;BMI page component | |
| ;;the data of the component is provided by function calc-bmi. Futhermore depending on the BMI value, the color and diagnose variables are set | |
| (defn bmi-page [] | |
| (let [{:keys [weight height bmi]} (calc-bmi) | |
| [color diagnose] (cond | |
| (< bmi 18.5) ["orange" "underweight"] | |
| (< bmi 25) ["inherit" "normal"] | |
| (< bmi 30) ["orange" "overweight"] | |
| :else ["red" "obese"])] | |
| [:div | |
| ;;instantiate navigation-view | |
| [navigation-view] | |
| [:h4 "BMI calculator"] | |
| [:div | |
| "Height: " (int height) "cm" | |
| ;;instantiate slider component with properties :height height 100 220 | |
| [slider :height height 100 220]] | |
| [:div | |
| "Weight: " (int weight) "kg" | |
| ;;instantiate slider component with properties :weight weight 30 150 | |
| [slider :weight weight 30 150]] | |
| [:div | |
| "BMI: " (int bmi) " " | |
| [:span {:style {:color color}} diagnose] | |
| ;;instantiate slider component with properties :bmi bmi 10 50 | |
| [slider :bmi bmi 10 50]]])) | |
| ;;pension-page component | |
| (defn pension-page [] | |
| (let [{:keys [dob pensiondate age]} @pension-age] | |
| [:div | |
| [navigation-view] | |
| [:div [:h4 "State Pension Age Calculator"] | |
| [:p "Enter Date Of Birth"] | |
| ;;using input control of type date to allow a date to be picked | |
| [:input {:type "date" | |
| :on-change (fn [e] (swap! pension-age assoc :dob (js/Date. (.-target.value e)))) | |
| }] | |
| ;; button to cal pension age | |
| [:input {:type "button" | |
| :value "Pension Age" | |
| :on-click calc-pensionage | |
| }] | |
| [:p "You will reach pension age at " age " as from " (get-date! pensiondate) ]]])) | |
| ;;using session from reagent to store session of the page | |
| (defn current-page [] | |
| [:div [(session/get :current-page)]]) | |
| ;; ————————- | |
| ;; Routes with secretary library | |
| (secretary/defroute "/" [] | |
| (session/put! :current-page #'home-page)) | |
| (secretary/defroute "/BMI" [] | |
| (session/put! :current-page #'bmi-page)) | |
| (secretary/defroute "/Pensionage" [] | |
| (session/put! :current-page #'pension-page)) | |
| ;; ————————- | |
| ;; Initialize app | |
| (defn mount-root [] | |
| ;;render from Reagent | |
| (r/render [current-page] (.getElementById js/document "app"))) | |
| ;;accountant library used together with secretary to make navigation easier | |
| (accountant/configure-navigation!) | |
| (accountant/dispatch-current!) | |
| (mount-root) |
To run the application type
lein figwheel

From the browser navigate to http://localhost:3449
The Maths Calculator is displayed by default

Click on BMI link to load the BMI calculator

The BMI calculation code has been taken from “Reagent: Minimalistic React for ClojureScript”
Click on Pension Age link to load Pension Age calculator









