Search code examples
javascriptstring-lengthnodelist

Why isn't the string.length updating?


This is my first piece of code js done on my own. I am trying to have an input field to add items to the list and then if you press the button to generate code it will collect all the checked items and copy the text into another div.

Basically, my question is around two variables:

const list = listUl.children;
const listCopy = listUl.querySelectorAll('span');

Let say I have 4 items in the list. If I add a new item to the list I can see the list.length add this new item, it changes from 4 to 5. But it doesn't happen with listCopy.length the value keeps being 4.

Why is it happening if lstCopy is inside of list? How can I have listCopy updated too?

Here is my code:

const addItemInput = document.querySelector('.addItemInput');
const addItemButton = document.querySelector('.addItemButton');
const copyText = document.querySelector('.generateCode');
const listUl = document.querySelector('.list');
const list = listUl.children;
const listCopy = listUl.querySelectorAll('span');
const clonedCode = document.querySelector('.code p');

//FUNCTION: Generate value/items = Draggable, Checkbox, Remove button
const attachItemListButton = (item) => {

    //Draggable
    item.draggable = "true";
    item.classList.add("list--item");

    //Checkbox
    let checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.className = 'checkbox';
    checkbox.name = "chkboxName1";
    checkbox.value = "value";
    checkbox.id = "id";
    item.insertBefore(checkbox, item.childNodes[0] || null);

    //Remove button
    let remove = document.createElement('button');
    remove.className = 'remove';
    remove.textContent = 'x';
    item.appendChild(remove);
};
for (let i = 0; i < list.length; i += 1) {
    attachItemListButton(list[i]);
}


//Cloning code if there are checked items
copyText.addEventListener('click', () => {
  let copyTextFromList = "";
  for (let i = 0; i < listCopy.length; i += 1) {
    if (listCopy[i].parentNode.querySelector("input:checked")) {
      copyTextFromList += listCopy[i].textContent + ',';
    }
  }
  clonedCode.innerHTML = copyTextFromList;
});


//Add item from the input field to the list
addItemButton.addEventListener('click', () => {
  let li = document.createElement('li');
  let span = document.createElement('span');
  span.textContent = addItemInput.value;
  listUl.appendChild(li);
  li.appendChild(span);
  attachItemListButton(li);
  addItemInput.value = '';
});


//FUNCTION: Remove button
listUl.addEventListener('click', (event) => {
    if (event.target.tagName == 'BUTTON') {
        if (event.target.className == 'remove') {
            let li = event.target.parentNode;
            let ul = li.parentNode;
            ul.removeChild(li);
        }
    }
});
/* Google fonts */
@import url('https://fonts.googleapis.com/css?family=Heebo:300,400,700');

/* Root */
:root {
    --color-white:           #fff;
    --color-black:           #2D3142;
    --color-black-2:         #0E1116;
    --color-gray:            #CEE5F2;
    --color-gray-2:          #ACCBE1;
    --color-gray-3:          #CEE5F2;
    --color-green:           #439775;
    --color-blue:           #4686CC;
}

body {
    font-family: 'Heebo', sans-serif;
    font-weight: 400;
    font-size: 16px;
    color: black;

}
h2 {
    font-weight: 700;
    font-size: 1.5rem;
}
h3 {
    font-weight: 700;
    font-size: 1.25rem;
}

button {
    background: var(--color-blue);
    padding: 5px 10px;
    border-radius: 5px;
    color: var(--color-white);
}

[draggable] {
    -moz-user-select: none;
    -khtml-user-select: none;
    -webkit-user-select: none;
    user-select: none;
    -khtml-user-drag: element;
    -webkit-user-drag: element;
}

ul.list {
    list-style-type: none;
    padding: 0;
    max-width: 300px;
}

.list  button {
    background: var(--color-black);
}

.list--item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: auto;
    margin: 5px auto;
    padding: 5px;
    cursor: move;
    background: var(--color-gray);
    border-radius: 5px;
}

.list--item.draggingElement {
    opacity: 0.4;
}

.list--item.over {
    border-top: 3px solid var(--color-green);
}

button.remove {
    margin: auto 0 auto auto;
}

input#id {
    margin: auto 5px auto 0;
}

.button-wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    max-width: 300px;
}

.button-wrapper .addItemInput { 
    width: 63%;
    border-radius: 5px 0 0 5px;
 }
.button-wrapper .addItemButton { 
    width: 35%;
    border-radius: 0 5px 5px 0;
 }
.button-wrapper .generateCode { 
    width: 100%;
    background: var(--color-green);
    margin-top: 5px;
 }


.code p {
    background: var(--color-gray); padding: 5px;
    border: 1px solid var(--color-gray-2);
    min-height: 20px;
    font-weight: 300;
}
  <ul class="list">
    <li><span>Header</span></li>
    <li><span>Hero</span></li>
    <li><span>Intro</span></li>
    <li><span>Footer</span></li>
  </ul>

  <div class="button-wrapper">
    <input type="text" class="addItemInput" placeholder="Item description">
    <button class="addItemButton">Add item</button>
    <button class="generateCode">Generate code</button>
  </div>

  <div class="code">
    <h2>Code</h2>
    <p></p>
  </div>


Solution

  • There are two variants of NodeList, live and non-live ones. querySelectorAll returns a static NodeList that is not live. .children returns a live one (technically it returns an HTMLCollection but you can ignore this distinction for now).

    To make listCopy be live as well, you could use listUl.getElementsByTagName('span')

    To select elements by their classes, use getElementsByClassName. There is no way (that I know of) to get a live collection with CSS or XPath (i.e. more complex) queries, though.