Guys! I have a menu with few links, and when i click on one of them i want to show horizontal line, like on screenshot below.
I've tried to use this script, but now css properties applies to all of the links. What should i do to remove propertie from inactive link, and apply it to one, that i clicked? Thank you!
$('.main-menu-link-container').click(function() {
$(this).find('.main-menu-link-line').css({"width": "6.11em", "marginRight": "2.36em"});
});
CodePudding user response:
Some context (your current HTML markup) was missing so it was hard to perfectly address your exact issue. Anyway, your main concern was how to address the element that fired the click event.
Your events seem attached to the parent menu element, which is a good coding habit considering Events are better handled using delegation in order to handle current or future, dynamic child elements.
The problem
jQuery's $(this)
is the somewhat "equivalent" of Event.currentTarget
in vanilla JavaScript. When using direct events like $("targetSelector").on("eventName", fn)
the this
inside the callback function refers directly to any element matching the "targetSelector". In your case the parent element.
.find("childSelector")
returns a collection of child Elements, .css()
will apply the new styles to every such child element in the collection.
The solution: proper event delegation
In a jQuery-syntax use Event delegation by providing a second argument to the .on("eventName", "dynamicChildSelector")
and inside the function use $(this)
- which will be the actual clicked child:
// "Static delegated parent" "EventName", "dynamicChild"
$('.main-menu-link-container').on("click", ".menu-link", function(evt) {
const $menu = $(evt.delegateTarget); // The '.main-menu-link-container' parent
const $links = $menu.find(".menu-link"); // All the children links
const $link = $(this); // or: $(evt.currentTarget) The clicked ".menu-link" element
$links.removeClass("is-active"); // remove from all children
$link.addClass("is-active"); // add to the clicked one
});
* {margin:0; box-sizing: border-box;}
.main-menu-link-container {
display: flex;
flex-direction: column;
background-color: red;
color: white;
padding: 1rem;
gap: 1rem;
}
.menu-link {
display: flex;
align-items: center;
border: solid white 3px;
padding: 1rem;
cursor: pointer;
}
.menu-link::before {
content: "";
display: inline-block;
width: 0rem;
height: 0.3rem;
background: #fff;
transition: all 0.3s;
}
.menu-link.is-active::before {
width: 6rem;
margin-right: 2rem;
}
<div >
<div >Link 1</div>
<div >Link 2</div>
<div >Link 3</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
Also, instead of changing the styles directly using JS (bad practice since hard to maintain) use the .[add/remove/toggle]Class()
methods - and set the styles in your Stylesheet - where due.
Event delegation in JavaScript
For comparison, here's the same approach to event delegation in pure JavaScript, without the overhead of a library like jQuery:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
// Task: Menu active items
const handleActiveMenuItem = (evt) => {
const elLink = evt.target.closest(".menu-link"); // The clicked element's closest selector / or self
if (!elLink) return; // Do nothing, no link clicked
const elMenu = evt.currentTarget; // The event-bound element
const elsLinks = els(".menu-link", elMenu);
elsLinks.forEach(elLink => elLink.classList.remove("is-active")); // remove from all children
elLink.classList.add("is-active"); // add to the clicked one
};
els(".main-menu-link-container").forEach(elMenu => {
elMenu.addEventListener("click", handleActiveMenuItem);
});
* {margin:0; box-sizing: border-box;}
.main-menu-link-container {
display: flex;
flex-direction: column;
background-color: red;
color: white;
padding: 1rem;
gap: 1rem;
}
.menu-link {
display: flex;
align-items: center;
border: solid white 3px;
padding: 1rem;
cursor: pointer;
}
.menu-link::before {
content: "";
display: inline-block;
width: 0rem;
height: 0.3rem;
background: #fff;
transition: all 0.3s;
}
.menu-link.is-active::before {
width: 6rem;
margin-right: 2rem;
}
<div >
<div >Link 1</div>
<div >Link 2</div>
<div >Link 3</div>
</div>