Get child element from element through an event with Vue - javascript

Goal
What I'm trying to achieve is that whenever the user clicks on the .menu-item element, the dropdown element .menu-item-dropdown will get a class that will show the element.
Problem
Currently, when I click on the .menu-item class element, the returned value from the console.log in showMobileNavDropdown() returns null. When clicking, the event.target element is .menu-item-main and not .menu-item. This happens only when I click on the text of the li. Otherwise this works as intended.
What would be the best way to include the li text so that I can still grab the .menu-item-dropdown class?
Vue
<template>
<div>
<nav class="nav">
<div class="nav-left">
<img class="logo" src="/images/logo.svg" alt="" />
</div>
<div class="nav-center">
<ul class="menu-items">
<li
class="menu-item"
#click="showMobileNavDropdown($event)"
>
<!-- Main Title On Nav -->
<a class="menu-item-main" href="#">Company</a>
<!-- Dropdown -->
<div class="menu-item-dropdown">
<ul>
<li class="">
About
</li>
<li class="">
Contact
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="nav-right">
Contact us
</div>
<div class="hamburger" #click="openMobileNav">
<span class="ham-line"></span>
<span class="ham-line ham-line-2"></span>
</div>
</nav>
<div class="mobile-nav"></div>
</div>
</template>
<script>
export default {
methods: {
openMobileNav() {
var mobileNav = document.querySelector(".mobile-nav");
mobileNav.classList.toggle("showMobileNav");
},
// This function
showMobileNavDropdown(event) {
console.log(event); // This element returns 'menu-item-main' and not 'menu-item'
console.log(event.target.querySelector(".menu-item-dropdown"));
},
},
};
</script>

