JavaScript JavaScript and the DOM Traversing the DOM Getting the First and Last Child

hc11
hc11
3,573 Points

[Not a question] Here is my challenge solution for Getting the First and Last Child

  • Here is live site of my working code.

  • Here is my code

const toggleList = document.getElementById('toggleList');
const listDiv = document.querySelector('.list');
const descriptionInput = document.querySelector('input.description');
const descriptionP = document.querySelector('p.description');
const descriptionButton = document.querySelector('button.description');
const listUl = listDiv.querySelector('ul');
const addItemInput = document.querySelector('input.addItemInput');
const addItemButton = document.querySelector('button.addItemButton');
const lis = listUl.children;
const firstListItem = listUl.firstElementChild;
const lastListItem = listUl.lastElementChild;

firstListItem.style.backgroundColor = 'lightskyblue';
lastListItem.style.backgroundColor = 'lightsteelblue';

function attachListItemButtons(li) {
  let listDiv = document.querySelector('.list');
  let listUl = listDiv.querySelector('ul');
  let firstListItem = listUl.firstElementChild;
  let lastListItem = listUl.lastElementChild;
  // only add `up` when li is NOT firstListItem
  if (li !== firstListItem) {
    let up = document.createElement('button');
    up.className = 'up';
    up.textContent = 'Up';
    li.appendChild(up);
  }

  // only add `down` when li is NOT firstListItem
  if (li !== lastListItem) {
    let down = document.createElement('button');
    down.className = 'down';
    down.textContent = 'Down';
    li.appendChild(down);  
  }

  let remove = document.createElement('button');
  remove.className = 'remove';
  remove.textContent = 'Remove';
  li.appendChild(remove);
}

// buttonType is className .down .up .remove
function removeButtonFromListItem(li, buttonType) {
  // collection of up/down/remove buttons
  const liChildren = li.children;

  for (let i = 0; i < liChildren.length; i++) {
    // only when className 'up' == 'up'
    if (liChildren[i].className === buttonType) {
      // remove the up button
      li.removeChild(liChildren[i]);
    }
  }
}

function addButtonToListItem(li, buttonType) {
  let button = document.createElement('button');
  // collection of up/down/remove buttons
  const liChildren = li.children;

  button.className = buttonType;
  if (buttonType === 'up') {
    button.textContent = 'Up';
  } else {
    button.textContent = 'Down';
  }

  for (let i = 0; i < liChildren.length; i++) {
    // user clicked on up
    if (buttonType === 'up' && liChildren[i].className === 'down') {
      li.insertBefore(button, liChildren[i]);
    } else if (buttonType === 'down' && liChildren[i].className === 'remove') {
      li.insertBefore(button, liChildren[i]);
    }
  }
}

for (let i = 0; i < lis.length; i += 1) {
  attachListItemButtons(lis[i]);
}

listUl.addEventListener('click', (event) => {
  if (event.target.tagName == 'BUTTON') {
    if (event.target.className == 'remove') {
      let li = event.target.parentNode;
      let prevLi = li.previousElementSibling;
      let nextLi = li.nextElementSibling;
      let ul = li.parentNode;

      // first li remove button was hit
      if (prevLi === null) {
        removeButtonFromListItem(nextLi, 'up');
      }

      // if el to remove is first el
      if (li === ul.lastElementChild) {
        removeButtonFromListItem(prevLi, 'down');
      }

      ul.removeChild(li);
    }
    if (event.target.className == 'up') {
      let li = event.target.parentNode;
      let prevLi = li.previousElementSibling;
      let ul = li.parentNode;
      if (prevLi) {
        ul.insertBefore(li, prevLi);
      }
      if (li === listUl.firstElementChild) {
        removeButtonFromListItem(li, 'up');
        // prevLi is no longer firstElement
        //  so add up button
        addButtonToListItem(prevLi, 'up');
      // now li is up prevLi is last Element
      } else if (prevLi === listUl.lastElementChild) {
        removeButtonFromListItem(prevLi, 'down');
        addButtonToListItem(li, 'down');
      }
    }  
    if (event.target.className == 'down') {
      let li = event.target.parentNode;
      let nextLi = li.nextElementSibling;
      let ul = li.parentNode;
      // only move down if extLi exists
      if (nextLi) {
        // currently selected li if reference point 
        //  bring nextLi before selected li
        ul.insertBefore(nextLi, li);
      }
      // now li becomes last li
      if (li === listUl.lastElementChild) {
        // remove down button
        removeButtonFromListItem(li, 'down');
        addButtonToListItem(nextLi, 'down');
      }
      // now nextLi is first li, 
      // when press 'down' -> 'up' button gone
      if (nextLi === listUl.firstElementChild) {
        removeButtonFromListItem (nextLi, 'up');
        addButtonToListItem(li, 'up');
      }
    } 
  }
});

