Submenu close function not working properly using Javascript - javascript

I have a function that is semi-working to get a submenu to close when a user clicks anywhere else outside of the box. The function is selecting a class of sub.menu-open. If the event target does not equal the sub.menu-open and its parentNodes, (the elements inside the box), the function should execute "submenu-.open.style.display = "none". So, this works for a few clicks then it stops. The console reads Null after the first click. What I believe is that in my HTML there is no initial set class of sub.menu-open, which is why its saying Null at first. I add the submenu-open class with javascript. That is how I get the submenu to open in the first place. Is this possible to fix?
arrowButton.forEach((el) =>
el.addEventListener("click", (event) => {
const subMenu = event.target.parentElement.querySelector(".sub-menu");
subMenu.classList.toggle("open");
})
);
-opening the submenu-
window.addEventListener("mouseup", (event) => {
let box = document.querySelector(".sub-menu.open");
if (event.target != box && event.target.parentNode != box) {
box.style.display = "none";
}
});
--closing the submenu--
.sub-menu {
display: none;
}
.sub-menu.open {
position: absolute;
width: 10vw;
background-color: white;
color: black;
text-align: left;
display: flex;
flex-direction: column;
margin: 6px auto;
padding-left: 0.3rem;
border-radius: 5px;
#include mobile {
display: flex;
flex-direction: column;
background-color: $footer-text;
width: 60vw;
margin: 0 auto;
color: $darkblue-headingtext;
border-radius: 5px;
position: relative;
left: 5px;
text-align: center;
}
}
<li class="parent">
Product
<img
class="menu-arrow mobile"
src="images/icon-arrow-dark.svg"
alt="arrow"
/>
<img
class="menu-arrow desktop"
src="./images/icon-arrow-light.svg"
alt="arrow"
/>
<ul class="sub-menu">
<li>Overview</li>
<li>Pricing</li>
<li>Marketplace</li>
<li>Features</li>
<li>Integrations</li>
</ul>
</li>
Any tips are greatly appreciated!

Related

How can I make it so only the tab area loads instead of the page jumping to the top after clicking on tab?

