jQueryで行の最初のtdにだけclassを付与(未完成):メモ

悩み
悩み

バッドノウハウかもシリーズ。

以前から試してはいながらも上手く実現できなくかったこどとができたかもなので急いでメモ。
結局未完。

次回忘れている可能性が大なので…。

[2015.4.23 「改良版コード」を追記]
[2015.4.24 「改良版コード2」を追記]
[2015.5.21 「改良版コード3」を追記]
[2016.6.7 「改良版コード4」を追記]

構築環境
JavaScriptjQuery

実現したいこと

実現したいことは以下の通り。

  • 行の最初(左端)のtdにだけclassを付ける
  • rowspanでセルが減少した行の最初のtdは除く

言葉で書くとややこしいですが…。

rowspanを付けたセルの真下の行のセルは、不要になり削除します。html上はtr内のセルが一つ減るわけです。

この状態でfirst-childで左端のセルだけを指定しようとすると、rowspanの下のtrの最初のセル、つまり見た目上は左から2つ目のtdを指定する形になってしまいます。

本来は最初(左端)のtdだけを指定したいので、これでは目的に合致しません。

改良版コード4

今まで何を勘違いしていたのか、colspanを気にする必要がないことに今更気がつきました…。

無駄を承知でクラスを付与してからrowspanの検出時に不要なクラスを消す方法なので、colspanの有無にかかわらず先頭のtdにクラスを付けても問題ありません。

そのため、不要なコードを削って以下のように修正しました。


jQuery(function($){
      //先頭のtdすべてにクラスを付与
      $(this).find('td:first-child').addClass('first');
    });
    //左端にrowspanがあった場合、その数に応じて付与したfirstを削除
    $(this).find('tr').each(function() {
      if( $(this).children('td:first-child[rowspan="2"]').length > 0  ){
        $(this).next('tr').find('td:first-child').removeClass('first');
      }
      if( $(this).children('td:first-child[rowspan="3"]').length > 0  ){
        $(this).next('tr').find('td:first-child').removeClass('first');
        $(this).next('tr').next('tr').find('td:first-child').removeClass('first');
      }
      if( $(this).children('td:first-child[rowspan="4"]').length > 0  ){
        $(this).next('tr').find('td:first-child').removeClass('first');
        $(this).next('tr').next('tr').find('td:first-child').removeClass('first');
        $(this).next('tr').next('tr').next('tr').find('td:first-child').removeClass('first');
      }
    });
  });
});

不要な処理が消えるので、少し速くなると思います。

しかし、まだrowspanの方のコードをまとめる方法がわかりません…。イメージはあるのですが形にできない状態です。

改良版コード3

先頭以外でrowspanがあると上手く動かない状態であったため、更にコードを改良。
また、今までのコードでは1ページにtableが一つという状態にしか対応できなかったので、tableが複数存在しても対応出来るようにした。


jQuery(function($){
  $('table').each(function(){
    var cellNum = new Array();
    $(this).find('tr').each(function(num){cellNum[num] = $(this).children().length;});
    var maxNum = Math.max.apply(null,cellNum);
    //横に並ぶセルの最大数が5までと仮定
    var maxNum_1 = maxNum - 1;
    var maxNum_2 = maxNum - 2;
    var maxNum_3 = maxNum - 3;
    //最大セル数からの差とrowspanの数でfirst付与対象を選択
    $(this).find('tr').each(function() {
      var len = $(this).children().length;
      if(len == maxNum || len == maxNum_1){
        $(this).find('td:first-child').addClass('first');
      } else if (len == maxNum_2){
        if( $(this).children('td[colspan="3"]').length > 0){
          $(this).find('td:first-child').addClass('first');
        }
      } else if (len == maxNum_3){
        if( $(this).children('td[colspan="4"]').length > 0){
          $(this).find('td:first-child').addClass('first');
        }
      }
    });
    //左端にrowspanがあった場合、その数に応じて付与したfirstを削除
    $(this).find('tr').each(function() {
      if( $(this).children('td:first-child[rowspan="2"]').length > 0  ){
        $(this).next('tr').find('td:first-child').removeClass('first');
      }
      if( $(this).children('td:first-child[rowspan="3"]').length > 0  ){
        $(this).next('tr').find('td:first-child').removeClass('first');
        $(this).next('tr').next('tr').find('td:first-child').removeClass('first');
      }
      if( $(this).children('td:first-child[rowspan="4"]').length > 0  ){
        $(this).next('tr').find('td:first-child').removeClass('first');
        $(this).next('tr').next('tr').find('td:first-child').removeClass('first');
        $(this).next('tr').next('tr').next('tr').find('td:first-child').removeClass('first');
      }
    });
  });
});

