7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptを復習するため、高級FWのサンプルにありがちな簡易ToDoアプリを手作りしてみた

Posted at

はじめに

だいぶ前にBackbone.jsのオライリー本でTODOのサンプルがあって写経して習得を試みたんですが今それをやろうとして全く思い出せなくなってしまっています。Angular.jsも同様。で、まあ大規模なことはやらないからフツーのJavaScriptでやろうと思ってほぼ同じ動きをするものを書いてみたくなりました。JavaScript自体も久しぶりに触っているのでけっこう大変でした。

こんな感じのもの

todo.png

ポイント

個人的にはタスクの定義の仕方が重要だと思いました。var tasks = [];とやりがちですが、タスクが完了済みか否かという状態を持たせるためにあえて2次元配列とし、各配列の第2要素のデフォルトを0とし、完了したら1にする、という具合にしました(連想配列がいいかなと思ったんですが書き方忘れちゃいました)。

これをしないとチェックしたあとにタスクを追加するとチェックが全部消えてしまうので、チェック状態を保持する工夫が必要でそこについてちょっとハマりました(状態保持ということに関しては本来はクロージャーっぽいことをして実現すべきなんでしょうけどとっくに記憶の彼方に消えてしまった)。

課題など

いろんな書き方があって本来はグローバル変数使うとかまずそうだし、オブジェクト作って・・とかやるんでしょう。あとバリデーションなどはほとんどやってません。エスケープ処理も(こうゆうところはFWだと楽できるのかな)。

変数tasksに関しては、グローバル宣言しつつ、関数内ではvarを付していないので、関数実行のたびにナチュラルにグローバル変数としてのtasksがいい具合に更新されているということなんですかね。正直、よく分かっていません。

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Backbone.js</title>
<script type="text/javascript">

/* タスクを2次元配列前提で定義 */
var tasks = [[],[]];

/* タスクを表示する */
function showTasks() {
  var out = '<ul>';
  for (var i in tasks) {
    /* 未完了タスク */
    if (tasks[i][1] == 0) {
      out += '<li>';
      out += '<input type="checkbox" compid="'+i+'" onclick="compTask(this);">';
      out += '<span>'+tasks[i][0]+'</span>';
      out += '<a href="#"  delid="'+i+'" onclick="delTask(this);" style="text-decoration:none;">[x]</a>';
      out += '</li>';
    /* 完了済みタスクならチェック済みにして取り消し線を付ける */
    } else if (tasks[i][1] == 1) {
      out += '<li>';
      out += '<input type="checkbox" compid="'+i+'" onclick="compTask(this);" checked>';
      out += '<span style="text-decoration:line-through;">'+tasks[i][0]+'</span>';
      out += '<a href="#"  delid="'+i+'" onclick="delTask(this);" style="text-decoration:none;">[x]</a>';
      out += '</li>';
    }
  }
  out += '</ul>';
  document.getElementById("tasks").innerHTML = out;
  /* 残余タスク数 */
  countTasks();
}

/* タスクを追加する */
function addTask() {
  var newTask = document.getElementById("title").value;
  if (newTask == '') {
    error();
    /* 処理終了 */
    return;
  }
  document.getElementById("error").innerText = '';
  tasks.push([[newTask],[0]]);
  // タスク表示へ
  showTasks();
}

/* タスクを実行済みにする */
function compTask(e) {
  var cid = e.getAttribute("compid");
  // チェックしたら完了済みフラグ「1」を配列第2要素に付与する
  if (e.checked == true) {
    e.nextSibling.style.textDecoration = 'line-through';
    tasks[cid][1] = 1;
    countTasks();
  // チェックを外したらその逆をやる
  } else if (e.checked == false) {
    e.nextSibling.style.textDecoration = 'none';
    tasks[cid][1] = 0;
    countTasks();
  }
}

/* タスクを削除する */
function delTask(e) {
  if (window.confirm("本当に削除しますか?")) {;
    var delid = e.getAttribute("delid");
    tasks.splice(delid, 1);
    showTasks();
  } else {
    return;
  }
}

/* 残りタスクを数える */
function countTasks() {
  var count = 2; // これは最初tasks定義時点で2個カウントされてしまうための調整
  for (var i in tasks) {
    // 完了済みタスクを数える
    if (tasks[i][1] == 1) {
      count++;
    }
  }
  // 総タスク数から完了済みタスク引いて残余タスク数を求める
  document.getElementById("count").innerText = tasks.length - count;
}

/* エラーメッセージ表示 */
function error() {
  document.getElementById("error").innerText = '未入力です。';
}
</script>
</head>
<body>
<h1>Tasks</h1>
<input type="text" id="title">
<input type="button" onclick="addTask();" value="add">
<span id="error"></span>
<div id="tasks">
</div>
<p>Tasks left: <span id="count"></span></p>
</body>
</html>

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?