As the title states, I'm making a tabbed section to switch content upon click which works fine, how can I make it so upon clicking a new tab it has a smooth transition to the content as well as prevent jumping to the top of the page every time I click a tab?
I've tried adding the function which prevents it for links but this isn't a link so that doesn't seem to be working.
HTML
<section class="featured-books">
<div class="featured-books-title"><h2>Featured Books</h2></div>
<ul class="tabs">
<li data-tab-target="#featured" class="active tab">Featured</li>
<li data-tab-target="#on-sale" class="tab">On Sale</li>
<li data-tab-target="#most-viewed" class="tab">Most Viewed</li>
</ul>
<div id="featured" data-tab-content class="active">
<div class="featured-tab">
<img src="./images/12-rules.jpg">
<img src="./images/7-habits.jpg">
<img src="./images/art-of-war.jpg">
<img src="./images/boundaries.jpg">
<img src="./images/unlimited-memory.jpg">
<img src="./images/meaning-of-marriage.jpg">
<img src="./images/meditations.jpg">
<img src="./images/peaceful-parents.jpg">
<img src="./images/plant-paradox.jpg">
<img src="./images/spirit-filled-life.jpg">
<img src="./images/javascript-definitive-guide.jpg">
<img src="./images/atomic-habits.jpg">
</div>
</div>
<div id="on-sale" data-tab-content>
</div>
<div id="most-viewed" data-tab-content>
</div>
</section>
CSS
.featured-books h1 {
display: flex;
justify-content: center;
}
[data-tab-content] {
display: none;
}
.active[data-tab-content] {
display: block;
}
.tabs {
display: flex;
justify-content: center;
list-style-type: none;
margin: 0;
padding-bottom: 60px;
padding-top: 16px;
}
.tab {
border-radius: 20px;
cursor: pointer;
padding: 10px;
}
.tab.active {
background-color: #CCC;
}
.tab:hover {
background-color: #aaa;
}
/**------FEATURED TAB CONTENT------*/
.featured-tab {
position: absolute;
justify-content: center;
align-items: center;
margin-top: 10px;
width: 100vw;
display: grid;
grid-template-columns: repeat(auto-fill,minmax(300px,300px));
column-gap: 3px;
row-gap: 40px;
}
.featured-tab img {
width: 180px;
height: auto;
object-fit: cover;
object-position: center;
}
JavaScript
const tabContents = document.querySelectorAll('[data-tab-content]')
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const target = document.querySelector(tab.dataset.tabTarget)
tabContents.forEach(tabContent => {
tabContent.classList.remove('active')
})
tabs.forEach(tab => {
tab.classList.remove('active')
})
tab.classList.add('active')
target.classList.add('active')
})
})
Here is a simple example using a opacity transition but you can use height, width or transform if you would like. I use aria-attributes to keep track of things like which article is open and if the information in the article should be picked up by screen readers. The two most important CSS classes are show and hide. These control the opacity and when the transition takes place. Show has a slight delay so it waits for the one being hidden to get out of the way. As far as the JavaScript.
Select all the buttons that have popups.
Create a event listener to handle the click.
Select the controlled article and all the articles.
Check if the controlled article is currently hidden.
If it is hide all the artiles.
Change all the buttons aria-expanded attributes to false.
Set the aria-expanded attribute on the clicked button to true.
Set aria-hidden class on the controlled article to false.
Remove the hide class and add the show class to the controlled article.
const buttons = document.querySelectorAll("[aria-haspopup=true]")
const handleClick = (event) => {
const controls = event.target.getAttribute("aria-controls"),
controlled = document.getElementById(controls),
articles = document.querySelectorAll("article");
if (controlled.getAttribute("aria-hidden") === "true") {
articles.forEach(article => {
article.setAttribute("aria-hidden", "true");
article.classList.add("hide");
article.classList.remove("show");
})
buttons.forEach(button => button.setAttribute("aria-expanded", "false"))
event.target.setAttribute("aria-expanded", "true");
controlled.setAttribute("aria-hidden", "false");
controlled.classList.remove("hide");
controlled.classList.add("show");
}
}
buttons.forEach(button => {
button.addEventListener("click", handleClick);
})
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
li {
margin-right: 10px;
}
article {
height: calc(100vh - 50px);
width: 100vw;
position: absolute;
top: 50px;
left: 0;
}
#feature {
background-color: red;
}
#sale {
background-color: green;
}
#view {
background-color: blue;
}
.show {
opacity: 1;
transition: opacity .2s ease-in-out .2s;
}
.hide {
opacity: 0;
transition: opacity .2s ease-in-out;
}
button[aria-expanded=true] {
background-color: #cceeff;
}
<ul>
<li>
<button aria-haspopup="true" aria-expanded="true" aria-controls="feature">Featured</button>
</li>
<li>
<button aria-haspopup="true" aria-expanded="false" aria-controls="sale">On Sale</button>
</li>
<li>
<button aria-haspopup="true" aria-expanded="false" aria-controls="view">Most Viewed</button>
</li>
</ul>
<article class="show" id="feature" aria-hidden="false">
<h1>Featured</h1>
</article>
<article class="hide" id="sale" aria-hidden="true">
<h1>On Sale</h1>
</article>
<article class="hide" id="view" aria-hidden="true">
<h1>Most Viewed</h1>
</article>

Change which image is displayed on hover and click

