Finalize functionality.

Add start/stop/reset buttons.

Tidy up the interactions between the view and the stopwatch model.

Add CSS styles.
This commit is contained in:
Rob Dodson 2012-06-08 07:06:54 -07:00
parent 31c251f6de
commit 4b5b869c14
8 changed files with 216 additions and 24 deletions

43
app.js
View File

@ -1,6 +1,7 @@
var express = require('express'), var express = require('express'),
app = express.createServer(express.logger()), app = module.exports = express.createServer(express.logger()),
io = require('socket.io').listen(app), io = require('socket.io').listen(app);
Stopwatch = require('./models/stopwatch'),
routes = require('./routes'); routes = require('./routes');
// Configuration // Configuration
@ -25,26 +26,44 @@ app.configure('production', function() {
// Heroku won't actually allow us to use WebSockets // Heroku won't actually allow us to use WebSockets
// so we have to setup polling instead. // so we have to setup polling instead.
// https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku // https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku
// io.configure(function () { io.configure(function () {
// io.set("transports", ["xhr-polling"]); io.set("transports", ["xhr-polling"]);
// io.set("polling duration", 10); io.set("polling duration", 10);
// }); });
// Routes // 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() { app.listen(port, function() {
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
}); });
app.get('/', routes.index); 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.on('connection', function (socket) {
io.sockets.emit('status', { status: status }); // note the use of io.sockets to emit but socket.on to listen io.sockets.emit('time', { time: stopwatch.getTime() });
socket.on('reset', function (data) {
status = "War is imminent!"; socket.on('click:start', function () {
io.sockets.emit('status', { status: status }); stopwatch.start();
});
socket.on('click:stop', function () {
stopwatch.stop();
});
socket.on('click:reset', function () {
stopwatch.reset();
}); });
}); });

105
models/stopwatch.js Normal file
View File

@ -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;

View File

@ -5,7 +5,8 @@
"dependencies": { "dependencies": {
"express": "~2.5.8", "express": "~2.5.8",
"ejs": "~0.7.1", "ejs": "~0.7.1",
"socket.io": "~0.9.6" "socket.io": "~0.9.6",
"underscore": "~1.3.3"
}, },
"engines": { "engines": {
"node": "0.6.x" "node": "0.6.x"

View File

@ -1,8 +1,58 @@
body { #wrapper {
padding: 50px; width: 475px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; height: 171px;
margin: 100px auto;
} }
a { #countdown {
color: #00B7FF; 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);
} }

View File

@ -1,9 +1,17 @@
var socket = io.connect(window.location.hostname); var socket = io.connect(window.location.hostname);
socket.on('status', function (data) { socket.on('time', function (data) {
$('#status').html(data.status); $('#countdown').html(data.time);
});
$('#start').click(function() {
socket.emit('click:start');
});
$('#stop').click(function() {
socket.emit('click:stop');
}); });
$('#reset').click(function() { $('#reset').click(function() {
socket.emit('reset'); socket.emit('click:reset');
}); });

View File

@ -1,3 +1,5 @@
var app = require('../app');
/* /*
* GET home page. * GET home page.
*/ */

View File

@ -1,2 +1,6 @@
<div id="status"></div> <div id="wrapper">
<button id="reset">Reset!</button> <div id="countdown"></div>
<button id="start" class="thoughtbot">Start</button>
<button id="stop" class="thoughtbot">Stop</button>
<button id="reset" class="thoughtbot">Reset</button>
</div>

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Title</title> <title>DEFCON</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
@ -14,6 +14,9 @@
<!-- styles --> <!-- styles -->
<link href="/css/main.css" rel="stylesheet"> <link href="/css/main.css" rel="stylesheet">
<!-- fonts -->
<link href='http://fonts.googleapis.com/css?family=Black+Ops+One' rel='stylesheet' type='text/css'>
</head> </head>
<body> <body>
<%- body %> <%- body %>