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

【JSコード解説】ToDoリストアプリ作成(4/5)

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

WEBブラウザで動くToDoリストアプリのコード解説(app.js)

前回作成したWEBアプリのコード解説(app.js)です。

  • 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-part4
  • WEBアプリのURL
  • GitHubのURL

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

ToDoリストアプリ

コード概要

  • ToDoリストアプリケーションのメインJavaScriptファイル
  • TaskManagerUIManagerクラスを使用
  • DOMContentLoadedイベントで初期化
  • タスク追加、日付選択、カレンダー表示の機能を実装
  • フォームのバリデーションとエラー表示を含む

app.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);
  • TaskManagerUIManagerクラスをインポート
  • DOMContentLoadedイベントリスナーを設定
  • TaskManagerUIManagerのインスタンスを作成

このコードは、アプリケーションの初期設定を行っています。まず、タスク管理とUI管理のためのクラスをインポートします。次に、DOMContentLoadedイベントリスナーを設定することで、HTMLの読み込みが完了した後にJavaScriptのコードが実行されるようにしています。そして、タスク管理とUI管理のインスタンスを作成し、アプリケーション全体で使用できるようにしています。

HTML要素の取得

    // 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');
  • 必要なHTML要素をgetElementByIdメソッドで取得
  • 取得した要素を定数に格納

このコードでは、HTMLドキュメント内の特定の要素をJavaScriptから操作するために、getElementByIdメソッドを使用して要素を取得しています。これらの要素は後のコードで頻繁に使用されるため、変数に格納しておくことで、コードの可読性と効率を向上させています。

変数の初期化

    // 変数の初期化
    let selectedDate = '';
    let isCalendarVisible = false;
    let currentCalendarDate = new Date();
  • selectedDate: 選択された日付を格納
  • isCalendarVisible: カレンダーの表示状態を管理
  • currentCalendarDate: 現在表示中のカレンダーの日付を管理

これらの変数は、アプリケーションの状態を管理するために使用されます。selectedDateは、ユーザーが選択した日付を保持します。isCalendarVisibleは、カレンダーが表示されているかどうかを示すブール値です。currentCalendarDateは、カレンダーに表示されている現在の月を追跡するために使用されます。

イベントリスナーの設定

    // イベントリスナーの設定
    taskForm.addEventListener('submit', handleFormSubmit);
    dateSelectBtn.addEventListener('click', toggleCalendar);
    document.addEventListener('click', handleOutsideClick);
  • taskFormsubmitイベントリスナーを追加
  • dateSelectBtnclickイベントリスナーを追加
  • document全体にclickイベントリスナーを追加

このコードでは、ユーザーの操作に応じてアプリケーションが反応するように、各要素にイベントリスナーを設定しています。taskFormのsubmitイベントは新しいタスクの追加を、dateSelectBtnのclickイベントはカレンダーの表示切り替えを、document全体のclickイベントはカレンダー外のクリックを処理します。

フォーム送信時の処理

    // フォーム送信時の処理
    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(); // フォームをリセット
    }
  • preventDefaultでデフォルトの送信動作を防止
  • 入力値のバリデーションを実行
  • エラーがなければタスクを追加し、フォームをリセット

この関数は、フォームが送信されたときに呼び出されます。まず、フォームのデフォルトの送信動作を防止します。次に、入力されたタスクと日付のバリデーションを行い、エラーがあれば表示します。すべてが正常であれば、UIManagerを通じてタスクを追加し、フォームをリセットします。

カレンダーの表示/非表示切り替え

    // カレンダーの表示/非表示を切り替え
    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;
    }
  • toggleCalendar: カレンダーの表示/非表示を切り替え
  • showCalendar: カレンダーを表示し、位置を設定
  • hideCalendar: カレンダーを非表示にする

これらの関数は、カレンダーの表示を制御します。toggleCalendarは、現在の状態に応じてカレンダーの表示/非表示を切り替えます。showCalendarは、カレンダーを表示し、ボタンの位置に基づいて適切な位置に配置します。hideCalendarは、カレンダーを非表示にします。

カレンダーのレンダリング

    // カレンダーをレンダリング
    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);
    }
  • 指定された日付に基づいてカレンダーをレンダリング
  • カレンダーのヘッダーと日付部分を生成
  • 生成したHTMLをDOMに挿入
  • カレンダーの各要素にイベントリスナーを追加

この関数は、指定された日付に基づいてカレンダーを生成し、表示します。まず、カレンダーのヘッダー部分と日付部分のHTMLを生成します。次に、生成したHTMLをcustomCalendar要素に挿入します。最後に、カレンダーの各要素(前月/次月ボタン、日付)にイベントリスナーを追加します。

