Refactoring

This commit is contained in:
Mattias Erming 2014-03-06 07:11:25 -08:00
parent 56ae48eb89
commit 4cc34d6456
9 changed files with 192 additions and 711 deletions

51
app.js
View file

@ -1,56 +1,11 @@
var argv = require("commander")
.option("-p, --port <n>", "port to use", parseInt)
.parse(process.argv);
.parse(process.argv);
PORT = 80; // Default port.
if (argv.port) {
PORT = argv.port;
}
// Run the server!
var server = new (require("./lib/server.js"))();
server.listen(PORT);
// Temporary data
var models = require("./client/js/models.js");
var network = new models.Network;
server.networks.push(network);
var channel_1 = new models.Channel;
var channel_2 = new models.Channel;
network.channels.push(channel_1);
network.channels.push(channel_2);
network.nick = "user";
network.address = "irc.freenode.org";
channel_1.name = "irc.freenode.org";
channel_1.type = "network";
channel_2.name = "#chan";
var user_1 = new models.User;
var user_2 = new models.User;
user_1.name = "john";
user_2.name = "jane";
channel_2.users.push(user_1);
channel_2.users.push(user_2);
var message_1 = new models.Message;
var message_2 = new models.Message;
message_1.time = "00:00";
message_1.user = "john";
message_1.text = "Hi!";
message_2.time = "00:00";
message_2.user = "jane";
message_2.text = "Hello!";
channel_2.messages.push(message_1);
channel_2.messages.push(message_2);
// Run the server.
(require("./lib/server.js")).listen(PORT);

View file

@ -40,8 +40,7 @@ h2 {
#sidebar .channel:hover {
color: #999;
}
#sidebar .network {
color: #000;
#sidebar .channel.network {
margin-top: 10px;
overflow: hidden;
}

View file

