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

Camilo Luna
Camilo Luna
8,836 Points

Here's my solution

I first created a solution where all buttons where removed on every Up, Down, Remove and Add item buttons, but I thought that might be inefficient, so I ended up with a solution where the function takes a list as an argument and adds or removes the necessary buttons.

const toggleList = document.querySelector('#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 listItems = document.getElementsByTagName('li');
const lis = listUl.children;

// Function accepts a list as parameter
function attachListItemButtons(ul) {
  let list = ul.children;
  // Loop over the li items (children of ul)
  for (i = 0; i < list.length; i++) {
    let li = list[i];
    let firstListItem = ul.firstElementChild;
    const lastListItem = ul.lastElementChild;

    // Select buttons by class
    let upBtn = li.querySelector('.up');
    let downBtn = li.querySelector('.down');
    let removeBtn = li.querySelector('.remove');

    if (!upBtn && li !== firstListItem) {
      // If there's no up button and the li is not the first list item, create an up button
      let up = document.createElement('button');
      up.className = 'up';
      up.textContent = 'Up';
      if (downBtn) {
        // If there's a down button, insert it before
        li.insertBefore(up, downBtn);
      } else if (removeBtn) {
        // Else, if there's a remove button, insert it before
        li.insertBefore(up, removeBtn);
      } else {
        // Else, append it
        li.appendChild(up);
      }
    } else if (upBtn && li === firstListItem) {
      // Else, if there's an up button and the li is the first item, remove the up button
      li.removeChild(upBtn);
    }

    if (!downBtn && li !== lastListItem) {
      // If there's no down button and the li is not the last list item, create a down button
      let down = document.createElement('button');
      down.className = 'down';
      down.textContent = 'Down';
      if (removeBtn) {
        // If tehre's a remove button, insert it before
        li.insertBefore(down, removeBtn);
      } else {
        // Else, append it
        li.appendChild(down);
      }
    } else if (downBtn && li === lastListItem) {
      // Else, if there's a down button and the li is the last item, remove the up button
      li.removeChild(downBtn);
    }

    if (!removeBtn) {
      // If there's no remove button, create and append it
      let remove = document.createElement('button');
      remove.className = 'remove';
      remove.textContent = 'Remove';
      li.appendChild(remove);
    }
  }
}

attachListItemButtons(listUl);

listUl.addEventListener('click', (event) => {
  if (event.target.tagName == 'BUTTON') {
    let li = event.target.parentNode;
    let ul = li.parentNode;  
    if (event.target.className == 'remove') {
      ul.removeChild(li);
    }
    if (event.target.className == "up") {
      let prevLi =  li.previousElementSibling;
      if (prevLi) {
        ul.insertBefore(li, prevLi);
      }
    }
    if (event.target.className == "down") {
      let nextLi =  li.nextElementSibling;
      if (nextLi) {
        ul.insertBefore(nextLi, li);
      }
    }
  }
  attachListItemButtons(listUl);
});

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);
  addItemInput.value = '';
  ul = document.getElementsByTagName('ul')[0];
  attachListItemButtons(ul);
});

1 Answer

Steven Parker
Steven Parker
195,276 Points

Works well! :+1:

One minor observation: it looks a bit odd to have the "down" button on top be the same color as all the "up" buttons instead of the other "down" buttons. :see_no_evil:

Camilo Luna
Camilo Luna
8,836 Points

Thanks Steven! Yeah I also changed some CSS using :first-child and :last-child pseudo-selectors, but didn't post the code here since I thought it was outside of the scope of this course :)

Steven Parker
Steven Parker
195,276 Points

That's probably right, but CSS now does many things that used to require JavaScript. And it usually does them more efficiently!