【Laravel】モーダルを開く/閉じるのJavaScriptがひとまとめになっているので分割する

モーダルの処理を役割ごとに分割して整理する備忘録記事のアイキャッチ プログラミング
スポンサーリンク

以前は登録用モーダルと編集用モーダルのjsコードが分かれていたので、それを一つにまとめる改修を行いました。

しかし今度は、「モーダルが増えて監視する要素が増えたため、コードをひとまとめにしていたらごちゃごちゃして分かりにくい」という事態に陥りました。

以前はモーダルごとに処理が分割されていたので、モーダルを開く/閉じるのコードをそれぞれのモーダル用関数に書かねばならず二度手間になってしまっていたから煩雑になっていました。

であれば、「役割ごとに関数を分割して、それぞれのモーダルの要素を監視するならすっきり書けるのでは?」と思ったため、そのように改修していきます。

ついでに、いちいち選択しているモーダルを判定してボタンの挙動を制御するのではなく、「そのボタンが押されたらこういった動作をする」という風に単純化していきたいと思います。

つまり、今回は以下の改修を行っていきます。

  • 開く処理、閉じる処理、チケット消費確認処理のjsコードを役割ごとに分割
  • ボタンごとに動作を分岐させる

本記事は個人の学習記録です。内容の正確性を保証するものではありませんので、あらかじめご了承ください。

ちなみに、以下がタスク管理アプリのGitHubです。

改修前のjsコード

改修前の該当箇所のコードは以下です。

  function manageTaskModal() {
    //登録ボタンや編集ボタン、閉じるボタン、モーダル背景を一括監視
    $(document).on('click', 
      '.task-create-btn, .task-edit-btn, .edit-tickets-btn, .confirm-yes-btn, #close-add-modal, #close-edit-modal, #close-tickets-modal, #add-task-modal, #edit-task-modal, #edit-tickets-modal', function(e) {
     
      //送信ボタンなら何もしない
      if ($(e.target).closest('button[type="submit"]').length) return;

      e.preventDefault();

      //ターゲットとなるモーダルを特定する
      //ボタンのクラス名やIDから、操作すべきモーダルを自動判別
      let targetModalId = '';
      if ($(this).hasClass('task-create-btn') || $(this).is('#add-task-modal, #close-add-modal')) {
        targetModalId = 'add-task-modal';
      }else if ($(this).hasClass('task-edit-btn') || $(this).is('#edit-task-modal, #close-edit-modal')) {
        targetModalId = 'edit-task-modal';
      }else if ($(this).hasClass('edit-tickets-btn') || $(this).hasClass('confirm-yes-btn') || $(this).is('#edit-tickets-modal, #close-tickets-modal')) {
        targetModalId = 'edit-tickets-modal';
      }

      const $modal = $('#' + targetModalId);
      if (!$modal.length) return;

      //表示・非表示の切り替え
      if ($(this).hasClass('task-create-btn') || $(this).hasClass('task-edit-btn') || $(this).hasClass('edit-tickets-btn')) {
        //開く処理
        $modal.addClass('is-open');

      } else if ($(this).hasClass('confirm-yes-btn')) {
      $modal.removeClass('is-open');        // まずチケット確認画面を閉じる
      $('#edit-task-modal').addClass('is-open'); // 次に編集モーダルを開く

      }else if ($(this).is('#close-add-modal, #close-edit-modal, #close-tickets-modal') || e.target.id === targetModalId) {
        //閉じる処理
        $modal.removeClass('is-open');

        //リセット処理(登録モーダルかつ閉じた時のみ実行)
        if (targetModalId === 'add-task-modal') {
          setTimeout(function() {
            const $container = $('#add-input-container');
            $container.find('.input-row').not(':first').remove();
            const $firstRow = $container.find('.input-row').first();
            $firstRow.find('input[type="text"]').val('');
            $firstRow.find('.priority-select').val('2').removeClass('p-1 p-3').addClass('p-2');
          }, 300);
        }
      }
    });
  }

このコードを見て、「煩雑なコードだな」と思う要因は主に以下2点です。

  • イベントデリゲートでまとめて監視しすぎているせいで、ぱっと見どの要素を監視しているのかが分かりにくい。
  • let targetModalId = ”;~以下のモーダル判定をしてからボタンの挙動を書いていくのが回りくどい

ということで、以下のように改修していきます。

  • 「モーダルを開く」「モーダルを閉じる」「はいボタンを押す」で関数を分割する
  • クリックされたボタンごとにコードを書いていく

data-openとdata-closeで開くボタン、閉じるボタンを判別

そもそも

