Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit of a simple chatroom service.

  • Loading branch information...
commit a253122911aff862d716557ac4f4ee59f4597aeb 0 parents
@alexkehayias authored
12 project/project.clj
@@ -0,0 +1,12 @@
+(defproject project "0.01"
+ :description "Aleph testing"
+ :dependencies [[org.clojure/clojure "1.3.0"]
+ [aleph "0.3.0-alpha2"]
+ [compojure "1.1.1"]
+ [ring "1.1.0-beta2"]
+ [hiccup "1.0.0-beta1"]
+ [lein-swank "1.4.4"]]
+ :source-path "src/clj"
+ :aot [core.main]
+ :ring {:handler core.main/-main}
+ :main core.main)
10 project/resources/coffee/app.coffee
@@ -0,0 +1,10 @@
+$ ->
+ window.socket = new WebSocket window.location.href.replace("http://", "ws://")
+ socket.onopen = ->
+ console.log "socket opened"
+ socket.onmessage = (msg) ->
+ $("#messages").append("<p>"+msg.data+"</p>")
+ $("form").on "submit", (e) ->
+ e.preventDefault()
+ socket.send $("#message").val()
+ $("#message").val ""
18 project/resources/public/javascripts/app.js
@@ -0,0 +1,18 @@
+(function() {
+
+ $(function() {
+ window.socket = new WebSocket(window.location.href.replace("http://", "ws://"));
+ socket.onopen = function() {
+ return console.log("socket opened");
+ };
+ socket.onmessage = function(msg) {
+ return $("#messages").append("<p>" + msg.data + "</p>");
+ };
+ return $("form").on("submit", function(e) {
+ e.preventDefault();
+ socket.send($("#message").val());
+ return $("#message").val("");
+ });
+ });
+
+}).call(this);
389 project/resources/public/javascripts/web_socket.js
@@ -0,0 +1,389 @@
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/rfc6455
+
+(function() {
+
+ if (window.WEB_SOCKET_FORCE_FLASH) {
+ // Keeps going.
+ } else if (window.WebSocket) {
+ return;
+ } else if (window.MozWebSocket) {
+ // Firefox.
+ window.WebSocket = MozWebSocket;
+ return;
+ }
+
+ var logger;
+ if (window.WEB_SOCKET_LOGGER) {
+ logger = WEB_SOCKET_LOGGER;
+ } else if (window.console && window.console.log && window.console.error) {
+ // In some environment, console is defined but console.log or console.error is missing.
+ logger = window.console;
+ } else {
+ logger = {log: function(){ }, error: function(){ }};
+ }
+
+ // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
+ if (swfobject.getFlashPlayerVersion().major < 10) {
+ logger.error("Flash Player >= 10.0.0 is required.");
+ return;
+ }
+ if (location.protocol == "file:") {
+ logger.error(
+ "WARNING: web-socket-js doesn't work in file:///... URL " +
+ "unless you set Flash Security Settings properly. " +
+ "Open the page via Web server i.e. http://...");
+ }
+
+ /**
+ * Our own implementation of WebSocket class using Flash.
+ * @param {string} url
+ * @param {array or string} protocols
+ * @param {string} proxyHost
+ * @param {int} proxyPort
+ * @param {string} headers
+ */
+ window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
+ var self = this;
+ self.__id = WebSocket.__nextId++;
+ WebSocket.__instances[self.__id] = self;
+ self.readyState = WebSocket.CONNECTING;
+ self.bufferedAmount = 0;
+ self.__events = {};
+ if (!protocols) {
+ protocols = [];
+ } else if (typeof protocols == "string") {
+ protocols = [protocols];
+ }
+ // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+ // Otherwise, when onopen fires immediately, onopen is called before it is set.
+ self.__createTask = setTimeout(function() {
+ WebSocket.__addTask(function() {
+ self.__createTask = null;
+ WebSocket.__flash.create(
+ self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
+ });
+ }, 0);
+ };
+
+ /**
+ * Send data to the web socket.
+ * @param {string} data The data to send to the socket.
+ * @return {boolean} True for success, false for failure.
+ */
+ WebSocket.prototype.send = function(data) {
+ if (this.readyState == WebSocket.CONNECTING) {
+ throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+ }
+ // We use encodeURIComponent() here, because FABridge doesn't work if
+ // the argument includes some characters. We don't use escape() here
+ // because of this:
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+ // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+ // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+ // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
+ // additional testing.
+ var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+ if (result < 0) { // success
+ return true;
+ } else {
+ this.bufferedAmount += result;
+ return false;
+ }
+ };
+
+ /**
+ * Close this web socket gracefully.
+ */
+ WebSocket.prototype.close = function() {
+ if (this.__createTask) {
+ clearTimeout(this.__createTask);
+ this.__createTask = null;
+ this.readyState = WebSocket.CLOSED;
+ return;
+ }
+ if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+ return;
+ }
+ this.readyState = WebSocket.CLOSING;
+ WebSocket.__flash.close(this.__id);
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {string} type
+ * @param {function} listener
+ * @param {boolean} useCapture
+ * @return void
+ */
+ WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+ if (!(type in this.__events)) {
+ this.__events[type] = [];
+ }
+ this.__events[type].push(listener);
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {string} type
+ * @param {function} listener
+ * @param {boolean} useCapture
+ * @return void
+ */
+ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+ if (!(type in this.__events)) return;
+ var events = this.__events[type];
+ for (var i = events.length - 1; i >= 0; --i) {
+ if (events[i] === listener) {
+ events.splice(i, 1);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {Event} event
+ * @return void
+ */
+ WebSocket.prototype.dispatchEvent = function(event) {
+ var events = this.__events[event.type] || [];
+ for (var i = 0; i < events.length; ++i) {
+ events[i](event);
+ }
+ var handler = this["on" + event.type];
+ if (handler) handler.apply(this, [event]);
+ };
+
+ /**
+ * Handles an event from Flash.
+ * @param {Object} flashEvent
+ */
+ WebSocket.prototype.__handleEvent = function(flashEvent) {
+
+ if ("readyState" in flashEvent) {
+ this.readyState = flashEvent.readyState;
+ }
+ if ("protocol" in flashEvent) {
+ this.protocol = flashEvent.protocol;
+ }
+
+ var jsEvent;
+ if (flashEvent.type == "open" || flashEvent.type == "error") {
+ jsEvent = this.__createSimpleEvent(flashEvent.type);
+ } else if (flashEvent.type == "close") {
+ jsEvent = this.__createSimpleEvent("close");
+ jsEvent.wasClean = flashEvent.wasClean ? true : false;
+ jsEvent.code = flashEvent.code;
+ jsEvent.reason = flashEvent.reason;
+ } else if (flashEvent.type == "message") {
+ var data = decodeURIComponent(flashEvent.message);
+ jsEvent = this.__createMessageEvent("message", data);
+ } else {
+ throw "unknown event type: " + flashEvent.type;
+ }
+
+ this.dispatchEvent(jsEvent);
+
+ };
+
+ WebSocket.prototype.__createSimpleEvent = function(type) {
+ if (document.createEvent && window.Event) {
+ var event = document.createEvent("Event");
+ event.initEvent(type, false, false);
+ return event;
+ } else {
+ return {type: type, bubbles: false, cancelable: false};
+ }
+ };
+
+ WebSocket.prototype.__createMessageEvent = function(type, data) {
+ if (document.createEvent && window.MessageEvent && !window.opera) {
+ var event = document.createEvent("MessageEvent");
+ event.initMessageEvent("message", false, false, data, null, null, window, null);
+ return event;
+ } else {
+ // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+ return {type: type, data: data, bubbles: false, cancelable: false};
+ }
+ };
+
+ /**
+ * Define the WebSocket readyState enumeration.
+ */
+ WebSocket.CONNECTING = 0;
+ WebSocket.OPEN = 1;
+ WebSocket.CLOSING = 2;
+ WebSocket.CLOSED = 3;
+
+ WebSocket.__initialized = false;
+ WebSocket.__flash = null;
+ WebSocket.__instances = {};
+ WebSocket.__tasks = [];
+ WebSocket.__nextId = 0;
+
+ /**
+ * Load a new flash security policy file.
+ * @param {string} url
+ */
+ WebSocket.loadFlashPolicyFile = function(url){
+ WebSocket.__addTask(function() {
+ WebSocket.__flash.loadManualPolicyFile(url);
+ });
+ };
+
+ /**
+ * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+ */
+ WebSocket.__initialize = function() {
+
+ if (WebSocket.__initialized) return;
+ WebSocket.__initialized = true;
+
+ if (WebSocket.__swfLocation) {
+ // For backword compatibility.
+ window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+ }
+ if (!window.WEB_SOCKET_SWF_LOCATION) {
+ logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+ return;
+ }
+ if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
+ !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
+ WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
+ var swfHost = RegExp.$1;
+ if (location.host != swfHost) {
+ logger.error(
+ "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
+ "('" + location.host + "' != '" + swfHost + "'). " +
+ "See also 'How to host HTML file and SWF file in different domains' section " +
+ "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
+ "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
+ }
+ }
+ var container = document.createElement("div");
+ container.id = "webSocketContainer";
+ // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+ // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+ // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+ // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+ // the best we can do as far as we know now.
+ container.style.position = "absolute";
+ if (WebSocket.__isFlashLite()) {
+ container.style.left = "0px";
+ container.style.top = "0px";
+ } else {
+ container.style.left = "-100px";
+ container.style.top = "-100px";
+ }
+ var holder = document.createElement("div");
+ holder.id = "webSocketFlash";
+ container.appendChild(holder);
+ document.body.appendChild(container);
+ // See this article for hasPriority:
+ // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+ swfobject.embedSWF(
+ WEB_SOCKET_SWF_LOCATION,
+ "webSocketFlash",
+ "1" /* width */,
+ "1" /* height */,
+ "10.0.0" /* SWF version */,
+ null,
+ null,
+ {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+ null,
+ function(e) {
+ if (!e.success) {
+ logger.error("[WebSocket] swfobject.embedSWF failed");
+ }
+ }
+ );
+
+ };
+
+ /**
+ * Called by Flash to notify JS that it's fully loaded and ready
+ * for communication.
+ */
+ WebSocket.__onFlashInitialized = function() {
+ // We need to set a timeout here to avoid round-trip calls
+ // to flash during the initialization process.
+ setTimeout(function() {
+ WebSocket.__flash = document.getElementById("webSocketFlash");
+ WebSocket.__flash.setCallerUrl(location.href);
+ WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+ for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+ WebSocket.__tasks[i]();
+ }
+ WebSocket.__tasks = [];
+ }, 0);
+ };
+
+ /**
+ * Called by Flash to notify WebSockets events are fired.
+ */
+ WebSocket.__onFlashEvent = function() {
+ setTimeout(function() {
+ try {
+ // Gets events using receiveEvents() instead of getting it from event object
+ // of Flash event. This is to make sure to keep message order.
+ // It seems sometimes Flash events don't arrive in the same order as they are sent.
+ var events = WebSocket.__flash.receiveEvents();
+ for (var i = 0; i < events.length; ++i) {
+ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+ }
+ } catch (e) {
+ logger.error(e);
+ }
+ }, 0);
+ return true;
+ };
+
+ // Called by Flash.
+ WebSocket.__log = function(message) {
+ logger.log(decodeURIComponent(message));
+ };
+
+ // Called by Flash.
+ WebSocket.__error = function(message) {
+ logger.error(decodeURIComponent(message));
+ };
+
+ WebSocket.__addTask = function(task) {
+ if (WebSocket.__flash) {
+ task();
+ } else {
+ WebSocket.__tasks.push(task);
+ }
+ };
+
+ /**
+ * Test if the browser is running flash lite.
+ * @return {boolean} True if flash lite is running, false otherwise.
+ */
+ WebSocket.__isFlashLite = function() {
+ if (!window.navigator || !window.navigator.mimeTypes) {
+ return false;
+ }
+ var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+ if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+ return false;
+ }
+ return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+ };
+
+ if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+ // NOTE:
+ // This fires immediately if web_socket.js is dynamically loaded after
+ // the document is loaded.
+ swfobject.addDomLoadEvent(function() {
+ WebSocket.__initialize();
+ });
+ }
+
+})();
1  project/resources/public/stylesheets/master.css
@@ -0,0 +1 @@
+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font:inherit;vertical-align:baseline}html{font-size:62.5%}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after{content:"";content:none}q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}body{background:#fff;font-family:"Helvetica Neue","HelveticaNeue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:13px;line-height:18px;color:#555;position:relative;-webkit-font-smoothing:antialiased}a{color:#2a85e8;text-decoration:none;outline:0;line-height:inherit}a:hover{color:#11639d}p a{line-height:inherit}p a:visited{line-height:inherit}ul,ol{margin-bottom:18px}ul{list-style:none outside}ol{list-style:decimal;margin-left:30px}ul.square,ul.circle,ul.disc{margin-left:30px}ul.square{list-style:square outside}ul.circle{list-style:circle outside}ul.disc{list-style:disc outside}ul ul{margin:4px 0 5px 30px}ol ol{margin:4px 0 5px 30px}li{margin-bottom:12px}ul.large li{line-height:21px}table{background:#fff;-moz-border-radius:3px;-webkit-border-radius:3px;width:100%;margin:0 0 18px;border:1px solid #ddd}table thead{background:#f5f5f5}table thead tr th{font-size:12px;line-height:18px;text-align:left}table tbody tr td{font-size:12px;line-height:18px;text-align:left}table thead tr th{padding:8px 10px 9px;font-size:14px;font-weight:bold;color:#222}table thead tr th:first-child{border-left:none}table thead tr th:last-child{border-right:none}table tbody tr.even,table tbody tr.alt,table tbody tr:nth-child(even){background:#f9f9f9}table tbody tr td{color:#333;padding:9px 10px;vertical-align:top;border:none}.left{float:left}.right{float:right}.hide{display:none}.highlight{background:#ff0}div.panel{border:1px solid #ccc}h1,h2,h3,h4,h5,h6{color:#181818;font-weight:bold;line-height:1.25}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:46px;font-size:4.6rem;margin-bottom:12px}h2{font-size:35px;font-size:3.5rem;margin-bottom:9px}h3{font-size:28px;font-size:2.8rem;margin-bottom:9px}h4{font-size:21px;font-size:2.1rem;margin-bottom:3px}h5{font-size:18px;font-size:1.8rem;font-weight:normal;margin-bottom:3px}h6{font-size:15px;font-size:1.5rem;font-weight:normal}.subheader{color:#777;font-weight:300;margin-bottom:24px}p{line-height:17px;margin:0 0 18px}p img{margin:0}p.lead{font-size:18px;font-size:1.8rem;line-height:24px}@media handheld, only screen and (max-width: 767px){body,p{font-size:15px;font-size:1.5rem;line-height:1.4}}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}h1 small,h2 small,h3 small,h4 small,h5 small{color:#777}blockquote{line-height:20px;color:#777;margin:0 0 18px;padding:9px 20px 0 19px;border-left:1px solid #ddd}blockquote p{line-height:20px;color:#777}blockquote cite{display:block;font-size:12px;font-size:1.2rem;color:#555}blockquote cite:before{content:"\2014 \0020"}blockquote cite a{color:#555}blockquote cite a:visited{color:#555}hr{border:solid #ddd;border-width:1px 0 0;clear:both;margin:12px 0 18px;height:0}abbr,acronym{text-transform:uppercase;font-size:90%;color:#222;border-bottom:1px solid #ddd;cursor:help}abbr{text-transform:none}@media print{*{background:transparent !important;color:#000 !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}p a{color:#444 !important;text-decoration:underline}p a:visited{color:#444 !important;text-decoration:underline}p a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after{content:""}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.button{background:#00a6fc;display:inline-block;text-align:center;padding:9px 34px 11px;color:#fff;text-decoration:none;font-weight:bold;line-height:1;font-family:"Helvetica Neue","Helvetica",Arial,Verdana,sans-serif;position:relative;cursor:pointer;border:none}input[type=submit].button{-webkit-appearance:none}.button.nice{background:#00a6fc url(/static/images/button-gloss.png) repeat-x 0 -34px;-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);text-shadow:0 -1px 1px rgba(0,0,0,0.28);background:#00a6fc url(/static/images/button-gloss.png) repeat-x 0 -34px,-moz-linear-gradient(top, rgba(255,255,255,0.4) 0%, transparent 100%);background:#00a6fc url(/static/images/button-gloss.png) repeat-x 0 -34px,-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(255,255,255,0.4)), color-stop(100%, transparent));border:1px solid #0593dc;-webkit-transition:background-color 0.15s ease-in-out;-moz-transition:background-color 0.15s ease-in-out;-o-transition:background-color 0.15s ease-in-out}.button.radius{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.button.round{-moz-border-radius:1000px;-webkit-border-radius:1000px;border-radius:1000px}.button.full-width{width:100%;padding-left:0 !important;padding-right:0 !important;text-align:center}.button.left-align{text-align:left;text-indent:12px}.small.button{font-size:11px;padding:8px 20px 10px;width:auto}.medium.button{font-size:13px;width:auto}.large.button{font-size:18px;padding:11px 48px 13px;width:auto}.nice.small.button{background-position:0 -36px}.nice.large.button{background-position:0 -30px}.blue.button{background-color:#00a6fc}.red.button{background-color:#e91c21}.white.button{background-color:#e9e9e9;color:#333}.black.button{background-color:#141414}.nice.blue.button{border:1px solid #0593dc}.nice.red.button{border:1px solid #b90b0b}.nice.white.button{border:1px solid #cacaca;text-shadow:none !important}.nice.black.button{border:1px solid #000}.button:hover,.button:focus{background-color:#0192dd;color:#fff}.blue.button:hover,.blue.button:focus{background-color:#0192dd}.red.button:hover,.red.button:focus{background-color:#d01217}.white.button:hover,.white.button:focus{background-color:#dadada;color:#333}.black.button:hover,.black.button:focus{background-color:#000}.button.disabled,.button[disabled]{opacity:0.6;cursor:default}div.alert-box{display:block;padding:6px 7px;font-weight:bold;font-size:13px;background:#eee;border:1px solid rgba(0,0,0,0.1);margin-bottom:12px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;text-shadow:0 1px rgba(255,255,255,0.9);position:relative}.alert-box.success{background-color:#7fae00;color:#fff;text-shadow:0 -1px rgba(0,0,0,0.3)}.alert-box.warning{background-color:#c08c00;color:#fff;text-shadow:0 -1px rgba(0,0,0,0.3)}.alert-box.error{background-color:#c00000;color:#fff;text-shadow:0 -1px rgba(0,0,0,0.3)}.alert-box a.close{color:#000;position:absolute;right:4px;top:0;font-size:18px;opacity:0.2;padding:4px}.alert-box a.close:hover,.alert-box a.close:focus{opacity:0.4}dl.tabs{display:block;margin:0 0 20px 0;padding:0;height:30px;border-bottom:solid 1px #ddd}dl.tabs dt{display:block;width:auto;height:30px;padding:0 9px 0 20px;line-height:30px;float:left;color:#999;font-size:11px;text-transform:uppercase;cursor:default}dl.tabs dt:first-child{padding:0 9px 0 0}dl.tabs dd{display:block;width:auto;height:30px;padding:0;float:left}dl.tabs dd a{display:block;width:auto;height:29px;padding:0 9px;line-height:30px;border:solid 1px #ddd;margin:0 -1px 0 0;color:#555;background:#eee}dl.tabs dd a.active{background:#fff;border-width:1px 1px 0 1px;height:30px}.nice.tabs{border-bottom:solid 1px #eee;margin:0 0 30px 0;height:43px}.nice.tabs dd a{padding:7px 18px 9px;font-size:15px;font-size:1.5rem;color:#555;background:none;border:none}.nice.tabs dd a.active{font-weight:bold;color:#333;background:#fff;border-left:1px solid #eee;border-right:1px solid #eee;border-top:3px solid #00a6fc;margin:0 10px;position:relative;top:-5px}.nice.tabs dd:first-child a.active{margin-left:0}dl.tabs.vertical{height:auto}dl.tabs.vertical dt,dl.tabs.vertical dd{float:none;height:auto}dl.nice.tabs.vertical dt,dl.nice.tabs.vertical dd{float:none;height:auto}dl.tabs.vertical dd a{display:block;width:auto;height:auto;padding:15px 20px;line-height:1;border:solid 0 #ccc;border-width:1px 1px 0;margin:0;color:#555;background:#eee;font-size:15px;font-size:1.5rem}dl.tabs.vertical dd a.active{height:auto;margin:0;border-width:1px 0 0;background:#fff}.nice.tabs.vertical{border-bottom:solid 1px #eee;height:auto;cursor:pointer}.nice.tabs.vertical dd a{padding:15px 20px;border:none;border-left:1px solid #eee;border-right:1px solid #eee;border-top:1px solid #eee;background:#fff}.nice.tabs.vertical dd a.active{border:none;background:#00a6fc;color:#fff;margin:0;position:static;top:0;height:auto}.nice.tabs.vertical dd:first-child a.active{margin:0}ul.tabs-content{margin:0;display:block}ul.tabs-content > li{display:none}ul.tabs-content > li.active{display:block}dl.contained,dl.nice.contained{margin-bottom:0}dl.contained.tabs dd a{padding:0 14px}dl.nice.contained.tabs dd a{padding:7px 18px 9px}ul.contained.tabs-content{padding:0}ul.contained.tabs-content > li{padding:20px;border:solid 0 #ddd;border-width:0 1px 1px 1px}ul.nice.contained.tabs-content > li{border-color:#eee}ul.pagination{display:block;height:24px;margin-left:-5px}ul.pagination li{float:left;display:block;height:24px;color:#999;font-size:15px;margin-left:5px}ul.pagination li a{display:block;padding:6px 7px 4px;color:#555}ul.pagination li.current a,ul.pagination li:hover a,ul.pagination li a:focus{border-bottom:solid 2px #00a6fc;color:#141414}ul.pagination li.unavailable a{cursor:default;color:#999}ul.pagination li.unavailable:hover a,ul.pagination li.unavailable a:focus{border-bottom:none}ul.nice{list-style:none;margin:0}ol.nice{list-style:none;margin:0}ul.nice li,ol.nice li{padding-left:13px;position:relative}ul.nice li span.bullet,ol.nice li span.number{position:absolute;left:0;top:0;color:#ccc}div.panel{padding:20px 20px 2px 20px;background:#efefef;background:-moz-linear-gradient(top, #fff 0%, #f4f4f4 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #f4f4f4));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFF', endColorstr='#F4F4F4',GradientType=0 );box-shadow:0 2px 5px rgba(0,0,0,0.15);-webkit-box-shadow:0 2px 5px rgba(0,0,0,0.15);-moz-box-shadow:0 2px 5px rgba(0,0,0,0.25);margin:0 0 20px 0}.nav-bar{height:45px;background:#fff;margin-top:20px;border:1px solid #ddd}.nav-bar > li{float:left;display:block;position:relative;padding:0;margin:0;border-right:1px solid #ddd;line-height:45px}.nav-bar > li > a{position:relative;font-size:14px;padding:0 20px;display:block;text-decoration:none;font-size:15px;font-size:1.5rem}.nav-bar > li > input{margin:0 16px}.nav-bar > li ul{margin-bottom:0}.nav-bar > li li{line-height:1.3}.nav-bar > li.has-flyout > a{padding-right:25px}.nav-bar > li.has-flyout > a:after{content:"";width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #2a85e8;display:block;position:absolute;right:5px;bottom:20px}.nav-bar > li:hover > a{color:#141414;z-index:2}.nav-bar > li:hover > a:after{border-top-color:#141414}.flyout{background:#eee;margin:0;padding:0 20px 0 20px;border:1px solid #ddd;position:absolute;top:45px;left:-1px;width:400px;z-index:10;text-indent:0;font-family:"myriad-pro",helvetica,arial,sans-serif}.flyout.small{width:300px}.flyout.large{width:600px}.flyout.right{left:auto;right:0}.flyout p:last-child{margin-bottom:0}.flyout a{font-size:0.8em}.nav-bar > li .flyout{display:none}.nav-bar > li:hover .flyout{display:block}.flex-video{position:relative;padding-top:25px;padding-bottom:67.5%;height:0;margin-bottom:16px;overflow:hidden}.flex-video.widescreen{padding-bottom:57.25%}.flex-video.vimeo{padding-top:0}.flex-video iframe,.flex-video object,.flex-video embed{position:absolute;top:0;left:0;width:100%;height:100%}ul.vcard{display:inline-block;margin:0 0 12px 0;border:1px solid #ddd;padding:10px}ul.vcard li{margin:0;display:block}ul.vcard li.fn{font-weight:bold;font-size:15px;font-size:1.5rem}p.vevent span.summary{font-weight:bold}p.vevent abbr{cursor:default;text-decoration:none;font-weight:bold;border:none;padding:0 1px}form{margin:0 0 18px}form label{display:block;font-size:13px;line-height:18px;cursor:pointer;margin-bottom:9px}input.input-text,textarea{border-right:1px solid #bbb;border-bottom:1px solid #bbb}input.input-text,textarea,select{display:block;margin-bottom:9px}label + input.input-text,label + textarea,label + select,label + div.dropdown{margin-top:-9px}select + div.dropdown{margin-top:-9px}input.input-text,textarea{font-size:13px;padding:4px 3px 2px;outline:none !important;background:#fff}input.input-text.oversize,textarea.oversize{font-size:18px !important;padding:4px 5px !important}input.input-text:focus,textarea:focus{background:#f9f9f9}input.placeholder,textarea.placeholder{color:#888}input.input-text,textarea{width:254px}input.small,textarea.small{width:134px}input.medium,textarea.medium{width:254px}input.large,textarea.large{width:434px}input.full,textarea.full{width:99%}form fieldset{padding:9px 9px 2px 9px;border:solid 1px #ddd;margin:18px 0}div.form-field input[type=radio],div.form-field input[type=checkbox]{display:inline;width:auto;margin-bottom:0}div.form-field.error input{border-color:red;background-color:rgba(255,0,0,0.15)}input.input-text.red{border-color:red;background-color:rgba(255,0,0,0.15)}div.form-field.error label,label.red{color:red}div.form-field.error small,small.error{margin-top:-6px;display:block;margin-bottom:9px;font-size:11px;color:red;width:260px}.small + small.error{width:140px}.medium + small.error{width:260px}.large + small.error{width:440px}form.nice div.form-field input,form.nice input.input-text,form.nice textarea{border:solid 1px #bbb;border-radius:2px;-webkit-border-radius:2px;-moz-border-radius:2px}form.nice div.form-field input,form.nice input.input-text,form.nice textarea{font-size:13px;padding:6px 3px 4px;outline:none !important}form.nice div.form-field input:focus,form.nice input.input-text:focus,form.nice textarea:focus{background-color:#f9f9f9}form.nice fieldset{border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px}form.nice div.form-field input[type=radio],form.nice div.form-field input[type=checkbox]{display:inline;width:auto;margin-bottom:0}form.nice div.form-field.error small{padding:6px 4px;border:solid 0px red;border-width:0px 1px 1px 1px;margin-top:-10px;background:red;color:#fff;font-size:12px;font-weight:bold;border-bottom-left-radius:2px;border-bottom-right-radius:2px;-webkit-border-bottom-left-radius:2px;-webkit-border-bottom-right-radius:2px;-moz-border-radius-bottomleft:2px;-moz-border-radius-bottomright:2px}form.nice small.error{padding:6px 4px;border:solid 0px red;border-width:0px 1px 1px 1px;margin-top:-10px;background:red;color:#fff;font-size:12px;font-weight:bold;border-bottom-left-radius:2px;border-bottom-right-radius:2px;-webkit-border-bottom-left-radius:2px;-webkit-border-bottom-right-radius:2px;-moz-border-radius-bottomleft:2px;-moz-border-radius-bottomright:2px}form.nice div.form-field.error .small + small,form.nice .small + small.error{width:132px}form.nice div.form-field.error .medium + small,form.nice .medium + small.error{width:252px}form.nice div.form-field.error .large + small,form.nice .large + small.error{width:432px}form.custom span.custom{display:inline-block;width:14px;height:14px;position:relative;top:2px;border:solid 1px #ccc;background:url(../images/custom-form-sprites.png) 0 0 no-repeat}form.custom span.custom.radio{border-radius:7px;-webkit-border-radius:7px;-moz-border-radius:7px}form.custom span.custom.radio.checked{background-position:0px -14px}form.custom span.custom.checkbox.checked{background-position:0px -28px}form.custom div.custom.dropdown{position:relative;display:inline-block;width:auto;height:28px;margin-bottom:9px}form.custom div.custom.dropdown a.current{display:block;width:auto;line-height:26px;padding:0 38px 0 6px;border:solid 1px #ddd;color:#141414}form.custom div.custom.dropdown a.selector{position:absolute;width:26px;height:26px;display:block;background:url(../images/custom-form-sprites.png) -14px 0 no-repeat;right:0px;top:0px;border:solid 1px #ddd}form.custom div.custom.dropdown:hover a.selector,form.custom div.custom.dropdown.open a.selector{background-position:-14px -26px}form.custom div.custom.dropdown ul{position:absolute;width:auto;display:none;margin:0;left:0px;top:27px;margin:0;padding:0;background:rgba(255,255,255,0.9);border:solid 1px #ddd;z-index:10}form.custom div.custom.dropdown ul li{cursor:pointer;padding:3px 38px 3px 6px;margin:0}form.custom div.custom.dropdown ul li.selected{background:url(../images/custom-form-sprites.png) right -52px no-repeat}form.custom div.custom.dropdown ul li:hover{background-color:#2a85e8;color:#fff}form.custom div.custom.dropdown ul li.selected:hover{background:url(../images/custom-form-sprites.png) #2a85e8 right -78px no-repeat}form.custom div.custom.dropdown ul.show{display:block}form.custom div.custom.dropdown.open ul{display:block}.container{padding:0px 20px}.row{width:100%;max-width:980px;min-width:727px;margin:0 auto}.row .row{min-width:0px}.column,.columns{margin-left:4.4%;float:left;min-height:1px;position:relative}.column:first-child,.columns:first-child{margin-left:0px}.row .one.columns{width:4.3%}.row .two.columns{width:13%}.row .three.columns{width:21.68%}.row .four.columns{width:30.4%}.row .five.columns{width:39.1%}.row .six.columns{width:47.8%}.row .seven.columns{width:56.5%}.row .eight.columns{width:65.2%}.row .nine.columns{width:73.9%}.row .ten.columns{width:82.6%}.row .eleven.columns{width:91.3%}.row .twelve.columns{width:100%}.row .offset-by-one{margin-left:13.1%}.row .offset-by-two{margin-left:21.8%}.row .offset-by-three{margin-left:30.5%}.row .offset-by-four{margin-left:39.2%}.row .offset-by-five{margin-left:47.9%}.row .offset-by-six{margin-left:56.6%}.row .offset-by-seven{margin-left:65.3%}.row .offset-by-eight{margin-left:74%}.row .offset-by-nine{margin-left:82.7%}.row .offset-by-ten{margin-left:91.4%}.row .one.centered{margin-left:47.9%}.row .two.centered{margin-left:43.5%}.row .three.centered{margin-left:39.2%}.row .four.centered{margin-left:34.8%}.row .five.centered{margin-left:30.5%}.row .six.centered{margin-left:26.1%}.row .seven.centered{margin-left:21.8%}.row .eight.centered{margin-left:17.4%}.row .nine.centered{margin-left:13.1%}.row .ten.centered{margin-left:8.7%}.row .eleven.centered{margin-left:4.3%}.row .offset-by-one:first-child{margin-left:8.7%}.row .offset-by-two:first-child{margin-left:17.4%}.row .offset-by-three:first-child{margin-left:26.1%}.row .offset-by-four:first-child{margin-left:34.8%}.row .offset-by-five:first-child{margin-left:43.5%}.row .offset-by-six:first-child{margin-left:52.2%}.row .offset-by-seven:first-child{margin-left:60.9%}.row .offset-by-eight:first-child{margin-left:69.6%}.row .offset-by-nine:first-child{margin-left:78.3%}.row .offset-by-ten:first-child{margin-left:87%}.row .offset-by-eleven:first-child{margin-left:95.7%}img,object,embed{max-width:100%;height:auto}img{-ms-interpolation-mode:bicubic}.row:before,.row:after{content:"";display:table}.clearfix:before,.clearfix:after{content:"";display:table}.row:after,.clearfix:after{clear:both}.row,.clearfix{zoom:1}.block-grid{display:block;overflow:hidden}.block-grid > li{display:block;height:auto;float:left}.block-grid.two-up{margin-left:-4%}.block-grid.two-up > li{margin-left:4%;width:46%}.block-grid.three-up{margin-left:-2%}.block-grid.three-up > li{margin-left:2%;width:31.3%}.block-grid.four-up{margin-left:-2%}.block-grid.four-up > li{margin-left:2%;width:23%}.block-grid.five-up{margin-left:-1.5%}.block-grid.five-up > li{margin-left:1.5%;width:18.5%}@media only screen and (max-width: 767px){body{-webkit-text-size-adjust:none}.row,body,.container{width:100%;min-width:0;margin-left:0px;margin-right:0px;padding-left:0px;padding-right:0px}.row .row .column,.row .row .columns{padding:0}.column,.columns{width:auto !important;float:none;margin-left:0px;margin-right:0px;padding-left:20px;padding-right:20px}.column:last-child,.columns:last-child{margin-right:0px}.offset-by-one,.offset-by-two,.offset-by-three,.offset-by-four,.offset-by-five,.offset-by-six,.offset-by-seven,.offset-by-eight,.offset-by-nine,.offset-by-ten,.offset-by-eleven,.centered{margin-left:0% !important}}@media only screen and (max-width: 767px){.block-grid.mobile{margin-left:0%}.block-grid.mobile li{float:none;width:100%;margin-left:0%}}.show-on-phones,.show-on-tablets{display:none !important}.show-on-desktops{display:block}.hide-on-phones,.hide-on-tablets{display:block !important}.hide-on-desktops{display:none}@media only screen and (max-device-width: 800px), only screen and (device-width: 800px){.hide-on-phones{display:block !important}.hide-on-tablets{display:none !important}.hide-on-desktops{display:block !important}.show-on-phones{display:none !important}.show-on-tablets{display:block !important}.show-on-desktops{display:none !important}}@media only screen and (max-width: 767px){.hide-on-phones{display:none !important}.hide-on-tablets,.hide-on-desktops,.show-on-phones{display:block !important}.show-on-tablets,.show-on-desktops{display:none !important}}@media only screen and (max-width: 767px){div.form-field input{display:block;width:96%;padding:6px 2% 4px;font-size:18px}div.form-field input.small,div.form-field input.medium,div.form-field input.large,div.form-field input.oversize{display:block;width:96%;padding:6px 2% 4px;font-size:18px}input.input-text{display:block;width:96%;padding:6px 2% 4px;font-size:18px}input.input-text.oversize{display:block;width:96%;padding:6px 2% 4px;font-size:18px}textarea{display:block;width:96%;padding:6px 2% 4px;font-size:18px}form.nice div.form-field input,form.nice input.input-text{display:block;width:96%;padding:6px 2% 4px;font-size:18px}form.nice div.form-field input.oversize,form.nice input.input-text.oversize{display:block;width:96%;padding:6px 2% 4px;font-size:18px}form.nice textarea{display:block;width:96%;padding:6px 2% 4px;font-size:18px}form.nice div.form-field input,form.nice input.input-text{-webkit-border-radius:2px;-moz-border-radius:2px}form.nice div.form-field input.oversize,form.nice input.input-text.oversize{-webkit-border-radius:2px;-moz-border-radius:2px}form.nice textarea{-webkit-border-radius:2px;-moz-border-radius:2px}form.nice div.form-field.error small,form.nice small.error{padding:6px 2%;display:block}form.nice div.form-field.error .small + small,form.nice .small + .error,form.nice div.form-field.error .medium + small,form.nice .medium + .error,form.nice div.form-field.error .large + small,form.nice .large + .error{width:auto}}@media only screen and (max-width: 767px){.button{display:block}button.button{width:100%;padding-left:0px;padding-right:0px}}@media only screen and (max-width: 767px){dl.tabs.mobile,dl.nice.tabs.mobile{width:auto;margin:20px -20px 40px;height:auto}dl.tabs.mobile dt,dl.tabs.mobile dd{float:none;height:auto}dl.nice.tabs.mobile dt,dl.nice.tabs.mobile dd{float:none;height:auto}dl.tabs.mobile dd a{display:block;width:auto;height:auto;padding:18px 20px;line-height:1;border:solid 0px #ccc;border-width:1px 0px 0px;margin:0;color:#555;background:#eee;font-size:15px;font-size:1.5rem}dl.tabs.mobile dd a.active{height:auto;margin:0;border-width:1px 0px 0px}.nice.tabs.mobile{border-bottom:solid 1px #ccc;height:auto}.nice.tabs.mobile dd a{padding:18px 20px;border:none;border-left:none;border-right:none;border-top:1px solid #ccc;background:#fff}.nice.tabs.mobile dd a.active{border:none;background:#00a6fc;color:#fff;margin:0;position:static;top:0px;height:auto}.nice.tabs.mobile dd:first-child a.active{margin:0}dl.contained.mobile,dl.nice.contained.mobile{margin-bottom:0px}dl.contained.tabs.mobile dd a,dl.nice.contained.tabs.mobile dd a{padding:18px 20px}}#caseStudies{width:1000px;height:210px;background:#fff url("../images/orbit/loading.gif") no-repeat center center;overflow:hidden}#caseStudies > img,#caseStudies > div,#caseStudies > a{display:none}div.orbit-wrapper{width:1px;height:1px;position:relative}div.orbit{width:1px;height:1px;position:relative;overflow:hidden}div.orbit.with-bullets{margin-bottom:40px}div.orbit > img{position:absolute;top:0;left:0}div.orbit > a{border:none;position:absolute;top:0;left:0;line-height:0;display:none}.orbit > div{position:absolute;top:0;left:0;width:100%;height:100%}div.timer{width:40px;height:40px;overflow:hidden;position:absolute;top:10px;right:10px;opacity:0.6;cursor:pointer;z-index:1001}span.rotator{display:block;width:40px;height:40px;position:absolute;top:0;left:-20px;background:url(../images/orbit/rotator-black.png) no-repeat;z-index:3}span.mask{display:block;width:20px;height:40px;position:absolute;top:0;right:0;z-index:2;overflow:hidden}span.rotator.move{left:0}span.mask.move{width:40px;left:0;background:url(../images/orbit/timer-black.png) repeat 0 0}span.pause{display:block;width:40px;height:40px;position:absolute;top:0;left:0;background:url(../images/orbit/pause-black.png) no-repeat;z-index:4;opacity:0}span.pause.active{background:url(../images/orbit/pause-black.png) no-repeat 0 -40px}div.timer:hover span.pause,span.pause.active{opacity:1}.orbit-caption{display:none;font-family:"HelveticaNeue","Helvetica-Neue",Helvetica,Arial,sans-serif}.orbit-wrapper .orbit-caption{background:#000;background:rgba(0,0,0,0.6);z-index:1000;color:#fff;text-align:center;padding:7px 0;font-size:13px;position:absolute;right:0;bottom:0;width:100%}div.slider-nav{display:block}div.slider-nav span{width:78px;height:100px;text-indent:-9999px;position:absolute;z-index:1000;top:50%;margin-top:-50px;cursor:pointer}div.slider-nav span.right{background:url(../images/orbit/right-arrow.png);right:0}div.slider-nav span.left{background:url(../images/orbit/left-arrow.png);left:0}.orbit-bullets{position:absolute;z-index:1000;list-style:none;bottom:-40px;left:50%;margin-left:-50px;padding:0}.orbit-bullets li{float:left;margin-left:5px;cursor:pointer;color:#999;text-indent:-9999px;background:url(../images/orbit/bullets.jpg) no-repeat 4px 0;width:13px;height:12px;overflow:hidden}.orbit-bullets li.active{color:#222;background-position:-8px 0}.orbit-bullets li.has-thumb{background:none;width:100px;height:75px}.orbit-bullets li.active.has-thumb{background-position:0 0;border-top:2px solid #000}.orbit{width:100% !important}.orbit .fluid-placeholder{visibility:hidden;position:static;display:block;width:100%}.orbit-wrapper{width:100% !important}.orbit-bullets{position:absolute;z-index:1000;list-style:none;bottom:-50px;left:50%;margin-left:-50px;padding:0}.orbit-bullets li{float:left;margin-left:5px;cursor:pointer;color:#999;text-indent:-9999px;background:url(../images/orbit/bullets.jpg) no-repeat 4px 0;width:13px;height:12px;overflow:hidden}.orbit-bullets li.has-thumb{background:none;width:100px;height:75px}.orbit-bullets li.active{color:#222;background-position:-8px 0}.orbit-bullets li.active.has-thumb{background-position:0 0;border-top:2px solid #000}.reveal-modal-bg{position:fixed;height:100%;width:100%;background:#000;z-index:2000;display:none;top:0;left:0}.reveal-modal{visibility:hidden;top:100px;left:50%;margin-left:-300px;width:520px;background:#eee url(../images/modal-gloss.png) no-repeat -200px -80px;position:absolute;z-index:2001;padding:30px 40px 34px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:0 0 10px rgba(0,0,0,0.4);-webkit-box-shadow:0 0 10px rgba(0,0,0,0.4);box-shadow:0 0 10px rgba(0,0,0,0.4)}.reveal-modal.small{width:200px;margin-left:-140px}.reveal-modal.medium{width:400px;margin-left:-240px}.reveal-modal.large{width:600px;margin-left:-340px}.reveal-modal.xlarge{width:800px;margin-left:-440px}.reveal-modal .close-reveal-modal{font-size:22px;line-height:0.5;position:absolute;top:8px;right:11px;color:#aaa;text-shadow:0 -1px 1px rbga(0, 0, 0, 0.6);font-weight:bold;cursor:pointer}@media handheld, only screen and (device-width: 768px), (device-width: 800px){.reveal-modal-bg{position:absolute}.reveal-modal{width:60%;top:30%;left:15%;margin-left:0px;padding:5%;height:auto}.reveal-modal.small,.reveal-modal.medium,.reveal-modal.large,.reveal-modal.xlarge{width:60%;top:30%;left:15%;margin-left:0px;padding:5%;height:auto}}@media handheld, only screen and (max-width: 767px){.reveal-modal-bg{position:absolute}.reveal-modal{width:80%;top:15%;left:5%;margin-left:0px;padding:5%;height:auto}.reveal-modal.small,.reveal-modal.medium,.reveal-modal.large,.reveal-modal.xlarge{width:80%;top:15%;left:5%;margin-left:0px;padding:5%;height:auto}}h1{color:#333;font-family:helvetica,arial,sans-serif;font-size:42px;font-weight:100;line-height:50px;text-transform:capitalize;margin:0 0 20px 0;text-shadow:0 1px 1px #fff;word-wrap:break-word}h1.homepage{font-size:64px;color:#666}h2{color:#333;font-family:helvetica,arial,sans-serif;font-size:24px;line-height:36px;font-weight:bold;word-wrap:break-word}h3{color:#666;font-family:helvetica,arial,sans-serif;font-weight:bold;font-size:18px;line-height:22px;margin:10px 0 10px 0;word-wrap:break-word}h4{color:#333;font-family:"myriad-pro",helvetica,arial,sans-serif}small{font-size:12px;font-weight:500;font-family:"myriad-pro",helvetica,sans-serif}body{color:#666;margin:0;font-family:helvetica,arial,serif;font-size:16px;font-weight:100;line-height:28px;word-wrap:break-word}p{line-height:28px}p.smaller{font-size:13px;line-height:21px;margin:10px 0}img{vertical-align:middle}img.background{z-index:-1;min-height:100%;min-width:1024px;width:100%;height:auto;position:fixed;top:0;left:0}pre{background:#eee;line-height:21px;padding:10px;font-size:14px;font-family:monaco,menlo,courier}a{word-wrap:break-word;cursour:pointer}form label{font-size:18px;font-weight:bold;margin-top:25px}form textarea{width:95%;height:80px;font-size:16px;line-height:28px;padding:8px;margin:5px 0 5px 0;border:1px solid #dfdfdf}input{font-size:18px;padding:8px;margin:5px 0 5px 0;font-family:inherit}select{width:99%}.errorlist li{margin-left:0;list-style-type:none;display:block;padding:6px 20px;font-weight:bold;font-size:13px;background:#eee;border:1px solid rgba(0,0,0,0.1);margin-bottom:12px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;text-shadow:0 1px rgba(255,255,255,0.9);position:relative;background-color:#c00000;color:#fff;text-shadow:0 -1px rgba(0,0,0,0.3)}.helptext{font-size:12px;padding-left:0;margin-left:0}tbody{font-family:"myriad-pro",helvetica,arial,sans-serif}tbody p{margin:0}tbody a{font-size:14px;font-height:18px;font-weight:bold;margin:0}.navigation .header h1{color:#333;position:relative;display:inline;top:0;left:0;z-index:1;height:1px;margin:0px 0;margin-right:40px;font-family:helvetica,sans-serif;font-weight:700;font-size:24px;font-height:24px;text-shadow:none}.navigation .header h1 a{color:inherit}.navigation .header h1 a span{display:none;margin:0}.navigation .header h1 a img{margin:10px 0}.navigation .header .navitem{position:relative;color:#333;display:inline-block;font-weight:normal;padding:0px 8px;font-size:11px;font-family:"myriad-pro",helvetica,calibri;text-transform:uppercase;margin-top:7px;margin-left:10px;cursor:pointer}.navigation .header .navitem:hover{-moz-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;-ms-border-radius:5px;-khtml-border-radius:5px;border-radius:5px;background:#eee;cursor:pointer;border-bottom:1px solid #ddd}.log{font-size:14px;text-decoration:underline;float:right;font-family:"myriad pro",helvetica,arial;margin-top:5px}.log.homepage{margin-top:0}#footer{margin:10px 0}.roundedbox{margin:0;z-index:1;border:1px solid #d0cdc3;background:#fff;padding:40px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.roundedbox b{font-family:helvetica,"myriad-pro",sans-serif}.roundedbox.small{padding:30px}.ribbon{position:absolute;min-width:24%;z-index:2;white-space:nowrap}.ribbon h1{font-family:"myriad-pro-condensed",helvetica,sans-serif;width:auto;font-size:28px;font-weight:bold;text-align:left;line-height:54px;position:relative;color:#eee;background:#666;clear:both;padding:0px 20px 0 20px;margin:20px 0px 10px -11px;text-shadow:0 -1px #1a1a1a,0 1px gray;-moz-box-shadow:2px 2px 0 #f2f2f2;-webkit-box-shadow:2px 2px 0 #f2f2f2;-o-box-shadow:2px 2px 0 #f2f2f2;box-shadow:2px 2px 0 #f2f2f2}.ribbon h1:before{z-index:1;content:" ";position:absolute;width:0;height:0;left:0px;top:100%;border-width:5px 5px;border-style:solid;border-color:#1a1a1a #1a1a1a transparent transparent}.ribbon h1:after{z-index:1;content:" ";position:absolute;width:1px;height:0;left:auto;top:0;right:-20px;border-width:27px 15px;border-style:solid;border-color:#666 transparent #666 #666;-moz-box-shadow:0px 2px 0 #f2f2f2;-webkit-box-shadow:0px 2px 0 #f2f2f2;-o-box-shadow:0px 2px 0 #f2f2f2;box-shadow:0px 2px 0 #f2f2f2}.ribbon.home{min-width:0;right:-10px}.ribbon.home h1{background:#800517;font-size:18px;line-height:32px;margin-left:0px;margin-top:25px;text-shadow:0 -1px #1a1a1a,0 1px maroon}.ribbon.home h1:after{z-index:1;content:" ";position:absolute;width:0;height:0;right:0px;top:100%;border-width:5px 5px;border-style:solid;border-color:#1a1a1a transparent transparent #1a1a1a}.ribbon.home h1:before{z-index:1;content:"";position:absolute;width:1px;height:0;left:auto;top:0;right:-20px;border-width:0;border-style:solid;border-color:#666 transparent #666 #666;-moz-box-shadow:0px 2px 0 #f2f2f2;-webkit-box-shadow:0px 2px 0 #f2f2f2;-o-box-shadow:0px 2px 0 #f2f2f2;box-shadow:0px 2px 0 #f2f2f2}.clear{clear:both}.blue.button{background-color:#e77627}.blue.button:hover{background-color:#bc5a15}.nice.blue.button{border:1px solid #e77627}.nice.tabs.vertical dd a{text-transform:uppercase;font-size:14px;font-weight:normal}.nice.tabs.vertical dd a.active{background-color:#e77627}.nice.tabs.vertical dd a.active:hover{background-color:#e77627}.nice.tabs.vertical dd a:hover{background-color:#fcf0e8}.alert-box,span{padding-left:12px}.snippet{border:1px solid #eee;margin-bottom:10px;padding:20px;-moz-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;-ms-border-radius:5px;-khtml-border-radius:5px;border-radius:5px}.tag{-moz-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;-ms-border-radius:5px;-khtml-border-radius:5px;border-radius:5px;padding:5px;border:1px solid #ddd;background:#eee;font-size:12px;text-shadow:1px 1px solid #fff;margin-right:8px}.tag:hover{background:#d4d4d4}.tag a{color:#333}.profilepicture{background:#eee;display:inline-block;width:42px;height:42px;-moz-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;-ms-border-radius:5px;-khtml-border-radius:5px;border-radius:5px}.profilepicture.tiny{width:24px;height:24px;padding:2px;margin-right:5px}.profilepicture.small{width:32px;height:32px}.profilepicture.medium{width:54px;height:54px}.profilepicture.large{width:220px;height:220px}.update{background:#eee;padding:12px;padding-left:30px;border-left:2px solid gray;border-bottom:1px solid #fff;width:94%}.update:before{content:" ";left:-8px;margin-top:20px;position:absolute;border:8px solid #52c3b4;-moz-border-radius:100px;-webkit-border-radius:100px;-o-border-radius:100px;-ms-border-radius:100px;-khtml-border-radius:100px;border-radius:100px}.update small{font-weight:normal}
47 project/src/clj/core/main.clj
@@ -0,0 +1,47 @@
+(ns core.main
+ (:use lamina.core
+ aleph.http
+ compojure.core
+ ; This sets the correct file type for js includes used by hiccup
+ (ring.middleware resource file-info)
+ core.views
+ core.templates)
+ (:require [compojure.route :as route])
+ (:gen-class))
+
+(defn sync-app [request]
+ "Rendered response of the chat page"
+ {:status 200
+ :headers {"content-type" "text/html"}
+ :body (page)})
+
+(def wrapped-sync-app
+ "Wraps the response with static files"
+ (-> sync-app
+ (wrap-resource "public")
+ (wrap-file-info)))
+
+(defn chat [ch request]
+ "View handler that handles a chat room. If it's not
+ a websocket request then return a rendered html response."
+ (let [params (:route-params request)
+ room (:room params)]
+ (if (:websocket request)
+ (chat-handler ch room)
+ (enqueue ch (wrapped-sync-app request)))))
+
+(defroutes app-routes
+ "Routes requests to their handler function. Captures dynamic variables."
+ (GET ["/chat/:room", :room #"[a-zA-Z]+"] {}
+ (wrap-aleph-handler chat))
+ (GET ["/"] {} "Hello world!")
+ ;;Route our public resources like css and js to the static url
+ (route/resources "/static")
+ ;;Any url without a route handler will be served this response
+ (route/not-found "Page not found"))
+
+(defn -main [& args]
+ "Main thread for the server which starts an async server with
+ all the routes we specified and is websocket ready."
+ (start-http-server (wrap-ring-handler app-routes)
+ {:host "localhost" :port 8080 :websocket true}))
20 project/src/clj/core/templates.clj
@@ -0,0 +1,20 @@
+(ns core.templates
+ (:use (hiccup core page)))
+
+(defn page []
+ "HTML page rendered using Hiccup. Includes the css and js for websockets."
+ (html5
+ [:head
+ (include-css "/static/stylesheets/master.css")]
+ [:body
+ [:div.container
+ [:div.row
+ [:div.columns.twelve
+ [:p [:h1#headline "Chat"]]
+ [:form
+ [:input#message {:type "text"}]
+ [:input.nice.large.blue.button {:type "submit"}]]
+ [:div#messages]]]]
+ (include-js "http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js")
+ (include-js "/static/javascripts/web_socket.js")
+ (include-js "/static/javascripts/app.js")]))
13 project/src/clj/core/views.clj
@@ -0,0 +1,13 @@
+(ns core.views
+ (:use lamina.core))
+
+(defn chat-init [ch]
+ "Initialize a new chat channel"
+ (receive-all ch #(println "message: " %)))
+
+(defn chat-handler [ch room]
+ "Relays messages into a chat room. If it doesn't
+ exist create a new channel"
+ (let [chat (named-channel room chat-init)]
+ (siphon chat ch)
+ (siphon ch chat)))
6 project/test/board/core/core.clj
@@ -0,0 +1,6 @@
+(ns board.test.core
+ (:use [project.core])
+ (:use [clojure.test]))
+
+(deftest replace-me ;; FIXME: write
+ (is false "No tests have been written."))
0  project/test/board/core/views.clj
No changes.
7 readme.md
@@ -0,0 +1,7 @@
+# Simple Real Time Chat using Clojure and Aleph
+Demonstration of a non-blocking server usng Aleph. Also shows how to use url routing via Compojure.
+
+```lein deps
+```lein run
+
+Go to http://localhost:8080/chat/room1, open another window with the same url and start talking to yourself!
Please sign in to comment.
Something went wrong with that request. Please try again.