Pure JS, groups of 3 wrap in rows and inser into DOM

Viewed 102

I am trying to take an array and wrap 3 items at a time in a row and append it to the DOM/container. I've found a lot of examples using jquery however I need vanilla js solution. If there is a better/simpler way I am all ears but I am having a rough go with this one. The closest I have managed to get is the last image in each group of three to display

example of what I am trying to do:

arr =  [1,2,3,4,5,6,7,8,9]

output

<div class="track">
  <div class="track__block">
    <figure>1<figure>
    <figure>2<figure>
    <figure>3<figure>
  </div>
 </div>
<div class="track">
  <div class="track__block">
    <figure>4<figure>
    <figure>5<figure>
    <figure>6<figure>
  </div>
</div>
<div class="track">
  <div class="track__block">
    <figure>7<figure>
    <figure>8<figure>
    <figure>9<figure>
  </div>
</div>

My Attempt:

// Create a function to group items by 3

function chunkArray (arr, size) {
  const results = []
  while (arr.length) {
    results.push(arr.splice(0, size))
  }

  return results
}

const groupedTeam = chunkArray(arr, 3)
// returns [{1,2,3}, {4,5,6}, {7,8,9}]


// Create the rows and append to the DOM
for (let i = 0; i < groupedTeam.length; i++) {
  const row = document.createElement('div')
  row.classList.add('track')

  const block = document.createElement('div')
  block.classList.add('track__block')

  container.appendChild(row)
  row.appendChild(block)
}

// This creates the correct number of rows on the DOM
<div class="track">
  <div class="track__block"></div>
 </div>
<div class="track">
  <div class="track__block"></div>
</div>
<div class="track">
  <div class="track__block"></div>
</div>

// Wrap the array items in a figure and place into the rows by threes

// This is the part I can't get right

const blocks = [...document.querySelectorAll('track__blocks')]


for (let i = 0; i < blocks.length; i++) {
  for (let x = 0; x < groupedTeam[i].length; x++) {
    let memberContainer =
    `
    <figure class="track track__block grid grid--team__track__block">
      <img class="image image--lazy image--w320 track__block grid--team__track__block--image" src=
"imgs/image-${groupedTeam[i][x]}" />
    </figure>
    `
    blocks[i].innerHTML = memberContainer
  }
}
2 Answers

You are selecting the block elements from the DOM, but they don't aren't appended to the DOM yet, so that will always come up empty.

const blocks = [...document.querySelectorAll('track__blocks')]

It might be easier to add your figure and img elements in the first loop when creating the rows and blocks. Add a second loop, like you do in the end of your code, and add the figures and images to each block when you create them.

for (let team of groupedTeams) {
  const row = document.createElement('div')
  row.classList.add('track')

  const block = document.createElement('div')
  block.classList.add('track__block')

  for (let item of team) {
    const figure = document.createElement('figure')
    figure.className = 'track track__block grid grid--team__track__block'

    const image = new Image()
    image.className = 'image image--lazy image--w320 track__block grid--team__track__block--image'
    image.src = item

    figure.append(image)
    block.append(figure)
  }

  container.appendChild(row)
  row.appendChild(block)
}

Your code is almost there, here are your issues:

  1. const blocks = [...document.querySelectorAll('track__blocks')]
    

    This is trying to get the element track__blocks, not the class. To target elements with this class, you can use .track_block (note you had a typo with the 's' as well)

  2. blocks[i].innerHTML = memberContainer 
    

    This will overwrite the HTML content of the block with memberContainer. An easy fix to this is to append to this using +=. This will cause the DOM to update multiple times though as it's in your for loop, which isn't very efficient. Instead, you can consider accumulating a string of the HTML content, and then update the DOM once you've made the string.

See example below:

function chunkArray(arr, size) {
  const results = []
  while (arr.length) {
    results.push(arr.splice(0, size))
  }
  return results
}

const container = document.querySelector("#container");
const arr =  [1,2,3,4,5,6,7,8,9];
const groupedTeam = chunkArray(arr, 3)
// returns [{1,2,3}, {4,5,6}, {7,8,9}]


// Create the rows and append to the DOM
for (let i = 0; i < groupedTeam.length; i++) {
  const row = document.createElement('div')
  row.classList.add('track')

  const block = document.createElement('div')
  block.classList.add('track__block')

  container.appendChild(row)
  row.appendChild(block)
}



// Wrap the array items in a figure and place into the rows by threes

// This is the part I can't get right

const blocks = [...document.querySelectorAll('.track__block')]

for (let i = 0; i < blocks.length; i++) {
  let figures = "";
  for (let x = 0; x < groupedTeam[i].length; x++) {
    let memberContainer =
      `
    <figure class="track track__block grid grid--team__track__block">
      <img class="image image--lazy image--w320 track__block grid--team__track__block--image" src=
"imgs/image-${groupedTeam[i][x]}" alt="${groupedTeam[i][x]}"/>
    </figure>
    `
     figures += memberContainer
  }
  blocks[i].innerHTML = figures;
}
<div id="container"></div>

Alternatively, I would approach this using template literals to make my life easier. Personally, I find this simpler as you can build the HTML string in one go, and then add it to the DOM:

function chunkArray(arr, size) {
  const results = [];
  while (arr.length) {
    results.push(arr.splice(0, size))
  }

  return results
}

const container = document.querySelector("#container");
const arr =  [1,2,3,4,5,6,7,8,9];
const groupedTeam = chunkArray(arr, 3);

const html = `
  <div class="track">
    ${groupedTeam.map(team => 
      `<div class="track__block">
          ${team.map(num => `
            <figure class="track track__block grid grid--team__track__block">
              <img class="image image--lazy image--w320 track__block grid--team__track__block--image" src=
"imgs/image-${num}" alt="Image for ${num}"/>
            </figure>
          `).join('')}
       </div>`
    ).join('')}
  </div>
`;

container.innerHTML = html;
<div id="container"></div>

Related