53

I have an HTML element with a large collection of unordered lists contained within it. I need to clone this element to place elsewhere on the page with different styles added (this is simple enough using jQuery).

$("#MainConfig").clone(false).appendTo($("#smallConfig"));

The problem, however, is that all the lists and their associated list items have IDs and clone duplicates them. Is there an easy way to replace all these duplicate IDs using jQuery before appending?

fragilewindows
  • 1,394
  • 1
  • 15
  • 26
Sheff
  • 3,474
  • 3
  • 33
  • 35

9 Answers9

44

If you need a way to reference the list items after you've cloned them, you must use classes, not IDs. Change all id="..." to class="..."

If you are dealing with legacy code or something and can't change the IDs to classes, you must remove the id attributes before appending.

$("#MainConfig").clone(false).find("*").removeAttr("id").appendTo($("#smallConfig"));

Just be aware that you don't have a way to reference individual items anymore.

Crescent Fresh
  • 115,249
  • 25
  • 154
  • 140
  • 1
    Can something similar not be achieved but replace the ids? – Sheff Jan 08 '09 at 15:09
  • 1
    Then you must track how the ids were changed (ie, prefix, postfix) and use string concatenation to reconstruct the correct ids when referencing the cloned elements. Annoying. Just use classes. – Crescent Fresh Jan 08 '09 at 16:55
  • Clone and change id: "http://stackoverflow.com/questions/10126395/how-to-jquery-clone-and-change-id" – Darius Miliauskas Jun 17 '15 at 20:55
19

Since the OP asked for a way to replace all the duplicate id's before appending them, maybe something like this would work. Assuming you wanted to clone MainConfig_1 in an HTML block such as this:

<div id="smallConfig">
    <div id="MainConfig_1">
        <ul>
            <li id="red_1">red</li>
            <li id="blue_1">blue</li>
        </ul>
    </div>
</div>

The code could be something like the following, to find all child elements (and descendants) of the cloned block, and modify their id's using a counter:

var cur_num = 1;    // Counter used previously.
//...
var cloned = $("#MainConfig_" + cur_num).clone(true, true).get(0);
++cur_num;
cloned.id = "MainConfig_" + cur_num;                  // Change the div itself.
$(cloned).find("*").each(function(index, element) {   // And all inner elements.
    if(element.id)
    {
        var matches = element.id.match(/(.+)_\d+/);
        if(matches && matches.length >= 2)            // Captures start at [1].
            element.id = matches[1] + "_" + cur_num;
    }
});
$(cloned).appendTo($("#smallConfig"));

To create new HTML like this:

<div id="smallConfig">
    <div id="MainConfig_1">
        <ul>
            <li id="red_1">red</li>
            <li id="blue_1">blue</li>
        </ul>
    </div>
    <div id="MainConfig_2">
        <ul>
            <li id="red_2">red</li>
            <li id="blue_2">blue</li>
        </ul>
    </div>
</div>
Russell G
  • 470
  • 6
  • 16
16
$("#MainConfig")
    .clone(false)
    .find("ul,li")
    .removeAttr("id")
    .appendTo($("#smallConfig"));

Try that on for size. :)

[Edit] Fixed for redsquare's comment.

Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
Salty
  • 6,688
  • 3
  • 33
  • 31
  • I would remove the id's before running the append otherwise your duplicating the id's in the dom still which could have nasty effects – redsquare Jan 06 '09 at 11:30
  • That' essentially what Salty is doing in his example I think. I will give it a go but I would rather use a global find for all children in case the markup changes and other elements within the parent get duped – Sheff Jan 06 '09 at 11:50
  • 2
    Thanks this was very useful in that I could take `$('#itemBase').clone(true).removeAttr('id').attr('id','item'+nextItemId++).removeClass('hidden').addClass('item').insertAfter($('#itemBase'));` – dlamblin Oct 28 '09 at 18:50
6

I use something like this: $("#details").clone().attr('id','details_clone').after("h1").show();

  • 3
    Oh that's even shorter: `var i=$('#itemBase'); i.clone(true).attr('id','item'+nextItemId++) .removeClass('hidden').addClass('item').insertAfter(i);` – dlamblin Oct 28 '09 at 18:52
4

This is based on Russell's answer but a bit more aesthetic and functional for forms. jQuery:

$(document).ready(function(){
   var cur_num = 1;    // Counter

    $('#btnClone').click(function(){

          var whatToClone = $("#MainConfig"); 
          var whereToPutIt = $("#smallConfig");

          var cloned = whatToClone.clone(true, true).get(0);
          ++cur_num;
          cloned.id = whatToClone.attr('id') + "_" + cur_num;                  // Change the div itself.

        $(cloned).find("*").each(function(index, element) {   // And all inner elements.
          if(element.id)
          {
              var matches = element.id.match(/(.+)_\d+/);
              if(matches && matches.length >= 2)            // Captures start at [1].
                  element.id = matches[1] + "_" + cur_num;
          }
          if(element.name)
          {
              var matches = element.name.match(/(.+)_\d+/);
              if(matches && matches.length >= 2)            // Captures start at [1].
                  element.name = matches[1] + "_" + cur_num;
          }

         });

       $(cloned).appendTo( whereToPutIt );

    });
});

The Markup:

<div id="smallConfig">
    <div id="MainConfig">
        <ul>
            <li id="red_1">red</li>
            <li id="blue_1">blue</li>
        </ul>
      <input id="purple" type="text" value="I'm a text box" name="textboxIsaid_1" />
    </div>
</div>
Dario Novoa
  • 115
  • 1
  • 7
3

FWIW, I used Dario's function, but needed to catch form labels as well.

Add another if statement like this to do so:

if(element.htmlFor){
var matches = element.htmlFor.match(/(.+)_\d+/);
if(matches && matches.length >= 2)            // Captures start at [1].
  element.htmlFor = matches[1] + "_" + cur_num;
}
Peter O.
  • 32,158
  • 14
  • 82
  • 96
Camwyn
  • 426
  • 3
  • 7
0

I believe this is the best way

var $clone = $("#MainConfig").clone(false);
$clone.removeAttr('id'); // remove id="MainConfig"
$clone.find('[id]').removeAttr('id'); // remove all other id attributes
$clone.appendTo($("#smallConfig")); // add to DOM.
TarranJones
  • 4,084
  • 2
  • 38
  • 55
0

If you will have several similar items on a page, it is best to use classes, not ids. That way you can apply styles to uls inside different container ids.

Danita
  • 2,464
  • 4
  • 23
  • 29
0

Here is a solution with id.

var clone = $("#MainConfig").clone();
clone.find('[id]').each(function () { this.id = 'new_'+this.id });
$('#smallConfig').append(clone);

if you want dynamically set id, you can use counter instead of 'new_'.

RAGINROSE
  • 694
  • 8
  • 13