rowspan
されてて2行で一組なテーブルってよくあるじゃないですか。
それを動的にまるっと挿し替えたいときどうするか。
<table>
<tbody>
<tr data-row-id="1">
<td rowspan="2">2行分使ってるセル</td>
<td>ほげほげ</td>
</tr>
<tr data-row-id="1">
<td>ふがふが</td>
</tr>
<tr data-row-id="2">
...
</tbody>
</table>
とりあえず試してみる
中の値だけ差し替えればいいじゃんとかは無しです。
そんなのちまちまやってたらキリがないですし、可搬性がありません。
やはりテンプレートから生成した要素でまるっと入れ替えたいものです。
上記のコード例では2行一組を想定し、検索用のキーとして組単位でdata-row-id
属性を振りました。
この2行をjQueryでいっぺんに入れ替えようとしたら・・・
const newRows = `
<tr data-row-id="1">
<td rowspan="2">新2行分使ってるセル</td>
<td>ぴよぴよ</td>
</tr>
<tr data-row-id="1">
<td>ふーばー</td>
</tr>
`;
$('table').find('tr[data-row-id="1"]').replaceWith(newRows);
って一見思うじゃん?( ^ω^)
駄目なんだなこれが。tableをダンプしてみるとこうなってる。
<tbody>
<tr data-row-id="1">
<td rowspan="2">新2行分使ってるセル</td>
<td>ぴよぴよ</td>
</tr>
<tr data-row-id="1">
<td>ふーばー</td>
</tr>
<tr data-row-id="1">
<td rowspan="2">新2行分使ってるセル</td>
<td>ぴよぴよ</td>
</tr>
<tr data-row-id="1">
<td>ふーばー</td>
</tr>
</tbody>
まあ冷静に考えればfind
条件が2箇所にヒットして、
結果の要素セットの中身は2個入ってるわけだから、each
したら2回置換走るよねって単純な答えです。
新しいのを挿入して、古いの消せばいいじゃん?
それじゃあ手前に新しい行をツッコんでから古い方をremove()
してやルルォ!って思うじゃないですか。
$('table').find('tr[data-row-id="1"]').insertBefore(newRows).remove();
やったか!?・・・結果↓
<tbody>
</tbody>
そしてみんないなくなった。
結構引っかかる人多いんじゃないですかね。どうもinsertBefore()
した時点でtraversingがひとつ進んで、
新しく追加された行が含まれた集合へと変化しています。
ならば、end()
で巻き戻してやる!どやっ!
$('table').find('tr[data-row-id="1"]').insertBefore(newRows).end().remove();
<tbody>
</tbody>
もう手遅れです。end()で戻ったとしても、traversingのキャッシュが更新されたことにより、
find('tr[data-row-id="1"]')
の結果には追加した行が含まれてしまっています。なので最早無駄です。
手の打ちようがないですね。万事休す、長ったらしいコードを書かざるを得ないか…
とはなりません。jQueryに拘らなければ良いのです。
似て非なる要素セット達
find()
結果の要素セットは、ネイティブのHTMLCollection
(document.getElementsByTagName()
などが返す)と検索結果セットが動的に変化することは似ていますが、
前者は後で動的に変化することはあっても、DOMツリーの変更がトリガーとなるわけではありません。
あくまでjQueryを利用したDOM操作係のメソッドが実行されることにより変化がもたらされます。
試しにjQuery.fn.find()
, document.getElementsByTagName()
,document.querySelector()
の3つのDOM検索系メソッドの挙動を確認してみます。table
やnewRows
変数は引き続き先のを再利用します。
const $trs = $(document).find('tr'); // jQuery Object
const trs1 = document.getElementsByTagName('tr'); // HTMLCollection
const trs2 = document.querySelectorAll('tr'); // NodeList
// 3種類色分けコンソール出力
for (let elm of $trs) console.info('$trs:', elm);
for (let elm of trs1) console.warn('trs1:', elm);
for (let elm of trs2) console.error('trs2:', elm);
document.querySelector('tbody').insertAdjacentHTML('afterBegin', newRows);
for (let elm of $trs) console.info('$trs:', elm);
for (let elm of trs1) console.warn('trs1:', elm);
for (let elm of trs2) console.error('trs2:', elm);
これの実行結果は以下のようになり、含まれているtr
要素の変化を見てみることで、
HTMLCollection
以外はDOMの直接操作による変化は起きないことがわかります。
結論:直接DOMに流し込め
つ・ま・り、新しい行の挿入をjQueryでやらなければいいのです。(それでも敢えて他の部分はjQuery使う)
ネイティブ実装のメソッドで手短に解決します。具体的には以下。
const $trs = $('table').find('tr[data-row-id="1"]');
$trs[0].insertAdjacentHTML('beforeBegin', newRows);
$trs.remove();
<tbody>
<tr data-row-id="1">
<td rowspan="2">新2行分使ってるセル</td>
<td>ぴよぴよ</td>
</tr>
<tr data-row-id="1">
<td>ふーばー</td>
</tr>
<tr data-row-id="2">
...
</tbody>
これにて一件落着。めでたし。
ちなみに、ネイティブJSだけでこう書いても同じことです。
const trs = document.querySelectorAll('tr[data-row-id="1"]');
trs[0].insertAdjacentHTML('beforeBegin', newRows);
trs.forEach(element => element.remove());
結局言いたいこと
今回のようにjQueryだけでなんでもやろうとすると、返って苦労することが時折あります。
jQueryは初心者の足がかりとしては有用かもしれませんが、ES6が浸透してJSそのものが強力になってきた今、深く学ぼうとするならばjQueryではなくネイティブJSを極めましょう!です。
コメント
返信が大変おそくなってしまってごめんなさい!(-_-;)
find(‘tr[data-row-id=”1″]:nth-child(2)’)
こちらを実際に試すと以下の部分しかマッチしないので、2行まとめて入れ替えたいという要件は満たしません。
find(‘tr[data-row-id=”1″]:nth-child(2)’).
上記の物でも出来るのではないでしょうか