I'm not a developer. I've been however tasked with coming up with a solution for a small project at work with jQuery and I have no clue where to begin. Here's my codepen: https://codepen.io/axo1/pen/mdBLRjL
What I need to is this (all graphics and texts are placeholders):
What I managed to achieve
Image item1 is supposed to be the first visible,
Hovering on the buttons below the graphic changes which image is displayed,
What I don't know how to achieve
The buttons below should be clickable. Clicking on a button changes the "active" graphic above. For example: if I click on the Second item button, the item2 image will be displayed even after I unhover the button, and so forth.
Any tips of what I should look into?
Unfortunately jQuery is heavily preferred here.
$(document).on({
mouseenter: function() {
$(".item1").toggleClass("active hidden");
$(".item2").toggleClass("hidden");
},
mouseleave: function() {
$(".item1").toggleClass("active hidden");
$(".item2").toggleClass("hidden");
}
}, ".item2btn");
$(document).on({
mouseenter: function() {
$(".item1").toggleClass("active hidden");
$(".item3").toggleClass("hidden");
},
mouseleave: function() {
$(".item1").toggleClass("active hidden");
$(".item3").toggleClass("hidden");
}
}, ".item3btn");
img {
max-width: 15%;
position: absolute;
top: 0;
left: 0;
}
.hidden {
visibility: hidden;
}
.active {
visibility: visible;
}
#container {
display: flex;
justify-content: flex-start;
align-items: center;
color: white;
position: absolute;
top: 250px;
}
#container ul {
padding: 1em;
}
#container ul>li {
background: black;
margin: 1em;
padding: 1em;
list-style-type: none;
display: inline;
}
#container ul>li:hover {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<img src="https://www.models-resource.com/resources/big_icons/13/12406.png" class="active items item1">
<img src="https://banner2.cleanpng.com/20180410/rce/kisspng-the-legend-of-zelda-majora-s-mask-hyrule-warriors-the-legend-of-zelda-5acc7b7a4870f6.4303262815233503942967.jpg" class="hidden items item2">
<img src="https://i.pinimg.com/originals/4a/a5/df/4aa5df83115df6c96732a2d76ccb4f1b.jpg" class="hidden items item3">
<div id="container">
<ul>
<li class="item1btn">First item</li>
<li class="item2btn">Second item</li>
<li class="item3btn">Third item</li>
</ul>
</div>
Here is a working version
I kept your style - I think it can be shortened to be more DRY
const $images = $(".items")
let $currentItem = $(".items").eq(0)
$("#container li").on({
"click": function() {
const id = $(this).data("id").replace("btn", "");
$images
.removeClass("active")
.addClass("hidden")
$currentItem = $(`.${id}`)
.removeClass("hidden")
.addClass("active");
},
"mouseover": function() {
const id = $(this).data("id").replace("btn", "");
$images
.removeClass("active")
.addClass("hidden")
$(`.${id}`)
.removeClass("hidden")
.addClass("active");
},
"mouseout": function() {
$images
.removeClass("active")
.addClass("hidden")
$currentItem
.removeClass("hidden")
.addClass("active");
}
})
img {
max-width: 15%;
position: absolute;
top: 0;
left: 0;
}
.hidden {
visibility: hidden;
}
.active {
visibility: visible;
}
#container {
display: flex;
justify-content: flex-start;
align-items: center;
color: white;
position: absolute;
top: 250px;
}
#container ul {
padding: 1em;
}
#container ul>li {
background: black;
margin: 1em;
padding: 1em;
list-style-type: none;
display: inline;
}
#container ul>li:hover {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<img src="https://www.models-resource.com/resources/big_icons/13/12406.png" class="active items item1">
<img src="https://banner2.cleanpng.com/20180410/rce/kisspng-the-legend-of-zelda-majora-s-mask-hyrule-warriors-the-legend-of-zelda-5acc7b7a4870f6.4303262815233503942967.jpg" class="hidden items item2">
<img src="https://i.pinimg.com/originals/4a/a5/df/4aa5df83115df6c96732a2d76ccb4f1b.jpg" class="hidden items item3">
<div id="container">
<ul>
<li data-id="item1">First item</li>
<li data-id="item2">Second item</li>
<li data-id="item3">Third item</li>
</ul>
</div>

When I click on the menu button It is not opening the menu

