A binary clock

2014-02-26
8
4
2
1
2
1
3
2
2
8
Click to toggle 1/100th seconds.

Fredrik Dyrkell wrote a very nice binary clock using Om. I thought I’d replicate that using Reagent for fun (another re-write, using Hoplon, can be seen here).

So, without further ado, here is a binary clock using Reagent.

(ns example
  (:require [reagent.core :as r]))

We start with the basics: The clock is built out of cells, with a light colour if the bit the cell corresponds to is set.

(defn cell [n bit]
  [:div.clock-cell {:class (if (bit-test n bit)
                             "light"
                             "dark")}])

Cells are combined into columns of four bits, with a decimal digit at the bottom.

(defn column [n]
  [:div.clock-col
   [cell n 3]
   [cell n 2]
   [cell n 1]
   [cell n 0]
   [:div.clock-cell n]])

Columns are in turn combined into pairs:

(defn column-pair [n]
  [:div.clock-pair
   [column (quot n 10)]
   [column (mod n 10)]])

We'll also need the legend on the left side:

(defn legend [& items]
  (into [:div.clock-col.clock-legend]
        (map (partial vector :div.clock-cell)
             items)))

We combine these element into a component that shows the legend, hours, minutes and seconds; and optionally 1/100 seconds. It also responds to clicks.

(defn clock [date show-100s toggle-100s]
  [:div.clock-main {:on-click toggle-100s
                    :class (when show-100s "wide")}
   [legend 8 4 2 1]
   [column-pair (.getHours date)]
   [column-pair (.getMinutes date)]
   [column-pair (.getSeconds date)]
   (when show-100s
     [column-pair (-> (.getMilliseconds date)
                      (quot 10))])])

We also need to keep track of the time, and of the detail shown, in a Reagent atom. And a function to update the time.

(def clock-state (r/atom {:time (js/Date.)
                          :show-100s false}))

(defn update-time []
  (swap! clock-state assoc :time (js/Date.)))

And finally we use the clock component. The current time is scheduled to be updated, after a suitable delay, every time the main component is rendered (reagent.core/next-tick is just a front for requestAnimationFrame):

(defn main []
  (let [{:keys [time show-100s]} @clock-state]
    (if show-100s
      (r/next-tick update-time)
      (js/setTimeout update-time 1000))
    [clock time show-100s
     #(swap! clock-state update-in [:show-100s] not)]))

The entire source is also available here.

How it all works

Reading through the source, it may look like the entire clock component is recreated from scratch whenever the time changes.

That is an illusion: Reagent and React together makes sure that only the parts of the DOM that actually need to change are updated. For example, the column-pair function corresponding to hours only runs once every hour.

And that’s what makes Reagent and React fast. Try clicking on the clock to toggle the display of 1/100th seconds. Most browsers should have no trouble at all keeping up (even if they won’t actually show every 1/100th second: they are typically limited to roughly 60 fps).

But it is a very handy illusion. Almost the entire UI is made up of pure functions, transforming immutable data into other immutable data structures. That makes them easy to reason about, and trivial to test. You don’t have to care about ”model objects”, or about how to update the DOM efficiently.

Just pass arguments to component functions, return a UI description that corresponds to those arguments, and leave it to React to actually display that UI.

Fork me on GitHub