jQuery collapse ul after h-elements except current - javascript

For this website I'm having a menu like, generated by markdown via MIIS
<h4><a>title</a></h4>
<ul>
<li><a>foo</a></li>
<li> <a>foo</a>
<ul>
<li><a>sub</a></li>
</ul>
</li>
<li><a>foo</a></li>
</ul>
<h4><a>other title</a></h4>
<ul>
<li><a>other…</a></li>
...
</ul>
By default I only want to show the h4 titles, not the lists below it. Only when you click a title, it show show the ul content (only showing the first level below). clicking on another title should hide the other ul content of other section and show current ul below the title.
I'm currently stuck with the collapsing code in common.js, everything is always collapsed:
$('.miis-toc > ul > li > a').filter(function(){
return ($(this).attr('href') != '');
}).parent().find('ul > li').hide();
$('.miis-toc > h4').filter(function(){
return ($(this).attr('href') != '');
}).parent().find('ul').hide();
currLink.parentsUntil('.miis-toc > ul').last().find('li').show()

If I understand your question correctly, and understand the way your menu HTML is defined and structured, then a possible solution might be to revise your jQuery like so:
// Hide all ul sublists that are direct children of .miis-toc by
// default
$('> ul', '.miis-toc').hide()
// Listen for click event on h4 elements that are directly under
// .miis-toc
$('> h4', '.miis-toc').click(function() {
// Hide all ul sublists that are direct children of .miis-toc
$('> ul', '.miis-toc').hide()
// Show the ul sublist that is "after" the h4 that has been clicked
// (ie this)
$('+ ul', this).show()
// Prevent default click behaviour
return false;
})
Please see a completed example of this solution on jsFiddle
Updated answer
For the new details that were clarified in the comments below, the following solution should meet all requirements. Note that the JS code will "pre-toggle" menu UL or sub-UL based on URL matching (ie based on the browsers URL pathname).
You will need to update you HTML as follows:
<div class="miis-toc">
<h4>title</h4>
<!-- If path name is /title/, entire tree to this point will be
preopened to here -->
<ul>
<li><a>foo</a></li>
<li>foo
<!-- If path name is /foo/, entire tree to this point will
be preopened to here -->
<ul>
<li><a>sub</a></li>
</ul>
</li>
<li><a>foo</a></li>
</ul>
<h4><a>other title</a></h4>
<ul>
<li><a>other…</a></li>
...
</ul>
</div>
And your updated jQuery as follows:
// This code block sets up the menu when the page first loads, and
// decides which UL or UL sublist to display based on current URL
{
// Hide all UL's that are under .miis-toc
$('ul', '.miis-toc').hide()
// Look for a UL sublist after an anchor with href matching our
// browsers current pathname
var node = $('a[href="'+location.pathname+'"] + ul', '.miis-toc')
// If not found, look for a UL after an h4 with child anchors href
// matching our browsers current pathname
if(node.length === 0) {
var a = $('h4 a[href="'+location.pathname+'"]', '.miis-toc')
node = $('+ ul', a.parent())
}
// If we have found a UL or UL sublist, we need to walk back up the
// "menu tree" and "show" each node (ie UL and/or UL sublist)
if(node.length) {
var parent = node
// When we reach to top of the menu (ie .miis-toc), terminate the
// loop
while(!parent.hasClass('miis-toc')) {
parent.show()
parent = parent.parent()
}
}
}
// Listen for click event on h4 and nested li > a elements under
// .miis-toc
$('h4, li > a', '.miis-toc').click(function(event) {
// Toggle (show/hide) adjacent ul sublist when clicked
$('+ ul', this).toggle()
// Prevent default click behaviour
return false;
})

Related

Closing one element when opening another within the same loop using Javascript

