什么是HTML重写的好方法?

时间:2011-05-16 00:12:40

标签: javascript jquery html regex

考虑这个文件片段:

<div id="test">
    <h1>An article about John</h1>
    <p>The frist paragraph is about John.</p>
    <p>The second paragraph contains a <a href="#">link to John's CV</a>.</p>
    <div class="comments">
        <h2>Comments to John's article</h2>
        <ul>
            <li>Some user asks John a question.</li>
            <li>John responds.</li>
        </ul>
    </div>
</div>

我想用字符串“Peter”替换字符串“John”的每一个匹配项。 可以通过HTML重写来完成:

$('#test').html(function(i, v) {
    return v.replace(/John/g, 'Peter');    
});

工作演示: http://jsfiddle.net/v2yp5/

上面的jQuery代码看起来很简单直接,但这是骗人的,因为它是一个糟糕的解决方案。 HTML重写会重新创建#test DIV中的所有DOM节点。随后,不会以编程方式(例如"onevent"处理程序)或用户(输入的表单字段)对该DOM子树进行的更改。

那么执行此任务的适当方法是什么?

8 个答案:

答案 0 :(得分:4)

jQuery插件版本如何减少代码?

http://jsfiddle.net/v2yp5/4/

jQuery.fn.textWalk = function( fn ) {
    this.contents().each( jwalk );
    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if( nn === '#text' ) {
            fn.call( this );
        } else if( this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea' ) {
            $(this).contents().each( jwalk );
        }
    }
    return this;
};

$('#test').textWalk(function() {
    this.data = this.data.replace('John','Peter');
});

或做一个小鸭子打字,并有一个选项来传递几个字符串替换:

http://jsfiddle.net/v2yp5/5/

jQuery.fn.textWalk = function( fn, str ) {
    var func = jQuery.isFunction( fn );
    this.contents().each( jwalk );

    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if( nn === '#text' ) {
            if( func ) {
                fn.call( this );
            } else {
                this.data = this.data.replace( fn, str );
            }
        } else if( this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea' ) {
            $(this).contents().each( jwalk );
        }
    }
    return this;
};

$('#test').textWalk(function() {
    this.data = this.data.replace('John','Peter');
});

$('#test').textWalk( 'Peter', 'Bob' );

答案 1 :(得分:3)

您希望遍历所有子节点并仅替换文本节点。否则,您可以匹配HTML,属性或序列化的任何其他内容。替换文本时,您只想使用文本节点,而不是整个HTML序列化。

我想你已经知道了,但是:)

Bobince有great piece of JavaScript for doing that

答案 2 :(得分:2)

我需要做类似的事情,但我需要插入HTML标记。我从@ user113716的答案开始,做了一些修改:

$.fn.textWalk = function (fn, str) {
    var func = jQuery.isFunction(fn);
    var remove = [];

    this.contents().each(jwalk);

    // remove the replaced elements
    remove.length && $(remove).remove();

    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if (nn === '#text') {
            var newValue;

            if (func) {
                newValue = fn.call(this);
            } else {
                newValue = this.data.replace(fn, str);
            }

            $(this).before(newValue);
            remove.push(this)
        } else if (this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea') {
            $(this).contents().each(jwalk);
        }
    }
    return this;
};

有一些隐含的假设:

  • 您总是插入HTML。如果没有,您需要添加一个检查以避免在不必要时操纵DOM。
  • 删除原始文本元素不会产生任何副作用。

答案 3 :(得分:1)

稍微不那么干扰,但不一定更高性能,是选择你知道只包含文本节点的元素,并使用.text()。在这种情况下(显然不是通用解决方案):

$('#test').find('h1, p, li').text(function(i, v) {
    return v.replace(/John/g, 'Peter');
});

演示:http://jsfiddle.net/mattball/jdc87/(在点击按钮之前在<input>中输入内容)

答案 4 :(得分:1)

我就是这样做的:

var textNodes = [], stack = [elementWhoseNodesToReplace], c;
while(c = stack.pop()) {
    for(var i = 0; i < c.childNodes.length; i++) {
        var n = c.childNodes[i];
        if(n.nodeType === 1) {
            stack.push(n);
        } else if(n.nodeType === 3) {
            textNodes.push(n);
        }
    }
}

for(var i = 0; i < textNodes.length; i++) textNodes[i].parentNode.replaceChild(document.createTextNode(textNodes[i].nodeValue.replace(/John/g, 'Peter')), textNodes[i]);

纯JavaScript,没有递归。

答案 5 :(得分:1)

您可以在具有特定CSS类的范围中包装每个可变的文本实例(例如“John”),然后对所有这些跨度执行.text('..')更新。似乎对我不那么干扰,因为DOM并没有真正被操纵。

<div id="test">
    <h1>An article about <span class="name">John</span></h1>
    <p>The frist paragraph is about <span class="name">John</span>.</p>
    <p>The second paragraph contains a <a href="#">link to <span class="name">John</span>'s CV</a>.</p>
    <div class="comments">
        <h2>Comments to <span class="name">John</span>'s article</h2>
        <ul>
            <li>Some user asks <span class="name">John</span> a question.</li>
            <li><span class="name">John</span> responds.</li>
        </ul>
    </div>
</div>


$('#test .name').text(function(i, v) {
    return v.replace(/John/g, 'Peter');    
});

另一个想法是使用jQuery Templates。它绝对是侵入性的,因为它与DOM有关并且不为此道歉。但我认为没有错......我的意思是你基本上做客户端数据绑定。这就是模板插件的用途。

答案 6 :(得分:0)

这似乎有效(demo):

$('#test :not(:has(*))').text(function(i, v) {
  return v.replace(/John/g, 'Peter');    
});

答案 7 :(得分:0)

提供的POJS解决方案没问题,但我不明白为什么要避免递归。 DOM节点通常不会嵌套太深,所以我认为它很好。我还认为构建单个正则表达式比使用文字并在每次调用替换时构建表达式要好得多。

// Repalce all instances of t0 in text descendents of
// root with t1
// 
function replaceText(t0, t1, root) {

  root = root || document;
  var node, nodes = root.childNodes;

  if (typeof t0 == 'string') {
    t0 = new RegExp(t0, 'g');
  }

  for (var i=0, iLen=nodes.length; i<iLen; i++) {
    node = nodes[i];

    if (node.nodeType == 1) {
      arguments.callee(t0, t1, node);

    } else if (node.nodeType == 3) {
      node.data = node.data.replace(t0, t1);
    }
  }
}