uhyohyo.net

JavaScript初級者から中級者になろう

二章第八回 テーブルの操作

テーブルとは、こういうやつのことです。

<table>
  <tr>
    <td>test1</td>
    <td>test2</td>
  </tr>
  <tr>
    <td>test3</td>
    <td>test4</td>
  </tr>
</table>

テーブルの木構造

上のテーブルの木構造は、次のようだと考えられます。

table
|
├――tr
|  |
|  ├――td
|  |  |
|  |  └――#text
|  └――td
|     |
|     └――#text
└――tr
   |
   ├――td
   |  |
   |  └――#text
   └――td
      |
      └――#text

見づらいので、タグとタグの間の改行によって生じるテキストノードは省略しています。

しかし、実際は違います。

table
|
└――tbody
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    └――tr
       |
       ├――td
       |  |
       |  └――#text
       └――td
          |
          └――#text
        

table直下にあるのはtbodyという要素で、trはその下にあります。

また、他にもthead,tfootという要素もtableの子として存在する可能性があり、また、tbodyは複数存在する可能性があり、これらのせいでtr要素やその下のtd要素がいろいろな要素の子に分散してしまい、普通にノードを操作してはうまく操作できない可能性があります。thead,tfoot,tbodyについては次回で解説します。

そこで、特別なプロパティやメソッドを使って、テーブルを操作します。

HTMLTableElement

table要素のHTMLElementは、HTMLTableElementと呼ばれます。これは、いくつもの独自のプロパティやメソッドを持っています。

rows

そのうちの1つが、rowsプロパティです。これは、そのテーブルが持つTR要素全てがまとめて集まっています。これを通してテーブルのtr要素を操作します。

要素がいくつも集まっているということからNodeListが思い浮かぶと思いますが、このオブジェクトはHTMLCollectionといいます。HTML要素専用のNodeListという感じで、NodeListと同じように使うことができます(逆にNodeListにHTML要素以外の要素が入るのかというと、XMLを扱う場合に違いがでます。詳しくは第六章で解説します)。

例えば、

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <table id="aaaaa">
      <tr>
        <td>test1</td>
        <td>test2</td>
      </tr>
      <tr>
        <td>test3</td>
        <td>test4</td>
      </tr>
    </table>

    <script type="text/javascript">
      var table = document.getElementById('aaaaa');

      var collection = table.rows;

      console.log(collection.item(0).tagName);
    </script>
  </body>
</html>
        

この場合、いつものようにgetElementByIdでtable要素を取得し、rowsを変数collectionに代入し、その0番目のノードのtagNameを表示しています。rowsはtr要素の集まりなので、当然TRが表示されます。

こうして、テーブルのうちのtr要素を取得する方法がわかりました。

HTMLTableRowElement

tr要素のHTMLElementは、HTMLTableRowElementといいます。

これもいろいろなプロパティやメソッドを持っています。

cells

cellsプロパティは、rowsプロパティのtd版という感じです。そのtr要素が持つtd要素(とth要素)のHTMLCollectionです。

ちなみに、入っている順は、単に左からです。一番左が0番目、次が1番目・・・のようになっています。総じてHTMLCollectionは、文書順(タグが先にくる順)で入っています。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <table id="aaaaa">
      <tr>
        <td>test1</td>
        <td>test2</td>
      </tr>
      <tr>
        <td>test3</td>
        <td>test4</td>
      </tr>
    </table>

    <script type="text/javascript">
      var table = document.getElementById('aaaaa');

      var collection = table.rows;

      var tr = collection.item(0);
      var td = tr.cells.item(0);

      td.firstChild.nodeValue = "tttttttt";
    </script>
  </body>
</html>
        

左上のtd要素の中身が"tttttttt"に変わりました。

collectionの0番目のtr要素を変数trに代入し、プロパティcellsを通して0番目のtd要素を変数tdに代入しています。

tdのfirstChildはテキストノードなので、そのnodeValueを変更することで左上のtd要素が変更されました。

テーブルの操作

さて、これで、テーブルの行やセル(1つ1つのtdやth要素のこと)をたどって、末端のテキストノードくらいは操作できるようになりました。

次に、行を増やしたり、セルを増やしたりといったテーブル自体の操作について解説していきます。

列・セルの追加

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <table id="aaaaa">
      <tr>
        <td>test1</td>
        <td>test2</td>
      </tr>
      <tr>
        <td>test3</td>
        <td>test4</td>
      </tr>
    </table>

    <script type="text/javascript">
      var table = document.getElementById('aaaaa');

      var newtr = table.insertRow( table.rows.length );

      for(var i=0;i<2;i++){
        var newtd = newtr.insertCell( newtr.cells.length );
        newtd.appendChild( document.createTextNode('testtest'+i) );
      }
    </script>
  </body>