toggleList.addEventListener('click', () => {
  if (listDiv.style.display == 'none') {
    toggleList.textContent = 'Hide list';
    listDiv.style.display = 'block';
  } else {
    toggleList.textContent = 'Show list';                        
    listDiv.style.display = 'none';
  }                         
});

descriptionButton.addEventListener('click', () => {
  descriptionP.innerHTML = descriptionInput.value + ':';
  descriptionInput.value = '';
});

addItemButton.addEventListener('click', () => {
  // ;
  let ul = document.getElementsByTagName('ul')[0];
  let li = document.createElement('li');
  li.textContent = addItemInput.value;
  ul.appendChild(li);
  attachListItemButtons(li);
  addItemInput.value = '';
  // grab set of div, ul with new li
  let prevLi = li.previousElementSibling;
  addButtonToListItem(prevLi, 'down');
});

Thank you for checking out my code!

hc11
hc11
3,573 Points

Steven Parker Could you check out my code when you are free please?

6 Answers

Steven Parker
Steven Parker
203,243 Points

Looks pretty good :+1:, but I think the backgrounds are supposed to change as the items are moved, so the first and last items are always the ones colored.

Also, while not mentioned in the instructions, it might look better if the down button color did not change when the up button is removed.

hc11
hc11
3,573 Points

Thanks for the feedback. I will implement those two points. I didn't realize that button color for up/down are changing! Good points. I will tag you once I improve my code.

hc11
hc11
3,573 Points

Steven Parker I updated the code with above two fixes (1. backgroundColor should stay, 2. button color should not change). While working on those, I found several bugs and fixed them all!

const toggleList = document.getElementById('toggleList');
const listDiv = document.querySelector('.list');
const descriptionInput = document.querySelector('input.description');
const descriptionP = document.querySelector('p.description');
const descriptionButton = document.querySelector('button.description');
const listUl = listDiv.querySelector('ul');
const addItemInput = document.querySelector('input.addItemInput');
const addItemButton = document.querySelector('button.addItemButton');
const lis = listUl.children;
const firstListItem = listUl.firstElementChild;
const lastListItem = listUl.lastElementChild;

function attachListItemButtons(li) {

  if (li === firstListItem) {
    firstListItem.style.backgroundColor = 'lightskyblue';
  }

  if (li === lastListItem) {
    lastListItem.style.backgroundColor = 'lightsteelblue';
  }
  // only add `up` when li is NOT firstListItem
  if (li !== firstListItem) {
    let up = document.createElement('button');
    up.className = 'up';
    up.textContent = 'Up';
    // attaching color assignment for button
    up.style.background = '#52bab3';
    li.appendChild(up);
  }

  // only add `down` when li is NOT firstListItem
  if (li !== lastListItem) {
    let down = document.createElement('button');
    down.className = 'down';
    down.textContent = 'Down';
    // attaching dynamic color assignment for button
    down.style.background = '#508abc';
    li.appendChild(down);  
  }

  let remove = document.createElement('button');
  remove.className = 'remove';
  remove.textContent = 'Remove';
  // attaching dynamic color assignment for button
  remove.style.background = '#768da3';
  li.appendChild(remove);
}



// buttonType is className .down .up .remove
function removeButtonFromListItem(li, buttonType) {
  // debugger;
  // collection of up/down/remove buttons
  const liChildren = li.children;

  for (let i = 0; i < liChildren.length; i++) {
    // only when className 'up' == 'up'
    if (liChildren[i].className === buttonType) {
      // remove the up button
      li.removeChild(liChildren[i]);
    }
  }
}

