Counter application example for Keechma framework.


(this space intentionally left almost blank)
(ns keechma-counter.main-component
  (:require [keechma.ui-component :as ui]))

Increments the counter if the current value is odd.

(defn inc-if-odd
  [ctx counter-val]
  (when (odd? counter-val)
    (ui/send-command ctx :inc)))

Increments the counter after 1 second.

(defn inc-async
  (.setTimeout js/window #(ui/send-command ctx :inc) 1000))

Main renderer function. ctx argument is partially applied when the application is started.

First we resolve the counter-sub subscription which will hold the current counter value.

User can do one of four actions:

  1. Increment the counter
  2. Decrement the counter
  3. Increment the counter if the current value is odd
  4. Increment the counter in async way (after 1 second)

Each of these actions sends the command to the controller by calling the `(ui/send-command ctx :command) function.

(defn render
  (let [counter-sub (ui/subscription ctx :counter-value)]
    (fn []
       (str "Clicked: " @counter-sub " times ")
       [:button {:on-click #(ui/send-command ctx :inc)} "+"]
       " "
       [:button {:on-click #(ui/send-command ctx :dec)} "-"]
       " "
       [:button {:on-click #(inc-if-odd ctx @counter-sub)} "Increment if odd"]
       " "
       [:button {:on-click #(inc-async ctx)} "Increment async"]])))

Definition of the component. This component depends on one subscription: counter-value. This subscription will be resolved on the record when the application is started.

After that the record will be partially applied to the :renderer function which will allow it to resolve it from the function body and read the value.

(def component
  (ui/constructor {:renderer render
                   :subscription-deps [:counter-value]}))
(ns keechma-counter.core
  (:require [keechma-counter.counter-controller :as counter]
            [keechma-counter.main-component :as main-component]
            [ :as app-state])
  (:require-macros [reagent.ratom :refer [reaction]]))

Subscription that returns the current value of the counter.

(defn counter-value-sub
   (get-in @app-state [:kv :counter])))

Definition of the application.

  • :controllers param holds all of the controllers needed to run the app
  • Counter controller is registered under the :counter key. Main component has the :counter :topic assoc-ed to it which allows it to send the commands to the Counter controller.
  • :component param holds all the component needed to render the app
  • :subscriptions param holds the application subscriptions
(def app-definition
  {:controllers {:counter (counter/->Controller)}
   :components {:main (assoc main-component/component :topic :counter)}
   :subscriptions {:counter-value counter-value-sub}
   :html-element (.getElementById js/document "app")})
(defonce running-app (clojure.core/atom))

Helper function that starts the application.

(defn start-app!
  (reset! running-app (app-state/start! app-definition)))

Helper function that restarts the application whenever the code is hot reloaded.

(defn restart-app!
  (let [current @running-app]
    (if current
      (app-state/stop! current start-app!)
(defn on-js-reload []
  ;; optionally touch your app-state to force rerendering depending on
  ;; your application
  ;; (swap! app-state update-in [:__figwheel_counter] inc))
(ns keechma-counter.counter-controller
  (:require [keechma.controller :as controller]
            [cljs.core.async :refer [<!]])
  (:require-macros [cljs.core.async.macros :refer [go]]))

Implements the counter controller.

  • params function returns true because this controller should be always running.
  • start function sets the default counter value (0). :kv is used to store any key - value pairs
  • handler function is waiting for commands on the in-chan. When the command comes (if it's :inc or :dec) it will increment or decrement the counter.
  Controller []
  (params [_ _] true)
  (start [_ params app-db]
    (assoc-in app-db [:kv :counter] 0))
  (handler [_ app-db-atom in-chan _] 
    (controller/dispatcher app-db-atom in-chan
                           {:inc #(swap! app-db-atom update-in [:kv :counter] inc)
                            :dec #(swap! app-db-atom update-in [:kv :counter] dec)})))