2011年3月23日水曜日

テーブルの一部の列だけソートする

先日、仕事でちょっと変わったJavaScriptのインタフェースを作成したので紹介します。
応募者を面接の枠に割る振ることで、面接時間の設定をするというものだったのですが、
  1. 応募者を面接枠の中にドラッグ&ドロップすることで、割振りをできるようにしたい。
  2. 割り振った応募者を面接表内をドラッグすることで、順番を入れかえられるようにしたい。
  3. キャンセル(応募者を面接枠から外す)ができるようにしたい。
という要望がありました。

下にあるのがこの2つの要望を考慮して作成したプロトタイプになります。
一度ドラッグした応募者は、名前をダブルクリックすることで元に戻すことができます。

このプログラムは以下の2つの部分に分けることができます。
それぞれの詳しい説明を下記に記します。

  1. 一覧(table)から特定の行の情報をドラッグ&ドロップで移動させる。
  2. 一覧(table)内の特定の列のみドラッグによるソートができるようにする。
  1. 一覧(table)から特定の行の情報をドラッグ&ドロップで移動させる
    ドラッグ&ドロップをするには、jquery UIのdraggableとdroppableを利用します。
    まず最初にドラッグ対象のブロック(本サンプルの場合、レポート画像)をdraggableにし、その際「revert」オプションをtrueにします。これによって、画像がドロップ対象のセル以外の場所にドロップされた場合、勝手に元の位置に戻るようになります。
    $('.draggable').draggable({revert:true, revertDuration:50});
    
    次にセル内にドロップした場合の処理ですが、
    1. ドラッグしたブロックを非表示にする。
    2. 新しい項目を作成して、新しい項目をドロップしたセルの子要素にする。
    3. ドラッグ元の行の色を変更する。
    ということをします。
    var changeRowColor = function(id, color) {
       $("#" + id).find("td").css('background-color', color);
     }
    
    drop:function(e, ui) {
          // ドラッグしたセルに名前を表示して、ドラッグ項目を非表示にする
          // そうするとドラッグした項目が名前に変わって表示されるように見える。
          ui.draggable.hide();
    
          // div要素を作成して、ドラッグしたセルの子要素にする
          $("
    ").text($("#name" + (ui.draggable.attr("id").replace("drag", ""))).text()) .dblclick( function() { $(this).remove(); ui.draggable.show(); // ドラッグ元の行の色を変更する changeRowColor("row" + ui.draggable.attr("id").replace("drag", ""), "#fff"); } ) .appendTo(this) .css('height', ($(this).height() - 5) + "px") .addClass("cell_item") .attr("id", "item_" + (ui.draggable.attr("id").replace("drag", ""))); // ドラッグ元の行の色を変更する changeRowColor("row" + ui.draggable.attr("id").replace("drag", ""), "#bbb"); } });
    最後に新しく作成したブロックをダブルクリックしたとき、非表示にしたブロックを表示させ、ダブルクリックしたブロックを消去すれば、ダブルクリックしたときに、ドラッグ前の状態に戻ったように見えます。

    全ソースはこちら
    <html>
       <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/base/jquery-ui.css" type="text/css" media="all" /> 
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.core.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.widget.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.mouse.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.draggable.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.sortable.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.droppable.min.js" type="text/javascript"></script>
          <style type="text/css">
            #wrapper {
             width:400px;
             padding:10px;
             height:80px;
            }
            
            #col1 {
             height:10px;
             float:left;
             width:140px;
            }
            #col2 {
             height:10px;
             width:250px;
             float:left;
            }
              table.name_list,
              .name_list td {
                border:1px solid #999999;
                border-collapse:collapse;
                padding:5px;
              }
              div.draggable {
                padding:3px;
              }
              
              #col2 table,
              #col2 th,
              #col2 table td {
                font-weight:normal;
                border:1px solid #999999;
                border-collapse:collapse;
                font-size:90%;
                padding:0px;
              }
              #col2 table {
                width:250px;
              }
              #col2 th {
                width:16%;
                padding:5px;
              }
              #col2 td.droppable {
                width:64%;
              }
              div.cell_item {
                padding:5px 5px 0px;
                margin:0px;
              }
          </style>
      <script>
      $(function() {
    
        /**
         * 指定したIdの子供のセル(td)の色を変更する
         */
        var changeRowColor = function(id, color) {
          $("#" + id).find("td").css('background-color', color);
        }
    
        $('.draggable').draggable({revert:true, revertDuration:50});
        $(".droppable").droppable(
          {accept:function(drag) {
             return (drag.hasClass("draggable"));
          }
          , drop:function(e, ui) {
                // ドラッグしたセルに名前を表示して、ドラッグ項目を非表示にする
                // そうするとドラッグした項目が名前に変わって表示されるように見える。
                ui.draggable.hide();
    
                // div要素を作成して、ドラッグしたセルの子要素にする
                $("<div></div>").text($("#name" + (ui.draggable.attr("id").replace("drag", ""))).text())
                .dblclick(
                  function() {
                     $(this).remove();
                     ui.draggable.show();
                     // ドラッグ元の行の色を変更する
                     changeRowColor("row" + ui.draggable.attr("id").replace("drag", ""), "#fff");
                  }
                )
                .appendTo(this)
                .css('height', ($(this).height() - 5) + "px")
                .addClass("cell_item")
                .attr("id", "item_" + (ui.draggable.attr("id").replace("drag", "")));
    
                // ドラッグ元の行の色を変更する
                changeRowColor("row" + ui.draggable.attr("id").replace("drag", ""), "#bbb");
              }
            });
    
       $(".cell_item").draggable({span:true, snapMode:"inner", axis:"y", revert:true, revertDuration:0})
                      .dblclick(function() {
                         // ダブルクリックしたら、自分自身を削除し、ドラッグ元のレポート画像を表示させる。
                         // これによって、ドラッグ&ドロップ前に戻ったように見せかける。
                         if ($("#drag" + $(this).attr("id").replace("item", "")).length) {
                            $("#drag" + $(this).attr("id").replace("item", "")).css("display", "block");
                            changeRowColor("row" + $(this).attr("id").replace("item", ""), "#fff");
                            $(this).remove();
                         }
                      });
      });
      </script>
       </head>
    <body>
    <div id="wrapper">
      <div id="col1">
       <table class="name_list">
         <tr id="row1">
           <td id="name1">テスト太郎</td>
           <td style="width:25px;height:35px;"><div class="draggable" id="drag1"><img src="https://sites.google.com/site/tatehiro/report.gif"></div></td>
         </tr>
       </table>
      </div>
      <div id="col2">
       <table>
          <tr>
             <th>①-1</th>
             <td id="time_cell1" class="droppable"></td>
          </tr>
       </table>
      </div>
    </div>
    </body>
    </html>
    
  2. 一覧(table)から特定の列のみドラッグによるソートができるようにする。
    こちらは、ドラッグしたことによって、空いたセルに向かって、他の項目を一つずつずらしているだけです。
    ソースはこちら
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
       <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/base/jquery-ui.css" type="text/css" media="all" /> 
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.core.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.widget.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.mouse.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.draggable.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.sortable.min.js" type="text/javascript"></script>
    <script src="https://sites.google.com/site/tatehiro/jquery.ui.droppable.min.js" type="text/javascript"></script>
    <style type="text/css">
        #col2 table,
        #col2 th,
        #col2 table td {
          font-weight:normal;
          border:1px solid #999999;
          border-collapse:collapse;
          font-size:90%;
          padding:0px;
        }
        #col2 table {
          width:150px;
        }
        #col2 th {
          width:40%;
          padding:5px;
        }
        #col2 td.droppable {
          width:60%;
        }
        div.cell_item {
          padding:5px 5px 0px;
          margin:0px;
        }
    </style>
    <script>
    $(function() {
      var dropList = new Array();
      for (var i = 0; i < $('.droppable').size(); i++) {
        dropList.push($('.droppable').get(i).id);
      }
      $(".droppable").droppable(
        {accept:function(drag) {
           return (drag.hasClass("cell_item"));
        }
        , drop:function(e, ui) {
            
            /**
             * 指定した値を持ったキーを取得する
             */
            var searchKey = function(item, list) {
              for (var key in list) {
                 if (list[key] == item) {
                    return Number(key);
                 }
              }
            }
            
            /**
             * 新しいIdを取得する
             */
            var getNewId = function(baseObject, goUp) {
              var increment = (goUp) ? -1 : 1;
              var key = searchKey(baseObject.attr("id"), dropList);
              return "#" + dropList[(key + increment)];
            }
            
            /**
             * 項目の入れ替えをする(親要素を交換する)。
             */
            var exchangeParent = function(parent, item, fromup) {
              item.appendTo("#" + parent.attr("id"));
              if (parent.children("div").length > 1) {
                 var newId = getNewId(parent, fromup);
                 exchangeParent($(newId), parent.children("div:first"), fromup);
              }
            }
            
            /**
             * 枠セルのIdを比較する
             */
            var compareTimeCellId = function (id1, id2) {
              id1 = searchKey(id1, dropList);
              id2 = searchKey(id2, dropList);
              //return ((id1 - id2) < 0);
              return (id1 - id2);
            }
            
            /*------------------ ここからが実際の処理 --------------------------------------------*/
            // 要素入れ替え処理をする
            var fromUP = compareTimeCellId(ui.draggable.parent().attr("id"), $(this).attr("id"));
            exchangeParent($(this), ui.draggable, (fromUP < 0));
          }
        });
    
     $(".cell_item").draggable({span:true, snapMode:"inner", axis:"y", revert:true, revertDuration:0})
    });
    </script>
    </head>
    <body>
      <div id="col2" align="center">
       <table>
          <tr>
             <th>①-1</th>
             <td id="time_cell1" class="droppable"><div class="cell_item">花子</div></td>
          </tr>
          <tr>
             <th>①-2</th>
             <td id="time_cell3" class="droppable"><div class="cell_item">太郎</div></td>
          </tr>
          <tr>
             <th>①-3</th>
             <td id="time_cell4" class="droppable"><div class="cell_item">次郎</div></td>
          </tr>
       </table>
      </div>
    <!-- End demo-description -->
    </body>
    </html>
    
