初投稿なのでアドバイスや指摘等あれば遠慮なくお願いします。
###0.前置き
jQueryにはDataTablesという便利なテーブルプラグインがあり、それを使っていたのだが、問題にぶち当たり、いくらググっても解決しないので、自力で解決した。
もしかしたら誰も書いてないかもしれないので、メモついでに書いておく。
(C#にDataTableというものがあるらしいが、別物なので注意)
###1.環境、目的
環境:jQuery 3.4.1, DataTables 1.10.18
目的:DataTablesを使った上で、表の中にボタンを置きたかった。
###2.もくろみ
例えば売上テーブル(売上ID、売上日、顧客名)と売上明細テーブル(売上ID、商品ID、売上数量)があったとして、表には売上テーブルの情報を出力し、各行のボタンをクリックするとその明細が出る、みたいなことをやりたかった。
さらにセレクトボックスを置き、その選択内容で条件づけ(例えば売上日が1週間以内/1ヶ月以内/半年以内)し、ajaxを使ってページリロードなしで表を更新する、という風にしたかった。
###3.問題
DataTablesのapiには表をクリアするメソッドや行を追加するメソッドがあるので、それを使ってデータ抽出のたびにクリア→1行ずつ追加、とすればできるだろうと考えた。
しかし実際に使ってみると、追加する行のデータは配列で渡す必要があり、その中にボタンオブジェクトを入れて渡しても、正しく出力されない。
ならばと行を追加したあと自分でjQueryでhtmlをいじくってボタンを追加してみると、今度は一部の行にしかボタンが表示されない。
これはイベントも同様で、ボタンではなくテキストを置き、セルをクリックしたらページ遷移、というふうにイベントを設定しうとしたが、これも一部のセルにしか適用されなかった。以下、うまくいかなかった例:
<script>
$(function(){
$('#sales_table').DataTable(); //#sales_tableにDataTablesを適用
$('tbody td').click(function(){ //tbodyの中のtdタグ全てにクリックイベントを設定。クリックすると行と列の番号をダイアログで出す
alert("行:" + ($(this).parent().index() +1) + ", 列:" + ($(this).index() + 1) + "");
});
})
</script>
</head>
<body>
<table id="sales_table">
<thead>
<tr><td>あ</td><td>い</td></tr>
</thead>
<tbody>
<c:forEach var="v" items="${sales}" varStatus="st"> //salesは売上データのコレクション
<tr><td>${st.count }</td><td>${v }</td></tr>
</c:forEach>
</tbody>
</table>
</body>
###4.原因の分析
DataTablesを適用したテーブルのhtmlをいじるとき、どうやら初期表示される範囲に視野が限定されることがわかった。
DataTablesにはページング機能があり、デフォルトで10行ずつ表示するようになっている。この場合、データが10行を超えていると、最初の10行にしかボタンやイベントがつかないことになる。
色々試してみると、DataTablesでは内的に存在しているテーブルと、見えているテーブルの2つがあるらしいことがわかってきた。
内的なテーブルのうち、例えば10行ずつの表示ならば、最初の10行だけを切り取り、ブラウザに表示する。
apiを使わずにこちらの手が届くのは、見えているその10行のテーブルだけであり、それ以外の領域にアクセスしようとすると、DataTablesのapiを使わなければいけない。
DataTablesのapiには.draw()というメソッドがあり、apiで追加した行などはこれを実行しないと出力されない。
.draw()する前に、上記のようにイベントを設定したりしてみると、やはり追加された行には操作が適用されなかった。
これもどうやら、追加は内部的なテーブルに対してのみ行われ、.draw()を実行するとそれが表面にも反映され、こちらの手が届くようになる、という動作のように思える。
###5.解決策
公式を見ても解決方法が見つからなかったので、力押しで解決することにした。
この問題はDataTablesが適用されている限りつきまとうので、ajaxで表を更新するたび、表を消して作り直す、という方針。
tableタグをdivで囲い、データを抽出するたびにその子要素のtableタグを消し、新しくtableタグを作ってデータを入れ直す。最後にDataTablesを適用。
これなら自分のやりたいことが実現できた。
以下の成功例では、ajaxによる更新を行っていないが、ajaxの.done()の中のコールバックで上記のことをやればよい。
<script>
$(function(){
$('tbody td').click(function(){ //先にクリックイベントを付ける。今回はめんどくさいのでajaxによる更新はなし
alert("行:" + ($(this).parent().index() +1) + ", 列:" + ($(this).index() + 1) + "");
});
$('#sales_table').DataTable();
})
</script>
</head>
<body>
<table id="sales_table">
<thead>
<tr><td>あ</td><td>い</td></tr>
</thead>
<tbody>
<c:forEach var="v" items="${sales}" varStatus="st">
<tr><td>${st.count }</td><td>${v }</td></tr>
</c:forEach>
</tbody>
</table>
</body>
apiを使ってスマートにやろうと思うとこういうドツボにはまる、ということをすでに2,3回経験している。
野蛮人みたいに、自分のできることでゴリ押しするという選択肢を、もっと上位に持ってきたほうがいいのかもしれない。
###6.まとめ
もっとスマートな方法があったら教えてください。