I'm working on a basic dropdown element in HTML and jQuery and I'm trying to get better at understanding JavaScript and jQuery so this questions is a bit about code refactoring as well.
So here is what I've gotten so far:
HTML
<li class="nav-item">
<a class="nav-link" href="#">Foo</a>
<div class="subnav">
...
</div>
</li>
JavaScript
const navLink = $('.nav-link');
navLink.each(function () {
let $this = $(this);
$this.click(function (e) {
let hasSubnav = $this.parent().find('.subnav');
if(hasSubnav.length !== 0) {
e.preventDefault();
$this.toggleClass('dropdown-active');
}
hasSubnav.stop(true, true).slideToggle(200);
})
});
This solutions works fine. So what I want to do next is to check if another element in my loop is active, close is accordingly and then open the one I just clicked.
I thought about just putting a default click function before the each function like this:
navLink.click(function () {
$('.subnav').slideUp();
});
navLink.each(function () {
let $this = $(this);
$this.click(function (e) {
let hasSubnav = $this.parent().find('.subnav');
if(hasSubnav.length !== 0) {
e.preventDefault();
$this.toggleClass('dropdown-active');
}
hasSubnav.stop(true, true).slideDown(200);
})
});
But this does not seem to work. So my question is, is there a pretty way to achieve this maybe even inside of the each function? I've red about .not(this) in this post, which will maybe work (haven't tried it yet) but I thought that this would be duplicated code and that there might be a better way to get this to work.
Your code is now looping through every single nav-link and adding a click handler to them one by one. It is possible to remove the each loop, since you can just add a click handler to all nav-links at once.
All you have to do is add a click handler to the nav-link and then remove the active class and slide up all open dropdowns before executing your logic. See working code example below for reference:
// Collapse all initially
$(".subnav").slideUp();
// Add click handler to all nav-links
const navLink = $('.nav-link');
navLink.click(function(e) {
// Remove active classes on other elements & slide up
const otherLinks = navLink.not(this);
otherLinks.removeClass('dropdown-active');
otherLinks.parent().find('.subnav').slideUp();
// Slide down the subnav of selected element
let hasSubnav = $(this).parent().find('.subnav');
if (hasSubnav.length !== 0) {
e.preventDefault();
$(this).addClass('dropdown-active');
}
hasSubnav.stop(true, true).slideToggle(200);
})
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<li class="nav-item">
<a class="nav-link" href="#">Foo</a>
<div class="subnav">
Link1
Link2
Link3
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Foo</a>
<div class="subnav">
Link1
Link2
Link3
</div>
</li>
Related
I've run into the following problem. I made a landing page, made links to page sections through pure javascript, everything seems to be working, super. But, I wanted to add a regular <а></а> link and it turns out it doesn't work, I understand that the reason lies in event.preventDefault(); but if this is removed, then the main script breaks
document.querySelector('.nav').addEventListener("click", (event) => {
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');
}
});
<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">services</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="#footer">Кfooter</a>
<a class="nav__link" href="https://www.google.com/" target="_blank">Discord</a>
</nav>
</div>
If you remove event.preventDefault(); instead of going to it, it takes me to the top page. Yes, I understand that you can insert a link outside the .nav element, but I need it there. Help pls :)
What you need to do in you click handler is to prevent the execution of the code if the clicked link is not an in-page anchor. You could do this by checking that the href of the clicked <a> element is not equal to #. You could do this by modifying your handler as follows:
document.querySelector('.nav').addEventListener("click", (event) => {
if (event.target.getAttribute('href') !== '#') { return; }
/* rest of handler logic remains the same */
});
I have created a fiddle for reference.
I have this website: https://www.australianathleticscalendar.com.au/
I want to make it so you can have selected an 'Events' filter and 'State' filter at once (I.e. 100m and QLD at once), and the bubbles in each filter will have styling (I.e. like :focus). With each filter, select one category. When you change categories within a filter, the previous one selected must also be unstyled (I.e. can't select 100m and 200m at once. Moving from 100m to 200m would remove styling from 100m and add it to 200m).
The problem is with css is that you can only :focus one element at a time.
How can I achieve this using javascript?
I've pulled the relevant code into a codepen here.
This is the functions which draw the two filters:
function drawCategories() {
var template = Handlebars.compile(document.getElementById("menu-template").innerHTML);
console.log('draw ', this.products);
document.getElementById('menu-container').innerHTML = template(this.events);
}
function drawStates() {
var template = Handlebars.compile(document.getElementById("menu-template-2").innerHTML);
console.log('draw ', this.products);
document.getElementById('menu-container-states').innerHTML = template(this.states);
}
function showCategory(event) {
this.title = event;
drawProducts();
}
function showState(state) {
this.titleStates = state;
drawProducts();
}
And this is the HTML for the two filters:
<!-- Events filter -->
<div class="container">
<script id="menu-template" type="text/x-handlebars-template">
<ul class="navbar-nav">
{{#each this as |event|}}
<li class="nav-item"></li>
<a class="nav-link" href="#" onclick="showCategory('{{event.name}}');">{{event.name}}</a>
{{/each}}
<a class="navbar-brand hover-color" href="#" onclick="showAllEvents();">All Events</a>
</ul>
<ul class="navbar-nav ml-auto"></ul>
<li class="nav-item">
</li>
</ul>
</div>
</script>
</div>
<!-- States filter -->
<div class="container">
<script id="menu-template-2" type="text/x-handlebars-template">
<ul class="navbar-nav">
{{#each this as |state|}}
<li class="nav-item"></li>
<a class="nav-link nav-link-states" href="#" onclick="showState('{{state.name}}');">{{state.name}}</a>
{{/each}}
<a class="navbar-brand hover-color-states" href="#" onclick="showAllStates();">All States</a>
</ul>
<ul class="navbar-nav ml-auto"></ul>
<li class="nav-item">
</li>
</ul>
</div>
</script>
</div>
As often there are many ways to achieve this. I think the easiest might be to add an extra argument to the calls made in the click handlers -- passing this -- so the handler knows exactly which DOM element needs to get styling:
<a onclick="showCategory(this, '{{event.name}}');">
<a onclick="showAllEvents(this);">
<a onclick="showState(this, '{{state.name}}');">
<a onclick="showAllStates(this);">
Then in your code define a function that will apply a CSS class to a given DOM element, but also makes sure that it will be the only element having that class. Then use this utility function in the click handlers, which must now also take that extra argument:
// Utility function that ensures only one element has the given CSS class:
function selectElem(elem, className) {
let prevSelected = document.querySelector("." + className);
if (prevSelected) prevSelected.classList.remove(className);
elem.classList.add(className);
}
function showCategory(elem, event) { // <-- extra parameter, here and below...
selectElem(elem, "selectedEvent"); // <-- add this call, here and below...
this.title = event;
drawProducts();
}
function showAllEvents(elem) {
selectElem(elem, "selectedEvent");
this.title = "All Events";
drawProducts();
}
function showState(elem, state) {
selectElem(elem, "selectedState"); // <-- different style here and below
this.titleStates = state;
drawProducts();
}
function showAllStates(elem) {
selectElem(elem, "selectedState");
this.titleStates = "All States";
drawProducts();
}
Now it is up to you to define the style of two new CSS classes: selectedEvent and selectedState. You can give them the same definition with a comma:
.selectedState, .selectedEvent {
# Your styling ...
}
I have a UL on my page which is acting as navigation. In my footer I have some jQuery code so when I click the link it removes the active class on the li and then places it on the current li that has been clicked. This works as I click however when the page reloads the active class goes back onto the previous li.
Here is my code
jQuery(document).ready(function () {
"use strict";
// Init Demo JS
Demo.init();
// Init Theme Core
Core.init();
$('.sidebar-menu > li').click(function (e) {
$(".active").removeClass("active");
$(this).addClass("active");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav sidebar-menu">
<li class="sidebar-label pt20">Menu</li>
<li class="active">
<a href="/dashboard">
<span class="glyphicon glyphicon-home"></span>
<span class="sidebar-title">Dashboard</span>
</a>
</li>
<li>
<a href="/fixtures">
<span class="fa fa-calendar"></span>
<span class="sidebar-title">Fixtures</span>
</a>
</li>
<li>
<a href="/players">
<span class="glyphicon glyphicon-book"></span>
<span class="sidebar-title">Players</span>
</a>
</li>
</ul>
Am I missing something in my jQuery to keep the class on the desired li?
Your JS is losing context with the refresh.
What you can do is run another function on load to check which url you're on, and set active state based on that. Something like this:
var setDefaultActive = function() {
var path = window.location.pathname;
var element = $(".sidebar-menu a[href='" + path + "']");
element.addClass("active");
}
setDefaultActive()
My solution is based on this link.
I needed nav pills to remain active after page refresh.
You can probably format this to fit your needs.
$(document).ready(function () {
// On page load
$('a[data-toggle="pill"]').on('show.bs.tab', function (e) {
// Get the id for the pill that was last shown and set it to local storage
localStorage.setItem('activeLink', $(e.currentTarget).attr('id'));
});
// After page loaded
// Get the id for the pill from local storage
var activeLink = localStorage.getItem('activeLink');
// If it's there, click it
if (activeLink) {
$(`#${activeLink}`).click();
}
});
http://i.stack.imgur.com/Q9OMB.png
Here's a little problem of mine. Basically I'm doing a theme for Ghost CMS but I've ran into a problem that has been bugging me for a few hours now and I can't solve this myself and haven't found a source of same kind of problem from google/sof neither.
My goal is to make a active page styling with different styles per page(home being red, about being blue etc) using jquery as I couldn't accomplish it in Ghost itself because it wants to rotate a single loop with same styles for all links.
Jquery Code so far
$(function(){
$('a').each(function() {
if ($(this).prop('href') == window.location.href) {
$(this).addClass('homeCurrent');}
});
});
Relevant HTML of navbar
<ul class= "nav navbar-nav navbar-left">
<li id="home" class="home hvr-sweep-to-top-home ">Home</li>
<li id="about" class="about hvr-sweep-to-top-about">About</li>
</ul>
I've tried running different kinds of IF-statements with jquery but without success.
The logic of the code would go like :
if page is home = li style is homeCurrent
<ul class= "nav navbar-nav navbar-left">
<li id="home" class="home hvr-sweep-to-top-home homeCurrent">Home</li>
<li id="about" class="about hvr-sweep-to-top-about ">About</li>
</ul>
if page is about = li style is aboutCurrent
<ul class= "nav navbar-nav navbar-left">
<li id="home" class="home hvr-sweep-to-top-home">Home</li>
<li id="about" class="about hvr-sweep-to-top-about aboutCurrent">About</li>
</ul>
Anyone?
Hopefully I included everything relevant.
Honestly, I would avoid using window.location.href comparisons as it just screams potential for problems to me.
What I would personally do would be to add to the CONTENT of the about page:
$(document).ready(function () {
$('.about').addClass('aboutCurrent');
});
You add that code to each page you wish to have a xxxCurrent class added.
This avoids you having to dynamically do it as above which will run into problems depending on the url and browsers
Try with a switch statement:
$(function () {
$('a').each(function () {
var link = $(this).prop('href');
if (link == window.location.href) {
switch (link) {
case "home":
$(this).addClass('homeCurrent');
break;
case "about":
$(this).addClass('aboutCurrent');
break;
default:
$(this).addClass('nothingactive');
}
}
});
});
Edit*
If you really want to stick with this method of checking up the URL you could go for something like this:
$(function () {
$('a').each(function () {
var base = "http://www.yourdomain.com/";
var link = base + $(this).prop('href');
if (link == window.location.href) {
switch (link) {
case "http://www.yourdomain.com/home" :
$(this).addClass('homeCurrent');
break;
case "http://www.yourdomain.com/about" :
$(this).addClass('aboutCurrent');
break;
default:
$(this).addClass('nothingActive');
}
}
});
});
I have created multiple top down menu items. When the links are clicked a div slides down to show some content.
What I am trying to do with these links is toggle between them. When one div is opened an active state is added to the link, when it is closed the active state is removed and the div hidden. When you click between the links I have managed to get them to toggle between each other and the active state is added to the div that is opened.
What I cannot achieve is removing the active state and resetting some css.
Here is my Javascript:
//menu toggle
$(".trigger").click(function() {
var divToToggle = $( $(this).data('target') );
$(".toggle:visible").not(divToToggle).hide();
$(".trigger").not(divToToggle).removeClass('active');
$('.top-nav').css('margin-top', '20px');
divToToggle.slideToggle("fast", "linear");
$(this).toggleClass('active');
$('.top-nav').css('margin-top', '0px');
return false;
});
The .toggle class is on all the divs that are toggled:
<div class="account-container toggle hide"></div>
<div class="collections-container toggle hide"></div>
<div class="search-container toggle hide"></div>
The .trigger class is on all my links:
<ul class="top-nav">
<li><a class="hidden-tablet" href="">home </a></li>
<li><a class="hidden-tablet" href="">about us </a></li>
<li><a class="hidden-tablet" href="">where to buy </a></li>
<li><a class="hidden-tablet" href="">contact us </a></li>
<li class="tablet-menu visible-tablet"><a class="tablet-menu trigger" href="" data-target=".tablet-menu-container">tablet menu</a></li>
<li class="account"><a class="account trigger" href="" data-target=".account-container">account</a></li>
<li class="collection"><a class="collection trigger" href="" data-target=".collections-container">collections</a></li>
<li class="search"><a class="search trigger" href="" data-target=".search-container">search</a></li>
<li class="basket"><a class="basket trigger" href="" data-target=".home-basket-container">basket</a></li>
</ul>
It's hard to say where exactly your code is going wrong, but I've re-written it so it works slightly differently. Your click handler has to handle two possibilities: either we're clicking to hide an existing section, or we're clicking to switch to a new section. But we can also think of this as being two steps, with the second one optional: either we hide an existing section, or we hide an existing section and show a new one.
I've also switched to using ev.preventDefault() to stop the link from firing, named the jQuery variables so they start with $ (which makes them easier to differentiate). You can try it out on jsfiddle here.
$(document).ready(function () {
$(".trigger").click(function (ev) {
ev.preventDefault();
var $clickedLink = $(this);
var $divToToggle = $($(this).data('target'));
var isHideOnly = $clickedLink.hasClass('active');
// Hide the existing div and remove 'active' class.
$(".toggle:visible").hide();
$(".trigger").removeClass('active');
$('.top-nav').css('margin-top', '20px');
// If we're showing a new one, reveal it and set the active class on the link.
if (!isHideOnly) {
$divToToggle.slideToggle("fast", "linear");
$('.top-nav').css('margin-top', '0');
$clickedLink.addClass('active');
}
});
});