Build a Lightbox

Hello,

As with my post last week this isn’t a direct issue with the task checklist, but a broader query about the code. (I’ve taken to building these lab projects outside of the fCC environment and just trying to get them to work).

I’ve come up against an issue with removing a child element. The code works, and the lightbox functions, but after the first execution I get an error at line 35 even though it continues to work:

Uncaught TypeError: Node.removeChild: Argument 1 is not an object.

I’ve struggled with adding/removing nodes. In particular, the code only works if I specify ‘container[0]’ as the parent rather than just ‘container’.

It works (functionally but not aesthetically) if I just use the lightbox element without specifying an index. What’s happening here please?

(Also please ignore my CSS, it’s my absolute nemesis and the styling is awful!)

<!-- file: Lightbox.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lightbox Viewer</title>
    <link rel = "stylesheet" href = "styles.css">
  </head>
  <body>
  <h1>Click on an image below:</h1>
  
<div class = "container"> 
  <div class = "gallery">
	<button class = "button" value = "stone"><img class = "thumb" id = "stone" src = "https://cdn.freecodecamp.org/curriculum/labs/stonehenge-thumbnail.jpg"></button>
	<button class = "button" value = "storm"><img class = "thumb" id = "storm" src = "https://cdn.freecodecamp.org/curriculum/labs/storm-thumbnail.jpg"></button>
	<button class = "button" value = "trees"><img class = "thumb" id = "trees" src = "https://cdn.freecodecamp.org/curriculum/labs/trees-thumbnail.jpg"></button>
    </div>
     <div class = "lightbox off"></div> 
 </div>
  <div class = "offbtn"><button class = "turnoff">OFF</button></div>
 
  </body>
    <script src = "script.js"></script> 
</html>
/* file: styles.css */
:root{
background-color: lavender; 
font-family: helvetica, sans-serif; 
}

h1{ 
text-align: center; 
}

.container{ 
display: flex;
flex-direction: row; 
flex-flow: nowrap; 
align-items: center;
justify-content: center; 
margin: auto; 
border: 2px black solid; 
height: 75vh;
z-index: -1; 
}

.gallery{ 
display: flex; 
width: 90%; 
margin: 25px auto;
padding: 25px; 
}

.button{ 
width: 25%; 
height: 150px; 
margin: auto; 
padding: 10px; 
border: 1px grey solid; 
background-color: transparent; 
transition: 0.2s ease-in; 
}

.thumb{
width: 100%; 
height: 100px; 
padding: 0px; 
margin: 0px; 
object-fit: fill; 
}

.lightbox{
position: absolute; 
margin: auto; 
height: 75vh;
width: 98%;
z-index: 3; 
background-color: black; 
opacity: 65%;
border: 2px black solid; 
transition: 0.3s ease-in-out; 
z-index: 1; 
}

.lightbox.off{
scale: 0%;
}


.large {
position: absolute; 
width: 95%;
margin: auto; 
z-index: 2;
}

#large {
position: absolute; 
width: 95%;
margin: auto; 
z-index: 2;
}


#largeimage{
display: block; 
width: 65%;
height: 50%;
margin: auto; 
}


button:hover{
scale: 120%;
}


.offbtn {
width: 100px; 
height: 100px; 
margin: auto; 
padding: 20px; 
}

.turnoff{
border: 2px black solid; 
padding: 20px; 
font-weight: bold; 
background-color: white;
transition: none; 
scale: 100%;
}

.turnoff:hover{
background-color: gray;
scale: 105%;
}

.turnoff:active{
background-color: gray;
scale: 95%;
}

.turnoff.off{
display: none; 
}

/* file: script.js */
const button = document.getElementsByClassName("button"); 
const lightbox = document.querySelector(".lightbox");
const offbtn = document.querySelector(".turnoff");
const gallery = document.getElementsByClassName("gallery");
const container = document.getElementsByClassName("container");


for (let i = 0; i < button.length; i++){
button[i].addEventListener("click", () => {
let clicked = button[i]; 
boxOn(clicked); 
});
};

