Dropdowm menu in Vanilla JavaScript - for unknown number of buttons - javascript

please, can you give me a hint with Vanilla JS?
I have dropdown menu opening via button click and have 2 issues:
1) Dropdown is opening by clicking on button with unique ID. I need to get it working on Class name, because it have to be working on multiple buttons - and the number of them is unknown (they will load from REST API).
In jQuery is it working, but I need it in Vanilla JS.
If I try to select button by Class name, it will return array of buttons, but I don't know how to select from array, which button was being clicked on.
2) Dropdown menu is opening only on the second click on the button (and then it is toggling like it should), but the first click doesn't do anything.
My code is here:
// select Button - now by ID - but I need unknown number of buttons - from REST API - and the code working for all of them
var btn = document.getElementById("dropBtn1");
// select Dropdown menu - next to the button - to be sure it will open the right menu no matter which button will be pressed
var menu = btn.nextSibling;
while(menu && menu.nodeType != 1) {
menu = menu.nextSibling
}
//toggle dropdown menu open/close
btn.addEventListener("click", function() {
if (menu.style.display == 'none') {
menu.style.display = 'block';
}
else {
menu.style.display = 'none';
}
});
And working prototype is here on Codepen:
https://codepen.io/vlastapolach/pen/EXdLMy
Please, do you have any ideas how to fix this?
Thank you!

Quite easy, you need a general function that works on the context (=this):
//toggle dropdown menu open/close
function toggle() {
var btn=this;
var menu = btn.nextSibling;
while(menu && menu.nodeType != 1) {
menu = menu.nextSibling
}
if(!menu) return;
if (menu.style.display != 'block') {//fix 2)
menu.style.display = 'block';
} else {
menu.style.display = 'none';
}
});
Now you can assign this functions as an event handler onto all your elements:
window.addEventListener("DOMContentLoaded",function(){
document.querySelectorAll(".sth").forEach(function(btn){
btn.addEventListener("click",toggle,true);
});
});
Note that NodeList.forEach is quite new, may use [].slice to create a real array...
And you need to assign the Handlers to the newly added elements manually, or you need to listen on window and trace back the target:
window.onclick=function(event){
if(event.target.classList.contains("sth")){
toggle.call(event.target);
}
};

Related

Button clicked does its function only once when it needs to do it for each item

