Return to Level1Techs.com

Learning JavaScript before it learns you

Hi!
I’m currently working on a website but have very little experience with JavaScript.
I want to do two things with one click event, but I was able to get only first one working.

With over(event) I’m changing the styling of the first child element inside the target slide element. But at the same time I want to change the style of a pagination div two DOM levels lower.

I almost get it working with the array of element with the same class but I couldn’t wrap my head around logic and I failed in the end.
What should be declared first and called last? My goal is to have a system that can be easily expanded in the future with more id="block_" elements.

I hope code snippet is not too big. Thank you in advance for any help.

<html>
	<head>
		<style>
		.overlay {
			display: none;
		}
		.overlay-on {
			display: block;
		}
		.pagination-hide {
			display: none;
		}
		</style>
	</head>

	<body>
	
	
		<div id="block1" class="big-wrapper">
			<div class="small-wrapper">
			
				<div class="slide" onclick="over(event)">
					<div class="overlay">Overlay</div>
					<div>1</div>
					<div>2</div>
				</div>
				<div class="slide">Slide 2</div>
				<div class="slide">Slide 3</div>
				<div class="slide">Slide 4</div>
				
			</div>
			
			<div class="pagination"></div>
		</div>


		<div id="block2" class="big-wrapper">
			<div class="small-wrapper">
			
				<div class="slide" onclick="over(event)">
					<div class="overlay">Overlay</div>
					<div>1</div>
					<div>2</div>
				</div>
				<div class="slide">Slide 2</div>
				<div class="slide">Slide 3</div>
				
			</div>
			
			<div class="pagination"></div>
		</div>


		<div id="block3" class="big-wrapper">
			<div class="small-wrapper">
			
				<div class="slide" onclick="over(event)">
					<div class="overlay">Overlay</div>
					<div>1</div>
					<div>2</div>
				</div>
				<div class="slide">Slide 2</div>
				<div class="slide">Slide 3</div>
				<div class="slide">Slide 4</div>
				<div class="slide">Slide 5</div>
				
			</div>
			
			<div class="pagination"></div>
		</div>
		
		
	<script>
		function over(evt) {
			evt.currentTarget.children[0].classList.toggle("overlay-on");
		}
		
		var hide = document.getElementsByClassName("pagination");
		for (var i = 0; i < hide.length; i++) {
			hide[i].this.classList.toggle("pagination-hide");
		};
	</script>		

	</body>
</html>

In not 100% sure I understand exactly what you’re trying to do, if I understand correctly you can refer to the pagination that’s inside of the same big wrapper by ev.target.parentNode.nextSibling

… or alternatively you could let the click event bubble up
… or alternatively you could do it with css

1 Like

Thank you.

By clicking on the first slide I want to open an overlay which is hidden by default and by the same time hide pagination that is placed after small-wrapper that contain the slideshow. I hope this explains it better.

When I try to refer to pagination in over function like this:

<script>
		function over(evt) {
			evt.currentTarget.children[0].classList.toggle("overlay-on");
			ev.target.parentNode.nextSibling.classList.toggle("pagination-hide");
		}
</script>

I get an error:
Uncaught ReferenceError: ev is not defined

When I change ev to evt i get:
Uncaught TypeError: evt.target.parentNode.nextSibling.classList is undefined

How can all of this be done in css?

Think ev.target was supposed to be evt.currentTarget, should probably work after that.

1 Like

Tried that too.
Firefox:
Uncaught TypeError: evt.currentTarget.parentNode.nextSibling.classList is undefined
Chromium:
Uncaught TypeError: Cannot read properties of undefined (reading 'toggle')

Seems nextSibling is returning a text element, could try to use

evt.currentTarget.parentNode.nextSibling.nextSibling.classList.toggle("pagination-hide");

or

evt.currentTarget.parentNode.parentNode.getElementsByClassName("pagination")[0].classList.toggle("pagination-hide");

2 Likes

Doubling nextSibling have done the trick. Thank you @Engle !

1 Like

Both of those are assuming a constant structure for you block_ elements. Should be fine in its current state, but if the structure changes or differs from one to the next, will likely need a function to find the top most element first.

2 Likes

