HTML、CSS、JavaScriptToDoリストアプリWEBアプリ(JavaScript)

【HTML,CSS,JS】ToDoリストアプリ作成(1/5)

ToDoリストアプリのアイキャッチ画像01 HTML、CSS、JavaScript

WEBブラウザで動くToDoリストアプリ作成

今回は、HTML、CSS、JavaScriptを使用して簡単な ToDoリストアプリを作成します。

  • ToDoリストアプリ(1/5)
  • ToDoリストアプリ(2/5)
  • ToDoリストアプリ(3/5)
  • ToDoリストアプリ(4/5)
  • ToDoリストアプリ(5/5)
https://www.programming-make-and-learn.tech/todo-list-app-part1
  • WEBアプリのURL
  • GitHubのURL

GitHubのPagesで公開しているのでToDoリストアプリを使用できます。

ToDoリストアプリ

アプリ概要

  • ToDoリストを管理するWebアプリケーション
  • タスクの追加、編集、削除、完了状態の切り替えが可能
  • タスクに日付を設定できる
  • カレンダー機能で日付選択が可能
  • ローカルストレージを使用してタスクを保存
## 使用技術

- HTML5
- CSS3
- JavaScript (ES6+)
- ローカルストレージ

VSCodeで作ってみよう!

作成手順
  1. todo-app」という名前の新しいフォルダを作成
  2. 必要なファイル(index.html、styles.css、app.js、taskManager.js、uiManager.js)を作成
  3. 各ファイルに対応するコードをコピーペースト
  4. Go Live」でブラウザを立ち上げ実行

まず「VSCode」を起動します。

左上の「ファイル」をクリックして「フォルダを開く」を選択します。

エクスプローラー内で作りたい場所を決め、「新しいフォルダー」をクリックして新規フォルダ「todo-app」を作成します。

ポップが出ますが、自分で作成したフォルダなので「はい、作成者を信頼します」をクリックします。

このような階層でフォルダファイルを作成していきます。

## プロジェクト構造
todo-app/

├── index.html
├── css/
│   └── styles.css
├── js/
    ├── app.js
    ├── taskManager.js
    └── uiManager.js
必要なファイル
  • index.html
  • styles.css
  • app.js
  • taskManager.js
  • uiManager.js

todo-app」の横にあるの新規フォルダと新規ファイルのマークをクリックして「css」「js」フォルダを作成してください。次にフォルダ内に以下のファイルを作成してください。

ファイルが作成出来たら、以下の完成コード対応するファイルにコピペしてください。

コードの細かい解説は次のページで行いますのでとりあえず動かしてみましょう。

  • ファイル名の箇所をクリックすると各コードが表示されます。
  • コードの右上にコピーマークがあります。

HTML (index.html)

<!-- HTMLドキュメントの開始を宣言 -->
<!DOCTYPE html>
<!-- HTML要素の開始。言語を日本語に設定 -->
<html lang="ja">
<!-- ヘッド部分の開始。メタ情報やタイトル、スタイルシートのリンクなどを含む -->
<head>
    <!-- 文字エンコーディングをUTF-8に設定 -->
    <meta charset="UTF-8">
    <!-- レスポンシブデザイン用のビューポート設定 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- ブラウザのタブに表示されるタイトル -->
    <title>ToDoリストアプリ</title>
    <!-- CSSファイルをリンク -->
    <link rel="stylesheet" href="css/styles.css">
</head>

