javascript – Place items with the same class in ordered columns

Question:

I am trying to make a masonry layout and the logic is as follows: I have an array of elements [elem1, elem2, elem3, elem4] and I want to distribute them in 3 columns, but they must appear in this order:

columna1 columna2 columna3
elem1    elem2     elem3

For some reason the first element is not added, I don't understand why

this is my javascript code:

I don't understand why not all elements are added, I even tried with an array of numbers to see the logic and the numbers were added correctly

let articles = document.getElementsByClassName("new-article")

let columns = [document.createElement("div"),
               document.createElement("div"),
               document.createElement("div")]

let longElements = articles.length-1
let elementsCounter = 0

for(let i=0; i<columns.length; i++){
    document.getElementById("container-articles").appendChild(columns[i])
}


for(let i=0; i<columns.length; i++){
    columns[i].classList.add("col-xl-4")
}

for(let i=0; i<=columns.length; i++){
  if(i===columns.length){
      i=0;
  }
  if(longElements<0){
      break
  }
  columns[i].appendChild(articles[elementsCounter])
  longElements--
  elementsCounter++
}

Here I test the algorithm with numbers before using the DOM

let elementos = [12,12,3,41,2]
let columnas = [[],[],[]]
let longElementos = elementos.length-1 //4

    for(let i=0; i<=columnas.length; i++){ //i=0,1,2,3
       if(i===columnas.length){  // i=0,1,2,3
         i=0
       }
       if(longElementos<0){  // longElementos  2,41,3,12,12
         break
       }
       columnas[i].push(elementos[longElementos]) //3,2,1,0
       longElementos--
    }

The output is this, which is exactly what I expect:

 arr1--->   0: [2, 12]
 arr2--->   1: [41, 12]
 arr3--->   2: [3]

Upgrade:

Here is the HTML I am using

<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
 <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
        type="text/css">
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
        integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>



<article class="new-article">
 <img src="http:lorempixel.com/500/500">
 <h1>Noticia 1</h1>
</article>

<article class="new-article">
 <img src="http:lorempixel.com/500/500">
 <h1>Noticia 2</h1>
</article>


<div class="container-fluid">
   <div class="row" id="container-articles">
   </div>
</div>
<script src="masonry.js"></script>
</body>
</html>

Answer:

Your problem is that you are iterating through the articles array while moving the elements that are within it within the HTML.

By definition, the getElementsByClassName function returns an HTMLCollection which is an object that represents a list of nodes. This list is ordered (unless otherwise indicated) respecting the order of the HTML tree. In addition, this list is alive , which means that any change that occurs in it will alter the list .

In your case, let's put ourselves in the last for (in which you assign the articles to the different columns) what is happening is the following:

  • In the first iteration, the articles array is ordered [News 1, News 2] and the variable is i=0 . You access columna[0] and articles[0] and News 1 becomes inside the container-articles . Everything is perfect.

  • Let's see what happens in the next iteration of the array. The array articles -remember that it is a live list and that it respects the order of the HTML- this time it will contain the values ​​like [News 2, News 1] since now News 1 is lower than News 2 in the HTML tree. However, i=1 so it will position News 1 again inside container-articles leaving News 2 out of it.

To solve it, I have created a variable j=0 that I reset in each iteration of the loop and that I will use to get the articles. I always get the article that is in position 0 since the articles array is ordered according to the order of the HTML and since each time we position an element we set it lower than the articles within the order of the HTML, as it moves the different articles the next element that I have to move will always be the first one in the array.

Also, I do a check to see if there are more columns than articles, which in that case I control the error that it would give when trying to put a child that does not exist in a column.

Note: I have removed your longElements and elementsCounter variables since I have not seen them necessary.

Your corrected code:

let articles = document.getElementsByClassName("new-article")

let columns = [document.createElement("div"),
               document.createElement("div"),
               document.createElement("div")]

for(let i=0; i<columns.length; i++){
    document.getElementById("container-articles").appendChild(columns[i])
}

for(let i=0; i<columns.length; i++){
    columns[i].classList.add("col-xl-4")
}

let columnas = document.getElementsByClassName("col-xl-4")
for(let i=0; i<columns.length; i++){
  j=0;
  if(articles[i] != undefined){
     columnas[i].appendChild(articles[j])
  }  
}
<article class="new-article">
 <img src="http:lorempixel.com/500/500">
 <h1>Noticia 1</h1>
</article>

<article class="new-article">
 <img src="http:lorempixel.com/500/500">
 <h1>Noticia 2</h1>
</article>

<div class="container-fluid">
   <div class="row" id="container-articles">
   </div>
</div>
Scroll to Top