JQuery trouble understanding event order - javascript

I am trying to create a Bootstrap dropdown menu with two sub-menus. My problem is that the dropdowns are dynamically created after the document is loaded, and don't respond to how I would normally get them to work.
How I would normally go about it would be
$(document).ready(function(){
addItem(); //adds the first instance of the drop down
$('ul.dropdown-menu [data-toggle=dropdown]').on('click', function(e){
e.stopPropagation();
e.preventDefault();
$(this).siblings().removeClass("open");
$(this).parent().toggleClass("open");
});
});
But this obviously doesn't work for dynamically created content. I saw elsewhere online that this would work for dynamic content;
//still has first instance of dropdown, just later in the doc
$(document).on('click', 'ul.dropdown-menu [data-toggle=dropdown]', function(e){
e.stopPropagation();
e.preventDefault();
$(this).siblings().removeClass("open");
$(this).parent().toggleClass("open");
});
but for the life of me, I can't figure out why this doesn't work. What I'm seeing is that it is adding the open class to the sub-menu, but toggle the parent menu(not stopping propagation?) So, my question is, why doesn't the second one execute as expected, and how can I make it?
List structure for reference:
<div class="dropdown open">
<button id="select0" class="btn new-btn" data-toggle="dropdown" aria-expanded="true">Select Your Item <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="sub" data-toggle="dropdown">Item 1</a>
<ul class="dropdown-menu sub-menu">
<li><a data-def-cost="6">Item 1.1</a></li>
<li><a data-def-cost="8">Item 1.2</a></li>
</ul>
</li>
<li><a class="sub" data-toggle="dropdown">Item 2</a>
<ul class="dropdown-menu sub-menu">
<li><a data-def-cost="5">Item 2.1</a></li>
<li><a data-def-cost="3">Item 2.2</a></li>
</ul>
</li>
<li><a data-plu="xyz" data-def-cost="8.00">Item 3</a></li>
<li><a data-plu="xyz" data-def-cost="8.00">Item 4</a></li>
<li><a data-plu="xyz" data-def-cost="10.00">Item 5</a></li>
<li class="dropdown-header">Other Items</li>
<li><a data-plu="143">Item 6</a></li>
<li><a data-plu="xyz">Item 7</a></li>
<li><a data-plu="xyz">Item 8</a></li>
</ul>
<input type="text" class="hidden" name="input1">
<input type="text" class="hidden" name="otherinput1">
</div>

$(this).siblings() points to an ul element, however, the .open class is supposed to be added to the parent li : http://getbootstrap.com/javascript/#dropdowns-usage.
Via data attributes or JavaScript, the dropdown plugin toggles hidden content (dropdown menus) by toggling the .open class on the parent list item.
Here is a suggestion (inspired by the following code snippet : Multi-Level Dropdowns) :
$(document).ready(function () {
// this variable keeps track of the last open menus
// in order to close them when another link is clicked
// or when the entire dropdown is closed
var open;
$("#my-dropdown").on("hide.bs.dropdown", function () {
if (open) open.removeClass("open");
});
$(".dropdown-submenu > a").on("click", function (e) {
if (open) open.removeClass("open");
open = $(this).parents(".dropdown-submenu");
open.addClass("open");
e.stopPropagation();
e.preventDefault();
});
});
.dropdown-submenu {
position: relative;
}
.dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
margin-top: -1px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<div class="dropdown" id="my-dropdown">
<button type="button" data-toggle="dropdown">
Dropdown trigger
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>Action 1</li>
<li>Action 2</li>
<li class="dropdown-submenu">
Submenu 1
<ul class="dropdown-menu">
<li>Action 1.1</li>
<li>Action 1.2</li>
</ul>
</li>
<li class="dropdown-submenu">
Submenu 2
<ul class="dropdown-menu">
<li>Action 2.1</li>
<li>Action 2.2</li>
<li class="dropdown-submenu">
Submenu 2.3
<ul class="dropdown-menu">
<li>Action 2.3.1</li>
<li>Action 2.3.2</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>

Unsure as to efficiency of my solution, but I rearranged the order a bit, and forced the parent to stay open
$(document).on('click', 'ul.dropdown-menu [data-toggle=dropdown]', function(e){
$(this).siblings().removeClass("open");
$(this).parent().addClass("open");
$(this).parent().parent().parent().addClass("open");
e.stopPropagation();
e.preventDefault();
});
and this solved my issue

use one of these below lines.
$(this).siblings('.dropdown-menu').removeClass('open');
$(this).next('.dropdown-menu').removeClass('open');

