Node.js+Socket.ioを用いてチャットルームを実装しよう

#Node.jsでチャットルームを作りたい
というわけで作っていきたいと思います。
方針としてはNode.jsでlocalhost:3000に鯖を立てて、
そこにHTML/CSS/JavaScriptを適用します。
##ファイル構成
今回の構成は以下の通りです。

```
- index.js

- index.html

- node_modules
   |
    - ..etc

- pic
   |
    - ..etc
- js
   |
    - window.js
    - socket.js
- css
   |
    - window.css
    
- package.json
```

##サーバ構築
下記コード内で require('module') をしている
nodeモジュールを npm install などでインストールしてください。

```javascript:index.js
var http = require('http');
var fs   = require('fs');
var path = require('path');
var mime = {
   ".html": "text/html",
   ".css":  "text/css",
   ".js":  "text/javascript",
   // 読み取りたいMIMEタイプはここに追記
   // MIMEを宣言しないとクライアント側のHTMLに通した
   // JSやCSSが取得できない
};
//socket.ioモジュールの読み込み
var socketio = require("socket.io");

//HTTPオブジェクトを生成
var http_server = new http.createServer(function(req, res {
   if(req.url == '/'){
      filePath = '/index.html';
   }else{
      filePath = req.url;
   }
   //ここで絶対PATHの取得
   var fullPath = __dirname + filePath;
   res.writeHead(200, {"Content-Type": mime[path.extname(fullPath)] || "text/plain"});

      fs.readFile(fullPath, function(err, data){
         if(err){
            //エラー時の応答
         }else{
            res.end(data, 'UTF-8');
         }
    });
}).listen(3000);

console.log('Server running at http://localhost:3000/');

//イベントハンドラを設定
var io = socketio.listen(http_server);
io.sockets.on("connection", function (socket){

   //メッセージ送信（送信者にも送られる）
   socket.on("C_to_S_message", function (data){
      io.sockets.emit("S_to_C_message", {value:data.value});
   });
 
   //ブロードキャスト（送信者以外の全員に送信）
   socket.on("C_to_S_broadcast", function (data){
      socket.broadcast.emit("S_to_C_message", {value:data.value});
   });
 
   //切断したときに送信
   socket.on("disconnect", function (){
      io.sockets.emit("S_to_C_message", {value:"user disconnected"});
   });
});
```

##HTML/CSS/JavaScriptについて
今回、クライアントサイドとサーバサイドの結合に
少々手間をとりました。
その理由として、
<font color="MediumBlue" size="7"><b>
HTMLに紐づけたjsにScoket.ioの
イベントハンドラ設定したらサーバサイドに認識されなくね？
</font>
まあこれですね、仕方なくHTMLに素書きしました...。
いい方法、教えていただきたいです(懇願)

###必要なもの
\- socket.io.js(socket.js) 
https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js

以上のリンクを参照するか、落としてください。
今回はsocket.jsとしてjsファイル内に置いています。

