HTML、CSS、JavaScriptメモアプリWEBアプリ(JavaScript)

【JavaScriptコード解説】メモアプリ作成(3/3) 

メモアプリのアイキャッチ画像03 HTML、CSS、JavaScript

WEBブラウザで動くシンプルなメモアプリのコード解説(JavaScript)

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

  • WEBアプリのURL
  • GitHubのURL

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

シンプルメモ帳

コード概要

  • JavaScriptで実装されたシンプルメモ帳アプリケーションのロジック部分
  • モジュール化された設計で、AppFileManagerUIManagerクラスを含む
  • File System Access APIを使用したローカルファイル操作機能
  • イベントリスナーを活用したユーザーインタラクション処理
  • 非同期処理を多用し、ファイル操作の効率化を図る

app.jsの解説

インポート部分

// メインのアプリケーションロジック
import { FileManager } from './fileManager.js';
import { UIManager } from './uiManager.js';
  • FileManagerUIManagerクラスを別のファイルからインポートしています。
  • これらのクラスは、それぞれファイル操作とユーザーインターフェース管理を担当します。

このインポート文は、アプリケーションの機能を分割して管理するためのモジュール化を実現しています。これにより、コードの再利用性と保守性が向上します。

Appクラスの定義

class App {
    constructor() {
        // FileManagerとUIManagerのインスタンスを作成
        this.fileManager = new FileManager();
        this.uiManager = new UIManager(this.fileManager);
        // イベントリスナーを初期化
        this.initEventListeners();
    }
        // ...
}
  • Appクラスを定義し、そのコンストラクタ内で初期化処理を行っています。
  • FileManagerUIManagerのインスタンスを作成しています。
  • initEventListenersメソッドを呼び出してイベントリスナーを初期化しています。

このクラスはアプリケーション全体を管理する中心的な役割を果たします。コンストラクタでは、ファイル操作とUI管理のためのインスタンスを作成し、イベントリスナーを初期化しています。UIManagerFileManagerのインスタンスを渡すことで、UIがファイル操作機能にアクセスできるようになっています。これは依存性注入の一形態で、コードの柔軟性と再利用性を高めています。

イベントリスナーの初期化

// 各ボタンとテキストエリアにイベントリスナーを設定
    initEventListeners() {
        // 新規ファイルボタン
        document.getElementById('newFile').addEventListener('click', () => this.uiManager.handleNewFile());
        // ファイルを開くボタン
        document.getElementById('openFile').addEventListener('click', () => this.uiManager.handleOpenFile());
        // 保存ボタン
        document.getElementById('saveFile').addEventListener('click', () => this.uiManager.handleSaveFile());
        // 名前を付けて保存ボタン
        document.getElementById('saveAsFile').addEventListener('click', () => this.uiManager.handleSaveAsFile());
        // テキストエリアの内容変更イベント
        document.getElementById('memoArea').addEventListener('input', () => this.uiManager.handleTextAreaChange());
        // ページを離れる前のイベント
        window.addEventListener('beforeunload', (e) => this.uiManager.handleBeforeUnload(e));
    }
  • このinitEventListenersメソッドは、各ボタンとテキストエリアにイベントリスナーを設定しています。
  • クリックやテキスト入力などのイベントが発生したときに、対応するUIManagerのメソッドを呼び出します。

このメソッドは、ユーザーの操作に応じてアプリケーションが適切に反応するように設定しています。アロー関数を使用することで、this の参照を正しく保持しつつ、簡潔に記述しています。

アプリケーションの初期化

// DOMの読み込みが完了したらアプリケーションを初期化
document.addEventListener('DOMContentLoaded', () => {
    new App();
});
  • このコードは、HTMLドキュメントの読み込みが完了したときに実行されます。
  • DOMContentLoadedイベントが発生したら、新しいAppインスタンスを作成します。
  • これにより、ページの読み込みが完了した後にアプリケーションが初期化されることが保証されます。

このコードは、HTMLドキュメントの読み込みが完了したタイミングでアプリケーションを初期化します。DOMContentLoadedイベントを使用することで、HTMLの解析が完了し、DOMツリーが構築された後にJavaScriptコードが実行されることを保証しています。これにより、JavaScriptがDOMを操作しようとしたときに、必要な要素が確実に存在することが保証されます。

fileManager.jsの解説

クラスの定義と constructor