<!-- ボディ部分の開始。ページの主要なコンテンツを含む -->
<body>
    <!-- メインコンテンツを包む親要素 -->
    <div class="container">
        <!-- ページのメインタイトル -->
        <h1>ToDoリスト</h1>
        <!-- タスク入力用のフォーム -->
        <form id="task-form">
            <!-- 入力フィールドとボタンをグループ化 -->
            <div class="input-group">
                <!-- タスク入力フィールド -->
                <input type="text" id="task-input" placeholder="新しいタスクを入力" required>
                <!-- 日付選択ボタン(カレンダーアイコン) -->
                <button type="button" id="date-select-btn" aria-label="日付を選択">📅</button>
                <!-- タスク追加ボタン -->
                <button type="submit">追加</button>
            </div>          
            <!-- エラーメッセージ表示用のコンテナ -->
            <div class="error-container">
                <!-- タスク関連のエラーメッセージ表示領域 -->
                <span class="error-message" id="task-error"></span>
                <!-- 日付関連のエラーメッセージ表示領域 -->
                <span class="error-message" id="date-error"></span>
            </div>            
            <!-- 選択された日付を表示するコンテナ -->
            <div id="selected-date-container">
                <!-- 選択された日付を表示する領域 -->
                <span id="selected-date"></span>
            </div>
        </form>      
        <!-- カスタムカレンダーを表示する領域 -->
        <div id="custom-calendar" class="custom-calendar"></div>       
        <!-- タスクリストを表示するリスト -->
        <ul id="task-list"></ul>
    </div>
    
    <!-- JavaScriptファイルを読み込む。type="module"はES6モジュールを使用することを示す -->
    <script type="module" src="js/app.js"></script>
</body>

<!-- HTML文書の終了 -->
</html>

CSS (styles.css)

/* ルート要素に対するカスタムプロパティ(変数)の定義 */
:root {
    --primary-color: #5cb85c; /* 主要な色(緑) */
    --primary-hover-color: #4cae4c; /* 主要な色のホバー時の色 */
    --secondary-color: #d9534f; /* 二次的な色(赤) */
    --secondary-hover-color: #c9302c; /* 二次的な色のホバー時の色 */
    --edit-color: #337ab7; /* 編集ボタンの色(青) */
    --edit-hover-color: #286090; /* 編集ボタンのホバー時の色 */
    --background-color: #f4f4f4; /* 背景色(薄いグレー) */
    --text-color: #333; /* テキストの色(濃いグレー) */
    --error-color: #d9534f; /* エラーメッセージの色(赤) */
    --date-button-color: #6c757d; /* 日付ボタンの色(グレー) */
    --date-button-hover-color: #5a6268; /* 日付ボタンのホバー時の色 */
}

/* 全ての要素に対するリセットスタイル */
* {
    box-sizing: border-box; /* パディングとボーダーを幅と高さに含める */
    margin: 0; /* 外側の余白をなくす */
    padding: 0; /* 内側の余白をなくす */
}

/* ボディ要素のスタイル */
body {
    font-family: Arial, sans-serif; /* フォントファミリーの設定 */
    line-height: 1.6; /* 行の高さを設定 */
    background-color: var(--background-color); /* 背景色を設定 */
    color: var(--text-color); /* テキストの色を設定 */
}

/* コンテナのスタイル */
.container {
    width: 90%; /* 幅を90%に設定 */
    max-width: 500px; /* 最大幅を500pxに制限 */
    margin: 2rem auto; /* 上下に2rem、左右は自動で中央揃え */
    padding: 1rem; /* 内側の余白を1remに設定 */
}

/* 見出し1のスタイル */
h1 {
    text-align: center; /* テキストを中央揃えに */
    margin-bottom: 1rem; /* 下側の余白を1remに設定 */
}

/* タスクフォームのスタイル */
#task-form {
    display: flex; /* フレックスボックスを使用 */
    flex-direction: column; /* 子要素を縦方向に配置 */
    gap: 0.5rem; /* 子要素間の間隔を0.5remに設定 */
    margin-bottom: 2rem; /* 下側の余白を2remに設定 */
}

/* 入力グループのスタイル */
.input-group {
    display: flex; /* フレックスボックスを使用 */
    gap: 0.5rem; /* 子要素間の間隔を0.5remに設定 */
}

/* タスク入力フィールドのスタイル */
#task-input {
    flex-grow: 1; /* 利用可能なスペースを最大限に使用 */
    padding: 0.5rem; /* 内側の余白を0.5remに設定 */
    font-size: 1rem; /* フォントサイズを1remに設定 */
    border: 1px solid #ddd; /* 1pxの実線のボーダーを設定 */
    border-radius: 4px; /* 角を丸くする */
}

