How to add :hover and click event to element same time? - javascript

When I hover parent element his child is appear. When I click parent element his child also appear and when I click it again his child disappear. But then(after clicking parent element for disappearing child) when I hover parent element his child is not appearing. Why?
var parent = document.querySelector('nav > ul > li');
var children = document.querySelector('nav > ul > li > ul');
parent.addEventListener('click', function () {
if (children.style.display == 'block') {
children.style.display = 'none';
} else {
children.style.display = 'block';
}
});
nav > ul > li > ul {
display: none;
}
nav > ul > li:hover ul {
display: block;
}
li {
width: max-content;
}
ul {
padding: 0px;
}
<body>
<nav>
<ul>
<li>Parent
<ul>
<li>child</li>
<li>child</li>
</ul>
</li>
</ul>
</nav>
</body>
Thanks.

It is easier to toggle a class when the parent is clicked.
var parent = document.querySelector('nav > ul > li');
var children = document.querySelector('nav > ul > li > ul');
parent.addEventListener('click', function() {
children.classList.toggle("showIt");
});
nav>ul>li>ul {
display: none;
}
nav>ul>li:hover ul {
display: block;
}
li {
width: max-content;
}
ul {
padding: 0px;
}
.showIt {
display: block;
}
<body>
<nav>
<ul>
<li>Parent
<ul>
<li>child</li>
<li>child</li>
</ul>
</li>
</ul>
</nav>
</body>

You can easily avoid this by setting children.style.display to the empty string instead of assigning none to it when you want to hide the children.
EDIT:
Need a correction: Instead of undefined the empty string needs to be assigned to children.style.display to drop the override (see the text marked in italics - I had to correct that).
The code snippet should look like this:
var parent = document.querySelector('nav > ul > li');
var children = document.querySelector('nav > ul > li > ul');
parent.addEventListener('click', function () {
alert(children.style.display);
if (children.style.display == 'block') {
children.style.display = '';
} else {
children.style.display = 'block';
}
}, true);

Use basic programming composition
$('#target').on('click mouseover', function () {
// Do something for both
});

Related

Modify code to take textContent from li instead of thumbnail img src?

