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
Related
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>
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>
<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');
});
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>
The code expands and collapses a list in which list items can have sublists. Any ideas to refactor this code - especially the toggling part. Is it necessary to use closures here ?
$(function()
{
$('li:has(ul)')
.click(function(event){
if (this == event.target)
{
var that = this;
$('li:has(ul)').children().filter(':not(:hidden)').parent().each(function(x){
if(this != that)
toggleList(this);
});
toggleList(this);
}
})
.css({cursor:'pointer', 'list-style-image':'url(plus.gif)'})
.children().hide();
$('li:not(:has(ul))').css({cursor: 'default', 'list-style-image':'none'});
});
function toggleList(L)
{
$(L).css('list-style-image', (!$(L).children().is(':hidden')) ? 'url(plus.gif)' : 'url(minus.gif)');
$(L).children().toggle('fast');
}
EDIT:
The script works on the following HTML snippet (source: jQuery in Action). Actually I was trying to extend the script given in the book.
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>
Item 3
<ul>
<li>Item 3.1</li>
<li>
Item 3.2
<ul>
<li>Item 3.2.1</li>
<li>Item 3.2.2</li>
<li>Item 3.2.3</li>
</ul>
</li>
<li>Item 3.3</li>
</ul>
</li>
<li>
Item 4
<ul>
<li>Item 4.1</li>
<li>
Item 4.2
<ul>
<li>Item 4.2.1</li>
<li>Item 4.2.2</li>
</ul>
</li>
</ul>
</li>
<li>Item 5</li>
</ul>
Your code doesn't work for me in Safari. When I click on a sub-list, the top-list is toggled.
How about:
$(document).ready(function() {
$('li:has(ul)').click(function(event) {
$(this).css('list-style-image', $(this).children().is(':hidden') ? 'url(minus.gif)' : 'url(plus.gif)')
$(this).children().toggle('fast')
return false
})
.css({cursor:'pointer', 'list-style-image':'url(plus.gif)'})
.children().hide()
$('li:not(:has(ul))').click(function(event) { return false })
.css({cursor:'default', 'list-style-image':'none'})
})