0

I have an input field which fires an AJAX request and fills a <div> ($container) element with HTML.

The JSON node response.resultsHTML contains the HTML that is to be added to $container, the HTML looks like:

<div class="js_livesearch livesearch">
    <div class="js_livesearch_results livesearch_results">
    <div class="js_livesearch_result livesearch_result" data-id="3">item_1</div>
    <div class="js_livesearch_result livesearch_result" data-id="2">item_2</div>
    <div class="js_livesearch_result livesearch_result" data-id="1">item_3</div>
    <div class="js_livesearch_result livesearch_result" data-id="4">item_4</div>
    </div>
</div>

The JavaScript/jQuery that performs the AJAX call and adds the AJAX response's HTML to the DOM looks like the following:

  $liveSearchTrigger.on('input', function () {
        var search = this.value;
        if (search) {
            $.ajax({
                url: '/ajax/livesearch/' + $(this).data('identifier'),
                type: 'POST',
                dataType: 'JSON',
                data: {
                    search: search
                },
                success: function (response) {
                    if (response.success) {
                        if (response.resultsCount) {
                            $container.html(response.resultsHTML);

                            $container.find('.js_livesearch_result').each(function() {
                                var $result = $(this);

                                //this is being called correctly for EVERY SINGLE $result 
                                alert("found: " + $result.text());

                                //this is only sporadically bound to $result, not every single $result has the click handler - WHY?
                                $result.on('click', function() {
                                    $liveSearchTrigger.val($result.text());
                                    alert('attempting to set ' + $result.text());
                                });
                            });

                            reLayout();
                            $container.slideDown(150);
                        } else {
                            $container.slideUp(150);
                        }
                    }
                }
            });
        } else {
            $container.slideUp(150);
        }
    });

Now, after adding the newly generated HTML to the DOM using $container.html(response.resultsHTML); I want to bind a click event to every single $('.js_livesearch_result') element.

In the $container.find('.js_livesearch_result').each(function() { ... }); scope, the alert is called on every .js_livesearch_result item and it alerts 'item_1', item_2', [...] for all elements, but the $result.on('click', function() { ... }); is not bound to all of them. Sometimes only the first two, sometimes three have the click event bound.

What am I doing wrong trying to bind the click event to the dynamically generated .js_livesearch_result elements and how do I bind the event properly, so that every single .js_livesearch_result element has the click handler bound?

EDIT:

Here is some more (updated) code hence I still can't get the $('.js_livesearch_result').on('click', function() {...}); to work properly:

The $liveSearchTrigger element's HTML:

<input class="js_livesearch" type="text" name="key" value="" data-identifier="langkey" autocomplete="off" />

The response.resultsHTML structure, which will be appended to $container:

<div class="js_livesearch_results livesearch_results">
    <div class="js_livesearch_result livesearch_result" data-id="3">menu_code</div>
    <div class="js_livesearch_result livesearch_result" data-id="2">menu_gallery</div>
    <div class="js_livesearch_result livesearch_result" data-id="1">menu_home</div>
    <div class="js_livesearch_result livesearch_result" data-id="4">menu_user</div>
</div>

The page.js to initialize the plugin:

$(document).ready(function() {
    $('.js_livesearch').liveSearch();
});

The livesearch.js (the plugin):