最後に、最初のサンプルの全ソースをここに記述しておきます。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/base/jquery-ui.css" type="text/css" media="all" /> 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
<script src="https://sites.google.com/site/tatehiro/jquery.ui.core.min.js" type="text/javascript"></script>
<script src="https://sites.google.com/site/tatehiro/jquery.ui.widget.min.js" type="text/javascript"></script>
<script src="https://sites.google.com/site/tatehiro/jquery.ui.mouse.min.js" type="text/javascript"></script>
<script src="https://sites.google.com/site/tatehiro/jquery.ui.draggable.min.js" type="text/javascript"></script>
<script src="https://sites.google.com/site/tatehiro/jquery.ui.sortable.min.js" type="text/javascript"></script>
<script src="https://sites.google.com/site/tatehiro/jquery.ui.droppable.min.js" type="text/javascript"></script>
      <style type="text/css">
        #wrapper {
         width:500px;
         padding:0px;
        }
        
        #col1 {
         height:150px;
         float:left;
         width:198px;
        }
        #col2 {
         height:150px;
         width:298px;
         float:left;
        }
          table.name_list,
          .name_list td {
            border:1px solid #999999;
            border-collapse:collapse;
            padding:5px;
          }
          div.draggable {
            padding:3px;
          }
          
          #col2 table,
          #col2 th,
          #col2 table td {
            font-weight:normal;
            border:1px solid #999999;
            border-collapse:collapse;
            font-size:90%;
            padding:0px;
          }
          #col2 table {
            width:250px;
          }
          #col2 th {
            width:16%;
            padding:5px;
          }
          #col2 td.droppable {
            width:64%;
          }
          div.cell_item {
            padding:5px 5px 0px;
            margin:0px;
          }
      </style>
  <script>
  $(function() {

    // 面接リストをドラッグ可能にする
    $("#stair_chart_reserve").draggable({ axis: "y",cancel:'.droppable' });

    /**
     * 指定したIdの子供のセル(td)の色を変更する
     */
    var changeRowColor = function(id, color) {
      $("#" + id).find("td").css('background-color', color);
    }

    var dropList = new Array();
    for (var i = 0; i < $('.droppable').size(); i++) {
      dropList.push($('.droppable').get(i).id);
    }
    $('.draggable').draggable({revert:true, revertDuration:50});
    $(".droppable").droppable(
      {accept:function(drag) {
         return (drag.hasClass("draggable")
                  || drag.hasClass("cell_item"));
      }
      , drop:function(e, ui) {
          
          /**
           * 指定した値を持ったキーを取得する
           */
          var searchKey = function(item, list) {
            for (var key in list) {
               if (list[key] == item) {
                  return Number(key);
               }
            }
          }
          
          /**
           * 新しいIdを取得する
           */
          var getNewId = function(baseObject, goUp) {
            var increment = (goUp) ? -1 : 1;
            var key = searchKey(baseObject.attr("id"), dropList);
            return "#" + dropList[(key + increment)];
          }
          
          /**
           * 項目の入れ替えをする(親要素を交換する)。
           */
          var exchangeParent = function(parent, item, fromup) {
            item.appendTo("#" + parent.attr("id"));
            if (parent.children("div").length > 1) {
               var newId = getNewId(parent, fromup);
               exchangeParent($(newId), parent.children("div:first"), fromup);
            }
          }
          
          /**
           * 枠セルのIdを比較する
           */
          var compareTimeCellId = function (id1, id2) {
            id1 = searchKey(id1, dropList);
            id2 = searchKey(id2, dropList);
            //return ((id1 - id2) < 0);
            return (id1 - id2);
          }
          
          /**
           * 空のセルがあるかを探索する
           */
          var searchEmptyCell = function(cell, goUp) {
            var increment = (goUp) ? -1 : 1;
            var newId = getNewId(cell, goUp);
            if (!$(newId).length) {
               return false;
            }
            else if ($(newId).children("div").length > 0) {
               return searchEmptyCell($(newId), goUp);
            }
            else {
               return newId;
            }
          }
          
          /*------------------ ここからが実際の処理 --------------------------------------------*/
          // 一覧内の項目をドラッグした場合の処理
          if (!ui.draggable.hasClass("draggable")) {
            // ドラッグ項目でなければ(既に一覧に設定している項目であれば)、要素入れ替え処理をする
            var fromUP = compareTimeCellId(ui.draggable.parent().attr("id"), $(this).attr("id"));
            exchangeParent($(this), ui.draggable, (fromUP < 0));
          }
          // 学生一覧から学生を持ってきた場合の処理
          else {
            // ドラッグした先に既に学生が登録されていたら、登録されている学生の位置をずらす
            // 空いているセルがなければ、何もせずに処理を終了する。
            // そうするとドラッグしていたセルが元の位置に戻っていく。
            if ($(this).children("div:first").length) {
               if (searchEmptyCell($(this), false)) {
                  exchangeParent($(getNewId($(this), false)), $(this).children("div:first"), false);
               }
               else if (searchEmptyCell($(this), true)) {
                  exchangeParent($(getNewId($(this), true)), $(this).children("div:first"), true);
               }
               else {
                  return;
               }
            }
            // ドラッグ項目なら、ドラッグしたセルに名前を表示して、ドラッグ項目を非表示にする
            // そうするとドラッグした項目が名前に変わって表示されるように見える。
            ui.draggable.hide();
            //alert(searchEmptyCell($(this), false));
            $("<div></div>").text($("#name" + (ui.draggable.attr("id").replace("drag", ""))).text())
            .dblclick(
              function() {
                 $(this).remove();
                 ui.draggable.show();
                 // ドラッグ元の行の色を変更する
                 changeRowColor("row" + ui.draggable.attr("id").replace("drag", ""), "#fff");
              }
            )
            .appendTo(this)
            .css('height', ($(this).height() - 5) + "px")
            .draggable({span:true, snapMode:"inner", axis:"y", revert:true, revertDuration:0})
            .addClass("cell_item")
            .attr("id", "item_" + (ui.draggable.attr("id").replace("drag", "")));
            // ドラッグ元の行の色を変更する
            changeRowColor("row" + ui.draggable.attr("id").replace("drag", ""), "#bbb");
          }
        }
      });

   $(".cell_item").draggable({span:true, snapMode:"inner", axis:"y", revert:true, revertDuration:0})
                  .dblclick(function() {
                     if ($("#drag" + $(this).attr("id").replace("item", "")).length) {
                        $("#drag" + $(this).attr("id").replace("item", "")).css("display", "block");
                        changeRowColor("row" + $(this).attr("id").replace("item", ""), "#fff");
                        $(this).remove();
                     }
                  });
   $("#submit").click(function() {
      // 一覧の名前があるセルから枠のIdと名前のIdを取得する
      var itemList = new Object();
      $('.droppable').each(function() {
         if ($(this).children("div:first").length) {
            $('<input type="hidden">').attr("name", "timecell[" + $(this).attr("id").replace("time_cell", "") + "]")
                                      .attr("value", $(this).children("div:first").attr("id").replace("item_", ""))
                                      .appendTo("#stair_chart_reserve");
         }
      });
   });
  });
  </script>
