maesblog

JavaScript / jQueryでtableの行を「追加」「削除」「移動」「変更」させる方法

昨年のこととなりますが、仕事で開発しているシステムの中に、メールソフトなどによくある「フィルタリング設定」のような機能を実装しました。フィルタリングの条件をリストに追加し、リストを上下に移動することで優先順位を変更でき、さらに内容も変更でき、必要のないものは削除するといったものです。

今、この「リストを操作する機能」を使って、このブログに新しい機能を追加してみようと考えています。それに先立ち、思い出す意味も含めてブログにまとめてみることにしました。以下、参考になればと思います。

サンプル

まずは、どんな機能化ということで、サンプルを挙げておきます。要は「テーブルの行を思うがままにJavaScriptで操作してしまおう」といった機能となります。

< サンプル >

リスト選択リスト名リスト順追加/削除
A 上へ  下へ 

手っ取り早く機能を確認するには、以下の方法をお試しください。

  • まず「リスト選択」の列の選択肢から「B」を選択します。
     ⇒「リスト名」が「B」に変更されます。(変更)
  • 「追加/削除」の列の「+」ボタンを押します。
     ⇒行が追加されます。(追加)
  • 追加された行の「リスト順」の列の「↑」をクリックします。
     ⇒行が上へ移動します。(移動)
  • 移動した行の「リスト順」の列の「↓」をクリックします。
     ⇒行が下へ移動します。(移動)
  • 最後に、好きな行の「追加/削除」の列の「-」ボタンを押します。
     ⇒行が削除されます。(削除)

いかがでしょうか。まあ、よくある機能ですね。では、この機能の実装方法について以下にまとめておきたいと思います。

HTML

HTMLでは、以下の点がポイントとなります。

  • それぞれの機能を発生させるためのイベントハンドラをセットしておきます。
  • 操作の対象となる「行」の部分は<tbody>タグ内に記述するようにします(これが後々大事になります)。
  • JavaScript側ではDOMを使用しますが、ブラウザーによって「改行」をノードとして扱うものがあったりなかったりするので、一切改行を入れずに記述します。
<table id="p2146-table">
  <thead>
    <tr>
      <th>リスト選択</th>
      <th>リスト名</th>
      <th>リスト順</th>
      <th>追加/削除</th>
    </tr>
  </thead>

<!--▼改行しない▼-->
<tbody id="p2146-tbody"><tr><td><select onchange="changeList(this);"><option>A</option><option>B</option><option>C</option></select></td><td>A</td><td><img src="up.png" alt="↑" onclick="upList(this);" />上へ <img src="down.png" alt="↓" onclick="downList(this);" />下へ</td><td><input type="button" value="+" onclick="addList(this);" /> <input type="button" value="-" onclick="removeList(this);" /></td></tr></tbody>
<!--▲改行しない▲-->

</table>

JavaScript

JavaScriptでは、HTML内に記述したイベントハンドラによって呼び出される関数をそれぞれ定義します。

行を追加する

テーブル内の「+」ボタンが押されると、以下のaddList()関数が呼び出されます。処理内容としては、テーブルの1行目の「行(tr要素)」のクローンを作成し、それを「+」ボタンが押された行の下に追加します。

function addList(obj) {
  // tbody要素に指定したIDを取得し、変数「tbody」に代入
  var tbody = document.getElementById('p2146-tbody');
  // objの親の親のノードを取得し(つまりtr要素)、変数「tr」に代入
  var tr = obj.parentNode.parentNode;
  // tbodyタグ直下のノード(行)を複製し、変数「list」に代入
  var list = tbody.childNodes[0].cloneNode(true);
  // 複製した行の2番目のセルを指定し、変数「td」に代入
  var td = list.childNodes[1];
  // 複製した行の2番目のセルの内容を「A」に置き換え
  td.textContent = 'A';
  // 複製したノード「list」を直後の兄弟ノードの上に挿入
  // (「tr」の下に挿入)
  tbody.insertBefore(list, tr.nextSibling);
}

行を削除する

