0

I am cloning a div and changing all of the child element id's and appending it below the original div. Now within that div I have several buttons that trigger events when clicked. Each one of these buttons have a class name that I am using to fire the event opposed to using the id. However when I try to click on any buttons within my cloned div none of my events are firing.

Here is how I am cloning my div, changing the id's and appending it:

$(function() {    
  var $section = $("#facility_section_info").clone();
  var $cloneID = 1; 
    $( ".addSection" ).click(function() { 
        var $sectionClone = $section.clone(true).find("*[id]").andSelf().each(function() { $(this).attr("id", $(this).attr("id") + $cloneID); });
        $('#facility_section_info').append($sectionClone);
        $cloneID++; 
    });
});

here is an original uncloned button within the div that fires an event.

<input type="button" value="+" id="addRows" class="addRows"/>

And here is what the cloned button looks like:

<input type="button" value="+" id="addRows1" class="addRows">

The only thing that has changed is that I have added a 1 at the end of the id.

Here is the event that gets fired when this button gets clicked:

$(function() {
    var $componentTB = $("#component_tb"),
        $firstTRCopy = $("#row0").clone();
        $idVal = 1;
    $(".addRows").click(function() {
        var copy = $firstTRCopy.clone();
        var newId = 'row' +$idVal;
        copy.attr('id', newId);
        $idVal += 1;
        copy.children('td').last().append("<a href=\"javascript:remove('" + newId + "')\">Remove</a>");
        $componentTB.append(copy);
    });
});

All this function does is clone a table row in its original form and append it to the end of a table with an added remove link. This function works exactly how I need it to other than not firing when I click on my cloned button.

Why do my cloned buttons not fire any of my events that are called by class name and not id?

BRBT
  • 1,467
  • 8
  • 28
  • 48
  • binding the events may be .... use .on to bind the events and see if works! – Kalish Apr 14 '15 at 12:58
  • @Kalish if you read my question you will notice that I am doing exactly that already. – BRBT Apr 14 '15 at 12:59
  • And the downvote was for....? – BRBT Apr 14 '15 at 13:00
  • I can't see you are using .on to bind the events!! – Kalish Apr 14 '15 at 13:03
  • @Kalish `.click()` is shortcut for `.on("click")` – Regent Apr 14 '15 at 13:07
  • well, there is a significance difference in the usage pattern .... see this for more information.... http://stackoverflow.com/questions/9122078/difference-between-onclick-vs-click – Kalish Apr 14 '15 at 13:11
  • @Kalish we are talking about `$(".addRows").click(function() {` and `$(".addRows").on("click", function() {`, which are just the same. You can use `.click()` to trigger events and `.on()` for delegated event handlers, but this is not we are talking about. My answer was to your phrase _I can't see you are using .on to bind the events!_ – Regent Apr 14 '15 at 13:14
  • @Regent - well, I think we both are on the same page! .click() and .on('click') are same but to answer this question, both are not same – Kalish Apr 14 '15 at 13:20

3 Answers3

3

jquery clone method has one parameter "withDataAndEvents" you need to pass true like this

var copy = $firstTRCopy.clone(true);

https://api.jquery.com/clone/

thecotne
  • 478
  • 3
  • 10
2

Your events aren't firing because the handlers are set before you do the cloning. You need to use event delegation.

Instead of this:

