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 trialNate Jonah
20,981 PointsMy solution for the first and last child challenge
This is my solution to the challenge at the end of the video. Instead of removing the buttons on the first and last children, which is what I actually did at first, I made a class that would grey them out and when you hover over them the cursor changes as well. My JS code is below but you can view the live version on my codepen
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;
function attachListItemButtons(li) {
let remove = document.createElement('button');
remove.className = 'remove';
remove.textContent = 'Remove';
li.appendChild(remove);
let down = document.createElement('button');
down.className = 'down';
down.textContent = 'Down';
li.appendChild(down);
let up = document.createElement('button');
up.className = 'up';
up.textContent = 'Up';
li.appendChild(up);
}
for (let i = 0; i < lis.length; i++) {
attachListItemButtons(lis[i]);
}
hideButtons(lis);
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);
}
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 (event.target.className == 'down') {
let li = event.target.parentNode;
let nextLi = li.nextElementSibling;
let ul = li.parentNode;
if (nextLi) {
ul.insertBefore(li, nextLi.nextSibling);
}
}
}
hideButtons(lis);
});
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;
attachListItemButtons(li);
ul.appendChild(li);
addItemInput.value = '';
hideButtons(lis);
});
// Loop through all list items and disable up button for first child and down button for last child
function hideButtons(lis) {
const firstListItem = listUl.firstElementChild;
const lastListItem = listUl.lastElementChild;
for (let i = 0; i < lis.length; i++) {
// if it's the first item, disabled up button
if (lis[i] == firstListItem) {
lis[i].lastElementChild.className = 'blocked';
} else {
lis[i].lastElementChild.className = 'up';
}
// if it's the last item, disabled down button
if (lis[i] == lastListItem) {
lis[i].firstElementChild.nextElementSibling.className = 'blocked';
} else {
lis[i].firstElementChild.nextElementSibling.className = 'down';
}
}
}
6 Answers
Steven Parker
231,268 PointsNice job. I think this creates an even better UX (user experience) than removing and replacing the buttons.
Another approach would be to set the disabled property of the buttons. You wouldn't need special styling classes since it automatically gives them a "dimmed" appearance, and has the additional benefit of making them "unclickable".
Nate Jonah
20,981 PointsThanks for your feedback! I think I will change it and just add the disabled attribute. Thanks for your suggestion :)
Josef Aidt
7,722 PointsSteven Parker, what is the functional difference between the following lines of JavaScript to disable buttons; besides the slight HTML difference?
button.setAttribute('disabled', 'true');
<button disabled="true"></button>
Versus what seems to be a clunkier (perhaps deprecated?) method,
button.disabled = true;
<button disabled></button>
Steven Parker
231,268 PointsJosef, there's a difference between HTML properties and element properties. In HTML, a boolean property is true if it exists at all, but you can optionally set it to a blank or it's own name. So all of these are equivalent and valid:
<button disabled></button>
<button disabled=""></button>
<button disabled="disabled"></button>
On the other hand, these are not valid, though browsers will likely let you get away with it:
<button disabled="true"></button>
<button disabled="false"></button> <!-- will actually DISABLE the button -->
But when it comes to JavaScript, it's a bit different, since the property itself always exists and is a boolean:
button.setAttribute('disabled', 'true'); // this is not valid (yet may work)
button.setAttribute('disabled', null); // but this is valid
button.disabled = true; // and so is this
Josef Aidt
7,722 PointsSteven Parker thank you for response. I have edited my code to reflect that change.
Aakash Srivastav
Full Stack JavaScript Techdegree Student 11,638 PointsHey Steven Parker , what does this line do?
lis[i].lastElementChild.className = 'blocked'
?
Did he used CSS for 'blocked' class to hide the element?
Steven Parker
231,268 PointsInstead of hiding the buttons, the "blocked" class gives both the button and the cursor a different appearance to indicate that it is not available. See Nate's codepen example.
Aakash Srivastav
Full Stack JavaScript Techdegree Student 11,638 PointsAnd why had he called hideButtons(lis) three times Steven? Does it needed only within the addItemButton.addEventListener
?
Steven Parker
231,268 PointsYou have the right idea to ask Nate directly. But for the best chance of getting his attention, tag him the same way you did me so his name shows up in blue.
Nate Jonah
20,981 PointsSo the purpose of that function is to make sure that whatever is at the top of the list, can’t be moved up, so it disabled the Up button and whatever is at the bottom can’t be moved down so it disables the Down button. It also makes sure that when the list items are moved around and their order changes, the ones that had their Up or Down buttons disabled previously, now become reenabled.
So when you see hideButtons(lis) the first time, it makes sure that the Up button is disabled for the top list item and the Down button is disabled for the bottom list item right from the start when everything loads.
The second time you see hideButtons(lis), it makes sure that whenever the list items change order, those same two buttons (Down on the first list item and Up on the last list item) are disabled again. Without that, if the second list item were to suddenly become the first one, then the Up button would be clickable and we don’t want that.
The third time you see hideButtons(lis) is for newly added list items. A new list item will become the last one on the list so that one needs to have its Down button disabled and the one that was previously the last one, needs to have its Down button reenabled.
I hope that makes sense :)
Josef Aidt
7,722 PointsNate Meyer, I used a bit of your logic and tweaked it to disable the buttons and keep the background colors on the first and last item in the list.
function disableButtons(list) {
const firstListItem = listUl.firstElementChild;
const lastListItem = listUl.lastElementChild;
let listLength = list.length;
for (let i = 0; i < listLength; i += 1) {
let up = list[i].querySelector('button.up');
let down = list[i].querySelector('button.down');
if (list[i] == firstListItem) {
up.disabled = true;
list[i].style.backgroundColor = 'lightskyblue';
}
else if (list[i] == lastListItem) {
down.disabled = true;
list[i].style.backgroundColor = 'lightsteelblue';
}
else {
up.disabled = false;
down.disabled = false;
list[i].style.backgroundColor = null;
}
}
}
I also gave the buttons a little color action by appending the following lines to style.css
.list li button:disabled {
background: dimgray;
color: gray;
}
Nate Jonah
20,981 Pointsoh nice :)
Aakash Srivastav
Full Stack JavaScript Techdegree Student 11,638 PointsCan you please tell me how many times I have to call this function disableButtons(list)
?
marekdzicio
6,429 PointsHi Adding If statement with empty string for addItemInput value, solves a little problem, that occurs, when user add "nothing" to the list.
addItemButton.addEventListener('click', () => {
if(addItemInput.value == ""){return}else{
let ul = document.getElementsByTagName('ul')[0];
let li = document.createElement('li');
li.textContent = addItemInput.value;
attachListItemButtons(li);
ul.appendChild(li);
addItemInput.value = '';
hideButtons(lis);
}});
Steven Parker
231,268 PointsNate — with all the "question piggybacking", did your issue get resolved?
Nate Jonah
20,981 PointsIt took a while but I eventually changed to disabling the buttons rather than styling them that way. I couldn't get setAttribute to work at first but then I realised that it was working but the styling was kind of messed up. I finally realised it was because of Bootstrap. In the CSS it's .btn[disabled] for the styling so once I added the btn class to my buttons, I had to change all the className references to classList and blah blah blah. On the upside, I have now used classList.contains and classList.remove, which I'd never used/didn't really know about. Thanks a lot for your help, Steven :)
Steven Parker
231,268 PointsGood job on finding and using the classList methods! But i would not have expected to need special styling for the disabled buttons, doesn't Bootstrap handle that automatically?
Nate Jonah
20,981 PointsBootstrap only seems to handle the styling automatically if the button element has the .btn class AND disabled="disabled", which is kind of annoying. If it's just an anchor element, you still need the .disabled class.
ICHEN WU
Courses Plus Student 5,427 PointsThanks for Nate's sharing! I like your new solution! very user friendly :) I just finished the challenge Guil gave..., just to share my code, it's a bit complicated though, tried to simplify it, but that's what I did now...
I made 4 functions for remove/add up/down btns..., 1 function for update list everytime a button in list items is clicked, and 1 function for initializing...
const Btn_toggleList = document.querySelector('.Btn_toggleList');
const listDiv = document.querySelector('.listDiv');
const input_Description = document.querySelector('input.description');
const p_Description = document.querySelector('p.description');
const Btn_Description = document.querySelector('button.description');
const listUl = document.querySelector('ul');
const input_addItem = document.querySelector('input.addItem');
const Btn_addItem = document.querySelector('button.addItem');
const lis = listUl.children;
// function: add 3 btns for list items
function attachListItemButtons (li) {
let up = document.createElement('button');
up.className = 'up';
up.textContent = 'Up';
li.appendChild(up);
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);
}
//function: remove the first btn (up btn)
function rmvUpBtn (li) {
let upBtn = li.children[0];
li.removeChild(upBtn);
console.log('removeup');
};
//function: remove the second btn (down btn)
function rmvDownBtn (li) {
let downBtn = li.children[1];
li.removeChild(downBtn);
console.log('removedown');
};
//function: add up btn back
function addUpBtnBack (li) {
let upBtn = document.createElement('button');
upBtn.className = 'up';
upBtn.textContent = 'Up';
let downBtn = li.firstElementChild;
li.appendChild(upBtn);
li.insertBefore(upBtn,downBtn);
console.log('addUpBtnBack');
};
//function: add down btn back
function addDownBtnBack (li) {
let downBtn = document.createElement('button');
downBtn.className = 'down';
downBtn.textContent = 'Down';
let removeBtn = li.lastElementChild;
li.appendChild(downBtn);
li.insertBefore(downBtn,removeBtn);
console.log('addDownBtnBack');
};
// function: update list
function updateList () {
if ( lis.length > 1 ) { //ckeck: if the number of list items more than 1
const lastLi = listUl.lastElementChild;
const firstLi = listUl.firstElementChild;
for (let i = 0 ; i < lis.length ; i ++ ) {
if (lis[i] !== firstLi && lis[i] !== lastLi ) { //check: if this item is not the first NOR the last item
if ( lis[i].children[0].className === 'down' ) { //check: if the first element of the li is 'down', add 'up' btn back
addUpBtnBack(lis[i]);
} else if ( lis[i].children[1].className === 'remove' ) { //check: if the second element of the li is 'remove', add down btn back
addDownBtnBack(lis[i]);
}
} else if (lis[i] === firstLi) { //check: if this is the first item, remove up btn
if (lis[i].children[0].className === 'up') {
rmvUpBtn(lis[i]);
}
} else if (lis[i] === lastLi) { //check: if this is the last item, remove down btn
if (lis[i].children[1].className === 'down') {
rmvDownBtn(lis[i]);
}
}
}
} else if (lis.length === 1 ) {
let li = lis[0];
rmvUpBtn(li);
}
console.log('updateList');
};
// function: initialize list(attach btns and update list)
function initialize () {
for (let i = 0 ; i < lis.length ; i ++ ) {
attachListItemButtons(lis[i]);
};
updateList();
};
initialize();
// event listener: up, down, remove btn
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);
}
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 (event.target.className === 'down') {
let li = event.target.parentNode;
let nextLi = li.nextElementSibling;
let ul = li.parentNode;
if (nextLi) {
ul.insertBefore(nextLi, li); //(li, nextLi.nextSibling) will do
}
}
//update list after pressed any btns in the list
updateList();
}
});
// event listener: change description btn
Btn_Description.addEventListener('click', () => {
p_Description.textContent = input_Description.value + ':'; // either use 'innerHTML' or 'textContent'
input_Description.value = '';
});
// event listener: hide and show btn
Btn_toggleList.addEventListener('click', () => {
if ( listDiv.style.display === 'block' ) {
listDiv.style.display = 'none';
Btn_toggleList.textContent = 'Show List';
} else {
listDiv.style.display = 'block';
Btn_toggleList.textContent = 'Hide List';
}
});
// event listener: add item btn
Btn_addItem.addEventListener('click', () => {
let ul = document.getElementsByTagName('ul')[0];
let li = document.createElement('li');
li.textContent = input_addItem.value;
ul.appendChild(li);
attachListItemButtons(li);
input_addItem.value = '';
updateList();
});
ICHEN WU
Courses Plus Student 5,427 PointsBTW, how to change inline code color like yours guys? why would my code appears in only green and orange..?
Zhenghao He
Courses Plus Student 2,389 PointsI have a question about
addItemButton.addEventListener('click', () => {
let ul = document.getElementsByTagName('ul')[0];
let li = document.createElement('li');
li.textContent = addItemInput.value;
attachListItemButtons(li);
ul.appendChild(li);
addItemInput.value = '';
hideButtons(lis);
});
why is the lis
variable updated automatically after adding new li
?
I mean lis
is declared at the top as a const and I don't see you explicitly change the value of it in the function. how come it ends up being updated with the new li
?
Steven Parker
231,268 PointsYou are adding to an old question which has already been marked solved by the owner. Please start a new question.
Aakash Srivastav
Full Stack JavaScript Techdegree Student 11,638 PointsAakash Srivastav
Full Stack JavaScript Techdegree Student 11,638 PointsWhy have you called hideButtons(lis) three times Nate Jonah