0

I have some trouble with .click(). I don't understand why.

So the problem is that I have the .click() in my .ready() function. It works fine the first time. I also got a script that rewrites that link over (I am trying to clone stuffs). So when that happens, the .click() stops working...

Here is the code:

$(document).ready(function() {
    $div = $('.period:first');
    clearClones();
    cloneDiv($div,$('#num_periods').val());
    $('a.overtime_add_button').click(function(event) {
        event.preventDefault();
        var id = $(this).attr('id');
        var id_split = id.split("_");
        var period_num = id_split[3];
        $('#period_' +period_num).find('#overtime').append(ot_div);
    });         
});

EDIT Here is the jfiddle: http://jsfiddle.net/K9Fzw/2/

j08691
  • 204,283
  • 31
  • 260
  • 272
Ara Sivaneswaran
  • 365
  • 1
  • 10
  • 25
  • 1
    Note: The `.find('#overtime')` in the snippet suggests that there are multiple elements with `id="overtime"`. If so, that isn't valid and won't work as might be expected. Each `id` value should only be used once throughout the entire `document`. A common alternative is to use `class="overtime"` and `.find('.overtime')`. – Jonathan Lonowski Dec 24 '13 at 02:22

3 Answers3

2

If you are removing the DOM element then the bind goes away and you need to add the bind again.

I would create a function that attaches the behaviors that you can call multiple times

// Pass in context to reduce how far jQuery has to search
function attachButtonBehaviors(context){
  // Add a class to know that you have bound the click event to this element and change your selector - alternatively this function could always unbind all clicks but I would argue the class method is preferable
  $('a.overtime_add_button:not(.processed)', context).addClass('processed').click(function(event) {
      event.preventDefault();
      var id = $(this).attr('id');
      var id_split = id.split("_");
      var period_num = id_split[3];

      $('#period_' +period_num).find('#overtime').append(ot_div);
  });  
}  

On document.ready call this function and when you clone the link call this function.

user2923779
  • 230
  • 1
  • 4
  • Hey, how do I pass the context has parameter? – Ara Sivaneswaran Dec 24 '13 at 02:32
  • Context is just any old jQuery object. So during your document.ready callback I would call the function like this attachButtonBehaviors($(document));. But when you are adding the new dom element, I would call attachButtonBehaviors($(yourNewjQueryDOMElement)). This is merely an efficiency thing and is not needed. – user2923779 Dec 24 '13 at 02:34
  • I can't get it to work. I updated the jsfiddle with what i have. Basically, it works the first time but when I add more stuff, it stops working. – Ara Sivaneswaran Dec 24 '13 at 02:46
  • How about this - the button behaviors seem to always be getting added. http://jsfiddle.net/K9Fzw/4/ – user2923779 Dec 24 '13 at 02:54
  • Hi, your jsfiddle doesn't work.. When you select another number of period, it doesnt work... – Ara Sivaneswaran Dec 24 '13 at 03:05
  • don't keep calling the function. use $.on(). that is one of the reasons it is there. – loushou Dec 24 '13 at 03:16
1

As you are cloning, dynamic binding of elements would be required as explained here ..

Event binding on dynamically created elements?

Community
  • 1
  • 1
srinigowda
  • 80
  • 9
  • So do I add a $(document).on() and then put the click function in there? I am kinda confused. But I think that's the problem, I thought so too but wasn't 100% sure. – Ara Sivaneswaran Dec 24 '13 at 02:28
1

In jQuery, the .click() function is a one time call. This means that, when you call it on a selector, like $('.my-class').click(function(e){ alert('click'); });, it only applies that callback to the elements that match the selector at that exact moment in time. Putting the .click() call into the $(document).ready() function, makes it apply after the entire page loads. If it lives outside of the $(document).ready() function, then it runs immediately when it is encountered by the browser. Here is an example to kind of demonstrate what I mean, since examples often help:

<html>
  <head>
    <script src="/path/to/jQuery.js"></script>
    ...
  </head>
  <body>
    <div class="is-clickable click1">I change color and cause a popup</div>

    <script type="text/javascript">
      $('.is-clickable').click(function(e) {
        alert('clickable div? blasphemy!');
      });
    </script>

    <div class="is-clickable click2">I grow in size</div>

    <script type="text/javascript">
      $('.click1').click(function(e) {
        $(this).css({ color:'#ff0000' });
      });
      $('.click2').click(function(e) {
        $(this).css({ fontSize:'300%' });
      });
    </script>
    ...
  </body>
