update/fix class-registry example
This commit is contained in:
parent
414c491483
commit
a4c935ec8c
|
@ -1,19 +1,16 @@
|
|||
# views.reagent Example - Class Registry
|
||||
|
||||
This is a "Class Registry" application that has a lot of CRUD operations
|
||||
in it which allow users to manage students and professors, as well as
|
||||
classes and then assign the students/professors to those classes. The
|
||||
idea is _very_ loosely based off one of the Om tutorial applications
|
||||
(the data used is almost identical).
|
||||
This is a "Class Registry" application that has a lot of CRUD operations in it which allow users
|
||||
to manage students and professors, as well as classes and then assign the students/professors to
|
||||
those classes. The idea is _very_ loosely based off one of the Om tutorial applications (the data
|
||||
used is almost identical).
|
||||
|
||||
Note that this example is somewhat complicated as there are several
|
||||
lists of data shown in the UI, all of which are completely editable
|
||||
to the user. While the code is longer as a result, this still serves
|
||||
as a more interesting example with several views being used (some
|
||||
using parameters).
|
||||
Note that this example is somewhat complicated as there are several lists of data shown in the UI,
|
||||
all of which are completely editable to the user. While the code is longer as a result, this still
|
||||
serves 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
|
||||
this and also be sure you're familiar with Reagent.
|
||||
Definitely take a look at the [Todo MVC][1] example app before diving into this and also be sure
|
||||
you're familiar with Reagent.
|
||||
|
||||
[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
|
||||
|
||||
This example app uses a PostgreSQL database. The SQL script to create
|
||||
it is in `create_db.sql`. You can easily pipe it into `psql` at a
|
||||
command line to create it quickly, for example:
|
||||
This example app uses a PostgreSQL database. The SQL script to create it is in `create_db.sql`.
|
||||
|
||||
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
|
||||
|
||||
|
@ -34,12 +38,10 @@ command line to create it quickly, for example:
|
|||
To build everything and run in one step:
|
||||
|
||||
$ 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
|
||||
the ClojureScript:
|
||||
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 the ClojureScript:
|
||||
|
||||
$ lein cljsbuild once
|
||||
|
||||
|
@ -47,5 +49,4 @@ And then in the REPL you can just run:
|
|||
|
||||
(-main)
|
||||
|
||||
to start the web app (you should be put in the correct namespace
|
||||
immediately).
|
||||
to start the web app (you should be put in the correct namespace immediately).
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
(defproject class-registry "0.1.0-SNAPSHOT"
|
||||
:dependencies [[org.clojure/clojure "1.8.0"]
|
||||
[org.clojure/clojurescript "1.8.51"]
|
||||
[ring "1.4.0"]
|
||||
[ring/ring-defaults "0.2.0" :exclusions [javax.servlet/servlet-api]]
|
||||
[ring-middleware-format "0.7.0"]
|
||||
[compojure "1.4.0"]
|
||||
[org.immutant/web "2.1.4"]
|
||||
:dependencies [[org.clojure/clojure "1.10.3"]
|
||||
[org.clojure/clojurescript "1.10.773"]
|
||||
[ring "1.9.4"]
|
||||
[ring/ring-defaults "0.3.3" :exclusions [javax.servlet/servlet-api]]
|
||||
[ring-middleware-format "0.7.4"]
|
||||
[compojure "1.6.2"]
|
||||
[org.immutant/web "2.1.10"]
|
||||
|
||||
[org.clojure/java.jdbc "0.6.1"]
|
||||
[org.postgresql/postgresql "9.4.1208.jre7"]
|
||||
[com.taoensso/sente "1.8.1"]
|
||||
[gered/views "1.5"]
|
||||
[gered/views.sql "0.1"]
|
||||
[gered/views.reagent "0.1"]
|
||||
[gered/views.reagent.sente "0.1"]
|
||||
[org.clojure/java.jdbc "0.7.12"]
|
||||
[org.postgresql/postgresql "42.3.1"]
|
||||
[com.taoensso/sente "1.16.2"]
|
||||
[net.gered/views "1.6-SNAPSHOT"]
|
||||
[net.gered/views.sql "0.2-SNAPSHOT"]
|
||||
[net.gered/views.reagent "0.2-SNAPSHOT"]
|
||||
[net.gered/views.reagent.sente "0.2-SNAPSHOT"]
|
||||
|
||||
[hiccup "1.0.5"]
|
||||
[reagent "0.6.0-alpha2"]
|
||||
[reagent "1.1.0"]
|
||||
[cljs-ajax "0.8.4"]
|
||||
[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.3"]
|
||||
[lein-environ "1.0.3"]]
|
||||
:plugins [[lein-cljsbuild "1.1.8"]]
|
||||
|
||||
:main class-registry.server
|
||||
|
||||
|
@ -40,10 +39,9 @@
|
|||
:optimizations :none
|
||||
:pretty-print true}}}}
|
||||
|
||||
:profiles {:dev {:env {:dev "true"}}
|
||||
:profiles {:dev {}
|
||||
|
||||
:uberjar {:env {}
|
||||
:aot :all
|
||||
:uberjar {:aot :all
|
||||
:hooks [leiningen.cljsbuild]
|
||||
:cljsbuild {:jar true
|
||||
:builds {:main
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
(:require
|
||||
[clojure.string :as string]
|
||||
[reagent.core :as r]
|
||||
[ajax.core :refer [POST default-interceptors to-interceptor]]
|
||||
[reagent.dom :as rdom]
|
||||
[ajax.core :as ajax]
|
||||
[taoensso.sente :as sente]
|
||||
[views.reagent.client.component :refer [view-cursor] :refer-macros [defvc]]
|
||||
[views.reagent.sente.client :as vr]))
|
||||
|
@ -31,20 +32,20 @@
|
|||
|
||||
;; AJAX actions
|
||||
|
||||
(defn add-person! [person] (POST "/people/add" {:params {:person person}}))
|
||||
(defn save-person! [person] (POST "/people/update" {:params {:person person}}))
|
||||
(defn delete-person! [id] (POST "/people/delete" {:params {:id id}}))
|
||||
(defn add-class! [class] (POST "/class/add" {:params {:class class}}))
|
||||
(defn save-class! [class] (POST "/class/update" {:params {:class class}}))
|
||||
(defn delete-class! [id] (POST "/class/delete" {:params {:id id}}))
|
||||
(defn add-person! [person] (ajax/POST "/people/add" {:params {:person person}}))
|
||||
(defn save-person! [person] (ajax/POST "/people/update" {:params {:person person}}))
|
||||
(defn delete-person! [id] (ajax/POST "/people/delete" {:params {:id id}}))
|
||||
(defn add-class! [class] (ajax/POST "/class/add" {:params {:class class}}))
|
||||
(defn save-class! [class] (ajax/POST "/class/update" {:params {:class class}}))
|
||||
(defn delete-class! [id] (ajax/POST "/class/delete" {:params {:id id}}))
|
||||
|
||||
(defn add-registration!
|
||||
[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!
|
||||
[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
|
||||
|
||||
(defn sente-event-msg-handler
|
||||
[{:keys [event id client-id] :as ev}]
|
||||
(let [[ev-id ev-data] event]
|
||||
(cond
|
||||
(and (= :chsk/state ev-id)
|
||||
(:open? ev-data))
|
||||
(vr/on-open! @sente-socket ev)
|
||||
(cond
|
||||
(vr/chsk-open-event? ev)
|
||||
(vr/on-open! @sente-socket ev)
|
||||
|
||||
(= :chsk/recv id)
|
||||
(when-not (vr/on-receive! @sente-socket ev)
|
||||
; TODO: any code here needed to handle app-specific receive events
|
||||
))))
|
||||
(= :chsk/recv id)
|
||||
(when-not (vr/on-receive! @sente-socket ev)
|
||||
; 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
|
||||
[]
|
||||
(reset! sente-socket (sente/make-channel-socket! "/chsk" {}))
|
||||
(sente/start-chsk-router! (:ch-recv @sente-socket) sente-event-msg-handler)
|
||||
(enable-console-print!)
|
||||
|
||||
(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
|
||||
[compojure.core :refer [routes GET POST]]
|
||||
[compojure.route :as route]
|
||||
[ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||
[ring.middleware.format :refer [wrap-restful-format]]
|
||||
[ring.util.anti-forgery :refer [anti-forgery-field]]
|
||||
[ring.util.response :refer [response]]
|
||||
[taoensso.sente :as sente]
|
||||
[taoensso.sente.server-adapters.immutant :refer [sente-web-server-adapter]]
|
||||
[immutant.web :as immutant]
|
||||
[hiccup.page :refer [html5 include-css include-js]]
|
||||
[hiccup.element :refer [javascript-tag]]
|
||||
[environ.core :refer [env]]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[views.sql.core :refer [vexec! with-view-transaction]]
|
||||
[views.sql.view :refer [view]]
|
||||
[views.reagent.sente.server :as vr]))
|
||||
|
||||
(def dev? (boolean (env :dev)))
|
||||
|
||||
(def db {:classname "org.postgresql.Driver"
|
||||
:subprotocol "postgresql"
|
||||
:subname "//localhost/class_registry"
|
||||
|
@ -147,12 +144,12 @@
|
|||
[:head
|
||||
[:title "Class Registry - views.reagent Example"]
|
||||
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
|
||||
[:meta {:name "csrf-token" :content *anti-forgery-token*}]
|
||||
(include-css
|
||||
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
|
||||
"app.css")
|
||||
(include-js "cljs/app.js")]
|
||||
[:body
|
||||
(anti-forgery-field)
|
||||
[:div#app [:h1 "This will be replaced by the Class Registry app when the ClojureScript is compiled."]]
|
||||
(javascript-tag "class_registry.client.run();")]))
|
||||
|
||||
|
@ -203,7 +200,7 @@
|
|||
(-> app-routes
|
||||
(wrap-restful-format :formats [:transit-json])
|
||||
(wrap-sente "/chsk")
|
||||
(wrap-defaults (assoc-in site-defaults [:security :anti-forgery] (not dev?)))))
|
||||
(wrap-defaults site-defaults)))
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue