Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery event delegation is not working on jQuery UI datepicker

I'm trying to stop a particular click event from bubbling to document-root, which in result closes one of my popup. I need to stop bubbling of the event and body or html are my only options to intercept and stop it.

The date-picker popup is generated on the fly so I cannot use a direct event on .ui-icon element, so I have registered a delegate event on body element to stop it from bubbling.

(function ($) {
    $(function () {
        $('body').on('click', '.ui-icon', function (e) {
            e.stopPropagation();
        });
    });
})(jQuery);

Surprisingly enough registering a direct event to body element and checking the event's target works just fine.

(function ($) {
    $(function () {
        $('body').on('click', function (e) {
            if ($(e.target).is('.ui-icon')) {
                e.stopPropagation();
            }
        });
    });
})(jQuery);

I am really at a loss, why the previous one does not work where the later does, both of them are supposed to do the same. What am I missing? It might have to do with jQuery datepicker getting recomposed (its whole content block is rebuilt on navigation) before the event reaches body (but it does not make sense)?

Snippet with the issue is added below. I just want the arrows (datepicker navigation) to stop bubbling to document/root level (which closes my popup) and because datepicker gets appended to body, the only available intercept points are body/html.

$(function() {
  let popup = $('#some-popup').addClass('visible');
  let input = $('#some-date');
  let toggler = $('#toggler');

  // binding popup
  toggler.on('click', function(e) {
    e.stopPropagation();
    popup.toggleClass('visible');
  });

  // initializing jQuery UI datepicker
  input.datepicker();

  // closing popup on document clicks other than popup itself
  $(document).on('click', function(e) {
    let target = $(e.target);

    if (target.is('.ui-icon, .ui-datepicker-prev, .ui-datepicker-next')) {
      console.warn('shouldn\'t have reached this, got: ' + target.attr('class'));
    }

    if (!(target.is('#some-popup'))) {
      popup.removeClass('visible');
    }
  });

  // trying to prevent click from reaching document
  $('body').on('click', '.ui-icon, .ui-datepicker-prev, .ui-datepicker-next', function(e) {
    e.stopPropagation();
  })
});
#some-popup {
  padding: 15px 25px;
  background: #000;
  color: #fff;
  display: none;
  max-width: 200px;
}

#some-popup.visible {
  display: block;
}

#toggler {
  margin-bottom: 10px;
}
<head>
  <link href="https://code.jquery.com/ui/1.11.4/themes/black-tie/jquery-ui.css" rel="stylesheet">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
</head>

<body>

  <div id="some-popup">
    This is the popup
  </div>
  <button type="button" id="toggler">Show/Hide Popup</button>
  <form>
    <label for="some-date">The Date-Picker</label>
    <input id="some-date" onclick="event.stopPropagation();" />
  </form>
</body>
like image 832
Ravenous Avatar asked Mar 13 '18 16:03

Ravenous


1 Answers

The delegate event does not fire because when you click on the arrows, at first the arrow button's click event is fired where jquery-ui-datepicker removes the whole calendar element from body element and generates a new calendar for previous/next month.

You can verify if an element is removed or not by checking if the element has any parent <body> tag i.e by checking the length of closest('body').

    $('body').on('click', function (e) {
        if ($(e.target).is('.ui-icon')) {
            console.log($(e.target).closest('body').length);
            // Prints 0 i.e this element is not a child of <body>
        }
    });

To fire the delegate event, the target element must be a child element of the event bound element, otherwise jQuery does not trigger the event. The following Demo confirms it.

$(function() {
  $('.a').on('click', function() {
    console.log('Direct Event');
  })
  $('.a').on('click', '.b,.c', function(e) {
    console.log('Delegate Event');
  })
  $('.b').on('click', function() {
    console.log('datepicker arrow event');
    if($('input').is(':checked')) $(this).remove();
  })
});
.a {
  padding: 20px;
  background: #ffc55a;
  text-align: center;
}
.b {
  margin-top: 5px;
  padding: 5px;
  background: #7ddbff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="a">
  This is the parent element (Delegate Bound Element)
  <div class="b">
    This is the Arrow element of date-picker (Delegate Target)<br>
    Click on it to see how many events fire
  </div>
</div>
<input type="checkbox" checked> Remove On<br>
With Remove Off 3 events fire<br>
With Remove On 2 events fire, Delegate event does not fire<br>
like image 200
Munim Munna Avatar answered Nov 05 '22 17:11

Munim Munna