update/fix class-registry example

This commit is contained in:
Gered 2022-01-12 17:18:47 -05:00
parent 414c491483
commit a4c935ec8c
4 changed files with 94 additions and 88 deletions

View file

@ -1,19 +1,16 @@
# views.reagent Example - Class Registry # views.reagent Example - Class Registry
This is a "Class Registry" application that has a lot of CRUD operations This is a "Class Registry" application that has a lot of CRUD operations in it which allow users
in it which allow users to manage students and professors, as well as to manage students and professors, as well as classes and then assign the students/professors to
classes and then assign the students/professors to those classes. The those classes. The idea is _very_ loosely based off one of the Om tutorial applications (the data
idea is _very_ loosely based off one of the Om tutorial applications used is almost identical).
(the data used is almost identical).
Note that this example is somewhat complicated as there are several Note that this example is somewhat complicated as there are several lists of data shown in the UI,
lists of data shown in the UI, all of which are completely editable all of which are completely editable to the user. While the code is longer as a result, this still
to the user. While the code is longer as a result, this still serves serves as a more interesting example with several views being used (some using parameters).
as a more interesting example with several views being used (some
using parameters).
Definitely take a look at the [Todo MVC][1] example app before diving into Definitely take a look at the [Todo MVC][1] example app before diving into this and also be sure
this and also be sure you're familiar with Reagent. you're familiar with Reagent.
[1]: https://github.com/gered/views.reagent/tree/master/examples/todomvc [1]: https://github.com/gered/views.reagent/tree/master/examples/todomvc
@ -21,9 +18,16 @@ this and also be sure you're familiar with Reagent.
### Creating the Database ### Creating the Database
This example app uses a PostgreSQL database. The SQL script to create This example app uses a PostgreSQL database. The SQL script to create it is in `create_db.sql`.
it is in `create_db.sql`. You can easily pipe it into `psql` at a
command line to create it quickly, for example: A Docker compose file `pgsql.docker-compose.yml` is provided and pre-configured to allow you to
quickly spin up a PostgreSQL database that will be pre-initialized via `create_db.sql` through
Docker.
$ docker-compose -f pgsql.docker-compose.yml up
Alternatively, if you already have a PostgreSQL database available, you can run the
`create_db.sql` via `psql` easily enough:
$ psql < create_db.sql $ psql < create_db.sql
@ -34,12 +38,10 @@ command line to create it quickly, for example:
To build everything and run in one step: To build everything and run in one step:
$ lein rundemo $ lein rundemo
Then open up a web browser or two and head to http://localhost:8080/
to see the web app in action.
If you want to run this application in a REPL, just be sure to build Then open up a web browser or two and head to http://localhost:8080/ to see the web app in action.
the ClojureScript:
If you want to run this application in a REPL, just be sure to build the ClojureScript:
$ lein cljsbuild once $ lein cljsbuild once
@ -47,5 +49,4 @@ And then in the REPL you can just run:
(-main) (-main)
to start the web app (you should be put in the correct namespace to start the web app (you should be put in the correct namespace immediately).
immediately).

View file

