1

I searched through a bunch of related questions that help with replacing site innerHTML using JavaScript, but most reply on targetting the ID or Class of the text. However, my can be either inside a span or td tag, possibly elsewhere. I finally was able to gather a few resources to make the following code work:

$("body").children().each(function() {
        $(this).html($(this).html().replace(/\$/g,"%"));
    });

The problem with the above code is that I randomly see some code artifacts or other issues on the loaded page. I think it has something to do with there being multiple "$" part of the website code and the above script is converting it to %, hence breaking things.using JavaScript or Jquery

Is there any way to modify the code (JavaScript/jQuery) so that it does not affect code elements and only replaces the visible text (i.e. >Here<)?

Thanks!

---Edit---

It looks like the reason I'm getting a conflict with some other code is that of this error "Uncaught TypeError: Cannot read property 'innerText' of undefined". So I'm guessing there are some elements that don't have innerText (even though they don't meet the regex criteria) and it breaks other inline script code.

Is there anything I can add or modify the code with to not try the .replace if it doesn't meet the regex expression or to not replace if it's undefined?

Yevgen
  • 767
  • 2
  • 9
  • 22

2 Answers2

1

Wholesale regex modifications to the DOM are a little dangerous; it's best to limit your work to only the DOM nodes you're certain you need to check. In this case, you want text nodes only (the visible parts of the document.)

This answer gives a convenient way to select all text nodes contained within a given element. Then you can iterate through that list and replace nodes based on your regex, without having to worry about accidentally modifying the surrounding HTML tags or attributes:

var getTextNodesIn = function(el) {
  return $(el)
    .find(":not(iframe, script)") // skip <script> and <iframe> tags
    .andSelf()
    .contents()
    .filter(function() {
      return this.nodeType == 3; // text nodes only
    }
  );
};

getTextNodesIn($('#foo')).each(function() {
  var txt = $(this).text().trim(); // trimming surrounding whitespace
  txt = txt.replace(/^\$\d$/g,"%"); // your regex
  $(this).replaceWith(txt);
})

console.log($('#foo').html()); // tags and attributes were not changed
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="foo"> Some sample data, including bits that a naive regex would trip up on:
  foo<span data-attr="$1">bar<i>$1</i>$12</span><div>baz</div>
    <p>$2</p>
    $3
  <div>bat</div>$0
  <!-- $1 -->
  <script>
    // embedded script tag:
    console.log("<b>$1</b>"); // won't be replaced
  </script>
</div>
Daniel Beck
  • 20,653
  • 5
  • 38
  • 53
  • Thanks, this is helpful. However, I'm still having an issue of some places not being replaced. 1) When there is a window with text inside a form 2) There is a button on page that opens up an additional data, part of it needs to have the symbol replaced. is there something that can be done to code to check inside forms as well? Also, maybe to re-run the script(not reload the page) if there is any change on page (i.e button clicked for more data to load)? Thanks! – Yevgen Nov 06 '17 at 21:52
  • "When there is a window with text inside a form" Can you be more explicit about what HTML structure you're talking about there? The above should work fine with forms, so I'm not sure what would be going wrong here. – Daniel Beck Nov 07 '17 at 00:29
  • "There is a button on page that opens up an additional data" This, too, I'm not entirely sure what you're talking about. If you mean you are loading new page data after the above script is run, you would need to re-run the script against that page data; this would most easily be done by calling this script against the parent element of the modified part of the page as part of the success callback of whatever script is updating the page in the first place – Daniel Beck Nov 07 '17 at 00:31
  • Sorry, so to clarify, it does work with forms but they loaded slightly after the script for some reason.. so fixed it by putting script in a function and calling that function on "$(document).ready(function() {". The second part with a button loading additional data on page, I also put "$(document).bind("DOMSubtreeModified",function(){" inside the above .ready function. – Yevgen Nov 07 '17 at 05:27
  • Unfortunately, the "$(document).bind("DOMSubtreeModified",function()" only works on the JavaScript variation below. When I try to apply that to wrap your code, it just keeps looping and crashes. Any ideas how to best call your function if the dom elements change (whenever more content is added by switching tabs)? – Yevgen Nov 07 '17 at 19:43
  • 1
    I guess in theory you could use a [mutation observer](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) but I wouldn't consider that safe to do: far too much risk of it affecting parts of the page you don't want it to. Better to manually call this only when needed, as part of whatever function is modifying the page (as part of its success callback, after the DOM is updated). – Daniel Beck Nov 08 '17 at 00:03
  • Second Tab – Yevgen Nov 08 '17 at 01:32
  • That is how the button code looks like, so I'm not sure what to use there to trigger a function to run again if that button or any similar button is clicked. Also, in addition to running the function again, I would have to make sure it waits for the new content to load. Any ideas for that? Thanks! – Yevgen Nov 08 '17 at 01:34
  • 1
    Looks like that has an oncomplete handler you could use — this is really a different topic than we started with here, though; you might want to post this as its own separate question. – Daniel Beck Nov 08 '17 at 14:44
0

I did it solved it slightly differently and test each value against regex before attempting to replace it:

var regEx = new RegExp(/^\$\d$/);
var allElements = document.querySelectorAll("*"); 
        for (var i = 0; i < allElements.length; i++){
            var allElementsText = allElements[i].innerText;
            var regExTest = regEx.test(allElementsText);
            if (regExTest=== true) {
                    console.log(el[i]);
                var newText = allElementsText.replace(regEx, '%');
                allElements[i].innerText=newText; 
        }
}

Does anyone see any potential issues with this?

One issue I found is that it does not work if part of the page refreshes after the page has loaded. Is there any way to have it re-run the script when new content is generated on page?

Yevgen
  • 767
  • 2
  • 9
  • 22