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

【HTML,CSS,JS】メモアプリ作成!(1/3)

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

WEBブラウザで動くシンプルなメモアプリ作成

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

  • WEBアプリのURL
  • GitHubのURL

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

シンプルメモ帳

アプリ概要

  • シンプルメモ帳アプリケーション
  • ウェブブラウザ上で動作するテキストエディタ
  • HTML, CSS, JavaScriptで構築
  • モジュール化された設計で保守性が高い

VSCodeで作ってみよう!

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

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

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

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

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

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

memo-app/

├── index.html

├── css/
│   └── style.css

├── js/
    ├── app.js
    ├── fileManager.js
    └── uiManager.js

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

必要なファイル
  • index.html
  • style.css
  • app.js
  • fileManager.js
  • uiManager.js

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

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

コードの右上にコピーマークがあります。

HTML (index.html)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <!-- ビューポートの設定:レスポンシブデザインのため -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>シンプルメモ帳</title>
    <!-- CSSファイルのリンク -->
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- メインのコンテナ -->
    <div class="container">
        <!-- アプリケーションのタイトル -->
        <h1>シンプルメモ帳</h1>
        <!-- 現在のファイル名を表示する要素 -->
        <p id="currentFileName">新規ファイル(未保存)</p>
        <!-- ボタングループ -->
        <div class="button-group">
            <!-- 各機能のボタン -->
            <button id="newFile">新規ファイル</button>
            <button id="openFile">ファイルを開く</button>
            <button id="saveFile">保存</button>
            <button id="saveAsFile">名前を付けて保存</button>
        </div>
        <!-- テキスト入力エリア -->
        <textarea id="memoArea" rows="20" cols="50"></textarea>
    </div>

    <!-- JavaScriptファイルの読み込み(モジュールとして) -->
    <script src="js/app.js" type="module"></script>
</body>
</html>

CSS (css/style.css)

/* ページ全体のスタイル */
body {
    font-family: Arial, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #f0f0f0;
}

/* メインコンテナのスタイル */
.container {
    text-align: center;
    background-color: white;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

/* タイトルのスタイル */
h1 {
    margin-bottom: 1rem;
}

/* ボタングループのスタイル */
.button-group {
    margin-bottom: 1rem;
}

/* ボタンのスタイル */
button {
    margin: 0 0.5rem;
    padding: 0.5rem 1rem;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
}

/* ボタンのホバー効果 */
button:hover {
    background-color: #45a049;
}

/* テキストエリアのスタイル */
textarea {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    resize: vertical;
}

/* 現在のファイル名表示のスタイル */
#currentFileName {
    font-style: italic;
    color: #666;
    margin-bottom: 1rem;
}

JavaScript (js/app.js)

// メインのアプリケーションロジック
import { FileManager } from './fileManager.js';
import { UIManager } from './uiManager.js';

class App {
    constructor() {
        // FileManagerとUIManagerのインスタンスを作成
        this.fileManager = new FileManager();
        this.uiManager = new UIManager(this.fileManager);
        // イベントリスナーを初期化
        this.initEventListeners();
    }

    // 各ボタンとテキストエリアにイベントリスナーを設定
    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));
    }
}

// DOMの読み込みが完了したらアプリケーションを初期化
document.addEventListener('DOMContentLoaded', () => {
    new App();
});

JavaScript (js/fileManager.js)

// ファイル操作を管理するクラス
export class FileManager {
    constructor() {
        // 現在開いているファイルのハンドル
        this.currentFileHandle = null;
        // 内容が保存されているかどうかのフラグ
        this.isContentSaved = true;
    }

    // ファイルを開く処理
    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 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('ファイルを保存できませんでした。');
        }
    }

    // 名前を付けて保存する処理
    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('ファイルを保存できませんでした。');
            }
        }
    }

    // 新規ファイルを作成する処理
    newFile() {
        // 現在のファイルハンドルをリセット
        this.currentFileHandle = null;
        this.isContentSaved = true;
    }
}

JavaScript (js/uiManager.js)

// UI操作を管理するクラス
export class UIManager {
    constructor(fileManager) {
        // FileManagerのインスタンスを保持
        this.fileManager = fileManager;
        // テキストエリアの要素を取得
        this.memoArea = document.getElementById('memoArea');
        // ファイル名表示要素を取得
        this.fileNameElement = document.getElementById('currentFileName');
    }

    // ファイル名表示を更新する処理
    updateFileName(fileName) {
        this.fileNameElement.textContent = fileName || '新規ファイル(未保存)';
    }

    // 新規ファイル作成の処理
    async handleNewFile() {
        // 未保存の変更がある場合、確認ダイアログを表示
        if (!this.fileManager.isContentSaved) {
            const confirmNew = confirm("未保存の変更があります。保存せずに新しいファイル作成しますか?");
            if (!confirmNew) return;
        }
        // 新規ファイルを作成
        this.fileManager.newFile();
        this.memoArea.value = '';
        this.updateFileName();
    }

    // ファイルを開く処理
    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);
        }
    }

    // ファイルを保存する処理
    async handleSaveFile() {
        try {
            // ファイルを保存
            const fileName = await this.fileManager.saveFile(this.memoArea.value);
            this.updateFileName(fileName);
        } catch (error) {
            alert(error.message);
        }
    }

    // 名前を付けて保存する処理
    async handleSaveAsFile() {
        try {
            // 名前を付けて保存
            const fileName = await this.fileManager.saveAsFile(this.memoArea.value);
            if (fileName) {
                this.updateFileName(fileName);
            }
        } catch (error) {
            alert(error.message);
        }
    }

    // テキストエリアの内容が変更された時の処理
    handleTextAreaChange() {
        // 内容が変更されたことをマーク
        this.fileManager.isContentSaved = false;
        // ファイル名表示を更新(未保存状態を表示)
        this.updateFileName(this.fileManager.currentFileHandle ? this.fileManager.currentFileHandle.name + '(未保存)' : null);
    }

    // ページを離れる前の処理
    handleBeforeUnload(e) {
        // 未保存の変更がある場合、確認ダイアログを表示
        if (!this.fileManager.isContentSaved) {
            e.preventDefault();
            e.returnValue = '';
        }
    }
}

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

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

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

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

まとめ

アプリの特徴

  • 新規ファイル作成ファイルを開く保存名前を付けて保存の基本機能を提供
  • レスポンシブデザインで様々なデバイスに対応
  • 未保存の変更がある場合、ユーザーに警告を表示
  • File System Access APIを使用し、ローカルファイルシステムと直接やり取り
  • モジュール化されたJavaScriptコードで、機能ごとに分離された設計

このシンプルメモ帳アプリケーションは、モダンなウェブ技術を活用して作られた使いやすいテキストエディタです。File System Access APIを利用することで、従来のウェブアプリケーションでは難しかったローカルファイルの直接操作を可能にしています。

AppFileManagerUIManagerクラスに分割された設計により、コードの可読性保守性が高められています。ユーザーインターフェースはシンプルかつ直感的で、基本的なテキスト編集機能を提供しながら、ファイル操作も簡単に行えるようになっています。

また、未保存の変更がある場合にユーザーに警告を出す機能や、レスポンシブデザインの採用により、ユーザー体験と使いやすさが向上しています。このアプリケーションは、シンプルさ機能性のバランスが取れた設計になっており、基本的なテキスト編集のニーズを満たしつつ、拡張性も考慮されています。今後、追加機能の実装や、さらなるユーザビリティの向上が期待できる堅牢な基盤となっています。

コメント

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