I found (and tweaked) the code below that was designed for switching the larger img src with the src of thumbnails in a list, but I'm not sure how to adjust it to use something like https://picsum.photos/id/CLICKED_LI_textContent/200/200 as the URL instead of pulling from a thumbnail's src.
For some more context here's the original post in which I was looking into this
How can I change img src from list of (non-image) items?
I haven't taken any JS classes, so I'm not sure how every component of the script works. I'm more comfortable with pure HTML and CSS, but think JS is the answer for making this work more smoothly.
(I did add the jquery script src to the document for this)
Sorry the code is a little ugly, I would have added the script and style tags and such but I ran out of time while posting this
$("#list li").click(function(e) {
// if i use this getting undefined
// var src = $(this).attr("src");
// so i use this method
var target = e.target;
var src = target.src;
console.log(src);
$("#display").fadeOut(function() {
$(this).on('load', function() {
$(this).fadeIn();
});
$(this).attr("src", src);
});
//record which thumb was clicked
$("#list li").removeClass("active"); //remove class
$(this).addClass("active"); //apply class to selected thumb
});
//move next
$("#left-arrow").click(function() {
if ($("#list li.active").next("#list li").length > 0) {
$("#list li.active").next().children( 'img' ).trigger("click");
} else {
$("#list li:first > img").trigger("click"); //go to first
}
return false;
});
//move previous
$("#right-arrow").click(function() {
if ($("#list li.active").prev("#list li").length > 0) {
$("#list li.active").prev().children( 'img' ).trigger("click");
} else {
$("#list li:last > img").trigger("click"); //go to end
}
return false;
});
//click the first thumb to begin
$("#list li:first > img").trigger("click");
.container {
display: flex;
}
.active {
border-bottom: 1px solid #990000;
}
.list {
width: 200px;
cursor: pointer;
padding: 0.25rem;
}
list > li * {
/* so only the li tag can be event.target, and none of it's children */
pointer-events: none;
}
.display {
max-width: 500px;
max-height: 500px;
}
<div class="container">
<div class="list">
<ul id="list">
<li>237</li>
<li>240</li>
<li>100</li>
<li>301</li>
</ul>
$larr; $rarr;
</div>
<div class="show">
<img src="https://picsum.photos/id/237/200/200" class="display" id="display">
</div>
</div>
Here is a pure javascript solution. The only difference is that it lacks the fading between the images.
I tried to write the code as pedagogic as possible, using variables as explanations. The code goes more with your original thread, where you had a bunch of images with different file endings. I gave the image an alt attribute, so you can see the change.
A short explanation:
Use an array for the images.
Create your list through javascript, using the array.
Add click listeners to your #list, where you read the .textContent. I added pointer-events: none; to any children to the li tags so they don't trigger the click listener.
Add click listeners to your prev/next buttons, where you check which index that the currently visible image has in the array (from 0 to 3 in imageArr) and then adds +1 och -1 to that index.
[edit] Added code for updating the CSS.
const listEl = document.getElementById('list');
const imgElement = document.querySelector('.right > img');
let imageArr = ["237.jpg", "240.gif", "100.jpeg", "301.png"]; // 1
let currentImage = '';
document.getElementById('next').addEventListener('click', () => shiftImage(1));
document.getElementById('prev').addEventListener('click', () => shiftImage(-1));
listEl.addEventListener('click', displayImage);
function updateImage(imageName) {
const subfolder = 'images/';
changeActive(imageName, currentImage); /* ADDED in EDIT */
currentImage = imageName;
imgElement.src = subfolder + imageName;
imgElement.alt = imageName;
}
/* ADDED in EDIT */
function changeActive(newImage, oldImage) {
if (oldImage) {
let oldIndex = imageArr.indexOf(oldImage);
toggleActiveClass(oldIndex);
}
let currentIndex = imageArr.indexOf(newImage);
toggleActiveClass(currentIndex);
}
/* ADDED in EDIT */
function toggleActiveClass(imageIndex) {
let liElements = listEl.childNodes;
liElements[imageIndex].classList.toggle('active');
}
function shiftImage(direction) {
let currentIndex = imageArr.indexOf(currentImage);
let newIndex = currentIndex + direction;
if (newIndex < 0) { newIndex = imageArr.length - 1; }
else if (newIndex >= imageArr.length) { newIndex = 0; }
let newImageName = imageArr[newIndex];
updateImage(newImageName);
}
function displayImage(event) {
let liElement = event.target;
updateImage(liElement.textContent);
}
function constructImageLinks() { // 2
let htmlOutput = '';
for (let imageSrc of imageArr) {
htmlOutput += `<li>${imageSrc}</li>`;
}
listEl.innerHTML = htmlOutput;
}
constructImageLinks();
updateImage(imageArr[0]);
section {
display: flex;
}
section ul {
margin-top: 0px;
}
section > .left li {
cursor: pointer;
padding: 0.25rem;
}
section > .left li.active {
background-color: pink;
}
section > .left li > * {
pointer-events: none;
}
section > div {
padding: 1rem;
}
section > .right > img {
width: 200px;
border: 1px solid;
padding: 0.5rem;
}
<section>
<div class="left">
<ul id="list"></ul>
<button id="prev">Previous</button>
<button id="next">Next</button>
</div>
<div class="right">
<img>
</div>
</section>
JSFiddle - Link
HTML
<div class="container">
<div class="list">
<ul id="list">
<li>237</li>
<li>240</li>
<li>100</li>
<li>301</li>
</ul>
$larr; $rarr;
</div>
<div class="show">
<img src="https://picsum.photos/id/240/200/200" class="display" id="display">
</div>
</div>
Javascript
// HELPER FUNCTIONS
function getImageURL(id, width=200, height=200){
return "https://picsum.photos/id/"+ id + "/" + width + "/" + height;
}
function removeActive(){
let lis = $('#list li');
for(let i=0;i<lis.length;i++){
$(lis[i]).removeClass('active');
}
}
// HANDLE EVENTS
$(document).on('click', "#list li", async (e)=>{
await $('#display').fadeOut();
removeActive();
let li = $(e.target);
li.addClass('active');
let image_id = parseInt($(e.target).html());
let image_url = getImageURL(image_id);
$('#display').attr('src', image_url);
await $('#display').fadeIn();
});
//move next
$(document).on('click', "#left-arrow", (e)=>{
// Handler Here
});
//move previous
$(document).on('click', "#right-arrow", (e)=>{
// Handler Here
});
CSS
.active {
border-bottom: 1px solid #990000;
}
.list {
width: 200px;
cursor: pointer;
padding: 0.25rem;
}
list > li * {
/* so only the li tag can be event.target, and none of it's children */
pointer-events: none;
}
.display {
max-width: 500px;
max-height: 500px;
}
.container {
display: flex;
}

loop through nav menu when li menu's hovered