const menuBtn = document.querySelector('#menuBtn')
const exitBtn = document.querySelector('#exitBtn');
const menu = document.getElementsByClassName('menu');
menuBtn.addEventListener('click' , () => {
menu.style.display = 'block'
})
.fa.fa-bars.menuBtn {
color: #fff;
font-size: 35px;
cursor: pointer;
display: none;
}
.fa.fa-times-circle.exit {
color: white;
font-size: 35px;
cursor: pointer;
}
#media (max-width: 934px) {
.max-width {
padding: 0 50px;
}
.fa.fa-bars.menuBtn {
display: block;
}
.navbar .menu {
position: fixed;
height: 100vh;
width: 100%;
left: 0;
top: 0;
background-color: #111;
text-align: center;
padding-top: 110px;
display: none;
}
.menu{
display: none
}
.exit {
z-index: 999;
display: none;
margin: 1.8rem;
}
.navbar .menu li {
display: block;
}
.navbar .menu li a {
display: inline-block;
margin: 20px 0;
font-size: 35px;
}
}
<nav class="navbar" id="nav">
<div class="max-width">
<div class="logo"><a id="headSpan" href="index.html">Port<span>folio.</span></a></div>
<ul class="menu">
<li>Home</li>
<li>About</li>
<li>Skills</li>
<li>Projects</li>
<li>CV</li>
<li>Contact</li>
</ul>
<div>
<i class="fa fa-bars menuBtn" id="menuBtn" aria-hidden="true"></i>
</div>
<div class="exit">
<i class="fa fa-times-circle exit" id="exitBtn" aria-hidden="true"></i>
</div>
</div>
</nav>
How do I make it work ? I tested to see if the add event listener was working and it was working but when it comes to displaying the menu when clicked it does not work. Any idea what the issue may be ? I am not that good at using Javascript so any help would be appreciated . Thank you
document.getElementsByClassName('menu'); doesn't return a single element. It returns an HTMLCollection. So menu isn't an element and menu.style.display = 'block doesn't do what you're trying to do.
document.getElementsByClassName('menu') gets multiple elements, all of which have the class 'menu'. The function returns an HTMLCollection, but you can treat it like a list. If you want to use classes, I would recommend using:
var list = document.getElementsByClassName('menu')
for (var item of list) {
// Do Stuff Here
}
If you have multiple menus, consider using JQuery with the .each(function) method for functions.

jQuery Dropdown keeps closing when hovering from div to div

I've made a dropdown from two different divs. One is the trigger and one the submenu.
If the mouse hovers from the trigger to the submenu, the submenu closes. How can I keep it open even when the mouse switches between the two divs?
Also I can't move the submenu-container inside the dropdown-trigger. They need to be two separate divs.
$('.dropdown-trigger,.submenu-container').hover(
function(){$('.submenu-container').css('display', 'block')},
function(){$('.submenu-container').css('display', 'none')},
);
Thank you so much in advance!
You may not need jquery. you can do by css. Similar way u can do in jquery
.dropdown {
max-width: 200px;
list-style: none;
padding: 10px;
position: relative;
}
.item {
display: none;
padding: 10px 20px;
border: 1px solid #000;
}
.item.default {
display: flex;
justify-content: space-between;
}
.dropdown:hover .item {
display: block;
}
.dropdown:hover .item.default {
display: flex;
}
<ul class="dropdown">
<li class="item default">Select <span>V</span></li>
<li class="item">1</li>
<li class="item">1</li>
<li class="item">1</li>
<li class="item">1</li>
</ul>

How to have certain buttons and balance show after login

