For loop closure - javascript

I am hoping someone could help me out here in explaining to me what it is with my code that is not outputting what I am expecting. I have played with this and still can't figure out why the inside closure is not outputting anything.
for (var i = 1, len = $('.items').length + 1; i < len; i = i + 1) {
var j = (function(i) {
for (j = 0; j < i.length; j = j + 1) {
$('nav').find('ul').addClass('tier' + j + '-items');
}
})(i);
}
Here is the HTML
<nav>
<ul class="items">
<li class="item">Top level 1</li>
<li class="item">Top level 1
<ul>
<li class="item">level 2</li>
<li class="item">level 2</li>
<li class="item">level 2
<ul>
<li class="item">level 3</li>
<li class="item">level 3</li>
<li class="item">level 3</li>
</ul>
</li>
</ul>
</li>
<li class="item">Top level 1</li>
</ul>
</nav>
EDIT
I updated hoping this would work but no luck:
for (var i = 0, len = $('.items').length; i < len; i = i + 1) {
(function(j) {
for (var j = 0; j < i; j = j + 1) {
$('nav').find('ul').addClass('tier' + j + '-items');
}
})(i);
}
EDIT 2:
Sorry it was not clear what I was trying to do. Spencer was able to understand after a couple times and I appreciate the help. Thank you for your help.

That's because i.length is undefined. i is a number, it dose not have a .length property to it. That will make it so the inner loop never runs.
Edit:
I'm going to assume that what you want is to add the class 'tierN-items' where N is the depth of the list as a tree. So your output would look like so:
<nav>
<ul class="items tier1-items">
<li class="item">Top level 1</li>
<li class="item">Top level 1
<ul class= "tier2-items">
<li class="item">level 2</li>
<li class="item">level 2</li>
<li class="item">level 2
<ul class= "tier3-items">
<li class="item">level 3</li>
<li class="item">level 3</li>
<li class="item">level 3</li>
</ul>
</li>
</ul>
</li>
<li class="item">Top level 1</li>
</ul>
</nav>
For that what you seem to do isn't going to work because you are finding all ul in the nav regardless of the value of j. You are going to need a different approach. My solution would be modifying the selector as a string variable. Basically we find if the first ul exist, then we find if there is a ul > li > ul, then ul > li > ul > li > ul, and so on until there isn't such an element. In code that would look like so:
var level = 1;
var tierSearch = "nav > ul";
while($(tierSearch).length)
{
$(tierSearch).addClass('tier' + level + '-items');
level++;
tierSearch += "> li > ul";
}
Fiddle Example