I am not familar with JavaScript DOM , mostly i use jQuery for script.. I am trying on JavaScript and in trouble with the below code.
Does anyone know what could be the problem?
What i would like to do is when LI is hovered, the nested OL is displayed..somehow, the below code doesn't work, even it doesn't show any error in console.
Please help me...
var ul = document.querySelector('.gnb');
var li = ul.children;
var ol = document.getElementsByTagName('ol');
var i;
for (i = 0; i < li.length; i++) {
li[i].addEventListener('mouseenter', myFunction(myshow));
li[i].addEventListener('mouseleave', myFunction(myhide));
}
function myshow() {
ol.style.display = 'block'
}
function myhide() {
ol.style.display = 'none'
}
function myFunction(fn) {
return function(e) {
if (e.target.type !== "mouseenter") return;
fn.call(e.target)
};
}
.gnb {
float: left;
margin-left: 30px;
width: auto;
height: 100%;
}
.gnb>li {
float: left;
width: 150px;
height: 100%;
list-style: none;
}
.sub {
display: none;
}
.sub.show {
display: block;
}
<ul class="gnb">
<li class="gnbLi">
Why Mailchimp?
</li>
<li class="gnbLi" onmouseenter="myFunction()">
What You Can Do
<ol class="sub">
<li>Overview</li>
<li>Create</li>
</ol>
</li>
</ul>
Problem:
Basically what you have done is dealing with an array as a single element because of getElementsByTagName returns an array of ol in the whole page.
Solve:
1. you need to get the nested ol inside the li that's being hovered using
-event.target to get the clicked li.
- querySelector to get the ol under that li.
2. you need the ol to be displayed/removed depending on the event.
so only change the Javascript to be as the following and it will work
var ul = document.querySelector('.gnb');
var li = ul.children;
for (i = 0; i < li.length; i++) {
li[i].addEventListener('mouseenter', mouseenter);
li[i].addEventListener('mouseleave',mouseleave );
}
function mouseenter(event) {
var ol = event.target.querySelector('ol');
if(ol){
ol.style.display = 'block';
}
}
function mouseleave(event) {
var ol = event.target.querySelector('ol');
if(ol){
ol.style.display = 'none';
}
}
Your code is not correct and clean, I changed a little bit of your code:
(function () {
var ul = document.querySelector('.gnb');
var li = ul.children
var ol = document.getElementsByTagName('ol')[0];
var i;
for (i = 0; i < li.length; i++) {
li[i].addEventListener('mouseenter', myFunction(myshow));
li[i].addEventListener('mouseleave', myFunction(myhide));
}
function myshow() {
ol.style.display = 'block'
}
function myhide() {
ol.style.display = 'none'
}
function myFunction(fn) {
return fn;
}
})();
.gnb {
float: left;
margin-left: 30px;
width: auto;
height: 100%;
}
.gnb > li {
float: left;
width: 150px;
height: 100%;
list-style: none;
}
.sub {
display: none;
}
.sub.show {
display: block;
}
<ul class="gnb">
<li class="gnbLi">
Why Mailchimp?
</li>
<li class="gnbLi">
What You Can Do
<ol class="sub">
<li>Overview</li>
<li>Create</li>
</ol>
</li>
</ul>

Compare link href with address path for menu state

