Home > other >  Adding an extra element with modulus in a Javascript for loop
Adding an extra element with modulus in a Javascript for loop

Time:04-26

I'm new to Javascript and I'm having a problem with a closing div to a loop after every 3 divs in order to restart the column of divs. Although it clearly adds the new div , Javascript refuses to add the closing div (see code below). Thank you in advance for your help.

var cats = 8;
var cat= '';
for(var i=1; i<=cats; i  ) {
  cat   = "<div class=\"cat three_columns two_columns\">"   i   "</div>";   
  if(i%3==0) {cat  = "</div><div class=\"column\">";}
}
 var threecolumns = document.getElementsByClassName('column');
 threecolumns[0].innerHTML = cat;
<style type="text/css">
* { box-sizing: border-box;}
.columnsBox {width:90%; padding:10px; margin:0 auto; border:1px solid green; overflow:auto;}
.column {float:left; width:100%; padding-right:8px;}
.cat {font-size:16px; text-align:center; margin-bottom:10px;padding:12px; color:#5a2e0f; background-color:#d7d7d7; border:1px solid #c1bfbf; outline:none; cursor:pointer;}
</style>
<div style="width:90%; min-width:200px; margin:0 auto; padding:10px; ">
    <div >
        <div ></div>
        This is where I placed the javascript
    </div>
</div>

Desired Output:
<div >
    <div >
        <div >1</div>
        <div >2</div>
        <div >3</div>
    </div>
        
    <div >
        <div >4</div>
        <div >5</div>
        <div >6</div>
    </div>
    
    <div >
        <div >7</div>
        <div >8</div>
    </div>
</div>

CodePudding user response:

The string you pass to innerHTML must be a self-contained piece of HTML. It cannot close a tag that is not opened in it. Such closing </div> is ignored.

To overcome this, I would suggest creating the elements with createElement:

let cats = 8;
let div;
let container = document.querySelector(".columnsBox");
for (let i = 1; i <= cats; i  ) {
  if (i % 3 == 1) {
    column = document.createElement("div");
    column.className = "column";
    container.appendChild(column);
  }
  let child = document.createElement("div");
  child.className = "cat three_columns two_columns";
  child.textContent = i;
  column.appendChild(child);
}
* {
    box-sizing: border-box;
}
.columnsBox {
    width:90%;
    padding:10px;
    margin:0 auto;
    border:1px solid green;
    overflow:auto;
}
.column {
    float:left;
    width:100%;
    padding-right:8px;
}
.cat {
    font-size:16px;
    text-align:center;
    margin-bottom:10px;
    padding:12px;
    color:#5a2e0f;
    background-color:#d7d7d7;
    border:1px solid #c1bfbf;
    outline:none;
    cursor:pointer;
}
.three_columns {
    width: 30%;
    display: inline-block;
    margin: 5px;
}
<div style="width:90%; min-width:200px; margin:0 auto; padding:10px; ">
    <div >
    </div>
</div>

CodePudding user response:

Here's another way. Use 2 loops. I show the output as <pre> html so you can see the structure.

let cats = 8,
  numCols = 3,
  x = 0,
  catsPerCol = Math.max(cats / numCols),
  output = "";

for (let r = 0; r < numCols; r  ) {
  output  = `<div >\n`;
  for (let c = 0; c < catsPerCol; c  ) {
    if (x   < cats) output  = `  <div >${x}</div>\n`;
  }
  output  = `</div>\n`;
}
document.querySelector('.columnsBox').innerText = output;
<pre ></pre>

CodePudding user response:

Solution using a generator function

You can also use a generator function to create the same result. This requires some more advanced JavaScript and is not the most efficient solution (will be irrelevant for most use cases), but can be quite an elegant solution without the need to use the modulo operator.

// wait for HTML to load
window.addEventListener("DOMContentLoaded", (e) => {
  // get parent component which holds all columns
  const colBox = document.querySelector(".columnsBox");
  // number of total categories
  const cats = 8;
  // number of categories per column
  const catsPerColumn = 3;

  // create all categories
  const allDivs = [...new Array(cats)].map((_, idx) => createCategory(idx   1));

  // create a column for every catsPerColumn elements
  for (const columnChildren of chunksArray(allDivs, catsPerColumn)) {
    const newCol = createColumn(columnChildren);
    colBox.appendChild(newCol);
  }
});

/**
 * Creates a category div
 * @param {number} id ID for the column
 * @returns div within a column
 */
function createCategory(id) {
  const newCol = document.createElement("div");
  // add classes to this div
  newCol.classList.add("cat", "three_columns", "two_columns");
  newCol.textContent = id;
  return newCol;
}

/**
 * Create a column containing children
 * @param {Iterable<HTMLElement>} columnChildren 
 * @returns column with children
 */
function createColumn(columnChildren) {
  const newColumn = document.createElement("div");
  // add class to this div
  newColumn.classList.add("column");
  // all all divs that belong to that column to it
  newColumn.append(...columnChildren);
  // add column to column box
  return newColumn;
}

/**
 * Generator function which returns all elements of an array in chunks of a specified size (or smaller if there are not enough values in array)
 * @param {Array<any>} array an array
 * @param {number} chunkSize size of one chunk
 */
function* chunksArray(array, chunkSize) {
  let cur = 0;
  do {
    // create a new array only holding the values from the current value till current value   chunkSize or till the end of the array
    yield array.slice(cur, Math.min(cur   chunkSize, array.length));
    cur  = chunkSize;
  } while (cur <= array.length);
}
* {
  box-sizing: border-box;
}

.columnsBox {
  width: 90%;
  padding: 10px;
  margin: 0 auto;
  border: 1px solid green;
  overflow: auto;
}

.column {
  float: left;
  width: 100%;
  padding-right: 8px;
  border: 5px solid black;
}

.cat {
  font-size: 16px;
  text-align: center;
  margin-bottom: 10px;
  padding: 12px;
  color: #5a2e0f;
  background-color: #d7d7d7;
  border: 1px solid #c1bfbf;
  outline: none;
  cursor: pointer;
}
<div >

Please note: I've added a thick black border to show which <div>s belong to the same column.

  • Related