meen(mean)でtodoアプリを作成してみた。

## 概要
いまさらながら、気になっていたmean環境をごにょごにょ触って、
mongo + express + ejs + node.js(meen(angularjs不使用))にて、定番のtodoアプリを作ってみました。
なんてことない~~ゴミのような~~アプリです。
bower | npm | mongo などの説明は割愛します。

## 環境
ローカル：
CentOS 6.8
node.js 9.2.0
npm 5.5.1
express 4.15


公開サーバ：
IBM Cloud(bluemix)無料枠

## やったこと（メモ）
node.js + npm + bower + mongo は既にローカルで使えるようになっていたので（たぶん気になったときにいれたまま）
expressだけnpmからインストールしました。

### webアプリのひな型作成
expressコマンドでひな型を、まず作成します。
デフォルトでviewがjadeになっているようですが、わかりやすそうなejsを指定しました。

```
express todoapp -e ejs
```

上記でひな型サイト(アプリ名)のディレクトリができるので、cd todoappをやって、npm startで実行！
するのですが、そのままやるとモジュールないよーエラーになります。

```
Error: Cannot find module 'express'
```

npm install or npm i でインストール後、npm startで動きます。
package.jsonに定義してあるscriptsに書いてあるものが動くようです。

ポートはbin/wwwに書いてあって、デフォルト3000になっています。

### bowerで使うjsやcssを取得
```
bower install jquery bootstrap4 underscore
```

/public/vendor 直下に配置されます。

### viewsの修正
views/index.ejsがトップページになっています。app.jsあたりをごにょごにょすれば返れそう。

```
  <%- include('_head'); %>
```
などとやれば、ファイルを分けて読み込むことができるのでheader, footer等を外出しにしました。

### javascriptをごにょごにょ
javascriptのほうで、jQuery(ajax)を使って登録や読み込みなどを書きました。

```main.js
$(function(){
  function getDate(task_date) {
      let date = new Date (task_date);
      let y = date.getFullYear();
      let m = date.getMonth() + 1;
      let d = date.getDate();
      let h = date.getHours();
      let i = date.getMinutes();

      if (m < 10) {
        m = '0' + m;
      }
      if (d < 10) {
        d = '0' + d;
      }
      if (h < 10) {
        h = '0' + h;
      }
      if (i < 10) {
        i = '0' + i;
      }
      return y+'/'+m+'/'+d+' '+h+':'+i;
  }

  function render_task_list(task_list) {
    let tasks = task_list;
    let list_tag = '';
    for(let i = 0; i < tasks.length; i++) {
      list_tag +=
        '<li class="list-group-item" style="padding-left:35px;">'+
          '<label class="form-check-label">'+
          '<input class="form-check-input" type="checkbox" value="" style="margin-top: 20px;margin-left: -25px;">'+
          getDate(tasks[i].create_date)+'<br />'+
          tasks[i].content+
          '</label>'+
          '<button type="button" class="close close_task_list"  data-id="'+tasks[i]._id+'">'+
              '<span>&times;</span>'+
          '</button>'+
        '</li> ';
    }
    $("#task_list").html(list_tag);
  }


  if(!_.isEmpty(localStorage.getItem('task_input_user'))) {
    $('#user_name').val(localStorage.getItem('task_input_user'));
    $('#user_name').prop('disabled', true);

     $.ajax({
      type: 'POST',
      url:'/selecttask',
      dataType: "json",
      data: {user_name:$('#user_name').val()}
    }).done(function(data, textStatus, jqXHR){
      if(data.task_list.length != 0){
        render_task_list(data.task_list);
      } else {
        $("#task_list").html('<li class="list-group-item">'+$('#user_name').val()+'さんのタスクはありません。'+'</li>');
      }
    }).fail(function(jqXHR, textStatus, errorThrown){
    });
  }

  // 閉じるボタン押下時
  $(document).on('click', '.close', function () {
    $(this).parent().slideUp();
  });

  // 取り消し線
  $(document).on('change', '.form-check-input', function () {
    if($(this).prop('checked')){
      $(this).parent().attr('style', 'text-decoration: line-through;');
    } else {
      $(this).parent().attr('style', '');
    }
  });

  // ユーザー名変更 一覧取得
  $("#user_name").change(function(){
    let current_user = $(this).val();
    $.ajax({
      type: 'POST',
      url:'/selecttask',
      dataType: "json",
      data: {user_name:current_user}
    }).done(function(data, textStatus, jqXHR){
      if(data.task_list.length != 0){
        render_task_list(data.task_list);
      } else {
        $("#task_list").html('<li class="list-group-item">'+current_user+'さんのタスクはありません。'+'</li>');
      }
    }).fail(function(jqXHR, textStatus, errorThrown){
    });

  });

  // 一覧削除ボタン押下
  $(document).on('click', '.close_task_list', function () {
    $.ajax({
      type: 'POST',
      url:'/deletetask',
      dataType: "json",
      data: {id:$(this).attr('data-id')}
    }).done(function(data, textStatus, jqXHR){
      $('#info_delete_success').slideDown();
    }).fail(function(jqXHR, textStatus, errorThrown){
      $('#error_delete_faild').slideDown();
    });
  });


  // タスク追加ボタン押下
  $("#btn_add").click(function(){
    // バリデーション
    if(_.isEmpty($('#user_name').val())) {
      $('#alert_empty_user').slideDown();
      return false;
    }

    if(_.isEmpty($('#contents').val())) {
      $('#alert_empty_task').slideDown();
      return false;
    }

    // タスク追加
    $.ajax({
      type: 'POST',
      url:'/addtask',
      dataType: "json",
      data: {user_name:$('#user_name').val(), content:$('#contents').val()}
    }).done(function(data, textStatus, jqXHR){
      $('#info_add_success').slideDown();
      if(data.task_list.length != 0){
        $('#user_name').prop('disabled', true);
        localStorage.setItem('task_input_user', $('#user_name').val());
        render_task_list(data.task_list);
      }
    }).fail(function(jqXHR, textStatus, errorThrown){
      $('#error_add_faild').slideDown();
    });
  });

});
```