// ファイル操作を管理するクラス
export class FileManager {
    constructor() {
        // 現在開いているファイルのハンドル
        this.currentFileHandle = null;
        // 内容が保存されているかどうかのフラグ
        this.isContentSaved = true;
    }
  • export: このクラスを他のファイルでも使えるようにします。
  • constructor: クラスのインスタンスが作成されるときに自動的に呼ばれるメソッドです。
  • currentFileHandle: 現在開いているファイルの参照を保持します。
  • isContentSaved: ファイルの内容が保存されているかどうかを示すフラグです。

このコードは FileManager クラスの基本構造を定義しています。constructor メソッドでは、クラスの初期状態を設定しています。currentFileHandlenull であることは、まだファイルが開かれていないことを意味し、isContentSavedtrue であることは、変更されていない(つまり保存が必要ない)状態を示しています。

openFile メソッド

    // ファイルを開く処理
    async openFile() {
        try {
            // ファイル選択ダイアログを表示
            const [fileHandle] = await window.showOpenFilePicker({
                types: [{ description: 'テキストファイル', accept: {'text/plain': ['.txt']} }],
            });
            // 選択されたファイルの内容を読み込む
            const file = await fileHandle.getFile();
            const contents = await file.text();
            // 現在のファイルハンドルを更新
            this.currentFileHandle = fileHandle;
            this.isContentSaved = true;
            return { name: file.name, contents };
        } catch (error) {
            // ユーザーがキャンセルした場合は何もしない
            if (error.name !== 'AbortError') {
                console.error('ファイルを開く際にエラーが発生しました:', error);
                throw new Error('ファイルを開けませんでした。');
            }
        }
    }
  • async/await: 非同期処理を扱うための JavaScript の機能です。
  • window.showOpenFilePicker: ファイル選択ダイアログを表示する Web API です。
  • try/catch: エラーハンドリングのための構文です。

このメソッドはファイルを開くための処理を行います。showOpenFilePicker を使ってユーザーにファイル選択を促し、選択されたファイルの内容を読み込みます。ファイルの選択や読み込みに成功した場合、currentFileHandle を更新し、isContentSavedtrue に設定します。エラーが発生した場合は適切に処理します。特に、ユーザーがファイル選択をキャンセルした場合(AbortError)は特別に扱い、エラーとしては扱いません。

saveFile メソッド

    // ファイルを保存する処理
    async saveFile(content) {
        try {
            // 現在のファイルハンドルがない場合は名前を付けて保存
            if (!this.currentFileHandle) {
                return await this.saveAsFile(content);
            }
            // ファイルに書き込む
            const writable = await this.currentFileHandle.createWritable();
            await writable.write(content);
            await writable.close();
            this.isContentSaved = true;
            return this.currentFileHandle.name;
        } catch (error) {
            console.error('ファイルの保存中にエラーが発生しました:', error);
            throw new Error('ファイルを保存できませんでした。');
        }
    }
  • createWritable: ファイルに書き込むためのストリームを作成します。
  • write: ストリームにデータを書き込みます。
  • close: ストリームを閉じて、変更を確定します。

このメソッドは現在開いているファイルに内容を保存します。currentFileHandlenull の場合(新規ファイルの場合)は、saveAsFile メソッドを呼び出して名前を付けて保存します。それ以外の場合は、現在のファイルハンドルを使用して直接保存を行います。保存が成功したら isContentSavedtrue に設定し、ファイル名を返します。

saveAsFile メソッド

    // 名前を付けて保存する処理
    async saveAsFile(content) {
        try {
            // ファイル保存ダイアログを表示
            const fileHandle = await window.showSaveFilePicker({
                types: [{ description: 'テキストファイル', accept: {'text/plain': ['.txt']} }],
            });
            // ファイルに書き込む
            const writable = await fileHandle.createWritable();
            await writable.write(content);
            await writable.close();
            // 現在のファイルハンドルを更新
            this.currentFileHandle = fileHandle;
            this.isContentSaved = true;
            return fileHandle.name;
        } catch (error) {
            // ユーザーがキャンセルした場合は何もしない
            if (error.name !== 'AbortError') {
                console.error('ファイルの保存中にエラーが発生しました:', error);
                throw new Error('ファイルを保存できませんでした。');
            }
        }
    }
  • showSaveFilePicker: ファイル保存ダイアログを表示する Web API です。

このメソッドは「名前を付けて保存」の機能を実装しています。showSaveFilePicker を使ってユーザーに保存先と名前を指定させ、その後ファイルに内容を書き込みます。保存が成功したら currentFileHandle を新しいファイルハンドルで更新し、isContentSavedtrue に設定します。ユーザーが保存をキャンセルした場合は特別に扱い、エラーとしては扱いません。

newFile メソッド

    // 新規ファイルを作成する処理
    newFile() {
        // 現在のファイルハンドルをリセット
        this.currentFileHandle = null;
        this.isContentSaved = true;
    }
}
  • このメソッドは非常にシンプルで、新規ファイルの作成を模倣します。

