jQuery で、マークアップされている部分を含むテキストを置換してみたいと思ったら、この記事を読むといいと思うよ。
一般的な手法とその限界
jQuery を用いて、JavaScript からページの文章の一部を置換したいと思ったとします。
このとき、普通にググると、大抵「jQuery オブジェクトのtext()
メソッドで得た文字列に対してreplace(substr, newSubstr)
メソッドを呼んで、返却された文字列を元の jQuery オブジェクトのtext(val)
メソッドに渡してやればいい」という解決策を提案されるでしょう。
function replaceText($obj, substr, newSubstr) {
$obj.text($obj.text().replace(substr, newSubstr));
}
See the Pen jQueryテキスト置換(通常) by Aᴏɪ Hikari (@BlueRayi) on CodePen.
この方法は確かに大抵の場合うまく行きます。 しかし、もし置換したい DOM が子要素を含んでいる、つまり内部のテキストがさらにマークアップされているとどうなるでしょう。
See the Pen jQueryテキスト置換(通常: 失敗) by Aᴏɪ Hikari (@BlueRayi) on CodePen.
見ての通り、マークアップの効果が完全に消えてしまいます。 これはtext()
メソッドおよびtext(val)
メソッドの特性によるもので、text()
メソッドは子要素のテキスト部のみを単に結合して返し、text(val)
メソッドは既存の構造を完全に無視して単なるテキストノードとして再配置するためです。
このような場合、どうすれば目標を達成することができるでしょうか。
構造を保ったまま置換する
理論
まず、対象の jQuery オブジェクトを単なるテキストノードと子の DOM 要素に分解します。 以下に記述するsplitChildren($obj)
関数は引数として与えられた jQuery オブジェクト(長さ 1 を想定しています)の子ノードの jQuery オブジェクトの配列を返却します。
function splitChildren($obj) {
let ret = [];
let children = $obj[0].childNodes;
for (let child of children) {
ret.push($(child));
}
return ret;
}
そして、そのうちのテキストノードの jQuery オブジェクトのみを置換していきます。 このとき、テキストノードの jQuery オブジェクトはtext()
およびtext(val)
メソッドで期待する動作を得られませんので、直接、内部のノードのnodeValue
属性を書き換えています。
function replaceText($obj, substr, newSubstr, isRecursive) {
var isRecursive = (isRecursive !== undefined) ? isRecursive : false;
$obj.each(function() {
let $children = splitChildren($(this));
for (let $child of $children) {
if ($child.prop('tagName')) {
if (isRecursive) {
replaceText($child, substr, newSubstr, true);
} else {
/* NOOP */
}
} else {
$child[0].nodeValue = $child[0].nodeValue.replace(substr, newSubstr);
}
}
});
}
実践
実際に実行してみます。
See the Pen jQueryテキスト置換(構造維持) by Aᴏɪ Hikari (@BlueRayi) on CodePen.
このようにマークアップされた子要素をそのままにしてテキストが置換できています。
第 4 引数であるisRecursive
に Truthy な値を与えると、テキストノードでない jQuery オブジェクトに対して再帰的に呼び出されるため、子要素を含めて置換できます。
See the Pen jQueryテキスト置換(構造維持: 再帰的) by Aᴏɪ Hikari (@BlueRayi) on CodePen.