### routersの修正
routers/index.jsに取得・登録・削除時にpostでアクセスするので、それぞれ処理を書きました。
mongodbを使うためにmongooseをインストールしました。
後、登録されるデータのXSS対応のためにhtmlspecialcharsモジュールも一緒にいれてみました。

```index.js
let mongoose = require('mongoose');
let express = require('express');
let router = express.Router();
let htmlspecialchars = require('htmlspecialchars');

mongoose.connect('mongodb://localhost/todoapp');
const Task = mongoose.model('Task', {user_name: String, content: String, create_date:Date});

/*
* 初期表示
*/
router.get('/', function(req, res, next) {
  res.render('index', {task_list:null});
});

/*
* タスク取得処理
*/
router.post('/selecttask', function(req, res) {
  let post_data = req.body;
  Task.find({user_name:post_data.user_name}, function (err, tasks) {
    if(err) throw err;
    res.send({ task_list:tasks });
  });
});

/*
* タスク追加処理
*/
router.post('/addtask', function(req, res) {
  let post_data = req.body;
  let task = new Task({user_name: htmlspecialchars(post_data.user_name), content: htmlspecialchars(post_data.content), create_date:new Date()});
  task.save(function(err){
    if(err) throw err;

    Task.find({user_name:post_data.user_name}, function (err, tasks) {
      if(err) throw err;
      res.send({message:'ok', task_list:tasks});
    });
  });
});

/*
* タスク削除処理
*/
router.post('/deletetask', function(req, res) {
  let post_data = req.body;
  Task.remove({_id:post_data.id}, function(err){
    if(err) throw err;
  });

  res.send({message:'ok'});
});

module.exports = router;
```

### 所感
npmでいろいろインストールして、expressでひな型を作るだけで簡単にページが作れるので楽だと思いますた。
angularjsを現在見ていたりしますが、angular2からangularになって、typescriptになっているので全然作り方が変わるぽいです。
ibm cloudを使ってみましたが、メリットは特に感じませんでした。herokuでもいいかも。

### ソース
ソースはこちら。
https://github.com/YasuakiHirano/todoapp
デモ
https://codelike-todoapp-timely-impala.mybluemix.net/
無料枠なので10日後には落ちてるかと思われます。（´・ω...:.;::..