Related

Can't open dropdown menu using querySelectorAll

I have a mobile navigation with two dropdown menus. Here is the markup of the nav:
<div id="mobile-menu" class="mobile-menu container fixed">
<ul>
<li>Home</li>
<li class="dropdown">
Articles <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Submenu item 1</li>
<li>Submenu item 2</li>
</ul>
</li>
<li>Contact</li>
<li class="dropdown">
My account <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Dashboard</li>
<li>Profile</li>
</ul>
</li>
</ul>
</div>
The dropdown menus should open/expand when clicked. Originally I grabbed the dropdown and submnenu classes like this:
const mobileDropdown = document.querySelector(".dropdown");
const mobileSubMenu = document.querySelector('.submenu');
and used an eventlistener to toggle the "hidden" class which is just a display:none
mobileDropdown.addEventListener('click', () => {
mobileSubMenu.classList.toggle('hidden');
});
The problem with this is that this will only open the first dropdown menu and I cannot open the second.
When I try to use querySelectorAll instead of just querySelector then i get thiserror:
Uncaught typeError addEventListener is not a function
Here I read that with querySelectorAll I need to use a for or foreach loop.
but i think I'm messing it up. I tried this:
const mobileDropdown = document.querySelectorAll(".dropdown");
const mobileSubMenu = document.querySelector('.submenu');
mobileDropdown.forEach(md => md.addEventListener('click', () => {
mobileSubMenu.classList.toggle('hidden');
}));
Now I don't get an error, I can open the first dropdown menu, but when I try to open the second dropdown menu, the first one opens. What am I doing wrong? Thanks in advance.
The problem in your code is that each mobileDropdown's clickEvent was linked to the first submenu, you should link mobileDropdown's clickEvents to their submenu children like that
document.querySelectorAll(".dropdown").forEach(md => md.addEventListener('click', () => {
md.querySelector(".submenu").classList.toggle('hidden');
}));
.hidden {
visibility: hidden;
}
<div class="mobile-menu container fixed" id="mobile-menu">
<ul>
<li>Home</li>
<li class="dropdown">
Articles <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Submenu item 1</li>
<li>Submenu item 2</li>
</ul>
</li>
<li>Contact</li>
<li class="dropdown">
My account <i class="bi bi-chevron-down"></i>
<ul class="submenu hidden">
<li>Dashboard</li>
<li>Profile</li>
</ul>
</li>
</ul>
</div>
here is a solution. You have to listen to the 'click' event for "submenu" on the element of "dropdown".
const mobileDropdown = document.querySelectorAll(".dropdown");
mobileDropdown.forEach((md) => {
md.addEventListener("click", () => {
const mobileSubMenu = md.querySelector(".submenu");
mobileSubMenu.classList.toggle("hidden");
});
});
mobileSubMenu always the first!

Is there a way to expand the dropdown menu content upwards?

I would like to use an accordion inside a bootstrap dropdown with up direction ("dropup") and make the menu expand upwards.
I managed to setup the "accordion" inside the dropdown as you can see in the code example.
Here is the html
<div class="dropup" id="dropdownUp">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="btnGroup"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Dropdown
</button>
<div class="dropdown-menu" aria-labelledby="btnGroup" id="dmenu">
<a class="dropdown-item keepopen" href="#c_1" data-toggle="collapse">Category 1</a>
<ul class="collapse" id="c_1" data-parent="#dmenu">
<li>Child 1-1</li>
<li>Child 1-2</li>
<li>Child 1-3</li>
</ul>
<a class="dropdown-item keepopen" href="#c_2" data-toggle="collapse">Category 2</a>
<ul class="collapse" id="c_2" data-parent="#dmenu">
<li>Child 2-1</li>
<li>Child 2-2</li>
</ul>
<a class="dropdown-item keepopen" href="#c_3" data-toggle="collapse">Category 3</a>
<ul class="collapse" id="c_3" data-parent="#dmenu">
<li>Child 3-1</li>
</ul>
</div>
</div>
And here is the javascript
$(document).ready(function() {
$('#dropdownUp').on('hide.bs.dropdown', function(e) {
if ($(e.clickEvent.target).hasClass('keepopen')) {
return false;
}
return true;
});
$('#dropdownUp').on('hidden.bs.dropdown', function() {
$('.dropdown-menu .collapse.show').collapse('hide');
});
});
Here the link to the example
https://jsbin.com/cirejudipu/edit?html,js,output
The problem is, when the collapse expands, the dropdown menu goes over the button. Is there a way to make the accordion and the dropdown menu expand upwards? Thank you.
you could use the transform/rotate CSS property to force the element to turn upside down. you may need to use the same property on the contents to flip them right side up. I've used this on animated CSS/JS apps as well as on flipping entire canvas projects sideways.
here is the line of CSS
transform: rotate(180deg);
let me know if that helps