header {
width: 100%;
background-color: #2C2F33;
border-bottom: 5px #FF9F00 solid;
font-size: 12.5px;
display: flex;
align-items: center
}
#logo img {
float: left;
margin: 0;
padding: 20px;
width: 125px;
height: 42.5px;
background-color: #2C2F33;
}
.beforelogin {
display: flex;
justify-content: space-between;
margin-left: auto;
}
.afterlogin {
display: flex;
justify-content: space-between;
margin-left: auto;
}
.navbar {
display: flex;
justify-content: space-between;
margin-left: auto;
}
li {
display: inline;
padding: 0 20px 0 20px;
}
#rightnavbar li {
display: inline-block;
position: relative;
}
.navbar,
li,
a {
text-decoration: none;
list-style-type: none;
text-transform: uppercase;
background-color: #2C2F33;
position: relative;
}
#rightnavbar a:hover {
color: #FF9F00;
}
#rightnavbar a::before {
content: '';
display: block;
height: 3px;
background-color: #FF9F00;
position: absolute;
top: -34px;
width: 0%;
transition: all ease-in-out 225ms;
}
#rightnavbar a:hover::before {
width: 100%;
}
#userbalance {
background-color: #23272A;
padding: 12.5px;
margin-right: auto;
border: 2.5px #FF9F00 solid;
}
#balance,
#dsh {
color: #FF9F00;
}
#dosh {
color: #FFFFFF;
}
<header>
<div id="logo">
<a href="index.html"><img src="./img/logo.png" title="Website Info"></img>
</a>
</div>
<div class="beforelogin">
<div id="rightnavbar">
<ul>
<li>Login</li>
</ul>
</div>
</div>
<div class="afterlogin">
<div class="navbar">
<div id="leftnavbar">
<ul>
<li id="userbalance"><span id="balance">Balance:</span> <span id="dosh">1.00000000</span> <span id="dsh">DSH</span></li>
</ul>
</div>
<div id="rightnavbar">
<ul>
<li>User Panel</li>
<li>News</li>
<li>View on Social Media</li>
<li>Logout</li>
</ul>
</div>
</div>
</div>
</header>
I am trying to make my website, where after user logs in .beforelogin's <li> disappears and .afterlogin's <li> appears.
So there is only Login button for the user till the user logs in (In the header/navbar) and after login the Login exchanges its place for Logout and 3 other buttons and User Balance appears.
I asked a webdev friend and he said to use JavaScript/jQuery but I am not that good in those. I am still trying to find some good tutorials to learn JavaScript/jQuery for webdev but I am not sure where to look for.
There needs to be some trigger that executes this script. In this case you can add this HTML temporarily under the header
<br />
<input type="radio" name="loginStatus" id="loggedIn" value="Logged in" checked="checked" /> User is logged in
<br />
<input type="radio" name="loginStatus" id="loggedOut" value="Logged out" /> User is logged out
Now the jQuery:
// sets initial status - this can be done by CSS as well
if ($('input#loggedIn').prop('checked')) {
alert('Login is visible so after-loggin is hidden');
$('.beforelogin').css('display', 'none');
$('.afterlogin').css('display', 'block');
} else {
alert('Login is visible so after-loggin is hidden');
$('.beforelogin').css('display', 'block');
$('.afterlogin').css('display', 'none');
}
// this changes the status based on the radio buttons
$('input[name = "loginStatus"]').change(function() {
if ($('input#loggedIn').prop('checked')) {
$('.beforelogin').css('display', 'none');
$('.afterlogin').css('display', 'block');
} else {
$('.beforelogin').css('display', 'block');
$('.afterlogin').css('display', 'none');
}
});
This gets you the display block or yes, or display none. To simply use jQuery to see if one is display and if so hide the other, you can do this:
var isDisplayed = $('.afterlogin').css('display', 'block');
var isNotDisplayed = $('.afterlogin').css('display', 'none');
if ($('.beforelogin').css('display') == 'none') {
$(isDisplayed);
} else {
$(isNotDisplayed);
}
If you are unfamiliar with jQuery then you should take a beginners tutorial to show you where to put the scripts and all that stuff. YouTube has good videos for free.
Here's a jsfiddle
As per above HTML you can use below code to hide containers based on logged in and logged out scenarios.
1) User non logged in scenario
$(".beforelogin").show();
$(".afterlogin").hide();
2) User logged in scenario.
$(".afterlogin").show();
$(".beforelogin").hide();
Of course we should call above functions at right place in the code and logged in and non logged in scenario should be dependent on login service call, but it depends on the requirement. And also to draw containers (afterlogin, beforelogin) at same location you have to use similar styling.

Categories

Resources