一度付与したクラスを再度除去するため、かなり面倒というか無駄が多いコードかと思います。

とはいえ、rowspanの存在はやはり厳しく。

rowspanの影響

rowspanで統合されて列からセルが1つ減った行では、その行内には消えた理由を特定する術がありません。
視点を変えて「colspanがないのにセルの数が減っている」という点からの指定方法も考えましたが、「rowspanの影響で消えた行にcolspanがある」という場合には不具合がでます。

そのため、今回は「rowspanに指定した統合数を抽出して、その数に応じた分だけ続く行の最初のセルからクラスを削除する」という方法を考えた次第です。
具体的には以下の通り。

  • 左端がrowspa="2"の時は、「rowspanが存在するtr」の、次のtrの左端に付与したクラスを削除する
  • 左端がrowspa="3"の時は、「rowspanが存在するtr」の、次と、その次のtrの左端に付与したクラスを削除する
  • 左端がrowspa="4"の時は、「rowspanが存在するtr」の、次と、その次と、その次のtrの左端に付与したクラスを削除する

全条件を満たす過不足ない指定が無理なら、多めに付与してから不要部分を削除しよう、という方法です。

仮にrowspanへの対応を切り分けない場合は、rowspancolspanの全パターンを網羅しなければならなくなります。
セルの全数に制限があれば可能でしょうが、「rowspan="3"colspan="5"がある場合」「rowspan="2"が2つとcolspan="2"が3つある場合」などの場合分けを逐一行うのは、あまり現実的とは思えませんでした。

プログラマの方なら上手い具合にできるのかもしれませんが、私には難しく。

分解してから考える

実は、この辺りを上手い具合に解析してなんとかできそうな情報自体は以前に見つけていました。それが下記の記事です。

[jQuery] tableの内容を取得する

記事中にある「1-1」「2-1」「3-1」などがまさにそれですね。ここさえ特定できれば、あとはtdかどうかを判別してtdならクラスを付与で実現できるはずです。が、私では完成できずでした。

改良版コード2

colspanrowspanが入り乱れる場合を想定して、更に改良。


  var cellNum=new Array();
  $('table').find('tr').each(function(num){cellNum[num] = $(this).children().length;});
  var maxNum = Math.max.apply(null,cellNum);
  var maxNum_1 = maxNum1 - 1;
  var maxNum_2 = maxNum1 - 2;
  var maxNum_3 = maxNum1 - 3;
  $('tr').each(function() {
          var len = $(this).children().length;
          if(len == maxNum){
                     $(this).find('td:first-child').addClass('first');
          } else if (len == maxNum_1){
            if( $(this).children('td[colspan="2"]').length > 0 ){
                    $(this).find('td:first-child').addClass('first');
            }
          } else if (len == maxNum_2){
            if( $(this).children('td[colspan="3"]').length > 0 ){
                    $(this).find('td:first-child').addClass('first');
            }
          } else if (len == maxNum_3){
            if( $(this).children('td[colspan="4"]').length > 0 ){
                    $(this).find('td:first-child').addClass('first');
          }
        }
  });

要点を箇条書に。

  • tr内の最大セル数を計測するのはいままでと同じ
  • セル数が減る可能性があるのは、colspanによる結合か、rowspanの影響で次の行が減少するかの2パターン
  • 「セルの最大数が1減る場合のcolspanは2」という対応関係があるので、これを満たす場合は左端がtdでありクラスを付与
  • rowspanでの減少は先頭が減るためクラスを付与する必要はなく無視(セル数の減少をみるだけでいい)