Assuming you have a longer list of possible click-targets and can't or won't just use refs, I would use event-delegation and in the click-handler check, whether the clicked element is inside a clickable parent and based on that, toggle a class on the parent, which can then be used to select the child in CSS, for example to change it's opacity.
Vue.component('foo-bar', {
methods: {
toggleSubItemVisibility({ target }) {
const item = target.closest('.nav-item');
// we check because the user might have clicked on the surrounding ul
if (item) {
item.classList.toggle('active');
}
}
},
template: `
<nav>
<ul #click="toggleSubItemVisibility">
<li class="nav-item">
<span class="nav-label">Foo</span>
<div class="nav-sub-item">More details...</div>
</li>
<li class="nav-item">
<span class="nav-label">Bar</span>
<div class="nav-sub-item">More details...</div>
</li>
</ul>
</nav>`
})
new Vue({
el: '#app'
});
nav ul {
list-style-type: none;
display: flex;
flex-direction: row;
gap: 12px;
}
.nav-label {
cursor: pointer;
}
.nav-sub-item {
opacity: 0;
transition: opacity 1s ease;
}
.nav-item.active .nav-sub-item {
opacity: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<foo-bar></foo-bar>
</div>

The solution was to either grab the element, and if that returned null, grab the sibling. This way, it's always possible to select the .menu-item-dropdown element. When clicking on the element, it can either be a child or sibling.
Part of Solution
var element =
event.target.querySelector(".menu-item-dropdown") ||
event.target.nextElementSibling;
element.classList.toggle("hide_drop");

Related

Looping through parent multiples times to fire correspoding child class

I have a dropdown menu and I am trying to toggle the display:block class on the ul child of the button, once that dropdown button is clicked. Currently the ul is set to display:none until the main button parent is clicked. So only the parent is shown on the screen currently.
I am having a hard time setting up these loops properly. I can set it up so when one button is clicked all the ul's for every project are shown but I only want the one under the corresponding button.
HTML:
<div class="dropdown">
<div class="project">
<button>website 1<i class="fas fa-caret-down"></i></button>
<ul class="drop-list">
<li>Live Website</li>
<li>Resources</li>
</ul>
</div>
<div class="project">
<button>Website 2<i class="fas fa-caret-down"></i></button>
<ul class="drop-list">
<li>Live Website</li>
<li>Resources</li>
</ul>
</div>
</div>
JAVASCRIPT:
const projects = document.querySelectorAll('.project')
const dropdown_lists = document.querySelectorAll('.drop-list')
projects.forEach(button => button.addEventListener('click', function(){
// Feel like I need a forEach loop here to loop through the dropdown_lists or a reg for loop
// for(i=0;i<dropdown_lists.length;i++) - maybe something like this
// then I need to classList.toggle('active') on this ul
// I'm getting stuck on how to make this in sync with the corresponding project button
}))
CSS:
.drop-list {
display: none;
}
.active {
display: block;
}
What I need is when projects[0] is clicked that toggles the class of 'active' to dropdown_lists[0] from the list.
When projects[1] is clicked that toggles the class of 'active' to dropdown_lists[1] from the list.
Again, I can set up the code so when I click one button all the ul dropdown-lists toggle the class 'active' but I only want the first button to go with the first ul and second button with the second ul.
I'm just not sure how to go about this, either make a boolean if dropdown lists === 2 and project === 2 than toggle the class. I just cant think of the best practice to do this easily.
Appreciate the help.
Thanks
I applied the toggle() method, together with the method of forEach(). In which you need to declare the variable of the current button - projects_current.
Next, .active class is assigned to <ul class="drop-list">...</ul> according to the index:
dropdown_lists[index]
const projects = document.querySelectorAll('.project')
const dropdown_lists = document.querySelectorAll('.drop-list')
projects.forEach(function (projects_current, index) {
projects_current.addEventListener('click', function () {
dropdown_lists[index].classList.toggle('active');
});
});
.drop-list {
display: none;
}
.active {
display: block;
}
<div class="dropdown">
<div class="project">
<button>website 1<i class="fas fa-caret-down"></i></button>
<ul class="drop-list">
<li>Live Website</li>
<li>Resources</li>
</ul>
</div>
<div class="project">
<button>Website 2<i class="fas fa-caret-down"></i></button>
<ul class="drop-list">
<li>Live Website</li>
<li>Resources</li>
</ul>
</div>
</div>
You could use "event delegation" to achieve the result without multiple event listeners. The basic idea is to use a click event listener on a common parent (div.dropdown) to check if a button was clicked, and if so, toggle the active class of the next sibling element.
A minimal event listener would then read like what you want to achieve:
document.querySelector(".dropdown").addEventListener( "click", event => {
if( event.target.tagName == "BUTTON") {
event.target.nextElementSibling.classList.toggle("active");
}
});
You can put an id in the dropdown to use it in the onclick of the buttons
const toggleDropdow = (dropdown) =>
document.getElementById(dropdown).classList.toggle("active");
.drop-list {
display: none;
}
.active {
display: block;
}
<div class="dropdown">
<div class="project">
<button onclick="toggleDropdow('dropdown1')">Website 1</button>
<ul id="dropdown1" class="drop-list">
<li>Live Website</li>
<li>Resources</li>
</ul>
</div>
<div class="project">
<button onclick="toggleDropdow('dropdown2')">Website 2</button>
<ul id="dropdown2" class="drop-list">
<li>Live Website</li>
<li>Resources</li>
</ul>
</div>
</div>

Hiding a dropdown in Vuejs by clicking outside (component)

I'm creating a menu component on a Vuejs project. This menu has 2 dropdowns, I already created some methods and used Vue directives so when I click in 1 of them, the other hides and vice versa, I'd also like to know how to hide them by clicking outside.
I've tried 2 Vue libs but didn't work for me. Also, I'd like to do this manually if possible and not externally.
HTML:
<!-- menu -->
<div>
<ul>
<li><span>Home</span></li>
<li v-on:click="toggle1(), dropAreas =! dropAreas">
<span>Areas</span>
</li>
<li v-on:click="toggle2(), dropAdmin =! dropAdmin">
<span>Administration</span>
</li>
</ul>
</div>
<!-- /menu -->
<!-- dropdowns-->
<div v-if="dropAreas">
<ul>
<li>
<span>Kitchen</span>
</li>
<li>
<span>Baths</span>
</li>
</ul>
</div>
<div v-if="dropAdmin">
<ul>
<li>
<span>Profile</span>
</li>
<li>
<span>Services</span>
</li>
</ul>
</div>
<!-- /dropdowns-->
JS
data () {
return {
dropAreas: false,
dropAdmin: false
}
},
methods: {
toggle1 () {
if (this.dropAdmin === true) {
this.dropAdmin = false
}
},
toggle2 () {
if (this.dropAreas === true) {
this.dropAreas = false
}
}
}
*This code is being called in another component, "Home", like this:
<template>
<div>
<menu></menu>
<!-- [...] -->
</div>
</template>
Any ideas on how to do it manually? Is it possible? Thank you.
It's a bit of a hack, but you can do it using tabindex HTML attribute and :focus CSS pseudo-class:
new Vue({
el: '#app',
template: `
<div class="container">
<div
ref="menu"
id="menu"
tabindex="0"
>Menu</div>
<ul id="dropdown">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
`
});
#menu {
display: inline-block;
padding: 1em;
border: 1px solid #e6e6e6;
cursor: pointer;
}
#dropdown {
display: none;
}
#menu:focus + #dropdown {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app"></div>