とりあえず、コードは以下の通りです。
ボタンの部分はどんなインスタンスでもいいです。
今回は丸いボタン風画像をボタンにできるように実装しています。(ゴミ箱も同様)

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="Access-Control-Allow-Origin" content="*">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
        <meta name="application-name" content="Olt_chat">
        <link rel="stylesheet" type="text/css" href="css/window.css">
    </head>
    <body>
        <div id = "window_panel" class = "window_panel">
            <div id="headder_panel"class="headder_panel">
                <a href="#" id="btn_clean" onclick = "clean_localStr()">
                    <div id = "btn_cln">
                         <img src="pic/clean_btn.png" style="width:100%; height:100%;">
                    </div>
                </a>
            </div>
            <div id="timeline_panel" >
                <div id="obj" class ="obj"></div>
            </div>
            <div id="input_panel" class = "input_panel" >
                <textarea id = "textInput_area" class ="textInput_area"></textarea>
               <a href="#" id="btn_sample" onclick = "setTimeout(btn_push(), 300);">
                    <div id = "btn_pic">
                         <img src="pic/button_post.png" style="width:100%; height:100%;">
                    </div>
                </a>
            </div>
        </div>
    </body>
    <script type="application/javascript" src="js/socket.js"></script>
    <script>
        var s = io.connect('http://localhost:3000');//ローカル
        //サーバから受け取るイベント
        s.on("connect", function () {});//接続時
        s.on("disconnect", function (client) {});//切断時
        s.on("S_to_C_message", function (data) {
            addMessage(data.value);
        });
        //クライアントからイベント送信時のイベントハンドラ
        function sendBroadcast(text) {
            let msg = text //取得
            s.emit("C_to_S_broadcast", {value:msg}); // サーバへ送信
            object_make(msg);
            console.log(msg);
        }
        function addMessage (value,color,size) {
            let msg = value.replace( /[!@$%<>'"&|]/g, ''); //タグ記号とかいくつか削除
            object_make(msg);//この関数を用いてチャットを表示
        }
    </script>
    <script type="application/javascript" src="js/window.js"></script>
</html>

```

描画処理というか全体的にネイティブJS使ってますが、
そこらへんは、レスポンシブデザインの処理の速さを求めた結果なのでご了承(自己満
....コメント打つのもだるくなってるレベルですが...。
画像のpathさえ入れてやればちゃんと動きます。




```js:window.js
var count = 0;
var touch_position_h;
var touch_prevent_flag = 0;

document.getElementById("timeline_panel").ontouchstart = function(event){
    if (touch_prevent_flag === 0){  
    }else{
        touch_position_h = event.changedTouches[0];
   　　}
}
document.getElementById("timeline_panel").ontouchmove = function(event){
    if (touch_prevent_flag === 0){
            event.preventDefault();
    }else{
   　　}
}
window.onload = function(){
    window_getsize();   
}
window.onresize = function(){  
    window_getsize();  
}

function window_getsize(){  
    var size_h = window.innerHeight;
    var size_w = window.innerWidth;

       if(size_h > size_w){
        var element = document.getElementById("window_panel"); 
        element.style.height = size_h + 'px';
        element.style.width = size_w + 'px';

        var element = document.getElementById("headder_panel");
        let headder_size_h = size_h*0.085;
        element.style.height = headder_size_h + 'px';
        element.style.width = size_w + 'px';
        element.style.top = 0 + 'px';

        var element = document.getElementById("btn_clean"); 
        let btncln_size_h = headder_size_h;//0.2*0.5*0.2
        element.style.height = btncln_size_h*0.75 + 'px';
        element.style.width = btncln_size_h*0.75 + 'px';
        element.style.bottom = btncln_size_h*(0.1) + 'px';
        element.style.right = btncln_size_h*(0.4) + 'px';

        var element = document.getElementById("input_panel"); 
        let IPsize_h = size_h*0.14;
        element.style.height = IPsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = 0 + 'px';

        var element = document.getElementById("timeline_panel"); 
        let TLsize_h = size_h - IPsize_h - headder_size_h;
        element.style.height = TLsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = size_h - TLsize_h - headder_size_h + 'px';

        var element = document.getElementById("btn_sample"); 
        let btn02_size_h = size_h*0.12;
        element.style.height = btn02_size_h*0.85 + 'px';
        element.style.width = btn02_size_h*0.85+ 'px';
        element.style.bottom = btn02_size_h*(0.25/2.5) + 'px';
        element.style.right = btn02_size_h*(0.25) + 'px';

        var element = document.getElementById("textInput_area");
        let textarea_size_h = size_h*0.12;
        element.style.height = textarea_size_h*0.75 + 'px';
        element.style.width = size_w - btn02_size_h*0.85 - btn02_size_h*(0.25)*3  + 'px';
        element.style.bottom = textarea_size_h*(0.25/1.5) + 'px';
        element.style.right = btn02_size_h*0.85+btn02_size_h*(0.25)*2 + 'px';
        element.style.max_height = textarea_size_h*0.75 + 'px';
        element.style.max_width = size_w*0.675 + 'px';
    }
    else if(size_h < size_w){
        var element = document.getElementById("window_panel"); 
        element.style.height = size_h + 'px';
        element.style.width = size_w + 'px';

        var element = document.getElementById("headder_panel");
        let headder_size_h = 0;
        element.style.height = headder_size_h + 'px';
        element.style.width = size_w + 'px';
        element.style.top = 0 + 'px';

        var element = document.getElementById("input_panel"); 
        let IPsize_h = size_h*0.215;
        element.style.height = IPsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = 0 + 'px';

        var element = document.getElementById("timeline_panel");    
        let TLsize_h = size_h - IPsize_h - headder_size_h;
        element.style.height = TLsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = size_h - TLsize_h - headder_size_h + 'px';
            
        var element = document.getElementById("btn_sample"); 
        let btn01_size_h = size_h*0.2;
        element.style.height = btn01_size_h*0.85 + 'px';
        element.style.width = btn01_size_h*0.85 + 'px';
        element.style.bottom = btn01_size_h*(0.25/2.75) + 'px';
        element.style.right = btn01_size_h*(0.25) + 'px';

        var element = document.getElementById("btn_clean"); 
        let btncln_size_h = size_h*0.085;
        element.style.height = btncln_size_h*0 + 'px';
        element.style.width = size_w*0 + 'px';
        element.style.bottom = btncln_size_h*(0.075) + 'px';
        element.style.right = btncln_size_h*(0.4) + 'px';

        var element = document.getElementById("textInput_area");
        let textarea_size_h = size_h*0.2;
        element.style.height = textarea_size_h*0.75 + 'px';
        element.style.width = size_w - btn01_size_h*(0.25)*3 - btn01_size_h*0.85 + 'px';
        element.style.bottom = textarea_size_h*(0.25/2) + 'px';
        element.style.right = btn01_size_h*(0.25)*2+btn01_size_h*0.85 + 'px';
        element.style.max_height = textarea_size_h*0.75 + 'px';
        element.style.max_width = size_w*0.675 + 'px';
    }
}
function btn_push(){
    var element = document.getElementById("textInput_area");
    let send_text = element.value;
    if(element.value!==null && element.value!=false){
        element.value=null;
    }else{
        return;
    }
    setTimeout(sendBroadcast(send_text), 1000);
}
var count_onclick_btnSample = 0;
var messageLast_containerSizeHeight = [];
function object_make(text){  

    let TLsize_h = document.getElementById("timeline_panel").height; 
    let TLsize_w = document.getElementById("timeline_panel").width;
    var str_tmp = 0 + '';
    if(count_onclick_btnSample > 0){
        str_tmp = TLsize_h + messageLast_containerSizeHeight[count_onclick_btnSample];
    }else {
        var str_tmp_s;
        str_tmp_s = str_tmp;
    }
    let object = document.createElement('div');  
    let object_s = document.getElementById("obj");

    object_s.innerHTML = ['<div id="message_container">'
        + '</div>'
            + '<div id = "message_0" >'
            + '<p>'+ text +'</p>'
            + '</div>' 
         + '</div>'
        + '</div>'].join("");
        
    object_s.append(object);

    var element = document.getElementById("message_container");
    var element1 = document.getElementById("message_0");

    messageLast_containerSizeHeight.splice(count_onclick_btnSample+1, 0, str_tmp);
    count_onclick_btnSample = count_onclick_btnSample + 1;  
}
function clean_localStr(){
    localStorage.clear(); 
}
```

ここまでくればあと少し。

```css:window.css
html,body{
        margin:0px;
        flex: 0;
        overflow:hidden;
 }
 #window_panel{
        position: absolute;
        flex: 0;
        background:#999;    
}
#input_panel{
        position: absolute;
        flex: 0;
        background:rgb(73, 72, 72);
 }
