1

I've got the following simple JQuery that's embedded on each page of the project I'm playing around with:

$(document).ready(function(){
    var replaced = $("body").html().replace(/\$([a-zA-Z]+)\b/g,
    '<a href="https://finance.yahoo.com/quote/$1">\$1</a>');
    $("body").html(replaced);
});

As you can see, if it finds a string of letters that are prefaced by $, it assumes that a stock symbol was mentioned and generates a link to Yahoo Finance.

The issue is, as part of the process for generating content, a user creates their post, and then goes to a verification page which both renders their post, and has pre-populated fields with their input, so that they can make edits. This is where the replace falls apart, it generates HTML tags in input fields which cause them close prematurely, basically breaking everything!

I'm barely conversant with anything Javascript (my project is a Laravel 8 project, as I want to learn more about that, it just so happens that I need this part for the front end).

How would I go about having Jquery perform the text replacement, but leave input boxes alone?

Here is a paste of the page after its loaded and the script has run. You can see at page 25 that the input is getting broken due to the HTML code being replaced inside the input:

https://pastebin.com/xGqKZXfT

Lucas Krupinski
  • 682
  • 5
  • 16
  • You could gather the inputs before replacing the links (save) and replace the inputs after replacing the links (restore). – Rojo Apr 18 '21 at 13:44
  • Also, could you please share a bit of your HTML? – Rojo Apr 18 '21 at 14:02
  • [You can't parse html with regex](https://stackoverflow.com/a/1732454/1175966) – charlietfl Apr 18 '21 at 14:09
  • 1
    There is one big issue you do need to be aware of when replacing all the body html. If there are event listeners already attached to any of those elements those listeners will be lost. To do this non destructively requires walking through the whole dom looking for text nodes – charlietfl Apr 18 '21 at 14:14
  • @rojo - I edited my post to include a link to pastebin where you can see that something is occurring that's breaking the prefilled input. Here https://pastebin.com/xGqKZXfT – Lucas Krupinski Apr 18 '21 at 15:03
  • @Charlietfl - can you suggest a path forward on this non-destructive replacement? – Lucas Krupinski Apr 18 '21 at 15:18
  • I could do this on the server side, but it seems like a lot of extra processing to inspect every string before it gets presented to the user. I thought it would be better to offload it to the client side – Lucas Krupinski Apr 18 '21 at 15:26
  • Doing it at the user input stage would be a lot cleaner. You aren't having to mess with all the other elements in a full page that way – charlietfl Apr 18 '21 at 15:49
  • If I do it at the user input stage, I'll effectively have to store two versions of their edit, their original input (which they can go back and edit), and the version that gets displayed on screen. That seems wasteful? – Lucas Krupinski Apr 18 '21 at 16:13
  • 1
    One more thought. If you only target the html inside the user's content container you won't have issues with forms and probably wouldn't run into event listener problems either – charlietfl Apr 18 '21 at 17:09
  • @charlieftl - that's what I needed to do! Instead of targeting `body`, I just had to target `content` and now it works fine. I was definitely overthinking that! Thank you! – Lucas Krupinski Apr 19 '21 at 13:21

1 Answers1

0

I've never used the native TreeWalker API before but it is ideally suited for what you need.

I suggest you not target any container that has script tags in it and am still figuring out the best filtering approaches.

Following is not perfect but it does only modify text nodes and replaces them with <span> when applicable in order to allow the <a> inside. I will probably tinker with adjustments but you can use this to get going

$('.widget p').click(e=> alert('still works'))

const stockReg =  /\$([a-zA-Z]+)\b/g;
const root = document.querySelector('#content')
const treeWalker = document.createTreeWalker(
  root,
  NodeFilter.SHOW_TEXT, {
    acceptNode: function(node) {
      return node.nodeValue.trim().length ? 1 : 2
      // NodeFilter.FILTER_ACCEPT == 1;
      // NodeFilter.FILTER_REJECT == 2;
    }
  },
  false
);

const nodeList = [];
let currentNode = treeWalker.currentNode;

while (currentNode) {
  nodeList.push(currentNode);
  currentNode = treeWalker.nextNode();
}

nodeList.filter(n => n.nodeType === 3).forEach(n => {
  // console.log('n.nodeTyp', n.nodeType, ' ', n.nodeName)  
  const txt = n.nodeValue;
  if (txt && txt.match(stockReg)) {
    const $span = $('<span>');
    const html = txt.replace(stockReg, '<a href="https://finance.yahoo.com/quote/$1">\$1</a>')
    $span.html(html);
    $(n).replaceWith($span);
  }
})

console.log($('#content')[0].outerHTML)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="content">
  <div class="widget">I bought $AAPL
    <p>After I sold $AMD and <span>some $GME</span>
    </p>
  </div>
</div>
charlietfl
  • 170,828
  • 13
  • 121
  • 150