テーブル内の「-」ボタンが押されると、以下のremoveList()関数が呼び出されます。処理内容としては、単純に「-」ボタンが押された行を削除します。

function removeList(obj) {
  // tbody要素に指定したIDを取得し、変数「tbody」に代入
  var tbody = document.getElementById('p2146-tbody');
  // objの親の親のノードを取得し(つまりtr要素)、変数「tr」に代入
  var tr = obj.parentNode.parentNode;
  // 「tbody」の子ノード「tr」を削除
  tbody.removeChild(tr); 
}

行を一つ上に移動させる

テーブル内の「↑」が押されると、以下のupList()関数が呼び出されます。処理内容としては、「↑」が押された行の上に「行が存在した場合」に、その行の上に挿入させます。

function upList(obj) {
  // tbody要素に指定したIDを取得し、変数「tbody」に代入
  var tbody = document.getElementById('p2146-tbody');
  // objの親の親のノードを取得し(つまりtr要素)、変数「tr」に代入
  var tr = obj.parentNode.parentNode;

  // もし「tr」の直前の兄弟ノード名が「TR」だった場合
  // (上に「行」が存在している場合)
  if(tr.previousSibling.nodeName === 'TR') {
    // 「tr」を直前の兄弟ノードの上に挿入
    tbody.insertBefore(tr, tr.previousSibling);
  }
}

行を一つ下に移動させる

テーブル内の「↓」が押されると、以下のdownList()関数が呼び出されます。処理内容としては、「↓」が押された行の下に「行が存在した場合」に、その行の下に挿入させます。

function downList(obj) {
  // tbody要素に指定したIDを取得し、変数「tbody」に代入
  var tbody = document.getElementById('p2146-tbody');
  // objの親の親のノードを取得し(つまりtr要素)、変数「tr」に代入
  var tr = obj.parentNode.parentNode;

  // もし「tr」の直前の兄弟ノード名が「TR」だった場合
  // (上に「行」が存在している場合)
  if(tr.nextSibling.nodeName === 'TR'){
    // 「tr」を直後の兄弟ノードの上に挿入
    tbody.insertBefore(tr.nextSibling, tr);
  }
}

行の一部を変更する

テーブル内の<select>要素のオプションが選択されると、以下のchangeList()関数が呼び出されます。処理内容としては、新たにtd要素を作成し、その要素内を選択されたオプションの値を取得して置き換え、さらに既存のtd要素と置き換える。

function changeList(obj) {
  // 選択したオプションの値を取得し、変数「type」に代入
  var type = obj.value;
  // objの親の親のノードを取得し(つまりtr要素)、変数「tr」に代入
  var tr = obj.parentNode.parentNode;
  // 「tr」の2番目のセルを指定し、変数「td」に代入
  var td = tr.childNodes[1];

  // 新たにtd要素を作成し、変数「cell」に代入
  var cell = document.createElement('td');
  // 「cell」内のHTMLを「type」に置き換え
  cell.innerHTML = type;
  // 「td」を「cell」に置き換え
  tr.replaceChild(cell, td);
}

以上のような感じで実装を試みたわけですが、純粋なJavaScriptのみでDOMを扱うとなると、ブラウザーによる挙動が不確定でわけがわからなくなります。そこでjQueryを使って書き換えてみることにしました。jQueryを使うことによって、以下のようにだいぶシンプルにコードを書くことができます。

jQueryを使って実装

jQueryを使う場合も、上記の純粋なJavaScriptのみで実装する時とほぼ同じような考え方で実装していくことができますが、一箇所だけ大きく異なる点があります。

行を追加する際に、上記ではテーブルの1行目の行を複製して挿入しましたが、今回は処理を簡単にするために、その複製用の1行目の行をCSSで非表示にしています。そのためテーブルを表示させる際に、その非表示とした1行目の行を複製して表示させる処理を追加しています。

HTML

jQueryを使うと、ブラウザーごとのDOMにおける改行の扱いを気にする必要はありません。HTMLも自分の見やすいようにコーディング可能です。今回はイベントハンドラではなく、jQueryで処理をしやすくするためにクラス属性をセットしておきます