#headder_panel{
        position:fixed;
        background:rgb(73, 72, 72);
}
#timeline_panel{
        position: absolute;
        background:#EEE;
}
#btn_clean{
        position:absolute;
        display: inline-block;
        background: rgb(73, 72, 72);
        color: rgb(73, 72, 72);
        text-align: center;
        vertical-align: middle;
        overflow: hidden;
}
#btn_clean:active {
        border-bottom: solid 1px rgb(73, 72, 75);;
        box-shadow: 0 0 0px rgba(0, 0, 0, 0.40);
        border-radius: 10%;
}
#btn_clean:btn_cln {
       position: absolute;
}
#btn_sample{
        position:absolute;
        display: inline-block;
        background: rgb(73, 72, 72);
        color: rgb(73, 72, 72);
        border-radius: 50%;
        text-align: center;
        vertical-align: middle;
        overflow: hidden;
        box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.29);
        text-shadow: -0.2px -0.2px rgba(255, 255, 255, 0.43), 1px 1px rgba(0, 0, 0, 0.49);
}
#btn_sample:active {
        border-bottom: solid 1px #6d6c6d;
        box-shadow: 0 0 1px rgba(0, 0, 0, 0.40);
        border-radius: 50%;
}
#btn_sanmple:btn_pic{
        position: absolute;
}
#textInput_area{
        position: absolute;
        font-size: 100%;
        background:#EEE;
        overflow:hidden;
        border:none;
        resize: none;
}
#message_container{
        position:absolute;
        background:#000;
}
#message_0{
         position:absolute;
         background:#CCC;
         font-size: 100%;
}
```
ようやくできました。こんな感じです！
<img width="967" alt="スクリーンショット 2018-01-18 16.13.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/212521/9bb30a48-ef51-26d0-5762-e2bf9f464fe6.png">

左がSafari右がChrome ブラウザです。
是非やって見てください。
※ 何度もいいますが丸ボタン画像とゴミ箱画像用意してくださいね。
操作方法はHTMLあたりのコードを見てください。

参考にした記事はこちら
http://www.tettori.net/post/852/

https://qiita.com/Suibari_cha/items/48da8519d6f363925b6a

##最後に
記事書くのって結構労力必要ですね。
JS初めて4ヶ月そこらなので
どうしてこうなのとか、ここはこうした方がいい等
是非コメントしていただけると嬉しいです。