Replace content in elements without replacing HTML
Asked Answered
C

3

7

Suppose I have the following HTML structure:

<test>
    <div>
        This is a test
        </div>
    <div>
        This is another test
        <button>
            Button test
        </button>
    </div>
</test>

Now I use the following jQuery code to replace, e.g., 'T':

$("test *").each(function(index, value) {
    $(this).html($(this).html().replace(new RegExp('t', "ig"), "<b>t</b>"));
});

However, this results in the following HTML structure (which is unexpected, see the <button> tag, which breaks my HTML):

<test>
    <div>
        <b>T</b>his is a <b>t</b>es<b>t</b>
        </div>
    <div>
        <b>T</b>his is ano<b>t</b>her <b>t</b>es<b>t</b>
        <bu<b>t</b><b>t</b>on>
            Bu<b>t</b><b>t</b>on <b>t</b>es<b>t</b>
            </bu<b>t</b><b>t</b>on>
        </div>
    </test>

What I want to achieve is:

<test>
    <div>
        <b>T</b>his is a <b>t</b>es<b>t</b>
        </div>
    <div>
        <b>T</b>his is ano<b>t</b>her <b>t</b>es<b>t</b>
        <button>
            Bu<b>t</b><b>t</b>on <b>t</b>es<b>t</b>
            </button>
        </div>
    </test>

Basically, I want to replace within the entire element but preserve the HTML tags and all the HTML attributes.

Croak answered 5/5, 2017 at 16:5 Comment(2)
So if I'm understanding correctly, you don't want <button> to become <bu<b>t</b><b>t</b>on>, and that's the main issue, correct?Branca
@Goose, correct but this applies not only to the button tag but to all html tags of course which match with the regex.Croak
F
3

With jQuery this could be achieved rather simply. Create a function that takes the element you wish to update the text of, the text you wish to replace and what you wish to replace it with. Then in the function you want to remove the child HTML and update any text left in the element with your replacement text. Then you can recursively run this same function for each of your child elements before appending them back into the parent.

function replaceTextInHtmlBlock($element, replaceText, replaceWith)
{
  var $children = $element.children().detach();
  //Now that there should only be text nodes left do your replacement
  $element.html($element.text().replace(replaceText, replaceWith));
  //Run this function for each child element
  $children.each(function(index, me){
    replaceTextInHtmlBlock($(me), replaceText, replaceWith);
  });
  $element.append($children);
}

$(document).ready(function(){
  $("#doReplace").click(function(){
    replaceTextInHtmlBlock($("#top"), $("#replace").val(), $("#with").val());
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="top">
  <div>
    This is a test
  </div>
  <div>
    This is another test
    <button>
            Button test
            </button>
  </div>
</div>
<br />
<br />
<div>
  <label>Replace</label>
  <input id="replace" value="t" />
  <label>with</label>
  <input id="with" value="<strong>t</strong>" />
  <button id="doReplace">Do Replace</button>
</div>
Frau answered 8/5, 2017 at 8:19 Comment(0)
A
0

Looks like you want to bold all the T characters but your regex is also catching your html.

Perform the operation on the innerText rather than the innerHTML. I have not used jQuery in a couple years so there might be a more optimal way of doing this but this should do it.

$("test *").each(function(index, value) {
    $(this)[0].innerText = $(this)[0].innerText.replace(new RegExp('t', "ig"), "<b>t</b>");
});
Attendant answered 5/5, 2017 at 16:17 Comment(2)
Thanks for your answer, however this will remove all the childrens from the parent right? If so, that is unwanted behaviorCroak
I have just tested your code, it doesn't preserve my HTML at all. Thanks for sharing your thoughts thoughCroak
P
0

You can avoid jQuery, and not worry about button in particular (since that won't help if you hit an input tag, etc.), if you replace only in Text nodes.

We look for text nodes, in each case spotting the first "t" (or "T") we see, wrapping it in <b>, and moving on. Other matches in the same area will be found in later text nodes.

var wrapText = "t";

var el = document.getElementsByTagName('test')[0];

replaceIn(el);

function replaceIn(el) {
  var i = 0;

  while (i < el.childNodes.length) {
    var cn = el.childNodes[i];

    if (cn.nodeType == 3) {   // text node
      var p = cn.textContent.toLowerCase().indexOf(wrapText);

      if (p >= 0) {
        var range = new Range();
        range.setStart(cn, p);
        range.setEnd(cn, p + 1);
        range.surroundContents(document.createElement('b'));
        ++i;   // don't check the <b> we just created
      }
    } 
    else {
      replaceIn(cn);
    }

    ++i;
  }
}
<test>
  <div>
    This is a test
  </div>
  <div>
    This is another test
    <button>
      Button test
   </button>

    <input value="input test" />
  </div>
</test>
Pneumato answered 5/5, 2017 at 16:39 Comment(2)
Thanks for you reply. It works great, however I have to process a whole page which is terribly slow. The webpage almost crashes when I perform the search.Croak
If you want to not process tags, you'll have to process the individual text nodes instead of just the string. Honestly I don't think there's a performant solution after the fact.Branca

© 2022 - 2024 — McMap. All rights reserved.