2011-05-16 163 views
6

考慮這個文檔片段:HTML重寫有什麼好的選擇?

<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> 

我想替換字符串「約翰」字符串「彼得」的每一次出現。這可以通過 HTML重寫來完成:

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

工作演示:以上的jQuery http://jsfiddle.net/v2yp5/

代碼看起來簡單和直接的,但是這是欺騙,因爲這是一個糟糕的解決方案。 HTML重寫將重新創建#test DIV中的所有DOM節點。隨後,以編程方式(例如"onevent"處理程序)或用戶(輸入的表單字段)對該DOM子樹所做的更改不會被保留。

那麼執行此任務的適當方法是什麼?

+0

如何使用.text代替.html。 – 2011-05-16 00:18:18

+0

[像這樣的事情](http://stackoverflow.com/questions/3899343/javascript-for-replacing-text-in-the-body-tag-of-pages-loaded-into-an-open-source )? – alex 2011-05-16 00:18:26

+0

@詹姆斯同樣的問題,我相信。 – alex 2011-05-16 00:18:40

回答

3

你要遍歷所有子節點,只更換文本節點。否則,您可能會匹配HTML,屬性或其他任何序列化的內容。在替換文本時,您只想處理文本節點,而不是整個序列化的HTML。

我想你已經知道,雖然:)

Bobince有great piece of JavaScript for doing that

+0

對我很好。 – 2011-05-16 00:23:32

+0

@alex我在考慮利用[Crockford的walkTheDOM函數](http://vidasp.net/crockford-walkthedom.gif),但我會研究bobince的代碼,因爲它看起來更復雜。 – 2011-05-16 00:32:12

+0

@ŠimeBobince的一個更適合這個,只處理文本節點。您需要修改Crockford的來處理文本節點。另外,他的原名'walk_the_DOM()'發生了什麼? :P – alex 2011-05-16 00:36:31

1

稍微沒有侵入性,但不一定更高性能,是選擇您只知道文本節點的元素,並使用.text()。在這種情況下(沒有通用的解決方案,很明顯):

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

演示:http://jsfiddle.net/mattball/jdc87/(類東西在<input>單擊按鈕之前)

+1

** [DEMO with events perserved](http://jsfiddle.net/jdc87/3/)** – qwertymk 2011-05-16 00:40:27

+0

&@qwertymk:此方法不起作用,因爲指向CV的鏈接被破壞。 – Mottie 2011-05-16 02:20:31

+0

@fudgey:謝謝。你是對的。 – 2011-05-16 02:36:09

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和沒有遞歸。

+0

@minitech看起來很有趣。可以在[我的演示]上實現這個(http://jsfiddle.net/v2yp5/)? – 2011-05-16 00:34:42

+0

http:// jsfiddle。net/minitech/4jABd/ – Ryan 2011-05-16 00:42:27

+0

@minitech +1富有想象力的解決方案。但是真的有必要完全替換文本節點嗎?不應該設置'textNode.nodeValue'工作一樣好? – 2011-05-16 01:04:02

1

您可以將每個可變的文本實例(例如「John」)封裝在具有某個CSS類的跨度中,然後在所有這些跨度上執行.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的方式並沒有道歉。但我沒有看到任何錯誤......我的意思是你基本上在做客戶端數據綁定。所以這就是模板插件的用途。

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'); 
+0

我想知道爲什麼DOM行走功能沒有在jQuery中提供......聽起來很基本。 – 2011-05-16 01:06:36

+0

@Šime:好問題。使用'*'選擇器,您可以獲取所有元素,但不能獲取文本節點。我猜想因爲jQuery方法通常不是用來操作文本節點的,它可能會讓人感到困惑。還有一套文本特定的功能似乎是有道理的。 – user113716 2011-05-16 01:09:11

+0

感謝這一優秀的代碼。我需要爲我的案子做一些修改。我已經發布了一個新的答案的細節(在這裏給你的原始位)。 – 2012-07-20 15:59:50

0

這似乎是工作(demo):

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

不幸的不是。它會刪除#test中不是他的子女的所有後代(在我的演示中爲ANCHOR,H2,UL和LI)。 – 2011-05-16 01:29:33

+0

糟糕,我已經更新了我的答案。 – Mottie 2011-05-16 01:41:40

+0

我意識到,如果要替換的文本位於具有子元素的元素中,則不起作用,如下所示:'

  • 查看John的blog
  • ',但它適用於您提供的示例。 – Mottie 2011-05-16 02:40:27

    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); 
        } 
        } 
    } 
    
    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。
    • 刪除原文本元素不會引起任何副作用。
    相關問題