BRBT
BRBT

Reputation: 1487

Jquery clone div breaking events

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?

Upvotes: 0

Views: 2349

Answers (3)

SpYk3HH
SpYk3HH

Reputation: 22570

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.

Upvotes: 3

thecotne
thecotne

Reputation: 478

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

var copy = $firstTRCopy.clone(true);

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

Upvotes: 3

elixenide
elixenide

Reputation: 44841

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",

Upvotes: 2

Related Questions