@ -12,7 +12,7 @@
<script type="text/html" id="networks">
{{#networks}}
<div class="network">
<div class="network" data-id="{{id}}">
{{#channels}}
<div class="channel {{type}}" data-id="{{id}}">
{{name}}
@ -33,7 +33,7 @@
{{> messages}}
</div>
<form onSubmit="return false;">
<input type="text" class="input"/>
<input type="text" class="input" data-target="{{id}}"/>
<input type="submit" style="display: none;"/>
</form>
</div>
@ -61,13 +61,7 @@
<script src="/socket.io/socket.io.js"></script>
<script src="/js/lib/mustache.min.js"></script>
<script src="/js/lib/jquery-2.1.0.min.js"></script>
<script src="/js/models.js"></script>
<script src="/js/client.js"></script>
<script>
// Run the client!
new Client().connect("");
</script>
<script src="/js/chat.js"></script>
</body>
</html>

93
client/js/chat.js Normal file
View file

@ -0,0 +1,93 @@
$(function() {
var socket = io.connect("");
socket.on(
"event",
function(event) {
View[event.action](event);
}
);
var chat = $("#chat");
var sidebar = $("#sidebar");
// Templates
var networks = $("#networks").html();
var channels = $("#channels").html();
var messages = $("#messages").html();
var users = $("#users").html()
var View = {};
View.redraw = function(event) {
if (event.data == undefined || event.data == []) {
return;
}
chat.html("");
event.data.forEach(function(network) {
chat.append(Mustache.render(channels, network, {
users: users,
messages: messages
}));
});
sidebar.html(
Mustache.render(networks, {
networks: event.data
})
);
};
View.add = function(event) {
var target = "";
var render = "";
switch (event.type) {
case "user":
target = ".users";
render = Mustache.render(
$("#users").html(), {users: event.data}
);
break;
case "message":
target = ".messages";
render = Mustache.render(
$("#messages").html(), {messages: event.data}
);
break;
}
if (target != "") {
target = $("[data-id='" + event.target + "'] " + target);
target.append(render);
}
};
View.remove = function(event) {
$("[data-id='" + event.target + "']").remove();
};
View.change = function(event) {
// ..
};
chat.on("submit", "form", function() {
var input = $(this).find(".input");
var text = input.val();
if (text != "") {
input.val("");
socket.emit("input", {
id: input.data("target"),
text: text
});
}
});
sidebar.on("click", ".channel", function() {
chat.find(".window[data-id='" + $(this).data("id") + "']")
.bringToTop();
});
});
(function() {
var highest = 1;
$.fn.bringToTop = function() {
this.css('z-index', highest++);
};
})();

View file

@ -1,299 +0,0 @@
/**
* The Client class.
*
* @public
*/
function Client() {
/**
* Self reference.
*
* @private
*/
var self = this;
/**
* The sidebar view.
*
* @type {Sidebar}
* @private
*/
var sidebar = new views.Sidebar();
/**
* The chat view.
*
* @type {Chat}
* @private
*/
var chat = new views.Chat();
/**
* The active socket.
*
* @type {Socket}
* @private
*/
var socket;
/**
* List of networks.
*
* @type {Array<Network>}
* @private
*/
var networks = [];
/**
* Initialize new socket connections.
*
* @param {Array<Network>} data
* @public
*/
this.init = function(data) {
networks = data;
chat.render(data);
sidebar.render(data);
socket.on(
"event",
self.handleEvent
);
};
/**
* Connect to the server via WebSockets and start listening
* for the `init` event.
*
* @param {String} host
* @public
*/
this.connect = function(host) {
socket = io.connect(host).on("init", self.init)
};
/**
* Handle events sent by the server.
*
* @param {Event} event
* @public
*/
this.handleEvent = function(event) {
// Debug
console.log(event);
};
/**
* Set up user events.
*
* @private
*/
// Handle window focus.
sidebar.element.on("click", ".channel", function(e) {
e.preventDefault();
var target = $(this).data("id");
chat.element.find(".window[data-id='" + target + "']")
.bringToTop();
});
// Emit events on user input.
chat.element.on("submit", "form", function() {
var form = $(this);
var input = form.find(".input");
if (input.val() != "") {
var text = input.val();
input.val("");
socket.emit("input", {
target: form.closest(".window").data("id"),
text: text
});
}
});
};
/**
* Views namespace.
*
* @namespace
*/
var views = {};
/**
* Sidebar view.
*
* @public
*/
views.Sidebar = function() {
/**
* Template container.
*
* @private
*/
var tpl = {
networks: $("#networks").html()
};
/**
* This is the target element where we will
* render the view.
*
* @type {jQuery.Object}
* @public
*/
this.element = $("#sidebar");
/**
* Render the view.
*
* @param {Array<Network>} networks
* @public
*/
this.render = function(networks) {
this.element.html(Mustache.render(tpl.networks, {networks: networks}));
};
};
/**
* Chat view.
*
* @public
*/
views.Chat = function() {
/**
* Template container.
*
* @private
*/
var tpl = {
channels: $("#channels").html()
};
/**
* Partial templates.
*
* @private
*/
var partials = {
users: $("#users").html(),
messages: $("#messages").html()
};
/**
* This is the target element where we will
* render the view.
*
* @type {jQuery.Object}
* @public
*/
this.element = $("#chat");
/**
* Render the view.
*
* @param {Array<Network>} networks
* @public
*/
this.render = function(networks) {
var render = "";
networks.forEach(function(n) {
render += Mustache.render(tpl.channels, n, partials);
});
this.element
.html(render);
};
/**
* Add to view.
*
* @param {Event} event
* @public
*/
this.add = function(event) {
var render = "";
var target = "";
switch(event.type) {
case "channel":
render = Mustache.render(
tpl.channels, {channels: event.data}
);
break;
case "message":
target = ".messages";
render = Mustache.render(
partials.messages, {messages: event.data}
);
break;
case "user":
target = ".users";
render = Mustache.render(
partials.users, {users: event.data}
);
break;
}
if (target == "") {
this.element
.append(render);
} else {
this.element
.find("[data-id='" + event.target + "'] " + target)
.append(render);
}
};
/**
* Remove from view.
*
* @param {Int} id
* @public
*/
this.remove = function(id) {
this.element.find("[data-id='" + id + "']").remove();
};
};
/**
* Bring element to top of the z-index stack.
*
* @public
*/
(function() {
var highest = 1;
$.fn.bringToTop = function() {
this.css('z-index', highest++);
};
})();

View file

@ -1,246 +0,0 @@
(function(exports) {
/**
* Declare the namespace.
*
* @namespace
*/
var models =
typeof window === "undefined" ? exports
: window.models = {};
/**
* Use this to create unique identifiers.
*
* @type {Int}
* @private
*/
var id = 0;
/**
* Network model.
*
* @public
*/
models.Network = function() {
/**
* Unique identifier.
*
* @type {Int}
* @public
*/
this.id = id++;
/**
* The network address.
*
* @type {String}
* @public
*/
this.address = "";
/**
* List of channels.
*
* @type {Array<Channel>}
* @public
*/
this.channels = [];
/**
* User nickname.
*
* @type {String}
* @public
*/
this.nick = "";
};
/**
* Channel model.
*
* @public
*/
models.Channel = function() {
/**
* Unique identifier.
*
* @type {Int}
* @public
*/
this.id = id++;
/**
* The channel name.
*
* @type {String}
* @public
*/
this.name = "";
/**
* The channel type.
* This property should be either `network` or `channel`.
*
* @type {String}
* @public
*/
this.type = "channel";
/**
* The current channel topic.
*
* @type {String}
* @public
*/
this.topic = "";
/**
* List of users.
*
* @type {Array<User>}
* @public
*/
this.users = [];
/**
* List of messages.
*
* @type {Array<Message>}
* @public
*/
this.messages = [];
};
/**
* User model.
*
* @public
*/
models.User = function() {
/**
* Unique identifier.
*
* @type {Int}
* @public
*/
this.id = id++;
/**
* The user name.
*
* @type {String}
* @public
*/
this.name = "";
};
/**
* Message model.
*
* @public
*/
models.Message = function() {
/**
* The timestamp.
*
* @type {String}
* @public
*/
this.time = "";
/**
* The content of the message.
*
* @type {String}
* @public
*/
this.text = "";
/**
* The author of the message.
*
* @type {String}
* @public
*/
this.user = "";
};
/**
* Event model.
*
* Used when pushing changes between the server
* and the clients.
*
* @public
*/
models.Event = function() {
/**
* Action to perform.
*
* @type {String}
* @public
*/
this.action = "";
/**
* Model type.
*
* @type {String}
* @public
*/
this.type = "";
/**
* The target network or channel.
*
* @type {Int|String|Object}
* @public
*/
this.target = "";
/**
* The data.
*
* @type {Int|String|Object}
* @public
*/
this.data = "";
};
})(this);

36
lib/models.js Normal file
View file

@ -0,0 +1,36 @@
var models = exports;
var id = 0;
models.Network = function() {
this.id = id++;
this.address = "";
this.nick = "";
this.channels = [];
};
models.Channel = function() {
this.id = id++;
this.name = "";
this.type = "channel";
this.topic = "";
this.users = [];
this.messages = [];
};
models.User = function() {
this.id = id++;
this.name = "";
};
models.Message = function() {
this.text = "";
this.time = "";
this.user = "";
};
models.Event = function() {
this.action = "";
this.data = "";
this.target = "";
this.type = "";
};

View file

@ -1,115 +1,63 @@
/**
* Module dependencies.
*/
var _ = require("lodash");
var connect = require("connect");
var io = require("socket.io");
var models = require("./models.js");
// Local library.
var models = require("../client/js/models.js");
exports.listen = listen;
/**
* Export module.
*/
var sockets;
var networks = [];
module.exports = Server;
var network = new models.Network;
var chan = _.assign(new models.Channel, {
name: "Network",
type: "network"
});
/**
* The Server class.
*
* @public
*/
function Server() {
/**
* Self reference.
*
* @private
*/
var self = this;
/**
* Active sockets managed by socket.io.
*
* @type {Object}
* @private
*/
var sockets;
/**
* List of networks.
*
* @type {Array<Network>}
* @public
*/
this.networks = [];
/**
* Start the server and listen for connections
* on the specified port.
*
* @param {Int} port
* @public
*/
this.listen = function(port) {
var app = connect()
.use(connect.static("client"))
.listen(port);
sockets =
io.listen(app).on("connection", this.init)
.sockets;
};
/**
* Initiate new socket connections.
*
* @param {Socket} socket
* @public
*/
this.init = function(socket) {
sockets.emit(
"init",
self.networks
);
socket.on(
"input",
function(input) {
self.handleUserInput(input)
}
);
};
/**
* Handle incoming inputs sent from clients.
*
* @param {String} input
* @public
*/
this.handleUserInput = function(input) {
var text = input.text;
if (text.charAt(0) != "/") {
console.log("MESSAGE: " + text);
return;
}
var args = text.substr(1).split(" ");
var cmd = args[0].toUpperCase();
switch (cmd) {
default:
console.log("COMMAND: " + cmd);
break;
}
};
network.channels.push(chan);
networks.push(network);
function listen(port) {
var http = connect()
.use(connect.static("client"))
.listen(port);
sockets = io.listen(http).sockets;
sockets.on("connection", function(socket) {
init(socket);
});
};
function init(socket) {
socket.on(
"input",
function(input) {
handleUserInput(input)
}
);
sockets.emit("event", _.assign(new models.Event, {
action: "redraw",
data: networks
}));
};
function handleUserInput(input) {
var id = input.id;
var text = input.text;
var message = _.assign(new models.Message, {text: text});
var event = _.assign(new models.Event, {
action: "add",
type: "message",
data: message,
target: id
});
sockets.emit("event", event);
_.each(networks, function(n) {
var chan = _.findWhere(n.channels, {id: id});
if (chan !== "undefined") {
chan.messages.push(message);
}
});
};

View file

@ -7,6 +7,7 @@
"dependencies": {
"commander": "2.1.0",
"connect": "2.13.0",
"lodash": "2.4.1",
"socket.io": "0.9.16"
}
}