<table id="p2146-2-table">
  <thead>
    <tr>
      <th>リスト選択</th>
      <th>リスト名</th>
      <th>リスト順</th>
      <th>追加/削除</th>
    </tr>
  </thead>

  <tbody id="p2146-2-tbody">
    <tr>
      <td>
        <select class="changeList">
          <option>A</option><option>B</option><option>C</option>
        </select>
      </td>
      <td>A</td>
      <td>
        <button class="upList">⬆️上へ</button> 
        <button class="downList">⬇️下へ</button>
      </td>
      <td>
        <input value="+" type="button" class="addList" /> 
        <input value="-" type="button" class="removeList" />
      </td>
    </tr>
  </tbody>
</table>

CSS

先にも述べたように、1行目の行を複製用のサンプルコードとして非表示にします。

#p2146-2-tbody tr:first-child {
  display: none;
}

jQuery(JavaScript)

ポイントとしては、jQuery 1.7から追加された.on()メソッドを使ってイベントをバインドしています。jQuery 1.7以前であれば.live()メソッドを使うところだと思いますが、この.onメソッドは、その.live()メソッドをはじめとして、.bind()メソッド、.delegate()メソッドの機能が統合された強力な機能を持つメソッドとなっています。

.on()メソッド

$(elements).on( events [, selector] [, data] , handler );
.on() – jQuery API

jQueryのソースコードは以下となります。

$(document).ready(function() {
  // CSSで非表示にした1行目の行を複製し、その行の下に挿入
  $('#p2146-2-tbody>tr')
    .clone(true)
    .insertAfter($('#p2146-2-tbody>tr'));

  // 行を追加する
  $(document).on('change', '.changeList', function() {
    $(this)
      .parent()
      .next()
      .html($(this).val());
  });

  // 行を削除する
  $(document).on('click', '#p2146-2-tbody>tr:gt(1) .upList', function() {
    var t = $(this)
      .parent()
      .parent();
    if (t.prev('tr')) {
      t.insertBefore(t.prev('tr')[0]);
    }
  });

  // 行を一つ上に移動させる
  $(document).on('click', '.downList', function() {
    var t = $(this)
      .parent()
      .parent();
    if (t.next('tr')) {
      t.insertAfter(t.next('tr')[0]);
    }
  });

  // 行を一つ下に移動させる
  $(document).on('click', '.addList', function() {
    $('#p2146-2-tbody>tr')
      .eq(0)
      .clone(true)
      .insertAfter(
        $(this)
          .parent()
          .parent(),
      );
  });

  // 行の一部を変更する
  $(document).on('click', '.removeList', function() {
    $(this)
      .parent()
      .parent()
      .remove();
  });
});

< jQueryで実装したサンプル >

上記のJavaScriptでの実装のサンプルと特に動きは変わらないかと思います。

See the Pen Tableの行入れ替えjQueryサンプル by Takanori Maeda (@maechabin) on CodePen.

https://codepen.io/maechabin/pen/bjYQRa

まとめ

以上のように、tableの行を操作する機能について紹介しましたが、この機能の使い道として考えられることとしては、やはりajax関連の機能ではないでしょうか。最初にも書きましたが、現在このブログに新しい機能を実装しようと考えています。

その機能というのが、簡単に言えば「後で読む」機能で、トップページの記事一覧にそれぞれ「後で読む」ボタンを設置しておき、そのボタンを押すと、サイドバーにリンクつきタイトルが追加されるといったものです。タイトルをクリックして記事にアクセスするとサイドバーの表示から消え、さらに順番も変更できるといったものです。これをHTML5のweb storageを使って実装できればなと思っています。

Web制作の現場で使うjQueryデザイン入門[改訂新版](ドーナッツ本)
  • 『Web制作の現場で使うjQueryデザイン入門[改訂新版]』(通称: ドーナツ本)
  • 著者: 西畑一馬
  • 出版社: KADOKAWA/アスキー・メディアワークス
  • 単行本: 312ページ
  • 発売日: 2013年3月7日
  • ISBN: 4048913913

関連記事

コメント

                • 必須

                コメント

                コメントをキャンセル