Navbar div onclick function does not work for inner elements

I have a div class .dropbtn inside my navbar that I wish would run a list drop down function when clicked, but only the text "TOTAL" works onclick.
The <span> and the <i> inside it do not do anything when I click on it, and I need all three elements to be clickable and display the dropdown function. I am using jQuery, but not Bootstrap. Thanks in advance! EDITED.
jQuery('body').on('click', '.dropbtn', function() {
jQuery("#myDropdown").toggleClass("show");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="reservas_right">
<div class="dropdown_reservas nav-item_reservas" id="inner_reservas_right">
<div class="dropbtn">
TOTAL
<br /><span id="totalprice">0,00€</span>
<i class="material-icons">arrow_drop_down</i>
</div>
<div class="dropdown-content_reservas" id="myDropdown">
<ul id="dropul" class="dropul">
<li id="drop1"></li>
<li id="drop2"></li>
<li id="drop3"></li>
<li id="drop4"></li>
<li id="drop5"></li>
<li id="drop6"></li>
</ul>
</div>
</div>
</div>
CSS:
.show{
list-style-type:none;
}
jQuery('body').on('click', '.dropbtn', function(e) {
if(e.target !== this && jQuery(e.target).parent().is(".dropbtn"))
{
jQuery("#myDropdown").toggleClass("show");
} else {
jQuery("#myDropdown").toggleClass("show");
}
});

jQuery switch active class depending on which section is showing

I have several sections within a document as follows:
<section id="step1">
</section>
<section id="step2" style="display:none">
</section>
<section id="step3" style="display:none">
</section>
I am showing and hiding these depending on user interaction. I wanted to have some kind of circular heading to indicate the user is on section 2 of 3 for example like so:
<div id="indicators" class="clearfix">
<div class="center-div">
<ul>
<li id="step1-circle" class="circle active"></li>
<li id="step2-circle" class="circle"></li>
<li id="step3-circle" class="circle"></li>
</ul>
</div>
</div>
How would I switch the active class depending on which section is showing?
You can use the addClass() method in tandem with the removeClass() method like:
function makeIndicatorCircleActive(id){
// Remove active state of current circle
$('#indicators .active').removeClass('active');
// Add active state to desired element
$(id).addClass('active');
}
// Somewhere else in the code...
makeIndicatorCircleActive('#step2-circle);
I've added a button for the animation. You'd need to show more example code of your implementation, but you may be able to get away with let $section = $('section:visible') and instead of putting this in a click function, create a generic function name, which is triggered whenever your event occurs.
$('button').click(function() {
// context used for jQuery
let $section = $(this).closest('section');
// get next circle, if current circle is last, then get first circle
var $next = $('.circle.active', $section).next('.circle'),
$next = ($next.length && $next) || $('.circle', $section).first();
$('.circle.active', $section).removeClass('active'); // remove all actives
$next.addClass('active'); // add new active
});
.circle {
background: black;
border-radius: 25px;
display: inline-block;
list-style-type: none;
width: 50px;
height: 50px;
-moz-border-radius: 25px;
-webkit-border-radius: 25px;
}
.active {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>
<div class="clearfix">
<div class="center-div">
<ul>
<li class="circle active"></li>
<li class="circle"></li>
<li class="circle"></li>
</ul>
</div>
<button>Next</button>
</div>
</section>
<section>
<div class="clearfix">
<div class="center-div">
<ul>
<li class="circle active"></li>
<li class="circle"></li>
<li class="circle"></li>
</ul>
</div>
<button>Next</button>
</div>
</section>

Find element by class and toggle it

I have inside my html code repeating elements like this:
<li>
<a class="synth-faq-trigger" href="#0">Link</a>
<div class="synth-faq-content">
<p class="filler">Text</p>
</div> <!-- synth-faq-content -->
</li>
and I use the following command so I can toggle the class 'content-visible' that will make this element slide to reveal the contents of <p>, in this case Text.
What I am trying to do is before doing this in my code, find the previous element that had a class of 'content-visible' and toggle it, so only one element remains visible at all times.
$(this).next('.synth-faq-content').slideToggle(200).end().parent('li').toggleClass('content-visible');
I tried various things lastly being
$(this).closest('synth-faq-trigger').next().find('.synth-faq-content').toggleClass('content-visible');
that has no effect at all. Moreover the suggestions from w3school on finding an element by class failed all together.
How can I make sure that I find all my items containing 'content-visible' and I remove this class from them?
I notice you are already using hash links in your example code, and because of this, you can achieve what you are trying without any JavaScript. Browsers already have functionality to handle displaying elements depending on the hash target, so let's exploit this browser feature rather than implementing it again in JavaScript.
Live demo: http://jsfiddle.net/g105b/cj5fscah/
The href of the anchor is to a hash link, which will be put into the URL bar of the browser. Each li element has an ID that corresponds to the hash targets, and using the :target CSS selector, you can style elements differently depending on what the hash is.
With some fancy CSS you can add animations too, that will perform much better than any JavaScript can.
<ul>
<li id="0">
<a class="synth-faq-trigger" href="#0">Link</a>
<div class="synth-faq-content">
<p class="filler">Text for one</p>
</div> <!-- synth-faq-content -->
</li>
<li id="1">
<a class="synth-faq-trigger" href="#1">Link</a>
<div class="synth-faq-content">
<p class="filler">Text for two</p>
</div> <!-- synth-faq-content -->
</li>
<li id="2">
<a class="synth-faq-trigger" href="#2">Link</a>
<div class="synth-faq-content">
<p class="filler">Text for three</p>
</div> <!-- synth-faq-content -->
</li>
</ul>
ul {
width: 320px;
}
li {
background: #aaf;
}
.synth-faq-content {
overflow: hidden;
height: 0;
transition: height 200ms ease-in-out;
}
li:target .synth-faq-content {
height: 3em;
}
Edit: if you still need to manipulate the class names, a simple piece of JavaScript can trigger when the hash changes to accomplish having a class added to the correct LI element.
window.addEventListener("hashchange", function(e) {
// Remove all .content-visible elements:
[].forEach.call(document.querySelectorAll(".content-visible"), function(li) {
li.classList.remove("content-visible");
});
// Add .content-visible to the li that contains the clicked anchor:
document.querySelector("a[href='" + location.hash + "']").parentElement.classList.add("content-visible");
});
Updated demo: http://jsfiddle.net/g105b/cj5fscah/1/
You can use the sibling relation like
$('a').click(function() {
var $li = $(this).next('.synth-faq-content').slideToggle(200).closest('li').toggleClass('content-visible');
$('li.content-visible').not($li).removeClass('content-visible').find('.synth-faq-content').slideUp();
})
.content-visible > a {
color: green;
}
.synth-faq-content {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul>
<li>
<a class="synth-faq-trigger" href="#0">Link</a>
<div class="synth-faq-content">
<p class="filler">Text</p>
</div>
<!-- synth-faq-content -->
</li>
<li>
<a class="synth-faq-trigger" href="#0">Link</a>
<div class="synth-faq-content">
<p class="filler">Text</p>
</div>
<!-- synth-faq-content -->
</li>
<li>
<a class="synth-faq-trigger" href="#0">Link</a>
<div class="synth-faq-content">
<p class="filler">Text</p>
</div>
<!-- synth-faq-content -->
</li>
</ul>

Categories