function addButtonToListItem(li, buttonType) {
  let button = document.createElement('button');
  // collection of up/down/remove buttons
  const liChildren = li.children;

  button.className = buttonType;
  if (buttonType === 'up') {
    button.style.background = '#52bab3';
    button.textContent = 'Up';
  } else {
    button.style.background = '#508abc';
    button.textContent = 'Down';
  }

  for (let i = 0; i < liChildren.length; i++) {
    // user clicked on up
    if (buttonType === 'up' && liChildren[i].className === 'down') {
      li.insertBefore(button, liChildren[i]);
    } else if (buttonType === 'down' && liChildren[i].className === 'remove') {
      li.insertBefore(button, liChildren[i]);
    }
  }
}

for (let i = 0; i < lis.length; i += 1) {
  attachListItemButtons(lis[i]);
}

listUl.addEventListener('click', (event) => {

  if (event.target.tagName == 'BUTTON') {
    if (event.target.className == 'remove') {
      let li = event.target.parentNode;
      let prevLi = li.previousElementSibling;
      let nextLi = li.nextElementSibling;
      let ul = li.parentNode;

      // check for li is only one => do nothing
      if (prevLi === null && li === ul.lastElementChild) {

      // first li remove button was hit
      } else if (prevLi === null) {
        if (nextLi){
          nextLi.style.backgroundColor = 'lightskyblue';
        }

        removeButtonFromListItem(nextLi, 'up');
      // if el to remove is first el
      } else if (li === ul.lastElementChild) {
        prevLi.style.backgroundColor = 'lightsteelblue';
        removeButtonFromListItem(prevLi, 'down');
      }

      ul.removeChild(li);
    }
    if (event.target.className == 'up') {
      // debugger;
      let li = event.target.parentNode;
      let prevLi = li.previousElementSibling;
      let ul = li.parentNode;
      if (prevLi) {
        ul.insertBefore(li, prevLi);
      }
      if (li === listUl.firstElementChild) {
        li.style.backgroundColor = 'lightskyblue';
        prevLi.style.backgroundColor = '';
        removeButtonFromListItem(li, 'up');
        // prevLi is no longer firstElement
        //  so add up button
        addButtonToListItem(prevLi, 'up');
      // now li is up prevLi is last Element
      } else if (prevLi === listUl.lastElementChild) {
        li.style.backgroundColor = '';
        prevLi.style.backgroundColor = 'lightsteelblue';
        removeButtonFromListItem(prevLi, 'down');
        addButtonToListItem(li, 'down');
      }
    }  
    if (event.target.className == 'down') {
      // debugger;
      let li = event.target.parentNode;
      let nextLi = li.nextElementSibling;
      let ul = li.parentNode;
      // only move down if extLi exists
      if (nextLi) {
        // currently selected li if reference point 
        //  bring nextLi before selected li
        ul.insertBefore(nextLi, li);
      }
      // now li becomes last li
      if (li === listUl.lastElementChild) {
        nextLi.style.backgroundColor = '';
        li.style.backgroundColor = 'lightsteelblue';
        // remove down button
        removeButtonFromListItem(li, 'down');
        addButtonToListItem(nextLi, 'down');
      }
      // now nextLi is first li, 
      // when press 'down' -> 'up' button gone
      if (nextLi === listUl.firstElementChild) {
        nextLi.style.backgroundColor = 'lightskyblue';
        li.style.backgroundColor = '';
        removeButtonFromListItem (nextLi, 'up');
        addButtonToListItem(li, 'up');
      }
    } 
  }
});

toggleList.addEventListener('click', () => {
  if (listDiv.style.display == 'none') {
    toggleList.textContent = 'Hide list';
    listDiv.style.display = 'block';
  } else {
    toggleList.textContent = 'Show list';                        
    listDiv.style.display = 'none';
  }                         
});

descriptionButton.addEventListener('click', () => {
  descriptionP.innerHTML = descriptionInput.value + ':';
  descriptionInput.value = '';
});

