最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Cloning a bootstrap element with an event listener - Stack Overflow

programmeradmin4浏览0评论

I'm trying to clone a bootstrap element that has data-toggle behavior provided by bootstrap:

HTML

<div class="container">
<button aria-expanded="false" data-target="#collapsible_obj_0" data-toggle="collapse" class="btn btn-link collapsed">click here</button>
<div style="height: 0px;" aria-expanded="false" id="collapsible_obj_0" class="collapse">
  <span>foo</span>
</div>
</div>

After cloning, I am changing the ID of the div to a new unique id, and the data-target of the button to point to the new div.

JS

  var header = objectContainer.clone(true);
  var counter = this.collapsibleObjCounter++;
  var collapseId = "collapsible_obj_" + counter;
  header.find(".collapse").attr("id", collapseId);
  header.find("button[data-toggle='collapse']").attr("data-target", "#"+collapseId);

The button and div are children of the object container I am cloning.

Sometimes this works, but sometimes I end up with a button that still expands and contracts the original div, even though when I inspect the HTML, the IDs look correct.

I suspect that the event handler that is copied may be hard coding the reference to the id of the div to be expanded and contracted, which is why just fixing the IDs in the DOM elements doesn't work. However, that doesn't explain why some of the clones work and others do not.

What is the correct way to clone something that has bootstrap behaviors attached to it?

So, a couple of answers have pointed out that just removing true from my clone() call will avoid copying the event listener. So I now realize that my problem is a little more plicated than the one I oversimplified here. I will ask it as a separate question. (Cloning a Bootstrap element but not all of the event listeners)

I'm trying to clone a bootstrap element that has data-toggle behavior provided by bootstrap:

HTML

<div class="container">
<button aria-expanded="false" data-target="#collapsible_obj_0" data-toggle="collapse" class="btn btn-link collapsed">click here</button>
<div style="height: 0px;" aria-expanded="false" id="collapsible_obj_0" class="collapse">
  <span>foo</span>
</div>
</div>

After cloning, I am changing the ID of the div to a new unique id, and the data-target of the button to point to the new div.

JS

  var header = objectContainer.clone(true);
  var counter = this.collapsibleObjCounter++;
  var collapseId = "collapsible_obj_" + counter;
  header.find(".collapse").attr("id", collapseId);
  header.find("button[data-toggle='collapse']").attr("data-target", "#"+collapseId);

The button and div are children of the object container I am cloning.

Sometimes this works, but sometimes I end up with a button that still expands and contracts the original div, even though when I inspect the HTML, the IDs look correct.

I suspect that the event handler that is copied may be hard coding the reference to the id of the div to be expanded and contracted, which is why just fixing the IDs in the DOM elements doesn't work. However, that doesn't explain why some of the clones work and others do not.

What is the correct way to clone something that has bootstrap behaviors attached to it?

So, a couple of answers have pointed out that just removing true from my clone() call will avoid copying the event listener. So I now realize that my problem is a little more plicated than the one I oversimplified here. I will ask it as a separate question. (Cloning a Bootstrap element but not all of the event listeners)

Share Improve this question edited May 23, 2017 at 12:22 CommunityBot 11 silver badge asked Jul 30, 2016 at 5:20 PurpleVermontPurpleVermont 1,2414 gold badges20 silver badges48 bronze badges 7
  • Can you show your code? – jonju Commented Jul 30, 2016 at 5:51
  • @jonju I added the javascript I am using to modify the clone. – PurpleVermont Commented Jul 30, 2016 at 18:00
  • what is objectContainer in above code ? – Sanjay Radadiya Commented Aug 1, 2016 at 6:42
  • what is the collapsibleObjCounter ? if you are doing this in some loop please provide the ple code block of cloning logic. It will be helpful. Thank you – Abdul Moiz Khan Commented Aug 1, 2016 at 6:49
  • I tried to create a jsfiddle but I couldn't get the bootstrap stuff to work properly. objectContainer is a div that contains the button and the other div. collapsibleObjCounter is just a counter, a global variable that is used to create new IDs for each new collapse target – PurpleVermont Commented Aug 1, 2016 at 6:55
 |  Show 2 more ments

4 Answers 4

Reset to default 9 +100

So far your code is ok, just remove true from clone() it's not needed.

UPDATED

This Boolean value indicates whether event handlers should be copied along with the elements. The default value is to the false . So, when we were calling the .clone() method without passing any Boolean value, it was just copying the elements but not the event handlers attached to it. But, when we pass the value true , it copies both the elements and any event handlers attached to it.

But Bootstrap is handling the event handlers for dynamic objects so you don't need to true in clone.

LIKE

If you're handling events for dynamic objects using this way

$(".btn").click(.....); 
// This button was dynamically created and you want a click event for it, 
// but it wont work because at the time of event binding this button wasn't exist at all.