(function ($, window) {

    $.fn.liveSearch = function (options) {

        var defaults = {
            highlightCSS: {
                'background-color': "lightgoldenrodyellow",
            }
        };

        var settings = $.extend({}, defaults, options);



        var $liveSearchTrigger = $(this);


        var searchUID = Math.floor(Math.random() * 26) + Date.now();
        var $container = $('.js_livesearch_uid' + searchUID);

        if (!$container.length) {
            var $elem = $('<div>').addClass('js_livesearch_uid' + searchUID).addClass('hidden');
            $('body').append($elem);
            $container = $('.js_livesearch_uid' + searchUID);
        }

        $liveSearchTrigger.on('input', function () {
            var search = this.value;
            if (search) {
                $.ajax({
                    url: '/ajax/livesearch/' + $(this).data('identifier'),
                    type: 'POST',
                    dataType: 'JSON',
                    data: {
                        search: search
                    },
                    success: function (response) {
                        if (response.success) {
                            if (response.resultsCount) {
                                $container.html(response.resultsHTML);

                                //this works fine if uncommented, every single
                                //element gets a highlight applied
//                                $container.find('.js_livesearch_result').each(function() {
//                                    $(this).highlight(search, settings.highlightCSS);
//                                });

                                //this does not work consistently, sometimes the first 3 (out of 4)
                                //items will bind the click handler sometimes only the first two 
                                //elements will bind the click handler
                                $container.on('click', '.js_livesearch_result', function() {
                                    console.log('Clicked: ' + $(this).text());
                                });

                                //another test which didn't work:
//                                $container.find('.js_livesearch_result').each(function() {
//                                    $(this).unbind('click.resultItemClick').bind('click.resultItemClick', function() {
//                                       console.log('Clicked: ' + $(this).text()); 
//                                    });
//                                });

                                reLayout();
                                $container.slideDown(150);
                                $container.removeClass('hidden');
                            } else {
                                $container.slideUp(150);
                                $container.addClass('hidden');
                            }
                        }
                    }
                });
            } else {
                $container.slideUp(150);
                $container.addClass('hidden');
            }
        });

        $(window).resize(function() {
            reLayout();
        });

        $liveSearchTrigger.on('focus', function() {
            reLayout();
            $container.slideDown(150);
            $container.removeClass('hidden');
        });

        $liveSearchTrigger.on('blur', function() {
            $container.slideUp(150);
            $container.addClass('hidden');
        });

        function reLayout() {
            var position = $liveSearchTrigger.position();
            var layoutCSS = {
                'position': 'absolute',
                'z-index': '1000',
                'top': (position.top + $liveSearchTrigger.outerHeight()) + 'px',
                'left': position.left + 'px',
                'width': $liveSearchTrigger.outerWidth() + 'px'
            };
            $container.css(layoutCSS);
        }

        return this;
    };

}(jQuery, window));

How do I bind a click event to all .js_livesearch_result elements that are being added to the DOM via AJAX?

phew
  • 808
  • 1
  • 15
  • 34
  • Are you asking how to add event handlers to dynamically-added dom elements? – Dave Newton Oct 01 '17 at 11:33
  • Yes. I am trying to add some `
    item
    ` via AJAX and bind a click event to these newly added elements. For some reason not all of the elements (and not always) receive the click handler, it only (randomly) works for a bunch of them. I've already tried what @Rory McCrossan suggested, without any luck. I am going to post more code, initially I thought the AJAX part itself where the bind occurs is where I'm having a mistake, now I think I am missing out on something somewhere else.
    – phew Oct 01 '17 at 15:16
  • 1
    Search for event bubbling or delegation. – Dave Newton Oct 01 '17 at 16:03
  • I have done some research and read about event delegation on [SO](https://stackoverflow.com/a/1207393/1686067) and on [learn.jQuery.com](http://learn.jquery.com/events/event-delegation/) yet I cannot fix the issue. I have tried using `$container.on('click', '.js_livesearch_result', function() { ... });` *outside* the scope of the `$liveSearchTrigger.on()` function but it still randomly binds events to elements, not consistently to all of them. Any further hint maybe? – phew Oct 01 '17 at 17:40
  • Hard to say w/o a live example to play with (e.g., JSFiddle or similar). Event delegation works pretty well, so I’d suspect something else is going wrong with your code. – Dave Newton Oct 01 '17 at 18:14
  • I think I just figured it out. The problem is that `$liveSearchTrigger.on('blur', ...)` is sometimes fired before the click event is being processed, hence clicking a `.js_livesearch_result` element make `$liveSearchTrigger` lose focus. – phew Oct 01 '17 at 18:27

1 Answers1

0

So the problem was the $liveSearchTrigger.on('blur', ...), removing that one fixed the issue!

phew
  • 808
  • 1
  • 15
  • 34