commit 98b4c7cb362ac85ea2c14da8e3c579bd8470e47a Author: Gijs Stuurman Date: Sat Apr 7 21:02:07 2012 +0200 add chat-demo diff --git a/chat-demo/.gitignore b/chat-demo/.gitignore new file mode 100644 index 0000000..2113f6e --- /dev/null +++ b/chat-demo/.gitignore @@ -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 diff --git a/chat-demo/Procfile b/chat-demo/Procfile new file mode 100644 index 0000000..e754de0 --- /dev/null +++ b/chat-demo/Procfile @@ -0,0 +1 @@ +web: lein trampoline run -m chat-demo.core diff --git a/chat-demo/README.md b/chat-demo/README.md new file mode 100644 index 0000000..e3bf126 --- /dev/null +++ b/chat-demo/README.md @@ -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. diff --git a/chat-demo/cljs/bc/core.cljs b/chat-demo/cljs/bc/core.cljs new file mode 100644 index 0000000..fe7e81d --- /dev/null +++ b/chat-demo/cljs/bc/core.cljs @@ -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") + )) diff --git a/chat-demo/cljs/bc/dom-helpers.cljs b/chat-demo/cljs/bc/dom-helpers.cljs new file mode 100644 index 0000000..a65b80e --- /dev/null +++ b/chat-demo/cljs/bc/dom-helpers.cljs @@ -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)) diff --git a/chat-demo/project.clj b/chat-demo/project.clj new file mode 100644 index 0000000..3afcbc6 --- /dev/null +++ b/chat-demo/project.clj @@ -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"] + ] + ) diff --git a/chat-demo/resources/dev/index-dev.html b/chat-demo/resources/dev/index-dev.html new file mode 100644 index 0000000..2949d28 --- /dev/null +++ b/chat-demo/resources/dev/index-dev.html @@ -0,0 +1,36 @@ + + + + + + BrowserChannel + + + + + +
+
+
+ + +
+
clj-browserchannel-demo
+
+ Written by: Gijs Stuurman + / @thegeez + / Blog / GitHub
+ + + + + + diff --git a/chat-demo/resources/dev/js/compile_target_dir b/chat-demo/resources/dev/js/compile_target_dir new file mode 100644 index 0000000..e69de29 diff --git a/chat-demo/resources/public/css/default.css b/chat-demo/resources/public/css/default.css new file mode 100644 index 0000000..06a397e --- /dev/null +++ b/chat-demo/resources/public/css/default.css @@ -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; +} diff --git a/chat-demo/resources/public/index.html b/chat-demo/resources/public/index.html new file mode 100644 index 0000000..5eda764 --- /dev/null +++ b/chat-demo/resources/public/index.html @@ -0,0 +1,30 @@ + + + + + + BrowserChannel + + + + + +
+
+
+ + +
+
clj-browserchannel-demo
+
+ Written by: Gijs Stuurman + / @thegeez + / Blog / GitHub
+ + + + diff --git a/chat-demo/resources/public/js/compile_target_dir b/chat-demo/resources/public/js/compile_target_dir new file mode 100644 index 0000000..e69de29 diff --git a/chat-demo/src/chat_demo/core.clj b/chat-demo/src/chat_demo/core.clj new file mode 100644 index 0000000..e753dc5 --- /dev/null +++ b/chat-demo/src/chat_demo/core.clj @@ -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)) + ) + ) diff --git a/chat-demo/src/tasks/build_advanced_js.clj b/chat-demo/src/tasks/build_advanced_js.clj new file mode 100644 index 0000000..3862810 --- /dev/null +++ b/chat-demo/src/tasks/build_advanced_js.clj @@ -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"})) diff --git a/chat-demo/src/tasks/build_dev_js.clj b/chat-demo/src/tasks/build_dev_js.clj new file mode 100644 index 0000000..57543e1 --- /dev/null +++ b/chat-demo/src/tasks/build_dev_js.clj @@ -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"}))