BUT

You need to handle events for dynamic objects using event delegation technique.

 $(document).on("click",".btn",function(){ .... });

This will work, because the event handler is bound to an element higher up the DOM tree (in this case, the document) and will be executed when an event reaches that element having originated on an element matching the selector, And that's what Bootstrap does for dynamic objects, also you do the same if needed for dynamic objects. Hers is JSFiddle for this.

Also you need to wrap the whole collapsible part in a div for cloning.

Note: Using .clone() has the side-effect of producing elements with duplicate id attributes, which are supposed to be unique. Where possible, it is remended to avoid cloning elements with this attribute or using class attributes as identifiers instead.

So, you need to update data-target and div id attribute after cloning, so that newly created button targets the newly created collapse panel

I am using jQuery for it.

Here is the code Snippet

$(function(){
  var target = "collapsible_obj_";
  var i = 1;
  
  $("#button").click(function(){
    $(".parent_colapse:last").clone().insertAfter(".parent_colapse:last");        
    $(".parent_colapse:last > button").attr("data-target", "#"+target+i);
    $(".parent_colapse:last .collapse").attr("id", target+i);
    i++;
  });
  
  $(document).on("click",".button",function(){
     alert();
  });
});
<link href="https://maxcdn.bootstrapcdn./bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn./bootstrap/3.3.7/js/bootstrap.min.js"></script>

<div class="parent_colapse">
<button aria-expanded="false" data-target="#collapsible_obj_0" data-toggle="collapse" class="btn btn-link collapsed">click here</button>

<div style="height: 0px;" aria-expanded="false" id="collapsible_obj_0" class="collapse">
  <span>foo</span>
  <button type="button" class="button">click</button>
</div>
  </div>
<button type="button" id="button">Clone</button>

About your question you haven't shown your full script that's the reason we are unable to find the bug. LIKE we don't know objectContainer nor collapsibleObjCounter what that is ?

Don't copy the events while cloning, remove the true flag.

var header = objectContainer.clone();

My guess is Bootstrap is handling the event binding of dynamic objects also for data-toggle, it only needs to have different id's & target.

Here's a fiddle.

PS: Don't know what was this or objectContainer in OP's question, so created a closure and pasting result in new wrapper.

The problem is not caused by the event listeners in and of themselves. The issue is how Bootstrap works. For most of the Bootstrap ponents, Bootstrap creates a JavaScript object associated with the DOM element. This object is what you need to take care of when you clone Bootstrap ponents. For a collapse element, Bootstrap creates a Collapse object.

The JavaScript object is associated with the DOM element through jQuery's $.data. This is the problem. If you use jQuery's clone and request that event handlers be copied to the clone, then you also get a copy of the data set with $.data. However, when the data is a reference to a JavaScript object, then it is the reference that is copied to the clone. So the original and the clone refer to the same JavaScript object and this is where everything goes astray. This is not special to Bootstrap, by the way: any reference is subject to this problem.

What you could do is perform a $.removeData on the cloned elements that are Bootstrap ponents. This will force Bootstrap to recreate the JavaScript object. For ponents that automatically enroll in the data API, this should be all that's needed. (collapse automatically enrolls.) For ponents that do not automatically enroll (e.g. tooltips), you need to call $.[ponent] to recreate the ponent manually.

I've forked a fiddle from aManHasNoName's fiddle that illustrates this. The only modifications were:

  1. Add the parameters true, true to var header = objectContainer.clone();

  2. Modify header.find(".collapse").attr("id", collapseId) to add .removeData() at the end.

I have created a JSFiddle of your code. The most possible error seems to that you where using a this.collapsibleObjCounter++ value of 0 that might be creating an issue.

Here is a working JSFiddle. Please let me know if that is what you are trying to do. Thank you.

https://jsfiddle/2fgoywzy/1/


JS

$( document ).ready(function() {
for (i = 1; i < 5; i++) { 
    var objectContainer =  $("#main");
    var header = objectContainer.clone(true);
    var counter = i; // replace this with this.collapsibleObjCounter++
    var collapseId = "collapsible_obj_" + counter;

    header.find(".collapse").attr("id", collapseId);
    header.find("button[data-toggle='collapse']").attr("data-target", "#"+collapseId);
    $(header).appendTo('body');
}
});

HTML

<div id="main">
    <button aria-expanded="false" data-target="#collapsible_obj_0" data-toggle="collapse" class="btn btn-link collapsed">click here</button>

    <div style="height: 0px;" aria-expanded="false" id="collapsible_obj_0" class="collapse">
        <span>foo</span>
    </div>
</div>

If you are having a value of 0 in this.collapsibleObjCounter++ then i would suggest doing ++this.collapsibleObjCounter. Instead of post increment do pre increment

发布评论

评论列表(0)

  1. 暂无评论