/* 日付選択ボタンと送信ボタンの共通スタイル */
#date-select-btn, button[type="submit"] {
    padding: 0.5rem; /* 内側の余白を0.5remに設定 */
    font-size: 1rem; /* フォントサイズを1remに設定 */
    border: none; /* ボーダーをなくす */
    border-radius: 4px; /* 角を丸くする */
    cursor: pointer; /* カーソルをポインターに変更 */
    transition: background-color 0.3s ease; /* 背景色の変更をアニメーション化 */
}

/* 日付選択ボタンのスタイル */
#date-select-btn {
    background-color: var(--date-button-color); /* 背景色を設定 */
    color: white; /* テキストの色を白に設定 */
}

/* 日付選択ボタンのホバー時のスタイル */
#date-select-btn:hover {
    background-color: var(--date-button-hover-color); /* ホバー時の背景色を設定 */
}

/* 送信ボタンのスタイル */
button[type="submit"] {
    background-color: var(--primary-color); /* 背景色を設定 */
    color: white; /* テキストの色を白に設定 */
}

/* 送信ボタンのホバー時のスタイル */
button[type="submit"]:hover {
    background-color: var(--primary-hover-color); /* ホバー時の背景色を設定 */
}

/* エラーコンテナのスタイル */
.error-container {
    display: flex; /* フレックスボックスを使用 */
    flex-direction: column; /* 子要素を縦方向に配置 */
    gap: 0.25rem; /* 子要素間の間隔を0.25remに設定 */
}

/* エラーメッセージのスタイル */
.error-message {
    color: var(--error-color); /* テキストの色を設定 */
    font-size: 0.875rem; /* フォントサイズを0.875remに設定 */
}

/* 選択された日付コンテナのスタイル */
#selected-date-container {
    font-size: 0.875rem; /* フォントサイズを0.875remに設定 */
    color: var(--date-button-color); /* テキストの色を設定 */
}

/* タスクリストのスタイル */
#task-list {
    list-style-type: none; /* リストマーカーを非表示に */
}

/* タスクアイテムのスタイル */
.task-item {
    background-color: #fff; /* 背景色を白に設定 */
    margin-bottom: 0.5rem; /* 下側の余白を0.5remに設定 */
    padding: 0.5rem; /* 内側の余白を0.5remに設定 */
    border-radius: 4px; /* 角を丸くする */
    display: flex; /* フレックスボックスを使用 */
    align-items: center; /* 子要素を垂直方向に中央揃え */
    gap: 0.5rem; /* 子要素間の間隔を0.5remに設定 */
}

/* 完了済みタスクのテキストスタイル */
.task-item.completed .task-text {
    text-decoration: line-through; /* 取り消し線を追加 */
    opacity: 0.6; /* 透明度を設定 */
}

/* タスクテキストのスタイル */
.task-text {
    flex-grow: 1; /* 利用可能なスペースを最大限に使用 */
    word-break: break-word; /* 長い単語を折り返す */
    max-width: calc(100% - 120px); /* 最大幅を設定 */
    white-space: nowrap; /* テキストを1行に表示 */
    overflow: hidden; /* はみ出た部分を隠す */
    text-overflow: ellipsis; /* はみ出た部分を省略記号で表示 */
}

/* タスクアクションのスタイル */
.task-actions {
    display: flex; /* フレックスボックスを使用 */
    gap: 0.5rem; /* 子要素間の間隔を0.5remに設定 */
}

/* タスクアクションボタンの共通スタイル */
.task-actions button {
    padding: 0.25rem 0.5rem; /* 内側の余白を設定 */
    color: white; /* テキストの色を白に設定 */
    border: none; /* ボーダーをなくす */
    cursor: pointer; /* カーソルをポインターに変更 */
    font-size: 0.75rem; /* フォントサイズを0.75remに設定 */
    border-radius: 4px; /* 角を丸くする */
    transition: background-color 0.3s ease; /* 背景色の変更をアニメーション化 */
}

/* 編集ボタンのスタイル */
.edit-btn {
    background-color: var(--edit-color); /* 背景色を設定 */
}

/* 編集ボタンのホバー時のスタイル */
.edit-btn:hover {
    background-color: var(--edit-hover-color); /* ホバー時の背景色を設定 */
}

/* 削除ボタンのスタイル */
.delete-btn {
    background-color: var(--secondary-color); /* 背景色を設定 */
}

