简体   繁体   中英

jquery: Paste a set of elements over another set of elements / merging elements

I have 2 sets of elements:

<div class='container container1'>
    <div class='colors'>
        <div class='blue'></div>
        <div class='red'></div>
    </div>
    <div class='drinks'>
        <div class='soda'>coke</div>
        <div class='juice'></div>
    </div>
</div>    

<div class='container container2'>
    <div class='cars'>
        <div class='sedans'></div>
        <div class='vans'></div>
    </div>
    <div class='drinks'>
        <div class='soda'>mountain dew</div>
        <div class='coffee'></div>
    </div>
</div>        

I want to paste container1 over container2 such that any replacements are over written and any uniques to each container are put left alone and put together.

The result should be:

<div class='container container-result'>
    <div class='colors'>
        <div class='blue'></div>
        <div class='red'></div>
    </div>
    <div class='cars'>
        <div class='sedans'></div>
        <div class='vans'></div>
    </div>
    <div class='drinks'>
        <div class='soda'>coke</div>
        <div class='juice'></div>
        <div class='coffee'></div>
    </div>
</div>        

The elements can have any arbitrary hierarchy / depth. What's the best way to do this?

Thanks in advance.

Since your question is tagged jQuery here's a slightly shorter answer using that library:

function copy(from, to) {
    from.children().each(function() {
      var match = to.children("." + this.className.split(' ').join('.'));
        if(match.length) {
            if(match.children().length == 0) {
                match.replaceWith(this);
            } else {
                copy($(this), match);
            }
        } else {
            to.append(this);
        }
    }).end().remove();
    from.remove();
}

Then you'd just call it like this:

copy($(".container1"), $(".container2"));

You can give it a try here , the result is:

<div class="container container2">
  <div class="cars">
    <div class="sedans"></div>
    <div class="vans"></div>
  </div>
  <div class="drinks">
    <div class="soda">coke</div>
    <div class="coffee"></div>
    <div class="juice"></div></div>
  <div class="colors">
    <div class="blue"></div>
    <div class="red"></div>
  </div>
</div>

Note that the class name is still container2 if you want to replace that just add this to switch the class after the copy() call:

$(".container2").toggleClass("container2 container-result");

The match is based on all classes the element contains, so if an element has class="car blue" and there's a corresponding class="blue car" it'll choose that one to overwrite.

This isn't the most efficient route since you're firing up the selector engine on the children each iteration, but unless you're doing lots of elements, it should be pretty quick.

With regard to unique merging I can't help you there, but if your app by any chance happens to be in PHP then you can use php's array_merge function to merge them before outputting the HTML.

ReplaceWith is a nice jquery function to replace aka "paste" over, it may will help you with half of your solution.

This appears to do what you wanted:

  <div class='container container1'>
      <div class='colors'>
          <div class='blue'></div>
          <div class='red'></div>
      </div>
      <div class='drinks'>
          <div class='soda'>coke</div>
          <div class='juice'></div>
      </div>
  </div>    

  <div class='container container2'>
      <div class='cars'>
          <div class='sedans'></div>
          <div class='vans'></div>
      </div>
      <div class='drinks'>
          <div class='soda'>mountain dew</div>
          <div class='coffee'></div>
      </div>
  </div>        

  <div class='container container-result'>
  </div>

  <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js" type="text/javascript"></script>
  <script type="text/javascript">

    function getContainerArray(containers, level) {
      level = level || 0;
      var result = [];
      for (var i=0; i<containers.length; ++i) {
        var el = containers.eq(i);
        var obj = { "class": el.attr("class") };
        if (level == 0) {
          obj.items = getContainerArray(el.children("div"), 1);
        } else {
          obj.text = el.text();
        }
        result.push(obj);
      }
      return result;
    }

    function mergeContainers(containerArray) {
      var result = [];

      function indexOfClass(name) {
        for (var i = 0; i < result.length; ++i) {
          if (result[i]["class"] == name) {
            return i;
          }
        }
        return -1;
      }

      for (var i = 0; i < containerArray.length; ++i) {
        var obj = containerArray[i];
        var name = obj["class"];
        var index = indexOfClass(name);
        if (index < 0) {
          result.push(obj);
        } else if (obj.items != null) {
          result[index].items = mergeContainers(new Array().concat(result[index].items, obj.items));
        }
      }

      return result;
    }

    function getHtml(objArray) {
      var result = [];
      for (var i = 0; i < objArray.length; ++i) {
        var obj = objArray[i];
        result.push("<div class=\"", obj["class"], "\">");
        if (obj.text != null && obj.text != "") {
          result.push(obj.text);
        }
        if (obj.items != null) {
          result.push(getHtml(obj.items));
        }
        result.push("</div>");
      }
      return result.join("");
    }

    var html = getHtml(mergeContainers(getContainerArray($("div.container1>div,div.container2>div"))));
    $("div.container-result").append(html);
  </script>

This answer:

  1. Does exactly what you asked for.
  2. Handles repeated mergings, if div class container-result already exists.
  3. Merges any number of container divs.
  4. Uses jQuery and is more efficient than some other solutions.

See it in action at jsfiddle.net.

/*--- Get all "container" divs but exclude any "container-result" divs.
*/
var zContainers = $("div.container").not ("div.container-result");
if (zContainers  &&  zContainers.length)
{
    //--- Get or create the results div.
    var zResultDiv  = $("div.container-result");
    if (!zResultDiv  ||  !zResultDiv.length)
    {
        zResultDiv  = zContainers.parent ().append ("<div class='container container-result'></div>");
        zResultDiv  = $("div.container-result");
    }

    //--- Move the container's contents to the master container, preserving order.
    zContainers.each (function () {$(this).children ().appendTo (zResultDiv);} )

    //--- Kill the old container(s).
    zContainers.remove ();

    RecursivelyMergeDivsByClass (zResultDiv);
}

function RecursivelyMergeDivsByClass (jNode)
{
    /*--- Get a list of the direct-child div's class names.
        Sort and winny out a list of duplicates.
    */
    var zDirectChildDivs    = jNode.find ("> div");
    var aClassList          = zDirectChildDivs.map (function () {return this.className;} ).get ();
    aClassList.sort ().unshift (0);

    for (var J = aClassList.length-1;  J > 0;  J--)
        if (aClassList[J] != aClassList[J-1])   aClassList.splice (J, 1); // Delete items without duplicates.

    aClassList.splice (0, 1);

    /*--- For any duplicates, copy the contents into the first instance, preserving order.
        For exact duplicate nodes, the first (oldest) version is kept and the remaining are discarded.
    */
    for (var J = aClassList.length-1;  J >= 0;  J--)
    {
        var zDupClasses     = zDirectChildDivs.filter ("." + aClassList[J]);

        var zFirstDiv       = zDupClasses.first ();
        zDupClasses         = zDupClasses.not (zFirstDiv);

        zDupClasses.each (function () {$(this).children ().appendTo (zFirstDiv);} )
        zDupClasses.remove ();

        RecursivelyMergeDivsByClass (zFirstDiv)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM