update/fix class-registry example
This commit is contained in:
parent
414c491483
commit
a4c935ec8c
|
@ -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).
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"))))
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue