@@ -1425,24 +1425,24 @@ example we'll limit the application to the bare minimum.
14251425
14261426The next step is to create the client code. For this we'll use
14271427https://github.com/cjohansen/replicant[Replicant] for updating the DOM,
1428- and https://github.com/r0man/cljs- http[cljs- http] for communicating with
1429- the server API.
1428+ and https://github.com/duct-framework/client. http[Duct client. http] for
1429+ communicating with the server API.
14301430
14311431This requires us to once again update the project dependencies:
14321432
14331433.deps.edn
14341434[,clojure]
14351435----
14361436{:deps {org.clojure/clojure {:mvn/version "1.12.1"}
1437+ org.duct-framework/client.http {:mvn/version "0.1.0"}
14371438 org.duct-framework/main {:mvn/version "0.1.10"}
14381439 org.duct-framework/module.cljs {:mvn/version "0.5.1"}
14391440 org.duct-framework/module.logging {:mvn/version "0.6.5"}
14401441 org.duct-framework/module.web {:mvn/version "0.12.9"}
14411442 org.duct-framework/module.sql {:mvn/version "0.8.0"}
14421443 org.xerial/sqlite-jdbc {:mvn/version "3.50.3.0"}
14431444 com.github.seancorfield/next.jdbc {:mvn/version "1.3.1048"}
1444- no.cjohansen/replicant {:mvn/version "2025.06.21"}
1445- cljs-http/cljs-http {:mvn/version "0.1.48"}}
1445+ no.cjohansen/replicant {:mvn/version "2025.06.21"}}
14461446 :aliases {:duct {:main-opts ["-m" "duct.main"]}}}
14471447----
14481448
@@ -1454,32 +1454,22 @@ file for the client UI.
14541454----
14551455(ns todo.client
14561456 (:require [replicant.dom :as r]
1457- [cljs-http .client :as http]
1457+ [duct .client.http :as http]
14581458 [clojure.core.async :as a :refer [<!]]))
14591459
1460- ;; Helper functions that add anti-forgery headers.
1461- (defn delete [url]
1462- (http/delete url {:headers {"X-Ring-Anti-Forgery" "1"}}))
1463-
1464- (defn post [url params]
1465- (http/post url {:headers {"X-Ring-Anti-Forgery" "1"}, :json-params params}))
1466-
1467- (defonce todos
1468- (js/document.getElementById "todos"))
1469-
14701460(defonce store (atom {}))
14711461
14721462(defn update-todos []
1473- (a/go (let [resp (<! (http/get "/ todos" ))]
1463+ (a/go (let [resp (<! (http/get [: todos] ))]
14741464 (swap! store assoc :todos (-> resp :body :results)))))
14751465
14761466(defn delete-todo [id]
1477- (a/go (<! (delete (str "/ todos/" id) ))
1467+ (a/go (<! (http/ delete [: todos id] ))
14781468 (<! (update-todos))))
14791469
14801470(defn create-todo []
14811471 (a/go (let [input (js/document.getElementById "todo-desc")]
1482- (<! (post "/ todos" {:description (.-value input)}))
1472+ (<! (http/ post [: todos] {:description (.-value input)}))
14831473 (<! (update-todos))
14841474 (set! (.-value input) ""))))
14851475
@@ -1496,30 +1486,35 @@ file for the client UI.
14961486 [:a {:href "#" :on {:click #(delete-todo id)}} "delete"]])
14971487 [:li (create-todo-form)]])
14981488
1489+ (defonce todos
1490+ (js/document.getElementById "todos"))
1491+
14991492(add-watch store ::render (fn [_ _ _ s] (r/render todos (todo-list s))))
15001493(update-todos)
15011494----
15021495
1503- WARNING: In the above example, the '`click`' event is bound to a function.
1504- This is not considered best practice for Replicant, but used in the
1505- example code for the sake of brevity.
1506-
15071496Here we reach the edge of Duct. This ClojureScript file is not specific
15081497to our framework, but would be at home in any Clojure project.
15091498Nevertheless, for the sake of completeness we'll provide some
15101499explanation of what this file does.
15111500
1512- The `delete` and `post` functions add the `X-Ring-Anti-Forgery` header,
1513- which is needed to get past the anti-forgery protection.
1501+ The `get`, `post` and `delete` functions from the Duct HTTP client
1502+ simplify communication with the server. They communicate using the
1503+ Transit serialization format, and automatically add headers to get
1504+ around the webservers CSRF protection.
15141505
15151506The `update-todos`, `delete-todo` and `create-todo` functions all update
1516- the `state ` atom, which contains a data structure that represents the
1507+ the `store ` atom, which contains a data structure that represents the
15171508state of the UI. In this case, it's a list of todo items.
15181509
1519- There is a watch attached to the `state ` atom. When the state is
1510+ There is a watch attached to the `store ` atom. When the store is
15201511changed, the `todos` DOM element is updated accordingly, with a new
15211512unordered list of todo items. Replicant is smart enough to update only
15221513the elements that have changed, making updates efficient.
15231514
1515+ WARNING: In the example code, the '`click`' event is bound to a
1516+ function. This is not considered best practice for Replicant, but is
1517+ used in this example for the sake of brevity.
1518+
15241519Now that we have both a server and client, we can `(reset)` the REPL
15251520and check the web application at: <http://localhost:3000>
0 commit comments