Search code examples
jqueryhtmlfacebookjquery-selectorsuserscripts

Use jQuery to Target Comments in the DOM?


I'm writing a browser extension that modifies Facebook and have run into a snag. Facebook is using HTML comments as markup, such as:

<a class="UFILikeLink">
  <!-- react-text: 9 -->
    Like
  <! -- /react-text -->
</a>

What I want is a way to change the text "Like" in the example above. However, if I use something like:

$('.UFILikeLink').html("<!-- react-text: 9 -->Like<!-- /react-text -->");

It breaks the functionality of the object for subsequent actions. In other words, I'm pretty sure his means some action is bound to the existing HTML that was there (by Facebook) and that isn't a part of the HTML I write in its place.

Is there any way to use jQuery to target the text between <!-- react-text --> and <-- /react-text -->? In a similar fashion to how one might change text with text()? This way I wouldn't have to overwrite the HTML for the a tag.

Perhaps helpful to this question is the overall structure of what lies inside the a. When viewed in Chrome devtools, it looks more like this:

<a class="UFILikeLink">
  ::before
  <!-- react-text: 9 -->
    "Like"
  <! -- /react-text -->
  ::after
</a>

When I replace the HTML using:

$('.UFILikeLink').html($('.UFILikeLink').html());

That ::before and ::after structure is still visible there (it looks identical in the devtools). But the functionality as far as Facebook is concerned is broken.


Solution

  • To change text with a minimum disruption to surrounding comments and non-standard event handlers, you need to target just the specific HTML Text node.

    jQuery does not deal well with just text nodes, but you can extend jQuery to accommodate Facebook's non-standard markup. This can be a mild PITA, depending on how involved you need to get, but here's a starter jQuery extension:

    jQuery.fn.extend ( {
        changeTextJustAfterCommentTag: function (commentTag, newText) {
            return this.each (function () {
                //--- process child nodes. No recursion for now.
                for (var J = 0, L = this.childNodes.length - 1;  J < L;  J++) {
                    var chldNd  = this.childNodes[J];
                    if (chldNd.nodeType === Node.COMMENT_NODE  &&  chldNd.nodeValue.indexOf (commentTag) !== -1) {
                        var targText    = this.childNodes[ J + 1 ];
                        if (targText.nodeType === Node.TEXT_NODE) {
                            targText.nodeValue  = newText;
                        }
                        break;
                    }
                }
            } );
        }
    } );
    

    You would use it like:

    $('.UFILikeLink').changeTextJustAfterCommentTag ("react-text: 9", "My new like");
    


    Show and run this code snippet for a live demo:

    jQuery.fn.extend ( {
        changeTextJustAfterCommentTag: function (commentTag, newText) {
            return this.each (function () {
                //--- process child nodes. No recursion for now.
                for (var J = 0, L = this.childNodes.length - 1;  J < L;  J++) {
                    var chldNd  = this.childNodes[J];
                    if (chldNd.nodeType === Node.COMMENT_NODE  &&  chldNd.nodeValue.indexOf (commentTag) !== -1) {
                        var targText    = this.childNodes[ J + 1 ];
                        if (targText.nodeType === Node.TEXT_NODE) {
                            targText.nodeValue  = newText;
                        }
                        break;
                    }
                }
            } );
        }
    } );
    
    //--- Simulated Facebook code:
    $(".UFILikeLink")[0].firstChild.facebook = "megaFubar";
    
    $(".UFILikeLink").click ( function (zEvent) {
        zEvent.preventDefault ();
        zEvent.stopImmediatePropagation ();
    
        if (this.firstChild.facebook) {
            $("body").append ('<span>You like!</span> ');
        }
    } );
    
    //--- Simulate userscript:
    $("#normRewrite").click ( function (zEvent) {
        $('.UFILikeLink').html ("<!-- react-text: 9 -->Oops!<!-- /react-text -->");
    } );
    
    $("#textRewrite").click ( function (zEvent) {
        $('.UFILikeLink').changeTextJustAfterCommentTag ("react-text: 9", "My new like");
    } );
    body {width:        96%;}
    body > span {
        margin-right:   2em;
        white-space:    nowrap;
    }
    .UFILikeLink {
        background:     buttonface;
        border:         3px outset black;
        cursor:         pointer;
        display:        inline-block;
        padding:        1ex 2em;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <p>Click the "Like" link, see the action. &nbsp; &nbsp; Then use the rewrite buttons and see if click still works.
    </p>
    <p><button id="textRewrite">Special text rewrite</button> &nbsp; <button id="normRewrite">Normal (destructive) rewrite</button>
    </p>
    <p><a class="UFILikeLink">
        <!-- react-text: 9 -->
            Like
        <! -- /react-text -->
    </a></p>
    <hr>