jQueryUIでドラッグ&ドロップ出来る表を作成する

はじめに

今回業務でWebページでドラッグ&ドロップで追加、移動ができるような表を作成する案件がありました。ドラッグ&ドロップの実装をしたことがなかったのでその際にいろいろ調べて完成まで至ったのをまとめていこうと思います。

実際に作ったもの

このようなかんじのものを作成しました。

ポイントとしては、

  • 表の外にあるリストから表のマスにドラッグ&ドロップして追加することが出来る
  • 表の中からドラッグで移動することが出来る
  • 同じマスに複数要素が入ることも可能
  • ホバーした時に背景色が変わる(おまけ)

というようにしています。

仕様したライブラリ

ドラッグ&ドロップの実装をしたと書きましたが、正確にはドラッグ&ドロップが簡単に使えるようにしてくれるライブラリを使用して実装しました。
ということで使用したのが、jQueryUIです。
jQueryUIはjQueryの公式のUI用のライブラリになっていて、今回はドラッグ&ドロップを実装するために使いましたが他にもUI周りで便利な機能が多数あります。
↓こちらが公式ページになります
https://jqueryui.com/

jQueryUIを使って出来るドラッグ&ドロップの機能

先程の公式のリファレンスに使用例がたくさん乗っていたので、いくつかピックアップしていこうと思います。

Draggable

ドラッグ出来る機能です。
こんなかんじでドラッグできます。

右の「Examples」に応用例が色々乗っています。いくつか見ていきましょう。

縦方向横方向だけに制限したり、指定の範囲の中だけしか移動できない設定もできます。

スクロールのイベントも取ることが出来ます。
ドラッグし始め、最中、話した瞬間にそれぞれイベントを取ることが出来ます。

Droppable

ドロップできる領域を定義することが出来ます。

Sortable

順番を入れ替えるようにすることが出来ます。

リストではなくグリッド形式も使うことが出来ます。

いま紹介したのが基本的な機能で、これを応用したものが他にも例として上がっているので興味のある人は見てみてください。
どの使用例もサンプルコードが乗っているので参考にできると思います。

jQueryUIの導入方法

jQueryUIの導入の方法はいたって簡単で、

<script type="text/javascript" src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js">

この1行を追加するだけです。

とりあえずまず簡単に作ってみました。

<!DOCTYPE html>
  <html lang="ja">
      <head>
          <meta charset="utf-8">
          <title>ページタイトル</title>
          <meta name="keywords" content="キーワード1,キーワード2">
          <meta name="description" content="ページの説明">
      </head>
      <body>
          <div class="box" style="background-color:red;width:100px;height:100px"></div>
          <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
          <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
          <script type="text/javascript">
              $(function() {
                  $('.box').draggable();
              });
          </script>
      </body>
  </html>

これを実行してみるとこのようになります。

<script type="text/javascript">
      $(function() {
          $('.box').draggable();
      });
  </script>

というようにすると、boxというクラスの要素がドラッグ可能になります。

Chromeの要素の検証で確認しながら動かしていますが、以下のようにcssが自動で変更されていることがわかると思います。

position: relative; left: xpx; right: ypx;

jQueryUIを使えばdraggable()を使うだけでドラッグ可能になりますが、内部的にはcssを先程のように書き換えることで実装されていることが分かるのではないかなと思います。

表を実装していく

前置きが長くなってしまいましたがjQueryUIを使用してここから表を実装していきます。

実際に作成したものは、冒頭でも載せましたがこのようなものです。

満たしたい要件としては、

  • 表の外にあるリストから表のマスにドラッグ&ドロップして追加することが出来る
  • 表の中からドラッグで移動することが出来る
  • 同じマスに複数要素が入ることも可能

ということでした。

実際のコードを以下に載せます。

