removeClass() is completely deleting elements from DOM - javascript

This is a build off of my previous question: Select a child of a child
I now have a <ul> within another <ul>. The behavior is an expandable menu. I'm doing this by adding and removing classes. For some reason...on the sub list - it completely removes the <li> elements from the DOM rather than just toggling it's classes. Why would it do that!?
You can see an example below:
$(function() {
// main expansion element
$(".expander").click(function() {
var subShown = $("ul > li", this).hasClass("show");
if (!subShown) {
$(".indented", this).slideDown('100').addClass("show");
$(".caret", this).addClass("reversedCaret");
} else {
$(".indented", this).slideUp('100').removeClass("show");
$(".caret", this).removeClass("reversedCaret");
}
});
// sub expansion element
$(".sub-expander, .caret").click(function() {
var subSelectText = $(".sub-expander").text();
if (subSelectText != "More") {
$(".indented--sub", this).slideUp('100').removeClass("show");
$(".caret", this).removeClass("reversedCaret");
$(".more-or-less").text("More");
} else {
$(".indented--sub", this).slideDown('100').addClass("show");
$(".caret", this).removeClass("reversedCaret");
$(".more-or-less").text("Show Less");
}
});
// stop propagation on the link element within .expander class
$(".indented").click(function(event) {
event.stopPropagation();
});
});
.expander:hover {
cursor: pointer;
}
.sub-expander--indented {
padding: 0 0 0 23px;
}
.sub-caret {
margin-right: 75px;
}
.indented,
.indented--sub {
display: none;
}
.show {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div class="expander">
<span class="caret downCaret right visibleCaret">+</span>
<ul>
<li class="category">Item 1
</li>
<li class="indented">Item 2</li>
<li class="indented">Item 3
<ul class="sub-expander indented more" style="padding-top:
0px;">
<li class="indented--sub">Chapter 5</li>
<li class="indented--sub">Chapter 6</li>
<li class="indented--sub">Chapter 7</li>
<span class="sub-caret moreCaret visibleLessCaret right">+</span>
<li class="more-or-less less sub-expander--
indented">More</li>
</ul>
</li>
</ul>
</div>
I'm giving it a separate classname to differentiate from the main section so that they don't show on initial open, so I'm not sure why it is behaving the way it is. It seems like there is a better way to go about this but I don't know what that would be.
UPDATE: was pointed out I did not have valid HTML. Fixed it following this thread. Still broken.
UPDATE #2: It seems like the problem is .text() - so it completely erases everything? I thought it just replaced the text node, and not all of it's children. I tried .html() but it does the same thing. What method do I use to just replace text then?
UPDATE #3 - one answer suggests I needed a more specific selector. I gave the list item a class of .more-or-less but doing that, it doesn't expand at all.

You'd probably want to use a more strict selector.
In your example case you use .sub-expander to select the node of which you want to replace the text. This matches with the ul.sub-expander however.
Since you want it to replace the text of the li.sub-expander the simplest thing you could do would be to use a more specific selector:
$("li.sub-expander").text("Show Less"); or (better) give the node which contains the text you want to replace another classname, id or other identifier to prevent targeting a different element.

Related

How can I hide an li containing text that starts with a dash

I am using the Chosen library. I have a long hierarchy select drop down list of parent and child items. It is automatically populated. I want to hide all li's containing text that starts with a dash character, example -General Discussion. I would also like to scope the hiding to only inside of the ul class name chosen-results.
<ul class="chosen-results">
//Below is the li of a child (I want to hide this li)
<li class="active-result" data-option-array-index="4">-General
Discussion</li>
//Below is the li of a parent ( I want to leave this alone)
<li class="active-result" data-option-array-index="0">Accident Prevention
</li>
</ul>
Updated. This works on node preview mode, but not in node edit mode
(function ($) {
$(document).ready(function () {
$("ul.chosen-results li").each(function () {
if ($(this).text().charAt(0) === '-') {
$(this).hide();
}
});
});
})(jQuery);
Below I tried to use .ajaxComplete, but it does not work.
(function ($) {
$(document).ajaxComplete(function() {
$("ul.chosen-results li").each(function () {
if ($(this).text().charAt(0) === '-') {
$(this).hide();
}
});
});
})(jQuery);
You can use JavaScript/jQuery to hide the li elements with the text starting with a dash character. Here is a sample code:
$(document).ready(function() {
$("ul.chosen-results li").each(function() {
if ($(this).text().charAt(0) === '-') {
$(this).hide();
}
});
});
This code will run when the document is ready and select all li elements inside the ul with the class chosen-results. It will then loop through each li element and check if the first character of its text is a dash. If it is, the li element will be hidden.
You don't need jQuery.
use String.prototype.trim() on the LI's textContent to remove wrapping whitespaces
use RegExp.prototype.test() to match ^- (starts with dash)
or use String.prototype.startsWith()
or use index String[i] to match a single character at String index
use Element.classList.toggle() to toggle a specific CSS utility class (to hide the element using i.e: display: none;)
Using RegExp.prototype.test()
document.querySelectorAll(".chosen-results li").forEach((li) => {
li.classList.toggle("u-hidden", /^-/.test(li.textContent.trim()));
});
/* Utility classes */
.u-hidden { display: none; }
<ul class="chosen-results">
<li class="active-result" data-option-array-index="4">
-General Discussion
</li>
<li class="active-result" data-option-array-index="0">
Accident Prevention
</li>
</ul>
Using String.prototype.startsWith()
document.querySelectorAll(".chosen-results li").forEach((li) => {
li.classList.toggle("u-hidden", li.textContent.trim().startsWith("-"));
});
/* Utility classes */
.u-hidden { display: none; }
<ul class="chosen-results">
<li class="active-result" data-option-array-index="4">
-General Discussion
</li>
<li class="active-result" data-option-array-index="0">
Accident Prevention
</li>
</ul>
Using String index [i]
document.querySelectorAll(".chosen-results li").forEach((li) => {
li.classList.toggle("u-hidden", li.textContent.trim()[0] === "-");
});
/* Utility classes */
.u-hidden { display: none; }
<ul class="chosen-results">
<li class="active-result" data-option-array-index="4">
-General Discussion
</li>
<li class="active-result" data-option-array-index="0">
Accident Prevention
</li>
</ul>

CSS Affect only parent list in the nested list

I have a nested list, as shown below. I'm struggling to find a specific CSS selector that I can use in querySelectorAll() JavaScript function, which will only affect all <li> tags that do NOT contain <ul> tag. So in this case it should be just the lines
<li>foot</li>
<li>leg</li>
<li>tiger</li>
<li>elephant</li>
<li>food</li>
I tried querySelectorAll("ol > li") and some others ways with the :not() selector, but to no success.
querySelectorAll("ol > li ul") is the opposite of what I want, because when I use
console.log(document.getElementById("translation").querySelectorAll("ol > li ul").length) it returns 3.
I need code of the following type console.log(document.getElementById("translation").querySelectorAll(blank).length) which will return 5. I don't know if that's possible, and I can't find it anywhere online.
Another way of looking at it is to let this code of CSS only color items which do not contain other nested lists (so that only points 2-6 will have colored background):
#translation ol > li ul{
background-color: cyan;
}
The entire list:
<div id="translation">
<ol>
<li>
<ul>
<li>parting</li>
<li>parting</li>
<li>parting</li>
<li>separation</li>
<li>separation</li>
<li>separation</li>
<li>farewell</li>
<li>(lateral) branch</li>
<li>fork</li>
<li>offshoot</li>
</ul>
</li>
<li>foot</li>
<li>leg</li>
<li>tiger</li>
<li>elephant</li>
<li>food</li>
<li>
<ul>
<li>branch</li>
<li>parting</li>
<li>disaffiliation</li>
<li>disaffiliation</li>
<li>separation</li>
<li>(lateral) branch</li>
<li>farewell</li>
<li>branch</li>
<li>division</li>
</ul>
</li>
<li>
<ul>
<li>dissociation</li>
<li>disaffiliation</li>
<li>dissociation</li>
</ul>
</li>
</ol>
</div>
You need to check each li is childNodes has ul. For example like this:
const test = [...document.querySelectorAll("ol > li")].filter(
li => ![...li.childNodes].find(child => child.localName === "ul")
);
if you are able to use jQuery then make the :not selector to catch elements that have ul as childs.
something like this:
$("translation").find("ol > li:not(:has(>ul))")
Notice this solution will work for jQuery selectors but not for the Vanilla JS (Vanilla JS doesn't support :has as a seclctor).
With Vanilla JS you might want to do something like this:
const elems = document.getElementById("translation").querySelectorAll('ol > li');
let items = Array.from(elems); // convert to Array type
items.filter(item => { // filter the array
item.querySelectorAll('ul').length == 0
})

Toggle submenu and if child clicked go to page

I've built a simple toggle menu that when clicked once shows a list of child elements and if clicked again hides those visible elements.
If a child element is clicked however I want it to visit that page only I cant seem to get it working? Is it to do with my prevent Default?
// Language select in global nav
$('.sub-lang').on('click', function(e) {
if ($(this).hasClass('active') && $(e.target).parent().hasClass('active')) {
$(this).removeClass('active');
$(this).css('height', 'auto');
$(this).children('ul').hide();
} else {
$(this).addClass('active');
$(this).css('height', $(this).find('ul').height() + 65);
$(this).children('ul').show();
}
e.preventDefault();
});
Here is the JsFiddle
Why don't you simple transform your main menu element in paragraph tag ?
or you could put an # inside your main menu element, and delete prevent default.
In this way you don't need to prevent default on your main elements.
Although google.com doesn't load inside an iFrame, you could check this fiddle. It works.
Look at this (with # in the anchors) fiddle
HTML
<ul style=" ">
<li class="sub-lang">
English
<ul style="">
<li>International</li>
<li>UK & Ireland</li>
</ul>
</li>
<li class="sub-lang">
Espanol
<ul style="">
<li>Español</li>
<li>España</li>
</ul>
</li>
<li class="sub-lang">
Francais
<ul style="">
<li>Français</li>
<li>France</li>
</ul>
</li>
</ul>
Add this line before your code
$('.sub-lang > a').removeAttr('href');
Remove
e.preventDefault();
this should work fine
You should add one condition for that.
if($(e.target).parent().hasClass('sub-lang') )
It will allow you to click on submenu.
// Language select in global nav
$('.sub-lang').on('click', function(e) {
if($(e.target).parent().hasClass('sub-lang') ){
if ($(this).hasClass('active') && $(e.target).parent().hasClass('active')) {
$(this).removeClass('active');
$(this).css('height', 'auto');
$(this).children('ul').hide();
} else {
$(this).addClass('active');
$(this).css('height', $(this).find('ul').height() + 65);
$(this).children('ul').show();
}
e.preventDefault();
}
});
ul li ul {
display: none;
z-index: 2;
right: 0;
background-color: #fff;
width: 250px;
}
ul li.active ul {
display: block;
text-align:left;
background: #f1f1f1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul style=" ">
<li class="sub-lang">
English
<ul style="">
<li>International</li>
<li>UK & Ireland</li>
</ul>
</li>
<li class="sub-lang">
Español
<ul style="">
<li>España</li>
</ul>
</li>
<li class="sub-lang">
Français
<ul style="">
<li>France</li>
</ul>
</li>
</ul>
Working Fiddle
Hope it helps.
Here Check This Out.
Explaination.
Instead of delegating the event on the whole li, I bound the event to all the immediate <a> children of the li, and then prevent the propagation for that specific element. So we don't have to figure out how to stop the event propagation (click in our case) to the children element.
Also the JSFiddle may have loading external iframe problems, so check the solution out here.
I think you have to add stopPropagation in your inside list items.
$('.sub-lang ul li').on('click', function(e) {
e.stopPropagation();
});

Hide/show div in ul with javascript

Let me start by saying I know this is a duplicate, however I couldn't find a solution by looking through previous answers so I was hoping someone can explain what I'm doing wrong with this.
This is part of a menu output by a php script:
<ul id="mtk_main_menu">
<li class="mtk_topmenu" onMouseOver="showMenu('mtk_submenu_0', 'mtk_div_submenu_0');">Manager Options
<div id="mtk_div_submenu_0">
<ul id="mtk_submenu_0">
<li class="mtk_submenu">Preferences</li>
<li class="mtk_submenu">Employee Options</li>
</ul>
</div>
</li>
with the following as my script as per https://stackoverflow.com/a/11842992, which should show each submenu when hovering its parent container
function showMenu(a,b) {
$(a).hover(
function(){
$(b).show();
},
function(){
$(b).hide();
})
}
Javascript and CSS being my weak suits, could someone tell me where my problem is? I feel like onMouseOver doesn't work the way I would expect it to. However I am still learning to manipulate the DOM, please bear with me, thank you!
Edited to reflect missingno's suggestions
For simple scenarios, i'd rather stay away from using JS
Heres how
HTML
<ul id="mtk_main_menu">
<li class="mtk_topmenu" onMouseOver="showMenu('mtk_submenu_0, mtk_div_submenu_0');">Manager Options
<div id="mtk_div_submenu_0">
<ul id="mtk_submenu_0">
<li class="mtk_submenu">Preferences</li>
<li class="mtk_submenu">Employee Options</li>
</ul>
</div>
</li>
CSS
#mtk_main_menu:before,
#mtk_main_menu:after {
content:"";
display:table;
clear:both;
}
#mtk_main_menu {
*zoom:1;
}
#mtk_main_menu > li {
position:relative;
float:left;
}
#mtk_main_menu > li > div {
position:absolute;
left:-999px;
background:grey;
}
#mtk_main_menu > li:hover > div {
left:0;
}
That will do the trick
Fiddle: http://jsfiddle.net/Varinder/7pXSw/
Edit
If you really want to go the JS way - heres how:
HTML
<ul id="mtk_main_menu">
<li class="mtk_topmenu" onMouseOver="showMenu('mtk_submenu_0, mtk_div_submenu_0');">Manager Options
<div id="mtk_div_submenu_0">
<ul id="mtk_submenu_0">
<li class="mtk_submenu">Preferences</li>
<li class="mtk_submenu">Employee Options</li>
</ul>
</div>
</li>
CSS
#mtk_main_menu:before,
#mtk_main_menu:after {
content:"";
display:table;
clear:both;
}
#mtk_main_menu {
*zoom:1;
}
#mtk_main_menu > li {
position:relative;
float:left;
}
#mtk_main_menu > li > div {
position:absolute;
display:none;
/*left:-999px;*/
background:grey;
}
#mtk_main_menu > li:hover > div {
/*left:0;*/
}
JS
function showMenu( args ) {
var arguments = args.split(",");
var submenuWrapper = arguments[1].replace(" ", "");
var $subMenuWrapper = $( "#" + submenuWrapper );
$subMenuWrapper.show();
var $menuItem = $subMenuWrapper.closest("li");
$menuItem.on("mouseout", function() {
$subMenuWrapper.hide();
$(this).off("mouseout");
});
}
Fiddle: http://jsfiddle.net/Varinder/vnwy3/1/
You are calling the event handler with a single string parameter instead of two. Try changing
showMenu('mtk_submenu_0, mtk_div_submenu_0')
into
showMenu('mtk_submenu_0', 'mtk_div_submenu_0')
Additionally, inside your script you should use are using literal strings instead of using your parameters
//This looks for an element of class "a"
$("a").hover(
//This uses the contents of the `a` variable instead:
$(a).hover(
Finally, your function is using 'mtk_submenu_0' as a jquery selector. This searches for a class instead of an id. Change the selector to add a "#" on front or change your jquery logic to not need ids (for example, you could create selectors to search for the first div and ul descendants of the current element.
By doing what you are doing, every time the onMouseOver event is triggered, you're attaching the jQuery hover event. Each time you're attaching another listener.
Instead, initialize your event on document ready:
$(function () {
$("#tk_div_submenu_0").hover(
function(){
$("#mtk_submenu_0").show();
},
function(){
$("#mtk_submenu_0").hide();
})
);
});
That will initialize it when the document is ready, and it will initialize it once.
Then just remove your onMouseOver event from the HTML.
<li class="mtk_topmenu">Manager Options ... </li>
First, you're going the long way around the problem. jQuery has a built in toggle method that performs the show/hide for you. Secondly you're putting the hover call on the child element of the item you're trying to show on hover. Here's an updated version of your code:
<ul id="mtk_main_menu">
<li class="mtk_topmenu" onMouseOver="showMenu(this,'mtk_div_submenu_0');">
Manager Options
<div id="mtk_div_submenu_0">
<ul id="mtk_submenu_0">
<li class="mtk_submenu">Preferences</li>
<li class="mtk_submenu">Employee Options</li>
</ul>
</div>
</li>
</ul>
JS:
function showMenu(a,b) {
var divStr = '#' + a.id + " div";
$(divStr).toggle();
}
I used the hover event on the LI element as it makes more sense in this case.
Here it is in a fiddle: http://jsfiddle.net/3Ecrq/
One thing I find strange about your code is that the first div you mention, mtk_submenu_0, is inside the div you are showing / hiding, mtk_div_submenu_0. Once you hide the outer div, the inner div cannot be 'hovered over', thus preventing it from being shown again.
To ensure the inner div does not get hidden, try something like this:
HTML:
<ul id="mtk_main_menu">
<li class="mtk_topmenu">Manager Options
<div id="mtk_div_submenu_0">
<ul id="mtk_submenu_0">
<li class="mtk_submenu">Preferences</li>
<li class="mtk_submenu">Employee Options</li>
</ul>
</div>
</li>
Javascript:
$(document).ready(function() {
$('.mtk_topmenu').hover(
function() {
$('#mtk_div_submenu_0').show();
},
function() {
$('#mtk_div_submenu_0').hide();
});
});
Because of your line:
<li class="mtk_topmenu" onMouseOver="showMenu('mtk_submenu_0', 'mtk_div_submenu_0');">
I assumed you were looking to have the mtk_div_submenu_0 div show / hide whenever the text Manager Options is moused over. Hopefully this helps!

Counting element height in Javascript

I have such a Javascript code:
$content.each(function(i) {
var $el = $(this);
// save each content's height (where the menu items are)
// and hide them by setting the height to 0px
$el.data('height', $el.outerHeight(true)).css('height', '0px').show();
});
In such a situation:
<div id="sbi_container" class="sbi_container">
<div class="sbi_panel" data-bg="images/1.jpg">
About
<div class="sbi_content">
<ul>
<li>Subitem</li>
<li>Subitem</li>
<li>Subitem</li>
</ul>
</div>
</div>
<div class="sbi_panel" data-bg="images/2.jpg">
...
</div>
I don't know why, but when only an <ul> (which height we are counting) have a <li> with more than a one word in it, it adds a separate space below <ul>.
When there is only one long word, everything is ok.
Any ideas? :)
Try this
.your_ul { font-size: 0; }
.your_ul li { font-size: 1234px }
it might work.

Categories

Resources