-2

I have a piece of code:

$("body").on("click", ".reply-button", function(){
    alert("test");
});

That is suppose to alert me when I click on an element that is generated on the fly (it is not part of the DOM when this code is executed).

Sometimes it works just as it's suppose to. I click the button and a little alert pops up. However, other times it stop working. Nothing I bind to it will work. If I bind to the container div (also generated on the fly), it does work, but not if I change the handler to incorporate button.

I am asking for what could be the possible reasons for this error? I do not know how to go about debugging this. My first guess was that it was something due to stopImmediatePropagation or stopPropagation but I couldn't find that being used in the same area.

Does anyone have any idea on how I should go about debugging this?

EDIT: How is the DOM being generated?

I get the HTML from a template that's hidden.

var html = $("#template").html();

Then I append the template to a div container

$("#container").append(html);

EDIT2: Here is the template being pulled:

<div id="tweets-container" class="feed-wrapper">
</div>

<div id="tweet-template" style="display:none;">
    <!-- Tweet 1 -->
    <div class="tweet animated" data-scannedTweetId="s_id">
        <!-- User -->
        <div class="tweet-user">
            <!-- User picture -->
            <img class="tweet-user-picture" src="s_twt_owner_profile_img_url" />

            <!-- User info -->
            <div class="tweet-user-info">
                <!-- User name -->
                <div class="tweet-user-info-name">
                    <a href="//twitter.com/s_twt_owner_sn" target="_blank">s_twt_owner_name (@s_twt_owner_sn)</a>
                </div>

                <!-- User biography -->
                <div class="tweet-user-info-biography">
                    s_twt_owner_desc
                </div>
            </div>
        </div>

        <!-- User statistics (following, followers, and tweets) -->
        <span class="tweet-statistics animated">
            <div class="following">
                <div class="statistic-count"><a href="http://twitter.com/s_twt_owner_sn/following">s_twt_owner_num_following</a></div>
                <div class="statistic-label">following</div>
            </div>

            <div class="followers">
                <div class="statistic-count"><a href="http://twitter.com/s_twt_owner_sn/followers">s_twt_owner_num_follower</a></div>
                <div class="statistic-label">followers</div>
            </div>

            <div class="tweets">
                <div class="statistic-count"><a href="http://twitter.com/s_twt_owner_sn">s_twt_owner_num_twt</a></div>
                <div class="statistic-label">tweets</div>
            </div>
        </span>

        <!-- Tweet bars/graph -->
        <div class="side-information animated">
            <div class="bar-wrapper">
                <!-- Actual bars -->
                <div class="bars">
                    <div class="bar big-bar bar-green" style="height: tqes_heightpx; background: tqes_color;" data-toggle="tooltip" data-placement="top" title="tq_engage_score"></div>
                    <div class="bar bar-yellow" style="height: tqrs_heightpx; background: tqrs_color;" data-toggle="tooltip" data-placement="top" title="tq_relevancy_score"></div>
                    <div class="bar bar-light-green" style="height: sks_heightpx; background: sks_color;" data-toggle="tooltip" data-placement="top" title="s_klout_score"></div>
                    <div class="bar bar-green" style="height: sls_heightpx; background: sls_color;" data-toggle="tooltip" data-placement="top" title="s_legitimacy_score"></div>
                    <div class="bar bar-gray" style="height: tqgs_heightpx; background: tqgs_color;" data-toggle="tooltip" data-placement="top" title="tq_geography_score"></div>
                </div>

                <!-- Labels that correspond with each bar -->
                <div class="bar-labels">
                    <div class="bar-label big-bar-label" data-toggle="tooltip" data-placement="bottom" title="Score">tq_engage_score</div>
                    <div class="bar-label-icon" style="font-size: 12px; color: #000;" data-toggle="tooltip" data-placement="bottom" title="Relevancy">
                        <i class="fa fa-bullseye"></i>
                    </div>
                    <div class="bar-label-icon" data-toggle="tooltip" data-placement="bottom" title="Influence">
                        <i class="fa fa-users"></i>
                    </div>
                    <div class="bar-label-icon" data-toggle="tooltip" data-placement="bottom" title="Legitimacy">
                        <i class="fa fa-check-circle"></i>
                    </div>
                    <div class="bar-label-icon" data-toggle="tooltip" data-placement="bottom" title="Geography">
                        <i class="fa fa-map-marker"></i>
                    </div>
                </div>
            </div>

            <!-- Notes below the bars/graph -->
            <div class="explanations">
                <!-- Note below the bars/graph -->
                <div class="explanation">
                    <div class="explanation-check"><i class="fa fa-first-comment">&nbsp;&nbsp;&nbsp;&nbsp;</i>
                        <div class="explanation-text">
                            comment_one
                        </div>
                    </div>
                </div>

                <!-- Note below the bars/graph -->
                <div class="explanation">
                    <div class="explanation-check"><i class="fa fa-second-comment">&nbsp;&nbsp;&nbsp;&nbsp;</i>
                        <div class="explanation-text">
                            comment_two
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Tweet score -->
        <div class="score-wrapper">
            <div class="score animated">tq_engage_score</div>
        </div>

        <!-- Tweet content -->
        <div class="tweet-content">
            s_twt_text
        </div>

        <!-- Time since tweet was posted -->
        <div class="tweet-time-elapsed">
            s_twt_time
        </div>

        <!-- Area below tweet with reply textarea and buttons -->
        <div class="tweet-reply-section animated">
            <!-- Reply textarea -->
            <textarea class="tweet-reply animated">@s_twt_owner_sn </textarea>

            <!-- Buttons -->
            <div class="buttons animated">
                <!-- Small buttons on top of reply button -->
                <div class="top-buttons">
                    <span class="character-count">
                    </span>
                </div>

                <!-- Reply button -->
                <div class="reply-button">
                    Reply <i class="fa fa-reply"></i>
                </div>
            </div>
        </div>
    </div>
