TODOアプリの作成の準備
- 下記の構造でファイルを作成
TODO
├── index.html
├── index.js
└── style.css
HTMLで構造を作成する(マークアップ)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODOアプリ</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div>
<input placeholder="TODOを入力">
<button>追加</button>
</div>
<div>
<p>予定のTODO</p>
<ul>
<li>
<div>
<p>勉強会</p>
<button>完了</button>
<button>削除</button>
</div>
</li>
</ul>
</div>
<div>
<p>完了したTODO</p>
<ul>
<li>
<div>
<p>飲み会</p>
<button>戻す</button>
</div>
</li>
</ul>
</div>
<script src="./index.js"></script>
</body>
</html>
CSSでスタイリング
- まずは
index.htmlにclassを付与
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODOアプリ</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="input-area">
<input placeholder="TODOを入力">
<button>追加</button>
</div>
<div class="incomplete-area">
<p class="title">予定のTODO</p>
<ul>
<li>
<div class="list-row">
<p class="todo-item">勉強会</p>
<button>完了</button>
<button>削除</button>
</div>
</li>
<li>
<div class="list-row">
<p class="todo-item">勉強会</p>
<button>完了</button>
<button>削除</button>
</div>
</li>
</ul>
</div>
<div class="complete-area">
<p class="title">完了したTODO</p>
<ul>
<li>
<div class="list-row">
<p class="todo-item">飲み会</p>
<button>戻す</button>
</div>
</li>
<li>
<div class="list-row">
<p class="todo-item">飲み会</p>
<button>戻す</button>
</div>
</li>
</ul>
</div>
<script src="./index.js"></script>
</body>
</html>
-
styles.cssを編集
body {
font-family: sans-serif;
color: #666;
}
input {
border-radius: 8px;
border: none;
padding: 6px 16px;
}
button {
border-radius: 8px;
border: none;
padding: 4px 16px;
margin: 0px 2px;
}
button:hover {
background-color: #79a8a9;
color: #fff;
cursor: pointer;
}
.input-area {
background-color: #c6e5d9;
width: 400px;
height: 30px;
padding: 8px;
margin: 8px;
border-radius: 8px;
}
.incomplete-area {
border: 2px solid #aacfd0;
width: 400px;
min-height: 200px;
padding: 8px;
margin: 8px;
border-radius: 8px;
}
.complete-area {
border: 2px solid #aacfd0;
width: 400px;
min-height: 200px;
padding: 8px;
margin: 8px;
border-radius: 8px;
background-color: #c9dede;
}
.title {
text-align: center;
margin-top: 0;
font-weight: bold;
}
.list-row {
display: flex;
align-items: center;
}
.todo-item {
margin: 6px;
}
タスクの追加機能を実装していく
index.htmlを編集
-
追加ボタンにidを付与 (id="add-button")
<!-- 省略 -->
<body>
<div class="input-area">
<input placeholder="TODOを入力">
<button id="add-button">追加</button>
</div>
<!-- 省略 -->
</body>
<!-- 省略 -->
index.jsを編集
-
id="add-button"のある追加ボタンをクリックしたらアラートがでるようにする。(onClickAdd関数が動作するかどうかの確認)
'use strict';
const onClickAdd = () => {
alert();
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
追加ボタンをクリックしてアラートがでるか試してみる。
-
TODOを入力して
追加ボタンをクリックするとアラートに追加したTODOが表示されるか試してみる(onClickAdd関数の実行時)。 -
class="input-area"のinputにid="add-text"を付与する。
index.htmlを編集
<!-- 省略 -->
<body>
<div class="input-area">
<input id="add-text" placeholder="TODOを入力">
<button id="add-button">追加</button>
</div>
<!-- 省略 -->
</body>
<!-- 省略 -->
index.jsを編集
'use strict';
const onClickAdd = () => {
// 入力したTODOを const inputText に格納
const inputText = document.getElementById('add-text').value;
alert(inputText);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
何か入力して 追加ボタン押下時にアラートテキストが出力されるか確認。
- 追加後は テキストボックスにテキストが残らないようにするため、
valueを空にする(初期化)。
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
// 追加した後にテキストボックスを空白にして初期化する
document.getElementById('add-text').value = '';
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
-
index.jsにliタグを生成する。
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
// liタグの生成
const li = document.createElement("li");
console.log(li); // TODO追加後に生成されているかどうか確認
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
TODO追加後はコンソールを確認
<li></li>
- 続いて
divタグの生成
index.jsに追加
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
// divタグの生成
const div = document.createElement("div");
console.log(div);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
同じくコンソールを確認
<div></div>
-
divタグにclass="list-row"を付ける
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row"; // divにclass="list-row"を付ける
console.log(div);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
<div class="list-row"></div>
-
pタグを生成 -
pタグにはtodo-itemクラスを付ける -
pタグには追加されたTODOが入るようにする(定数inputTextに追加するTODOが入る)
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
// pタグの生成
const p = document.createElement("p");
// pタグにclass="todo-item"を付ける
p.className = "todo-item";
// pタグに追加されたTODOを入れる
p.innerText = inputText;
console.log(p);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
<p class="todo-item">hoge</p>
-
<div class="list-row">の子要素に<p class="todo-item"></p>を配置する。
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
// divタグの子要素にpタグを配置
div.appendChild(p);
console.log(div);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
<div class="list-row">
<p class="todo-item">hoge</p>
</div>
-
liタグの子要素に<div class="list-row>を配置する
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
div.appendChild(p);
// liタグの子要素にdivを配置
li.appendChild(div);
console.log(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
<li>
<div class="list-row">
<p class="todo-item">hoge</p>
</div>
</li>
-
incomplete-area内のulタグにid="incomplete-list"を付与する
index.htmlを編集
<!-- 省略 -->
<div class="incomplete-area">
<p class="title">予定のTODO</p>
<ul id="incomplete-list"> <!-- id="incomplete-list"の付与 -->
<li>
<div class="list-row">
<p class="todo-item">勉強会</p>
<button>完了</button>
<button>削除</button>
</div>
</li>
<li>
<div class="list-row">
<p class="todo-item">勉強会</p>
<button>完了</button>
<button>削除</button>
</div>
</li>
</ul>
</div>
<!-- 以下省略 -->
-
<ul id="incomlete-list">の子要素にliタグを配置する(未完了リストに追加)。
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
div.appendChild(p);
li.appendChild(div);
// idがincomlete-listの子要素にliタグを入れる(未完了リストに追加)
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
これで、ブラウザに追加したTODOが表示される。
TODOの完了ボタンと削除ボタンの生成
- 完了ボタンの生成
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
// button(完了)ボタンの生成
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
console.log(completeButton);
div.appendChild(p);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
<button>完了</button>
- 削除ボタンの生成
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
// button(削除)ボタンの生成
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
console.log(deleteButton);
div.appendChild(p);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
<button>削除</button>
-
divタグ(incomplete-list内) の子要素に完了ボタンと削除ボタンを配置する。
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
// 順番通りに。。。
div.appendChild(p);
div.appendChild(completeButton); // 完了ボタンをdivの子要素に配置
div.appendChild(deleteButton); // 削除ボタンをdivの子要素に配置
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
これで何かTodo追加時に完了ボタンと削除ボタンがブラウザで表示される。
TODOの削除機能を作成
-
削除ボタンと完了ボタンにaddEventListenerの適用
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
// addEventListenerの適用
completeButton.addEventListener('click', () => {
alert("完了"); // 完了ボタン押下時の処理
});
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener('click', () => {
alert("削除"); // 削除ボタンを押下時の処理
});
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
完了ボタン及び削除ボタンをクリックするとアラートがでる(テスト)。
- TODOを削除する際は、
liタグから削除されるようにするclosestを使用すると良い。
(削除ボタンの親にあるliタグから拾う)
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
alert("完了");
});
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener('click', () => {
// 削除ボタンの親にあるliから要素を拾う
// deleteTargetには削除する要素が入っている
const deleteTarget = deleteButton.closest("li");
console.log(deleteTarget);
});
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
下記が削除したい要素になる
<li>
<div class="list-row">
<p class="todo-item">hoge</p>
<button>完了</button>
</div>
</li>
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
alert("完了");
});
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener('click', () => {
const deleteTarget = deleteButton.closest("li");
// deleteTargetは<ul id="incomplete-list">の子要素になるため removeChildメソッドにdeleteTargetを指定して上げればよい
document.getElementById('incomplete-list').removeChild(deleteTarget);
});
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
これでTODOは削除可能になる。
TODOの完了機能を作成
-
完了ボタン押下時のプロセス
-
完了ボタン押下するとcomplete-areaの方へ移動するようになるので、その要素を選択するようにする。 -
削除ボタンが削除される
(nextElementSiblingメソッドを使い完了ボタン要素の次に続く兄弟要素ノードを選択できる。つまり、ボタンの直後に存在するHTML要素を取得して、それをremoveメソッドで削除ボタンを削除できる) -
完了ボタンが削除される
(単純にcompleteButtonをremoveメソッドを使って削除すればよい)
完了ボタンを押下時に削除ボタンが消えるようにする
index.jsを編集
// 省略
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
// completeエリアに移動する要素を選択
const moveTarget = completeButton.closest("li");
// 完了ボタンの兄弟要素の削除ボタンを選択し、削除する
completeButton.nextElementSibling.remove();
// 完了ボタンを削除
completeButton.remove();
});
// 以下省略
これで完了ボタンを押下すると削除ボタンと完了ボタンは削除される。
完了ボタン押下後に戻すボタンが追加されるようにする
-
moveTargetの要素の初めの子要素(div)の子要素に戻すボタンを追加するようにするにはfirstElementChildメソッドとappendChildメソッドを繋げるようにする。
index.jsを編集
// 省略
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
const moveTarget = completeButton.closest("li");
completeButton.nextElementSibling.remove();
completeButton.remove();
// ボタン要素の作成
const backButton = document.createElement("button");
// ボタン要素にテキスト '戻す'を設定
backButton.innerText = "戻す";
// moveTarget(li要素)の子要素(div)のさらに子要素に'戻す'ボタンを追加
moveTarget.firstElementChild.appendChild(backButton);
});
// 以下省略
これで完了ボタンを押下すると 戻すボタンが追加される
完了したTODOエリア(complete-area)に移動する
-
htmlのcomplete-areaのulタグにid="complete-list"を付与する。 -
moveTargetを<ul id="complete-list">の子要素に配置する。
<!-- 省略 -->
<div class="complete-area">
<p class="title">完了したTODO</p>
<ul id="complete-list"> <!-- id="complete-list"を付与 -->
<li>
<div class="list-row">
<p class="todo-item">飲み会</p>
<button>戻す</button>
</div>
</li>
<li>
<div class="list-row">
<p class="todo-item">飲み会</p>
<button>戻す</button>
</div>
</li>
</ul>
</div>
<!-- 以下省略 -->
-
moveTargetをid="complete-list"の子要素に移動
index.jsを編集
// 省略
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
const moveTarget = completeButton.closest("li");
completeButton.nextElementSibling.remove();
completeButton.remove();
const backButton = document.createElement("button");
backButton.innerText = "戻す";
moveTarget.firstElementChild.appendChild(backButton);
// complete-listの子要素に moveTargetを移動
document.getElementById("complete-list").appendChild(moveTarget);
});
// 以下省略
これで 完了ボタンを押下すると完了したTODOのリストに移動する
完了したTODOを予定のTODOへ戻す機能の実装
-
createIncompleteTodo関数を作成して渡された引数を基に未完了のTODOを作成する機能を実装します。 -
下記の
index.jsの 下記のコードの部分をcreateImcompleteTodo関数の中に移動します。 -
onClickAdd関数の中の 定数inputTextをcreateIncompleteTodoに送るようにしなくてはならないので、onClickAdd関数の中にcreateIncompleteTodo関数を実行させる記述が必要になります。
// 下記の部分のコードをcreateImcompleteTodo関数を作成して、その中に移動する
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = inputText;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
const moveTarget = completeButton.closest("li");
completeButton.nextElementSibling.remove();
completeButton.remove();
const backButton = document.createElement("button");
backButton.innerText = "戻す";
moveTarget.firstElementChild.appendChild(backButton);
document.getElementById("complete-list").appendChild(moveTarget);
});
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener('click', () => {
const deleteTarget = deleteButton.closest("li");
document.getElementById('incomplete-list').removeChild(deleteTarget);
});
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
// inputTextをcreateIncompleteTodo関数に送る
createIncompleteTodo(inputText);
}
// 渡された引数を基に未完了のTODOを作成する関数
const createIncompleteTodo = (todo) => {
// ここから移動してきたコード
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
// inputTextはtodoの引数に入ってくるので todo に変更する
p.innerText = todo;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
const moveTarget = completeButton.closest("li");
completeButton.nextElementSibling.remove();
completeButton.remove();
const backButton = document.createElement("button");
backButton.innerText = "戻す";
moveTarget.firstElementChild.appendChild(backButton);
document.getElementById("complete-list").appendChild(moveTarget);
});
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener('click', () => {
const deleteTarget = deleteButton.closest("li");
document.getElementById('incomplete-list').removeChild(deleteTarget);
});
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
// ここまで
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
-
戻すボタンをクリックした時のaddEventListenerを追加します。
index.jsを編集
'use strict';
const onClickAdd = () => {
const inputText = document.getElementById('add-text').value;
document.getElementById('add-text').value = '';
createIncompleteTodo(inputText);
}
const createIncompleteTodo = (todo) => {
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = todo;
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener('click', () => {
const moveTarget = completeButton.closest("li");
completeButton.nextElementSibling.remove();
completeButton.remove();
const backButton = document.createElement("button");
backButton.innerText = "戻す";
// 戻すボタンのイベントリスナーを追加
backButton.addEventListener("click", () => {
alert();
});
moveTarget.firstElementChild.appendChild(backButton);
document.getElementById("complete-list").appendChild(moveTarget);
});
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener('click', () => {
const deleteTarget = deleteButton.closest("li");
document.getElementById('incomplete-list').removeChild(deleteTarget);
});
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
document.getElementById("incomplete-list").appendChild(li);
}
document.getElementById('add-button').addEventListener('click', onClickAdd);
-
戻すボタンのイベントリスナーの中の処理内容
-
previousElementSiblingメソッドを使ってbuckButtonの一個手前に要素(pタグになる)を参照してinnerTextメソッドでテキストだけを取得する。 -
createIncompleteTodo関数の引数にそのテキストを入れるようにして実行させる
(予定のTODOのリストに追加される) - まだ
戻すボタンを押下しても完了したTODOに残ってるのでそれを削除するようにする
index.jsを編集
// 省略
backButton.addEventListener("click", () => {
// backbutton.previousElementSiblingはbuckButtonの一個手前に要素(pタグになる)を参照してinnerTextでテキストだけを取得する
const todoText = backButton.previousElementSibling.innerText;
// createIncompleteTodoの引数にtodoTextを入れて実行
createIncompleteTodo(todoText);
// 完了したTODOに戻したTODOが残ってしまうので削除する
backButton.closest("li").remove();
});
// 以下省略
以上で 戻す機能の実装は終了です。
後は以下のように index.htmlの li要素ノードを消しておけばOKです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODOアプリ</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="input-area">
<input id="add-text" placeholder="TODOを入力">
<button id="add-button">追加</button>
</div>
<div class="incomplete-area">
<p class="title">予定のTODO</p>
<ul id="incomplete-list">
</ul>
</div>
<div class="complete-area">
<p class="title">完了したTODO</p>
<ul id="complete-list">
</ul>
</div>
<script src="./index.js"></script>
</body>
</html>
JavaScriptだけでこのようなアプリを作成するとなるとこんなに複雑になるのですね。
仕組みはよく理解できました。