[JavaScript] jQueryでrowspanな行を置換したい時は?

  • このエントリーをはてなブックマークに追加

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()結果の要素セットは、ネイティブのHTMLCollectiondocument.getElementsByTagName()などが返す)と検索結果セットが動的に変化することは似ていますが、
前者は後で動的に変化することはあっても、DOMツリーの変更がトリガーとなるわけではありません。
あくまでjQueryを利用したDOM操作係のメソッドが実行されることにより変化がもたらされます。

試しにjQuery.fn.find(), document.getElementsByTagName(),document.querySelector()
の3つのDOM検索系メソッドの挙動を確認してみます。tablenewRows変数は引き続き先のを再利用します。

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を極めましょう!です。

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。




コメント

  1. たかおファン(surface0) より:

    返信が大変おそくなってしまってごめんなさい!(-_-;)

    find(‘tr[data-row-id=”1″]:nth-child(2)’)

    こちらを実際に試すと以下の部分しかマッチしないので、2行まとめて入れ替えたいという要件は満たしません。

    <tr data-row-id="1">
      <td>ふがふが</td>
    </tr>
    
  2. 名無し より:

    find(‘tr[data-row-id=”1″]:nth-child(2)’).
    上記の物でも出来るのではないでしょうか

コメントを残す