newFile メソッドは新しいファイルを作成する状況をシミュレートします。具体的には、currentFileHandlenull に設定して現在開いているファイルとの関連付けを解除し、isContentSavedtrue に設定して新しい(まだ変更されていない)状態を表します。このメソッドを呼び出した後、アプリケーションは新しい空のファイルで作業を始められる状態になります。

uiManager.jsの解説

クラスの定義と constructor

// UI操作を管理するクラス
export class UIManager {
    constructor(fileManager) {
        // FileManagerのインスタンスを保持
        this.fileManager = fileManager;
        // テキストエリアの要素を取得
        this.memoArea = document.getElementById('memoArea');
        // ファイル名表示要素を取得
        this.fileNameElement = document.getElementById('currentFileName');
    }
}
  • export: このクラスを他のファイルからインポートできるようにします。
  • constructor: クラスのインスタンスを作成する際に呼び出される特別なメソッドです。
  • fileManager: 外部から渡されるFileManagerのインスタンスを保持します。
  • memoArea, fileNameElement: HTMLの要素を取得して保持します。

このコードはUIManagerクラスを定義しています。constructor(コンストラクタ)メソッドは、クラスのインスタンスが作成されるときに自動的に呼び出されます。ここでは、FileManagerのインスタンスを受け取り、それをthis.fileManagerに保存しています。また、document.getElementById()を使用してHTML上の特定の要素(メモ入力エリアとファイル名表示要素)を取得し、クラス内で使用できるように保存しています。

ファイル名更新メソッド

    // ファイル名表示を更新する処理
    updateFileName(fileName) {
        this.fileNameElement.textContent = fileName || '新規ファイル(未保存)';
    }
  • updateFileName: ファイル名を更新するメソッドです。
  • ||: 論理OR演算子で、左側がfalsy(この場合は未定義や空文字)の場合に右側の値を使用します。

このメソッドは、ファイル名を表示する要素の内容を更新します。fileNameが提供されない場合(つまり、undefinedや空文字の場合)、「新規ファイル(未保存)」というテキストが表示されます。これにより、ユーザーは現在編集中のファイル名や新規ファイルであることを常に確認できます。

新規ファイル作成処理

    // 新規ファイル作成の処理
    async handleNewFile() {
        // 未保存の変更がある場合、確認ダイアログを表示
        if (!this.fileManager.isContentSaved) {
            const confirmNew = confirm("未保存の変更があります。保存せずに新しいファイル作成しますか?");
            if (!confirmNew) return;
        }
        // 新規ファイルを作成
        this.fileManager.newFile();
        this.memoArea.value = '';
        this.updateFileName();
    }
  • async: このメソッドが非同期処理を含むことを示します。
  • confirm: ユーザーに確認を求めるダイアログを表示します。
  • this.fileManager.newFile(): FileManagerの新規ファイル作成メソッドを呼び出します。

このメソッドは新規ファイルを作成する処理を行います。まず、未保存の変更がある場合、ユーザーに確認を求めます。ユーザーが確認した場合、または未保存の変更がない場合、FileManagernewFileメソッドを呼び出し、テキストエリアをクリアし、ファイル名表示を更新します。これにより、ユーザーは安全に新規ファイルを作成できます。

ファイルを開く処理

    // ファイルを開く処理
    async handleOpenFile() {
        // 未保存の変更がある場合、確認ダイアログを表示
        if (!this.fileManager.isContentSaved) {
            const confirmOpen = confirm("未保存の変更があります。保存せずに新しいファイルを開きますか?");
            if (!confirmOpen) return;
        }
        try {
            // ファイルを開く
            const file = await this.fileManager.openFile();
            if (file) {
                this.memoArea.value = file.contents;
                this.updateFileName(file.name);
            }
        } catch (error) {
            alert(error.message);
        }
    }
  • try-catch: エラーが発生した場合に適切に処理するための構文です。
  • await: 非同期処理の結果を待ちます。
  • this.fileManager.openFile(): FileManagerのファイルを開くメソッドを呼び出します。

このメソッドはファイルを開く処理を行います。未保存の変更がある場合、ユーザーに確認を求めます。その後、FileManageropenFileメソッドを呼び出してファイルを開きます。成功した場合、ファイルの内容をテキストエリアに設定し、ファイル名を更新します。エラーが発生した場合は、アラートでユーザーに通知します。

ファイルを保存する処理

    // ファイルを保存する処理
    async handleSaveFile() {
        try {
            // ファイルを保存
            const fileName = await this.fileManager.saveFile(this.memoArea.value);
            this.updateFileName(fileName);
        } catch (error) {
            alert(error.message);
        }
    }
  • this.fileManager.saveFile(): FileManagerのファイル保存メソッドを呼び出します。
  • this.memoArea.value: テキストエリアの現在の内容を取得します。