カレンダーのヘッダー部分の作成

    // カレンダーのヘッダー部分を作成
    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>
        `;
    }
  • カレンダーのヘッダー部分のHTMLを生成
  • 年月の表示、前月/次月ボタン、曜日の表示を含む

この関数は、カレンダーのヘッダー部分のHTMLを生成します。ヘッダーには、現在の年月、前月と次月に移動するためのボタン、そして曜日の表示が含まれます。テンプレートリテラルを使用して、動的に年月を挿入しています。

カレンダーの日付部分の作成

    // カレンダーの日付部分を作成
    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>';
    }
  • 月の最初の日までの空白を追加
  • 1日から月末までの日付を追加
  • 生成したHTMLを返す

この関数は、カレンダーの日付部分のHTMLを生成します。まず、月の最初の日が週の何日目かに応じて空白のdivを追加します。次に、1日から月末までの日付を表示する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()]})`;
    }
  • 選択された日付を指定のフォーマットで表示
  • 年月日と曜日を含む

この関数は、ユーザーが選択した日付を適切なフォーマットで表示します。日本語の曜日表示を含め、「YYYY年MM月DD日(曜日)」の形式で日付を表示します。これにより、ユーザーが選択した日付を明確に確認できます。

エラー処理と表示

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

    // エラーメッセージを表示
    function showError(element, message) {
        element.textContent = message;
    }
  • resetErrors: エラーメッセージをクリア
  • showError: 指定された要素にエラーメッセージを表示

これらの関数は、フォームのバリデーションエラーを処理します。resetErrorsは、すべてのエラーメッセージをクリアします。showErrorは、指定された要素に特定のエラーメッセージを表示します。これらを使用することで、ユーザーに適切なフィードバックを提供し、フォームの正しい入力を促します。

フォームのリセット

    // フォームをリセット
    function resetForm() {
        taskInput.value = '';
        selectedDate = '';
        selectedDateSpan.textContent = '';
    }
  • タスク入力フィールドをクリア
  • 選択された日付をリセット
  • 日付表示をクリア

この関数は、タスクが正常に追加された後やユーザーがフォームをリセットしたい場合に呼び出されます。タスク入力フィールドをクリアし、選択された日付をリセットします。また、画面上の日付表示もクリアします。これにより、ユーザーは新しいタスクを入力する準備が整います。

初期表示時のタスク表示

    // 初期表示時にタスクを表示
    uiManager.renderTasks();
  • アプリケーション起動時に既存のタスクを表示

このコードは、DOMContentLoadedイベントリスナーの最後に配置されています。アプリケーションが起動し、DOMの読み込みが完了した直後に実行されます。UIManagerrenderTasksメソッドを呼び出すことで、既存のタスク(例えば、ローカルストレージに保存されているタスク)を画面に表示します。これにより、ユーザーは前回のセッションで追加したタスクを即座に確認できます。

このスクリプト全体は、即時実行関数(IIFE: Immediately Invoked Function Expression)の中に配置されているわけではありませんが、DOMContentLoadedイベントリスナーの中に全ての処理が含まれています。これには以下のような利点があります:

  1. グローバルスコープの汚染を防ぐ: 変数や関数がグローバルスコープに漏れるのを防ぎます。
  2. DOMの準備完了を保証: スクリプトがDOMの読み込み完了後に実行されることを保証し、DOM操作に関するエラーを防ぎます。
  3. モジュール的なアプローチ: コードを論理的なユニットにまとめることで、保守性と可読性が向上します。

まとめ

コードの特徴

  • モジュール化されたアーキテクチャ(TaskManagerとUIManagerの分離)
  • イベント駆動型のプログラミングスタイル
  • カスタムカレンダーの動的生成と操作
  • クロージャを利用した変数のスコープ管理
  • **ES6+**の機能(アロー関数、テンプレートリテラル)の活用
  • バリデーションとエラーハンドリングの実装
  • DOM操作を効率的に行うための要素キャッシング
  • 日付操作のための独自ロジック

このJavaScriptコードは、モダンで機能的なToDoリストアプリケーションのコアロジックを実装しています。コードは明確に構造化され、責任の分離原則に従ってTaskManagerとUIManagerクラスを利用しています。これにより、ビジネスロジックとユーザーインターフェース管理が適切に分離され、コードの保守性と拡張性が向上しています。

アプリケーションの主要機能として、タスクの追加、日付の選択、カスタムカレンダーの表示と操作が実装されています。特に、カスタムカレンダーの実装は注目に値し、動的なHTML生成とイベントハンドリングを組み合わせて、ユーザーフレンドリーな日付選択インターフェースを提供しています。

コード全体を通して、モダンなJavaScript機能が効果的に活用されています。アロー関数やテンプレートリテラルの使用は、コードの簡潔さと可読性を向上させています。また、イベントリスナーの適切な使用とイベントの伝播制御により、ユーザーインタラクションを効果的に管理しています。

バリデーションとエラーハンドリングも適切に実装されており、ユーザー入力の検証とフィードバックの提供を確実に行っています。これにより、アプリケーションの堅牢性と使いやすさが向上しています。

コメント

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