first time on here so i'll try my best to explain what I'm asking.
So I have 3 list items with the same class name. I've put them in a looping function so that when you click on one it will display a sub set of list items for that specific list item. I also have them inside an if statement that adds a new class name to the specific list item that was clicked. It allows opening and closing of the sub list items when you click the corresponding parent element.
My question is; how can I use this same principle of checking for the additional class name, when the user clicks any of the list items. In other words, I am trying to code it in a way that will allow me to close any of the open sub list items when the user clicks a new list item.
This is what I came up with but it doesn't know what button[i] is when I include it within the "click" function. What I was trying to do with this code is to take whatever list item was clicked, and then check the previous and next iterations of the class name "button" to see if any of the contain also contain the class name "clicked.
HTML
<div class="main">
<ul>
<li>One
<ul>
<li>One-1</li>
<li>One-2</li>
</ul>
</li>
<li>Two
<ul>
<li>Two-1</li>
<li>Two-2</li>
</ul>
</li>
<li>Three
<ul>
<li>Three-1</li>
<li>Three-2</li>
</ul>
</li>
</ul>
</div>
CSS
.main ul ul {
display: none;
}
.main ul ul li {
display: block;
}
Javascript
var button = document.getElementsByClassName("button");
for (i = 0; i < button.length; i++) {
button[i].addEventListener("click", function() {
var prevItem = button[i - 1];
var nextItem = button[i + 1];
if (prevItem.className !== "button") {
prevItem.className = "button";
prevItem.nextElementSibling.style.display = "none";
}
if (nextItem.className !== "button") {
nextItem.className = "button";
nextItem.nextElementSibling.style.display = "none";
}
if (this.className === "button") {
this.className += " clicked";
this.nextElementSibling.style.display = "block";
}
});
}
I am wanting to make this code usable no matter how many list items you add. So checking exactly button[0] button[1] and button[2] wasn't really an option, but I can see how button[i + 1] might not check every list item after it but rather just the next one. I tried adding another loop but ran into similar issues. anyway that's why I'm here. Thanks for any help in advance.
Since I am not sure whether I understood your question correctly, I quickly rephrase it in my own words.
Question: "I have an arbitrary number of list elements, of which each contains a button and a nested list. The button is always visible, the nested list is hidden by default. When the user clicks on a button, the corresponding nested list should be shown. At the same time, all other shown nested lists should be hidden again. How can I achieve this?"
The original HTML looks fine:
<div class="main">
<ul>
<li>One
<ul>
<li>One-1</li>
<li>One-2</li>
</ul>
</li>
<li>Two
<ul>
<li>Two-1</li>
<li>Two-2</li>
</ul>
</li>
<li>Three
<ul>
<li>Three-1</li>
<li>Three-2</li>
</ul>
</li>
</ul>
</div>
The CSS I did not fully understand, but I suggest the following:
.main ul ul {
display: none;
}
.main li.is-active ul {
display: block;
}
.main ul ul li {
display: block;
}
By adding the "is-active" class to an LI element, it is shown. This way, the CSS controls the visibility.
For the JavaScript part, I suggest this:
const buttonElements = Array.from(document.querySelectorAll('.button'));
buttonElements.forEach(buttonElement => {
buttonElement.addEventListener('click', () => {
const activeElements = Array.from(document.querySelectorAll('.is-active'));
activeElements.forEach(activeElement => {
activeElement.classList.remove('is-active');
});
buttonElement.parentElement.classList.add('is-active');
});
});
This solution assumes you can use newer versions of JavaScript/ECMAScript. Overall, it makes use of const and arrow functions.
First, we get all elements with the class "button" by using document.querySelectorAll(). Since the result is a NodeList and no array, we convert it using Array.from(). Afterwards, we loop through the array by using Array.prototpye.forEach(). We add an event listener for the "click" event. When a button is clicked, we search for all elements with the "is-active" class and for each one remove it. Finally, we add the "is-active" class to the parent element of the clicked button using Node.prototype.parentElement().
Here is another solution that works in older browsers:
var buttonElements = document.getElementsByClassName('button');
for (var i = 0; i < buttonElements.length; i++) {
buttonElements[i].addEventListener('click', function(event) {
var activeListElements = document.getElementsByClassName('is-active');
for (var i = 0; i < activeListElements.length; i++) {
activeListElements[i].setAttribute('class', '');
}
event.target.parentNode.setAttribute('class', 'is-active');
});
}
This is pretty much the same as the other approach but works with older versions of JavaScript.
Generally, the idea is to focus on an arbitrary sum of elements instead of an array with a specific length. In natural language something like: "Give me all buttons. For every button, add an event listener. When a button is clicked, give me all active list elements and remove their active status. Then, mark the list item above the button as active".
Hope this helps