The structure will stay in place, and only thing that will change will be amount of slides. This is a static image gallery, so I assume it’s fine?

So I decided to use this topis for asking general noob JS questions. I hope this is OK with moderation policy.

the_truth

Just to update the OG problem I learned a little about DOM traversal and was able to simplify the code:

evt.currentTarget.parentNode.nextElementSibling.classList.toggle("pagination-hide");

nextElementSilbling points to next closest element defined by a tag.


Now to the next thing. Lets say we have multiple elements with the class cat and one element with id dog. By hovering over every cat there should be fired event that modifies classes of dog.

So far I have partial success. The problem is that the event isn’t fired on hovering over the cats but on hovering over the whole viewport. I don’t know where to put EventListener so it can detect the area properly.

<style>
	.cat { width: 100px; height: 100px }
	.bark { noise: 100% }
	.sleep { noise: 0% }
</style>

<div class="wrapper">
	<div class="cat"></div>
	<div class="cat"></div>
	<div id="dog" class="sleep">DOG</div>
	<div class="cat"></div>
	<div class="cat"></div>
</div>

const cats = Array.from(document.getElementsByClassName('cat'));
const dog = document.getElementById("dog");

cats.forEach(styleHover)
function styleHover() {
	addEventListener("mouseover", event => {
		dog.classList.add("bark");
		dog.classList.remove("sleep");
	});
}

cats.forEach(styleOff)
function styleOff() {
	addEventListener("mouseout", event => {
		dog.classList.remove("bark");
		dog.classList.add("sleep");
	});
}

As always - thanks for any help!

Your styleHover and styleOff functions will need a parameter that points to the current object that is getting passed in the forEach.

function styleHover (item) { ... }

Will then need to use the addEventListener on the object you want it work with, so item.addEventListener. Currently just being added to the top most element, document, so moving the mouse anywhere on / off the page will trigger it.

1 Like

It works! Thank you!

I’m trying to pass calculated number stored in var as a value in CSS class property, but I can’t get pass the syntax.

let imageHeight = window.innerHeight - textHeight;

var imageBox = document.getElementsByClassName('image-wrapper');

for (var i = 0; i < imageBox.length; i++) {
	imageBox[i].style.height = '(imageHeight)';
}

I’m getting error on last line with '(imageHeight)' I’m pretty convinced this should be written differently. What should be the proper syntax for the last line to insert height: calculated-value px; in CSS class?

I’ve got textHeight calculated as a number. And according to the console its working properly and returns a valid number.
Thanks!

reality

Is there a reason to have '(imageHeight)' with parenthesis and quotes?

Should just set .height = imageHeight;

If you need to explicitly have px on the end can do imageHeight + "px";, but the browser usually just assumes that and does it automatically.

2 Likes

Thank you @Engle you helped me a lot.
I saw the parenthesis in some example and followed along blindly :grimacing:.
With your advice I was able to get this code working but only with onclick event. But it’s a progress.

Now I started looking how to trigger this function in real time based on window resize, but I can’t get it working. The function should be triggered only when two conditions are met - window resize and presence in DOM a div with a class auto-height.

start();
window.onresize = start;
function start(){
	if (document.classList.contains('auto-height')) {
	// GET TEXT DIV HEIGHT
		let textHeight = document.querySelector('.auto-height').offsetHeight;
	// CALCULATE IMAGE DIV HEIGHT
		let imageHeight = window.innerHeight - textHeight;
	// ADD HEIGHT STYLE TO IMAGE CLASS
		var imageBox = document.getElementsByClassName('image-wrapper');
		for (var i = 0; i < imageBox.length; i++) {
			imageBox[i].style.height = imageHeight + "px";
		}
	}
}

I do not think document.classList is valid syntax.

can do var auto_height_element = document.querySelector('.auto-height'); , check if that is null and return/exit. Otherwise get the offset height from it and continue along.

1 Like

Thanks again @Engle ! JS syntax still feels a little alien to me.

I’ve restructured the code into something more usable. The thing I omitted is that this code should in theory work on many containers - in example below named big-wrapper.

The code is little to big for forum embedding, but I tested it with W3Schools editor:

Pasting code from below into it should give current working state.

