remove browserchannel stuff

it's old and now that websockets is everywhere, it is unneeded
This commit is contained in:
Gered 2022-01-12 16:53:20 -05:00
parent 1c33cb7c2f
commit b832409dfa
11 changed files with 0 additions and 1194 deletions

View file

@ -1,18 +0,0 @@
.DS_Store
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.settings/
.project
.classpath
.idea/
*.iml
*.ipr
*.iws
/resources/public/cljs

View file

@ -1,48 +0,0 @@
# views.reagent Example - Todo MVC (BrowserChannel)
This is a modification of the Todo MVC app for Reagent [demonstrated here][1].
This version of the app has been modified to use a PostgreSQL database
to store the Todos and to provide realtime synchronization of changes
to that data to any number of users currently viewing the app.
[1]: http://reagent-project.github.io/
> **NOTE:** This is a copy of the [other Todo MVC example][2] and is the same
> in every respect, except that this one is using [BrowserChannel][3] instead of
> Sente as the underlying client/server messaging implementation.
[2]: https://github.com/gered/views.reagent/tree/master/examples/todomvc
[3]: https://github.com/gered/views.reagent/tree/master/views.reagent.browserchannel
## Running
### 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:
$ psql < create_db.sql
(Of course, add any username/host parameters you might need)
### Starting It Up
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:
$ lein cljsbuild once
And then in the REPL you can just run:
(-main)
to start the web app (you should be put in the correct namespace
immediately).

View file

@ -1,21 +0,0 @@
-- For PostgreSQL
-- run with psql. e.g. 'psql < create_db.sql'
CREATE ROLE todomvc LOGIN PASSWORD 's3cr3t';
CREATE DATABASE todomvc OWNER todomvc;
-- assumes you're piping this script into psql ...
\c todomvc;
CREATE TABLE todos
(
id SERIAL PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
done BOOLEAN DEFAULT FALSE NOT NULL
);
ALTER TABLE todos OWNER TO todomvc;
INSERT INTO todos (title, done) VALUES ('Rename Cloact to Reagent', TRUE);
INSERT INTO todos (title, done) VALUES ('Add undo demo', TRUE);
INSERT INTO todos (title, done) VALUES ('Make all rendering async', TRUE);
INSERT INTO todos (title, done) VALUES ('Allow any arguments to component functions', TRUE);

View file

@ -1,56 +0,0 @@
(defproject todomvc-browserchannel "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]]
[compojure "1.4.0"]
[org.immutant/web "2.1.4"]
[org.clojure/java.jdbc "0.6.1"]
[org.postgresql/postgresql "9.4.1208.jre7"]
[gered/clj-browserchannel "0.3.2"]
[gered/clj-browserchannel-immutant-adapter "0.0.3"]
[gered/views "1.5"]
[gered/views.sql "0.1"]
[gered/views.reagent "0.1"]
[gered/views.reagent.browserchannel "0.1"]
[hiccup "1.0.5"]
[reagent "0.6.0-alpha2"]
[cljs-ajax "0.5.4"]
[environ "1.0.3"]]
:plugins [[lein-cljsbuild "1.1.3"]
[lein-environ "1.0.3"]]
:main todomvc.server
:clean-targets ^{:protect false} [:target-path
[:cljsbuild :builds :main :compiler :output-dir]
[:cljsbuild :builds :main :compiler :output-to]]
:cljsbuild {:builds {:main
{:source-paths ["src"]
:compiler {:main todomvc.client
:output-to "resources/public/cljs/app.js"
:output-dir "resources/public/cljs/target"
:asset-path "cljs/target"
:source-map true
:optimizations :none
:pretty-print true}}}}
:profiles {:dev {:env {:dev "true"}}
:uberjar {:env {}
:aot :all
:hooks [leiningen.cljsbuild]
:cljsbuild {:jar true
:builds {:main
{:compiler ^:replace {:output-to "resources/public/cljs/app.js"
:optimizations :advanced
:pretty-print false}}}}}}
:aliases {"rundemo" ["do" ["clean"] ["cljsbuild" "once"] ["run"]]
"uberjar" ["do" ["clean"] ["uberjar"]]}
)

