ExpressとSocket.ioを使ったチャットサンプル

このエントリはリアルタイムWebハッカソンのハンズオン資料その4です。

前回の続きです。それでは次に簡単なチャットアプリのコードを見てみましょう。かなりの部分(特にデザイン面)をSocket.ioのチャットサンプルをパクって参考にしています。

サーバ側であるapp.jsはこんな感じです。

var express = require('express'),
    io = require('socket.io'),
    json = JSON.stringify;

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.use(express.bodyDecoder());
  app.use(express.methodOverride());
  app.use(express.compiler({ src: __dirname + '/public', enable: ['less'] }));
  app.use(app.router);
  app.use(express.staticProvider(__dirname + '/public'));
  app.use(express.logger());
});

app.configure('development', function(){
  express.logger("development mode");
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  express.logger("production mode");
  app.use(express.errorHandler());
});

// Routes

app.get('/', function(req, res){
  res.render('index.jade', {
    locals: {
        title: 'リアルタイムWebハッカソン Chat Room'
    }
  });
});

// Only listen on $ node app.js

if (!module.parent) {
  app.listen(3000);
  console.log("Express server listening on port %d", app.address().port)
}

var socket = io.listen(app);
var count = 0;
socket.on('connection', function(client) {
  count++;
  client.broadcast(json({count: count}));
  client.send(json({count: count}));

  client.on('message', function(message) {
    // message
    client.broadcast(message);
    client.send(message);
  });
  client.on('disconnect', function() {
    // disconnect
    count--;
    client.broadcast(json({count: count}));
  });
});

やりとりする内容をJSON形式に変えてます。人数のカウント情報だけでなく、チャット内容もやりとりするため、ただの文字列ではどちらが来てるのかを判断しづらいからです。
messageを受け取るところの処理では受け取った内容をそのまま配信してます。

次にクライアント側を見ましょう。views/layout.jadeはこんな感じです。

!!! 5
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js')
    script(type='text/javascript', src='http://cdn.socket.io/stable/socket.io.js')
    script(type='text/javascript', src='/javascripts/client.js')
  body!= body

先頭に5が加わってHTML5の宣言にした事以外前回から変わりありません。HTML5にしてるのは下にあるindex.jadeのテキストボックスにplaceholder属性の設定をしているからです。
views/index.jadeは次のようになっています。

h1= title
p 現在接続している人は
  span#count
  人います
#chat
form#form(onsubmit='send(); return false;')
  input#name(type='text', placeholder='Twitter ID')
  input#text(type='text', autocomplete='off')
  input(type='submit', value='送信')

チャット表示欄#chatと入力フォーム#formが追加されています。#chatのように要素を省略すると自動的にdivだと判断されます。
public/stylesheets/style.lessは次のようになっています。

body {
  padding: 10px 50px 5px 50px;
  font: 14px "Lucida Grande", "Helvetica Nueue", Arial, sans-serif;
}
#chat {
  height: 500px;
  overflow: auto;
  width: 800px;
  border: 1px solid #eee;
  p {
    padding: 0px;
    margin: 0px;
  }
  div {
    padding: 5px;
    margin: 0;
  }
  div:nth-child(odd) {
    background: #F6F6F6
  }
  .permalink {
    font: 3px;
    color: #AEAEAE;
  }
}
#form {
  width: 782px;
  background: #333;
  padding: 5px 10px;
  input[type=text] {
    padding: 5px;
    background: #fff;
    border: 1px solid #eee;
  }
  #name {
    width: 85px;
  }
  #text {
    width: 620px;
  }
  input[type=checkbox] {
  }
  input[type=submit] {
    cursor: pointer;
    background: #999;
    border: none;
    padding: 6px 8px;
    -moz-border-radius: 8px;
    -webkit-border-radius: 8px;
    margin-left: 5px;
    text-shadow: 0 1px 0 #fff;
  }
  input[type=submit]:hover {
      background: #A2A2A2;
  }
  input[type=submit]:active {
      position: relative;
      top: 2px;
  }
}

最後にclient.jsを見てみましょう。

var socket = new io.Socket('localhost'),
    json = JSON.stringify;
socket.connect();
socket.on('message', function(message) {
  message = JSON.parse(message);
  if (message.count) {
    $('#count').text(message.count);
  }
  if (message.message) {
    var data = message.message;
    var date = new Date();
    date.setTime(data.time);
    $('#chat').append('<div class="chatlog"><p><a name=' + data.time + '></a><a href="http://twitter.com/' + data.name + '"><img src="http://api.dan.co.jp/twicon/' + data.name + '/mini" /></a> ' + data.text + '</p><a class="permalink" href="#' + data.time + '">' + date.toString() + '</a></div>')
    $('#chat').scrollTop(1000000);
  }
});

function send() {
  var name = $('#name').val();
  var text = $('#text').val();
  if (text && name && name != "Twitter ID") {
    var time = new Date().getTime();
    socket.send(json({message: {name: name, text: text, time: time}}));
    $('#text').val('');
  }
}

特に難しいところはないと思います!