Is this what you want
$(function(){
$('.items ul').each(function(k,v){
$(this).addClass('tier' + k + '-items');
$(this).prepend('<strong>Tier' + k + '-items</strong>'); // just to highlight
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav class="items">
<ul >
<li class="item">Top level 1</li>
<li class="item">Top level 1
<ul>
<li class="item">level 2</li>
<li class="item">level 2</li>
<li class="item">level 2
<ul>
<li class="item">level 3</li>
<li class="item">level 3</li>
<li class="item">level 3</li>
</ul>
</li>
</ul>
</li>
<li class="item">Top level 1</li>
</ul>
</nav>

Related

How to use foreach function for list tag in vanilla javascript

I want to use for each function for getting all li elements in an array using core javascript
var li_arr = []
$('li').each(function(){
li_arr.push($(this).attr("id"));
})
this is the code i want to convert
You can use querySelectorAll method to get all li elements, convert it to an array using spread syntax and finally use Array#map method to generate id array.
let li_arr = [...document.querySelectorAll('li')].map(ele => ele.id)
let li_arr = [...document.querySelectorAll('li')].map(ele => ele.id)
console.log(li_arr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li id="1">Item 1</li>
<li id="2">Item 2</li>
<li id="3">Item 3</li>
</ul>
Try like this
<script>
var all = document.getElementsByTagName("li");
var li_arr = []
for (var i=0, max=all.length; i < max; i++) {
li_arr.push(all[i].getAttribute("id"))
}
console.log(li_arr)
</script>
<ul>
<li id="1">Item 1</li>
<li id="2">Item 2</li>
<li id="3">Item 3</li>
<li id="4">Item 4</li>
</ul
use document.querySelectorAll('li').
const lis = document.querySelectorAll('li');
lis.forEach((li) => {
console.log(li.id);
});
<li id="1">Item 1</li>
<li id="2">Item 2</li>
<li id="3">Item 3</li>
If you want to target the lis of specific ul.
console.log([...document.querySelectorAll('ul#list>li')].map(el => el.id));
<ul id="list">
<li id="1">Item 1</li>
<li id="2">Item 2</li>
<li id="3">Item 3</li>
</ul>

Can you dynamically add an event listener to variables in a for loop using Javascript

I am trying to dynamically add an eventlistener to a group of li tags that will toggle a CSS class
const item = $('#dynamic-list').getElementsByTagName('li');
const strikeOut = () => this.classList = this.classList.toggle('strike-out');
const addClass = function() {
for (let i = 0; i < item.length; i++) {
let link = item[i];
link.onclick = strikeOut;
}
}
addClass();
.strike-item {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
I have a feeling that I'm not assigning the strikeout function correctly to each link however I'm open to all suggestions
Some problems to fix:
jQuery collections don't have a getElementsByTagName function. No need for jQuery, just use a plain querySelectorAll
Arrow functions do not capture their calling context - rather, they inherit the calling context of their surrounding block. Use a standard function instead so that the this inside strikeOut refers to the clicked element
Your CSS refers to strike-item, but your JS toggles a class name of strike-out.
After fixing:
const item = document.querySelectorAll('#dynamic-list li');
const strikeOut = function() {
this.classList.toggle('strike-item');
}
const addClass = function() {
for (let i = 0; i < item.length; i++) {
let link = item[i];
link.onclick = strikeOut;
}
}
addClass();
.strike-item {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
But you might consider using event delegation instead, that way you only add one listener rather than a listener for every li:
document.querySelector('#dynamic-list')
.addEventListener('click', (e) => {
const { target } = e;
if (!target.matches('li.dynamic-item')) return;
target.classList.toggle('strike-item');
});
.strike-item {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
You have many things going wrong here, getElementsByTagName is not a jquery selector its a javascript selector. Here is an example how to toggle :
const item = $('#dynamic-list li');
const strikeOut = (e) =>{
$(e.target).toggleClass('strike-item')
}
const addClass = function() {
for( let i=0; i<item.length; i++) {
let link = item[i];
link.onclick = strikeOut;
}
}
addClass();
.strike-item {
text-decoration: line-through;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
You Can Try Simple Code here
function myAction(me){
me.classList.toggle('strike-out');
}
.dynamic-item.strike-out {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item" onClick="myAction(this)">Item 1</li>
<li class="dynamic-item" onClick="myAction(this)">Item 2</li>
<li class="dynamic-item" onClick="myAction(this)">Item 3</li>
<li class="dynamic-item" onClick="myAction(this)">Item 4</li>
<li class="dynamic-item" onClick="myAction(this)">Item 5</li>
</ul>

Change <li> order with Jquery [duplicate]

Given the following list
<ul class="listitems">
<li data-position="1">Item 1</li>
<li data-position="2">Item 2</li>
<li data-position="3">Item 3</li>
<li data-position="4">Item 4</li>
</ul>
there is some functionality on the page that will allow the possibility of these items changing position. For example, they may get to the following state (example only, the order could be anything):
<ul class="listitems">
<li data-position="3">Item 3</li>
<li data-position="2">Item 2</li>
<li data-position="1">Item 1</li>
<li data-position="4">Item 4</li>
</ul>
I am looking for a small function to reset the order. So far I have the following:
function setPositions()
{
$( '.listitems li' ).each(function() {
var position = $(this).data('position');
$(this).siblings().eq(position+1).after(this);
});
}
But it isnt working correctly. What am i doing wrong?
An additonal condition is that the order of the list might not have changed, and so the function has to work in that scenario also.
Try to use sort() with appendTo(),
$(".listitems li").sort(sort_li) // sort elements
.appendTo('.listitems'); // append again to the list
// sort function callback
function sort_li(a, b){
return ($(b).data('position')) < ($(a).data('position')) ? 1 : -1;
}
Snippet:
$(function() {
$(".listitems li").sort(sort_li).appendTo('.listitems');
function sort_li(a, b) {
return ($(b).data('position')) < ($(a).data('position')) ? 1 : -1;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<ul class="listitems">
<li data-position="3">Item 3</li>
<li data-position="2">Item 2</li>
<li data-position="1">Item 1</li>
<li data-position="4">Item 4</li>
</ul>
Expanding on Rohan's answer, if you want this to work for multiple lists rather than just one, you can use the following:
HTML:
<ul class="listitems autosort">
<li data-position="3">Item 3</li>
<li data-position="2">Item 2</li>
<li data-position="1">Item 1</li>
<li data-position="4">Item 4</li>
</ul>
<ul class="listitems autosort">
<li data-position="5">Item 5</li>
<li data-position="6">Item 6</li>
<li data-position="3">Item 3</li>
<li data-position="4">Item 4</li>
</ul>
Javascript:
$(".listitems.autosort").each(function(){
$(this).html($(this).children('li').sort(function(a, b){
return ($(b).data('position')) < ($(a).data('position')) ? 1 : -1;
}));
});
That will allow you to add as many lists as you like and sort them all by just setting the class autosort.
Live Example
My proposal, in full javascript, is:
document.addEventListener("DOMContentLoaded", function(e) {
Array.prototype.slice.call(document.querySelectorAll('.listitems li')).sort(function(a, b) {
return a.getAttribute('data-position').localeCompare(b.getAttribute('data-position'));
}).forEach(function(currValue) {
currValue.parentNode.appendChild(currValue);
});
});
<ul class="listitems">
<li data-position="3">Item 3</li>
<li data-position="2">Item 2</li>
<li data-position="1">Item 1</li>
<li data-position="4">Item 4</li>
</ul>
What about using sort and replace inside DOM
function sortLiElements(a,b) {
return parseInt($(a).data('position')) - parseInt($(b).data('position'));
}
$('.listitems').html($('.listitems li').sort(sortLiElements));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<ul class="listitems">
<li data-position="3">Item 3</li>
<li data-position="2">Item 2</li>
<li data-position="1">Item 1</li>
<li data-position="4">Item 4</li>
</ul>
Try this
function sortList() {
var list, i, switching, b, shouldSwitch;
list = document.getElementById("id01");
switching = true;
/* Make a loop that will continue until
no switching has been done: */
while (switching) {
// start by saying: no switching is done:
switching = false;
b = list.getElementsByTagName("LI");
// Loop through all list-items:
for (i = 0; i < (b.length - 1); i++) {
// start by saying there should be no switching:
shouldSwitch = false;
/* check if the next item should
switch place with the current item: */
//alert(b[i].getAttribute("order"));
if (b[i].getAttribute("order").toLowerCase() > b[i + 1].getAttribute("order").toLowerCase()) {
/* if next item is alphabetically
lower than current item, mark as a switch
and break the loop: */
shouldSwitch = true;
break;
}
}
if (shouldSwitch) {
/* If a switch has been marked, make the switch
and mark the switch as done: */
b[i].parentNode.insertBefore(b[i + 1], b[i]);
switching = true;
}
}
}
<button onclick="sortList()">Sort List</button>
<ul id="id01">
<li order="A" class="disname disheader">Pending</li>
<li order="AA" class="disname">302-1 Energy Consumption</li>
<li order="AA" class="disname">302-3 Energy Intensity</li>
<li order="DD" class="disname">EN-31 Environmental Expenditure/Investment</li>
<li order="DD" class="disname">103-2 Environmental Grievances</li>
<li order="D" class="disname disheader">Deactive</li>
<li order="BB" class="disname">305-4 Emission Intensity</li>
<li order="BB" class="disname">306-2 Total Waste</li>
<li order="BB" class="disname">307-1 Compliance</li>
<li order="AA" class="disname">302-4 Energy/Electricity Reduction Initiative</li>
<li order="AA" class="disname">303-1 Water Withdrawal by Source</li>
<li order="AA" class="disname">303-3 Recycled Water</li>
<li order="C" class="disname disheader">Auto Generated</li>
<li order="CC" class="disname">305-1 GHG Emission</li>
<li order="CC" class="disname">305-2 GHG Emission</li>
<li order="CC" class="disname">305-3 GHG Emission</li>
<li order="BB" class="disname">05-5 Reduction of GHG Emissions</li>
<li order="BB" class="disname">306-1 Water Discharge</li>
<li order="B" class="disname disheader">Overdue</li>
</ul>
Codepen Example

How can I add class on active li with JavaScript code

<ul class="nav nav-tabs">
<li onclick="this.className='active'">Home</li>
<li onclick="this.className='active'">Menu 1</li>
<li onclick="this.className='active'">Menu 2</li>
<li onclick="this.className='active'">Menu 3</li>
</ul>
How can I add active class on li tag with JavaScript. Here I am try to do it. It is working but not properly. I am doing for tabs here.
As per the comments, I guess you are expecting this:
var a = document.querySelectorAll(".nav li a");
for (var i = 0, length = a.length; i < length; i++) {
a[i].onclick = function() {
var b = document.querySelector(".nav li.active");
if (b) b.classList.remove("active");
this.parentNode.classList.add('active');
};
}
.active {
background-color: #0f9;
}
<ul class="nav nav-tabs">
<li>Home
</li>
<li>Menu 1
</li>
<li>Menu 2
</li>
<li>Menu 3
</li>
</ul>
if you have jquery
<ul class="nav nav-tabs">
<li>Home</li>
<li>Menu 1</li>
<li>Menu 2</li>
<li>Menu 3</li>
</ul>
<style>
.active {background-color: #0f9;}
</style>
<script type="text/javascript">
$("ul.nav.nav-tabs").on('click', 'li', function(){
$(this).siblings().removeClass('active');
$(this).addClass('active');
});
</script>
Vanilla JavaScript (ES6 where I tested, might work on ES5)
const elm = document.querySelector('ul');
elm.addEventListener('click', (el) => {
const elActive = elm.querySelector('.active');
if (elActive) {
elActive.removeAttribute('class');
}
el.target.setAttribute('class', 'active');
});

How to get the parent LI index and the sub LI index

Fiddle: http://jsfiddle.net/fyrx459k/
Script:
$(function() {
$('.ul1 li').click( function(e) {
e.preventDefault();
var liIndex = $(this).index('li');
console.log(liIndex);
);
});
HTML:
<ul class="ul1" id="ul1">
<li>Test</li>
<li>Test2</li>
<li>Test3
<ul class="ul2">
<li>Test3 - 1</li>
<li>Test3 - 2</li>
</ul>
</li>
<li>Test4
<ul class="ul2">
<li>Test4 - 1</li>
<li>Test4 - 2</li>
</ul>
</li>
</ul>
How can I modify the script to have the following:
When a parent LI (Test#) is clicked the console will display the index - for example, clicking on Test4 will output a 3 (fourth item)
When clicking on a sub LI (Test# - #) it will display the parent index and the child index - for example, clicking on Test3 - 1 will output 2.0 (2 being the parent LI and 0 being the first child sub LI)
This is one way of doing it:
$(function () {
$('.ul1 li').click(function (e) {
e.preventDefault();
e.stopPropagation();
var i = $(this).parentsUntil('#ul1').add(this).map(function () {
return this.tagName === 'LI' ? $(this).index() : null;
}).get().join('.');
});
});
A pretty basic solution using $(this).parents("li")
$(function() {
$('.ul1 li').click(function(e) {
e.preventDefault();
e.stopPropagation();
var liIndex = "";
var $parents = $(this).parents("li");
if ($parents.length > 0)
liIndex += ($parents.index() + 1) + ".";
liIndex += ($(this).index() + 1);
alert(liIndex);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="ul1" id="ul1">
<li>Test
</li>
<li>Test2
</li>
<li>Test3
<ul class="ul2">
<li>Test3 - 1
</li>
<li>Test3 - 2
</li>
</ul>
</li>
<li>Test4
<ul class="ul2">
<li>Test4 - 1
</li>
<li>Test4 - 2
</li>
</ul>
</li>
</ul>
I love html5 tags, so check if this solution can be useful. Otherwise you can do the same thing with the ID or whatever you prefer.
function checkIndex(element) {
deepCheck(element, '');
}
function deepCheck(element, index) {
if($(element).data('index') && $(element).is('li')) {
var newIndex = '';
if(index.length === 0) {
newIndex = 'Element indexes: ' + $(element).data('index')
} else {
newIndex = index + ' - ' + $(element).data('index');
}
deepCheck($(element).parent(), newIndex);
} else if($(element).data('root')) {
alert(index);
} else {
deepCheck($(element).parent(), index)
}
}
$(document).ready(function() {
$('a').click(function(e) {
e.preventDefault();
checkIndex(this);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul data-root="true" class="ul1" id="ul1">
<li data-index="1">Test</li>
<li data-index="2">Test2</li>
<li data-index="3">Test3
<ul class="ul2">
<li data-index="1">Test3 - 1</li>
<li data-index="2">Test3 - 2</li>
</ul>
</li>
<li data-index="4">Test4
<ul class="ul2">
<li data-index="1">Test4 - 1</li>
<li data-index="2">Test4 - 2</li>
</ul>
</li>
</ul>
Use parents('li') to check if the clicked li has a li ancestor, .index() to determine the index of a li, .preventDefault() to stop the click from following the link and .stopPropagation to stop event from bubbling. This should do it:
$('#ul1 li').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var arr = [];
arr.push( $(this).index() );
!$(this).parents('li').length ||
arr.push( $(this).parents('li').index() );
console.log( arr.reverse().join('.') );
});
$(function() {
$('#ul1 li').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var arr = [];
arr.push( $(this).index() );
!$(this).parents('li').length ||
arr.push( $(this).parents('li').index() );
console.log( arr.reverse().join('.') );
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="ul1" id="ul1">
<li>Test</li>
<li>Test2</li>
<li>Test3
<ul class="ul2">
<li>Test3 - 1</li>
<li>Test3 - 2</li>
</ul>
</li>
<li>Test4
<ul class="ul2">
<li>Test4 - 1</li>
<li>Test4 - 2</li>
</ul>
</li>
</ul>
I would suggest returning false. It removes the need to use both preventDefault and stopPropagation, and also to store the event in e.
Further, some more structured selections would be helpful here. There are only two cases to handle, where a parent element classed ul2 exits and where not. In the case it does, there needs to be two queries to determine the parents index and the current index with regards to the ul2 element. In the case where it does not, then only the relative ul1 index needs to be determined.
I included some extra elements to show the need for a more targeted approach.
$(function(){
$('.ul1 li').click(function() {
var ul2 = $(this).closest('.ul2'),
location = ul2.length > 0 ?
ul2.parent().index('.ul1 > li')+"."+ul2.find('li').index(this) :
$(this).index('.ul1 > li');
log(location);
return false;
});
});
function log(msg){ $("#result").text(msg); }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="ul1" id="ul1">
<li>Test</li>
<li>Test2</li>
<li>Test3
<ul class="ul2">
<li>Test3 - 1</li>
<li>Test3 - 2</li>
</ul>
</li>
<li>Test4
<ul class="ul2">
<li>Test4 - 1</li>
<li>Test4 - 2</li>
</ul>
</li>
</ul>
<div id="result"></div>

Categories