Created: 2025/08/16
先日、サークル内で「Webハッカソン」を開催しました。
サークル内でハッカソンをするのは初だったので、結構ぐだった印象でしたが、なんとか...
(シンプルに準備に時間が取れなかったという言い訳...🤫)
参加してくれたのは、主に1年生で、大学からプログラミングを始めたメンバーでした。
初心者向けというのもあり、流石に、3日間で完成まではいきませんでした🥲
そこで、基本的なcrud処理を網羅したTODOアプリを、この記事でまとめようと思います!
今回のTODOアプリは、ブラウザ上だけで完結する使用です。
ゆくゆくは、バックエンドを取り入れた、本格的なWebアプリを目指しています😤
そのため、DBっぽいデータ保管をするために、ブラウザのsessionStorageを無理くり利用しました。
sessionStorageをあまり理解せず利用したので、結構手こずりました笑
とてもシンプルです。
Githubにもコード載せてるので、よかったら
まずは、HTMLから作成しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="todo.css">
<title>TODOアプリ</title>
<script src="todo.js"></script>
</head>
<body>
<h1 id="title">俺のTODOアプリ</h1>
<p>これは、<strong>俺のTODOアプリだ</strong>、いっぱいTODOを入れろ!</p>
<label>タスクを入力</label>
<input type="text" id="input-task">
<button id="save-button">保存</button>
<h2>タスク一覧</h2>
<ul id="task-list"></ul>
</body>
</html>
こんな感じです。
todo.html
は、ここから変わりません。
特にこだわりはありません笑
form
タグ使わずにlabel
タグ使ってますが、見なかったことにしてください...🫥
まず、取り掛かるのはcrudのc、createの部分ですね。
createはこんな感じです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
document.addEventListener('DOMContentLoaded', () => {
const save_button = document.getElementById('save-button');
const task_list = document.getElementById('task-list');
// ボタンがクリックされた時、タスクの保存
save_button.addEventListener('click', () => {
const input_task = document.getElementById('input-task');
// <li>タグを作成
const task = document.createElement('li');
// <li>タグに入力されたテキストデータを挿入
task.textContent = input_task.value
// <ul>タグに、作成した<li>を入れ込む
task_list.append(task);
// 入力エリアの初期化
input_task.value = "";
});
});
作成したタスクを<ul id="task-list"></ul>
を使ってリスト表示するところまで作りました。
なので、createとreadを作った感じですかね。
DBの操作とかしてないので、曖昧ですが...
これで、見た目上タスクが追加され、タスクの一覧が表示されるようになりました!
1日目はここまででした。
2日目は、内定先のインターンのため参加できませんでした。
なので、1日目の帰宅後にcrudのd、deleteを作っておきました。
deleteを作るにあたり、sessionStorageを使って、作成したタスクをDBっぽく保存します。
(sessionStorageは後々、編集機能やリロード時の一覧表示に重宝します)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
document.addEventListener('DOMContentLoaded', () => {
const save_button = document.getElementById('save-button');
const task_list = document.getElementById('task-list');
//タスクの管理用(セッションストレージのキーとして利用)
let task_count = 1;
//タスクの保存
save_button.addEventListener('click', () => {
const input_task = document.getElementById('input-task');
const task = document.createElement('li');
//ブラウザのセッションストレージに保存
sessionStorage.setItem(task_count, input_task.value);
task.textContent = input_task.value
task_list.append(task);
input_task.value = "";
task_count++;
});
});
ブラウザのsessionStorageとは、ブラウザ上で一時的にデータを保持する際に利用される機能です。
今回は、これをDBの代わりに使う作戦です。(初見)
sessionStorageのデータは、開発者ツールで確認できます。
画像の用意に、sessionStorageは[キー:値]
のような辞書型のデータ構造しか対応していません。
なので、このsessionStorageをこねくり回してTODOアプリを作っていきます。
(ちなみに、ローカルストレージが気になる人はこの辺をチェック)
続いて、削除機能を作っていく。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
document.addEventListener('DOMContentLoaded', () => {
const save_button = document.getElementById('save-button');
const task_list = document.getElementById('task-list');
//タスクの管理用(変数名変更)
let task_id = 1;
//タスクの保存
save_button.addEventListener('click', () => {
const input_task = document.getElementById('input-task');
const task = document.createElement('li');
task.setAttribute('id', `task-${task_id}`);
//削除ボタン
const delete_button = document.createElement('p');
delete_button.setAttribute('id', task_id);
delete_button.setAttribute('class', "delete");
delete_button.textContent = "削除";
//削除機能
delete_button.addEventListener('click', (event) => {
//クリックされた要素のidを取得
const delete_task_id = event.currentTarget.id;
//セッションに保存したデータの削除
sessionStorage.removeItem(delete_task_id);
//選択したタスクの要素を削除
document.getElementById(`task-${delete_task_id}`).remove();
event.currentTarget.remove();
});
//ブラウザのセッションストレージに保存
sessionStorage.setItem(task_id, input_task.value);
task.textContent = input_task.value;
task_list.append(task);
task_list.append(delete_button);
input_task.value = "";
task_id++;
});
});
削除機能は、タスクが作成された時点で作りました。
削除ボタンをpタグて作っているのは、なんとなく、cssでボタンっぽくできるし、クリックできたらなんでも良いかなと思って...(結局、最終的にbuttonにしました😗)
正直、ここまではsessionStorageを使っていません笑
ただデータを保存しているだけで、sessionStorageを使って一覧を表示しているわけではないので...
次の編集機能やページのリロード時に活用していくので、この時点でsessionStorageを利用しました。
2日目はここまででした。
3日目は、crudのu、updateを作りました。
編集機能ですね。
編集機能は、edit_todo.html
を作成し、新たに編集ページを作ることにしました。
最初は、todo.html
内で作ることを考えましたが、todo.js
が複雑になりそう?
あと、せっかくなのでsessionStorageを有効活用するためにページを分けました。
sessionStorageはページリロードされてもデータを保持できるので、相性は良いと思いました。
まずは、edit_todo.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="edit.js"></script>
<title>TODOの編集</title>
</head>
<body>
<h1>編集ページ</h1>
<input type="text" id="edit-task">
<button type="submit" id="edit-button">変更を保存</button>
</body>
</html>
まぁ、シンプルです。
あとは、edit_todo.html
をロードする際に、選択したタスクのキーを取得できれば、編集できそう
ということで、todo.js
に編集機能を追加します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
document.addEventListener('DOMContentLoaded', () => {
const save_button = document.getElementById('save-button');
const task_list = document.getElementById('task-list');
//タスクの管理用
let task_id = 1;
//タスクの保存
save_button.addEventListener('click', () => {
const input_task = document.getElementById('input-task');
const task = document.createElement('li');
task.setAttribute('id', `task-${task_id}`);
//編集ボタン
const edit_button = document.createElement('p');
edit_button.setAttribute('id', `edit-${task_id}`);
edit_button.setAttribute('class', "edit");
edit_button.textContent = "編集";
//編集機能
edit_button.addEventListener('click', (event) => {
const edit_task_id = event.currentTarget.id;
// クリックしたタスクidを取得して
// 新たに['edit_task_id': task_id]というデータを追加
sessionStorage.setItem('edit_task_id', edit_task_id[5]);
// edit_todo.htmlをロード(遷移)
window.location.href = 'edit_todo.html';
});
// 削除ボタン
割愛
// 削除機能
割愛
//ブラウザのセッションストレージに保存
sessionStorage.setItem(task_id, input_task.value);
task.textContent = input_task.value;
task_list.append(task);
task_list.append(edit_button);
task_list.append(delete_button);
input_task.value = "";
task_id++;
});
});
編集機能はこんな感じです。
工夫点は、sessionStorage.setItem('edit_task_id', edit_task_id[5]);
ですね。
新たに、edit_task_id
をキーに持つデータをsessionStorageに追加することで、edit_todo.html
に遷移後に、編集するタスクを取得できます。
そして、edit.js
を作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
document.addEventListener('DOMContentLoaded', () => {
const edit_task = document.getElementById('edit-task');
const edit_button = document.getElementById('edit-button');
// 編集するタスクIDの取得
const task_id = sessionStorage.getItem('edit_task_id');
// 編集するタスクの値を取得
const current_task = sessionStorage.getItem(task_id);
// 編集前のタスクを入力エリアに表示
edit_task.value = current_task;
edit_button.addEventListener('click', () => {
const input_task = edit_task.value;
// タスクの上書き
sessionStorage.setItem(task_id, input_task);
// edit_task_idは編集完了後に必要ないため削除
sessionStorage.removeItem('edit_task_id');
// todo.htmlに戻る
window.location.href = 'todo.html';
});
});
`edit.jsはこんな感じです。
ページを分けたので、jsがかなりシンプルで、すっきりしたと思います。
別のページでも、データを扱えるのは便利ですね。
ここで問題が発生します。
ブラウザでsessionStorageを確認すると、データの更新はうまく行っています。
なので、編集機能はできています。
しかし、window.location.href = 'todo.html';
で、戻るとタスクの一覧が消えています。
これは、todo.html
をロードしているためです。
todo.html
を確認すると
1
2
3
4
...
<h2>タスク一覧</h2>
<ul id="task-list"></ul>
...
タスクの一覧を表示する<ul id="task-list"></ul>
部分が空の状態です。
なので、ロードすると、<ul id="task-list"></ul>
部分が空の状態でロードされ、今まで作成したタスクが消えるのです。
なので、todo.html
のロード時に、すでにタスクがあれば、そのタスクを表示する機能を作る必要があります。
これが、crudのr、readの部分になります。
タスク一覧の読み込みですね。
それでは、todo.js
に下記の関数を用意しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function indexTaskList(task_id_list) {
const task_list = document.getElementById('task-list');
for( let i=0; i<task_id_list.length; i++){
//タスク
const task = document.createElement('li');
task.setAttribute('id', task_id_list[i]);
task.textContent = sessionStorage.getItem(task_id_list[i]);
//編集ボタン
const edit_button = document.createElement('p');
edit_button.setAttribute('id', `edit-${task_id_list[i]}`);
edit_button.setAttribute('class', "edit");
edit_button.textContent = "編集";
//削除ボタン
const delete_button = document.createElement('p');
delete_button.setAttribute('id', `delete-${task_id_list[i]}`);
delete_button.setAttribute('class', "delete");
delete_button.textContent = "削除";
//編集機能
edit_button.addEventListener('click', (event) => {
const edit_task_id = event.currentTarget.id;
sessionStorage.setItem('edit_task_id', edit_task_id[5]);
window.location.href = 'edit_todo.html';
});
//削除機能
delete_button.addEventListener('click', (event) => {
//クリックされた要素のidを取得
const delete_task_id = event.currentTarget.id;
//保存済みタスク
let task_id_list = sessionStorage.getItem('task_id_list');
sessionStorage.setItem('task_id_list', task_id_list.replace(task_id_list[i], ""));
//セッションに保存したデータの削除
sessionStorage.removeItem(task_id_list[i]);
//選択したタスクの要素を削除
document.getElementById(delete_task_id[7]).remove();
document.getElementById(`edit-${delete_task_id[7]}`).remove();
event.currentTarget.remove();
});
task_list.append(task);
task_list.append(edit_button);
task_list.append(delete_button);
}
}
この関数をtodo.html
のロード時に実行すれば、sessionStorageにあるタスクを表示できるははずです。
そこで、sessionStorageにtask_id_list
というキーを持つデータを作成します。
このtask_id_list
に、保存されているtask_id
を全て保存します。
さらに、todo.js
に追加していく。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
document.addEventListener('DOMContentLoaded', () => {
const save_button = document.getElementById('save-button');
const task_list = document.getElementById('task-list');
//タスクの管理用
let task_id = 1;
// 初回ロード時
if (sessionStorage.getItem('task_id_list') === null) {
sessionStorage.setItem('task_id_list', '');
// 2回目以降のロード時
} else {
let current_task_id_list = sessionStorage.getItem('task_id_list');
indexTaskList(current_task_id_list);
}
//タスクの保存
save_button.addEventListener('click', () => {
// 現在のタスク一覧を取得
let current_task_id_list = sessionStorage.getItem('task_id_list');
...
// 追加するtask_idをlistに追加(文字列として扱っている)
current_task_id_list = current_task_id_list + task_id;
//ブラウザのセッションストレージに保存
sessionStorage.setItem(task_id, input_task.value);
sessionStorage.setItem('task_id_list', current_task_id_list);
task_id++;
});
});
メインの処理はこんな感じです。
これで、画像の様に、task_id_list
を使って、リロードしてもタスクの一覧が表示できる様になりました。
あとは、削除するときに、task_id_list
から、削除したtask_id
を取り除く処理を書けば良い
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//削除機能
delete_button.addEventListener('click', (event) => {
//クリックされた要素のidを取得
const delete_task_id = event.currentTarget.id;
//保存済みタスク
let task_id_list = sessionStorage.getItem('task_id_list');
// 削除したidをtask_id_listから取り除く
sessionStorage.setItem('task_id_list', task_id_list.replace(delete_task_id[7], ""));
//セッションに保存したデータの削除
sessionStorage.removeItem(delete_task_id[7]);
//選択したタスクの要素を削除
document.getElementById(delete_task_id[7]).remove();
document.getElementById(`edit-${delete_task_id[7]}`).remove();
event.currentTarget.remove();
});
これで、crudの基本的な機能をtodoアプリに実装できました!
ここまでが、3日目でした。
お気づきの通り、このtodoアプリは致命的なバグがあります
そこを、+αで改善していきます...
まず、致命的なバグは、task_idの管理周りです。
現状、sessionStorageで管理しているtask_id_list
は単純な文字列で管理しています。
そのため、task_idが2桁の数値になった時にロジックが破綻します。
画像の様に、タスク10が作られると、バグります。
リロードする前は、
この様に、綺麗に表示されています。
しかし、ページをリロードすると
「タスク10」がバグります。
これは、indexTaskList
に8910
という文字列を引数として渡し、
8910
を、一つずつtask_id_list[i]
でsessionStorageから値を取得しているためである。
1
2
3
4
5
6
7
8
9
function indexTaskList(task_id_list) {
const task_list = document.getElementById('task-list');
for( let i=0; i<task_id_list.length; i++){
//タスク
const task = document.createElement('li');
task.setAttribute('id', task_id_list[i]);
task.textContent = sessionStorage.getItem(task_id_list[i]);
...
これを解決するために、task_id_list
を違う文字列で保存する必要があります。
そこで、数値ごとに.
で区切る方法を考えました。
1.2.3.9.10.12.101
この様に区切ることで2桁以上でもtask_id
を取得可能です。
まず、task_id_list
の計算部分を
1
2
3
4
5
if (current_task_id_list === "") {
current_task_id_list = String(task_id);
} else {
current_task_id_list = current_task_id_list + "." + String(task_id);
}
この様に修正しました。
次は、`1.2.3.9.10
この文字列を.
で分割し、新たに[1,2,3,9,10]
という配列にして処理したいです。
そこで、.
で分割して、配列を作る関数を用意しました。
1
2
3
4
5
6
function getCurrentTaskIdList () {
let current_task_id_list = sessionStorage.getItem('task_id_list');
current_task_id_list = current_task_id_list.split(".");
return current_task_id_list;
}
そして、ページロード時の処理を修正しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
document.addEventListener('DOMContentLoaded', () => {
const save_button = document.getElementById('save-button');
const task_list = document.getElementById('task-list');
let task_id;
// 初回ロード時
if (sessionStorage.getItem('task_id_list') === null) {
sessionStorage.setItem('task_id_list', '');
task_id = 1;
// リロード時
} else {
task_id = parseInt(sessionStorage.getItem('next_task_id'));
// 配列でtask_idを取得
let current_task_id_list = getCurrentTaskIdList();
// current_task_id_list = ["1", "2", "10", "30"]の配列
indexTaskList(current_task_id_list);
}
...
// そして、ちゃっかりtask_idのバグも修正
task_id++;
sessionStorage.setItem('next_task_id', String(task_id));
}
});
});
task_id_list
に関するバグの修正は、こんな感じです。
sessionStorage.setItem('next_task_id', String(task_id));
などで、リロード時にtask_idがリセットされるバグも修正しました。
そして、編集、削除機能も細かな修正を行いました。
1
2
3
4
5
6
7
8
9
//編集機能
edit_button.addEventListener('click', (event) => {
// -で分割して、正しくidを取得(id="edit-10")
const edit_task = event.currentTarget.id.split("-");
// edit_task = ["edit", "10"]
const edit_id = edit_task[1]
sessionStorage.setItem('edit_task_id', edit_id);
window.location.href = 'edit_todo.html';
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//削除処理
function deleteTaskId(current_task_id_list, delete_id) {
// 削除するtask_idを除外した配列を作成
delete_task_id_list = current_task_id_list.filter((id) => id != delete_id);
new_task_id_list = "";
for (let i=0; i<delete_task_id_list.length; i++) {
if (new_task_id_list === "") {
new_task_id_list = delete_task_id_list[i];
} else {
new_task_id_list = new_task_id_list + "." + delete_task_id_list[i];
}
}
return new_task_id_list;
}
document.addEventListener('DOMContentLoaded', () => {
...
//削除機能
delete_button.addEventListener('click', (event) => {
// -で分割して、正しくidを取得(id="delete-10")
const delete_task = event.currentTarget.id.split("-");
// edit_task = ["delete", "10"]
const delete_task_id = parseInt(delete_task[1]);
const current_task_id_list = getCurrentTaskIdList();
// 選択したtask_idを削除する関数を用意
const new_task_id_list = deleteTaskId(current_task_id_list, delete_task_id);
sessionStorage.setItem('task_id_list', new_task_id_list);
//セッションデータの削除
sessionStorage.removeItem(delete_task_id);
//選択したタスクの要素を削除
document.getElementById(delete_task_id).remove();
document.getElementById(`edit-${delete_task_id}`).remove();
event.currentTarget.remove();
});
これで、全体的なバグ修正は完了です。
やっと、sessionStorageをこねくり回したtodoアプリができました!
(軽く動作確認はしたけど、まだバグあるかもです...)
今回は、web開発の初歩ということで簡単なtodoアプリを作ってみました。
ハッカソンでは、最後まで作りきることはできませんでしたが、+αでなんとか...
sessionStorageはDBとしては使えないことがわかりました笑
まぁ、そもそもDB目的じゃないので当然ではありますが...
でも、試行錯誤したらなんとかなるもんですね!
こねくりtodoアプリはgithubにあげてるので興味があれば
夏休みは、まだあるので、バックエンドまで踏み込んだweb開発もやりたいですね〜
(卒研が...😵💫)