/* 削除ボタンのホバー時のスタイル */
.delete-btn:hover {
    background-color: var(--secondary-hover-color); /* ホバー時の背景色を設定 */
}

/* カスタムカレンダーのスタイル */
.custom-calendar {
    display: none; /* 初期状態では非表示 */
    position: absolute; /* 絶対位置指定 */
    background: white; /* 背景色を白に設定 */
    border: 1px solid #ccc; /* ボーダーを設定 */
    box-shadow: 0 2px 5px rgba(0,0,0,0.15); /* ボックスシャドウを追加 */
    z-index: 1000; /* 重なり順を設定 */
}

/* カレンダーヘッダーのスタイル */
.calendar-header {
    display: flex; /* フレックスボックスを使用 */
    justify-content: space-between; /* 子要素を両端に配置 */
    padding: 10px; /* 内側の余白を10pxに設定 */
}

/* カレンダーグリッドのスタイル */
.calendar-grid {
    display: grid; /* グリッドレイアウトを使用 */
    grid-template-columns: repeat(7, 1fr); /* 7列のグリッドを作成 */
    gap: 5px; /* グリッドアイテム間の間隔を5pxに設定 */
    padding: 10px; /* 内側の余白を10pxに設定 */
}

/* カレンダーの日付ヘッダーと日付のスタイル */
.calendar-day-header, .calendar-day {
    text-align: center; /* テキストを中央揃えに */
    padding: 5px; /* 内側の余白を5pxに設定 */
}

/* カレンダーの日付ヘッダーのスタイル */
.calendar-day-header {
    font-weight: bold; /* フォントを太字に */
}

/* カレンダーの日付のスタイル */
.calendar-day {
    cursor: pointer; /* カーソルをポインターに変更 */
}

/* カレンダーの日付のホバー時のスタイル */
.calendar-day:hover {
    background-color: #f0f0f0; /* ホバー時の背景色を設定 */
}

/* レスポンシブデザイン: 画面幅が600px以下の場合 */
@media (max-width: 600px) {
    .container {
        width: 95%; /* コンテナの幅を95%に変更 */
    }
}

JavaScript (app.js、taskManager.js、uiManager.js)

// タスク管理とUI管理のクラスをインポート
import { TaskManager } from './taskManager.js';
import { UIManager } from './uiManager.js';

