1

I have this:

$(".forum-threadview-post-text:contains(':P')").html(":P").replaceWith("<img src='http://website.com/images/emotes/tongue.png' />");

It is supposed to take any instances of ':P' and replace it with an emoticon image. However, it takes posts with :P and replaces the entire post with that image. How can I replace only the :P.

PSL
  • 123,204
  • 21
  • 253
  • 243
user3015600
  • 31
  • 1
  • 7

2 Answers2

5

Try

var tImg = "<img src='http://website.com/images/emotes/tongue.png' />";
$(".forum-threadview-post-text:contains(':P')").html(function (_, html) {
     return html.replace(/:P/g , tImg )
});

Demo

Reason why yours doesn't work as expected is because you are replacing the matched element(s) with the image, not the specific content of it. You can use .html( function(index, oldhtml) ) to get the html of each element and replace it.

Or:

$(".forum-threadview-post-text:contains(':P')").contents().each(function () {
    if(this.nodeType === 3 && /:P/g.test(this.nodeValue)) {
       $(this).parent().html(this.nodeValue.replace(/:P/g,"<img src='http://placehold.it/10x10' />"));
    }
});
PSL
  • 123,204
  • 21
  • 253
  • 243
  • Look into `contents()` in jQuery... there might be a better way. – ErikE Nov 22 '13 at 02:45
  • @ErikE Well i think this way should work as well, as smileys are not gng to be used as a part of html tag :). But yes using contents and check ing for nodeType would make it more specific... – PSL Nov 22 '13 at 02:47
  • 1
    It just gives me the willies to do a replace on HTML when the content is supposed to be a text node. Saying "not going to be used" is a poor man's bet. +1 now. But wait, how about this example: `type:Private`. I don't think it's so far-fetched, and would prefer to see you steer people toward the more provably-correct answer! – ErikE Nov 22 '13 at 02:58
  • @ErikE haha thats an awsomely insane example that can come anywhere... yeah you are right. I just got carried away with OPs html snippet... :) – PSL Nov 22 '13 at 03:11
  • Actually... your second code block has several problems. Try it on `
    will this really work :P
    ` for some challenges to overcome.
    – ErikE Nov 22 '13 at 04:23
2

I think it is best to not assume that the text will be an immediate child of the passed-in selector. I also see it as a bit chancy to assume that the text you are searching for cannot appear inside a descendant HTML tag. All other answers given so far have issues in at least one of these areas.

Furthermore, it is even better to make the code reusable. The below easily reusable functions will do exactly what you need, won't be messed up by stray HTML tag attributes, and work!

function descendantContents($el, textToFind) { // this could be made a jQuery plugin
   var result = $el
      .find(':not(script)')
      .contents()
      .filter(function () {
         return this.nodeType === 3 && $(this).text().indexOf(textToFind) !== -1;
      });
   return result;
}

function replaceText(scopeSelector, textToFind, replacementHtml) {
   descendantContents($(scopeSelector), textToFind)
      .each(function () {
         var element = $(this);
         var parts = element.text().split(textToFind);
         element.before(document.createTextNode(parts[0]));
         for (var i = 1, l = parts.length; i < l; i += 1) {
            element.before(replacementHtml);
            element.before(document.createTextNode(parts[i]));
         }
         element.remove();
      });
};

These functions have been tested in Firefox 25.0.1, Chrome 30.0.1599.101 m, and 10.0.9200.16721 using jQuery 1.6.1 (I know, that's an old version, but that should make you feel better, not worse).

For anyone wishing to do better, try your code against this HTML:

<div>
   <div>will this <span title="alt:Private">really</span> work :P</div>
</div>
ErikE
  • 48,881
  • 23
  • 151
  • 196