</html>
        

insertRow,insertCellという2つの新しいメソッドが登場しています。insertRowはtable要素のHTMLTableElementが、insertCellはtr要素のHTMLTableRowElementが持つメソッドだということがわかります。

insertRowは、テーブルに新しい列(tr要素)を追加して、そのtr要素を返すメソッドです。つまり、

table
|
└――tbody
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    |
    └――tr          ←これ(新しく追加されたtr)
        

を返すということになります。

createElementやappendChildなどをしなくても、insertRowを行った時点でもうtr要素が新しく作成されて、追加するところまでやってくれるわけですね。そして、これをいじれば、それがそのままテーブルに反映されるわけです。

また、appendChildは一番最後でしたが、insertRowは好きな場所に列を挿入することができます。入れる場所は、引数で数値で指定します。

指定のしかたは、その要素が「何番目になるか」というふうに指定します。今回は、table.rows.lengthです。table.rowsはtr要素のリストなので、要するにtr要素の数です。今回の場合も最初は0番から数えるので、もともと0〜(length-1)番目のTR要素があることになります。したがって、length番目に追加すると一番最後になるというわけです。

こうしてできたtr要素は、その下にtd要素などは持っていません。そこで、tr要素にセルを追加します。

さて、それでは、追加している部分はその下のfor文です。このfor文は何をしているかというと、「2回繰り返す」ということになります。iが0,iが1のときに処理が行われ、2回処理が行われてi++によってiが2になり終了します。

これは、同じような処理を2回書くのが面倒なので、for文で繰り返すようにしたというだけです。

それでは、for文の中身を見てみると、ここでinsertCellが出てきています。これは、insertRowと同じく、tr要素にtd要素を追加して、それを返すというものです。引数も、insertRowと同じように指定します。

つまり、newtdには新しくできた

table
|
└――tbody
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    |
    └――tr
         |
         └――td       ←これ
        

が代入されるということになります。

これも、insertRowと同じく空なので、中身のテキストノードを追加する必要があります。その処理が次の行です。td要素にappendChildで、createTextNodeで作ったテキストノードが追加されています。中身であるcreateTextNodeの引数は、「'testtest'+i」で、この変数iはループのカウンタだから、1回目(iが0)は'testtest0'、2回目(iが1)は'testtest1'となります。

つまり、最終的に

table
|
└――tbody
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    ├――tr
    |  |
    |  ├――td
    |  |  |
    |  |  └――#text
    |  └――td
    |     |
    |     └――#text
    |
    └――tr
       |
       ├――td
       |  |
       |  └――#text      "testtest0"
       └――td
          |
          └――#text      "testtest1"
      

が代入されるということになります。画面の結果を見るとそのとおりになっているのがわかります。

列・セルの削除

逆に、列やセルを削除する方法もあります。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <table id="aaaaa">
      <tr>
        <td>test1</td>
        <td>test2</td>
      </tr>
      <tr>
        <td>test3</td>
        <td>test4</td>
      </tr>
    </table>

    <script type="text/javascript">
      var table = document.getElementById('aaaaa');

      table.deleteRow(0);
    </script>
  </body>
</html>
      

「test1」「test2」の行が消えてしまいました。

これは、2行しかありませんね。1行目はtable要素の取得で、2行目でdeleteRowというメソッドを呼び出しています。

引数は数字ですね。このメソッドは、(引数)番目の行を消すというものです。消すといっても、removeChildなどと同じで、オブジェクトそのものは残っていて、木構造からなくなるということです。

今回は引数が0なので、0番目の行、つまり最初の行が消えたというわけです。removeChildなどと同じように、子ノードがあってもまとめて消えます。

列の番号

removeChildなどでは引数として消すノードのオブジェクトを渡したのに、insertRowやdeleteRowでは番号を引数として渡しています。これは、たまに、特にdeleteRowで不都合となることがあります。

先に消したいtr要素のHTMLElementが手に入った場合など、どうするのでしょう。

実は、それを解決してくれるrowIndexというプロパティをtr要素は持っています。ずばり、テーブルの中でそのtrが何番目かを表します。一番最初は例のごとく0番目です。これを使えば、簡単に何番目か知ることができます。

さて、列も同じように削除することができます。tr要素のオブジェクトが持つdeleteCellを使います。deleteRowと同じように番号を引数に渡します。

ちなみに、tr要素ではrowIndexだった自身の番号は、td,th要素ではcellIndexにあたります。