Recycling DOM structure instead of rebuilding it

Viewed 842

I create divs dynamically and want to place new divs in the correct order by checking the ids of the divs.

When creating new data, I sort my array and create a new div container. When building the DOM for the first time, it works fine because I create the data first and then create the div containers for each element in my array.

When creating new containers by clicking on a button, the array will get sorted but the new div containers are just appended below the other ones.

Each div container has a unique id, so I could check the placemenet by checking their id.

var myObjs = [];

function Obj(number, name) {
    this.number = number;
    this.name = name;
}

$(document).ready(function () {
myObjs.push(new Obj(4, "div # " + 4)); // create test data
myObjs.push(new Obj(15, "div # " + 15));
myObjs.push(new Obj(9, "div # " + 9));

myObjs = sortMyList(); // SORT

$(myObjs).each(function (index, current) {
buildContainer(current); // create the divs
});

createNewContainer(12); // create more test data by button click
createNewContainer(3); // create test data
});

function buildContainer(currentObj){ // Create a DOM element here
    var container = $("<div></div>");
   container.addClass("singleContainer");
   container.attr("id", currentObj.number);
    container.html(currentObj.name);
   $("#listContainer").append(container);
}

function createNewContainer(newNumber){
 var newObj = new Obj(newNumber, "div # " + newNumber); 
  myObjs.push(newObj);
  
  myObjs = sortMyList(); // SORT
  
  buildContainer(newObj);
}

function sortMyList() {
    return myObjs.sort(function (a, b) {
        var key = "number";
        var x = a[key];
        var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}
.singleContainer{
  height: 50px;
  background: red;
  color: white;
  margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="listContainer">

</div>

So as you can see, I need to fix the buildContainer() function to achieve this. The new created container just needs to know where to put itself between the other existing containers.

The only idea I have is destroying all the containers and rebuilding them all again. But because of performance, I don't want this, right?

So I could go with this code, I would have to place it into my createNewContainer() function right after sorting the array.

  $("#listContainer").empty(); // Remove all children / div containers
  $(myObjs).each(function (index, current) {
    buildContainer(current); // build them all again
  });

Some help would be awesome!

2 Answers

Taking into account that current divs are already in order in the DOM, one option is to select all your current containers and create a loop to find the right position...

var newId = 10;
var pos = 0;

$('div.singleContainer').each(function() {
    if (newId < $(this).attr('id')) {
        pos = parseInt($(this).attr('id'));
        return false; // We have the position, we don't need to keep looping!
    }
});

if (pos > 0)
    $('div.singleContainer#'+pos).before('<div id="'+newId+'" class="singleContainer">div # '+newId+'</div>');
else
    $('div#listContainer').append('<div id="'+newId+'" class="singleContainer">div # '+newId+'</div>');

NOTE: In case the new id equals to an existing one, this will position the new div before the existing one.

So, in your case...

function buildContainer(currentObj) { // Create a DOM element here

    var pos = 0;

    $('div.singleContainer').each(function() {
        if (currentObj.number < $(this).attr('id')) {
            pos = parseInt($(this).attr('id'));
            return false; // We have the position, we don't need to keep looping!
        }
    });

    if (pos > 0)
        $('div.singleContainer#'+pos).before('<div id="'+currentObj.number+'" class="singleContainer">'+currentObj.name+'</div>');
    else
        $('div#listContainer').append('<div id="'+currentObj.number+'" class="singleContainer">'+currentObj.name+'</div>');
}

EDITED:

Having a look at Martin Chaov answer, he has a good point. And considering that you have an array of objects that mantain the same order and elements, probably a better option would be to search the position in that array, instead of in the DOM elements. If you have many divs, this option could be faster...

function buildContainer(currentObj) { // Create a DOM element here

    var pos = 0;

    for (var i=0, l=myObjs.length; i<l; i++) {
        if (currentObj.number < myObjs[i].number) {
            pos = myObjs[i].number;
            break;
        }
    }

    if (pos > 0)
        $('div.singleContainer#'+pos).before('<div id="'+currentObj.number+'" class="singleContainer">'+currentObj.name+'</div>');
    else
        $('div#listContainer').append('<div id="'+currentObj.number+'" class="singleContainer">'+currentObj.name+'</div>');
}

I hope it helps

You should have a in memory structure that is representation of the DOM you are using and interact only with it. For example:

var struct = {};
struct["id" + number] = domRef

Then when you call the build function you will pass the reference to the object you want to place it in like that:

buildContainer(newObj, struct["id" + number]);

You won't need to cycle through all to find what you need because you are passing it at the time of the function call.

Related