</div>

JavaScript:

   /**
     * Add a tweet to the feed.
     */
    function _addTweetToFeed(tweet, keywords) {
        /** Get the tweet template */
        var tweetHtml = $('#tweet-template').html();

        // add score heights and colors properties to the tweet
        tweet = _setScoreBars(tweet);

        /** Linkify elements of the tweet */
        tweet.s_twt_text = twitterify(tweet.s_twt_text); // the tweet
        tweet.s_twt_owner_desc = twitterify(tweet.s_twt_owner_desc);

       // fix search terms to be highlighted
        tweet.s_twt_text = _highlightSearchTerms(tweet.s_twt_text, keywords); // the tweet
        tweet.s_twt_owner_desc = _highlightSearchTerms(tweet.s_twt_owner_desc, keywords);

       // change from twitter links to readable links
       tweet = _fixTweetLinks(tweet);

        /** Make numbers readable */
        tweet.s_twt_owner_num_following = abbrNum(tweet.s_twt_owner_num_following, 1);
        tweet.s_twt_owner_num_follower = abbrNum(tweet.s_twt_owner_num_follower, 1);
        tweet.s_twt_owner_num_twt = abbrNum(tweet.s_twt_owner_num_twt, 1);

        /** Loop through the properties of tweet object and populate tweetHtml with them */
        for (var prop in tweet) {
            if (tweet.hasOwnProperty(prop)) {
                tweetHtml = _replaceAll(tweetHtml, prop, tweet[prop]);
            }

            // add comments
            tweetHtml = _addComments(tweet, tweetHtml);

            /** If both location and url are not present, remove the comma */
            if (!(tweet.s_twt_owner_loc && tweet.s_twt_owner_url)) {
                $('#url_comma').html('');
            }
        }
        $('#tweets-container').append(tweetHtml);
    }