View file

@ -1,558 +0,0 @@
@charset "utf-8";
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea;
/* background: #eaeaea url('bg.png'); */
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
button,
input[type="checkbox"] {
outline: none;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
/* Mobile Safari */
border: none;
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-ms-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
}
.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}

View file

@ -1,18 +0,0 @@
.todoitem-enter {
opacity: 0.1;
transition: opacity .2s ease-in;
}
.todoitem-enter.todoitem-enter-active {
opacity: 1;
}
.todoitem-leave {
opacity: 0.8;
transition: opacity 0.2s ease-out;
}
.todoitem-leave.todoitem-leave-active {
opacity: 0.1;
}

View file

@ -1,180 +0,0 @@
(ns todomvc.client
(:require
[reagent.core :as r]
[ajax.core :refer [POST default-interceptors to-interceptor]]
[net.thegeez.browserchannel.client :as browserchannel]
[views.reagent.client.component :refer [view-cursor] :refer-macros [defvc]]
[views.reagent.browserchannel.client :as vr]))
;; Todo MVC - views.reagent example app
;;
;; This is taken from the example code shown on http://reagent-project.github.io/
;; It has been modified so that instead of using todo data stored client-side in
;; an atom, the data is retrieved from the server.
;;
;; AJAX requests are used to add/edit/delete the todos. The list is refreshed
;; whenever a change is made (by any client currently viewing the app) by a
;; view subscription. See the 'todo-app' component near the bottom-middle of this
;; file for more details about this.
;; AJAX operations
(defn add-todo [text] (POST "/todos/add" {:format :url :params {:title text}}))
(defn toggle [id] (POST "/todos/toggle" {:format :url :params {:id id}}))
(defn save [id title] (POST "/todos/update" {:format :url :params {:id id :title title}}))
(defn delete [id] (POST "/todos/delete" {:format :url :params {:id id}}))
(defn complete-all [v] (POST "/todos/mark-all" {:format :url :params {:done? v}}))
(defn clear-done [] (POST "/todos/delete-all-done"))
;; UI Components
(defn todo-input
[{:keys [title on-save on-stop]}]
(let [val (r/atom title)
stop #(do (reset! val "")
(if on-stop (on-stop)))
save #(let [v (-> @val str clojure.string/trim)]
(if-not (empty? v) (on-save v))
(stop))]
(fn [props]
[:input (merge props
{:type "text" :value @val :on-blur save
:on-change #(reset! val (-> % .-target .-value))
:on-key-down #(case (.-which %)
13 (save)
27 (stop)
nil)})])))
(def todo-edit (with-meta todo-input
{:component-did-mount #(.focus (r/dom-node %))}))
(defn todo-stats
[{:keys [filt active done]}]
(let [props-for (fn [name]
{:class (if (= name @filt) "selected")
:on-click #(reset! filt name)})]
[:div
[:span#todo-count
[:strong active] " " (case active 1 "item" "items") " left"]
[:ul#filters
[:li [:a (props-for :all) "All"]]
[:li [:a (props-for :active) "Active"]]
[:li [:a (props-for :done) "Completed"]]]
(when (pos? done)
[:button#clear-completed {:on-click clear-done}
"Clear completed " done])]))
(defn todo-item
[]
(let [editing (r/atom false)]
(fn [{:keys [id done title]}]
[:li {:class (str (if done "completed ")
(if @editing "editing"))}
[:div.view
[:input.toggle {:type "checkbox" :checked done
:on-change #(toggle id)}]
[:label {:on-double-click #(reset! editing true)} title]
[:button.destroy {:on-click #(delete id)}]]
(when @editing
[todo-edit {:class "edit" :title title
:on-save #(save id %)
:on-stop #(reset! editing false)}])])))
;; Main TODO app component
;;
;; Note that this component is defined using 'defvc' instead of 'defn'. This is a
;; macro provided by views.reagent which is required to be used by any Reagent
;; component that will directly subscribe/unsubscribe to views. It handles all the
;; housekeeping operations that working with views on the client entails.
;;
;; The call to 'view-cursor' is where the rest of the magic happens. This function
;; will:
;;
;; - Send a subscription request to the server for the specified view and parameters
;; if a subscription for the view (and the exact provided parameters) does not
;; already exist.
;; - Returns the most recent data for this view in a Reagent cursor. When the data
;; is changed and the server sends a view refresh, components dereferencing this
;; cursor will be rerendered, just like any other Reagent atom/cursor.
;; - If the values of the (optional) parameters passed to view-cursor change, a
;; view resubscription (with the new parameters) will be triggered automatically
;; and the server will send us new view data.
;;
;; NOTE:
;; view-cursor cannot be used in a Reagent component that was created using defn.
(defvc todo-app
[props]
(let [filt (r/atom :all)]
(fn []
(let [items (view-cursor :todos)
done (->> @items (filter :done) count)
active (- (count @items) done)]
[:div
[:section#todoapp
[:header#header
[:h1 "todos"]
[todo-input {:id "new-todo"
:placeholder "What needs to be done?"
:on-save add-todo}]]
(when (-> @items count pos?)
[:div
[:section#main
[:input#toggle-all {:type "checkbox" :checked (zero? active)
:on-change #(complete-all (pos? active))}]
[:label {:for "toggle-all"} "Mark all as complete"]
[:ul#todo-list
(for [todo (->> @items
(filter
(case @filt
:active (complement :done)
:done :done
:all identity))
(sort-by :id))]
^{:key (:id todo)} [todo-item todo])]]
[:footer#footer
[todo-stats {:active active :done done :filt filt}]]])]
[:footer#info
[:p "Double-click to edit a todo"]]]))))
;; Some unfortunately necessary set up to ensure we send the CSRF token back with
;; AJAX requests
(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))
;; Page load
(defn ^:export run
[]
; Initialize views.reagent
(vr/init!)
; Initialize BrowserChannel
; NOTE: We are passing in an empty event handler map to connect! only because
; this todo app is not using BrowserChannel for any purpose other then to
; provide client/server messaging for views.reagent. If we wanted to use it
; for client/server messaging in our application as well, we could pass in
; any event handlers we want here and it would not intefere with views.reagent.
(browserchannel/connect! {} {:middleware [vr/middleware]})
(r/render-component [todo-app] (.getElementById js/document "app")))

View file

@ -1,188 +0,0 @@
(ns todomvc.server
(:gen-class)
(:require
[compojure.core :refer [routes GET POST]]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.util.response :refer [response]]
[net.thegeez.browserchannel.server :refer [wrap-browserchannel]]
[net.thegeez.browserchannel.immutant-async-adapter :refer [wrap-immutant-async-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.browserchannel.server :as vr]))
(def dev? (boolean (env :dev)))
(def db {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost/todomvc"
:user "todomvc"
:password "s3cr3t"})
;; View system atom
;;
;; We just declare it, don't need to fill it with anything. The call below to
;; views.reagent.browserchannel.server/init! will take care of it.
(defonce view-system (atom {}))
;; View functions.
;;
;; These are functions which accept any number of parameters provided when the view
;; is subscribed to and run whenever a subscriber needs refreshed data for it.
;;
;; A view function's return value requirement depends on what views IView
;; implementation is being used.
;;
;; This example app is using views.sql, so view templates should return a SQL SELECT
;; query in a clojure.java.jdbc "sqlvec" which is a vector where the first string is
;; the actual SQL query and is followed by any number of parameters to be used in
;; the query.
(defn todos-list
[]
["SELECT id, title, done FROM todos ORDER BY title"])
;; Views list.
;;
;; A definition/declaration of the views in the system. Each view is given an id and
;; points to a function that returns the query that will be used to retrieve the view's
;; data. Also other properties can be provided to each view, such as the database connection.
;;
;; The view id and parameters to the view function get used later on to form a
;; "view signature" or "view-sig" when the client subscribes to a view.
(def views
[(view :todos db #'todos-list)])
;; SQL operations triggered by AJAX requests.
;;
;; These functions are just your ordinary AJAX request handlers that do the various
;; CRUD operations on the example app's data. The only difference is that instead
;; of using clojure.java.jdbc/execute!, we instead use vexec!.
;;
;; vexec! performs the exact same operation as execute!, except that it also
;; analyzes the SQL query being run and dispatches "hints" to the view system which
;; trigger view refrehses for all subscribers of the views that the hints match.
(defn add-todo!
[title]
(vexec! view-system db ["INSERT INTO todos (title) VALUES (?)" title])
(response "ok"))
(defn delete-todo!
[id]
(vexec! view-system db ["DELETE FROM todos WHERE id = ?" id])
(response "ok"))
(defn update-todo!
[id title]
(vexec! view-system db ["UPDATE todos SET title = ? WHERE id = ?" title id])
(response "ok"))
(defn toggle-todo!
[id]
; note that a transaction is obviously not necessary here as we could have used
; just a single UPDATE query. however, it is being done this way to demonstrate
; using transactions with vexec!.
(with-view-transaction
view-system
[dt db]
(let [done? (:done (first (jdbc/query dt ["SELECT done FROM todos WHERE id = ?" id])))]
(vexec! view-system dt ["UPDATE todos SET done = ? WHERE id = ?" (not done?) id]))
(response "ok")))
(defn mark-all!
[done?]
(vexec! view-system db ["UPDATE todos SET done = ?" done?])
(response "ok"))
(defn delete-all-done!
[]
(vexec! view-system db ["DELETE FROM todos WHERE done = true"])
(response "ok"))
;; main page html
(defn render-page
[]
(html5
[:head
[:title "TodoMVC - views.reagent Example"]
(include-css "todos.css" "todosanim.css")
(include-js "cljs/app.js")]
[:body
(anti-forgery-field)
[:div#app [:h1 "This will become todomvc when the ClojureScript is compiled"]]
(javascript-tag "todomvc.client.run();")]))
;; Compojure routes and Ring handler
(def app-routes
(routes
; db action routes
(POST "/todos/add" [title] (add-todo! title))
(POST "/todos/delete" [id] (delete-todo! (Integer/parseInt id)))
(POST "/todos/update" [id title] (update-todo! (Integer/parseInt id) title))
(POST "/todos/toggle" [id] (toggle-todo! (Integer/parseInt id)))
(POST "/todos/mark-all" [done?] (mark-all! (Boolean/parseBoolean done?)))
(POST "/todos/delete-all-done" [] (delete-all-done!))
; main page
(GET "/" [] (render-page))
(route/resources "/")
(route/not-found "not found")))
; NOTE: We are passing in an empty event handler map to wrap-browserchannel only
; because this todo app is not using BrowserChannel for any purpose other
; then to provide client/server messaging for views.reagent. If we
; wanted to use it for client/server messaging in our application as well,
; we could pass in any event handlers we want here and it would not intefere
; with views.reagent.
(def handler
(-> app-routes
(wrap-defaults (assoc-in site-defaults [:security :anti-forgery] (not dev?)))
(wrap-browserchannel {} {:middleware [(vr/->middleware view-system)]})
(wrap-immutant-async-adapter)))
;; Web server startup & main
(defn run-server
[]
; views.reagent.browserchannel.server/init! takes care of initialization of views
; and views.reagent at the same time. As a result, we do not need to also call
; views.core/init! anywhere. The same arguments and options you are able to pass to
; views.core/init! can also be passed in here and they will be forwarded along, as
; this function is intended to be a drop-in replacement for views.core/init!.
;
; if you need to shutdown the views system (e.g. if you're using something like
; Component or Mount), you can just call views.core/shutdown!.
(vr/init! view-system {:views views})
(immutant/run handler {:port 8080}))
(defn -main
[& args]
(run-server))

View file

@ -1,22 +0,0 @@
(defproject gered/views.reagent.browserchannel "0.2-SNAPSHOT"
:description "BrowserChannel client/server messaging adapter for views.reagent."
:url "https://github.com/gered/views.reagent"
:license {:name "MIT License"
:url "http://opensource.org/licenses/MIT"}
:dependencies [[org.clojure/clojure "1.8.0"]]
:plugins [[lein-cljsbuild "1.1.3"]]
:profiles {:provided
{:dependencies
[[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.8.51"]
[reagent "0.6.0-alpha"]
[gered/views "1.5"]
[gered/views.reagent "0.1"]
[gered/clj-browserchannel "0.3.2"]]}}
:cljsbuild {:builds
{:main
{:source-paths ["src"]}}})

View file

@ -1,33 +0,0 @@
(ns views.reagent.browserchannel.client
(:require
[net.thegeez.browserchannel.client :as browserchannel]
[views.reagent.client.core :as client]))
(defn init!
"performs initial configuration necessary to hook browserchannel into views.reagent
as the client/server messaging backend. should be called once on page load before
browserchannel is initialized."
[]
(reset! client/send-fn
(fn [data]
(browserchannel/send-data! data))))
(def middleware
"clj-browserchannel client-side event middleware. this should be included in the
middleware list provided to net.thegeez.browserchannel.client/connect!"
{:on-receive
(fn [handler]
(fn [data]
(if-not (client/on-receive! data)
; only pass along receive events for data not intended for the views system
(handler data))))
:on-opening
(fn [handler]
(fn []
; we do this in on-opening instead of on-open since with browserchannel we
; have the ability to queue up messages to be sent to the server in the initial
; connection request. if this connection is actually a reconnection, then any
; subscription requests that need to be resent get sent all in one go this way.
(client/on-open!)
(handler)))})

View file

@ -1,52 +0,0 @@
(ns views.reagent.browserchannel.server
(:import
(clojure.lang Atom))
(:require
[clojure.tools.logging :as log]
[net.thegeez.browserchannel.server :as browserchannel]
[views.core :as views]
[views.reagent.server.core :as server]))
(defn- views-send-fn
[client-id [view-sig view-data]]
(log/trace client-id "refresh view" view-sig)
(browserchannel/send-data! client-id [:views/refresh [view-sig view-data]]))
(defn init!
"initializes the views system and adds browserchannel-specific configuration
to it to enable the necessary hooks into views.reagent.
this function acts as a direct replacement to calling views.core/init!, so
are able to initialize both views and views.reagent by calling this
function. the arguments and return value are the same as in views.core/init!
so see that function for more information.
one additional option :context-fn can be specified which is a function
that accepts an initial context map created by views.reagent and
allows your application to add any information necessary to the context
passed to various view system functions (such as auth-fn, namespace-fn, etc)."
([^Atom view-system options]
(let [options (-> options
(assoc :send-fn views-send-fn))]
(views/init! view-system options)
(server/set-context-fn! view-system (:context-fn options))))
([options]
(init! (atom {}) options)))
(defn ->middleware
"returns clj-browserchannel server-side event middleware for injecting
views.reagent handling into the clj-browserchannel client session
lifecycle handling. simply include the returned middleware map in your
Ring handler's wrap-browserchannel options."
[^Atom view-system]
{:on-receive
(fn [handler]
(fn [client-id request data]
(if-not (server/on-receive! view-system client-id data {:request request})
; only pass along receive events for data not intended for the views system
(handler client-id request data))))
:on-close
(fn [handler]
(fn [client-id request reason]
(server/on-close! view-system client-id {:request request})
(handler client-id request reason)))})