Skip to content

A small library to be used with Reagent to define control states based on your app state.

Notifications You must be signed in to change notification settings

colinkahn/control-state

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 

Repository files navigation

control-state

A small library to be used with Reagent to define control states based on your app state.

Usage

First, create a control state based on a reagent state atom:

(ns my-app.core
    (:require [reagent.core :as r]
              [control-state.core :as c]))

(defonce app-state (r/atom {}))

(defonce ctrl-state (c/ctrl-state app-state))

This will give you a CtrlState, which implements IDeref as well as the ISynchronized protocols. Don't worry about what ISynchronized does, the important part is that you can deref it in your components.

Let's define our first control state predicate. We'll pretend this is an app that launches a rocket (see here and here for why this example was chosen). We'll give it some initial state as well:

(def counter-max 10)

(defonce app-state (r/atom {:count counter-max
                            :started false
                            :launched false
                            :aborted false}))

(defonce ctrl-state (c/ctrl-state app-state))

(c/reg-pred
  :ready
  #(and (= (:count %) counter-max)
        (not (:started %))
        (not (:launched %))
        (not (:aborted %))))

Now, when we deref ctrl-state it will return :ready. We can use this in our views to enable or disable actions available to the user.

(defn view []
  (let [state @app-state
        ctrl @ctrl-state]
    [:div
      (condp = ctrl
        :ready
        [:button "Start Countdown"]

        :counting
        [:h1 (:count state)]
        
        :launched
        [:h1 "Launched!"]
        
        :aborted
        [:h1 "Aborted"]
        
        :else
        "Invalid state")
      ]))

Let's define the other control state predicates as well:

(c/reg-pred
  :counting
  #(and (<= (:count %) counter-max)
        (:started %)
        (not (:launched %))
        (not (:aborted %))))

(c/reg-pred
  :launched
  #(and (zero? (:count %))
        (:started %)
        (:launched %)
        (not (:aborted %))))

(c/reg-pred
  :aborted
  #(and (<= (:count %) counter-max)
        (not (zero? (:count %)))
        (:started %)
        (not (:launched %))
        (:aborted %)))

Let's add some more controls. We'll use these to update our app-state:

(defn view []
  (let [state @app-state
        ctrl @ctrl-state]
    [:div
      (condp = ctrl
        :ready
        [:button {:on-click #(swap! app-state assoc :started true)} "Start Countdown"]

        :counting
        [:div
          [:h1 (:count state)]
          [:button {:on-click #(swap! app-state assoc :aborted true)} "Abort"]]
        
        :launched
        [:h1 "Launched!"]
        
        :aborted
        [:h1 "Aborted"]
        
        :else
        "Invalid state")
      ]))

Finally, we need something to do the actual countdown. We'll use a next action predicate (nap) to start the countdown after we enter the counting state:

(c/reg-nap
  :counting
  (fn [state]
    (if (zero? (:count state))
      (swap! app-state assoc :launched true)
      (js/setTimeout #(swap! app-state update :count dec) 1000))))

API

ctrl-state

Takes a reagent atom src-atom and returns a CtrlState that when dereference returns an equivalent control state keyword.

reg-pred

Takes a control state key k and a predicate f and registers them to be checked against when the app state updates.

reg-nap

Takes a control state key k and a next action predicate f and registers them to be called when k becomes the current control state.

reg-dests

Takes a control state key k and a set of destination control states s and registers them to be used to determine a valid next control state from which the control state k can transition to. If destinations are not defined for a control state then all registered control state predicates are checked against.

This is mainly here for performance tweaking, and can be ignored otherwise.

is-state?

Takes an app state m and a control state k and checks to see if they are equivalent.

About

A small library to be used with Reagent to define control states based on your app state.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published