add chat-demo
This commit is contained in:
commit
98b4c7cb36
12
chat-demo/.gitignore
vendored
Normal file
12
chat-demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/target
|
||||||
|
/lib
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
.lein-deps-sum
|
||||||
|
.lein-failures
|
||||||
|
.lein-plugins
|
||||||
|
resources/dev/js
|
||||||
|
resources/public/js
|
1
chat-demo/Procfile
Normal file
1
chat-demo/Procfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
web: lein trampoline run -m chat-demo.core
|
37
chat-demo/README.md
Normal file
37
chat-demo/README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# clj-browserchannel-demo
|
||||||
|
|
||||||
|
Cross-browser compatible, real-time, bi-directional
|
||||||
|
communication between ClojureScript and Clojure using Google Closure
|
||||||
|
BrowserChannel.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
clj-browserchannel-demo is an example chat application using a server
|
||||||
|
side implementation for BrowserChannel written in Clojure. The server
|
||||||
|
component is for BrowserChannel version 8.
|
||||||
|
|
||||||
|
This enables client->server and server->client communication in
|
||||||
|
ClojureScript and Closure web apps, without any javascript
|
||||||
|
dependencies other than the Google Closure [library][2].
|
||||||
|
|
||||||
|
[2]: https://developers.google.com/closure/library/
|
||||||
|
|
||||||
|
The example runs in at least:
|
||||||
|
|
||||||
|
* Chrome
|
||||||
|
* Firefox
|
||||||
|
* Internet Explorer 5.5+ (!!)
|
||||||
|
* Android browser
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Written by:
|
||||||
|
Gijs Stuurman / [@thegeez][twt] / [Blog][blog] / [GitHub][github]
|
||||||
|
|
||||||
|
[twt]: http://twitter.com/thegeez
|
||||||
|
[blog]: http://thegeez.github.com
|
||||||
|
[github]: https://github.com/thegeez
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
Copyright (c) 2012 Gijs Stuurman and released under an MIT license.
|
50
chat-demo/cljs/bc/core.cljs
Normal file
50
chat-demo/cljs/bc/core.cljs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
(ns bc.core
|
||||||
|
(:require
|
||||||
|
[bc.dom-helpers :as dom]
|
||||||
|
[goog.net.BrowserChannel :as goog-browserchannel]
|
||||||
|
[goog.events :as events]
|
||||||
|
[goog.events.KeyCodes :as key-codes]
|
||||||
|
[goog.events.KeyHandler :as key-handler]))
|
||||||
|
|
||||||
|
(defn handler []
|
||||||
|
(let [h (goog.net.BrowserChannel.Handler.)]
|
||||||
|
(set! (.-channelOpened h)
|
||||||
|
(fn [channel]
|
||||||
|
(enable-chat)))
|
||||||
|
(set! (.-channelHandleArray h)
|
||||||
|
(fn [x data]
|
||||||
|
(let [msg (aget data "msg")]
|
||||||
|
(dom/append (dom/get-element "room") (dom/element :div (str "MSG::" msg))))))
|
||||||
|
h))
|
||||||
|
|
||||||
|
(defn say [text]
|
||||||
|
(.sendMap channel (doto (js-obj)
|
||||||
|
(aset "msg" text)) ))
|
||||||
|
|
||||||
|
(defn enable-chat []
|
||||||
|
(let [msg-input (dom/get-element "msg-input")
|
||||||
|
send-button (dom/get-element "send-button")
|
||||||
|
handler (fn [e]
|
||||||
|
(say (dom/value msg-input))
|
||||||
|
(dom/set-value msg-input ""))]
|
||||||
|
(dom/set-disabled msg-input false)
|
||||||
|
(dom/set-disabled send-button false)
|
||||||
|
(events/listen (goog.events.KeyHandler. msg-input)
|
||||||
|
"key"
|
||||||
|
(fn [e]
|
||||||
|
(when (= (.-keyCode e) key-codes/ENTER)
|
||||||
|
(handler e))))
|
||||||
|
(events/listen send-button
|
||||||
|
"click"
|
||||||
|
handler)))
|
||||||
|
|
||||||
|
(def channel (goog.net.BrowserChannel.))
|
||||||
|
|
||||||
|
(defn ^:export run []
|
||||||
|
(events/listen js/window "unload" #(do
|
||||||
|
(.disconnect channel ())
|
||||||
|
(events/removeAll)))
|
||||||
|
(doto channel
|
||||||
|
(.setHandler (handler))
|
||||||
|
(.connect "/channel/test" "/channel/bind")
|
||||||
|
))
|
141
chat-demo/cljs/bc/dom-helpers.cljs
Normal file
141
chat-demo/cljs/bc/dom-helpers.cljs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
; Copyright (c) Rich Hickey. All rights reserved.
|
||||||
|
; The use and distribution terms for this software are covered by the
|
||||||
|
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
||||||
|
; which can be found in the file epl-v10.html at the root of this distribution.
|
||||||
|
; By using this software in any fashion, you are agreeing to be bound by
|
||||||
|
; the terms of this license.
|
||||||
|
; You must not remove this notice, or any other, from this software.
|
||||||
|
|
||||||
|
(ns bc.dom-helpers
|
||||||
|
(:require [clojure.string :as string]
|
||||||
|
[goog.style :as style]
|
||||||
|
[goog.dom :as dom]
|
||||||
|
[goog.dom.classes :as classes]
|
||||||
|
[goog.dom.forms :as forms]
|
||||||
|
[goog.fx :as fx]
|
||||||
|
[goog.fx.dom :as fx-dom]
|
||||||
|
[goog.Timer :as timer]
|
||||||
|
))
|
||||||
|
|
||||||
|
(defn get-element
|
||||||
|
"Return the element with the passed id."
|
||||||
|
[id]
|
||||||
|
(dom/getElement (name id)))
|
||||||
|
|
||||||
|
(defn show-element [e b]
|
||||||
|
(style/showElement e b))
|
||||||
|
|
||||||
|
(defn add-remove-class [e add-classes remove-classes]
|
||||||
|
(classes/addRemove e remove-classes add-classes))
|
||||||
|
|
||||||
|
(defn get-radio-value [form-name name]
|
||||||
|
(forms/getValueByName (get-element form-name) name))
|
||||||
|
|
||||||
|
(defn value [element]
|
||||||
|
(forms/getValue element))
|
||||||
|
|
||||||
|
(defn set-value [element]
|
||||||
|
(forms/setValue element))
|
||||||
|
|
||||||
|
(defn set-disabled [element disabled]
|
||||||
|
(forms/setDisabled element disabled))
|
||||||
|
|
||||||
|
(defn append
|
||||||
|
"Append all children to parent."
|
||||||
|
[parent & children]
|
||||||
|
(do (doseq [child children]
|
||||||
|
(dom/appendChild parent child))
|
||||||
|
parent))
|
||||||
|
|
||||||
|
(defn set-text
|
||||||
|
"Set the text content for the passed element returning the
|
||||||
|
element. If a keyword is passed in the place of e, the element with
|
||||||
|
that id will be used and returned."
|
||||||
|
[e s]
|
||||||
|
(let [e (if (or (keyword? e) (string? e)) (get-element e) e)]
|
||||||
|
(doto e
|
||||||
|
(dom/setTextContent s))))
|
||||||
|
|
||||||
|
(defn normalize-args [tag args]
|
||||||
|
(let [parts (string/split tag #"(\.|#)")
|
||||||
|
[tag attrs] [(first parts)
|
||||||
|
(apply hash-map (map #(cond (= % ".") :class
|
||||||
|
(= % "#") :id
|
||||||
|
:else %)
|
||||||
|
(rest parts)))]]
|
||||||
|
(if (map? (first args))
|
||||||
|
[tag (merge attrs (first args)) (rest args)]
|
||||||
|
[tag attrs args])))
|
||||||
|
|
||||||
|
;; TODO: replace call to .strobj with whatever we come up with for
|
||||||
|
;; creating js objects from Clojure maps.
|
||||||
|
|
||||||
|
(defn element
|
||||||
|
"Create a dom element using a keyword for the element name and a map
|
||||||
|
for the attributes. Append all children to parent. If the first
|
||||||
|
child is a string then the string will be set as the text content of
|
||||||
|
the parent and all remaining children will be appended."
|
||||||
|
[tag & args]
|
||||||
|
(let [[tag attrs children] (normalize-args tag args)
|
||||||
|
;; keyword/string mangling screws up (name tag)
|
||||||
|
parent (dom/createDom (subs tag 1)
|
||||||
|
(. (reduce (fn [m [k v]]
|
||||||
|
(assoc m k v))
|
||||||
|
{}
|
||||||
|
(map #(vector (name %1) %2)
|
||||||
|
(keys attrs)
|
||||||
|
(vals attrs))) -strobj))
|
||||||
|
[parent children] (if (string? (first children))
|
||||||
|
[(set-text (element tag attrs) (first children))
|
||||||
|
(rest children)]
|
||||||
|
[parent children])]
|
||||||
|
(apply append parent children)))
|
||||||
|
|
||||||
|
(defn remove-children
|
||||||
|
"Remove all children from the element with the passed id."
|
||||||
|
[parent-el]
|
||||||
|
(dom/removeChildren parent-el))
|
||||||
|
|
||||||
|
(defn html
|
||||||
|
"Create a dom element from an html string."
|
||||||
|
[s]
|
||||||
|
(dom/htmlToDocumentFragment s))
|
||||||
|
|
||||||
|
(defn- element-arg? [x]
|
||||||
|
(or (keyword? x)
|
||||||
|
(map? x)
|
||||||
|
(string? x)))
|
||||||
|
|
||||||
|
(defn build
|
||||||
|
"Build up a dom element from nested vectors."
|
||||||
|
[x]
|
||||||
|
(if (vector? x)
|
||||||
|
(let [[parent children] (if (keyword? (first x))
|
||||||
|
[(apply element (take-while element-arg? x))
|
||||||
|
(drop-while element-arg? x)]
|
||||||
|
[(first x) (rest x)])
|
||||||
|
children (map build children)]
|
||||||
|
(apply append parent children))
|
||||||
|
x))
|
||||||
|
|
||||||
|
(defn insert-at
|
||||||
|
"Insert a child element at a specific location."
|
||||||
|
[parent child index]
|
||||||
|
(dom/insertChildAt parent child index))
|
||||||
|
|
||||||
|
(defn set-timeout [func ttime]
|
||||||
|
(timer/callOnce func ttime))
|
||||||
|
|
||||||
|
(defn set-position [e x y]
|
||||||
|
(style/setPosition e x y))
|
||||||
|
|
||||||
|
(defn get-position [e]
|
||||||
|
(style/getPosition e))
|
||||||
|
|
||||||
|
(defn toggle-class [el classname]
|
||||||
|
(classes/toggle el classname))
|
||||||
|
|
||||||
|
(defn add-class [el classname]
|
||||||
|
(classes/add el classname))
|
||||||
|
(defn remove-class [el classname]
|
||||||
|
(classes/remove el classname))
|
11
chat-demo/project.clj
Normal file
11
chat-demo/project.clj
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
(defproject chat-demo "0.0.1"
|
||||||
|
:description "Example for using BrowserChannel and a client side with ClojureScript"
|
||||||
|
:dependencies [[org.clojure/clojure "1.3.0"]
|
||||||
|
[ring/ring-core "1.1.0-SNAPSHOT"]
|
||||||
|
[org.clojure/clojurescript "0.0-1011" :exclusions [org.clojure/google-closure-library]]
|
||||||
|
[net.thegeez/google-closure-library "0.0-1698"]
|
||||||
|
[net.thegeez/clj-browserchannel-server "0.0.1"]
|
||||||
|
[net.thegeez/clj-browserchannel-jetty-adapter "0.0.1"]
|
||||||
|
[net.thegeez/clj-browserchannel-netty-adapter "0.0.1"]
|
||||||
|
]
|
||||||
|
)
|
36
chat-demo/resources/dev/index-dev.html
Normal file
36
chat-demo/resources/dev/index-dev.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>BrowserChannel</title>
|
||||||
|
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="css/default.css" media="screen" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="room">
|
||||||
|
</div>
|
||||||
|
<div id="type-bar">
|
||||||
|
<input id="msg-input" size="30" type="text" disabled="disabled"/>
|
||||||
|
<input id="send-button" type="button" value="Send" disabled="disabled"/>
|
||||||
|
</div>
|
||||||
|
<div class="about"><a href="https://github.com/thegeez/clj-browserchannel-demo">clj-browserchannel-demo</a></div>
|
||||||
|
<div class="about">
|
||||||
|
Written by: Gijs Stuurman
|
||||||
|
/ <a href="http://twitter.com/thegeez">@thegeez</a>
|
||||||
|
/ <a href="http://thegeez.github.com">Blog</a> / <a href="https://github.com/thegeez">GitHub</a></div>
|
||||||
|
<script>
|
||||||
|
var CLOSURE_NO_DEPS = true;
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="js/bc-dev.js"></script>
|
||||||
|
<script>
|
||||||
|
goog.require('bc.core');
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
bc.core.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
0
chat-demo/resources/dev/js/compile_target_dir
Normal file
0
chat-demo/resources/dev/js/compile_target_dir
Normal file
19
chat-demo/resources/public/css/default.css
Normal file
19
chat-demo/resources/public/css/default.css
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
background-color: #8fb3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#room {
|
||||||
|
border: 3px solid #5780d7;
|
||||||
|
background-color: #f4eeee;
|
||||||
|
min-height: 10em;
|
||||||
|
}
|
||||||
|
#type-bar {
|
||||||
|
padding: 3px;
|
||||||
|
background-color: #62b031;
|
||||||
|
}
|
||||||
|
.about {
|
||||||
|
padding: 3px;
|
||||||
|
background-color: #90db46;
|
||||||
|
}
|
30
chat-demo/resources/public/index.html
Normal file
30
chat-demo/resources/public/index.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>BrowserChannel</title>
|
||||||
|
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="css/default.css" media="screen" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="room">
|
||||||
|
</div>
|
||||||
|
<div id="type-bar">
|
||||||
|
<input id="msg-input" size="30" type="text" disabled="disabled"/>
|
||||||
|
<input id="send-button" type="button" value="Send" disabled="disabled"/>
|
||||||
|
</div>
|
||||||
|
<div class="about"><a href="https://github.com/thegeez/clj-browserchannel-demo">clj-browserchannel-demo</a></div>
|
||||||
|
<div class="about">
|
||||||
|
Written by: Gijs Stuurman
|
||||||
|
/ <a href="http://twitter.com/thegeez">@thegeez</a>
|
||||||
|
/ <a href="http://thegeez.github.com">Blog</a> / <a href="https://github.com/thegeez">GitHub</a></div>
|
||||||
|
<script type="text/javascript" src="js/bc.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
bc.core.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
0
chat-demo/resources/public/js/compile_target_dir
Normal file
0
chat-demo/resources/public/js/compile_target_dir
Normal file
75
chat-demo/src/chat_demo/core.clj
Normal file
75
chat-demo/src/chat_demo/core.clj
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
(ns chat-demo.core
|
||||||
|
(:require [net.thegeez.browserchannel :as browserchannel]
|
||||||
|
[net.thegeez.jetty-async-adapter :as jetty]
|
||||||
|
[net.thegeez.netty-adapter :as netty]
|
||||||
|
[ring.middleware.resource :as resource]
|
||||||
|
[ring.middleware.file-info :as file]))
|
||||||
|
|
||||||
|
(defn handler [req]
|
||||||
|
{:status 200
|
||||||
|
:headers {"Content-Type" "text/plain"}
|
||||||
|
:body "Hello World"})
|
||||||
|
|
||||||
|
(def clients (atom #{}))
|
||||||
|
|
||||||
|
(def dev-app
|
||||||
|
(-> handler
|
||||||
|
(resource/wrap-resource "dev")
|
||||||
|
(resource/wrap-resource "public")
|
||||||
|
file/wrap-file-info
|
||||||
|
(browserchannel/wrap-browserchannel {:base "/channel"
|
||||||
|
:on-session
|
||||||
|
(fn [session-id]
|
||||||
|
(println "session " session-id "connected")
|
||||||
|
|
||||||
|
(browserchannel/add-listener
|
||||||
|
session-id
|
||||||
|
:close
|
||||||
|
(fn [reason]
|
||||||
|
(println "session " session-id " disconnected: " reason)
|
||||||
|
(swap! clients disj session-id)
|
||||||
|
(doseq [client-id @clients]
|
||||||
|
(browserchannel/send-map client-id {"msg" (str "client " session-id " disconnected " reason)}))))
|
||||||
|
(browserchannel/add-listener
|
||||||
|
session-id
|
||||||
|
:map
|
||||||
|
(fn [map]
|
||||||
|
(println "session " session-id " sent " map)
|
||||||
|
(doseq [client-id @clients]
|
||||||
|
(browserchannel/send-map client-id map))))
|
||||||
|
(swap! clients conj session-id)
|
||||||
|
(doseq [client-id @clients]
|
||||||
|
(browserchannel/send-map client-id {"msg" (str "client " session-id " connected")})))})))
|
||||||
|
|
||||||
|
#_(defn -main [& args]
|
||||||
|
(println "Using Jetty adapter")
|
||||||
|
(jetty/run-jetty-async #'dev-app {:port (Integer.
|
||||||
|
(or
|
||||||
|
(System/getenv "PORT")
|
||||||
|
8080)) :join? false}))
|
||||||
|
|
||||||
|
(defn -main [& args]
|
||||||
|
(println "Using Netty adapter")
|
||||||
|
(netty/run-netty #'dev-app {:port (Integer.
|
||||||
|
(or
|
||||||
|
(System/getenv "PORT")
|
||||||
|
8080)) :join? false}))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(def jetty-async-server (-main))
|
||||||
|
(.stop jetty-async-server)
|
||||||
|
(do
|
||||||
|
(.stop jetty-async-server)
|
||||||
|
(def jetty-async-server (-main))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(def netty-async-server (-main))
|
||||||
|
(netty-async-server)
|
||||||
|
(do
|
||||||
|
(netty-async-server)
|
||||||
|
(def netty-async-server (-main))
|
||||||
|
)
|
||||||
|
)
|
6
chat-demo/src/tasks/build_advanced_js.clj
Normal file
6
chat-demo/src/tasks/build_advanced_js.clj
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
(ns tasks.build-advanced-js
|
||||||
|
(:require [cljs.closure :as cljs]))
|
||||||
|
|
||||||
|
(defn -main [& args]
|
||||||
|
(cljs/build "cljs" {:optimizations :advanced
|
||||||
|
:output-to "resources/public/js/bc.js"}))
|
7
chat-demo/src/tasks/build_dev_js.clj
Normal file
7
chat-demo/src/tasks/build_dev_js.clj
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(ns tasks.build-dev-js
|
||||||
|
(:require [cljs.closure :as cljs]))
|
||||||
|
|
||||||
|
(defn -main [& args]
|
||||||
|
(cljs/build "cljs" {;; whitespace makes it a single file
|
||||||
|
:optimizations :whitespace
|
||||||
|
:output-to "resources/dev/js/bc-dev.js"}))
|
Reference in a new issue