### Clojure: Example of Starting a UI State Machine Instance Source: https://book.fulcrologic.com/index Provides practical examples of how to use `uism/begin!` to start a state machine. It demonstrates configuring the `actor-map` with singleton components and with dynamic actors using `with-actor-class` or `this` (component instance). ```Clojure (uism/begin! this login-machine ::loginsm {:actor/dialog Dialog :actor/session Session :actor/form LoginForm}) ``` ```Clojure (uism/begin! this person-editing-machine ::personsm {:person (uism/with-actor-class [:person/id 3] Person) :editor this :dialog Dialog}) ``` -------------------------------- ### Verify Clojure CLI Installation Source: https://book.fulcrologic.com/index This command demonstrates how to start the Clojure REPL (Read-Eval-Print Loop) using the `clj` command-line tool, confirming a successful installation of Clojure CLI Tools. The expected output shows the Clojure version and the `user=>` prompt, indicating readiness for Clojure development. ```Shell $ clj Clojure 1.10.0 user=> ``` -------------------------------- ### Initialize Node.js Project and Install Dependencies Source: https://book.fulcrologic.com/index Commands to create the basic project directory structure, initialize a new npm project, and install essential JavaScript dependencies like `shadow-cljs`, `react`, and `react-dom`. ```bash mkdir app cd app mkdir -p src/main src/dev resources/public npm init npm install shadow-cljs react react-dom --save ``` -------------------------------- ### Querying from a Pre-Selected Starting Entity Source: https://book.fulcrologic.com/index This example shows an alternative way to use `fdn/db->tree` by pre-selecting a specific entity from the `sample-db` (e.g., `[:person/id 1]`) and providing it as the `starting-entity`. This allows querying properties relative to that specific entity, demonstrating how Fulcro performs localized refreshes by pulling an entity and then applying its query. ```Clojure (let [starting-entity (get-in sample-db [:person/id 1])] (fdn/db->tree [:person/name] starting-entity sample-db)) => #:person{:name "Bob"} ``` -------------------------------- ### Basic Fulcro Server Setup for Server-Side Rendering (Clojure) Source: https://book.fulcrologic.com/index This Clojure code defines a basic Fulcro server that supports server-side rendering (SSR) for dynamic routes. It includes functions to build the initial UI tree, perform the actual SSR into an HTML string, and configure the `make-fulcro-server` with extra routes using Ring responses. Critical steps for installing and routing to dynamic components are highlighted. ```Clojure (ns ssr-dynamic-routing.server (:require [ring.util.response :as resp] [fulcro.easy-server :refer [make-fulcro-server]] [fulcro.server-render :as ssr] [ssr-dynamic-routing.ui.other :as other] [ssr-dynamic-routing.ui.root :as root] [com.fulcrologic.fulcro.dom-server :as dom] [com.fulcrologic.fulcro.components :as comp] [com.fulcrologic.fulcro.algorithms.denormalize :as fdn] [com.fulcrologic.fulcro.routing.legacy-ui-routers :as r])) (defn build-ui-tree [match] (let [client-db (ssr/build-initial-state (comp/get-initial-state root/Root {}) root/Root) final-db (-> client-db ;; CRITICAL: Install the routes, or their state won't be in the db (r/install-route* :main root/Main) (r/install-route* :other other/Other) (r/route-to* match))] ;; CRITICAL: Pass the final database to get-query!!! Or you won't get the updated dynamic query (fdn/db->tree (comp/get-query root/Root final-db) final-db final-db))) (defn server-side-render [env {:keys [handler] :as match}] (let [ui-tree (build-ui-tree match) html (dom/render-to-str ((comp/factory root/Root) ui-tree))] (-> (resp/response (str "
" html "
")) (resp/content-type "text/html")))) (defn build-server [{:keys [config] :or {config "config/dev.edn"}}] (make-fulcro-server :parser-injections #{:config} ;; Quick way to hack a couple of pages in with different match handlers :extra-routes {:routes ["/" {"main.html" :main "other.html" :other}] :handlers {:main server-side-render :other server-side-render}} :config-path config)) ``` -------------------------------- ### APIDOC: uism/begin! - Start a Fulcro UI State Machine Instance Source: https://book.fulcrologic.com/index Documents the `uism/begin!` function used to initialize and start a UI state machine instance in Fulcro. It explains parameters like `app-or-component`, `machine-def`, `instance-id`, and `actor-map`, and notes its transactional nature and storage mechanism. ```APIDOC (uism/begin! app-or-component machine-def instance-id actor-map) app-or-component: The Fulcro application or component context. machine-def: The definition of the state machine. instance-id: A unique keyword identifier for the state machine instance. actor-map: A map defining UI components (actors) the state machine interacts with. ``` -------------------------------- ### Complete Fulcro UI Routing and Data Loading Example Source: https://book.fulcrologic.com/index A comprehensive Fulcro example showcasing client-side UI routing and server-side data resolution. It includes namespace declarations, a Pathom resolver for settings data, and client-side components (`SomeSetting`, `SettingsTab`, `MainTab`, `UITabs`) demonstrating how data is fetched and displayed based on UI navigation. ```Clojure (ns book.demos.loading-in-response-to-UI-routing (:require [com.fulcrologic.fulcro.routing.legacy-ui-routers :as r] [com.fulcrologic.fulcro.mutations :as m] [com.fulcrologic.fulcro.dom :as dom] [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.data-fetch :as df] [com.wsscode.pathom.connect :as pc])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SERVER: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (pc/defresolver all-settings-resolver [env input] {::pc/output [{::all-settings [:id :value]}]} {::all-settings [{:id 1 :value "Gorgon"} {:id 2 :value "Thraser"} {:id 3 :value "Under"}]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CLIENT: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defsc SomeSetting [this {:keys [id value]}] {:query [:ui/fetch-state :id :value] :ident [:setting/by-id :id]} (dom/p nil "Setting " id " from server has value: " value)) (def ui-setting (comp/factory SomeSetting {:keyfn :id})) (defsc SettingsTab [this {:keys [settings-content settings]}] {:initial-state {:kind :settings :settings-content "Settings Tab" :settings []} ; This query uses a "link"...a special ident with '_ as the ID. This indicates the item is at the database ; root, not inside of the "settings" database object. This is not needed as a matter of course...it is only used ; for convenience (since it is trivial to load something into the root of the database) :query [:kind :settings-content {:settings (comp/get-query SomeSetting)}]} (dom/div nil settings-content (if (seq settings) (mapv ui-setting settings) (dom/div "No settings.")))) (defsc MainTab [this {:keys [main-content]}] {:initial-state {:kind :main :main-content "Main Tab"} :query [:kind :main-content]} (dom/div nil main-content)) (r/defsc-router UITabs [this props] {:router-id :ui-router :ident (fn [] [(:kind props) :tab]) :default-route MainTab :router-targets {:main MainTab }) ``` -------------------------------- ### Complete Fulcro Application with Single Counter Pre-merge Example Source: https://book.fulcrologic.com/index A comprehensive example demonstrating a Fulcro application with both server-side (Pathom resolver) and client-side components. It showcases how a single counter component uses the `:pre-merge` hook for data initialization and how it integrates with data loading (`df/load!`) and state management. ```Clojure (ns book.demos.pre-merge.countdown (:require [com.fulcrologic.fulcro.data-fetch :as df] [book.demos.util :refer [now]] [com.fulcrologic.fulcro.mutations :as m] [com.fulcrologic.fulcro.dom :as dom] [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.wsscode.pathom.connect :as pc])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SERVER: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def all-counters [{::counter-id 1 ::counter-label "A"}]) (pc/defresolver counter-resolver [env {::keys [counter-id]}] {::pc/input #{::counter-id} ::pc/output [::counter-id ::counter-label]} (let [{:keys [id]} (-> env :ast :params)] (first (filter #(= id (::counter-id %)) all-counters)))) (def resolvers [counter-resolver]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CLIENT: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defsc Countdown [this {::keys [counter-label] :ui/keys [count]}] {:ident [::counter-id ::counter-id] :query [::counter-id ::counter-label :ui/count] :pre-merge (fn [{:keys [current-normalized data-tree]}] (merge {:ui/count 5} current-normalized data-tree))} (dom/div (dom/h4 counter-label) (let [done? (zero? count)] (dom/button {:disabled done? :onClick #(m/set-value! this :ui/count (dec count))} (if done? "Done!" (str count)))))) (def ui-countdown (comp/factory Countdown {:keyfn ::counter-id})) (defsc Root [this {:keys [counter]}] {:initial-state (fn [_] {}) :query [{:counter (comp/get-query Countdown)}]} (dom/div (dom/h3 "Counters") (if (seq counter) (ui-countdown counter) (dom/button {:onClick #(df/load! this [::counter-id 1] Countdown {:target [:counter]})} "Load one counter")))) (defn initialize "To be used in :started-callback to pre-load things." [app]) ``` -------------------------------- ### Install and Navigate Dynamic Routes in Fulcro 3 Source: https://book.fulcrologic.com/index This snippet demonstrates how to dynamically install routes for a Fulcro 3 application and then immediately navigate to a specific route. It utilizes `comp/transact!` to perform the operations, registering `:new-user` and `:login` routes with their respective components and then routing to the `:login` page. ```Clojure (comp/transact! app [(r/install-route {:target-kw :new-user :component NewUser}) (r/install-route {:target-kw :login :component Login}) (r/route-to {:handler :login})]) ``` -------------------------------- ### Fulcro Network Activity Simulation and UI Example Source: https://book.fulcrologic.com/index A comprehensive Clojure example demonstrating Fulcro's network activity tracking. It includes a simulated Pathom resolver, an `ActivityIndicator` component to display active remotes, and a `Root` component that triggers a data load, allowing observation of the network status in real-time. ```Clojure (ns book.server.network-activity (:require [com.fulcrologic.fulcro.application :as app] [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.dom :as dom] [com.fulcrologic.fulcro.mutations :as m :refer [defmutation]] [com.wsscode.pathom.connect :as pc] [taoensso.timbre :as log] [clojure.pprint :refer [pprint]] [com.fulcrologic.fulcro.data-fetch :as df])) ;; Simulated server (pc/defresolver silly-resolver [_ _] {::pc/output [::data]} {::data 42}) ;; Client (defsc ActivityIndicator [this props] {:query [[::app/active-remotes '_]] :ident (fn [] [:component/id ::activity]) :initial-state {}} (let [active-remotes (::app/active-remotes props)] (dom/div (dom/h3 "Active Remotes") (dom/pre (pr-str active-remotes))))) (def ui-activity-indicator (comp/factory ActivityIndicator {:keyfn :id})) (defsc Root [this {:keys [indicator]}] {:query [{:indicator (comp/get-query ActivityIndicator)}] :initial-state {:indicator {}}} (dom/div {} (dom/p {} "Use the server controls to slow down the network, so you can see the activity") (dom/button {:onClick #(df/load! this ::data nil)} "Trigger a Load") (ui-activity-indicator indicator))) ``` -------------------------------- ### Fulcro Pathom Mock Server and API Definitions Source: https://book.fulcrologic.com/index This code defines a mock server-side database and Pathom Connect resolvers and mutations for a login system. It includes `account-resolver` to fetch account details, `session-resolver` to get the current session, `server-login` to authenticate users, and `server-logout` to clear the session. This setup simulates backend API interactions directly within the client for demonstration purposes. ```Clojure (ns book.raw.raw-uism (:require [com.fulcrologic.fulcro.algorithms.data-targeting :as dt] [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.dom :as dom :refer [div p input button h2 label]] [com.fulcrologic.fulcro.dom.events :as evt] [com.fulcrologic.fulcro.mutations :as m] [com.fulcrologic.fulcro.raw.components :as rc] [com.fulcrologic.fulcro.react.hooks :as hooks] [com.fulcrologic.fulcro.ui-state-machines :as uism] [com.wsscode.pathom.connect :as pc] [taoensso.timbre :as log])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Mock Server and database, in Fulcro client format for ease of use in demo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defonce pretend-server-database (atom {:account/id {1000 {:account/id 1000 :account/email "bob@example.com" :account/password "letmein"}}})) ;; For the UISM DEMO (defonce session-id (atom 1000)) ; pretend like we have server state to remember client (pc/defresolver account-resolver [_ {:account/keys [id]}] {::pc/input #{:account/id} ::pc/output [:account/email]} (select-keys (get-in @pretend-server-database [:account/id id] {}) [:account/email])) (pc/defresolver session-resolver [_ {:account/keys [id]}] {::pc/output [{:current-session [:account/id]}]} (if @session-id {:current-session {:account/id @session-id}} {:current-session {:account/id :none}})) (pc/defmutation server-login [_ {:keys [email password]}] {::pc/sym `login ::pc/output [:account/id]} (let [accounts (vals (get @pretend-server-database :account/id)) account (first (filter (fn [a] (and (= password (:account/password a)) (= email (:account/email a)))) accounts))] (when (log/spy :info "Found account" account) (reset! session-id (:account/id account)) account))) (pc/defmutation server-logout [_ _] {::pc/sym `logout} (reset! session-id nil)) (def resolvers [account-resolver session-resolver server-login server-logout]) ``` -------------------------------- ### Require Correct Target in Application Entry Points Source: https://book.fulcrologic.com/index These examples show how the main entry point for each platform (e.g., `app.browser.main` or `app.native.main`) requires its corresponding target namespace (`app.targets.browser` or `app.targets.native`). This ensures that the correct platform-specific API implementations are initialized and available when the application starts. ```Clojure (ns app.browser.main (:require app.targets.browser)) ... ``` ```Clojure (ns app.native.main (:require app.targets.native)) ... ``` -------------------------------- ### Fulcro 3 Autocomplete Root Component Example Source: https://book.fulcrologic.com/index Demonstrates how to instantiate and use the `Autocomplete` component within a parent `AutocompleteRoot` component. It defines the initial state and query necessary for embedding the child component, providing a simple example of its usage in a UI. ```ClojureScript (defsc AutocompleteRoot [this {:keys [airport-input]}] {:initial-state (fn [p] {:airport-input (comp/get-initial-state Autocomplete {:id :airports})}) :query [{:airport-input (comp/get-query Autocomplete)}]} (dom/div (dom/h4 "Airport Autocomplete") (ui-autocomplete airport-input))) ``` -------------------------------- ### Start State Machine with Initial Configuration (Clojure) Source: https://book.fulcrologic.com/index Demonstrates how to initiate a state machine using `uism/begin!`, passing an `actor-map` that contains initial configuration data. This data can then be accessed and stored within the state machine's lifecycle. ```Clojure (uism/begin! this SM ::sm-id actor-map {:other-machine :machine-id}) ``` -------------------------------- ### LOAD CACHE NOT INSTALLED! Did you remember to use with-load-cache on your app? Source: https://book.fulcrologic.com/index - ```APIDOC LOAD CACHE NOT INSTALLED! Did you remember to use with-load-cache on your app? ``` -------------------------------- ### Live Demo of Post Mutations - Morphing Example Source: https://book.fulcrologic.com/index A comprehensive example demonstrating data morphing using Fulcro. This snippet includes namespace declarations, component definitions (`CategoryQuery`, `ItemQuery`, `ToolbarItem`, `ToolbarCategory`, `Toolbar`), sample server response, query definitions, and mutation logic to transform loaded data for UI display. ```Clojure (ns book.server.morphing-example (:require [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.dom :as dom] [book.macros :refer [defexample]] [com.fulcrologic.fulcro.mutations :refer [defmutation]] [com.fulcrologic.fulcro.algorithms.normalize :as fnorm] [com.fulcrologic.fulcro.algorithms.merge :as merge])) (defsc CategoryQuery [this props] {:query [:db/id :category/name] :ident [:categories/by-id :db/id]}) (defsc ItemQuery [this props] {:query [:db/id :item/name {:item/category (comp/get-query CategoryQuery)}] :ident [:items/by-id :db/id]}) (def sample-server-response {:all-items [{:db/id 5 :item/name "item-42" :item/category {:db/id 1 :category/name "A"}} {:db/id 6 :item/name "item-92" :item/category {:db/id 1 :category/name "A"}} {:db/id 7 :item/name "item-32" :item/category {:db/id 1 :category/name "A"}} {:db/id 8 :item/name "item-52" :item/category {:db/id 2 :category/name "B"}}]}) (def component-query [{:all-items (comp/get-query ItemQuery)}]) (def hand-written-query [{:all-items [:db/id :item/name {:item/category [:db/id :category/name]}]}] (defsc ToolbarItem [this {:keys [item/name]}] {:query [:db/id :item/name] :ident [:items/by-id :db/id]} (dom/li name)) (def ui-toolbar-item (comp/factory ToolbarItem {:keyfn :db/id})) (defsc ToolbarCategory [this {:keys [category/name category/items]}] {:query [:db/id :category/name {:category/items (comp/get-query ToolbarItem)}] :ident [:categories/by-id :db/id]} (dom/li name (dom/ul (mapv ui-toolbar-item items)))) (def ui-toolbar-category (comp/factory ToolbarCategory {:keyfn :db/id})) (defmutation group-items-reset [params] (action [{:keys [app state]}] (swap! state (fn [s] (-> s (dissoc :categories/by-id :toolbar/categories) (merge/merge* component-query sample-server-response)))))) (defn add-to-category "Returns a new db with the given item added into that item's category." [db item] (let [category-ident (:item/category item) item-location (conj category-ident :category/items)] (update-in db item-location (fnil conj []) (comp/ident ItemQuery item)))) (defn group-items* "Returns a new db with all of the items sorted by name and grouped into their categories." [db] (let [sorted-items (->> db :items/by-id vals (sort-by :item/name)) category-ids (-> db (:categories/by-id) keys) clear-items (fn [db id] (assoc-in db [:categories/by-id id :category/items] [])) db (reduce clear-items db category-ids) db (reduce add-to-category db sorted-items) all-categories (->> db :categories/by-id vals (mapv #(comp/ident CategoryQuery %)))] (assoc db :toolbar/categories all-categories))) (defmutation ^:intern group-items [params] (action [{:keys [state]}] (swap! state group-items*))) (defsc Toolbar [this {:keys [toolbar/categories]}] {:query [{:toolbar/categories (comp/get-query ToolbarCategory)}]} (dom/div (dom/button {:onClick #(comp/transact! this [(group-items {})])} "Trigger Post Mutation") (dom/button {:onClick #(comp/transact! this [(group-items-reset {})])} "Reset") (dom/ul (mapv ui-toolbar-category categories)))) (defexample "Morphing Data" Toolbar "morphing-example" :initial-db (fnorm/tree->db component-query sample-server-response true)) ``` -------------------------------- ### Fulcro Simple Data Targeting Example Source: https://book.fulcrologic.com/index Demonstrates a simple data targeting mechanism in Fulcro using `df/load!`. This example shows how to load data for `:all-people` and direct the results to a specific path `[:list/id :people :list/people]` in the local UI graph, effectively replacing existing data at that target. ```Clojure (df/load comp :all-people Person {:target [:list/id :people :list/people]}) ``` -------------------------------- ### Verify npm Installation and Local Packages Source: https://book.fulcrologic.com/index This command lists the npm packages installed in the current directory. It is used to confirm that Node.js and npm are correctly set up, which are required by the `shadow-cljs` compiler for managing JavaScript dependencies in Fulcro projects. An empty output indicates no local packages, which is expected in a fresh directory. ```Shell $ npm list /your/directory └── (empty) ``` -------------------------------- ### Fulcro Dynamic Query Example with Toggleable Component State Source: https://book.fulcrologic.com/index A comprehensive example demonstrating dynamic queries in Fulcro. It defines a `Leaf` component whose query can be toggled between `[:x]` and `[:y]` at runtime using `comp/set-query!`. The `Root` component initializes and renders the `Leaf`, showcasing how component state adapts based on the active query. ```ClojureScript (ns book.queries.dynamic-queries (:require [com.fulcrologic.fulcro.dom :as dom] [goog.object] [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.mutations :as m])) (declare ui-leaf) ; This component allows you to toggle the query between [:x] and [:y] (defsc Leaf [this {:keys [x y]}] {:initial-state (fn [params] {:x 1 :y 42}) :query (fn [] [:x]) ; avoid error checking so we can destructure both :x and :y in props :ident (fn [] [:LEAF :ID])} ; there is only one leaf in app state (dom/div (dom/button {:onClick (fn [] (comp/set-query! this ui-leaf {:query [:x]}))} "Set query to :x") (dom/button {:onClick (fn [] (comp/set-query! this ui-leaf {:query [:y]}))} "Set query to :y") ; If the query is [:x] then x will be defined, otherwise it will not. (dom/button {:onClick (fn [e] (if x (m/set-value! this :x (inc x)) (m/set-value! this :y (inc y))))} (str "Count: " (or x y))) ; only one will be defined at a time " Leaf")) (def ui-leaf (comp/factory Leaf {:qualifier :x})) (defsc Root [this {:keys [root/leaf] :as props}] {:initial-state (fn [p] {:root/leaf (comp/get-initial-state Leaf {})}) :query (fn [] [{:root/leaf (comp/get-query ui-leaf)}])} (dom/div (ui-leaf leaf))) ``` -------------------------------- ### Fulcro 3 Parallel vs. Sequential Loading Example Source: https://book.fulcrologic.com/index A comprehensive Clojure/ClojureScript example demonstrating the difference between parallel and sequential network loads in Fulcro 3. It includes a server-side resolver (`long-query-resolver`) and a client-side component (`Child`) with buttons to trigger parallel and sequential `load-field!` calls, illustrating the impact on loading times and the use of markers for loading status. ```Clojure (ns book.demos.parallel-vs-sequential-loading (:require [com.fulcrologic.fulcro.data-fetch :as df] [com.fulcrologic.fulcro.dom :as dom] [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.wsscode.pathom.connect :as pc] [taoensso.timbre :as log])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SERVER: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (pc/defresolver long-query-resolver [_ _] {::pc/output [:background/long-query]} {:background/long-query 42}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CLIENT: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defsc Child [this {:keys [id name background/long-query] :as props}] {:query (fn [] [:id :name :background/long-query [df/marker-table '_]]) :ident [:background.child/by-id :id]} (let [status (get-in props [df/marker-table [:fetching id]])] (dom/div {:style {:display "inline" :float "left" :width "200px"}} (dom/button {:onClick #(df/load-field! this :background/long-query {:parallel true :marker [:fetching id]})} "Load stuff parallel") (dom/button {:onClick #(df/load-field! this :background/long-query {:marker [:fetching id]})} "Load stuff sequential") (dom/div name (if (df/loading? status) (dom/span "Loading...") (dom/span long-query)))))) (def ui-child (comp/factory Child {:keyfn :id})) (defsc Root [this {:keys [children] :as props}] ; cheating a little...raw props used for child, instead of embedding them there. {:initial-state (fn [params] {:children [{:id 1 :name "A"} {:id 2 :name "B"} {:id 3 :name "C"}]}) :query [{:children (comp/get-query Child)}]} (dom/div (mapv ui-child children) (dom/br {:style {:clear "both"}}) (dom/br))) ``` -------------------------------- ### Fulcro Data Loading with `df/load!` Source: https://book.fulcrologic.com/index Illustrates how to use `data-fetch/load` in Fulcro to query the server for data. This specific example targets the `:all-numbers` query and places the result into a specific path within the application's state tree. ```Clojure (df/load! app :all-numbers PhoneDisplayRow {:target [:screen/phone-list :tab :phone-numbers]}) ``` -------------------------------- ### Start shadow-cljs Development Server for Fulcro 3 Source: https://book.fulcrologic.com/index Command to launch the shadow-cljs development server. This server provides a web-based user interface for managing builds, enabling live compilation, and serving the application's static files during development. ```Shell $ npx shadow-cljs server ``` -------------------------------- ### Programmatic Route Change with Dynamic Router Source: https://book.fulcrologic.com/index A simple example demonstrating how to programmatically change the application's route using Fulcro's dynamic router. ```Clojure (dr/change-route app ["main"])) ``` -------------------------------- ### Full Example: Normalizing Tree Data to Fulcro Database Source: https://book.fulcrologic.com/index This comprehensive Fulcro example demonstrates how to define interconnected components with `ident` and `query` definitions. It includes mutations to normalize a tree-structured data (`:from`) into the Fulcro application state database using `fnorm/tree->db` and displays the normalized result, showcasing a common data transformation pattern. ```Clojure (ns book.tree-to-db (:require [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.dom :as dom] [devcards.util.edn-renderer :refer [html-edn]] [com.fulcrologic.fulcro.mutations :as m :refer [defmutation]] [com.fulcrologic.fulcro.algorithms.normalize :as fnorm])) (defsc SubQuery [t p] {:ident [:sub/by-id :id] :query [:id :data]}) (defsc TopQuery [t p] {:ident [:top/by-id :id] :query [:id {:subs (comp/get-query SubQuery)}]}) (defmutation normalize-from-to-result [ignored-params] (action [{:keys [state]}] (let [result (fnorm/tree->db TopQuery (:from @state) true)] (swap! state assoc :result result)))) (defmutation reset [ignored-params] (action [{:keys [state]}] (swap! state dissoc :result))) (defsc Root [this {:keys [from result]}] {:query [:from :result] :initial-state (fn [params] ; some data we're just shoving into the database from root...***not normalized*** {:from {:id :top-1 :subs [{:id :sub-1 :data 1} {:id :sub-2 :data 2}]}})} (dom/div (dom/div (dom/h4 "Pretend Incoming Tree") (html-edn from)) (dom/div (dom/h4 "Normalized Result (click below to normalize)") (when result (html-edn result))) (dom/button {:onClick (fn [] (comp/transact! this [(normalize-from-to-result {})]))} "Normalized (Run tree->db)") (dom/button {:onClick (fn [] (comp/transact! this [(reset {})]))} "Clear Result"))) ``` -------------------------------- ### Complete Fulcro Application with Data Merging and Mutations Source: https://book.fulcrologic.com/index A comprehensive example of a Fulcro application demonstrating component definition, state management, data merging using `merge-component!`, and client-side mutations. It includes UI elements for displaying and interacting with counter entities. ```Clojure (ns book.merge-component (:require [com.fulcrologic.fulcro.components :as comp :refer [defsc]] [com.fulcrologic.fulcro.dom :as dom] [com.fulcrologic.fulcro.mutations :as m :refer [defmutation]] [com.fulcrologic.fulcro.data-fetch :as df] [com.fulcrologic.fulcro.algorithms.merge :as merge])) (defsc Counter [this {:keys [counter/id counter/n] :as props} {:keys [onClick] :as computed}] {:query [:counter/id :counter/n] :ident [:counter/by-id :counter/id]} (dom/div :.counter (dom/span :.counter-label (str "Current count for counter " id ": ")) (dom/span :.counter-value n) (dom/button {:onClick #(onClick id)} "Increment"))) (def ui-counter (comp/factory Counter {:keyfn :counter/id})) ; the * suffix is just a notation to indicate an implementation of something..in this case the guts of a mutation (defn increment-counter* "Increment a counter with ID counter-id in a Fulcro database." [database counter-id] (update-in database [:counter/by-id counter-id :counter/n] inc)) (defmutation increment-counter [{:keys [id] :as params}] ; The local thing to do (action [{:keys [state] :as env}] (swap! state increment-counter* id)) ; The remote thing to do. True means "the same (abstract) thing". False (or omitting it) means "nothing" (remote [env] true)) (defsc CounterPanel [this {:keys [counters]}] {:initial-state (fn [params] {:counters []}) :query [{:counters (comp/get-query Counter)}] :ident (fn [] [:panels/by-kw :counter])} (let [click-callback (fn [id] (comp/transact! this [(increment-counter {:id id}) :counter/by-id]))] (dom/div ; embedded style: kind of silly in a real app, but doable (dom/style ".counter { width: 400px; padding-bottom: 20px; } button { margin-left: 10px; }") ; computed lets us pass calculated data to our component's 3rd argument. It has to be ; combined into a single argument or the factory would not be React-compatible (not would it be able to handle ; children). (mapv #(ui-counter (comp/computed % {:onClick click-callback})) counters)))) (def ui-counter-panel (comp/factory CounterPanel)) (defonce timer-id (atom 0)) (declare sample-of-counter-app-with-merge-component-fulcro-app) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; The code of interest... ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn add-counter "NOTE: A function callable from anywhere as long as you have a reconciler..." [app counter] (merge/merge-component! app Counter counter :append [:panels/by-kw :counter :counters])) (defsc Root [this {:keys [panel]}] {:query [{:panel (comp/get-query CounterPanel)}] :initial-state {:panel {}}} (dom/div {:style {:border "1px solid black"}} ; NOTE: A plain function...pretend this is happening outside of the UI...we're doing it here so we can embed it in the book... (dom/button {:onClick #(add-counter (comp/any->app this) {:counter/id 4 :counter/n 22})} "Simulate Data Import") (dom/hr) "Counters:" (ui-counter-panel panel))) ``` -------------------------------- ### Denormalize Full Database with EQL Query Source: https://book.fulcrologic.com/index This example demonstrates using `fdn/db->tree` to denormalize the `sample-db` from its root. It queries for `:people` and their `:person/name`, showing how idents are resolved into a tree structure. ```Clojure (let [starting-node sample-db] (fdn/db->tree [{:people [:person/name]}] starting-node sample-db)) => {:people [#:person{:name "Bob"} #:person{:name "Judy"}]} ``` -------------------------------- ### Defining Fulcro Components and Routers for IDE-Navigable Paths Source: https://book.fulcrologic.com/index Illustrates how to structure Fulcro components with `:route-segment` metadata and define `defrouter` instances. This setup enables the use of component references for routing, improving code readability and IDE navigation. ```Clojure (ns com.example.root) (defsc MyIndex [_ _] {:route-segment ["root"]} ;; composes in settings router ...) (defrouter RootRouter [this props] {:router-targets [MyIndex]}) ... (ns com.example.settings) (defsc Settings [_ _] {:route-segment ["settings"]} ; composes in SettingPanesRouter ...) (defrouter SettingsRouter [this props] {:router-targets [Settings]}) ... (ns com.example.settings-pane) (defsc UserPane [_ _] {:route-segment ["user" :user-id]} ...) (defrouter SettingPanesRouter [this props] {:router-targets [UserPane]}) ``` -------------------------------- ### Define Server-Side Pathom Mutation Source: https://book.fulcrologic.com/index An example of a server-side mutation defined using Pathom's `pc/defmutation` in Clojure. The `env` parameter provides context that can be augmented during parser setup, such as database connections. ```Clojure (ns my-app.mutations (:require [com.wsscode.pathom.connect :as pc])) ;; `env` can be augmented in parser setup to include things like database connection, etc. (pc/defmutation do-something [env params] {} ...)) ``` -------------------------------- ### Fulcro Startup: Initial State and Keyframe Render Source: https://book.fulcrologic.com/index Demonstrates the initial state setup and the first keyframe render process in Fulcro. It copies the initial component tree state, normalizes it using the application's query, and performs a single keyframe render to display the initial DOM. ```Clojure (let [tree (get-initial-state Root {}) Q (get-query Root) database (tree->db Q tree)] (reset! state-atom database)) (let [Q (get-query Root) tree (db->tree @state-atom Q)] (react-render Root tree)) ``` -------------------------------- ### Initialize Fulcro Application and Set Initial Route Early Source: https://book.fulcrologic.com/index This snippet demonstrates a recommended approach for starting a Fulcro application. It initializes the application (`fulcro-app`), sets the root component, immediately calls `dr/change-route!` to establish an initial route, and then mounts the application, ensuring a consistent user experience by setting up routing before rendering. ```Clojure (defonce SPA (app/fulcro-app ...)) (defn start [] (app/set-root! SPA ui/Root {:initialize-state? true}) (dr/change-route! SPA ["landing-page"]) (app/mount! app ui/Root "app" {:initialize-state? false})) ``` -------------------------------- ### Build and Evolve Initial Application State on Server Source: https://book.fulcrologic.com/index Demonstrates how to use `build-initial-state` from `com.fulcrologic.fulcro.algorithms.server-render` to create a normalized client application database on the server. It then shows how to evolve this state using custom mutation implementations and `assoc` to incorporate dynamic data like user information, preparing it for server-side rendering. ```Clojure (let [base-state (ssr/build-initial-state (my-app/get-initial-state) my-app/Root) user (get-current-user (:session req)) user-ident (comp/get-ident my-app/User user)] (-> base-state (todo-check-item-impl* 3 true) ; some combo of mutation impls (assoc :current-user user-ident) ; put normalized user into root (assoc-in user-ident user))) ``` -------------------------------- ### Fulcro Mutation Progress Update Example Source: https://book.fulcrologic.com/index An example of a Fulcro mutation that updates a component's UI with overall network progress using `http-remote/overall-progress` within its `progress-action`. ```Clojure (defmutation large-mutation [_] (progress-action [{:keys [state] :as env}] (swap! state assoc-in [:component/id :thing :ui/progress] (http-remote/overall-progress env))) (remote [_] true)) ``` -------------------------------- ### Example Incoming Data Tree Source: https://book.fulcrologic.com/index An illustrative example of a data tree structure that Fulcro might receive, typically from a server. This tree contains a collection of 'people' entities, each with a unique database ID and other attributes. ```Clojure { :people [ {:db/id 1 :person/name "Joe" ...} {:db/id 2 ...} ... ] } ``` -------------------------------- ### Creating a Fulcro Server System with Configuration Path Source: https://book.fulcrologic.com/index Demonstrates how to define a `make-system` function that initializes a Fulcro server using `server/make-fulcro-server`. It explicitly sets the `:config-path` to a specific EDN file for server configuration. This snippet shows the basic structure for integrating server setup with configuration loading. ```Clojure (defn make-system [] (server/make-fulcro-server :config-path "/usr/local/etc/app.edn" ... ``` -------------------------------- ### Initialize Pre-loaded Routes in Fulcro Application Source: https://book.fulcrologic.com/index This ClojureScript code demonstrates how to set up pre-loaded routes (`:new-user`, `:login`) using `comp/transact!` and `r/install-route` within the `application-loaded!` function. This function is invoked via `:client-did-mount` during application startup to ensure routes are available immediately. It also includes a call to `loader/set-loaded!` for the entry point. ```ClojureScript (defn application-loaded! [app] ; Let the dynamic router know that two of the routes are already loaded. (comp/transact! app `[(r/install-route {:target-kw :new-user :component ~NewUser}) (r/install-route {:target-kw :login :component ~Login}) (r/route-to {:handler :login})]) ; Clojurescript requires you call this on every successfully "loaded" module: (loader/set-loaded! :entry-point)) ... (make-fulcro-client Root {:client-did-mount application-loaded!}) ``` -------------------------------- ### Initialize Fulcro App and Inspect Normalized State Source: https://book.fulcrologic.com/index This Clojure snippet demonstrates the foundational steps for setting up a Fulcro application. It defines two components, `Person` and `Root`, specifying their queries and initial states. The application is then mounted in a headless environment, and its internal normalized state is inspected, showcasing how Fulcro structures data with references and tables. ```Clojure (defsc Person [this props] {:query [:person/id :person/name] :ident :person/id :initial-state (fn [params] {:person/id (:id params) :person/name (:name params)})}) (defsc Root [this props] {:query [{:root/people (comp/get-query Person)}] :initial-state (fn [_] {:root/people [(comp/get-initial-state Person {:id 1 :name "Bob"}) (comp/get-initial-state Person {:id 2 :name "Judy"})]})}) (def app (app/fulcro-app)) (app/mount! app Root :headless) (app/current-state app) => {:fulcro.inspect.core/app-id "user/Root", :root/people [[:person/id 1] [:person/id 2]], :person/id {1 #:person{:id 1, :name "Bob"}, 2 #:person{:id 2, :name "Judy"}}} ``` -------------------------------- ### Query Specific Table with db→tree Source: https://book.fulcrologic.com/index This example uses `db→tree` to query the `:person/id` table directly. Similar to the previous example, it shows that without a join specified in the EQL, the returned data for the table will contain unresolved idents. ```Clojure (let [starting-node sample-db] (fdn/db->tree [:person/id] starting-node sample-db)) => #:person{:id {1 #:person{:name "Bob"}, 2 #:person{:name "Judy"}}} ``` -------------------------------- ### Defining Fulcro UI Components and App Initialization Source: https://book.fulcrologic.com/index This snippet demonstrates how to define Fulcro UI components (`LargeList`, `Root`) and an `initialize` function for application startup. It shows the use of `dom` for UI elements, `comp/factory` for component creation, `comp/get-initial-state` and `comp/get-query` for state and query definitions, and `comp/transact!` for triggering state changes and page navigation. ```Clojure (dom/button {:onClick #(comp/transact! this [(goto-page {:page-number (inc number)})])} "Next Page") (ui-list-page current-page)))) (def ui-list (comp/factory LargeList)) (defsc Root [this {:keys [pagination/list]}] {:initial-state (fn [params] {:pagination/list (comp/get-initial-state LargeList {})}) :query [{:pagination/list (comp/get-query LargeList)}]} (dom/div (ui-list list))) (defn initialize "To be used as started-callback. Load the first page." [{:keys [app]}] (comp/transact! app [(goto-page {:page-number 1})])) ```