Mark Nunes
Mark Nunes

Reputation: 965

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>

Upvotes: 1

Views: 119

Answers (3)

Orkun T&#252;mer
Orkun T&#252;mer

Reputation: 11

if you want to use querySelectorAll, this maybe help. with this every time you check the length it recalculates and returns the value. Symbol.iterator helps you to manipulate for...of loops.

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 = {
    get length() {
        return listUl.querySelectorAll('span').length
    },
    *[Symbol.iterator]() {
        let i = 0;
        const l = listUl.querySelectorAll('span');
        while( i < l.length ) {
            yield l[i];
            i++;
        }
    }
};
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 item of listCopy) {
    if (item.parentNode.querySelector("input:checked")) {
      copyTextFromList += item.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>

Upvotes: 1

Ciprian B
Ciprian B

Reputation: 550

The problem is that const listCopy = listUl.querySelectorAll('span'); is initiated with the span array from the beginning.

In order to get updated list const listCopy = listUl.querySelectorAll('span'); will be let listCopy = listUl.querySelectorAll('span'); and in your function

//Cloning code if there are checked items
copyText.addEventListener('click', () => {
  // add the following line - in this way you will select the span from the updated list
  listCopy = listUl.querySelectorAll('span');
  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;
});

Upvotes: 1

Raphael Schweikert
Raphael Schweikert

Reputation: 18556

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.

Upvotes: 2

Related Questions