Blog

ブログ

JS初心者がMeteor.jsで超シンプルなチャットを作りました

2015.09.28 公開
Yuko Hashimoto
Yuko Hashimoto デザイナー

Meteor.jsがアツいらしいです

nodeベースのWebアプリケーションプラットフォーム、Meteor
弊社プログラマの皆は楽しそうに使いこなしています。
(弊社のアプリ旅デラックスWebサイトでもMeteorを使用しています)

サーバーサイドでJavaScriptが動くことにもたじろいでしまうのですが、システム周りの詳しいことがわからないデザイナーでもJavaSciptならちょっとはわかるのでは…?ということで、

作ってみました。
Po Po Chat
ハトの写真はランダム。自由に書き込めます。書き込む時は紙飛行機ボタンを押してください。
meteor.com上にデプロイしていて、実は全世界から上書きできる状態です。

ファイル数はとんでもなく少ない

使っているファイルですが、こんな感じです。
これらに加えてハト写真が7枚。

var Messages = new Meteor.Collection('messages');

if (Meteor.isClient) {

  var img = [
    'img/hato1.png','img/hato2.png','img/hato3.png',
    'img/hato4.png','img/hato5.png','img/hato6.png','img/hato7.png'];
  var num = Math.floor(Math.random() * img.length);
  var usrimg = img[num];

  var maxrec = 10;

  Template.contents.helpers({
    maxrec: maxrec,
    usrimg: usrimg,
    messages : function () {
      return Messages.find({},{sort:{date:-1}})
    }
  });

  Template.contents.events({
    'click .submit-btn' : function(event){
      var message = $('#postmes').val();
      if(message != ''){
        $('#postmes').val('');
        var date = Date.parse(new Date());
        var datetime = toDateStr(date);
        var name = $('#postname').val();
        var img = usrimg;
        if(name == ''){
          name = 'guest';
        }

      // コレクションへ新レコードを登録
      Messages.insert({
        date : date,
        datetime : datetime,
        message : message,
        name : name,
        img : img
        },function(err,_id){
          var len = Messages.find({}).fetch().length;
          if(len > maxrec){
            var doc = Messages.findOne({}); 
            Messages.remove({_id:doc._id}); 
          }
        });
      }
    }
  });

  // 1桁の数字を0埋めで2桁にする
  var toDoubleDigits = function(num) {
    num += "";
    if (num.length === 1) {
      num = "0" + num;
    }
   return num;
  };

  // 日付を文字列に変換
  var toDateStr = function(parseDate){
    var date = new Date(parseDate);
    var y = toDoubleDigits(date.getFullYear());
    var m = toDoubleDigits(date.getMonth()+1);
    var d = toDoubleDigits(date.getDate());
    var h = toDoubleDigits(date.getHours());
    var min = toDoubleDigits(date.getMinutes());
    return y+"/"+m+"/"+d+" "+h+":"+min;
  }

}

if (Meteor.isServer) {
}
<head>
  <title>Po Po Chat</title>
  <link rel="stylesheet" href="https://storage.googleapis.com/code.getmdl.io/1.0.0/material.blue_grey-red.min.css">
  <script src="https://storage.googleapis.com/code.getmdl.io/1.0.0/material.min.js"></script>
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>
  {{> contents}}
</body>