How to access list items within ul that get added via a jquery function

I am basically working on a responsive navigation bar where if the current window width doesn't accommodate the number of items, last item of the list will get appended to another un-ordered list.
My problem is I need to target menu items within the hidden list which is empty when the width of the window is 100%. I could access the un-ordered list for visible list but not for the hidden list as per below jQuery. I understand that I am trying to access items that doesn't exist yet, but there must be a way.
Snippet:
var $vlinks = $('#hrmenu .visible-links');
var $hlinks = $('#hrmenu .hidden-links');
availableSpace = $vlinks.width() - 30;
var
break = [];
areaAvail += w + 20;
break.push(areaAvail);
visibleItems = $vlinks.children().length;
requiredSpace =
break [visibleItems - 1];
if (requiredSpace > availableSpace) {
$vlinks.children().last().prependTo($hlinks);
}
$(document).ready(function() {
//Visible list
$('#shuffle-btn > li > a').click(function(event) {
$item = $(event.currentTarget).parent('li');
console.log($item.index());
});
//Hidden list list
$('#hidshuffle-btn > li > a').click(function(event) {
$item = $(event.currentTarget).parent('li');
console.log($item.index());
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav id="hrmenu" class="prdct-hrmenu">
<ul id="shuffle-btn" class="visible-links">
<li>item-1
<ul>
<li>Item-1-a</li>
</ul>
</li>
<li>item-2
<ul>
<li>Item-1-a</li>
</ul>
</li>
<li>item-3
<ul>
<li>Item-3-a</li>
</ul>
</li>
<li>item-4
<ul>
<li>Item-4-a</li>
</ul>
</li>
</ul>
<ul id="hidshuffle-btn" class="hidden-links">
</ul>
</nav>
As the elements in the hidden list are not available during DOM ready, you need to define a click handler that can delegate to these elements when they are available. You can use JQuery's on function for this like below.
$(document).ready(function(){
//Hidden list list
$('#hidshuffle-btn').on('click', ' li > a', function(event){
var $item = $(event.currentTarget).parent('li');
console.log($item.index());
});
});
Here's a sample Pen in action :)
For dynamically added elements, you just need to change your line from:
$('#shuffle-btn > li > a').click(function(event) {
to
$(document).on('click', '#shuffle-btn > li > a', function(event) {
Similarly for the hidden list:
$('#hidshuffle-btn > li > a').click(function(event) {
to
$(document).on('click', '#hidshuffle-btn > li > a', function(event) {
**#Arkantos's answer is more correct in terms of performance.

Menu has "scroll to #href" function, BUT unable to use _target blank links there

My problem is that my navbar has scroll to link function, because my site is in one page.
But I tried to add a new link to menu, with an external link (not in the same page).
When I click it, it won't open the link (I assume jQuery script tries to scroll to it instead of opening...)
How should I fix it or add exception?
This is the html:
<ul class="menyy">
<li>Mis on Rahapuu?</li>
<li>KKK</li>
<li>Kes me oleme?</li>
<li>Laenud</li>
<li>Kontakt</li>
</ul>
And this is the jQuery script for smooth scrolling:
//smooth scroll to href value
jQuery(".tabs-btn ul li a, .navbar-nav li a, .navbar-brand, .menyy a").click(function(event){
event.preventDefault();
//calculate destination place
var dest=0;
if($(this.hash).offset().top > $(document).height()-$(window).height()){
dest=$(document).height()-$(window).height();
}else{
dest=$(this.hash).offset().top;
}
//go to destination
jQuery('html,body').animate({scrollTop:dest}, 1000,'swing');
});
Any advice?
You could filter it to only include anchors with a hash based link
jQuery(".tabs-btn ul li a, .navbar-nav li a, .navbar-brand, .menyy a").filter('[href^=#]').click(function(event){ ...
Since someone else threw out adding a class (which is by far the best way to do this, however I recommend just applying that class across the board and simplifying the selector), I figured I would provide an alternative that was universal and didn't require modding the HTML (in case you couldn't for some reason):
$(".tabs-btn ul li a, .navbar-nav li a, .navbar-brand, .menyy a").filter(function(){
return (!this.target || this.target !== '_blank');
}).click(function(e){
e.preventDefault();
//calculate destination place
var dest = 0,
htDiff = $(document).height() - $(window).height(),
hashOffset = $(this.hash).offset().top;
if(hashOffset > htDiff){
dest = htDiff;
} else {
dest = hashOffset;
}
//go to destination
$('html,body').animate({scrollTop:dest}, 1000,'swing');
});
This will only filter your original collection of objects down to items that either do not have a target listed, or do not have a target with a value of _blank. I also took the liberty of caching your calculations, since you use them multiple times.
You can give all the others a class name and leave the link one out
<ul class="menyy">
<li><a class="yourclass" href="#info">Mis on Rahapuu?</a></li>
<li><a class="yourclass" href="#kkk">KKK</a></li>
<li><a class="yourclass" href="#meist">Kes me oleme?</a></li>
<li>Laenud</li>
<li><a class="yourclass" href="#kontakt">Kontakt</a></li>
</ul>
And change to this:
jQuery(".tabs-btn ul li a, .navbar-nav li a, .navbar-brand, .menyy a .yourclass").click(function(event){

how to append data from stating in js tree using jquery?

I Found one example of js tree in jquery ? in this user can add new data inside after selecting the
the row.
here http://jsfiddle.net/fuu94/
But When I remove all row (remove ul and li from mark up)and start making from first it will not work why ?
http://jsfiddle.net/fuu94/1/
$('#tree').jstree({
"core": {
"check_callback": true
},
"plugins": ["dnd"]
});
$('button').click(function () {
var ref = $('#tree').jstree(true),
sel = ref.get_selected();
if (!sel.length) {
return false;
}
sel = sel[0];
sel = ref.create_node(sel);
if (sel) {
ref.edit(sel);
}
});
According to the jstree documentation
"The minimal required markup is a < ul > node with some nested < li > nodes with some text inside."
-> http://www.jstree.com/docs/html/
As long as you make the menu using UL and LI it should do the rest for you (as in creating the tree).
So basically, if you remove the text from the LI and UL nodes and make your own text, duplicate the structure, you could make something like this:
-> http://jsfiddle.net/fuu94/3/
but the minimum requirements is something like:
<ul>
<li></li>
</ul>
and if you want to use a submenu, add one of these :
<li> Title Here
<ul>
<li></li>
<li></li>
</ul>
</li>

Collapsible menu javascript problem

I am struggling with a collapsible vertical menu. The first part of the script below works, so that the upper UL display its sibling LIs, while the other ULs keep their sibling LIs hidden.
My difficult task (to me at least) is to make the parent UL to the active link keep its sibling LIs visible. This is what I tried in the lower part of the script.
My a-links some times get a trailing hash (#) which I want to remove in order to compare i to the active URL. This is done through the trimHash(string)-function--which works when tested on a simple string, but not in this script.
Any good advice out there?
$(document).ready(function() {
// Collapse everything but the first menu:
$(".mainmenu > li > a").not(":first").find("+ ul").slideUp(1);
// Expand or collapse:
$(".mainmenu > li > a").click(function() {
$(this).find("+ ul").slideToggle("fast");
});
$(".mainmenu li").each(function () {
var li = $(this);
var a = rtrimHash(li[0].firstChild);
if (a.href == location.href) {
$(this).find("+ ul").slideDown(1);
}
});
I ended up with this solution. As it is a Wordpress site, while stepping through the menu items in the menu I could check if each link is active by comparing the link to the active post, and insert the class "current" to these menu items:
echo '<li class="child';
if ( $menuPost->ID == $post->ID ) { echo ' current'; }
And then use jQuery to find all instances of the li "current" class, and trigger the parent ul's to slideDown:
$(document).ready(function() {
// Collapse everything:
$("li.cat").find("+ ul").slideUp(1);
// Collapse everything but the first menu:
//$("li.cat").not(":first").find("+ ul").slideUp(1);
// Expand or collapse:
$("li.cat").click(function() {
$(this).find("+ ul").slideToggle("fast");
});
$("li.current").parent().slideDown(1);
});
Sorry I didn't solve this through javascript as I intended, but I achieved what I wanted.

Categories

Resources