</html>

Ok so in the first JavaScript block, at first glance, you might think that $('.is-clickable') matches 2 elements, both of the divs. This is wrong. It only matches the first div. The reason is because, at the moment that the JavaScript block is executed, only one element with the is-clickable class, exists. The second div, that also has the class is-clickable is created after the first JavaScript block runs. The second JavaScript block runs as expected. Both selectors each find a single element, because at this point in time there is both one element with the click1 class and one with the click2 class.

If you were to wrap these bits of JavaScript in the $(document).ready() block, then their execution would be deferred until after the entire page loads.

<html>
  <head>
    <script src="/path/to/jQuery.js"></script>
    ...
  </head>
  <body>
    <div class="is-clickable click1">I change color and cause a popup</div>

    <script type="text/javascript">
      $(function() {
        $('.is-clickable').click(function(e) {
          alert('clickable div? blasphemy!');
        });
      });
    </script>

    <div class="is-clickable click2">I grow in size</div>

    <script type="text/javascript">
      $(function() {
        $('.click1').click(function(e) {
          $(this).css({ color:'#ff0000' });
        });
        $('.click2').click(function(e) {
          $(this).css({ fontSize:'300%' });
        });
      });
    </script>
    ...
  </body>
</html>

This code now yields one div that pops an alert() and changes color & one div that pops an alert() and grows in text size. The first JavaScript bit is now executed after the entire page loads, thus the selector $('.is-clickable') now actually finds 2 matches, and therefore adds the alert() bit to both.

Your problem sounds like you suffer from this; however, your problem also sounds like you suffer from the exact same problem with different timing. It sounds like when you put the code in the $(document).ready() block, it runs correctly, and your $('.is-clickable') selector actually matches both divs (from my example). But after you manipulate the DOM, and after the $(document).ready() block has already run, your click no longer works. This is because, again, your code has run before the elements (which you are trying to modify the click event of) exist. You can solve this easily. You can do one bit of code that will run your special click function on all the selected elements, regardless of when they are added to the page. Use the $('.is-clickable').on() function (well technically the $(document).on('click', '.is-clickable', function(){}) function, lol).

.on() can be used to apply an event listener to every element that matches the selecter, that ever enters the page, before or after your .on() function call happens. This is how you would do it, with your code:

// still run your extra setup, after the page loads
$(function() {
  $div = $('.period:first');
  clearClones();
  cloneDiv($div,$('#num_periods').val());
});

// change your old line to use the .on() function
// OLD LINE:  $('a.overtime_add_button').click(function(event) {
$(document).on('click', 'a.overtime_add_button', function(event) {
  event.preventDefault();
  var id = $(this).attr('id');
  var id_split = id.split("_");
  var period_num = id_split[3];

  $('#period_' +period_num).find('#overtime').append(ot_div);
});         

Now your click event is applied to all <a class="overtim_add_button"> that ever enter the page. It applies to ones that currently exist and ones that you will add in the future. You only ever have to define this code once. Now lets say that you only want your code to run on <a class="overtim_add_button"> that live inside of the <div id="special-click-links">. You could do this, once:

// still run your extra setup, after the page loads
$(function() {
  $div = $('.period:first');
  clearClones();
  cloneDiv($div,$('#num_periods').val());
});

// change your old line to use the .on() function
// OLD LINE:  $('a.overtime_add_button').click(function(event) {
$(document).on('click', '#special-click-links a.overtime_add_button', function(event) {
  event.preventDefault();
  var id = $(this).attr('id');
  var id_split = id.split("_");
  var period_num = id_split[3];

  $('#period_' +period_num).find('#overtime').append(ot_div);
});         

The .on() function, will almost always solve this type of problem. On top of that, .on can be used to defining any element based events you need to hook to an element. It is kinda an all in one function.

Anyways, hope this helps explain why you have the problem, as well as how you can easily solve this problem, now and in the future. Furthermore, I hope that others find it helpful too. Here is a jsfiddle of a working example of how .on() can be used to solve your problem.

loushou
  • 1,462
  • 9
  • 15