Make parent menu link toggle submenu link

I have a menu which I have working fine but I need a small change and that is I need the parent menu to toggle the submenu, currently if you click the parent menu the submenu appears but I need it so when you click the parent menu again it closes the sub menu.
You can see the menu in action here.
and this is the javascript that is for the menu:
$('.dropdown-toggle').click(function() {
$('.dropdown').css('display', 'none'); // Hide submenus when other submenus are clicked
$(this).next('.dropdown').toggle();
});
$(document).click(function(e) {
var target = e.target;
if (!$(target).is('.dropdown-toggle') && !$(target).parents().is('.dropdown-toggle')) {
$('.dropdown').hide();
}
});
This is the menu html
<nav class="main">
<a class="dropdown-toggle" href="#" title="Menu">Menu One</a>
<ul class="dropdown">
<li>Menu Item</li>
<li>Menu</li>
<li>Settings</li>
<li>Search</li>
</ul>
</nav>
Replace this code:
$('.dropdown-toggle').click(function() {
$('.dropdown').css('display', 'none'); // Hide submenus when other submenus are clicked
$(this).next('.dropdown').toggle();
});
With the code below:
$('.dropdown-toggle').click(function() {
var $currentDropdown = $(this).next('.dropdown');
$currentDropdown.siblings('.dropdown').not($currentDropdown).removeClass('toggled');
$currentDropdown.siblings('.dropdown').not($currentDropdown).hide();
$currentDropdown.toggleClass('toggled');
$currentDropdown.toggle();
});
That should do it.
I think this is the simplier way :)
Hope you helped
$(".dropdown").css('display', 'none');
$('.dropdown-toggle').click(function(e) {
if ($(this).next().is(":visible")){
$(this).next().hide();
}else{
$(".dropdown").hide();
$(this).next().show();
}
});
a {
display: block;
}
<script src="https://code.jquery.com/jquery-3.1.0.js"></script><nav class="main">
<nav>
<a class="dropdown-toggle" href="#" title="Menu">Menu One</a>
<ul class="dropdown">
<li>Menu Item</li>
<li>Menu</li>
<li>Settings</li>
<li>Search</li>
</ul>
<a class="dropdown-toggle" href="#" title="Menu">Menu One</a>
<ul class="dropdown">
<li>Menu Item</li>
<li>Menu</li>
<li>Settings</li>
<li>Search</li>
</ul>
</nav>
I think this code will be enough.
$('.dropdown-toggle').click(function() {
$(this).next('.dropdown').toggle();
});
Codepen Example
This can be done easily with Bootstrap:
<li class="submenu">
<i class="la la-user"></i>
<span> Main menue </span>
<span class="menu-arrow"></span>
<ul style="display: none;">
<li>sub menu 1</li>
<li> sub menu 2 </li>
<li> sub menu 3 </li>
</ul>
</li>

Accordion menu with rotating arrows