<!DOCTYPE html>
  <html lang="ja">
      <head>
          <meta charset="utf-8">
          <title>ページタイトル</title>
          <meta name="keywords" content="キーワード1,キーワード2">
          <meta name="description" content="ページの説明">
      </head>
      <body>
          <div class="table"></div>
          <div class="addable-contents"></div>
          <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
          <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
          <script type="text/javascript">
              $(function() {
                  const add_list = ["あ", "い", "う"];
                  const column = 4;
                  const row = 4;
                  var now_drag_object = null;
                  var elements = [];

                  function create_keywords_table() {
                      var table_html = '</p><tr class="head"><th></th><p>';
                      for (var j = 1; j <= column; j++) {
                          table_html += '</p><th>' + j + '</th><p>';
                      };
                      for (var i = 1; i <= row; i++) {
                          table_html += '</p><tr class="content"><th>' + i + '</th><p>';
                          for (var j = 1; j <= column; j++) {
                              table_html += '</p><td class="droppable" data-row="'+ i +'" data-column="' + j + '"><p>';
                              $.each(elements, function(index, element) {
                                  if (element["row"] != i || element["column"] != j) return true;
                                  table_html += '</p><div class="table-content" data-id="' + element["id"] + '">' + element["name"] + '</div>';
                              });
                              table_html += '</td><p>';
                          };
                          table_html += '</tr><p>';
                      };
                      $('#table').html(table_html);
                  }

                  function create_keyword_list() {
                      const keyword_list_div = add_list.map(function(element, index, array) { return '</p><div class="addable-content">' + element + '</div><p>'}).join('');
                      $('.addable-contents').html(keyword_list_div);
                  }
  
                  function bind_droppable() {
                      $('.table-content').draggable({
                          start:function() {
                              now_drag_object = $(this);
                          },
                          stop: function(event, ui) {
                              create_contents();
                              now_drag_object = null;
                          }
                      });

                      $('.addable-content').draggable({
                          start:function() {
                              now_drag_object = $(this);
                          },
                          stop: function(event, ui) {
                              create_contents();
                              now_drag_object = null;
                          }
                      });

                      $('.droppable').droppable({
                          classes: {
                              "ui-droppable-hover": "ui-state-hover"
                          },
                          drop: function(event, ui) {
                              console.log("drop");
                              const row = $(this).data("row");
                              const column = $(this).data("column");
                              const id = now_drag_object.data("id");
                              const index = elements.map(function(element, index, array) { return element.id }).indexOf(id);
                              if (now_drag_object.hasClass("addable-content")) {
                                  elements.push({"id": elements.length + 1, "name": now_drag_object.text(), "row": row, "column": column});
                              } else {
                                  elements[index]["row"] = row;
                                  elements[index]["column"] = column;
                              }
                          }
                      });
                  };

                  function create_contents() {
                      create_keywords_table();
                      create_keyword_list();
                      bind_droppable();
                  }

                  create_contents();
              });
            </script>
        </body>
    </html>

コードをいきなり見てもわかりにくいと思うので1つずつ説明していこうと思います。
まず最初にドラッグ可能にするために、先程も簡単に説明しましたが、draggableメソッドを用います。

$('.table-content').draggable({
      start:function() {
          now_drag_object = $(this);
      },
      stop: function(event, ui) {
          create_contents();
          now_drag_object = null;
      }
    });

    $('.addable-content').draggable({
        // ドラッグ開始時の処理
        start: function() {
            now_drag_object = $(this);
        },
        // ドラッグ終了時の処理
        stop: function(event, ui) {
            create_contents();
            now_drag_object = null;
        }
    });

このようにしました。
表の中の要素と表の下にある要素にそれぞれdraggableを適用しました。
先程の例だと$('.box').draggable();というように何も引数に渡していませんでしたが、このように関数を渡すこともできます。
startの部分でドラッグし始めの処理、stopではなしたときの処理を渡すことが出来ます。

今回の場合now_drag_objectを定義してドラッグ中のオブジェクトを一時的に格納するようにしています。

次にdroppableを定義していきます。
droppableでドロップできる場所を定義することが出来ます。

$('.droppable').droppable({
      classes: {
          "ui-droppable-hover": "ui-state-hover"
      },
      drop: function(event, ui) {
          const row = $(this).data("row");
          const column = $(this).data("column");
          const id = now_drag_object.data("id");
          const index = elements.map(function(element, index, array) { return element.id }).indexOf(id);
          if (now_drag_object.hasClass("addable-content")) {
              elements.push({"id": elements.length + 1, "name": now_drag_object.text(), "row": row, "column": column});
          } else {
              elements[index]["row"] = row;
              elements[index]["column"] = column;
          }
      }
  });

このようにしました。
dropにdroppable上でdraggableがドロップされた場合に実行される処理を渡すことが出来ます。
classesの部分ではこのように定義することで、hoverされたときにui-state-hoverというクラスがつくように出来て、今回の場合は背景色を変えるために使用しています。

今回の実装で使用したjQueryUIの機能は以上です。
これら以外は普通のjQueryで実装していきました。

どのように実装したかというと、elementsという配列を定義して、表の中に入った要素を管理するようにしていて、draggableとdroppableのイベントが発行されるたびにelementsを参照して表を1から全て作り直しています。
このようにしたのは、表のマスにドラッグ&ドロップした際にマスの中心にいいかんじにフィットしてくれるようにするのが、jQueryUIの機能だけでは(もしかしたら用意されているかもしれませんが)、なかったからです。

1つ1つ説明していきます。
表のマスそれぞれにdroppableを適用していて、draggableの要素をドラッグ&ドロップするとdropに渡した関数を実行します。
マスはdata-rowとdata-column属性を持っていてどのマスかが分かるようになっています。先ほど説明したようにドラッグしている要素はnow_drag_objectに格納されているので、その要素が表の下のリストであれば、elements配列に追加、表の中の要素であればrowとcolumnを書き換えるようにしています。
その後にdraggableのstopに渡した関数が呼ばれ、表を1から作成し直すcreate_contentsメソッドを呼び出し、その後にnow_drag_objectをnullにします。

1つ気になったのが、draggableのdropイベントとdroppableのdropイベントがどちらが先に呼ばれるのかどうかというところで、それ次第でより複雑な実装になりそうではあったのですが、そこはうまく作られていて毎回、droppableのdropイベントが先に呼ばれるようになっていました。

まとめ

今回jQueryUIを使って簡単なサンプルを作成してみましたが、拡張性も高いので幅広く対応できるのではないかなと思います。この記事が参考になれば嬉しいです。

SNSでもご購読できます。

コメントを残す

*