function boxOn(clicked){
	lightbox.classList.remove("off");
	offbtn.classList.remove("off"); 
	let child  = clicked.childNodes[0].src.replace("-thumbnail", "");
	let bigImage = document.createElement("img");
	let imageContainer = document.createElement("div");
	imageContainer.setAttribute("id", "large");
	bigImage.setAttribute("src", child);
	bigImage.setAttribute("id", "largeimage");
	bigImage.innerHTML = child;
	
	setTimeout (timer, 300); 
	function timer (){
	container[0].appendChild(imageContainer).appendChild(bigImage)}; 
	buttonFunc(); 
return; 
};

function boxOff(){
let bigImage = document.getElementById("large"); 
container[0].removeChild(bigImage);
lightbox.classList.add("off");
offbtn.classList.add("off");

}

function buttonFunc(){
offbtn.addEventListener("click", ()=> {
boxOff();
});
};

Thanks!!

you used getElementsByClassName here, so container is a list of elements
same for gallert and button

1 Like

Aha! I was wondering about that. This is a sketchy area for me and I can’t get the concepts to stick. So am I right in thinking:

getElementsByClassName returns an array of the node + all its children rather than jsut the node?

I also tried getElementById (replacing the selector in the html) and that didn’t work either.

So to access a node better to use querySelector? Does that require there to be a selector in the actual CSS file, or is it enough that a selector is defined in the html? (apols if I’m getting my terminology wrong!)

Thanks!

getElementById will select a single element, make sure you do not have a duplicate id

querySelector uses a css selector to select one element (that doesn’t mean that the selector needs to be in the css file)

querySelectorAll selects a list of elements using a css selector

querySelector and querySelectorAll are newner than getElementByClassName or getElementById

a big difference is that getElementByClassName returns a live collection that updates when the html is updated, querySelectorAll returns a static list that correspond to the html status when the method is called

That’s great, thanks again!

I’m still stymied by this though. I’ve changed the syntax to querySelector and also the relevant html class/ids.

When I run the code it works as before, but I’m still getting: Uncaught TypeError: Node.removeChild: Argument 1 is not an object.

Using console log I’ve found that it seems to be running the boxOff function twice and I can’t see why.

The child ‘large’ is added, and then correctly removed, but after the first execution this removal happens twice, and there is no object to remove (having just been removed) so an error is thrown.

Are you able to see anything I’m missing?

const button = document.querySelectorAll(".button"); 
const lightbox = document.querySelector(".lightbox");
const offbtn = document.querySelector(".turnoff");
const gallery = document.querySelector(".gallery");
let container = document.querySelector(".container");



for (let i = 0; i < button.length; i++){
button[i].addEventListener("click", () => {
let clicked = button[i].firstChild; 
boxOn(clicked); 
});
};

function boxOn(clicked){
	lightbox.classList.remove("off");
	offbtn.classList.remove("off"); 
	let child  = clicked.src.replace("-thumbnail", "");
	let bigImage = document.createElement("img");
	let imageContainer = document.createElement("div");
	imageContainer.setAttribute("class", "large");
	bigImage.setAttribute("src", child);
	bigImage.setAttribute("id", "largeimage");
	bigImage.innerHTML = child;
	setTimeout (timer, 300); 
	function timer (){
	container.appendChild(imageContainer).appendChild(bigImage)
	console.log(container.childNodes); 
	}; 
	
	buttonFunc(); 
	
return;
};

function boxOff(){
container.removeChild(document.querySelector(".large"));
lightbox.classList.add("off");
offbtn.classList.add("off");
return; 
}

function buttonFunc(){
offbtn.addEventListener("click", ()=> {
boxOff();
});
};

Edit: It works! I fixed it.

It was the buttonFunc function. For some reason the anonymous function in the eventlistener was calling the boxOff function twice. But by removing it works. Still not sure why.

function buttonFunc(){
offbtn.addEventListener("click", boxOff);
};