im having trouble with the js in this code. I have 2 clickble dropdowns, but only one of them (the first dropdown) is working. I dont know how to fix it.
here's the html part:
<div id="wrap">
<nav>
<div class="logo">
<img src="./photos-docs/ME-marine-logo.png" alt="logo" class="logo" />
</div>
<button type="button" class="btn-hamburger" data-action="nav-toggle">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</button>
<ul class="nav-menu">
<li class="nav-item">עמוד ראשי</li>
<li class="nav-item dropdown">
עיסויים
<div class="dropdown-menu">
<a class="dropdown-item" href="#">רפואי</a>
<a class="dropdown-item" href="#">שוודי</a>
<a class="dropdown-item" href="#">רקמות עמוקות</a>
<a class="dropdown-item" href="#">ניקוז לימפטי</a>
<a class="dropdown-item" href="#">אבנים חמות</a>
</div>
</li>
<li class="nav-item dropdown">
טיפולי פנים
<div class="dropdown-menu">
<a class="dropdown-item" href="#">קלאסי</a>
<a class="dropdown-item" href="#">יופי</a>
<a class="dropdown-item" href="#">אקנה</a>
<a class="dropdown-item" href="#">פילינג</a>
<a class="dropdown-item" href="#">מיצוק</a>
<a class="dropdown-item" href="#">פיגמנטציה</a>
<a class="dropdown-item" href="#">אנטי אייג׳ינג</a>
</div>
</li>
<li class="nav-item">מזותרפיה</li>
<li class="nav-item">מיקרובליידינג</li>
<li class="nav-item">הזמינו תור</li>
<li class="nav-item">צרו קשר</li>
<li class="nav-item"></li>
<li class="nav-item"><i class="fa-brands fa-whatsapp"></i>
</li>
and here's the js part:
let nav = document.querySelector('nav');
let dropdown = nav.querySelector('.dropdown');
let dropdownToggle = nav.querySelector("[data-action='dropdown-toggle']");
let navToggle = nav.querySelector("[data-action='nav-toggle']");
dropdownToggle.addEventListener('click', () => {
if (dropdown.classList.contains('show')) {
dropdown.classList.remove('show');
} else {
dropdown.classList.add('show');
}
})
navToggle.addEventListener('click', () => {
if (nav.classList.contains('opened')) {
nav.classList.remove('opened');
} else {
nav.classList.add('opened');
}
})
what should I do from here? I know the problem ia in the js but I dont know how to keep going from here, im stuck.
In your code, you are using the querySelector method to select a single .dropdown element, which is why only the first dropdown is working.
You need to use the querySelectorAll method instead, which will return a list of all elements that match the given selector. You can then loop through this list and add the click event listener to each dropdown menu.
let nav = document.querySelector('nav');
let dropdowns = nav.querySelectorAll('.dropdown');
let dropdownToggles = nav.querySelectorAll("[data-action='dropdown-toggle']");
let navToggle = nav.querySelector("[data-action='nav-toggle']");
dropdownToggles.forEach(function(toggle, index) {
toggle.addEventListener('click', function() {
let dropdown = dropdowns[index];
if (dropdown.classList.contains('show')) {
dropdown.classList.remove('show');
} else {
dropdown.classList.add('show');
}
});
});
navToggle.addEventListener('click', () => {
if (nav.classList.contains('opened')) {
nav.classList.remove('opened');
} else {
nav.classList.add('opened');
}
});
Problem is here
let dropdown = nav.querySelector('.dropdown');
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
querySelector() returns only first element that matches the specified selector!
Thats why only one dropdown works.
You should use
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll
And loop through every element to add event listener to them - just like you did in your code.
Related
I have 2 links on the site, they work, but if you log in from your phone, they stop working, tag a.
<section class="section section--map" id="map">
<div class="container">
<div class="map">
<h2 class="map__title">
<div>
<i class="fa-solid fa-location-dot"></i>
</div>
<a href="https://dayz.xam.nu/" target="_blank">
map dayz
</a>
</h2>
</div>
</div>
</section>
there is also a problem with the second link
<div class="container__header">
<div class="header__inner">
<div class="header__logo" data-scroll="#intro"></div>
<nav class="nav" id="nav">
<a class="nav__link active" href="#" data-scroll="#about">about</a>
<a class="nav__link" href="#" data-scroll="#services">howtoplay</a>
<a class="nav__link" href="#" data-scroll="#mods">mods</a>
<a class="nav__link" href="#" data-scroll="#updates">updates</a>
<a class="nav__link" href="#" data-scroll="#map">map</a>
<a class="nav__link" href="https://dayzmsk.dayzplay.ru/" target="_blank">shop</a>
</nav>
<a class="btn__discord" href="https://discord.gg/qyUnGSePWf" target="_blank"><img src="../img/dslogosite.png" alt=""></a>
</div>
</div>
almost all links work on js, fast and smooth transition to the section
document.querySelector('.nav').addEventListener("click", (event) => {
if (event.target.getAttribute('href') !== '#') {
return;
}
event.preventDefault();
let elementOffset = 0;
let parent = document.querySelector('.nav');
let menuItem = parent.querySelectorAll('.nav__link');
if(event.target.classList.contains('nav__link')) {
for (let i = 0; i < menuItem.length; i++) {
menuItem[i].classList.remove('active');
}
}
let elementId = event.target.getAttribute('data-scroll');
let element = event.target;
if(elementId && element){
elementOffset = getElementScrollOffset(elementId);
scrollToTop(elementOffset);
element.classList.add('active');
}
});
I tried to give index 999 to elements, tried to remove the target, but it didn’t help, I don’t understand why it works on the computer, but not on the phone
The display block was to blame, if you remove it from the element, everything works. Close.
I'm not sure if my post title is specific enough for the question I'm asking. I couldn't think of any other way to phrase it.
I am building a rudimentary CSM and I'd like to have the following "Edit" button at the side of every entry. When the button is clicked, a drop-down menu is displayed. Each button and corresponding ul tag would have unique IDs based on the post it's attached to.
How can I generalize the following JS code to work with every button, regardless of ID, instead of being dependent on getting element by ID?
Here's the button markup.
<button id="someId" aria-expanded="false">Edit</button>
<ul class="list-edit text-black" id="someOtherId" style="display: none; margin-top: 10px;">
<a href="#">
<li class="list-items-edit">Edit</li>
</a>
<a href="#" class="popup-with-zoom-anim">
<li class="list-items-edit">Unpublish</li>
</a>
<a href="#" class="popup-with-zoom-anim">
<li class="list-items-edit">Delete</li>
</a>
</ul>
Here is the JS.
const toggle = document.getElementById('someId')
const content = document.getElementById('someOtherId')
const show = () => {
toggle.setAttribute('aria-expanded', true)
content.style.display = "block"
}
const hide = () => {
toggle.setAttribute('aria-expanded', false)
content.style.display = "none"
}
toggle.addEventListener('click', event => {
event.stopPropagation()
JSON.parse(toggle.getAttribute('aria-expanded')) ? hide() : show()
})
const handleClosure = event => !content.contains(event.target) && hide()
window.addEventListener('click', handleClosure)
window.addEventListener('focusin', handleClosure)
This can be done with multiple ways, One way to surround each dropdown with a container div and give it all same class, and then use querySelectorAll to select all the dropdowns, and loop throw, and on each dropdown, use querySelector to get the button and ul.
const dropdowns = document.querySelectorAll('.dropdown');
dropdowns.forEach(dropdown => {
const toggle = dropdown.querySelector('button')
const content = dropdown.querySelector('ul')
const show = () => {
toggle.setAttribute('aria-expanded', true)
content.style.display = "block"
}
const hide = () => {
toggle.setAttribute('aria-expanded', false)
content.style.display = "none"
}
toggle.addEventListener('click', event => {
event.stopPropagation()
JSON.parse(toggle.getAttribute('aria-expanded')) ? hide() : show()
})
const handleClosure = event => !content.contains(event.target) && hide()
window.addEventListener('click', handleClosure)
window.addEventListener('focusin', handleClosure)
})
<div class="dropdown">
<button aria-expanded="false">Edit</button>
<ul class="list-edit text-black" style="display: none; margin-top: 10px;">
<a href="#">
<li class="list-items-edit">Edit</li>
</a>
<a href="#" class="popup-with-zoom-anim">
<li class="list-items-edit">Unpublish</li>
</a>
<a href="#" class="popup-with-zoom-anim">
<li class="list-items-edit">Delete</li>
</a>
</ul>
</div>
<div class="dropdown">
<button aria-expanded="false">Add</button>
<ul class="list-edit text-black" style="display: none; margin-top: 10px;">
<a href="#">
<li class="list-items-edit">Add</li>
</a>
<a href="#" class="popup-with-zoom-anim">
<li class="list-items-edit">Unpublish</li>
</a>
<a href="#" class="popup-with-zoom-anim">
<li class="list-items-edit">Delete</li>
</a>
</ul>
</div>
My problems
Hi guys,
I am building a website that uses vuejs (vue, vuex, vue-router). Besides, I have used another template which is material dashboard (this template uses bootrsap4 and a custom js file).
At first, everything was very convenient, my vue-router worked perfectly. But when I checked the interface on my phone, there was a problem:
In this interface, the user menu in the upper right corner works with the vue-router normally, my page does not reload every time the url changes.
But when I changed the browser size, made it smaller and the user menu bar will now show as above. The problem now is that the links act like a normal anchor tag, and my page re-loads every time the URL changes.
Found the cause
This is the code of the user menu bar.
<!-- language-all: lang-html -->
<div class="collapse navbar-collapse justify-content-end">
<div class="navbar-form"></div>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link" href="javascript:;" id="navbarDropdownProfile" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="material-icons">person</i>
<p class="d-lg-none d-md-block">
Account
</p>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownProfile">
<router-link class="dropdown-item" to="/test">Profile</router-link>
<router-link class="dropdown-item" to="/another_one">Settings</router-link>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Log out</a>
</div>
</li>
</ul>
</div>
The problem is in the template's javascript custom file
They use a function that rewrites the menu bar whenever the window size gets smaller and
this may accidentally overwrite or disrupt the vue-router. The code is as follows:
initRightMenu: debounce(function() {
$sidebar_wrapper = $('.sidebar-wrapper');
if (!mobile_menu_initialized) {
$navbar = $('nav').find('.navbar-collapse').children('.navbar-nav');
mobile_menu_content = '';
nav_content = $navbar.html();
nav_content = '<ul class="nav navbar-nav nav-mobile-menu">' + nav_content + '</ul>';
navbar_form = $('nav').find('.navbar-form').get(0).outerHTML;
$sidebar_nav = $sidebar_wrapper.find(' > .nav');
// insert the navbar form before the sidebar list
$nav_content = $(nav_content);
$navbar_form = $(navbar_form);
$nav_content.insertBefore($sidebar_nav);
$navbar_form.insertBefore($nav_content);
$(".sidebar-wrapper .dropdown .dropdown-menu > li > a").click(function(event) {
event.stopPropagation();
});
// simulate resize so all the charts/maps will be redrawn
window.dispatchEvent(new Event('resize'));
mobile_menu_initialized = true;
} else {
if ($(window).width() > 991) {
// reset all the additions that we made for the sidebar wrapper only if the screen is bigger than 991px
$sidebar_wrapper.find('.navbar-form').remove();
$sidebar_wrapper.find('.nav-mobile-menu').remove();
mobile_menu_initialized = false;
}
}
}, 200),
What have I tried
I tried rewriting javascript in their js file as follows:
nav_content = $navbar.html(); // I turned this
// into this
nav_content = `<li class="nav-item dropdown">
<a class="nav-link" href="javascript:;" id="navbarDropdownProfile" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="material-icons">person</i>
<p class="d-lg-none d-md-block">
Account
</p>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownProfile">
<router-link class="dropdown-item" to="/test">Profile</router-link>
<router-link class="dropdown-item" to="another_one">Settings</router-link>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Log out</a>
</div>
</li>`
But it still does not work
To be more clear
I tried printing out nav_content, everything seems right. I think the problem is $nav_content = $(nav_content);
Is the problem I pointed out correct? If yes, I'd appreciate it if I got your advice, thank you.
I looked for similar posts but they all seem to resort to Jquery, which by now is considered outdated in most cases.
My question is:
How can I change the class of active (currently on my "Projects" labeled tag, to the siblings when clicking it?
I'm really trying to achieve results with only Vanilla Javascript and hopefully a .forEach method, to make it more functional.
Sorry for not providing any JS at this moment, it seems I'm really struggling with the basics .
<nav id="sidebar">
<div class="toggle-btn" onclick="toggleSidebar()">
<i class="fas fa-angle-double-right fa-2x"></i>
</div>
<ul class="navbar-nav">
<li class="nav-item">
PROJECTS
</li>
<li class="nav-item">
CREATION PROCESS
</li>
<li class="nav-item">
BEFORE AND AFTER
</li>
<li class="nav-item">
ABOUT THE STUDIO
</li>
<li class="nav-item">
CONTACT
</li>
</ul>
</nav>
EDIT:
Thanks to the first reply, I managed to figure out a clean and modern way to refactor it to my taste:
const tabs = document.querySelectorAll('.nav-link');
tabs.forEach(tab => tab.addEventListener('click', toggleActiveTab));
function toggleActiveTab(e) {
tabs.forEach(tab => {
tab.classList.remove('active');
});
e.currentTarget.classList.toggle('active');
}
Here you go with a solution
var items = document.getElementsByClassName('nav-link');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', printDetails);
}
function printDetails(e) {
for (var i = 0; i < items.length; i++) {
if (items[i].classList.contains("active")) {
items[i].classList.toggle("active")
}
}
this.classList.add("active");
}
.active {
background: #ddd;
}
<nav id="sidebar">
<div class="toggle-btn" onclick="toggleSidebar()">
<i class="fas fa-angle-double-right fa-2x"></i>
</div>
<ul class="navbar-nav">
<li class="nav-item">
PROJECTS
</li>
<li class="nav-item">
CREATION PROCESS
</li>
<li class="nav-item">
BEFORE AND AFTER
</li>
<li class="nav-item">
ABOUT THE STUDIO
</li>
<li class="nav-item">
CONTACT
</li>
</ul>
</nav>
Add an event listener to each nav-link if you can't add onClick event to HTML code.
Then in the click method check for active class in each nav-link element, if active class is present then toggle it.
Then add active class to the clicked tab.
It looks ridiculous to do so many tasks on a button click while every button should have its own events:
function allStories() {
$('#zero-md').hide();
$('.container-aboutme').hide();
$('.container-allstories').show();
$('.container-allstories').load("pages/allstories.html");
$("#home").removeClass("nav-link active").addClass("nav-link");
$("#aboutme").removeClass("nav-link active").addClass("nav-link");
$("#allposts").removeClass("nav-link").addClass("nav-link active");
}
function aboutMe() {
$('#zero-md').hide();
$('.container-allstories').hide();
$('.container-aboutme').show();
$('.container-aboutme').load("pages/about.html");
$("#home").removeClass("nav-link active").addClass("nav-link");
$("#allposts").removeClass("nav-link active").addClass("nav-link");
$("#aboutme").removeClass("nav-link").addClass("nav-link active");
}
<li class="nav-item">
<a class="nav-link" id="allposts" onclick="allStories()" href="#">All posts</a>
</li>
<li class="nav-item">
<a class="nav-link" id="aboutme" onclick="aboutMe()" href="#">About me</a>
</li>
Is there is a better, more effective way to organize such events with less code?
You mean this
$("#nav").on("click",".nav-link",function(e) {
e.preventDefault(); // stop the link
const id = this.id;
const $thisContainer = $('.container'+id);
$('#zero-md').hide();
$('.container').hide(); // hide all containers
$thisContainer.load("pages/"+id+".html",function() { // perhaps not load if already loaded
$thisContainer.fadeIn("slow");
}) ;
$(".nav-link").removeClass("active")
$(this).addClass("active")
})
<ul id="nav">
<li class="nav-item">
<a class="nav-link" id="allposts" href="#">All posts</a>
</li>
<li class="nav-item">
<a class="nav-link" id="about" href="#">About me</a>
</li>
</ul>
Yes. Try to keep your code DRY (don't repeat yourself.)
Add an event listener in your JS.
Use e.target to determine what was clicked.
Chain your commands together when they're operating on the same elements.
Don't remove a class and then add the same class back. Just remove the one you want to get rid of.
I've added some stand in elements since not everything was present in your HTML.
$('.nav-link').click( (e)=>{
let theLink = $(e.target).attr('id');
const container = '.container-'+$(theLink).attr('id');
$('#zero-md').hide();
$('.container').hide();
$(container).show().load("pages/"+theLink+".html");
alert('loading: pages/'+theLink+'.html');
$("#home").removeClass("nav-link active").addClass("nav-link");
$(".nav-link").removeClass("active");
$("#"+theLink).addClass("active");
});
.active {
font-size: 1.5rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<li class="nav-item">
<a class="nav-link" id="allstories" href="#">All posts</a>
</li>
<li class="nav-item">
<a class="nav-link" id="aboutme" href="#">About me</a>
</li>
<div class="container container-allstories">All Stories</div>
<div class="container container-aboutme">About Me</div>
<div id="zero-md">Zero MD</div>