addItemButton.addEventListener('click', () => {
  // debugger;
  let ul = document.getElementsByTagName('ul')[0];
  let li = document.createElement('li');
  li.textContent = addItemInput.value;
  li.style.backgroundColor = 'lightsteelblue';

  ul.appendChild(li);
  attachListItemButtons(li);
  addItemInput.value = '';
  // grab set of div, ul with new li
  let prevLi = li.previousElementSibling;
  prevLi.style.backgroundColor = '';
  addButtonToListItem(prevLi, 'down');
});
Steven Parker
Steven Parker
203,243 Points

Well, maybe not quite "all". :see_no_evil:

If you create a new item, it starts at the bottom, but with a "down" button. Then, if you move it up, it has two "down" buttons. The good news is they both work. :wink:

hc11
hc11
3,573 Points

Steven Parker I found my bug! It was not getting the latest lastListItem correctly. It is fixed!

const toggleList = document.getElementById('toggleList');
const listDiv = document.querySelector('.list');
const descriptionInput = document.querySelector('input.description');
const descriptionP = document.querySelector('p.description');
const descriptionButton = document.querySelector('button.description');
const listUl = listDiv.querySelector('ul');
const addItemInput = document.querySelector('input.addItemInput');
const addItemButton = document.querySelector('button.addItemButton');
const lis = listUl.children;
// const firstListItem = listUl.firstElementChild;
// const lastListItem = listUl.lastElementChild;

function attachListItemButtons(li) {
// debugger;
  let listDiv = document.querySelector('.list');
  let listUl = listDiv.querySelector('ul');
  let firstListItem = listUl.firstElementChild;
  let lastListItem = listUl.lastElementChild;
  if (li === firstListItem) {
    firstListItem.style.backgroundColor = 'lightskyblue';
  }

  if (li === lastListItem) {
    lastListItem.style.backgroundColor = 'lightsteelblue';
  }
  // only add `up` when li is NOT firstListItem
  if (li !== firstListItem) {
    let up = document.createElement('button');
    up.className = 'up';
    up.textContent = 'Up';
    // attaching color assignment for button
    up.style.background = '#52bab3';
    li.appendChild(up);
  }

  // only add `down` when li is NOT firstListItem
  if (li !== lastListItem) {
    let down = document.createElement('button');
    down.className = 'down';
    down.textContent = 'Down';
    // attaching dynamic color assignment for button
    down.style.background = '#508abc';
    li.appendChild(down);  
  }

  let remove = document.createElement('button');
  remove.className = 'remove';
  remove.textContent = 'Remove';
  // attaching dynamic color assignment for button
  remove.style.background = '#768da3';
  li.appendChild(remove);
}



// buttonType is className .down .up .remove
function removeButtonFromListItem(li, buttonType) {
  // debugger;
  // collection of up/down/remove buttons
  const liChildren = li.children;

  for (let i = 0; i < liChildren.length; i++) {
    // only when className 'up' == 'up'
    if (liChildren[i].className === buttonType) {
      // remove the up button
      li.removeChild(liChildren[i]);
    }
  }
}

function addButtonToListItem(li, buttonType) {
  // debugger;
  let button = document.createElement('button');
  // collection of up/down/remove buttons
  const liChildren = li.children;

  button.className = buttonType;
  if (buttonType === 'up') {
    button.style.background = '#52bab3';
    button.textContent = 'Up';
  } else {
    button.style.background = '#508abc';
    button.textContent = 'Down';
  }

  for (let i = 0; i < liChildren.length; i++) {
    // user clicked on up
    if (buttonType === 'up' && liChildren[i].className === 'down') {
      li.insertBefore(button, liChildren[i]);
    } else if (buttonType === 'down' && liChildren[i].className === 'remove') {
      li.insertBefore(button, liChildren[i]);
    }
  }
}

for (let i = 0; i < lis.length; i += 1) {
  attachListItemButtons(lis[i]);
}