I have created a simple accordion with rotating arrows on click. It works fine with one exception :
I have a few collapsible item and if I click the first one it works ok. But clicking the other one do not rotate the previous icon to the default state.
How to make arrow to come back to the default state when clicking the other collapsible item?
$(function() {
$('.arrow-r').on('click', function() {
$(this).find('.fa-angle-down').toggleClass('rotate-element');
})
})
.rotate-element {
#include transform (rotate(180deg));
}
.fa-angle-down {
&.rotate-icon {
position: absolute;
right: 0;
top: 17px;
#include transition (all 150ms ease-in 0s);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="collapsible collapsible-accordion">
<li><a class="collapsible-header arrow-r"> Menu 1<i class="fa fa-angle-down rotate-icon"></i></a>
<div class="collapsible-body">
<ul>
<li><a>Item 1</a>
</li>
<li><a>Item 2</a>
</li>
</ul>
</div>
</li>
<li><a class="collapsible-header arrow-r"> Menu 2<i class="fa fa-angle-down rotate-icon"></i></a>
<div class="collapsible-body">
<ul>
<li><a>Item 1</a>
</li>
<li><a>Item 2</a>
</li>
</ul>
</div>
</li>
<li><a class="collapsible-header"> Menu 3</a>
<div class="collapsible-body">
<ul>
<li><a>Item 1</a>
</li>
<li><a>Item 2</a>
</li>
</ul>
</div>
</li>
Please try this:
$(function() {
$('.arrow-r').on('click', function() {
//Reset all but the current
$('.arrow-r').not(this).find('.fa-angle-down').removeClass('rotate-element');
//Rotate/reset the clicked one
$(this).find('.fa-angle-down').toggleClass('rotate-element');
})
})
If it helps other folks; this combination worked for me: (it's almost identical except I added the ".arrow-r" after the firs .find)
I had 4 main Accordions with multiple Accordions inside, but I only wanted the main arrow on each of the main accordions to animate. (this combination stopped all the inner accordions from triggering the main animation)
$(function() {
$('.arrow-r').on('click', function() {
//Reset all but the current
$('.arrow-r').not(this).find('.arrow-r').removeClass('rotate-element');
//Rotate/reset the clicked one
$(this).find('.fa-angle-down').toggleClass('rotate-element');
})
})
This is my actual code if it helps to see the pattern:
$(function() {
$('.accroordlite').on('click', function() {
//Reset all but the current
$('.accroordlite').not(this).find('.accroordlite').removeClass('down');
//Rotate/reset the clicked one
$(this).find('.spinOnClick').toggleClass('down');
})
})

How to refactor this JQuery code so that it follows the DRY principle

See. I have the following html <ul>-<li> lists here.
<ul class="nav">
<li class="active">Home</li>
<li class="dropdown">
About Us<b class="caret"></b>
<ul class="dropdown-menu">
<li>Who we are?</li>
<li>What we stand for?</li>
</ul>
</li>
<li class="dropdown">
Campaigns<b class="caret"></b>
<ul class="dropdown-menu">
<li>Get Involved</li>
</ul>
</li>
<li>News</li>
<li>Donate</li>
</ul>
Then I have the following jquery code
$("#about-us").click(function(){
$("ul.nav").children("li").children("a").css("background-color", "#0E0E0E");
$(this).css("background-color","#47F514");
});
$("#campaigns").click(function(){
$("ul.nav").children("li").children("a").css("background-color", "#0E0E0E");
$(this).css("background-color","#F2720A");
});
$("#news").click(function(){
$("ul.nav").children("li").children("a").css("background-color", "#0E0E0E");
$(this).css("background-color","#0A76F2");
});
$("#donate").click(function(){
$("ul.nav").children("li").children("a").css("background-color", "#0E0E0E");
$(this).css("background-color","#F7A116");
});
I can see that line $("ul.nav").children("li").children("a").css("background-color", "#0E0E0E") is repeated in many lines. So surely I can take this out and put a new function in each click events to make this same calls this many times.
What should I do to refactor this?
You can do it this way :
var colors_array_by_id = { "about-us" : "#47F514", "campaigns" : "#F2720A", "news" : "#0A76F2", "donate" : "#F7A116" };
$("#about-us, #campaigns, #news, #donate").click(function(){
$("ul.nav > li > a").css("background-color", "#0E0E0E");
$(this).css("background-color", colors_array_by_id[$(this).attr('id')]);
});
The only thing that changes is the color depending of the clicked element. So, I created here an associative array containing id-color couples.
Here's how I would do it -
<ul class="nav">
<li class="active">Home</li>
<li class="dropdown">
About Us<b class="caret"></b>
<ul class="dropdown-menu">
<li>Who we are?</li>
<li>What we stand for?</li>
</ul>
</li>
<li class="dropdown">
Campaigns<b class="caret"></b>
<ul class="dropdown-menu">
<li>Get Involved</li>
</ul>
</li>
<li><a href="#" id="news" class="navLinks" data-bgcolor="#0A76F2" >News</a></li>
<li><a href="#" id="donate" class="navLinks" data-bgcolor="#F7A116" >Donate</a></li>
</ul>
I've added a data attribute to the links called data-bgcolor which has the color value. Next I attach a single click handler to all the links.
$("#about-us, #campaigns, #news, #donate").click(function(){
$("ul.nav").children("li").children("a").css("background-color", "#0E0E0E");
$(this).css("background-color", $(this).attr("data-bgcolor"));
});
You could do this,
In css,
.myClass{ background-color: #OEOEOE;}
on load,
var elm = $("ul.nav").children("li").children("a");
and then,
$("#about-us, #campaigns, #news, #donate").click(function(){
$this = $(this);
elm.addClass("myClass");
$this.css("background-color", $this.data("bgcolor"));
});
You can use the .data() instead of .attr().

Categories

Resources