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.
Related
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;
}
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>
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
});
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>
hey there i have little list here:
<ul id="selectable">
<li id='id.00'></li>
<li id='id.10'></li>
<li id='id.20'></li>
<li id='id.30'></li>
<li id='id.40'></li>
<li id='id.50'></li>
<li id='id.60'></li>
<li id='id.70'></li>
<li id='id.80'></li>
<li id='id.90'></li>
</ul
and i am able to select one or more of those listelement´s using css:
ul {overflow:hidden;}
#selectable .ui-selecting { background: #FECA40; }
#selectable .ui-selected { background: black; color: white; }
#selectable { list-style-type: none; margin: 0; padding: 0; width: 300px; }
#selectable li {float: left; width: 30px; height: 20px; background-color: none; display:block; }
and now i want to save the id´s of all selected element´s using jquery:
$(function() {
$( "#selectable" ).selectable({
stop: function() {
var result = $( "#select-result" ).empty();
$( ".ui-selected", this ).each(function() {
var index = $(this).attr('id');
result.append( index );
alert(index);
});
}
});
});
ok so far so good:
When i select e.g. four listelements, they get marked, BUT: only the last id is saved.
For example, when i select this listelement´s:
<li id='id.00'></li>
<li id='id.10'></li>
<li id='id.20'></li>
<li id='id.30'></li>
only the id: "id.30" is safed in my variable "index".
How can i get my jquery to safe all id´s of selected elements and not just the last one? greetings!
Try .map()
var result = $(".ui-selected", this).map(function () {
return this.id;
}).get();
Note: This will return array
If you want to get string use .join()
var result = $(".ui-selected", this).map(function () {
return this.id;
}).get().join(',');
Updated after OP's comment
fiddle Demo
$(function () {
$("#selectable").selectable({
stop: function () {
var result = $(".ui-selected", this).map(function () {
return this.id;
}).get().join(',');
$('#select-result').html(result);
}
});
});