on(‘click’, ‘.task-create-btn, .task-edit-btn, .edit-tickets-btn, .confirm-yes-btn, #close-add-modal, #close-edit-modal, #close-tickets-modal, #add-task-modal, #edit-task-modal, #edit-tickets-modal’, function(e) {……

みたいに長くなってしまうのは、開く動作だけでも「’.task-create-btn, .task-edit-btn, .edit-tickets-btn」と一つ一つ抜き出していたからです。

なので送られてくるデータを一つの変数にまとめられればそれだけですっきりします。

ということでHTMLの該当部分にdata-openを追記していきます。

    <!-- タスクの登録、編集、変更ボタン -->
   <div class="btn-area">
    @if (now()->format('Y/m/d') == $displayDate)
      @if ($tasks -> whereNull('master_id') -> isEmpty())
      <div class="task-create-area">
        <button type="button" class="btn-primary task-create-btn">タスクを登録する</button>
      </div>
      @else
      <div class="use-edit-tickets">
        <button type="button" class="btn-primary edit-tickets-btn">タスクを変更する</button>
      </div>
      @endif
    @elseif (\Carbon\Carbon::parse($displayDate)->lt(today()))
    <div class="btn-primary placeholder" style="visibility: hidden;"></div>
    @elseif ($tasks -> whereNull('master_id') -> isNotEmpty())
    <div class="task-edit-area">
      <button type="button" class="btn-primary task-edit-btn">タスクを編集する</button>
    </div>
    @else
    <div class="task-create-area">
      <button type="button" class="btn-primary task-create-btn">タスクを登録する</button>
    </div>
    @endif
   </div>
    <!-- タスクの登録、編集、変更ボタン -->
   <div class="btn-area">
    @if (now()->format('Y/m/d') == $displayDate)
      @if ($tasks -> whereNull('master_id') -> isEmpty())
      <div class="task-create-area">
        <button type="button" class="btn-primary task-create-btn" data-open="add-task-modal">タスクを登録する</button>
      </div>
      @else
      <div class="use-edit-tickets">
        <button type="button" class="btn-primary edit-tickets-btn" data-open="edit-tickets-modal">タスクを変更する</button>
      </div>
      @endif
    @elseif (\Carbon\Carbon::parse($displayDate)->lt(today()))
    <div class="btn-primary placeholder" style="visibility: hidden;"></div>
    @elseif ($tasks -> whereNull('master_id') -> isNotEmpty())
    <div class="task-edit-area">
      <button type="button" class="btn-primary task-edit-btn" data-open="edit-task-modal">タスクを編集する</button>
    </div>
    @else
    <div class="task-create-area">
      <button type="button" class="btn-primary task-create-btn" data-open="add-task-modal">タスクを登録する</button>
    </div>
    @endif
   </div>

閉じる動作data-closeも該当部分にそれぞれ追記します。

    <div class="modal-btn">
      <button type="button" id="close-edit-modal" class="modal-btn-primary modal-cancel-btn" data-close="edit-task-modal">キャンセル</button>
      <button type="submit" class="modal-btn-primary modal-edit-btn">更新</button>
    </div>

この時、data-openやdata-closeの中身は、実際に開閉するブロックの要素と同じでないと動作しないので注意。

つまり、たとえば「タスクを登録する」ボタンに設定するdata-openは、以下のコードのid=”add-task-modal”と同じ「add-task-modal」じゃないと、モーダルを開こうとしてもボタンが反応しません。

   <div id="add-task-modal" class="modal-overlay"  data-close="add-task-modal">
    @include('tasks._create_modal', ['tasks' => $tasks, 'displayDate' => $displayDate])
   </div>
   
   <div id="edit-task-modal" class="modal-overlay"  data-close="edit-task-modal">
    @include('tasks._edit_modal', ['tasks' => $tasks, 'displayDate' => $displayDate])
   </div>

   <div id="edit-tickets-modal" class="modal-overlay"  data-close="edit-tickets-modal">
    @include('tasks._edit_tickets_modal', ['editTickets' => $editTickets])
   </div>

また、モーダル背景をクリックorタップした時にも閉じる処理を行いたいので、class=”modal-overlay”(モーダル背景)の要素と同じところにdata-close=”add-task-modal”などを追記してます。

これで、開く処理の時は「data-open」を、閉じる処理の時は「data-close」を目印にすればOK!

監視対象の記述や分岐処理を減らせます。

JavaScript側で開く処理と閉じる処理を分割する

openModal()、closeModal()、そしてconfirmTicketModal()で機能を分割していきます。

分割後のJavaScriptは以下です。
※大枠の$(function () {});は省略しています。

    function openModal() {
      $(document).on('click', '[data-open]', function(e) {
        e.preventDefault();

        const modalId = $(this).data('open');
        $('#' + modalId).addClass('is-open');
      });
    }

    function closeModal() {
      $(document).on('click', '[data-close]', function(e) {

        // 背景クリック以外は無視
        if ($(this).hasClass('modal-overlay') && e.target !== this) {
          return;
        }

        e.preventDefault();

        const modalId = $(this).data('close');
        //閉じる処理
        $('#' + modalId).removeClass('is-open');

        //リセット処理(登録モーダルの時のみ実行)
        if (modalId === 'add-task-modal') {
          setTimeout(function() {
              const $container = $('#add-input-container');
              $container.find('.input-row').not(':first').remove();
              const $firstRow = $container.find('.input-row').first();
              $firstRow.find('input[type="text"]').val('');
              $firstRow.find('.priority-select').val('2').removeClass('p-1 p-3').addClass('p-2');
            }, 300);
          }
      });
    }

  <中略>

  openModal()
  closeModal()
  confirmTicketModal()

基本的にはmanageTaskModal()の中の記述を分けただけです。

開く処理ーopenModal()

    function openModal() {
      $(document).on('click', '[data-open]', function(e) {
        e.preventDefault();

        const modalId = $(this).data('open');
        $('#' + modalId).addClass('is-open');
      });
    }

イベントデリゲートで監視する要素には'[data-open]’と入れるだけです。

そうすることで、たとえばdata-open=”edit-task-modal”なら
const modalId = $(this).data(‘open’);の中に「edit-task-modal」が入ります。

ターゲットのedit-task-modalはid要素なので、$()で指定する時は冒頭に’#’と付けます。

つまり、$()の中身は$(‘#edit-task-modal’)になり、それをaddClassする流れです。

閉じる処理ーcloseModal()

    function closeModal() {
      $(document).on('click', '[data-close]', function(e) {

        // 背景クリック以外は無視
        if ($(this).hasClass('modal-overlay') && e.target !== this) {
          return;
        }

        e.preventDefault();

        const modalId = $(this).data('close');
        //閉じる処理
        $('#' + modalId).removeClass('is-open');

        //リセット処理(登録モーダルの時のみ実行)
        if (modalId === 'add-task-modal') {
          setTimeout(function() {
              const $container = $('#add-input-container');
              $container.find('.input-row').not(':first').remove();
              const $firstRow = $container.find('.input-row').first();
              $firstRow.find('input[type="text"]').val('');
              $firstRow.find('.priority-select').val('2').removeClass('p-1 p-3').addClass('p-2');
            }, 300);
          }
      });
    }

背景クリックorタップでモーダルを閉じる処理のために、大切なのは以下の部分です。

        // 背景クリック以外は無視
        if ($(this).hasClass('modal-overlay') && e.target !== this) {
          return;
        }

この部分がないと、モーダルの中身をクリックorタップしてもモーダルが閉じてしまいますので、タスクの編集などができなくなります。

  • $(this).hasClass(‘modal-overlay’)……クリックorタップした要素は「modal-overlay」というクラスを持っているか確認しています。
  • e.target !== this……今クリックorタップしている要素が「data-close=””」の要素そのものかどうか確認しています。

ということは、「もしも、クリックorタップした要素の中にmodal-overlayクラスがあり、かつ、e.target(data-close=””)を直接指定していないのであれば、trueとする」という判定をしています。

trueになったらreturn;で処理が中断されるので、つまりは「モーダル背景をクリックorタップした場合以外は処理を中断する」という分岐処理になっています。

後は普通に閉じる処理を書いて、

        e.preventDefault();

        const modalId = $(this).data('close');
        //閉じる処理
        $('#' + modalId).removeClass('is-open');

登録モーダルだった場合のみタスクカードのリセットを行います。

        //リセット処理(登録モーダルの時のみ実行)
        if (modalId === 'add-task-modal') {
          setTimeout(function() {
              const $container = $('#add-input-container');
              $container.find('.input-row').not(':first').remove();
              const $firstRow = $container.find('.input-row').first();
              $firstRow.find('input[type="text"]').val('');
              $firstRow.find('.priority-select').val('2').removeClass('p-1 p-3').addClass('p-2');
            }, 300);
          }
      });

チケット消費確認で「はい」を押した時の処理ーconfirmTicketModal()

    function confirmTicketModal() {
      $(document).on('click', '.confirm-yes-btn', function(e) {
        e.preventDefault();

        $('#edit-tickets-modal').removeClass('is-open'); // まずチケット確認画面を閉じる
        $('#edit-task-modal').addClass('is-open'); // 次に編集モーダルを開く

      });
    }

おまけで、チケット消費確認ボタンの処理も分割しました。

普通に「はい」ボタンのクラスを監視して、それがクリックされたらチケット確認モーダルを閉じ、編集モーダルを表示させるだけです。

まとめ

この改修によって役割分担が明確になり、すっきりとしたコードになりました。

manageTaskModal()のままだと、「この中で開く処理どれだっけ? 閉じる処理の部分はどこ?」と確認するフェーズが入りますが、改修後は「開く処理はopenModal()を確認すればいいんだな」となります。

さらにリセット処理を分割できそうな気もしますが、今回はここまでにします。

この備忘録が同じ悩みを持つ方のお役に立ったなら幸いです。

コメント

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