かなりまどろっこいしですが、これでなんとかいけるかと。
数が1増えたり減ったりするのでここはまとめられると思いますが、うまくできていません。

また、左端のセルがthでありこれが横に2つ並んだり、tr内にちりばめられるとどうなるやら…。
通常こんなセルはあまりないとは思いますが、表の性質上いろいろな構造がありえますし、実際に使う際にイレギュラーな形にならざるを得ない可能性は否定できず。

改良版コード

以前のコードではIE8で動作しなかったのとcolspanが存在すると破綻しため、改良もかねて以下のようにコードを変更しました。
意図としては、

  • すべてのtrごとに含まれるセルの数を取得してから最大値を決定。
  • その最大値と同じ数のセルを持つtrだけを選んで処理を施す。

となります。


//新規の配列を用意
var cellNum=new Array();
//trごとのセル数を配列に保存
$('table').find('tr').each(function(num){cellNum[num] = $(this).children().length;});
//配列から最大値を取得
var maxNum = Math.max.apply(null,cellNum);
//セルの結合を考えて最大値よりも一つ下の値を取得
var notMaxNum = maxNum-- ;
$('table tr').each(function() {
//セルの数を取得
 var len = $(this).children().length;
//セルの数と最大値を比較
 if(len == maxNum){
//セルの数と最大値が同じなら最初のtdにクラスを付与
  $(this).find('td:first-child').addClass('first');
 } else if (len == notMaxNum1){
//セルの数と最大値-1が同じで、colspanが存在していたら最初のtdにクラスを付与
  if( $(this).children('td[colspan]').length > 0 ){
   $(this).find('td:first-child').addClass('first');
  }
 }
});

配列の中から最大値を取得する部分は下記のサイトを参考にしました。感謝です。
【jQuery】配列から最大値を求めて、div要素の高さを揃える

試行錯誤していますが、これもまた不完全です。

ソース上は左端のtdは明白なのですが、rowspancolspanの存在とそれぞれの結合数によってパターンは数多く存在します。組み合わせによってはセルの左端がテーブルの端にあるのかどうかが分かりません。

方法としては、rowspancolspanの組み合わせを網羅しつつ、それぞれの組み合わせごとに縦と横のどちらにいくつ結合されているのかを計測し、かつその場所が左端かそれ以外かでパターンを更にわけて…。
ということになるかもしれません。

IE8で動かない

ちなみに、以前のコードで動かなかったのは以下の部分でした。


(:has(*[colspan]))

td[colspan]などであれば動作しましたが、今回はthtdの2つが必要なため断念。
IE8だけ動かないのでその線で調べた際に以下の記事を発見。

how to select th with no colspan in IE

分割して記述する方法ですが、これも上手くいかずでした。

以前のコード


//colspanが存在しない最初のtrのセルの数を取得。
var lentop = $('tr:not(:has(*[colspan])):first').children().length;
$('tr').each(function() {
  //trのセルの数を取得
  var len = $(this).children().length;
  //trのセルの数とrowspanが存在しないセルの数を比較し、同じなら実行
  if(len == lentop){
     $(this).find('td:first-child').addClass('first');
  }
});

仕組みは単純で、trに含まれるセルの最大数と同じtrだけを選び、そのtrの最初のtdにクラスを付与しています。

行のセルが減る状況はcolspanで結合した場合ですから、横にならぶセルの最大数を取得するにはcolspanのない行を選びます。
rowspanで縦に結合されても横に並ぶセルは減りますが、rowspanは必ず結合するセルの先頭にありますし、なによりrowspanのあるセル自体は数が減少しないため、最初の一つを選ぶ場合には問題はないはずです。

結び

もっとよい指定方法があるのではと思いますが、現状ではこれが精一杯。
最上段のtrが必ずthcolspanrowspanもない、などの制限があるならもっと単純にできるでしょう。

tableは色々なパターンがあるので、全部を網羅するべきなのかどうかも判断が難しいかもしれません。

3人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