From 4b5b869c1477dabfbe2a6fd2a7e1160197d6984d Mon Sep 17 00:00:00 2001 From: Rob Dodson Date: Fri, 8 Jun 2012 07:06:54 -0700 Subject: [PATCH] Finalize functionality. Add start/stop/reset buttons. Tidy up the interactions between the view and the stopwatch model. Add CSS styles. --- app.js | 43 +++++++++++++----- models/stopwatch.js | 105 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- public/css/main.css | 60 ++++++++++++++++++++++--- public/js/main.js | 14 ++++-- routes/index.js | 2 + views/index.ejs | 8 +++- views/layout.ejs | 5 ++- 8 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 models/stopwatch.js diff --git a/app.js b/app.js index 14a9af1..065ce1d 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,7 @@ var express = require('express'), - app = express.createServer(express.logger()), - io = require('socket.io').listen(app), + app = module.exports = express.createServer(express.logger()), + io = require('socket.io').listen(app); + Stopwatch = require('./models/stopwatch'), routes = require('./routes'); // Configuration @@ -25,26 +26,44 @@ app.configure('production', function() { // Heroku won't actually allow us to use WebSockets // so we have to setup polling instead. // https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku -// io.configure(function () { -// io.set("transports", ["xhr-polling"]); -// io.set("polling duration", 10); -// }); +io.configure(function () { + io.set("transports", ["xhr-polling"]); + io.set("polling duration", 10); +}); // Routes -var port = process.env.PORT || 5000; // Use the port that Heroku provides or default to 5000 +// Use the port that Heroku provides or default to 5000 +var port = process.env.PORT || 5000; app.listen(port, function() { console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); }); app.get('/', routes.index); -var status = "All is well."; +var stopwatch = new Stopwatch(); +stopwatch.on('tick:stopwatch', function(time) { + io.sockets.emit('time', { time: time }); +}); + +stopwatch.on('reset:stopwatch', function(time) { + io.sockets.emit('time', { time: time }); +}); + +stopwatch.start(); io.sockets.on('connection', function (socket) { - io.sockets.emit('status', { status: status }); // note the use of io.sockets to emit but socket.on to listen - socket.on('reset', function (data) { - status = "War is imminent!"; - io.sockets.emit('status', { status: status }); + io.sockets.emit('time', { time: stopwatch.getTime() }); + + socket.on('click:start', function () { + stopwatch.start(); + }); + + socket.on('click:stop', function () { + stopwatch.stop(); + }); + + socket.on('click:reset', function () { + stopwatch.reset(); }); }); \ No newline at end of file diff --git a/models/stopwatch.js b/models/stopwatch.js new file mode 100644 index 0000000..29ace51 --- /dev/null +++ b/models/stopwatch.js @@ -0,0 +1,105 @@ +var util = require('util'), + events = require('events') + _ = require('underscore'); + +// --------------------------------------------- +// Constructor +// --------------------------------------------- +function Stopwatch() { + if(false === (this instanceof Stopwatch)) { + return new Stopwatch(); + } + + this.hour = 3600000; + this.minute = 60000; + this.second = 1000; + this.time = this.hour; + this.interval = undefined; + + events.EventEmitter.call(this); + + // Use Underscore to bind all of our methods + // to the proper context + _.bindAll(this); +}; + +// --------------------------------------------- +// Inherit from EventEmitter +// --------------------------------------------- +util.inherits(Stopwatch, events.EventEmitter); + +// --------------------------------------------- +// Methods +// --------------------------------------------- +Stopwatch.prototype.start = function() { + if (this.interval) { + return; + } + + console.log('Starting Stopwatch!'); + // note the use of _.bindAll in the constructor + // with bindAll we can pass one of our methods to + // setInterval and have it called with the proper 'this' value + this.interval = setInterval(this.onTick, this.second); + this.emit('start:stopwatch'); +}; + +Stopwatch.prototype.stop = function() { + console.log('Stopping Stopwatch!'); + if (this.interval) { + clearInterval(this.interval); + this.interval = undefined; + this.emit('stop:stopwatch'); + } +}; + +Stopwatch.prototype.reset = function() { + console.log('Resetting Stopwatch!'); + this.time = this.hour; + this.emit('reset:stopwatch', this.formatTime(this.time)); +}; + +Stopwatch.prototype.onTick = function() { + this.time -= this.second; + + var formattedTime = this.formatTime(this.time); + this.emit('tick:stopwatch', formattedTime); + + if (this.time === 0) { + this.stop(); + } +}; + +Stopwatch.prototype.formatTime = function(time) { + var remainder = time, + numHours, + numMinutes, + numSeconds, + output = ""; + + numHours = String(parseInt(remainder / this.hour, 10)); + remainder -= this.hour * numHours; + + numMinutes = String(parseInt(remainder / this.minute, 10)); + remainder -= this.minute * numMinutes; + + numSeconds = String(parseInt(remainder / this.second, 10)); + + output = _.map([numHours, numMinutes, numSeconds], function(str) { + if (str.length === 1) { + str = "0" + str; + } + return str; + }).join(":"); + + return output; +}; + +Stopwatch.prototype.getTime = function() { + return this.formatTime(this.time); +}; + +// --------------------------------------------- +// Export +// --------------------------------------------- +module.exports = Stopwatch; \ No newline at end of file diff --git a/package.json b/package.json index e9c7349..7fec741 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "dependencies": { "express": "~2.5.8", "ejs": "~0.7.1", - "socket.io": "~0.9.6" + "socket.io": "~0.9.6", + "underscore": "~1.3.3" }, "engines": { "node": "0.6.x" diff --git a/public/css/main.css b/public/css/main.css index 30e047d..469fb5f 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1,8 +1,58 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +#wrapper { + width: 475px; + height: 171px; + margin: 100px auto; } -a { - color: #00B7FF; +#countdown { + font-family: 'Black Ops One', cursive; + font-size: 90px; +} + +button.thoughtbot { + background-color: #ee432e; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee432e), color-stop(50%, #c63929), color-stop(50%, #b51700), color-stop(100%, #891100)); + background-image: -webkit-linear-gradient(top, #ee432e 0%, #c63929 50%, #b51700 50%, #891100 100%); + background-image: -moz-linear-gradient(top, #ee432e 0%, #c63929 50%, #b51700 50%, #891100 100%); + background-image: -ms-linear-gradient(top, #ee432e 0%, #c63929 50%, #b51700 50%, #891100 100%); + background-image: -o-linear-gradient(top, #ee432e 0%, #c63929 50%, #b51700 50%, #891100 100%); + background-image: linear-gradient(top, #ee432e 0%, #c63929 50%, #b51700 50%, #891100 100%); + border: 1px solid #951100; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + -webkit-box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 1px 3px #333333; + -moz-box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 1px 3px #333333; + box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 1px 3px #333333; + color: #fff; + font: bold 20px "helvetica neue", helvetica, arial, sans-serif; + line-height: 1; + padding: 12px 0 14px 0; + text-align: center; + text-shadow: 0px -1px 1px rgba(0, 0, 0, 0.8); + width: 150px; +} + +button.thoughtbot:hover { + background-color: #f37873; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f37873), color-stop(50%, #db504d), color-stop(50%, #cb0500), color-stop(100%, #a20601)); + background-image: -webkit-linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); + background-image: -moz-linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); + background-image: -ms-linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); + background-image: -o-linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); + background-image: linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); + cursor: pointer; +} + +button.thoughtbot:active { + background-color: #d43c28; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d43c28), color-stop(50%, #ad3224), color-stop(50%, #9c1500), color-stop(100%, #700d00)); + background-image: -webkit-linear-gradient(top, #d43c28 0%, #ad3224 50%, #9c1500 50%, #700d00 100%); + background-image: -moz-linear-gradient(top, #d43c28 0%, #ad3224 50%, #9c1500 50%, #700d00 100%); + background-image: -ms-linear-gradient(top, #d43c28 0%, #ad3224 50%, #9c1500 50%, #700d00 100%); + background-image: -o-linear-gradient(top, #d43c28 0%, #ad3224 50%, #9c1500 50%, #700d00 100%); + background-image: linear-gradient(top, #d43c28 0%, #ad3224 50%, #9c1500 50%, #700d00 100%); + -webkit-box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4); + -moz-box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4); + box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4); } \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index ca7570e..986ab5a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,9 +1,17 @@ var socket = io.connect(window.location.hostname); -socket.on('status', function (data) { - $('#status').html(data.status); +socket.on('time', function (data) { + $('#countdown').html(data.time); +}); + +$('#start').click(function() { + socket.emit('click:start'); +}); + +$('#stop').click(function() { + socket.emit('click:stop'); }); $('#reset').click(function() { - socket.emit('reset'); + socket.emit('click:reset'); }); \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 92bf01a..8662c02 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,3 +1,5 @@ +var app = require('../app'); + /* * GET home page. */ diff --git a/views/index.ejs b/views/index.ejs index 4d57453..1fa1d27 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -1,2 +1,6 @@ -
- \ No newline at end of file +
+
+ + + +
\ No newline at end of file diff --git a/views/layout.ejs b/views/layout.ejs index 7af23f8..9839179 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -2,7 +2,7 @@ - Title + DEFCON @@ -14,6 +14,9 @@ + + + <%- body %>