javascriptでtodoリストを作成 ※データをjsonで管理

#目的
ググったらたくさんtodoリストが出てきたけど、データをjsonで管理するのは
なかったので、javascriptの勉強がてらに作ってみました。

##画面キャプチャ
デモ動画を入れたかったけどうまくいかなかったので
画像で.....

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487273/4ac0d4a6-9051-6458-65f5-decdf9decf98.png)
###機能
・タスク追加
・タスク削除
・期限・状態でソート可
・メモ欄あり

[github](https://github.com/kaihatutarou716/todo/tree/todo)

##推奨環境(動作確認済み)
###PC
- window10
    - internet explorer11
<br>他ブラウザに非対応理由<br>
ActiveXObjectを使用してjsonファイル
に読み書きを行っているから

##ソース
###html

 ```todo.html
   <!DOCTYPE html>
  <html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/i18n/jquery-ui-i18n.min.js"></script>
    <script type="text/javascript" src="todo.js" charset="UTF-8"></script>
    <title>todoリスト</title>
  </head>
  <link rel="stylesheet" type="text/css" href="todo.css">
  <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">

  <body>
    <h1>todo list</h1>
    <form name="inputForm" id="inputForm_id" action="">
      <div class="taskTest">
        <input type="text" id="item" placeholder="タスク内容を入力">
      </div>

      <div class="calendar">
        <input type="text" id="datepicker" placeholder="期限を入力">
      </div>
      <div class="addButton">
        <button type="submit" onClick="submitItem();">追加</button>
      </div>
    </form>

    <div id="app">
      <div id="div_memoArea" style="display:none;">
        <textarea name="memoArea" id="memoArea_id" value="1" cols="60" rows=11></textarea>
      </div>
      <table id="dataTtable">
        <thead v-pre>
          <form name="Formtable" id="Formtable_id" action="">
            <tr>
              <th class="task"><button id="tskButton">
                  <font size=4>タスク</font>
                </button></th>
              <th class="deadLine"><button id="dieButton">
                  <font size=4>期限</font>
                </button></th>
              <th class="state"><button id="steButton">
                  <font size=4>状態</font>
                </button></th>
              <th class="memo"><button id="btnMemo">
                  <font size=4>メモ</font>
                </button></th>
              <th class="delBtn"><button id="btnButton">
                  <font size=4>-</font>
                </button></th>
            </tr>
          </form>
        </thead>
        <tbody>
          <!--javaScript でリストを追加-->
        </tbody>
      </table>
    </div>

    <!--クラス定義-->
    <script type="text/javascript">
      function dataInfo(editFlg) {
        this.setEditFlg(editFlg);
      };
      dataInfo.prototype = {
        setEditFlg: function (editFlg) {
          this._editFlg = editFlg;
        },
        getEditFlg: function () {
          return this._editFlg;
        }
      };
    </script>

    <!--グローバル変数定義-->
    <script type="text/javascript">
      dataFileName = "data.json";
    </script>

    <!--カレンダー表示-->
    <script type="text/javascript">
      $(function () {
        $.datepicker.setDefaults($.datepicker.regional['ja']);
        $('#datepicker').datepicker();
      });
    </script>

    <!--データ読み込む処理-->
    <script type="text/javascript">
      window.onload = onLoad;

      function onLoad() {

        /*指定したファイルが存在するかチェック
          存在しない場合新規でファイルを作成*/
        createJsonData();

        var xmlhttp = createXMLHttpObject();
        xmlhttp.onreadystatechange = function () {

          //ステータスが4の場合、jsonデータを取得
          if (xmlhttp.readyState == 4) {
            var JsonData = JSON.parse(xmlhttp.responseText);
            JsonData = sortJsonData(JsonData);

            //動的テーブル作成
            for (var j = 0; j < JsonData.data.length; j++) {

              //Id
              var dataId = document.createElement('input');
              dataId.setAttribute('type', 'hidden');
              dataId.setAttribute('id', 'dataId');
              dataId.value = JsonData.data[j].id;
              dataId.name = 'dataId';

              //タスク
              var dataTask = document.createElement('td');
              dataTask.setAttribute('id', 'task');
              dataTask.appendChild(document.createTextNode(JsonData.data[j].task));

              //期限
              var dataDeadLine = document.createElement('td');
              dataDeadLine.setAttribute('id', 'deadLine');
              dataDeadLine.appendChild(document.createTextNode(JsonData.data[j].deadLine));

              //状態
              var dState = "";
              if (JsonData.data[j].state == 1) {
                dState = "作業中";
              } else if (JsonData.data[j].state == 2) {
                dState = "完了";
              }

              var dataState = document.createElement('td');
              dataState.setAttribute('class', 'state');
              var stateButton = document.createElement('button');
              stateButton.setAttribute('id', 'stateButton');
              stateButton.value = JsonData.data[j].id;
              stateButton.appendChild(document.createTextNode(dState));
              var span = document.createElement('span');
              span.setAttribute('id', 'span');
              stateButton.appendChild(span);
              dataState.appendChild(stateButton);

              //メモボタン
              var btnMemo = document.createElement('td');
              btnMemo.setAttribute('class', 'btnMemo');
              var memoButton = document.createElement('button');
              memoButton.setAttribute('id', 'memoButton');
              memoButton.value = JsonData.data[j].id;
              memoButton.appendChild(document.createTextNode('メモ'));
              btnMemo.appendChild(memoButton);

              //削除ボタン
              var tdButton = document.createElement('td');
              tdButton.setAttribute('class', 'button');
              var deleteButton = document.createElement('button');
              deleteButton.setAttribute('id', 'deleteButton');
              deleteButton.value = JsonData.data[j].id;
              deleteButton.appendChild(document.createTextNode('削除'));
              tdButton.appendChild(deleteButton);

              //trに追加
              var tr = document.createElement('tr');
              tr.appendChild(dataId);
              tr.appendChild(dataTask);
              tr.appendChild(dataDeadLine);
              tr.appendChild(dataState);
              tr.appendChild(btnMemo);
              tr.appendChild(tdButton);

              document.getElementsByTagName('tbody')[0].appendChild(tr);
            }
          }
        }
        xmlhttp.open("GET", dataFileName);
        xmlhttp.send();
      }
    </script>
  </body>

  </html>
 ```

###javascript


 ```todo.js

//-----------------------------共通処理-----------------------------

//XHRのオブジェクト作成
var XMLHttpFactories = [
  function () {
    return new ActiveXObject("Msxml2.XMLHTTP")
  },
  function () {
    return new ActiveXObject("Microsoft.XMLHTTP")
  },
  function () {
    return new ActiveXObject("Msxml3.XMLHTTP")
  },
  function () {
    return new XMLHttpRequest()
  }
];

//XHRのオブジェクト作成
function createXMLHttpObject() {
  var xmlhttp = false;
  for (var i = 0; i < XMLHttpFactories.length; i++) {
    try {
      xmlhttp = XMLHttpFactories[i]();
    } catch (e) {
      continue;
    }
    break;
  }
  return xmlhttp;
}

//新規jsonファイル作成
function createJsonData() {
  var dirPath = location.pathname;

  //先頭文字が"/"だった場合の対策
  if (dirPath.substring(0, 1) == "/") {
    dirPath = dirPath.substring(1, dirPath.length);
  }

  dirPath = dirPath.substring(0, dirPath.lastIndexOf("/")) + "/";
  var filename = dirPath + dataFileName;

  var ua = navigator.userAgent.toLowerCase();
  try {

    //ieの場合
    if (ua.indexOf("ie")) {

      var fso = new ActiveXObject("Scripting.FileSystemObject");

      //ファイル存在チェック
      if (!(fso.FileExists(filename))) {

        //jsonデータの初期値
        var JsonData = '{"data":[],"dataInfo":[{"sortId":1}]}';

        //OpenTextFileの引数：対象のファイルパス、書き込み、新規作成、UTF-16 　
        var file = fso.OpenTextFile(filename, 2, true, -1);
        file.Write(JsonData);
        file.Close();
        location.reload();
      }
    } else {
      alert("IE以外対応していません");
    }
  } catch (e) {
    alert("Error: " + e);
  } finally { }
}

//ソート処理
function sortJsonData(Jsondata) {
  var data = Jsondata.data

  var sortValue = 1;
  sortValue = Jsondata.dataInfo[0].sortId;

  //期限：昇順、ID：昇順順に表示
  if ((sortValue === undefined || sortValue === null) || (sortValue == 1)) {
    data.sort(function (a, b) {
      if (a.deadLine < b.deadLine) return -1;
      if (a.deadLine > b.deadLine) return 1;
      if (a.id < b.id) return -1;
      if (a.id > b.id) return 1;
    })

    //期限：降順、ID：昇順順に表示
  } else if (sortValue == 2) {
    data.sort(function (a, b) {
      if (a.deadLine < b.deadLine) return 1;
      if (a.deadLine > b.deadLine) return -1;
      if (a.id < b.id) return -1;
      if (a.id > b.id) return 1;
    })

    //状態：作業中、ID：昇順順に表示
  } else if (sortValue == 3) {
    data.sort(function (a, b) {
      if (a.state < b.state) return -1;
      if (a.state > b.state) return 1;
      if (a.id < b.id) return -1;
      if (a.id > b.id) return 1;
    })

    //状態：完了、ID：昇順順に表示
  } else if (sortValue == 4) {
    data.sort(function (a, b) {
      if (a.state < b.state) return 1;
      if (a.state > b.state) return -1;
      if (a.id < b.id) return -1;
      if (a.id > b.id) return 1;
    })
  }

  Jsondata.data = data
  return Jsondata;
}

//jsonデータ取得
function getJsonData() {
  var JsonData = "";
  var xmlhttp = createXMLHttpObject();
  xmlhttp.onreadystatechange = function () {

    //ステータスが4の場合、jsonデータを取得
    if (xmlhttp.readyState == 4) {
      JsonData = JSON.parse(xmlhttp.responseText);
      JsonData = sortJsonData(JsonData);
    }
  }
  xmlhttp.open("GET", dataFileName);
  xmlhttp.send();

  return JsonData;
}

//jsonデータ変更
function writeJsonData(filename, content) {
  var dirPath = location.pathname;

  //先頭文字が"/"だった場合の対策
  if (dirPath.substring(0, 1) == "/") {
    dirPath = dirPath.substring(1, dirPath.length);
  }

  dirPath = dirPath.substring(0, dirPath.lastIndexOf("/")) + "/";
  filename = dirPath + filename;

  var ua = navigator.userAgent.toLowerCase();
  try {

    //ieの場合
    if (ua.indexOf("ie")) {
      var fso = new ActiveXObject("Scripting.FileSystemObject");

      //OpenTextFileの引数：対象のファイルパス、書き込み、新規作成、UTF-16 　
      var file = fso.OpenTextFile(filename, 2, true, -1);
      file.Write(content);
      file.Close();
    } else {
      alert("IE以外対応していません");
    }
  } catch (e) {
    alert("Error: " + e);
  } finally {
    location.reload();
  }
}

//-----------------------------ボタン処理-----------------------------

//ソート処理(期限）
$(function () {
  $(document).on("click", "#dieButton", function (e) {

    var JsonData = "";
    var sortValue = 1;
    JsonData = getJsonData();
    sortValue = JsonData.dataInfo[0].sortId;

    //1:昇順、2:降順
    if (sortValue == 1) {
      JsonData.dataInfo[0].sortId = 2;
    } else {
      JsonData.dataInfo[0].sortId = 1;
    }
    writeJsonData(dataFileName, (JSON.stringify(JsonData)));

  });
});

//ソート処理(ステータス）
$(function () {
  $(document).on("click", "#steButton", function (e) {

    var JsonData = "";
    var sortValue = 1;
    JsonData = getJsonData();
    sortValue = JsonData.dataInfo[0].sortId;

    //3:作業中、4:完了
    if (sortValue == 3) {
      JsonData.dataInfo[0].sortId = 4;
    } else {
      JsonData.dataInfo[0].sortId = 3;
    }
    writeJsonData(dataFileName, (JSON.stringify(JsonData)));
  });
});

//データ追加
function submitItem() {

  var FormTask = document.forms.inputForm_id.item.value;
  var FormCalendar = document.forms.inputForm_id.datepicker.value;

  if ((FormTask == "") || (FormTask == null)) {

    alert("タスク名を入力してください");
    return;
  }

  if ((FormCalendar == "") || (FormCalendar == null)) {

    alert("期限を入力してください");
    return;
  }

  var JsonData = "";
  var maxId = 0;
  JsonData = getJsonData();

  var numArray = new Array();
  for (var j = 0; j < JsonData.data.length; j++) {
    numArray.push(JsonData.data[j].id);
  }

  //Idは最大値+1を設定
  if (numArray.length >= 1) {
    maxId = (Math.max.apply(null, numArray)) + 1;
  } else {
    maxId = 1;
  }

  //データ作成
  var obj = new Object();
  obj.id = maxId;
  obj.task = FormTask;
  obj.deadLine = FormCalendar;
  obj.state = 1;
  obj.memo = "";
  JsonData["data"].push(obj);
  writeJsonData(dataFileName, (JSON.stringify(JsonData)));
}

//ステータス更新処理
$(function () {
  $(document).on("click", "#stateButton", function (e) {
    var id = $(this).val();

    var JsonData = "";
    JsonData = getJsonData();

    //取得したidに対応するステータス値を変更
    for (var j = 0; j < JsonData.data.length; j++) {
      if (JsonData.data[j].id == id) {
        if (JsonData.data[j].state == 1) {
          JsonData.data[j].state = 2;
        } else if (JsonData.data[j].state == 2) {
          JsonData.data[j].state = 1;
        }
        break;
      };
    }
    writeJsonData(dataFileName, (JSON.stringify(JsonData)));
  });
});

//メモ追加・編集
$(function () {
  $(document).on("click", "#memoButton", function (e) {
    var id = $(this).val();

    var JsonData = "";
    var i = -1;
    JsonData = getJsonData();

    //一致するjson配列のインデックスを取得
    i = JsonData.data.reduce(function (prev, item, index, array) {
      if (item.id == id) {
        prev.push(index);
      }
      return prev;
    }, []);

    //ダイヤログ入力完了後、jsonデータ更新
    $(function () {
      var editObj = new dataInfo(false);
      var dialog = dialogProcess(editObj);
      dialog.done(function () {
        if (editObj.getEditFlg()) {
          JsonData.data[i].memo = memoArea.innerText;
          writeJsonData(dataFileName, (JSON.stringify(JsonData)));
        }
      });
    });

    var dialogProcess = function (editObj) {
      var defer = $.Deferred();
      var memoArea = document.getElementById("memoArea_id");
      memoArea.innerText = JsonData.data[i].memo;

      //入力ダイヤログ
      $("#div_memoArea").dialog({
        dialogClass: 'memoAreaTitle',
        modal: true, //モーダル表示
        title: "追加・編集",
        width: 600, //ダイアログの横幅(px)
        height: 400, //ダイアログの縦幅(px)

        buttons: [{
          text: "確定",
          class: "maBtnOk",
          click: function () {
            editObj.setEditFlg(true);
            $(this).dialog("close");
            defer.resolve();
          }
        },
        {
          text: "キャンセル",
          class: "maBtnNg",
          click: function () {
            $(this).dialog("close");
            defer.resolve();
          }
        }
        ]
      });

      // プロミスを作って返す 
      return defer.promise();
    };
  });
});

//データ削除処理
$(function () {

  //削除対象のid取得
  $(document).on("click", "#deleteButton", function (e) {
    var id = $(this).val();

    var JsonData = "";
    var i = -1;
    JsonData = getJsonData();

    //一致するjson配列のインデックスを取得
    i = JsonData.data.reduce(function (prev, item, index, array) {
      if (item.id == id) {
        prev.push(index);
      }
      return prev;
    }, []);

    //jsonから対象データ削除処理
    JsonData.data.splice(i, 1);
    writeJsonData(dataFileName, (JSON.stringify(JsonData)));
  });
});
 ```

###json

 ```data.json
{"data":[{"id":7,"task":"TEST","deadLine":"2019/08/08","state":2,"memo":""},{"id":5,"task":"入力ダイヤログにcssを実装","deadLine":"2019/08/14","state":1,"memo":"\r\n参考サイト：\r\nhttp://teqspaces.com/jQuery/1\r\nhttps://codeday.me/jp/qa/20181223/87610.html"},{"id":6,"task":"修正する変数名(todo)","deadLine":"2019/08/14","state":1,"memo":"TEST"},{"id":8,"task":"TEST1","deadLine":"2019/08/27","state":1,"memo":""}],"dataInfo":[{"sortId":1}]}
 ```

###css

 ```todo.css
.taskTest {
  margin: 0 auto;
  width: 650.16px;
}

.taskTest input[type="text"] {
  font: 15px/24px sans-serif;
  box-sizing: border-box;
  width: 650.16px;
  padding: 0.3em;
  transition: 0.3s;
  letter-spacing: 1px;
  border: 1px solid #1b2538;
  border-radius: 4px;
  margin: 3px;
}

.calendar {
  margin: 0 auto;
  width: 650.16px;
}

.calendar input[type="text"] {
  font: 15px/24px sans-serif;
  box-sizing: border-box;
  width: 150.16px;
  padding: 0.3em;
  transition: 0.3s;
  letter-spacing: 1px;
  border: 1px solid #1b2538;
  border-radius: 4px;
  margin: 3px;
}

tbody button {
  border: none;
  border-radius: 20px;
  line-height: 24px;
  padding: 0 8px;
  background: #93ffab;
  color: #000000;
  cursor: pointer;
  width: 70px;
  height: 35px;
}

.addButton button {
  border: none;
  border-radius: 20px;
  line-height: 24px;
  padding: 0 8px;
  background: #93ffab;
  color: #000000;
  cursor: pointer;
  width: 70px;
  height: 35px;
}

.addButton {
  position: absolute;
  margin-top: -70px;
  left: 50%;
  width: 500px;
  margin-left: 340px;
}

thead th button {
  background-color: transparent;
  border: none;
  cursor: pointer;
  outline: none;
  padding: 0;
  appearance: none;
  color: #0099e4;
}

.memoAreaTitle .ui-dialog-titlebar {
  background: #93ffab;
  color: #000000;
}

.memoAreaTitle .maBtnOk {
  background: #93ffab;
  color: #000000;
}

.memoAreaTitle .maBtnNg {
  background: #93ffab;
  color: #000000;
}

/*Elementに対してのCSS*/

h1 {
  background: #c2edff;
  padding: 0.5em;
}

* {
  box-sizing: border-box;
}
#app {
  max-width: 640px;
  margin: 0 auto;
}
table {
  width: 100%;
  border-collapse: collapse;
}
thead th {
  border-bottom: 2px solid #0099e4;
  color: #0099e4;
}

th,
th {
  padding: 0 8px;
  line-height: 40px;
}
thead th.task {
  width: 400px;
}
thead th.state {
  width: 100px;
}
thead th.button {
  width: 60px;
}
tbody td.button,
tbody td.state {
  text-align: center;
}
tbody tr td,
tbody tr th {
  border-bottom: 1px solid #ccc;
  transition: all 0.4s;
}
tbody tr.done td,
tbody tr.done th {
  background: #93ffab;
  color: #bbb;
}
tbody tr:hover td,
tbody tr:hover th {
  background: #f4fbff;
}

 ```


###フォルダ構成
todo
|-- data.json
|-- todo.css
|-- todo.html
|-- todo.js

##振り返り
###jquery ui 活用
jquery uiのカレンダーや入力ダイヤログを初めて使用したが、すごく
簡単に追加できてびっくり!　気になった人は参考のURLを張っているので
ググってみてください。
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487273/3062b24a-599f-9d72-a471-881fdfdc1991.png)
<b>UIのカスタマイズも可能</b>
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487273/6e2b7724-a65c-30a0-205f-fc3e75075837.png)

[【jQuery入門】Datepicker](https://www.sejuku.net/blog/44165)
[【jQuery入門】オリジナルのダイアログ](https://webllica.com/jquery-ui-dialog/)

###感想
ローカルファイルに対して読み書きを行う方法を調べるのに特に時間が
かかりました。ブラウザに依存するjavascriptでファイルの読み書きをさせる
ことに無理があったかな(笑)
それなりに楽しかったので、また投稿したいと思います。

###参考

[ToDoリストを作りながら学習しよう！](https://cr-vue.mio3io.com/tutorials/todo.html
)