listUl.addEventListener('click', (event) => {

  if (event.target.tagName == 'BUTTON') {
    if (event.target.className == 'remove') {
      let li = event.target.parentNode;
      let prevLi = li.previousElementSibling;
      let nextLi = li.nextElementSibling;
      let ul = li.parentNode;

      // check for li is only one => do nothing
      if (prevLi === null && li === ul.lastElementChild) {

      // first li remove button was hit
      } else if (prevLi === null) {
        if (nextLi){
          nextLi.style.backgroundColor = 'lightskyblue';
        }

        removeButtonFromListItem(nextLi, 'up');
      // if el to remove is first el
      } else if (li === ul.lastElementChild) {
        prevLi.style.backgroundColor = 'lightsteelblue';
        removeButtonFromListItem(prevLi, 'down');
      }

      ul.removeChild(li);
    }
    if (event.target.className == 'up') {
      // debugger;
      let li = event.target.parentNode;
      let prevLi = li.previousElementSibling;
      let ul = li.parentNode;
      if (prevLi) {
        ul.insertBefore(li, prevLi);
      }
      if (li === listUl.firstElementChild) {
        li.style.backgroundColor = 'lightskyblue';
        prevLi.style.backgroundColor = '';
        removeButtonFromListItem(li, 'up');
        // prevLi is no longer firstElement
        //  so add up button
        addButtonToListItem(prevLi, 'up');
      // now li is up prevLi is last Element
      } else if (prevLi === listUl.lastElementChild) {
        li.style.backgroundColor = '';
        prevLi.style.backgroundColor = 'lightsteelblue';
        removeButtonFromListItem(prevLi, 'down');
        addButtonToListItem(li, 'down');
      }
    }  
    if (event.target.className == 'down') {
      // debugger;
      let li = event.target.parentNode;
      let nextLi = li.nextElementSibling;
      let ul = li.parentNode;
      // only move down if extLi exists
      if (nextLi) {
        // currently selected li if reference point 
        //  bring nextLi before selected li
        ul.insertBefore(nextLi, li);
      }
      // now li becomes last li
      if (li === listUl.lastElementChild) {
        nextLi.style.backgroundColor = '';
        li.style.backgroundColor = 'lightsteelblue';
        // remove down button
        removeButtonFromListItem(li, 'down');
        addButtonToListItem(nextLi, 'down');
      }
      // now nextLi is first li, 
      // when press 'down' -> 'up' button gone
      if (nextLi === listUl.firstElementChild) {
        nextLi.style.backgroundColor = 'lightskyblue';
        li.style.backgroundColor = '';
        removeButtonFromListItem (nextLi, 'up');
        addButtonToListItem(li, 'up');
      }
    } 
  }
});

toggleList.addEventListener('click', () => {
  if (listDiv.style.display == 'none') {
    toggleList.textContent = 'Hide list';
    listDiv.style.display = 'block';
  } else {
    toggleList.textContent = 'Show list';                        
    listDiv.style.display = 'none';
  }                         
});

descriptionButton.addEventListener('click', () => {
  descriptionP.innerHTML = descriptionInput.value + ':';
  descriptionInput.value = '';
});

addItemButton.addEventListener('click', () => {
  // debugger;
  let ul = document.getElementsByTagName('ul')[0];
  let li = document.createElement('li');
  let lastListItem = listUl.lastElementChild;
  li.textContent = addItemInput.value;
  li.style.backgroundColor = 'lightsteelblue';

  ul.appendChild(li);
  attachListItemButtons(li);
  addItemInput.value = '';
  // grab set of div, ul with new li
  let prevLi = li.previousElementSibling;
  prevLi.style.backgroundColor = '';
  addButtonToListItem(prevLi, 'down');
});
Steven Parker
Steven Parker
203,243 Points

Great job! :+1:

Since you're posting snapshots (great idea!) you don't really need to paste in the code. But excellent use of Markdown! And forking a snapshot to run the preview is pretty easy, but those live demo's are definitely "above and beyond" :star2:

hc11
hc11
3,573 Points

Wow, how did I miss that? Ok, I will fix it.

hc11
hc11
3,573 Points

Good to know! I will follow your recommendation going forward!

Steven Parker
Steven Parker
203,243 Points

hc11 — Glad to help. You can mark the question solved by choosing a "best answer".
And happy coding!

Trevor Maltbie
seal-mask
.a{fill-rule:evenodd;}techdegree
Trevor Maltbie
Full Stack JavaScript Techdegree Student 12,719 Points

Great job, hc11.

I could not wrap my mind around how to create this feature into the project. After a few hours struggling and flailing around I decided to peak at other people's answers. I'm impressed by your grit to keep tweaking the code to get it working just right. Bravo!