I apologize upfront if this problem is simple, I know I almost get the answer but I just ran out of ideas how to do it. I'll leave a link to the glitch project (copy pasted it there) so you can have a better visual understanding of what I'd like to do.
Here's the link: https://glitch.com/edit/#!/skitter-aftermath?path=index.html:9:7
Basically, the Add Todo button, does shows the "Added todo item!" notice, but it does so on every second added item ( I have it on .toggle, because I couldn't find a solution for .add & .remove to work both), while the Delete todo button, shows its notice only on one delete of a todo item, on every other deleted item it doesnt shows. Thanks in advance!
P.S. Here's the part of the code thats included in the above link that refers to the notices:
//Show an added notice when clicked Add Todo
createAddedNotice:function(){
var itemAdded = document.getElementById("added");
itemAdded.classList.toggle("addedShow");
},
//Show completed notice when the list item is toggled as completed
createCompletedNotice: function(){
var itemCompleted = document.getElementById("completed");
itemCompleted.classList.toggle("finished");
},
//Show deleted notice when the list item is deleted
createDeletedNotice: function(){
var itemDeleted = document.getElementById("deleted");
itemDeleted.classList.add("deletedShow");
},
//Show toggled all notice when all list items are toggled by pressing TOGGLE ALL button
createToggledAllNotice:function(){
var allCompleted = document.getElementById("allCompleted");
allCompleted.classList.toggle("allCompletedShow")
},
//Show deleted all notice when all list items are toggled by pressing DELETE ALL button
createDeletedAllNotice: function(){
var allDeleted = document.getElementById("allDeleted");
allDeleted.classList.toggle("allDeletedShow");
},
//
setUpEventListeners: function(){
var todosUl = document.querySelector("ul");
todosUl.addEventListener("click", function(event){
var elementClicked = event.target;
if(elementClicked.className === "deleteBtn"){
handlers.deleteTodo(parseInt(elementClicked.parentNode.id));
}
//If the delete button is clicked, show the deleted notice
if(elementClicked.className === "deleteBtn"){
view.createDeletedNotice();
}
if (elementClicked.className === "toggleBtn") {
todoList.toggleCompleted(parseInt(elementClicked.parentNode.id));
var itemCompleted = document.getElementById("completed");
itemCompleted.classList.remove("finished");
view.displayTodos();
}
//If the completed button is clicked then show the completed notice
if( elementClicked.className === "toggleBtn"){
view.createCompletedNotice();
}
//If addTodo button is clicked, then remove the class (?)
});
This is a problem with CSS animation's, you basically need to retrigger the animation, you can do this by removing the class and then adding it. But this does not work unless you have a sleep in between.
But there is a trick, you can cause a document reflow by asking the element it's offsetWidth.
Here is a example below.
createAddedNotice:function(){
var itemAdded = document.getElementById("added");
itemAdded.classList.remove("addedShow");
itemAdded.offsetWidth; //document reflow
itemAdded.classList.add("addedShow");
},
ps. The reason you toggle worked every second attempt, is because the first toggle would go on, you next one off, and then on again causing animation to restart..

Make menu parent expand if child is currently active

I'm currently attempting to make a menu which is expandable when clicked, stays open, until a child is clicked, then user should be taken to that page, and the menu should still be expanded when that site is reached. (currently it collapses).
I have currently got the part down where i have a menu that expands and show childs when clicked. It's currently made in javascript. I'm making the menu with wordpress, and wordpress automatically adds class "current-menu-item" to the li which is being visited (if that helps).
var clickMenu = function() {
var getEls = document.getElementById("menu-shopmenu").getElementsByTagName("LI");
var getAgn = getEls;
for (var i=0; i<getEls.length; i++) {
getEls[i].onclick=function() {
for (var x=0; x<getAgn.length; x++) {
getAgn[x].className=getAgn[x].className.replace("unclick", "");
getAgn[x].className=getAgn[x].className.replace("click", "unclick");
}
if ((this.className.indexOf('unclick'))!=-1) {
this.className=this.className.replace("unclick", "");;
}
else {
this.className+=" click";
}
}
getEls[i].onmouseover=function() {
this.className+=" hover";
}
getEls[i].onmouseout=function() {
this.className=this.className.replace("hover", "");
}
}
}
window.addEventListener("load", clickMenu);
Now I'm just looking for help making it stay expanded once new page is loaded.
Also, will these two functions make a conflict in the design?
You can just check 'parent' of current-menu-item element. And apply display:block or any other css which makes menu element visible.
check below code if it meets your requirements.
$(document).ready(function(){
$(".current-menu-item").parent("your_parent_ul_div").css("display": "block");
});
ref-https://api.jquery.com/parent/
ref->Expand parent menu if child menu is selected
ref->https://webdesignerhut.com/active-class-navigation-menu/

Toggle visibility for multiple divs with one function: navigation bar

My Assignment: Hi! I am doing an assignment in school where I am supposed write code in Javascript in order to toggle visibility for the submenus each belonging to their own topmenu in a navigation bar for a webpage. The visibility should be set to hidden by default and should be shown when a topmenu is clicked on.
I know how to toggle visibility for ONE submenu belonging to a topmenu, but fail to make my code work for multiple elements. My HTML-code:
<a class="left_top1" onclick = "toggle()">Opinion</a><br>
<div class="left_submenu_1" style="display: none;">
<a class="left_sub1">Leaders</a><br>
<a class="left_sub1">Debates</a><br>
</div>
<br>
<a class="left_top2" onclick = "toggle()">Economy</a><br>
<div class="left_submenu_2" style="display: none;">
<a class="left_sub2">News</a><br>
<a class="left_sub2">Your Economy</a><br>
</div>
My Problem: The topmenus I speak of are "Opinion" and "Economy". The visibility of the div with the class "left_submenu_1" should be toggled when you click the topmenu "left_top1". Thus should the visibilily of the div with the class "left_submenu_2" be toggled when you click the topmenu "left_top2". This is what I fail to do. My Javascript code is so far:
function toggle() {
var e = document.querySelectorAll("div.left_submenu_1, div.left_submenu_2");
for (var i=0; i < e.length; i++) { // I know this will enable/disable the visibility for ALL elements selected from the querySelectorAll, which should NOT happen
if(e[i].style.display == "none") {
e[i].style.display = "block";
} else if(e[i].style.display == "block") {
e[i].style.display = "none";
}
}
}
Anyone who knows how to solve this issue of mine? I know there are errors in the for-loop (as I wrote next to it), but this is the best I can manage for now.
Please note: We are NOT allowed to use jQuery or to give the topmenus id:s, as the idea is to use one general function to toggle the visibility. Furthermore, the code which enables the toggle-function should be done in Javascript.
I would approach it by passing the class name of the div to be shown (or hidden) into the function to begin with.
HTML
<a class="left_top1" onclick = "toggle('.left_submenu_1')">Opinion</a>
Then in the function you can grab the element and toggle it's display state.
JavaScript
function toggle(qs) {
var e = document.querySelector(qs);
e.style.display = e.style.display === 'block' ? 'none' : 'block';
}
The e.style.display === 'block' ? 'none' : 'block' part is saying if the elements display state is equal to block, return none, otherwise return block.
The return value is set as the new element display state due to the e.style.display = beforehand.
Tring to make it work modifying it as less as possible :
- use onClick="toggle(this)" in the anchors tags
- use a bit different toggle function like:
function toggle (el) {
var e = document.querySelectorAll('.' + el.className.replace('top', 'submenu_'))[0];
e.style.display = e.style.display.match(/none/) ? '' : 'none';
}
hope it helps, but I have to suggest You to make a small step forward and search for event delegation. Bye

Remove Class when current toggle is clicked

The title is a bit of a tongue twister. A brief description of the fiddle, is that it's a toggle style accordion where the toggle state changes color when one of the divs is toggled. I've got it working to where if another div is toggled it will close that previous div and open the new div while changing the toggle state.
The issue I am running into is if a user wants to close the current toggle without clicking a different div it will close the current toggle but not change the toggle state back to it's original state. I am currently using this and have tried multiple things including if the container 'is: visible' or hasClass then to remove the toggle class, but nothing seems to work. I've also tried a different slideToggle function, but of course that applied it to the toggled element I've found.
Fiddle: http://jsfiddle.net/NFTFw/1256/
What I am trying to do?
I want the current toggle class to change back to its original state if the user clicks the current toggled div or clicks another div. So essentially I want the user to have either option.
CODE:
$(document).ready(function () {
$('.column').each(function (index) {
$(this).delay(750 * index).fadeIn(1500);
});
$('.column').hide();
$('.body').hide();
$('.column').each(function () {
var $toggle = $(this);
$('.toggle', $toggle).click(function () {
$(".toggle").removeClass("toggle-d");
$(this).addClass('toggle-d');
$body = $('.body', $toggle);
$body.slideToggle();
$('.body').not($body).hide();
});
});
});
Check to see if the thing that you're clicking already has the class. If so, remove it, if not, add it. I suspect the problem you were having with hasClass() is that you were attempting to check the wrong this.
Oooh I did a bad thing and didn't remove the class when a new div was clicked. I've fixed that and updated the jsfiddle
jsfiddle
js:
$(document).ready(function () {
$('.column').each(function (index) {
$(this).delay(750 * index).fadeIn(1500);
});
$('.column').hide();
var width = $(window).width();
if (width <= 600) {
$('.body').hide();
$('.column').each(function () {
var $toggle = $(this);
$('.toggle', $toggle).click(function () {
if($(this).hasClass('toggle-d')){
$(this).removeClass("toggle-d");
}
else{
$('.toggle').removeClass('toggle-d');
$(this).addClass('toggle-d');
}
$body = $('.body', $toggle);
$body.slideToggle();
$('.body').not($body).hide();
});
});
}
});
What i would suggest is to pass the element itself in the function
in the index.html Do this
<a class = 'classname' onclick = toggle(this)>
Your Content Here
</a>
After that in the script.js
what i am saying is in javascript, i believe you can easily convert it to jquery
function toggle(value){
if(value.className == 'the predefined value'){
value.className = value.className + ' Your new class addition'
// remember there should be a space if you are adding an additional class to the present class, else directly change the classname
}
else{
value.className = 'the predefined value'
}}
this will toggle your classname whenever the element is clicked

more concise jquery for multi button controlling visibility of same div

I have 3 buttons that control the visibility of 1 div.
we want to do following to div:
show the first time any of three buttons are clicked
show if button clicked is different to previously button click
hide if button clicked is the same as previous clicked and if div is currently visible
show if button clicked is the same as previous clicked and if div is currently invisible
currently I have this:
//$('#alert_area') = target div
$button = $('.button')
if ($button.attr('id') != $('#alert_area').attr('showing')){
$('#alert_area').show()
}else{
if ($('#alert_area').is(":visible")){
$('#alert_area').hide();
}else{
$('#alert_area').show();
}
}
$('#alert_area').attr('showing', $button.attr('id'))
It's only a slight improvement, but you can replace your else block with toggle. You can also cache your selector to neaten things up.
var $button = $('.button'), $alertArea = $("#alert_area");
if ($button.attr('id') != $alertArea.attr('showing')) {
$alertArea.show()
} else {
$alertArea.toggle();
}
$alertArea.attr('showing', $button.attr('id'));

Categories