@ -1,29 +1,28 @@
(defproject class-registry "0.1.0-SNAPSHOT" (defproject class-registry "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"] :dependencies [[org.clojure/clojure "1.10.3"]
[org.clojure/clojurescript "1.8.51"] [org.clojure/clojurescript "1.10.773"]
[ring "1.4.0"] [ring "1.9.4"]
[ring/ring-defaults "0.2.0" :exclusions [javax.servlet/servlet-api]] [ring/ring-defaults "0.3.3" :exclusions [javax.servlet/servlet-api]]
[ring-middleware-format "0.7.0"] [ring-middleware-format "0.7.4"]
[compojure "1.4.0"] [compojure "1.6.2"]
[org.immutant/web "2.1.4"] [org.immutant/web "2.1.10"]
[org.clojure/java.jdbc "0.6.1"] [org.clojure/java.jdbc "0.7.12"]
[org.postgresql/postgresql "9.4.1208.jre7"] [org.postgresql/postgresql "42.3.1"]
[com.taoensso/sente "1.8.1"] [com.taoensso/sente "1.16.2"]
[gered/views "1.5"] [net.gered/views "1.6-SNAPSHOT"]
[gered/views.sql "0.1"] [net.gered/views.sql "0.2-SNAPSHOT"]
[gered/views.reagent "0.1"] [net.gered/views.reagent "0.2-SNAPSHOT"]
[gered/views.reagent.sente "0.1"] [net.gered/views.reagent.sente "0.2-SNAPSHOT"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[reagent "0.6.0-alpha2"] [reagent "1.1.0"]
[cljs-ajax "0.8.4"]
[cljsjs/bootstrap "3.3.6-1"] [cljsjs/bootstrap "3.3.6-1"]
[cljs-ajax "0.5.4"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]]
[environ "1.0.3"]] :plugins [[lein-cljsbuild "1.1.8"]]
:plugins [[lein-cljsbuild "1.1.3"]
[lein-environ "1.0.3"]]
:main class-registry.server :main class-registry.server
@ -40,10 +39,9 @@
:optimizations :none :optimizations :none
:pretty-print true}}}} :pretty-print true}}}}
:profiles {:dev {:env {:dev "true"}} :profiles {:dev {}
:uberjar {:env {} :uberjar {:aot :all
:aot :all
:hooks [leiningen.cljsbuild] :hooks [leiningen.cljsbuild]
:cljsbuild {:jar true :cljsbuild {:jar true
:builds {:main :builds {:main

View file

@ -2,7 +2,8 @@
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[reagent.core :as r] [reagent.core :as r]
[ajax.core :refer [POST default-interceptors to-interceptor]] [reagent.dom :as rdom]
[ajax.core :as ajax]
[taoensso.sente :as sente] [taoensso.sente :as sente]
[views.reagent.client.component :refer [view-cursor] :refer-macros [defvc]] [views.reagent.client.component :refer [view-cursor] :refer-macros [defvc]]
[views.reagent.sente.client :as vr])) [views.reagent.sente.client :as vr]))
@ -31,20 +32,20 @@
;; AJAX actions ;; AJAX actions
(defn add-person! [person] (POST "/people/add" {:params {:person person}})) (defn add-person! [person] (ajax/POST "/people/add" {:params {:person person}}))
(defn save-person! [person] (POST "/people/update" {:params {:person person}})) (defn save-person! [person] (ajax/POST "/people/update" {:params {:person person}}))
(defn delete-person! [id] (POST "/people/delete" {:params {:id id}})) (defn delete-person! [id] (ajax/POST "/people/delete" {:params {:id id}}))
(defn add-class! [class] (POST "/class/add" {:params {:class class}})) (defn add-class! [class] (ajax/POST "/class/add" {:params {:class class}}))
(defn save-class! [class] (POST "/class/update" {:params {:class class}})) (defn save-class! [class] (ajax/POST "/class/update" {:params {:class class}}))
(defn delete-class! [id] (POST "/class/delete" {:params {:id id}})) (defn delete-class! [id] (ajax/POST "/class/delete" {:params {:id id}}))
(defn add-registration! (defn add-registration!
[class-id people-id] [class-id people-id]
(POST "/registry/add" {:params {:class-id class-id :people-id people-id}})) (ajax/POST "/registry/add" {:params {:class-id class-id :people-id people-id}}))
(defn remove-registration! (defn remove-registration!
[id] [id]
(POST "/registry/remove" {:params {:id id}})) (ajax/POST "/registry/remove" {:params {:id id}}))
@ -363,35 +364,34 @@
;; AJAX CSRF stuff
(defn get-anti-forgery-token
[]
(if-let [hidden-field (.getElementById js/document "__anti-forgery-token")]
(.-value hidden-field)))
(def csrf-interceptor
(to-interceptor {:name "CSRF Interceptor"
:request #(assoc-in % [:headers "X-CSRF-Token"] (get-anti-forgery-token))}))
(swap! default-interceptors (partial cons csrf-interceptor))
;; Sente event/message handler ;; Sente event/message handler
(defn sente-event-msg-handler (defn sente-event-msg-handler
[{:keys [event id client-id] :as ev}] [{:keys [event id client-id] :as ev}]
(let [[ev-id ev-data] event] (cond
(cond (vr/chsk-open-event? ev)
(and (= :chsk/state ev-id) (vr/on-open! @sente-socket ev)
(:open? ev-data))
(vr/on-open! @sente-socket ev)
(= :chsk/recv id) (= :chsk/recv id)
(when-not (vr/on-receive! @sente-socket ev) (when-not (vr/on-receive! @sente-socket ev)
; TODO: any code here needed to handle app-specific receive events ; TODO: any code here needed to handle app-specific receive events
)))) )))
;; Utility functions for dealing with CSRF Token garbage in AJAX requests.
(defn get-csrf-token
[]
(when-let [csrf-token-element (.querySelector js/document "meta[name=\"csrf-token\"]")]
(.-content csrf-token-element)))
(defn add-csrf-token-ajax-interceptor!
[csrf-token]
(let [interceptor (ajax/to-interceptor
{:name "CSRF Interceptor"
:request #(assoc-in % [:headers "X-CSRF-Token"] csrf-token)})]
(swap! ajax/default-interceptors #(cons interceptor %))))
@ -399,9 +399,19 @@
(defn ^:export run (defn ^:export run
[] []
(reset! sente-socket (sente/make-channel-socket! "/chsk" {})) (enable-console-print!)
(sente/start-chsk-router! (:ch-recv @sente-socket) sente-event-msg-handler)
(vr/init! @sente-socket {}) (let [csrf-token (get-csrf-token)]
(if csrf-token (add-csrf-token-ajax-interceptor! csrf-token))
(r/render-component [class-registry-app] (.getElementById js/document "app"))) ; Sente setup. create the socket, storing it in an atom and set up a event
; handler using sente's own message router functionality.
(reset! sente-socket (sente/make-channel-socket! "/chsk" csrf-token {}))
; set up a handler for sente events
(sente/start-chsk-router! (:ch-recv @sente-socket) sente-event-msg-handler)
; Configure views.reagent for use with Sente.
(vr/init! @sente-socket {})
(rdom/render [class-registry-app] (.getElementById js/document "app"))))

View file

@ -3,23 +3,20 @@
(:require (:require
[compojure.core :refer [routes GET POST]] [compojure.core :refer [routes GET POST]]
[compojure.route :as route] [compojure.route :as route]
[ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]] [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[ring.middleware.format :refer [wrap-restful-format]] [ring.middleware.format :refer [wrap-restful-format]]
[ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.util.response :refer [response]] [ring.util.response :refer [response]]
[taoensso.sente :as sente] [taoensso.sente :as sente]
[taoensso.sente.server-adapters.immutant :refer [sente-web-server-adapter]] [taoensso.sente.server-adapters.immutant :refer [sente-web-server-adapter]]
[immutant.web :as immutant] [immutant.web :as immutant]
[hiccup.page :refer [html5 include-css include-js]] [hiccup.page :refer [html5 include-css include-js]]
[hiccup.element :refer [javascript-tag]] [hiccup.element :refer [javascript-tag]]
[environ.core :refer [env]]
[clojure.java.jdbc :as jdbc] [clojure.java.jdbc :as jdbc]
[views.sql.core :refer [vexec! with-view-transaction]] [views.sql.core :refer [vexec! with-view-transaction]]
[views.sql.view :refer [view]] [views.sql.view :refer [view]]
[views.reagent.sente.server :as vr])) [views.reagent.sente.server :as vr]))
(def dev? (boolean (env :dev)))
(def db {:classname "org.postgresql.Driver" (def db {:classname "org.postgresql.Driver"
:subprotocol "postgresql" :subprotocol "postgresql"
:subname "//localhost/class_registry" :subname "//localhost/class_registry"
@ -147,12 +144,12 @@
[:head [:head
[:title "Class Registry - views.reagent Example"] [:title "Class Registry - views.reagent Example"]
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
[:meta {:name "csrf-token" :content *anti-forgery-token*}]
(include-css (include-css
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
"app.css") "app.css")
(include-js "cljs/app.js")] (include-js "cljs/app.js")]
[:body [:body
(anti-forgery-field)
[:div#app [:h1 "This will be replaced by the Class Registry app when the ClojureScript is compiled."]] [:div#app [:h1 "This will be replaced by the Class Registry app when the ClojureScript is compiled."]]
(javascript-tag "class_registry.client.run();")])) (javascript-tag "class_registry.client.run();")]))
@ -203,7 +200,7 @@
(-> app-routes (-> app-routes
(wrap-restful-format :formats [:transit-json]) (wrap-restful-format :formats [:transit-json])
(wrap-sente "/chsk") (wrap-sente "/chsk")
(wrap-defaults (assoc-in site-defaults [:security :anti-forgery] (not dev?))))) (wrap-defaults site-defaults)))