Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Port html5rocks.com SSE example to cramp

  • Loading branch information...
commit 349d4b3a1260cb0ed5c297c461dfcda7fd823ab7 1 parent 56dea0b
@lifo authored
View
35 examples/sse/HTML5ROCK_LICENSE
@@ -0,0 +1,35 @@
+This project includes the html5lib library:
+http://code.google.com/p/html5lib/
+
+Which is licensed under the following license:
+Copyright (c) 2006-2008 The Authors
+
+Contributors:
+James Graham - jg307@cam.ac.uk
+Anne van Kesteren - annevankesteren@gmail.com
+Lachlan Hunt - lachlan.hunt@lachy.id.au
+Matt McDonald - kanashii@kanashii.ca
+Sam Ruby - rubys@intertwingly.net
+Ian Hickson (Google) - ian@hixie.ch
+Thomas Broyer - t.broyer@ltgt.net
+Jacques Distler - distler@golem.ph.utexas.edu
+Henri Sivonen - hsivonen@iki.fi
+The Mozilla Foundation (contributions from Henri Sivonen since 2008)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
View
318 examples/sse/public/coolclock.js
@@ -0,0 +1,318 @@
+/**
+ * CoolClock 2.1.4
+ * Copyright 2010, Simon Baird
+ * Released under the BSD License.
+ *
+ * Display an analog clock using canvas.
+ * http://randomibis.com/coolclock/
+ *
+ */
+
+// Constructor for CoolClock objects
+window.CoolClock = function(options) {
+ return this.init(options);
+}
+
+// Config contains some defaults, and clock skins
+CoolClock.config = {
+ tickDelay: 1000,
+ longTickDelay: 15000,
+ defaultRadius: 85,
+ renderRadius: 100,
+ defaultSkin: "swissRail",
+ // Should be in skin probably...
+ // (TODO: allow skinning of digital display)
+ showSecs: true,
+ showAmPm: true,
+
+ skins: {
+ // There are more skins in moreskins.js
+ // Try making your own skin by copy/pasting one of these and tweaking it
+ swissRail: {
+ outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 },
+ smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 },
+ largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 },
+ hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 },
+ minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 },
+ secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+ secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 }
+ },
+ chunkySwiss: {
+ outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 },
+ smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 },
+ largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 },
+ hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 },
+ minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 },
+ secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+ secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
+ },
+ chunkySwissOnBlack: {
+ outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 },
+ smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 },
+ largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 },
+ hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 },
+ minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 },
+ secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
+ secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
+ }
+
+ },
+
+ // Test for IE so we can nurse excanvas in a couple of places
+ isIE: !!document.all,
+
+ // Will store (a reference to) each clock here, indexed by the id of the canvas element
+ clockTracker: {},
+
+ // For giving a unique id to coolclock canvases with no id
+ noIdCount: 0
+};
+
+// Define the CoolClock object's methods
+CoolClock.prototype = {
+
+ // Initialise using the parameters parsed from the colon delimited class
+ init: function(options) {
+ // Parse and store the options
+ this.canvasId = options.canvasId;
+ this.skinId = options.skinId || CoolClock.config.defaultSkin;
+ this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius;
+ this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true;
+ this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null;
+ this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false;
+ this.logClock = typeof options.logClock == "boolean" ? options.logClock : false;
+ this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false;
+
+ this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ];
+
+ // Get the canvas element
+ this.canvas = document.getElementById(this.canvasId);
+
+ // Make the canvas the requested size. It's always square.
+ this.canvas.setAttribute("width",this.displayRadius*2);
+ this.canvas.setAttribute("height",this.displayRadius*2);
+ this.canvas.style.width = this.displayRadius*2 + "px";
+ this.canvas.style.height = this.displayRadius*2 + "px";
+
+ // Explain me please...?
+ this.renderRadius = CoolClock.config.renderRadius;
+ this.scale = this.displayRadius / this.renderRadius;
+
+ // Initialise canvas context
+ this.ctx = this.canvas.getContext("2d");
+ this.ctx.scale(this.scale,this.scale);
+
+ // Keep track of this object
+ CoolClock.config.clockTracker[this.canvasId] = this;
+
+ // Start the clock going
+ //this.tick();
+
+ return this;
+ },
+
+ // Draw a circle at point x,y with params as defined in skin
+ fullCircleAt: function(x,y,skin) {
+ this.ctx.save();
+ this.ctx.globalAlpha = skin.alpha;
+ this.ctx.lineWidth = skin.lineWidth;
+
+ if (!CoolClock.config.isIE) {
+ this.ctx.beginPath();
+ }
+
+ if (CoolClock.config.isIE) {
+ // excanvas doesn't scale line width so we will do it here
+ this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
+ }
+
+ this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false);
+
+ if (CoolClock.config.isIE) {
+ // excanvas doesn't close the circle so let's fill in the tiny gap
+ this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false);
+ }
+
+ if (skin.fillColor) {
+ this.ctx.fillStyle = skin.fillColor
+ this.ctx.fill();
+ }
+ else {
+ // XXX why not stroke and fill
+ this.ctx.strokeStyle = skin.color;
+ this.ctx.stroke();
+ }
+ this.ctx.restore();
+ },
+
+ // Draw some text centered vertically and horizontally
+ drawTextAt: function(theText,x,y) {
+ this.ctx.save();
+ this.ctx.font = '15px sans-serif';
+ var tSize = this.ctx.measureText(theText);
+ if (!tSize.height) tSize.height = 15; // no height in firefox.. :(
+ this.ctx.fillText(theText,x - tSize.width/2,y - tSize.height/2);
+ this.ctx.restore();
+ },
+
+ lpad2: function(num) {
+ return (num < 10 ? '0' : '') + num;
+ },
+
+ tickAngle: function(second) {
+ // Log algorithm by David Bradshaw
+ var tweak = 3; // If it's lower the one second mark looks wrong (?)
+ if (this.logClock) {
+ return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak));
+ }
+ else if (this.logClockRev) {
+ // Flip the seconds then flip the angle (trickiness)
+ second = (60 - second) % 60;
+ return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)));
+ }
+ else {
+ return second/60.0;
+ }
+ },
+
+ timeText: function(hour,min,sec) {
+ var c = CoolClock.config;
+ return '' +
+ (c.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' +
+ this.lpad2(min) +
+ (c.showSecs ? ':' + this.lpad2(sec) : '') +
+ (c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '')
+ ;
+ },
+
+ // Draw a radial line by rotating then drawing a straight line
+ // Ha ha, I think I've accidentally used Taus, (see http://tauday.com/)
+ radialLineAtAngle: function(angleFraction,skin) {
+ this.ctx.save();
+ this.ctx.translate(this.renderRadius,this.renderRadius);
+ this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5));
+ this.ctx.globalAlpha = skin.alpha;
+ this.ctx.strokeStyle = skin.color;
+ this.ctx.lineWidth = skin.lineWidth;
+
+ if (CoolClock.config.isIE)
+ // excanvas doesn't scale line width so we will do it here
+ this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
+
+ if (skin.radius) {
+ this.fullCircleAt(skin.startAt,0,skin)
+ }
+ else {
+ this.ctx.beginPath();
+ this.ctx.moveTo(skin.startAt,0)
+ this.ctx.lineTo(skin.endAt,0);
+ this.ctx.stroke();
+ }
+ this.ctx.restore();
+ },
+
+ render: function(hour,min,sec) {
+ // Get the skin
+ var skin = CoolClock.config.skins[this.skinId];
+ if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin];
+
+ // Clear
+ this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2);
+
+ // Draw the outer edge of the clock
+ if (skin.outerBorder)
+ this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder);
+
+ // Draw the tick marks. Every 5th one is a big one
+ for (var i=0;i<60;i++) {
+ (i%5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator);
+ !(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator);
+ }
+
+ // Write the time
+ if (this.showDigital) {
+ this.drawTextAt(
+ this.timeText(hour,min,sec),
+ this.renderRadius,
+ this.renderRadius+this.renderRadius/2
+ );
+ }
+
+ // Draw the hands
+ if (skin.hourHand)
+ this.radialLineAtAngle(this.tickAngle(((hour%12)*5 + min/12.0)),skin.hourHand);
+
+ if (skin.minuteHand)
+ this.radialLineAtAngle(this.tickAngle((min + sec/60.0)),skin.minuteHand);
+
+ if (this.showSecondHand && skin.secondHand)
+ this.radialLineAtAngle(this.tickAngle(sec),skin.secondHand);
+
+ // Second hand decoration doesn't render right in IE so lets turn it off
+ if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration)
+ this.radialLineAtAngle(this.tickAngle(sec),skin.secondDecoration);
+ },
+
+ // Check the time and display the clock
+ refreshDisplay: function() {
+ var now = new Date();
+ if (this.gmtOffset != null) {
+ // Use GMT + gmtOffset
+ var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60));
+ this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds());
+ }
+ else {
+ // Use local time
+ this.render(now.getHours(),now.getMinutes(),now.getSeconds());
+ }
+ },
+
+ // Set timeout to trigger a tick in the future
+ nextTick: function() {
+ setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay);
+ },
+
+ // Check the canvas element hasn't been removed
+ stillHere: function() {
+ return document.getElementById(this.canvasId) != null;
+ },
+
+ // Main tick handler. Refresh the clock then setup the next tick
+ tick: function() {
+ if (this.stillHere()) {
+ this.refreshDisplay()
+ this.nextTick();
+ }
+ }
+};
+
+// Find all canvas elements that have the CoolClock class and turns them into clocks
+CoolClock.findAndCreateClocks = function() {
+ // (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery)
+ var canvases = document.getElementsByTagName("canvas");
+ for (var i=0;i<canvases.length;i++) {
+ // Pull out the fields from the class. Example "CoolClock:chunkySwissOnBlack:1000"
+ var fields = canvases[i].className.split(" ")[0].split(":");
+ if (fields[0] == "CoolClock") {
+ if (!canvases[i].id) {
+ // If there's no id on this canvas element then give it one
+ canvases[i].id = '_coolclock_auto_id_' + CoolClock.config.noIdCount++;
+ }
+ // Create a clock object for this element
+ return new CoolClock({
+ canvasId: canvases[i].id,
+ skinId: fields[1],
+ displayRadius: fields[2],
+ showSecondHand: fields[3]!='noSeconds',
+ gmtOffset: fields[4],
+ showDigital: fields[5]=='showDigital',
+ logClock: fields[6]=='logClock',
+ logClockRev: fields[6]=='logClockRev'
+ });
+ }
+ }
+};
+
+// If you don't have jQuery then you need a body onload like this: <body onload="CoolClock.findAndCreateClocks()">
+// If you do have jQuery and it's loaded already then we can do it right now
+if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks);
View
237 examples/sse/public/demo.html
@@ -0,0 +1,237 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="chrome=1" />
+<title>HTML5 Server-Sent Test</title>
+<style>
+@-webkit-keyframes glowGreen {
+ from {
+ -webkit-box-shadow: rgba(0, 255, 0, 0) 0 0 0;
+ }
+ 50% {
+ -webkit-box-shadow: rgba(0, 255, 0, 1) 0 0 10px;
+ }
+ to {
+ -webkit-box-shadow: rgba(0, 255, 0, 0) 0 0 0;
+ }
+}
+@-webkit-keyframes glowRed {
+ from {
+ -webkit-box-shadow: rgba(255, 0, 0, 0) 0 0 0;
+ }
+ 50% {
+ -webkit-box-shadow: rgba(255, 0, 0, 1) 0 0 10px;
+ }
+ to {
+ -webkit-box-shadow: rgba(255, 0, 0, 0) 0 0 0;
+ }
+}
+body {
+ font: 12px Arial, sans-serif;
+ background-color: #eee;
+ color: #444;
+}
+#connection {
+ font: 14px Arial, sans-serif;
+ font-weight: bold;
+ vertical-align: middle;
+ color: black;
+}
+#connection div {
+ background-color: orange;
+ width: 10px;
+ height: 10px;
+ display: inline-block;
+ border-radius: 10px;
+ margin-left: 5px;
+ -webkit-animation-duration: 2s;
+ -webkit-animation-iteration-count: infinite;
+ -webkit-animation-timing-function: linear;
+}
+#connection.connected div {
+ background-color: green;
+ -webkit-box-shadow: rgba(0, 255, 0, 0.5) 0px 0px 5px;
+ -webkit-animation-name: glowGreen;
+}
+#connection.disconnected div {
+ background-color: red;
+ -webkit-box-shadow: rgba(255, 0, 0, 0.5) 0px 0px 5px;
+ -webkit-animation-name: glowRed;
+}
+#log {
+ overflow: auto;
+ width: 300px;
+ height: 350px;
+ margin-right: 20px;
+ float: left;
+}
+#log p {
+ margin: 0;
+ padding: 0;
+}
+#log .info {
+ color: navy;
+ font-weight: bold;
+}
+#log .msg {
+ margin-left: 11px;
+}
+.border {
+ border: 2px solid black;
+ border-radius: 5px;
+ padding: 10px;
+ background-color: white;
+}
+#right-panel div {
+ text-align: left;
+}
+#right-panel {
+ height: 350px;
+ width: 585px;
+ float: left;
+ font-size: 14px;
+ text-align: center;
+}
+button {
+ background: -webkit-gradient(linear, 0% 40%, 0% 70%, from(#F9F9F9), to(#E3E3E3));
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ margin: 0 8px 0 0;
+ color: black;
+ padding: 5px 8px;
+ outline: none;
+ white-space: nowrap;
+ vertical-align: middle;
+ -webkit-user-select:none;
+ user-select: none;
+ cursor: pointer;
+}
+button:not([disabled]):hover {
+ border: 1px solid #939393;
+}
+button:not([disabled]):active {
+ background: -webkit-gradient(linear, 0% 40%, 0% 70%, from(#E3E3E3), to(#F9F9F9));
+}
+button[disabled] {
+ color: #ccc;
+}
+</style>
+</head>
+<body>
+
+<p>
+ <button onclick="logger.clear()">Clear log</button>
+ <button onclick="closeConnection()">Stop reconnections</button>
+ <span id="connection">Connecting...<div></div></span>
+</p>
+<div class="border" id="log"></div>
+<div class="border" id="right-panel"><div>The clock before is implemented using HTML5 <code>&lt;canvas&gt;</code> and
+</code><a href="http://www.html5rocks.com/tutorials/sse/basics/">Server-Sent Events</a>.
+The server's time is pushed to the client every 5 seconds using a single open connection.
+The server is set to break connections lasting longer than 10 seconds. However, the browser
+automatically reconnects SSEs ~3 seconds after dropped connections!</div>
+<canvas class="CoolClock::140:::showDigital" title="Server Sent Events are powering this clock"></canvas>
+</div>
+
+<script src="coolclock.js" type="text/javascript"></script>
+<script>
+if (!window.DOMTokenList) {
+ Element.prototype.containsClass = function(name) {
+ return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(this.className);
+ };
+
+ Element.prototype.addClass = function(name) {
+ if (!this.containsClass(name)) {
+ var c = this.className;
+ this.className = c ? [c, name].join(' ') : name;
+ }
+ };
+
+ Element.prototype.removeClass = function(name) {
+ if (this.containsClass(name)) {
+ var c = this.className;
+ this.className = c.replace(
+ new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), "");
+ }
+ };
+}
+
+// sse.php sends messages with text/event-stream mimetype.
+var source = new EventSource('sse');
+
+function Logger(id) {
+ this.el = document.getElementById(id);
+}
+
+Logger.prototype.log = function(msg, opt_class) {
+ var fragment = document.createDocumentFragment();
+ var p = document.createElement('p');
+ p.className = opt_class || 'info';
+ p.textContent = msg;
+ fragment.appendChild(p);
+ this.el.appendChild(fragment);
+};
+
+Logger.prototype.clear = function() {
+ this.el.textContent = '';
+};
+
+var logger = new Logger('log');
+
+function closeConnection() {
+ source.close();
+ logger.log('> Connection was closed');
+ updateConnectionStatus('Disconnected', false);
+}
+
+function updateConnectionStatus(msg, connected) {
+ var el = document.querySelector('#connection');
+ if (connected) {
+ if (el.classList) {
+ el.classList.add('connected');
+ el.classList.remove('disconnected');
+ } else {
+ el.addClass('connected');
+ el.removeClass('disconnected');
+ }
+ } else {
+ if (el.classList) {
+ el.classList.remove('connected');
+ el.classList.add('disconnected');
+ } else {
+ el.removeClass('connected');
+ el.addClass('disconnected');
+ }
+ }
+ el.innerHTML = msg + '<div></div>';
+}
+
+source.addEventListener('message', function(event) {
+ var data = JSON.parse(event.data);
+ var d = new Date(data.time * 1e3);
+ var timeStr = [d.getHours(), d.getMinutes(), d.getSeconds()].join(':');
+
+ coolclock.render(d.getHours(), d.getMinutes(), d.getSeconds());
+
+ logger.log('lastEventID: ' + event.lastEventId +
+ ', server time: ' + timeStr, 'msg');
+}, false);
+
+source.addEventListener('open', function(event) {
+ logger.log('> Connection was opened');
+ updateConnectionStatus('Connected', true);
+}, false);
+
+source.addEventListener('error', function(event) {
+ if (event.eventPhase == 2) { //EventSource.CLOSED
+ logger.log('> Connection was closed');
+ updateConnectionStatus('Disconnected', false);
+ }
+}, false);
+
+var coolclock = CoolClock.findAndCreateClocks();
+</script>
+
+</body>
+</html>
View
25 examples/sse/server.rb
@@ -0,0 +1,25 @@
+require "rubygems"
+require "bundler"
+Bundler.setup(:default, :example)
+
+require 'cramp'
+require 'thin'
+require 'http_router'
+require 'active_support/json'
+
+class TimeController < Cramp::SSE
+ on_start :send_latest_time
+ periodic_timer :send_latest_time, :every => 5
+
+ def send_latest_time
+ data = {'time' => Time.now.to_i}.to_json
+ render data
+ end
+end
+
+routes = HttpRouter.new do
+ add('/sse').to(TimeController)
+end
+
+file_server = Rack::File.new(File.join(File.dirname(__FILE__), 'public'))
+Rack::Handler::Thin.run Rack::Cascade.new([file_server, routes]), :Port => 3000
Please sign in to comment.
Something went wrong with that request. Please try again.