Unfortunately I still cant get it to work as there are additional conditions like toggling main overlay container with a toggleOverlay function.
The best way to test the problem is to toggle blue button, resize the viewport to bigger size. The image container wont resize. Next untoggle blue button, and toggle it again. The image container should be resized now.
The thing I’m after is to automatically resize image-box when the viewport is resized but only when overlay-show is toggled on.

I tried couple different approaches like moving the start and resize functions outside the toggleOverlay but I must done something wrong because it didn’t worked.

<!DOCTYPE html>
<head>
<style>
* {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
html, body {
	margin: 0;
	padding: 0;
	height: 100%;
	width: 100%;
}
.big-wrapper {
	display: block;
	width: 100%;
	height: 100%;
}
.overlay-button {
	position: absolute;
	left: 0;
	top: 0;
	width: 100px;
	height: 100px;
	background-color: blue;
	z-index: 10;
	cursor: pointer;
}
.overlay {
	display: none;
}
.overlay-show {
	display: block;
	position: absolute;
	width: 100%;
	height: 100%;
	background: grey;
	z-index: 5;
}
.text-box {
	position: absolute;
	padding: 20px 20px 20px 120px;
	top: 0;
	width: 100%;
	height: max-content;
	max-height: max-content;
	block-size: fit-content;
	border: 1px solid blue;
}
.image-box {
	position: absolute;
	bottom: 0;
	display: block;
	width: 100%;
	border: 1px solid red;
}
.image-box img {
	position: absolute;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.main-content {
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
}
</style>
</head>

<body>
	
	
	<div class="big-wrapper">
		<div class="overlay-button" onclick="toggleOverlay(event)"></div>
		<div class="overlay">
			<div class="text-box">
				<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Enim tortor at auctor urna nunc id cursus. Sit amet commodo nulla facilisi nullam vehicula ipsum a arcu. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi. Risus sed vulputate odio ut. Enim ut sem viverra aliquet. Vitae semper quis lectus nulla at volutpat diam. In arcu cursus euismod quis viverra nibh cras pulvinar mattis. Sodales neque sodales ut etiam sit amet nisl. Suscipit tellus mauris a diam maecenas sed enim ut. Imperdiet sed euismod nisi porta lorem mollis aliquam. Aenean pharetra magna ac placerat vestibulum lectus mauris ultrices. In aliquam sem fringilla ut. Purus sit amet volutpat consequat mauris nunc congue.<br><br>In nulla posuere sollicitudin aliquam ultrices sagittis orci. At elementum eu facilisis sed odio morbi quis. Odio eu feugiat pretium nibh ipsum consequat nisl. Congue quisque egestas diam in arcu cursus euismod quis viverra. Mollis nunc sed id semper risus in. Condimentum lacinia quis vel eros donec ac odio tempor. Mi bibendum neque egestas congue. Imperdiet massa tincidunt nunc pulvinar sapien et ligula ullamcorper malesuada. Ullamcorper dignissim cras tincidunt lobortis. Eget est lorem ipsum dolor sit amet consectetur. Sit amet consectetur adipiscing elit. Sit amet dictum sit amet justo. Pharetra sit amet aliquam id diam maecenas. Amet venenatis urna cursus eget. Posuere sollicitudin aliquam ultrices sagittis orci. Turpis egestas integer eget aliquet nibh praesent. Nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Eget magna fermentum iaculis eu non diam phasellus vestibulum. Dui nunc mattis enim ut tellus elementum sagittis vitae et.</p>
			</div>
			<div class="image-box">
				<img src="https://forum.level1techs.com/uploads/default/original/4X/1/6/3/163ca4c9485d27721328f2befa33852db4a39b6f.jpeg">
			</div>
		</div>
		<!--main content that will be covered by overlay - not very important here. -->
		<div class="main-content">____________________BIG WRAPPER MAIN CONTENT</div>
	</div>
	
	
	<!--all BIG-WRAPPERS have vertical scroll snap so only one can fit into the screen at once. They have the same internal structure as the first one above, and they should share onclick "toggleOverlay" function.
	<div class="big-wrapper">
	</div>
	<div class="big-wrapper">
	</div>
	<div class="big-wrapper">
	</div> 
	...	

	-->


<script>
	// OVERLAY TOGGLE
		function toggleOverlay(overlay) {
		// toggle overlay on div with "overlay" class:
			overlay.currentTarget.nextElementSibling.classList.toggle("overlay-show");
		// RESPONSIVE IMAGE LAYOUT
			// get all text wrappers - there is multiple of them on the page, so the code below might need to be restructured:
			const textWrapper = Array.from(document.getElementsByClassName('text-box'));
			// I don't know if this should be done with applying the function for each or it would be better to pick one item from an array, as there will be only one text box visible simultaneously:
			textWrapper.forEach(toggleHeight);
			// toggle a class that will be used for text-box height measurement:
			function toggleHeight(text) {
				text.classList.toggle("read-height");
			}
			// get text-box element with a 'read-height' class
			let textBox = document.querySelector('.read-height');
			// get text-box height
			let textHeight = document.querySelector('.read-height').offsetHeight;
			// calculate image-box height
			let imageHeight = window.innerHeight - textHeight;
			// get image-box element
			// again I'm not sure if 'forEach' is better here and not picking one element from ana array - I include the code for the second option in comments below.
			//~ let imageBox = document.getElementsByClassName('overlay-img-box');
			let imageBox = Array.from(document.getElementsByClassName('image-box'));

			start();
			window.onresize = start;
			function start() {
				if (textBox === null) {
					return	
				} else {
					//~ for (var i = 0; i < imageBox.length; i++) {
					//~ imageBox[i].style.height = imageHeight + "px";
					//~ }
					imageBox.forEach(resize)
					function resize(element) {
						element.style.height = imageHeight + "px";
					}
				}
			}
		}
</script>

</body>
</html>

First thing that popped out is everything is contained in the toggleOverlay function. The resize event does not get added until after it has been toggled/called once.

Could probably break things down into several functions, toggleOverlay dealing with the toggle and start (probably should be renamed :)) is dealing with the resize / resize variables.

Yeah the naming could be better for sure, still learning.

What I tried is to end toggleOverlay after toggleHeight function - just by moving last braces, and leave the rest as it is. Which I guess is ok-ish with declaring vars/const first and then executing a function.

<script>
	// OVERLAY TOGGLE
		function toggleOverlay(overlay) {
		// toggle overlay on div with "overlay" class
			overlay.currentTarget.nextElementSibling.classList.toggle("overlay-show");
		// RESPONSIVE IMAGE LAYOUT
			// get all text wrappers - there is multiple of them on the page, so this might be restructured 
			const textWrapper = Array.from(document.getElementsByClassName('text-box'));
			// I don't know if this should be done with applying the function for each or it would be better to pick one item from an array, as there will be only one text box visible simultaneously.
			textWrapper.forEach(toggleHeight);
			// toggle a class that will be used for text-box height measurement
			function toggleHeight(text) {
				text.classList.toggle("read-height");
			}
		}
	// IMAGE RESIZE
		// get text-box element with a 'read-height' class
		let textBox = document.querySelector('.read-height');
		// get text-box height
		let textHeight = document.querySelector('.read-height').offsetHeight;
		// calculate image-box height
		let imageHeight = window.innerHeight - textHeight;
		// get image-box element
		// again I'm not sure if 'forEach' is better here and not picking one element from ana array - I include code for the second option in comments below
		//~ let imageBox = document.getElementsByClassName('overlay-img-box');
		let imageBox = Array.from(document.getElementsByClassName('image-box'));

		start();
		window.onresize = start;
		function start() {
			if (textBox === null) {
				return	
			} else {
				//~ for (var i = 0; i < imageBox.length; i++) {
				//~ imageBox[i].style.height = imageHeight + "px";
				//~ }
				imageBox.forEach(resize)
				function resize(element) {
					element.style.height = imageHeight + "px";
				}
			}
		}
</script>

But it leads to an error about textHeight being null. And I don’t quite get it, since the next step after exiting toggleOverlay is getting all read-height elements which are supposed to be present since toggleOverlay is on.

read-height does not exist until you toggle first, but the querySelector call is happening when the page loads.

can move the textbox, textheight, imageheight, and imagebox variables to the beginning of the start () function. Will just need to call start () at the end of toggleOverlay.

1 Like