2

I'm an experienced programmer but new to JQuery. I'm trying to clone a data-role="collapsible" object and wish to do so with the event bindings that the previous object had. My code is below:

$(document).ready(function() {
    var appointment = $("#appointment").clone(true).attr("id", "appointment1");
    $("#appointment-list").append(appointment);
});

The problem is that this copies the event bindings exactly, which means that when you click on the cloned object, the original is what is expanded. How do you clone it so that the events trigger actions on the clone?

EDIT:

That's the only code I've written as I'm doing it as a test before I complete my app. The rest taken from the JQuery mobile library. The element being cloned is a collapsible content block that I'm using as a template for dynamically added blocks. A stripped down version of it is below:

<div data-role="collapsible" id="appointment">
  <h1>Click me - I'm collapsible!</h1>
  <p>I'm the expanded content.</p>
</div>

EDIT:

AlexKM, NEVER take it upon yourself to correct the spelling and grammar of a native English speaker, which you are clearly not. Where do you get off completely butchering someone else's post into barely intelligible drivel? I have reverted your changes.

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • Seems like this goes to the logic behind the click event with the "data-role="collapsible" code. Please show that. – josephnvu May 30 '14 at 09:01
  • Forget anything I said about delegated `on`. Now you have explained the problem I have added a new answer below to clone jQuery UI `collapsible` elements :) – iCollect.it Ltd May 30 '14 at 14:51

2 Answers2

1

The question has changed completely based on new information and comments, so will add an appropriate answer here instead (my other answer is now deleted).

The specific problem is that you wish to clone jQuery UI collapsible objects already on the page.

Unfortunately jQuery UI attaches to existing collapsible objects (decorated with data-role="collapsible) at document load time, in doing this it also creates a dynamic structure on each item to manage the open and closed states.

It also stores state data referencing the new pieces of the collapsible element. This means that when it is clicked it will reference the original child elements of the cloned item (not the clone).

A clone has more elements than you expect, so you cannot just reapply collapsible() to them.

  1. If you deep clone a collapsible element, it incorrectly references the original element's components and toggles that one instead of itself.
  2. If you shallow clone a collapsible element you get additional structure that needs to be undone/removed before you can apply collapsible(). otherwise you get this result:

enter image description here

So the options are:

  • Find out how to attach a new code instance without changing the structure (hard)
  • Undo the structure changes and reapply collapsible (easier)

To do the second of these, first we look at what collapsible does to elements:

Element before collapsible:

<div class="cloneme" data-role="collapsible">
      <h1>Click me - I'm collapsible!</h1>
      <p>I'm the expanded content.</p>
</div>

Element after collapsible:

<div class="cloneme ui-collapsible ui-collapsible-inset ui-corner-all ui-collapsible-themed-content ui-collapsible-collapsed" data-role="collapsible">
    <h1 class="ui-collapsible-heading ui-collapsible-heading-collapsed">
         <a href="#" class="ui-collapsible-heading-toggle ui-btn ui-icon-plus ui-btn-icon-left ui-btn-inherit">Click me - I'm collapsible!<span class="ui-collapsible-heading-status"> click to expand contents</span>
          </a>
    </h1>
    <div class="ui-collapsible-content ui-body-inherit ui-collapsible-content-collapsed" aria-hidden="true">
        <p>I'm the expanded content.</p>
    </div>
</div>

Answer "undo and reapply":

The code to do all this is:

JSFiddle: http://jsfiddle.net/TrueBlueAussie/yMcqB/2/

$(function () {
    var clone = $(".cloneme").clone();
    $('.ui-content').append(clone);
    clone.addClass('ImAClone');        // Just for testing - REMOVE THIS
    clone.find('.ui-collapsible-heading-toggle').children().unwrap();
    clone.find('.ui-collapsible-heading-status').remove();
    clone.find('.ui-collapsible-content').children().unwrap();
    clone.collapsible();
});

You can probably combine some of the operations into a single one (e.g. the unwraps), but I leave that for you to do :)

Thoughts: Ideally these plugins would allow for applying to an existing element that already has collapsible decorations.

The end result is two independent collapsible elements:

enter image description here

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
0

I'm not sure you understand how jQuery.fn.clone works, consider this:

var $d1 = $('<div/>').prop('id', 'd1').on('click', function() { 
    alert('d1 clicked? The id is: ' + this.id); 
});
var $d2 = $d1.clone(true).prop('id', 'd2').click(); // alerts d1 clicked? The is is: d2

What im trying to show it that jQuery triggers the event on the cloned object, but the event handler is shared between the two elements.

Btw as of jQuery 1.7 - you should use jQuery.fn.prop instead of jQuery.fn.attr

Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
  • 1
    I think it's not true about using the prop method to retrieve ids. Please check the doc here: http://api.jquery.com/attr/. – jme11 May 30 '14 at 09:45
  • @jme11 Not true, you can use both, but it is preferred to use `.prop`. Basically a good "rule" is: does it exists on the DOM Node i should use prop. (http://stackoverflow.com/a/11218764/887539) – Andreas Louv May 30 '14 at 21:00
  • 1
    Hmmmm, relying on a 2 year old SO answer, that seems to be contradicted by other SO answers and the official doc seems suspect to me, but I'm not inclined to do my own performance testing with 1.11 or dig under the covers to see if indeed prop() is still used internally, so I won't dispute your assertion. However, there are cases clearly outlined even in the post you referenced where prop doesn't work at all, so I think it would be helpful if you could clarify the broad statement in you answer so others aren't misinformed. – jme11 May 30 '14 at 21:20
  • 1
    @jme11 All im trying to say is that `node.id = 'abc'` is faster than `node.setAttribute('id', 'abc')`. As far as i can see jQuery doesn't bother with id if you call prop `node.id` will be used, and if you call attr `node.setAttribute('id'` will be used. – Andreas Louv May 30 '14 at 21:46
  • @jme11: One obvious case, where you should *only* use `prop` and *never* `attr` is to set the `checked` state on a checkbox. `attr` will not work at all for `checked`. – iCollect.it Ltd May 31 '14 at 08:13