8

In my web browser userscript project I need to replace just one text node without affecting the other HTML elements under the same parent node as the text. And I need to replace it with more than one node:

<div id="target"> foo / bar; baz<img src="/image.png"></div>

Needs to become:

<div id="target"> <a href="#">foo</a> / <a href="#">bar</a>; <a href="#">baz</a><img src="/image.png"></div>

I know jQuery doesn't have a whole lot of support for text nodes. I know I could use direct DOM calls instead of jQuery. And I know I could just do something like $('#target').html(my new stuff + stuff I don't want to change). Also note that I'd like to preserve the initial space, this on its own seems to be tricky.

What I'd like to ask the experts here is, Is there a most idiomatic jQuery way to do this?

hippietrail
  • 15,848
  • 18
  • 99
  • 158
  • How would you know where to break up the replace text? – j_mcnally Oct 12 '12 at 03:20
  • http://api.jquery.com/contents/ – Ram Oct 12 '12 at 03:23
  • @j_mcnally: Just assume we have "some way" to generate the replacement text/HTML. In my actual project I'm parsing the original text into some objects which I then use to generate basically the same text but with certain words changed to links. – hippietrail Oct 12 '12 at 03:25
  • 1
    I don't know of any jQuery solution; but I did write a highlighting feature that does something close to what you want to accomplish: http://jsfiddle.net/rkw79/5cCuc/ – rkw Oct 12 '12 at 03:37
  • @rkw: Yes that's definitely a related problem. – hippietrail Oct 12 '12 at 03:39

3 Answers3

10

You basically want to replace the first child (text node) with the new content. You need http://api.jquery.com/replaceWith/

// Text node we want to process and remove
var textNode = $("#target").contents().first();
// Break the text node up
var parts = textNode.text().split(/\s/);
// Put links around them
var replaceWith = "";
for (var i =0; i < parts.length;i++) {
    replaceWith += "<a href='http://www.google.com'>"  + escapeHTML(parts[i]) + "</a> ";
}
// Replace the text node with the HTML we created
textNode.replaceWith(replaceWith);

function escapeHTML(string) {
  return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="target">example, handles escaping properly, you should see an ampersand followed by "amp;" here: &amp;amp; <img src="/foobar.png"></div>

http://jsfiddle.net/NGYTB/1/

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • Well the new HTML isn't purely based whitespace vs non whitespace but this approach is definitely very good! – hippietrail Oct 12 '12 at 04:11
  • @hippietrail How am I supposed to know how you're going to break it up? It's just a simple and clear example so you can modify it to your needs. The relevant part is having access to the text node, and replacing with the HTML you are going to come up with – Ruan Mendes Oct 12 '12 at 04:22
  • 1
    Sorry that wasn't supposed to be a complaint, just a comment to anyone else reading. I'll be accepting your answer unless something really surprising comes along so don't worry (-: – hippietrail Oct 12 '12 at 04:24
1

Try this approach

// nodeType = 1 corresponds to element node
   // You are filtering everything out other than that and 
   // removing the textnodes from it..
   // contents() will get everything including the text
    ​$('#target'​).contents().filter(function() {
        return this.nodeType != 1; 
    }).remove();

   // Then you are prepending the div with the html  that needs to 
   //  replaced with the text...
    $('#target').prepend('<a href="#">mixed</a> text <a href="#">and</a> HTML');
   // This makes sure you do not touch the initial img element inside the div

You can add other node types if you do not want to remove specific nodetypes CHECK FIDDLE​

Sushanth --
  • 55,259
  • 9
  • 66
  • 105
  • This works. I suppose it's not a very general approach but it does seem to be a problem domain slightly outside jQuery's scope and this *is* idiomatic jQuery. – hippietrail Oct 12 '12 at 03:49
0

try this..edited version

$('#target').html('<a href="#">mixed</a> text <a href="#">and</a> HTML' + $('#target').html());
soonji
  • 79
  • 4
  • But that would result in `some textmixed text and HTML` – hippietrail Oct 12 '12 at 03:28
  • No that doesn't work. the call to `.html()` returns the old HTML *including* the old text nodes. So basically it ends up prepending the new stuff rather than replacing the old stuff with the new stuff. – hippietrail Oct 12 '12 at 03:38
  • Well I don't have code yet. I have the HTML from the website I'm writing the userscript for and I'm just trying out various approaches directly in the JavaScript console. If you really need to see some HTML I'm trying to transform from the site we can do that in an SE chatroom. I have updated my example HTML to look more like my problem though if that helps. – hippietrail Oct 12 '12 at 03:45