$(".addRows").click(

do this:

$("body").on("click", ".addRows",
elixenide
  • 44,308
  • 16
  • 74
  • 100
2

Easiest answer; the clone isn't breaking the event. The event isn't firing because it's never assigned to the new element. What you're doing when you clone the element is creating a dynamic element. This means it's added to the DOM after the page has loaded, at which point, your event was already delegated.

The way you're delegating the event is not wrong. It's simply not the best practice when you will have dynamic elements. There are a few ways you can handle this.

Using .clone()'s withDataAndEvents parameter.

The withDataAndEvents parameter of .clone() is intended to make this process easier by copying data and events to the new element, as such

var copy = $firstTRCopy.clone(true)

However, this can have a few nasty side-effects. For instance, it's been known to clone id's and if your event is assigned to an id, you'll still only get fire on the first element.

Event Delegation - See Understanding Event Delegation

This is a method by which we use a parent element, or even the document root Object to attach events that will always be fired on all children. Such as:

$(document).on('click', '.addRow', function(e) {...})

Due to convenience more than anything, it's often not recommended you attach to the document root. I had first suggested it because I was in public and it was a quick and easy answer. It's not unsafe to attach to the document root, but can become an issue if you have a lot going on in your page. See, each element an event is assigned to will bubble up when the event is fired. Simply put, when you attach 'click' to an element with an event delegated via the document, you're essentially clicking the entire DOM. More often than not, it's not really an issue and I make use of it all day long for convenience of readability.

However, the more proper way would be to look for the closest parent element that exist on load. That is to say, it is not created dynamically. Simply put, like so:

    $('#parentAlreadyLoaded').on('click', '.addRow', function(e) {...})

Note: Assigning an event to .addRow using $(document) or $('parent element selector') does not change the event parameter in function(e) {. The e in $('.addRow').click is the same as the e in $(document OR 'parent').on('click', '.addRow'

Short Example

function addRow(e) {
    if (console && console['log']) console.log(e);
 var par = $('#addRows'),
  tr = $('<tr />').appendTo(par),
  tdBlue = $('<td />').appendTo(tr),
  tdRed = $('<td />').appendTo(tr),
  btnBlue = $('<button />', { 'class': 'make-blue', 'text': 'Make Blue' }).appendTo(tdBlue),
  btnRed = $('<button />', { 'class': 'make-red', 'text': 'Make Red' }).appendTo(tdRed)
}

$(function() {
 $('thead button').on('click', addRow);
 $('#addRows').on('click', '.make-blue', function(e) {
        if (console && console['log']) console.log(e);
  $(this).closest('tr').removeClass('red').addClass('blue');
 });
 $(document).on('click', '.make-red', function(e) {
        if (console && console['log']) console.log(e);
  $(this).closest('tr').removeClass('blue').addClass('red');
 });
})
table { border-collapse: collapse; margin: 0 auto; }
tr { padding: .25em .5em; text-align: center; }
th, td { border: 3px groove; }
th button { margin: .25em auto; }
td button { margin: .5em; }
.blue { background-color: #00F; }
.red { background-color: #F00; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<table>
    <thead>
        <th colspan="2">
            <button>Click Me</button>
        </th>
    </thead>
    <tbody id="addRows"></tbody>
</table>

Also

Making use of $(document).on, as aforementioned is handy for readability. For example, If I have a simple page, with 20 or less events and none are really overlapping, then I'll use it to assign events as such:

$(document)
    .on('event', 'selector1', function(e) { /*  do Work */ })
    .on('differentEvent', 'selector1', function(e) { /* do Work */ })
    .on('event', 'selector2', function(e) { /*  do Work */ })
    .on('event', 'selector3', function(e) { /*  do Work */ })
    .on('differentEvent', 'selector3', function(e) { /* do Work */ })
    .on('differentEvent2', 'selector3', function(e) { /*    do Work */ })

This is possible due to jQuery's chainability. Just be careful doing this as you made to use e.stopPropagation() to prevent bubbling.

SpYk3HH
  • 22,272
  • 11
  • 70
  • 81
  • Why should I do this? – BRBT Apr 14 '15 at 13:02
  • To assign the event to dynamic elements – SpYk3HH Apr 14 '15 at 13:04
  • 1
    @BigRabbit here you go: [docs](https://api.jquery.com/on/#direct-and-delegated-events). – Regent Apr 14 '15 at 13:08
  • @Regent thanks, so would it be safe to always delegate events instead of having them direct? – BRBT Apr 14 '15 at 13:24
  • 1
    @BigRabbit it's not about safety but about purpose of usage :) If element is created dynamically, you can use delegated event handler or you can add event handler after element is inserted into DOM. – Regent Apr 14 '15 at 13:28
  • @BigRabbit was in public and in a hurry with previous answer, now it's been updated with a lot more detail. feel free to ask me anything about it – SpYk3HH Apr 14 '15 at 13:55
  • @SpYk3HH Thats awesome that makes a lot more sense to me now thanks so much. My only confusion now is will I have to change anything within my event if I am taking in a parameter `(e)`, into my function or ..? – BRBT Apr 14 '15 at 14:05
  • @BigRabbit I added an example. Also, to answer your question, if I think I know what you mean, *no*. In *event delegation*, the parameter `e` or `event` is still the same as always. I'll add to answer real quick – SpYk3HH Apr 14 '15 at 14:14
  • @BigRabbit Not a problem. I added a little more to the answer, just to try and give you a little more understanding of the usefulness of this type of delegation. – SpYk3HH Apr 14 '15 at 14:26
  • @SpYk3HH yeah that helps for sure, very interesting and clean. I wish all these people upvoted this answer instead! ;) – BRBT Apr 14 '15 at 14:35