ファイル送信フォームダイアログ(HTML +JQuery)

備忘録

以下のよくある画面を作成するにあたってhtmlとjqueryを用いて実装しています。
①ボタンクリックをトリガとしてダイアログを表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297087/b9b23ad1-4f82-5d90-9cd2-b4fdd3346ba1.png)

②ダイアログ内で画像アップロードボタンをクリックして画像アップロードが出来る
③アップロードされた画像はダイアログ内のプレビューとプレビュー表示エリアと同期させておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297087/ca1db18d-e77f-676f-4dd8-a6dc9bfa036f.png)

④ダイアログ内であれば画像クリックで削除ダイアログに遷移できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297087/026892c1-7901-8f6c-8c9e-43a973562a6c.png)



html
```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ポップアップダイアログの例</title>
    <!-- CSSファイルの読み込み -->
    <link rel="stylesheet" href="styles.css">
</head>
<body>

    <button id="open-dialog">ダイアログを開く</button>

    <!-- アップロード前の画像表示用エリア -->
    <div id="preview-before-upload">
        <h2>アップロード前の画像プレビュー</h2>
        <div id="outside-previewbox"></div> <!-- 外部プレビュー用ボックス -->
    </div>

    <!-- アップロード用ダイアログ -->
    <div id="dialog-overlay"></div>
    <div id="dialog-box">
        <div id="dialog-header">
            <span id="close-dialog">✕</span>
        </div>
        <div id="dialog-content">
            <p>ここにダイアログの内容を入れます。</p>
            <!-- ファイルアップロードフォーム -->
            <form action="https://example.com/post/form/" method="post" enctype="multipart/form-data">
                <p>
                   <label>
                      <span class="filelabel" title="ファイルを選択">
                      </span>
                      <input type="file" name="datafile" id="filesend" multiple accept=".jpg,.gif,.png,image/gif,image/jpeg,image/png">
                   </label>
                   <span id="previewbox"></span>
                   <input type="reset" value="Reset" onclick="resetPreview();">
                </p>
             </form>
        </div>
        <div id="dialog-footer">
            <button id="ok-button">OK</button>
        </div>
    </div>

    <!-- 削除用ダイアログ -->
    <div id="delete-dialog-overlay"></div>
    <div id="delete-dialog-box">
        <div id="delete-dialog-header">
            <span id="close-delete-dialog">✕</span>
        </div>
        <div id="delete-dialog-content">
            <p>選択した画像を削除しますか？</p>
            <div id="delete-image-preview"></div> <!-- 削除対象の画像を表示 -->
            <button id="confirm-delete-button">削除する</button>
        </div>
    </div>

    <!-- jQueryの読み込み -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- JavaScriptファイルの読み込み -->
    <script src="script.js"></script>

</body>
</html>
```
js
```js
$(document).ready(function() {
    // アップロードダイアログを開く
    $('#open-dialog').click(function() {
        $('#dialog-overlay').fadeIn();
        $('#dialog-box').fadeIn();
    });

    // アップロードダイアログを閉じる際、アップロード前プレビューに反映
    $('#close-dialog').click(function() {
        $('#dialog-overlay').fadeOut();
        $('#dialog-box').fadeOut();
        updateOutsidePreview();  // 閉じた際にアップロード前プレビューを更新
    });

    // OKボタンでダイアログを閉じる際、アップロード前プレビューに反映
    $('#ok-button').click(function() {
        $('#dialog-overlay').fadeOut();
        $('#dialog-box').fadeOut();
        updateOutsidePreview();  // 閉じた際にアップロード前プレビューを更新
    });

    // ファイル選択時のイベント
    $('#filesend').on('change', function(e) {
        var files = e.target.files;
        previewUserFiles(files);
    });
    
    // 削除ダイアログの閉じるボタン
    $('#close-delete-dialog').click(function() {
        $('#delete-dialog-overlay').fadeOut();
        $('#delete-dialog-box').fadeOut();
    });

    // 削除確認ダイアログの削除ボタン
    $('#confirm-delete-button').click(function() {
        if (selectedImageElement) {
            var idToDelete = selectedImageElement.attr('id');  // 削除対象のIDを取得
            selectedImageElement.remove();  // 対象画像を削除
            selectedImageElement = null;    // 参照をリセット
            $('#delete-dialog-overlay').fadeOut();
            $('#delete-dialog-box').fadeOut();
            removeImageFromOutsidePreview(idToDelete);  // アップロード前のプレビューからも削除
        }
    });
});

// グローバル変数として選択された画像の参照を保持
var selectedImageElement = null;

// プレビューを表示する処理
function previewUserFiles(files) {
    resetPreview();
    resetOutsidePreview();

    $.each(files, function(i, file) {
        if (file.type.indexOf("image") < 0) {
            return true;  // 画像以外はスキップ
        }

        var reader = new FileReader();

        // ファイルが読み込まれたらプレビューを生成
        reader.onload = function(e) {
            var uniqueId = 'img-' + i;  // 各画像にユニークなIDを付与

            // ダイアログ内プレビュー
            var img = $('<img>').attr('src', e.target.result).attr('id', uniqueId).addClass('previewImage').height(100);
            $('#previewbox').append(img);

            // クリックイベントを追加して削除ダイアログに表示
            img.on('click', function() {
                openDeleteDialog($(this));
            });

            // 外部プレビュー（クリックイベントなし）
            var outsideImg = $('<img>').attr('src', e.target.result).attr('id', uniqueId + '-outside').addClass('previewImage').height(100);
            $('#outside-previewbox').append(outsideImg);
        };

        reader.readAsDataURL(file);
    });
}

// 削除確認ダイアログを開く処理
function openDeleteDialog(imgElement) {
    selectedImageElement = imgElement; // クリックされた画像を保存
    $('#delete-image-preview').html(''); // 前回のプレビューをリセット

    // 選択した画像を削除ダイアログに表示
    var img = $('<img>').attr('src', imgElement.attr('src')).css('width', '300px');
    $('#delete-image-preview').append(img);

    // 削除確認ダイアログを表示
    $('#delete-dialog-overlay').fadeIn();
    $('#delete-dialog-box').fadeIn();
}

// アップロード前プレビューを更新する処理
function updateOutsidePreview() {
    // アップロードダイアログ内の画像と一致するものをアップロード前プレビューに反映
    $('#previewbox img').each(function() {
        var id = $(this).attr('id');
        var outsideImg = $('#' + id + '-outside');
        if (outsideImg.length) {
            outsideImg.attr('src', $(this).attr('src'));  // 画像を更新
        }
    });
}

// 外部プレビューエリアから削除する処理
function removeImageFromOutsidePreview(id) {
    $('#' + id + '-outside').remove();  // 外部プレビューにある該当IDの画像を削除
}

// プレビュー領域をリセットする処理
function resetPreview() {
    $('#previewbox').empty();
    $('#selectednum').text('選択');
}

// 外部プレビューエリアをリセットする処理
function resetOutsidePreview() {
    $('#outside-previewbox').empty();
}

```

css
```css
/* オーバーレイのスタイル */
#dialog-overlay, #delete-dialog-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 999;
}

/* ダイアログボックスのスタイル */
#dialog-box, #delete-dialog-box {
    display: none;
    position: fixed;
    background: #fff;
    width: 670px;
    height: 800px;
    max-width: 90%;
    max-height: 90%;
    border-radius: 5px;
    z-index: 1000;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    overflow: auto;
}

/* 削除確認ダイアログのスタイル */
#delete-dialog-content {
    padding: 20px;
}

/* 「✕」ボタンのスタイル */
#close-dialog, #close-delete-dialog {
    cursor: pointer;
    font-size: 20px;
}

/* 削除ボタンのスタイル */
#confirm-delete-button {
    display: block;
    margin: 20px auto;
    padding: 10px 20px;
    background-color: red;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

#confirm-delete-button:hover {
    background-color: darkred;
}
```