</head>
<body>
<div id="wrapper">
  <div id="col1">
   <table class="name_list">
     <tr id="row1">
       <td id="name1">テスト太郎</td>
       <td>○○大学</td>
       <td style="width:30px;"><div class="draggable" id="drag1"><img src="https://sites.google.com/site/tatehiro/report.gif"></div></td>
     </tr>
     <tr id="row2">
       <td id="name2">テスト次郎</td>
       <td>○○大学</td>
       <td><div id="drag2" class="draggable"><img src="https://sites.google.com/site/tatehiro/report.gif"></div></td>
     </tr>
     <tr id="row3">
       <td id="name3">テスト三郎</td>
       <td>○○大学</td>
       <td><div class="draggable" id="drag3"><img src="https://sites.google.com/site/tatehiro/report.gif"></div></td>
     </tr>
   </table>
  </div>
  <div id="col2">
   <table>
      <tr>
         <th>①-1</th>
         <td id="time_cell1" class="droppable"></td>
      </tr>
      <tr>
         <th>①-2</th>
         <td id="time_cell3" class="droppable"></td>
      </tr>
      <tr>
         <th>①-3</th>
         <td id="time_cell4" class="droppable"></td>
      </tr>
   </table>
  </div>
</div>
<!-- End demo-description -->
</body>
</html>
※追伸 >- この投稿が役にたったと思った方は、上か下の広告をクリックしていただけるとうれいいです。

1 件のコメント:

  1. Wynn Las Vegas - Mapyro
    Wynn Hotel 보령 출장샵 and Casino Map, Las Vegas. Las Vegas Metropolitan Area. 서산 출장안마 1.6 million people 안산 출장마사지 checked in 남양주 출장안마 here every week. See map location, 상주 출장마사지 reviews, directions,

    返信削除