このメソッドは現在のファイルを保存する処理を行います。FileManagersaveFileメソッドを呼び出し、テキストエリアの内容を渡します。保存に成功した場合、返されたファイル名でファイル名表示を更新します。エラーが発生した場合は、アラートでユーザーに通知します。

名前を付けて保存する処理

    // 名前を付けて保存する処理
    async handleSaveAsFile() {
        try {
            // 名前を付けて保存
            const fileName = await this.fileManager.saveAsFile(this.memoArea.value);
            if (fileName) {
                this.updateFileName(fileName);
            }
        } catch (error) {
            alert(error.message);
        }
    }
  • this.fileManager.saveAsFile(): FileManagerの名前を付けて保存するメソッドを呼び出します。

このメソッドは「名前を付けて保存」の処理を行います。FileManagersaveAsFileメソッドを呼び出し、テキストエリアの内容を渡します。新しいファイル名が返された場合、ファイル名表示を更新します。エラーが発生した場合は、アラートでユーザーに通知します。

テキストエリアの内容変更時の処理

    // テキストエリアの内容が変更された時の処理
    handleTextAreaChange() {
        // 内容が変更されたことをマーク
        this.fileManager.isContentSaved = false;
        // ファイル名表示を更新(未保存状態を表示)
        this.updateFileName(this.fileManager.currentFileHandle ? this.fileManager.currentFileHandle.name + '(未保存)' : null);
    }
  • isContentSaved: 内容が保存されているかどうかを示すフラグです。
  • currentFileHandle: 現在開いているファイルのハンドルです。

このメソッドはテキストエリアの内容が変更されたときに呼び出されます。内容が変更されたことを示すためにisContentSavedフラグをfalseに設定し、ファイル名表示を更新して未保存状態であることを示します。

ページを離れる前の処理

    // ページを離れる前の処理
    handleBeforeUnload(e) {
        // 未保存の変更がある場合、確認ダイアログを表示
        if (!this.fileManager.isContentSaved) {
            e.preventDefault();
            e.returnValue = '';
        }
    }
  • e.preventDefault(): ブラウザのデフォルトの動作を防ぎます。
  • e.returnValue: ブラウザに返す値を設定します。

このメソッドはユーザーがページを離れようとしたときに呼び出されます。未保存の変更がある場合、ブラウザのデフォルトの確認ダイアログを表示させます。これにより、ユーザーが誤って未保存の変更を失うことを防ぎます。以上がUIManagerクラスの詳細な解説です。

まとめ

コードの特徴

  • クラスベースの設計で、責任分離と再利用性を向上
  • FileManagerクラスによるファイル操作の抽象化
  • UIManagerクラスによるユーザーインターフェース操作の一元管理
  • エラーハンドリングユーザーへのフィードバック機能の実装
  • 未保存の変更を追跡し、ユーザーに警告を表示する機能
  • モジュールシステムを活用した、整理されたコード構造
  • async/awaitを使用した、読みやすい非同期処理の実装
  • イベント駆動型のアプリケーション設計

このJavaScriptコードは、モダンなウェブ技術を活用して構築された、シンプルで効率的なメモ帳アプリケーションのコア機能を提供しています。オブジェクト指向プログラミングの原則に従い、責任を明確に分離したクラス設計により、保守性拡張性の高いコード構造を実現しています。

Appクラスがアプリケーション全体の初期化と調整を担当し、FileManagerクラスがファイル操作を抽象化、UIManagerクラスがユーザーインターフェースの管理を行うという明確な役割分担が特徴的です。この設計により、将来的な機能追加や変更が容易になっています。

File System Access APIの利用により、ウェブアプリケーションでありながら、デスクトップアプリケーションのようなネイティブなファイル操作を実現しています。これにより、ユーザーは馴染みのあるファイル操作感覚でアプリケーションを使用することができます。

また、非同期処理を積極的に活用することで、ファイル操作などの時間のかかる処理でもユーザーインターフェースがブロックされることなく、スムーズな操作感を維持しています。エラーハンドリングやユーザーへのフィードバック機能も充実しており、ユーザーフレンドリーな設計になっています。

特に、未保存の変更がある状態でページを離れようとした際の警告機能は、ユーザーの作業を保護する重要な役割を果たしています。総じて、このコードはモダンなJavaScript開発の好例であり、効率性保守性ユーザビリティのバランスが取れた設計となっています。今後の機能拡張や改善にも柔軟に対応できる、堅牢な基盤を提供していると言えるでしょう。

コメント

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