Will Hua
  • 1,729
  • 2
  • 23
  • 33
  • 3
    Are there any console errors? How is the DOM element being generated? – Aweary Feb 26 '15 at 18:36
  • How are you adding new elements to the document? All jQuery selectors require DOM elements to exist. Using `$(target).append(element);` should be enough to add it to the DOM. – Alejandro Iván Feb 26 '15 at 18:37
  • 2
    Using the correct terminology is important. You are not binding anything to the button, you bind the handler to `body`. – Felix Kling Feb 26 '15 at 18:37
  • There aren't any errors in the console. I get HTML that I clone from a hidden template, and then append it to a container that is empty when the document is ready. – Will Hua Feb 26 '15 at 18:38
  • Evaluate: `$("body.reply-button")` in your console. Is the element you are clicking on part of the results? – Dave Feb 26 '15 at 18:39
  • You just said in a comment to an answer "I just tried wrapping the function and it didn't seem to work." I was assuming that "intermittently" meant it was happening once a week or something. If you can reproduce it THAT easily, can you put your JS into a JSFiddle? – aquinas Feb 26 '15 at 18:44
  • Wouldn't `$(".reply-button").on('click', function())` be suficient? – aldux Feb 26 '15 at 18:45
  • 1
    @aldux: Event delegation is used for a reason: *"an element that is generated on the fly"* – Felix Kling Feb 26 '15 at 18:46
  • @aquinas I'm not sure what you mean by that. I can't reproduce the desired result easily. It wasn't working for a few days; an hour ago it started working, and then it stopped again. – Will Hua Feb 26 '15 at 18:48
  • 1
    I'm saying, can you post your HTML and JS here: http://jsfiddle.net/ so we can see how it *doesn't* work :). As it stands, there is too little information to go on. – aquinas Feb 26 '15 at 18:50
  • In other words, whether or not you figure out how to create a jsFiddle, at least post the ***relevant*** HTML markup in your OP. – Sparky Feb 26 '15 at 18:54
  • I thougth `on()` replaced the deprecated `live()`. It did not. I think this can help clarify it: http://stackoverflow.com/questions/8021436/turning-live-into-on-in-jquery – aldux Feb 26 '15 at 18:54
  • 1
    Definitely agree with @acquinas, and recommend recreating your problem in jsfiddle *before* asking questions on SO. Half the time this will cause you to realize what the problem is. – Luke Feb 26 '15 at 19:00
  • @aldux: Yes it did. `.on` replaces `.live` and `.delegate` and `.bind`. – Felix Kling Feb 26 '15 at 19:08
  • @aquinas Yes you are right, sorry. I meant for the tweets-container to be the container and the tweets-template to be the template. I forgot about that when I copy pasted the code over. The "reply-button" is near the bottom of the HTML I believe. – Will Hua Feb 26 '15 at 19:09
  • @Luke, sorry about that thanks for the recommendation and will do next time. – Will Hua Feb 26 '15 at 19:09
  • 1
    Where is the rest of your JS? I don't see the code to hook up your event handler anywhere. – aquinas Feb 26 '15 at 19:10
  • @acquinas are you talking about the code that calls `_addTweetToFeed`? or the code that is called on the `$("body").on("click")`? – Will Hua Feb 26 '15 at 19:18

2 Answers2

0

"Sometimes it works just as it's suppose to" this line alone suggests that you run your code prior to DOM creation. sometimes your browser creates the dom fast enough and the handler is being attached to the body, and sometimes your javascript runs first and it isnt being attached. wrap your code in this:

$(function(){
    $("body").on("click", ".reply-button", function(){
        alert("test");
    });
});
r0m4n
  • 3,474
  • 3
  • 34
  • 43
Banana
  • 7,424
  • 3
  • 22
  • 43
  • 3
    *"sometimes your browser creates the dom fast enough and the handler is being attached to the body, and sometimes your javascript runs first and it isnt being attached."* I highly doubt that. The page is parsed sequentially. Either `body` exists when the code is called or it doesn't. There is no race condition here. – Felix Kling Feb 26 '15 at 18:41
  • I just tried wrapping the function and it didn't seem to work. I thought the purpose of using .on() instead of .click() was to handle for the fact that the DOM wasn't present when the JavaScript ran? – Will Hua Feb 26 '15 at 18:42
  • body is part of the dom as well. if you want to attach it before dom is ready then delegate it to `document` – Banana Feb 26 '15 at 18:44
  • This might help untangle the order of things? http://stackoverflow.com/questions/22083248/which-is-the-load-rendering-and-execution-order-of-elements-in-a-html-page/22159928#22159928 – Luke Feb 26 '15 at 18:44
  • Banana, my comment was in response to the one above mine... although, I have to agree with @FelixKling's comment too. – Sparky Feb 26 '15 at 18:45
0

Does anyone have any idea on how I should go about debugging this?

Sometimes it's best to pull out a tiny portion of code, and see if it works in isolation. For example: http://jsfiddle.net/6v7z9fak/

js:

$("body").on("click", ".reply-button", function(){
    alert("test");
});

html:

<button type="button" class="reply-button">Reply</button>

As you'll see, it works fine. So you can be confident that the error is not in that bit of code alone. It is either another part of the code, or how the parts are interacting together, or something else. Now slowly add more code, test, and see where it breaks.

Luke
  • 18,811
  • 16
  • 99
  • 115
  • Keep in mind that jsFiddle is automatically using an internal DOM ready event handler. – Sparky Feb 26 '15 at 18:55
  • 1
    ^True, good point. You should also take the bit of code you're testing and put it in a separate, otherwise blank page in the same environment as the page that's giving you the problem. Debugging is all about keeping one thing constant and changing other variables. – Luke Feb 26 '15 at 18:58
  • How would you recommend isolating it if it were highly reliant on other portions of code? Would you just copy paste everything in? – Will Hua Feb 26 '15 at 19:15
  • If it is the reply button that is failing then start with a blank page, and add in the button only -- like the jsfiddle I linked to. Then start rebuilding the page, adding existing code piece by piece. There are many ways to debug, this is one approach. You'll have to do some more work to narrow down your issue because you can ask a question that is answerable. – Luke Feb 26 '15 at 20:24