Liking cljdoc? Tell your friends :D

Fulcro RAD Statecharts

Status

fulcro rad statecharts

This library requires Fulcro 3.9.3+ and Clojure 1.11.4+. Everything is CLJC, enabling headless testing of the full application lifecycle without a browser.

Why Statecharts?

Fulcro RAD originally used UI State Machines (UISM) for form and report lifecycle and Dynamic Router for navigation. This worked, but had limitations:

  • UISMs are ad-hoc — they lack formal semantics, making complex state logic hard to reason about.

  • Dynamic Router couples routing to components — route segments are declared on components, mixing concerns.

  • Testing required a browser — CLJS-only code paths made headless testing difficult.

Statecharts provide formal state management with hierarchical states, guards, and actions. Combined with statechart-based routing, all lifecycle and navigation logic is testable from a plain Clojure REPL.

Quick Start

1. Define Attributes

Attributes are unchanged from standard RAD:

(ns com.example.model.account
  (:require
   [com.fulcrologic.rad.attributes :as attr :refer [defattr]]
   [com.fulcrologic.rad.attributes-options :as ao]))

(defattr id :account/id :uuid
  {ao/identity? true
   ao/schema    :production})

(defattr name :account/name :string
  {ao/identities #{:account/id}
   ao/schema     :production})

2. Define a Form

defsc-form works the same as before. The macro generates statechart options automatically:

(ns com.example.ui.account-forms
  (:require
   [com.fulcrologic.rad.form :as form]
   [com.fulcrologic.rad.form-options :as fo]
   [com.example.model.account :as account]))

(form/defsc-form AccountForm [this props]
  {fo/id           account/id
   fo/attributes   [account/name]
   fo/route-prefix "account"
   fo/title        "Edit Account"})

3. Define a Report

defsc-report also works the same:

(ns com.example.ui.account-report
  (:require
   [com.fulcrologic.rad.report :as report]
   [com.fulcrologic.rad.report-options :as ro]
   [com.example.model.account :as account]))

(report/defsc-report AccountReport [this props]
  {ro/title      "Accounts"
   ro/source-attribute :account/all-accounts
   ro/row-pk     account/id
   ro/columns    [account/name]
   ro/route-prefix "accounts"})

4. Define the Routing Chart

Instead of Dynamic Router, you declare a statechart that defines all routes:

(ns com.example.ui.ui
  (:require
   [com.fulcrologic.fulcro.components :as comp :refer [defsc]]
   [com.fulcrologic.rad.routing :as rroute]
   [com.fulcrologic.statecharts.chart :refer [statechart]]
   [com.fulcrologic.statecharts.integration.fulcro.routing :as scr]))

;; A container component that renders the current route
(defsc Routes [this props]
  {:query                   [:ui/placeholder]
   :preserve-dynamic-query? true
   :initial-state           {}
   :ident                   (fn [] [:component/id ::Routes])}
  (scr/ui-current-subroute this comp/factory))

(def routing-chart
  (statechart {:initial :state/route-root}
    (scr/routing-regions
      (scr/routes {:id :state/root :routing/root `Routes}

        ;; Landing page (plain route state)
        (scr/rstate {:route/target `LandingPage})

        ;; Reports
        (rroute/report-route-state {:route/target `AccountReport})

        ;; Forms (with route params for the entity id)
        (rroute/form-route-state {:route/target `AccountForm
                                  :route/params #{:account/id}})))))

5. Bootstrap the Application

(ns com.example.system
  (:require
   [com.example.ui.ui :refer [routing-chart]]
   [com.fulcrologic.rad.application :as rad-app]
   [com.fulcrologic.rad.rendering.headless.plugin]))  ;; or your UI plugin

(defn start! [app]
  ;; 1. Install statechart infrastructure
  (rad-app/install-statecharts! app)
  ;; 2. Start routing with your chart
  (rad-app/start-routing! app routing-chart)
  ;; 3. (CLJS only) Enable URL synchronization
  #?(:cljs (rad-app/install-url-sync! app)))

6. Navigate

Use com.fulcrologic.rad.routing for all navigation:

(require '[com.fulcrologic.rad.routing :as rroute])

;; Navigate to a report
(rroute/route-to! this AccountReport)

;; Edit an existing entity
(rroute/edit! this AccountForm account-id)

;; Create a new entity
(rroute/create! this AccountForm)

Routing API

com.fulcrologic.rad.routing is the recommended namespace for RAD applications. It provides:

  • route-to! — Navigate to any route target (form, report, or plain component).

  • edit! — Route to a form and start editing an existing entity.

  • create! — Route to a form and start creating a new entity.

  • back! / route-forward! — Browser history navigation.

  • force-continue-routing! — Override a route-denied guard (e.g., unsaved changes).

  • abandon-route-change! — Cancel a denied route change.

For advanced use, com.fulcrologic.statecharts.integration.fulcro.routing (scr/) provides the lower-level statecharts routing API. Most RAD applications should use rroute/ exclusively.

Rendering Plugins

The old install-ui-controls! pattern has been replaced by multimethods. Rendering plugins register via defmethod on:

  • fr/render-field — Field rendering (by [type style])

  • fr/render-form — Form layout rendering

  • rr/render-report — Report layout rendering

  • form/render-element — Generic form element rendering (by [element style])

  • control/render-control — Control rendering (by [control-type style])

Simply requiring the plugin namespace is sufficient — no explicit install-ui-controls! call needed.

A headless rendering plugin is included at com.fulcrologic.rad.rendering.headless.plugin for testing.

Demo Application

A complete working demo is included in src/demo/. It shows:

  • Attribute definitions (com.example.model.*)

  • Form and report definitions (com.example.ui.*)

  • Routing chart with forms, reports, and a landing page

  • Bootstrap sequence with install-statecharts! and start-routing!

  • Both CLJS (browser) and CLJ (headless test) entry points

Migration from fulcro-rad

If you are migrating from the original fulcro-rad, see the migration guide.

Support

Questions, issue reports, or requests should go through the #fulcro channel of Clojurians Slack.

Paid support is available from Fulcrologic, LLC.

License

The MIT License (MIT) Copyright (c), Fulcrologic, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close