// DOMの読み込みが完了したら実行
document.addEventListener('DOMContentLoaded', () => {
    // タスク管理とUI管理のインスタンスを作成
    const taskManager = new TaskManager();
    const uiManager = new UIManager(taskManager);

    // HTML要素の取得
    const taskForm = document.getElementById('task-form');
    const taskInput = document.getElementById('task-input');
    const dateSelectBtn = document.getElementById('date-select-btn');
    const selectedDateSpan = document.getElementById('selected-date');
    const taskError = document.getElementById('task-error');
    const dateError = document.getElementById('date-error');
    const customCalendar = document.getElementById('custom-calendar');

    // 変数の初期化
    let selectedDate = '';
    let isCalendarVisible = false;
    let currentCalendarDate = new Date();

    // イベントリスナーの設定
    taskForm.addEventListener('submit', handleFormSubmit);
    dateSelectBtn.addEventListener('click', toggleCalendar);
    document.addEventListener('click', handleOutsideClick);

    // フォーム送信時の処理
    function handleFormSubmit(e) {
        e.preventDefault(); // デフォルトの送信動作を防止
        const taskText = taskInput.value.trim(); // 入力されたタスクのテキストを取得

        resetErrors(); // エラーメッセージをリセット

        // バリデーション
        if (!taskText) {
            showError(taskError, 'タスクを入力してください');
            return;
        }

        if (!selectedDate) {
            showError(dateError, '日付を設定してください');
            return;
        }

        // タスクを追加
        uiManager.addTask(taskText, selectedDate);
        resetForm(); // フォームをリセット
    }

    // カレンダーの表示/非表示を切り替え
    function toggleCalendar(e) {
        e.stopPropagation(); // イベントの伝播を停止
        isCalendarVisible ? hideCalendar() : showCalendar();
    }

    // カレンダーを表示
    function showCalendar() {
        const rect = dateSelectBtn.getBoundingClientRect();
        customCalendar.style.display = 'block';
        // カレンダーの位置を設定
        customCalendar.style.top = `${rect.bottom + window.scrollY}px`;
        customCalendar.style.left = `${rect.left + window.scrollX}px`;
        renderCalendar(currentCalendarDate);
        isCalendarVisible = true;
    }

    // カレンダーを非表示
    function hideCalendar() {
        customCalendar.style.display = 'none';
        isCalendarVisible = false;
    }

    // カレンダーをレンダリング
    function renderCalendar(date) {
        const year = date.getFullYear();
        const month = date.getMonth();
        const firstDay = new Date(year, month, 1);
        const lastDay = new Date(year, month + 1, 0);
        
        let calendarHTML = createCalendarHeader(year, month);
        calendarHTML += createCalendarDays(firstDay, lastDay);
        
        customCalendar.innerHTML = calendarHTML;
        
        addCalendarEventListeners(year, month);
    }

    // カレンダーのヘッダー部分を作成
    function createCalendarHeader(year, month) {
        return `
            <div class="calendar-header">
                <button id="prev-month"><</button>
                <span>${year}${month + 1}月</span>
                <button id="next-month">></button>
            </div>
            <div class="calendar-grid">
                <div class="calendar-day-header">日</div>
                <div class="calendar-day-header">月</div>
                <div class="calendar-day-header">火</div>
                <div class="calendar-day-header">水</div>
                <div class="calendar-day-header">木</div>
                <div class="calendar-day-header">金</div>
                <div class="calendar-day-header">土</div>
        `;
    }

    // カレンダーの日付部分を作成
    function createCalendarDays(firstDay, lastDay) {
        let calendarHTML = '';
        // 月の最初の日までの空白を追加
        for (let i = 0; i < firstDay.getDay(); i++) {
            calendarHTML += '<div></div>';
        }
        // 日付を追加
        for (let i = 1; i <= lastDay.getDate(); i++) {
            calendarHTML += `<div class="calendar-day">${i}</div>`;
        }
        return calendarHTML + '</div>';
    }

    // カレンダーのイベントリスナーを追加
    function addCalendarEventListeners(year, month) {
        // 前月ボタン
        document.getElementById('prev-month').addEventListener('click', (e) => {
            e.stopPropagation();
            currentCalendarDate = new Date(year, month - 1, 1);
            renderCalendar(currentCalendarDate);
        });
        // 次月ボタン
        document.getElementById('next-month').addEventListener('click', (e) => {
            e.stopPropagation();
            currentCalendarDate = new Date(year, month + 1, 1);
            renderCalendar(currentCalendarDate);
        });
        
        // 各日付にクリックイベントを追加
        customCalendar.querySelectorAll('.calendar-day').forEach(day => {
            day.addEventListener('click', () => {
                selectedDate = new Date(year, month, parseInt(day.textContent));
                updateDateDisplay(selectedDate);
                hideCalendar();
            });
        });
    }

    // カレンダー外をクリックした時の処理
    function handleOutsideClick(e) {
        if (!customCalendar.contains(e.target) && e.target !== dateSelectBtn) {
            hideCalendar();
        }
    }

    // 選択された日付の表示を更新
    function updateDateDisplay(date) {
        const days = ['日', '月', '火', '水', '木', '金', '土'];
        selectedDateSpan.textContent = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日(${days[date.getDay()]})`;
    }

    // エラーメッセージをリセット
    function resetErrors() {
        taskError.textContent = '';
        dateError.textContent = '';
    }

    // エラーメッセージを表示
    function showError(element, message) {
        element.textContent = message;
    }

    // フォームをリセット
    function resetForm() {
        taskInput.value = '';
        selectedDate = '';
        selectedDateSpan.textContent = '';
    }

    // 初期表示時にタスクを表示
    uiManager.renderTasks();
});
// タスク管理を行うクラス
export class TaskManager {
    // コンストラクタ
    constructor() {
        // タスクを読み込んで初期化
        this.tasks = this.loadTasks();
    }

    // 新しいタスクを追加するメソッド
    addTask(taskText, date) {
        // 新しいタスクオブジェクトを作成
        const task = {
            id: Date.now(),  // 現在のタイムスタンプをIDとして使用
            text: taskText,  // タスクの内容
            completed: false,  // 完了状態(初期値は未完了)
            date: date  // タスクの日付
        };
        // タスクリストに新しいタスクを追加
        this.tasks.push(task);
        // タスクを保存
        this.saveTasks();
        // 作成したタスクを返す
        return task;
    }

    // タスクを削除するメソッド
    deleteTask(taskId) {
        // 指定されたIDのタスクを除外した新しい配列を作成
        this.tasks = this.tasks.filter(task => task.id !== taskId);
        // 変更を保存
        this.saveTasks();
    }

    // タスクの完了状態を切り替えるメソッド
    toggleTaskStatus(taskId) {
        // 指定されたIDのタスクを見つける
        const task = this.tasks.find(task => task.id === taskId);
        if (task) {
            // タスクが見つかった場合、完了状態を反転
            task.completed = !task.completed;
            // 変更を保存
            this.saveTasks();
        }
    }

    // タスクの内容を編集するメソッド
    editTask(taskId, newText) {
        // 指定されたIDのタスクを見つける
        const task = this.tasks.find(task => task.id === taskId);
        if (task) {
            // タスクが見つかった場合、内容を更新
            task.text = newText;
            // 変更を保存
            this.saveTasks();
        }
    }

    // タスクをローカルストレージに保存するメソッド
    saveTasks() {
        // タスクリストをJSON文字列に変換してローカルストレージに保存
        localStorage.setItem('tasks', JSON.stringify(this.tasks));
    }

    // ローカルストレージからタスクを読み込むメソッド
    loadTasks() {
        // ローカルストレージからタスクのJSON文字列を取得
        const tasksJSON = localStorage.getItem('tasks');
        // JSON文字列が存在する場合はパースして返す、存在しない場合は空配列を返す
        return tasksJSON ? JSON.parse(tasksJSON) : [];
    }

    // 全タスクを取得するメソッド
    getTasks() {
        // タスクを日付順にソートして返す
        return this.tasks.sort((a, b) => new Date(a.date) - new Date(b.date));
    }
}
// UIを管理するクラス
export class UIManager {
    // コンストラクタ
    constructor(taskManager) {
        this.taskManager = taskManager; // TaskManagerのインスタンスを保持
        this.taskList = document.getElementById('task-list'); // タスクリストのDOM要素を取得
    }

    // 新しいタスクを追加するメソッド
    addTask(taskText, date) {
        this.taskManager.addTask(taskText, date); // TaskManagerを使ってタスクを追加
        this.renderTasks(); // タスクリストを再描画
    }

    // タスク要素を作成するメソッド
    createTaskElement(task) {
        const li = document.createElement('li'); // リスト項目要素を作成
        li.className = `task-item ${task.completed ? 'completed' : ''}`; // クラスを設定
        
        // タスク要素の内部HTMLを設定
        li.innerHTML = `
            <input type="checkbox" ${task.completed ? 'checked' : ''}>
            <span class="task-text" title="${task.text}">${task.text}</span>
            <div class="task-actions">
                <button class="edit-btn">編集</button>
                <button class="delete-btn">削除</button>
            </div>
        `;

        this.addTaskEventListeners(li, task); // イベントリスナーを追加

        return li; // 作成したタスク要素を返す
    }

    // タスク要素にイベントリスナーを追加するメソッド
    addTaskEventListeners(li, task) {
        const checkbox = li.querySelector('input[type="checkbox"]');
        const editBtn = li.querySelector('.edit-btn');
        const deleteBtn = li.querySelector('.delete-btn');

        // チェックボックスの状態変更イベント
        checkbox.addEventListener('change', () => this.toggleTaskStatus(task, li));
        // 編集ボタンのクリックイベント
        editBtn.addEventListener('click', () => this.editTask(task));
        // 削除ボタンのクリックイベント
        deleteBtn.addEventListener('click', () => this.deleteTask(task.id));
    }

    // タスクの完了状態を切り替えるメソッド
    toggleTaskStatus(task, li) {
        this.taskManager.toggleTaskStatus(task.id); // TaskManagerを使ってタスクの状態を切り替え
        li.classList.toggle('completed'); // タスク要素のクラスを切り替え
    }

    // タスクを編集するメソッド
    editTask(task) {
        const newText = prompt('タスクを編集:', task.text); // 新しいタスクテキストを入力
        if (newText && newText.trim() !== '') {
            this.taskManager.editTask(task.id, newText.trim()); // TaskManagerを使ってタスクを編集
            this.renderTasks(); // タスクリストを再描画
        }
    }

    // タスクを削除するメソッド
    deleteTask(taskId) {
        this.taskManager.deleteTask(taskId); // TaskManagerを使ってタスクを削除
        this.renderTasks(); // タスクリストを再描画
    }

    // タスクリストを描画するメソッド
    renderTasks() {
        this.taskList.innerHTML = ''; // タスクリストをクリア
        const tasks = this.taskManager.getTasks(); // すべてのタスクを取得
        const groupedTasks = this.groupTasksByDate(tasks); // タスクを日付ごとにグループ化

        // 日付でソート
        const sortedDates = Object.keys(groupedTasks).sort((a, b) => new Date(a) - new Date(b));

        // 各日付のタスクを描画
        sortedDates.forEach(date => {
            const dateHeader = document.createElement('h2');
            dateHeader.textContent = this.formatDate(date); // 日付ヘッダーを作成
            this.taskList.appendChild(dateHeader);

            // その日のタスクを描画
            groupedTasks[date].forEach(task => {
                this.taskList.appendChild(this.createTaskElement(task));
            });
        });
    }

    // タスクを日付ごとにグループ化するメソッド
    groupTasksByDate(tasks) {
        return tasks.reduce((acc, task) => {
            const dateKey = this.formatDateKey(task.date);
            if (!acc[dateKey]) {
                acc[dateKey] = [];
            }
            acc[dateKey].push(task);
            return acc;
        }, {});
    }

    // 日付キーをフォーマットするメソッド
    formatDateKey(dateString) {
        const date = new Date(dateString);
        return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
    }

    // 日付を表示用にフォーマットするメソッド
    formatDate(dateString) {
        const date = new Date(dateString);
        const days = ['日', '月', '火', '水', '木', '金', '土'];
        return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日(${days[date.getDay()]})`;
    }
}

コピペが終わったら左上の「ファイル」のメニューから「すべて保存」を押してください。

※Ctrlを押しながらKを押して、キーを離してSを押すとショートカットキーですべてのファイルを保存できます。

最後に右下の「Go Live」をクリックするとブラウザが立ち上がりプログラムが実行されます。

以下のようにブラウザが立ち上がります。

まとめ

アプリの特徴

  • モダンなUI:シンプルで使いやすいインターフェース
  • レスポンシブデザイン:様々な画面サイズに対応
  • 日付別タスク管理:タスクを日付ごとにグループ化して表示
  • カスタムカレンダー:JavaScriptで実装された独自のカレンダー機能
  • エラー処理:タスク入力や日付選択の際のバリデーション機能
  • モジュール化:TaskManagerとUIManagerクラスによる機能の分離

このToDoリストアプリケーションは、モダンなJavaScriptを使用して開発されたタスク管理ツールです。HTMLCSSJavaScriptを効果的に組み合わせることで、ユーザーフレンドリーなインターフェースと強力な機能を実現しています。

アプリの中核となるTaskManagerクラスがタスクのデータ管理を担当し、UIManagerクラスがユーザーインターフェースの制御を行うという明確な責任分担により、コードの保守性と拡張性が高められています。

特筆すべき機能として、カスタムカレンダーの実装があります。これにより、ユーザーは直感的に日付を選択してタスクを追加できます。また、タスクを日付ごとにグループ化して表示する機能は、ユーザーのタスク管理をより効率的にサポートします。

ローカルストレージを活用してタスクデータを保存することで、ページのリロード後もタスクが保持される点も、ユーザビリティを向上させる重要な要素です。全体として、このアプリケーションはモダンなWeb開発技術を活用し、ユーザーエクスペリエンスを重視した設計となっており、日々のタスク管理を効果的にサポートする優れたツールとなっています。

コメント

タイトルとURLをコピーしました