Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript JavaScript and the DOM (Retiring) Traversing the DOM Getting the First and Last Child

Jonathon Irizarry
Jonathon Irizarry
9,154 Points

Can I get some help trying to add this feature?

So I added the features the instructor talks about at the end of the video (thanks to people in another question). My program now makes sure the firstElementChild and lastElementChild have the correct buttons and it refreshes the buttons as well to maintain that feature. The last feature I was trying to add was keeping the firstListItem and lastListItem background colors on the first and last items in the list, even when adding buttons. I tried to replicate the same functions I used to refresh the attached li buttons, but for the past few days no bueno.

<!DOCTYPE html>
<html>
  <head>
    <title>JavaScript and the DOM</title>
    <link rel="stylesheet" href="css/style.css">
  </head>
  <body>
    <h1 id="myHeading">JavaScript and the DOM</h1>
    <p>Making a web page interactive</p>
    <button id="toggleList">Hide list</button>
    <div class="list">
      <p class="description">Things that are purple:</p>
      <input type="text" class="description">
      <button class="description">Change list description</button>
      <ul>
        <li>grapes</li>
        <li>amethyst </li>
        <li>lavender</li>
        <li>plums</li>
      </ul>
        <input type="text" class="addItemInput">
        <button class="addItemButton">Add item</button>
    </div>
    <script src="app.js"></script>
  </body>
</html>
/*jshint esversion: 6 */

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 removeItemButton = document.querySelector('button.removeItemButton');
const lis = listUl.querySelectorAll('li');
const firstListItem = listUl.firstElementChild;
const lastListItem = listUl.lastElementChild;

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


function removeLis (lis) {
  while (lis.firstElementChild)
    lis.removeChild(lis.firstElementChild);
}

function attachLiButtons(li) {
  let up = document.createElement('button');
  let down = document.createElement('button');
  let remove = document.createElement('button');
  up.className = 'up';
  up.textContent = 'Up';
  down.className = 'down';
  down.textContent = 'Down';
  remove.className = 'remove';
  remove.textContent = 'Remove';
  if (li.previousElementSibling) {
    li.appendChild(up);
  }
  if (li.nextElementSibling) {
    li.appendChild(down);
  }
  li.appendChild(remove);

}

function removeLiButtons (li) {
  while (li.firstElementChild)
   li.removeChild(li.firstElementChild);
}

function refreshLiButtons (li) {
  removeLiButtons(li);
  attachLiButtons(li);
}

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

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

// listDiv.addEventListener('mouseover', (e) => {
//   if (e.target.tagName == 'LI') {
//     e.target.textContent = e.target.textContent.toUpperCase();
//   }
// });
//
// listDiv.addEventListener('mouseout', (e) => {
//   if (e.target.tagName == 'LI') {
//     e.target.textContent = e.target.textContent.toLowerCase();
//   }
// });

// document.addEventListener('click', (e) => {
//   console.log(event.target);
// });

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');
    let lis = listUl.children;
    li.textContent = addItemInput.value; //spits out the li with the buttons attached and class names!
    listUl.appendChild(li);
    firstListItem.style.backgroundColor = 'lightskyblue';
    lastListItem.style.backgroundColor = 'lightsteelblue';
    removeLis(lis);
    attachLiButtons(li);
    refreshLiButtons(li.previousElementSibling);
    // li.addEventListener('mouseover', () => {
    //   li.textContent = li.textContent.toUpperCase();
    // });
    // li.addEventListener('mouseout', () => {
    //   li.textContent = li.textContent.toLowerCase();
    // });
    addItemInput.value = '';
});

Don't mind the commented out section that is just how I type out code, I keep alternative methods to the code around it commented out for learning purposes. So how would I go about keeping styles(the background color) only on the first and last items in the list and remaining after adding new items? I feel like it is something simple and small but I can't quite figure out, I have tried to remove all the children with a function and refresh them just after appending the new list items. Someone put my brain to ease!

5 Answers

Steven Parker
Steven Parker
229,644 Points

You already have most of the logic needed.

Since you already determine if you should add an "up" button after a move, then the item that does not get one should get the top color (just add an "else" to your condition). And any item that does get one should have the top color taken away (if it has it).

Similarly, the item that does not get a "down" button should get the bottom color. All the others should not have the bottom color.

:warning: SPOILER ALERT! — ignore this box if you want to try doing it just from the hints. Otherwise, in attachLiButtons:

  if (li.previousElementSibling) {
    if (li.style.backgroundColor == "lightskyblue")    // has color but not on top?
      li.style.backgroundColor = null;                 // ...then remove color
    li.appendChild(up);
  } else li.style.backgroundColor = "lightskyblue";    // always add color to top item
  if (li.nextElementSibling) {
    if (li.style.backgroundColor == "lightsteelblue")  // has color but not on bottom?
      li.style.backgroundColor = null;                 // ...then remove color
    li.appendChild(down);
  } else li.style.backgroundColor = "lightsteelblue";  // always add color to bottom item
Reid Everett
Reid Everett
14,009 Points

I would create a function that removes the background colors from the list and then re applies them after the change is made. You can piggy back that onto the refreshLiButtons function.

function bgColor () { //remove the background color from the current list and reapply it to the modified list //make sure you account for when the list has none, 1 or 2 or more items }

function refreshLiButtons (li) { removeLiButtons(li); attachLiButtons(li); *call the function here* bgColor(); }

Reid Everett
Reid Everett
14,009 Points

Another thing I noticed is that you need to account for when the "li" variable is null so you're not throwing errors.

function attachLiButtons(li) { let up = document.createElement('button'); let down = document.createElement('button'); let remove = document.createElement('button'); up.className = 'up'; up.textContent = 'Up'; down.className = 'down'; down.textContent = 'Down'; remove.className = 'remove'; remove.textContent = 'Remove'; if ( ---->li != null && li.previousElementSibling) { li.appendChild(up); } if ( ---->li != null && li.nextElementSibling) { li.appendChild(down); } if ( ---->li != null) { li.appendChild(remove); } }

Steven Parker
Steven Parker
229,644 Points

How could the "li" variable ever be null?

Reid Everett
Reid Everett
14,009 Points

The variable, not the element "li" becomes null when all of the items are completely removed from the list and when you try to add an item to an empty list you will see that error in the console.

Steven Parker
Steven Parker
229,644 Points

I see now. That happens in a different function than the one I was looking at.

Jonathon Irizarry
Jonathon Irizarry
9,154 Points

Thanks everyone for the guidance and advice, hope everyone is having good days!