I'm working on right menu and i want to save that state with compare link href tag with address path location.
function setNavigation() {
var path = window.location.pathname;
path = path.replace(/\/$/, "");
path = decodeURIComponent(path);
$(".sidebar-menu li ul a").each(function () {
var href = $(this).attr('href');
if (path.substring(0, href.length) === href) {
$(this).addClass('active-item');
}
});
The problem is that the code not recognize similar addresses in one ul
for example both of below item give active-item class.
href="site/city/company/"
href="site/city/company/sample"
also here is my html
<li></li>
<li></li>
<li></li>
<li><a href="/Panel/Definitions/Attribute">/a></li>
<li></li>
Correct : $(".sidebar-menu li ul a") to $(".sidebar-menu ul li a")
I have changed the path matching method, check it out:
//sample path
var root_path = "www.xyz.com/";
function setNavigation(path) {
path = path.substring(path.indexOf("/"), path.length);
path = decodeURIComponent(path);
$(".sidebar-menu ul li a").removeClass('active-item');
$(".sidebar-menu ul li a[href='" + path + "']").addClass('active-item');
}
$("a").click(function(e) {
e.preventDefault();
});
$("#navigate").click(function() {
var path = root_path + $("#path").val();
setNavigation(path);
});
$("#navigate").click();
.active-item {
color: #eee;
background-color: #0095ff;
}
ul {
width: 80%;
}
li {
display: block;
margin: 5px;
}
li a {
display: block;
border: 1px solid #888;
padding: 5px;
}
.center {
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="center">
<p>
<b>Path :</b>
www.xyz.com/
<input id="path" value="Panel/Place/item"/>
<input type="button" value="Navigate" id="navigate"/>
</p>
</div>
<div class="sidebar-menu">
<ul>
<li>/Panel/Place</li>
<li>/Panel/Place/item</li>
<li>/Panel/Place/item/City</li>
<li>/Panel/Definitions/Attribute</li>
<li>/Panel/TouristDestination</li>
</ul>
</div>
You can try indexOf method:
if (href.indexOf(path.substring(0, href.length)) !== -1) {
...
Example:
function setNavigation() {
var path = window.location.pathname;
path = path.replace(/\/$/, "");
path = decodeURIComponent(path);
path = '/Panel/Place'; //tmp solution
$(".sidebar-menu li ul a").each(function() {
var href = $(this).attr('href');
if (href.indexOf(path.substring(0, href.length)) !== -1) {
$(this).addClass('active-item');
}
});
}
setNavigation();
.active-item {
color: #ff0000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="sidebar-menu">
<li>
<ul>
<li>/Panel/Place</li>
<li>/Panel/Place/item</li>
<li>/Panel/Place/City</li>
<li>/Panel/Definitions/Attribute</li>
<li>/Panel/TouristDestination</li>
</ul>
</li>
</ul>

jQuery onclick ul li i and display children ul if exists

I am trying to make a custom mobile menu. The idea is when user clicks on the chevron-right it will slideToggle the children UL and add class to the current LI (active) as well as it will display: block "chevron-down" and display: none "chevron-right". In other words the arrow will be changed from right to down.
jQuery(function(){
jQuery("#menu-main-menu-m li i.fa-chevron").click(function() {
if(jQuery(this).closest("li").children("ul").length) {
jQuery(this).toggleClass('active');
jQuery(this).children('ul').slideToggle(500);
}
else {
}
});
});
Here is the HTML
<ul id="menu-main-menu-m" class="menu"><li id="menu-item-3" class="menu-item-has-children menu-item-3 menu-item-ancestor">Open Account<i class="fa fa-chevron-right"></i><i class="fa fa-chevron-down"></i><ul class="sub-menu"><li>....</li><li></li></ul>
I think this is what you are looking for:
$("#menu-main-menu-m i[class*='fa-chevron']").click(function() {
if($(this).closest("li").children("ul").length) {
$(this).text($(this).text() == "Right" ? "Down" : "Right");
$(this).closest("li").children("ul").toggleClass('active');
$(this).closest("li").children("ul").slideToggle(500);
}
});
http://jsfiddle.net/rzseLj27/2/
Basic idea using CSS and binding the click event to the elements.
jQuery(function() {
jQuery("#menu-main-menu-m").on("click", "li i.fa", function() {
console.log("here");
var li = $(this).closest("li");
li.toggleClass("active");
li.find(".sub-menu").slideToggle();
});
});
.menu li i {
display: inline-block;
cursor: pointer;
}
.menu li.active i {
display: none;
}
.menu li i + i {
display: none;
}
.menu li.active i + i {
display: inline-block;
}
.sub-menu { display: none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<ul id="menu-main-menu-m" class="menu">
<li id="menu-item-3" class="menu-item-has-children menu-item-3 menu-item-ancestor">Open Account<i class="fa fa-chevron-right"></i><i class="fa fa-chevron-down"></i>
<ul class="sub-menu">
<li>....</li>
<li></li>
</ul>
</li>
</ul>

How do I expand this CSS list?

I've gotten my menu to expand by one level, but cannot figure out how to get it to expand a second time. What am I doing wrong?
HTML:
<ul id="nav">
<li>Root
<ul>
<li>Option 1
<ul>
<li>Link3</li>
<li>Lin4</li>
<li>Link5</li>
<li>Link6</li>
</ul>
</li>
<li>Option2
<ul>
<li>Link3</li>
</ul>
</li>
</ul>
</li>
</ul>
CSS:
ul {
padding: 0px;
margin: 0px;
list-style: none;
background-color: #53BF58;
width: 10em;
}
li ul {
display: none;
background-color: #86EF8A;
}
li.active ul {
display: block;
}
li ul li ul {
display: none;
background-color: #86EF8A;
}
li ul li.active ul {
display:block;
}
Javascript:
function hideAll() {
var navList = document.getElementById("nav");
for (var i=0; i<navList.childNodes.length; i++) {
var node = navList.childNodes[i];
if (node.tagName == "LI") {
node.className = node.className.replace(new RegExp("\s?active", "i"), "");
}
}
}
window.onload = function () {
var navList = document.getElementById("nav");
for (var i=0; i<navList.childNodes.length; i++) {
var node = navList.childNodes[i];
if (node.tagName == "LI") {
node.onclick = function() {
hideAll();
this.className += " active";
}
}
}
}
childNodes only contains the direct children of the element--you need to recurse the childNodes of each node as well.
I highly recommend that you use a framework like jQuery (http://jquery.com) to make the code simpler:
http://jsfiddle.net/jDEhU/5/
$('#nav').delegate('li', 'click', function() {
var self = $(e.target), //get a reference to the clicked element
active = self.parents().andSelf() //select all li's that should be active
.addClass('active'); //and activate them
$('#nav .active').not(active).removeClass('active'); //deactivate others
});
I think the problem is that you're only looping through the first level of nodes in your list. You need to go through the child elements of each subsequent list and add an onClick function to keep it expanding.

Categories

Resources