<template name="contents">
  <div class="submit-btn"></div>
  <div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--overlay-drawer-button">
    <div class="mdl-layout__drawer">
      <div class="usrimg"><img src="{{usrimg}}"></div>
      <div class="mdl-textfield mdl-js-textfield textfield-name">
        <input class="mdl-textfield__input" id="postname" type="text">
        <label class="mdl-textfield__label" for="postname">Your name</label>
      </div>
    </div>
    <main class="mdl-layout__content">
      <header class="mdl-layout__header">
        <div class="mdl-layout__header-row">
          <span class="mdl-layout-title">Po Po Chat</span>
          <div class="mdl-layout-spacer"></div>
          <span>{{maxrec}}件まで表示されます</span>
        </div>
      </header>

      <div class="rec_all">
        {{#each messages}}
          <div class="rec_wrap">
            <div class="rec_usrimg"><img src="{{img}}"></div>
            <div class="rec_content">
              <div class="rec_name" id="name{{_id}}">{{name}}</div>
              <div class="rec_date" id="date{{_id}}">{{datetime}}</div>
              <div class="rec_mes" id="mes{{_id}}">{{message}}</div>
            </div>
           </div>
         {{/each}}
       </div>

       <div class="shim"></div>
       <div class="send-message">
         <div class="form">
           <div class="mdl-textfield mdl-js-textfield textfield-message">
             <input class="mdl-textfield__input" id="postmes" type="text">
             <label class="mdl-textfield__label" for="postmes">Message</label>
           </div>
         </div>
         <div class="button-wrap">

           <button class="mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
            <i class="material-icons">send</i>
          </button>
        </div>
      </div>
    </main>
  </div>
</template>
.mdl-textfield.textfield-name{
  width: 208px;
  margin-left: 15px;
  }
.shim{
  position: fixed;
  bottom: 54px;
  left: 0;
  right: 0;
  height: 36px;
  background: linear-gradient(rgba(255,255,255,0), #eee 80%) top center no-repeat;
}

main{
  background: #eee;
}
.mdl-layout__header-row{
  padding-left: 40px!important;
}
.send-message{
  display: flex;
  display: -webkit-flex;
  position: fixed;
  bottom: 0;
  right: 0;
  left: 240px;
  height: 54px;
  padding: 0 16px 6px 16px;
  background: #eee;
  width: auto;
}

.mdl-textfield.textfield-message{
  width: 95%;
}

@media (max-width: 850px) {
.send-message{
  width: 95%;
  left: 0;
  }
}

.send-message .form{
  flex: 1;
  -webkit-flex: 1;
}
.send-message .button-wrap{
  width: 56px;
  position: fixed;
  bottom: 3px;
  right: 16px;
  background: #eee;
  padding-left: 20px;
}

.rec_all{
  padding-bottom: 60px;
}
.rec_wrap{
  display: flex;
  display: -webkit-flex;
  padding: 15px;
  border-bottom: 1px solid #ccc;
  padding-left: 40px;
}
.rec_content{
  flex: 1;
  -webkit-flex: 1;
}
.rec_name{
  color: #999;
  font-weight: bold;
  float: left;
}
.rec_usrimg{
  width: 50px;
  margin-right: 20px;
}
.rec_usrimg img{
  width: 50px;
  height: 50px;
  border-radius: 50%;
  border: 3px solid rgb(255,82,82);
}
.rec_mes{
  clear: both;
}
.rec_date{
  width: 120px;
  font-size: 0.8em;
  color: #999;
  float: right;
}

.usrimg{
  text-align: center;
  margin-top: 20px;
}
.usrimg img{
  width: 100px;
  height: 100px;
  border-radius: 50%;
  border: 3px solid rgb(255,82,82);
}

.submit-btn{
  position: fixed;
  bottom: 3px;
  right: 16px;
  width: 56px;
  height: 56px;
  z-index: 10;
  cursor: pointer;
}

CSSはMaterial Design Liteを読み込んでいるので特に少ない。

以下のサイトを参考にしています。

Polymerを触っていた頃に見つけた猫のチャット、かわいいな〜と思ったのでデザインの参考にしています(リンク先のデモ)。
PolymerでMaterial Designなチャットアプリを作ろう
マテリアルデザインが上手に使えるように、模写で勉強です。

デザイナーが使ってみてどうよ、な話

Meteorに関しては「画期的だ!」という玄人の記事は多くてもウルトラ初心者に向けた記事はほぼないので、チュートリアルをやっていても「クライアント…?サーバー…?セッション…?DB…みる…?」な状態に陥りやすいです(私だけでしょうか)。

しかしJSは見慣れていることと、テンプレートやファイル構成から少しずつ概要が見えてきます。
それとブラウザのコンソールから操作することで、何が起こっているかの動きがわかりやすいです。

実務にバリバリ使うには時間を要しそうですが、Webアプリケーションの学習には適していそう。meteor.comで公開までできますしね。

全体の構成のおかげで、プログラマとの共同作業はスムーズです。

meteorとマテリアルデザイン

今回作ったチャットでは Material Design Lite を使用しています。
旅デラックスのサイトでは Polymer を使用しました。

使ってみて何箇所かひっかかる部分がありました。どうも相性がよろしくないようで、うまく動かない部分が出てきます。うーん。

何かいい方法はあるのかなあと検索してみて、

今頃見つけてちょっと泣きそう。

そのほか参考記事

Meteorのサンプルにチャットがちょうどいいみたい?作って解説している記事を見つけたのでメモしておきます。
JavaScriptのみ!Meteorで作る簡単リアルタイムWebアプリ
Meteorを使ったリアルタイムチャットアプリケーション

学習には Discover Meteor は必読。
DISCOVER METEOR
ただ翻訳が追いついてなくて情報が古くて動かないこともあるので、この辺も参考にすると捗ります。
JavaScript超初心者向け Meteor メモ (1)
Meteorが1.0になったので入門してみた(1/2)

最近のアップデートで公式サイトのサンプルも増えてるぞ!
Get started|meteor