Ordering a list of elements without losing the event handler - javascript

I have this list :
<ul>
<li id="6">
list 6: somethings
</li>
<li id="2">
list 2: somethings
</li>
<li id="4">
list 4: somethings
</li>
<li id="5">
list 5: somethings
</li>
<li id="0">
list 0: somethings
</li>
</ul>
and I'd like (with Javascript/jQuery) order these elements by the id (ASC) keeping the event handler for each element.
Is it possible? How can I do it?

You could just assign the ID's into an array and use sort():
var a = [];
$("ul li").attr('id',function(i,e){
a.push(e);
});
$.each(a.sort(),function(i,e){
$("#"+e).appendTo('ul');
});
You are never removing them from the list, just moving them around. Click handler stays intact:
http://jsfiddle.net/niklasvh/nVLqR/

This should work fine. Using detach preserves any events associated with the element. You then use a custom sort function to compare the id attributes of each li element.
var ul = $("ul");
var li = ul.children("li");
li.detach().sort(function(a, b) {
var compA = $(a).prop("id");
var compB = $(b).prop("id");
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
});
ul.append(li);
See an example fiddle here. A click event is attached to the li with ID "6". After the list has been reordered, that click event is still handled.

I think to order them you'll had to remove and add them back into the DOM... and therefore you'll certainly lose the event handler. Are you in control of the handler, can you rebind or use live() instead?
The alternative would be to absolutely position the li elements and use the css position properties (top, right, bottom, left) to move them around, this will keep them in the same order in the DOM, but render them in your desired order.t

This example work for me:
var mylist = $('.item').detach().sort(function (a, b) {
return $(a).find(selector).html() > $(b).find(selector).html();
});
$("#container").html(mylist);
or if you want to sort with other informations:
var mylist = $('.item').detach().sort(function (a, b) {
return $(a).find(selector).attr('data-title')() > $(b).find(selector).attr('data-title');
});
$("#container").html(mylist);

Related

How to toggle list element through DOM Events in Javascript

I have a list containing three elements inside a . I'm trying to toggle on and off a CSS class which strikes the text when the list element is clicked. I've created a function and used a for loop to check for the array index of the list element that was clicked.
I've defined the following:
List Items:
<ul>
<li class="items line-through">Watch</li>
<li class="items line-through">Shoes</li>
<li class="items line-through">Cake</li>
</ul>
var list = document.getElementsByClassName("line-through");
Created a function to toggle the array element from the list based on the index when "click" event happens
function checkItem(index) {
console.log("event = " + index)
list[index].classList.toggle("line-through");
console.log("list = " + index);}
for (var i = 0; i <= list.length; i++) {
console.log("i= " + i);
list[i].addEventListener("click", checkItem(i));}
So after my logic I've declared the for and i index with a value of zero, which is the value of the first list in the array. Then I've added the prints to check if it runs and then checked if list 0, 1, or 2 was clicked. If yes, run function and toggle list[i]. If not, i++ and verify the next one.
The problem is that now it immediately adds the toggle to the first and last items from the list. I've also tried to use querySelectorAll("li") but then it says that list[i] is undefined, which I do not understand why, cause when verifying list[0] in the console it present the first element.
You could refactor your code and use the this keyword to target the clicked element, like this:
document.addEventListener('DOMContentLoaded', () => {
// Execute the code after the HTML content is loaded
const myList = document.querySelector('.my-list');
const listElements = Array.from(myList.children);
listElements.forEach( li => {
li.addEventListener('click', checkItem);
});
function checkItem() {
// The `this` keyword is the clicked element
this.classList.toggle('line-through');
}
});
<ul class="my-list">
<li class="items line-through">Watch</li>
<li class="items line-through">Shoes</li>
<li class="items line-through">Cake</li>
</ul>
This will add the event listener to all the children elements of the list and toggle the line-through class on the clicked one.
Keep in mind that when manipulating the DOM, you should wrap your JS code inside a DOMContentLoaded event listener to prevent selecting elements that are not yet present on the page.

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 get the items inserted in the DOM using DOMNodeInserted

I want to get the values of items in a Dynamically generated DOM using DOMNodeInserted.
Here is my code.The items #I want to get the values are li eg
<div id="demo">
<ul>
<li class="req">Chemistry</li>
<li class="req">English</li>
<li class="req">Maths</li>
</ul>
</div>
Here is the code
$('#demo').on('DOMNodeInserted', function(e) {
var that = $(this);
if ($(e.target).is('.req')) {
alert(oneoftheitemsintheli);
}
});
I want to get on of the items in the li eg Maths, Chemistry etc. I need to know how to get the items.
Thanks
Given that each li has the class req, you can use each to iterate over them and get the text value - or any other property you need.
$('#demo').on('DOMNodeInserted', function(e) {
$('.req').each(function() {
alert($(this).text());
});
});
Example fiddle

Determine if element has children with X tag

I am looping through some elements and need to determine if an element has a child(grandchild?) with the li tag, like in the information element below. The li elements will vary in id so I am not referencing them that way. I am currently looping through the li elements and if I check for children it always returns true because there are "a" tag children, I just want to check for 'lil' tag children.
<ul id="navMenu">
<li id="home">Home</li>
<li id="information">Information
<ul>
<li>Credits</li>
<li>Lorem Ipsum</li>
</ul>
</li>
<li id="contact">Contact</li>
</ul>
Here is what I have now...
$('#test').load('../common.html #navMenu', function() {
$.each($("#test #navMenu li"), function(i,v) {
var theElement = $(v);
if ($(theElement).children('li')){
alert('This Element has children');
}
});
});
Thank you once again,
Todd
You could try -
$('#test').load('../common.html #navMenu', function() {
$.each($("#test #navMenu li"), function(i,v) {
var theElement = $(v);
if ($(theElement).find('li').length > 0){
alert('This Element has children');
}
});
});
find will go deeper into the current element than children which only searches one level down.
$(theElement).children('li') returns a jQuery object which always passes an if clause, even when it's empty.
Moreover, you want .find, since .children only returns direct children and not grandchildren.
So:
if ($(theElement).find('li').length > 0) {
or:
if ($(theElement).find('li').length) {
// 0 won't pass an if clause, and all other numbers will, so you can eliminate `> 0`
Given:
> var theElement = $(v);
> if ($(theElement).children('li')) {
> alert('This Element has children');
> }
doesn't $(v) return an jQuery object? So $(theElement) is redundant.
Anyhow, if v is a reference to one of the elements passed to .each, then you can replace all of the above with:
if (v.getElementsByTagName('li').length) {
/* v has li descendants */
]
you could also add the extra li to your query: "#test #navMenu li li"

javascript looping through li attributes and changing values per item

I want to loop through a bunch of <li> elements and update the value held in it, and show this on the document.
The reason i'm doing this is because I want to sort a list of elements in an order. I do this using Sortable in JQuery.
<li> 1 </li>
<li> 2 </li>
<li> 3 </li>
<li> 4 </li>
<li> 5 </li>
the order may become:
<li> 3 </li>
<li> 1 </li>
<li> 4 </li>
<li> 2 </li>
<li> 5 </li>
Then clicking a button i would like my JS function to change the value of the li items back to 1,2,3,4,5. Its worth noting i am not looking for a solution to revert back to how the list was before.
Thanks.
This should work (as of jQuery 1.4):
$('.your_list li').text(function (index) {
return index + 1;
});
I think you would rather reorder the list items than their contents. This way, you don't lose any of the li's attributes/classes/... And you can keep the DOM's elements intact, only change the order of some ul's children.
I found a nice little snippet do do it:
http://www.onemoretake.com/2009/02/25/sorting-elements-with-jquery/
The only thing you still need to do is remember the initial order
function mysortA( a, b ) {
var compA = $(a).text().toUpperCase();
var compB = $(b).text().toUpperCase();
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
}
function mysortB( a, b ) {
return a.originalindex < b.originalindex ? -1
: a.originalindex > b.originalindex ? 1
: 0;
}
You have to initialize:
var ul = $('ul');
var listitems = ul.children('li').get();
// remember original index
listitems.each( function(index, li){ li.originalindex=index; } );
Then you can sort one way:
// sort them using your sort function
listitems.sort( mysort )
// rebuild the list container
listitems.each( function(idx, itm) { mylist.append(itm); });
And sort back:
var ul=$('ul');
ul.children('li').get().sort(mysortB).each(function(i,li){ ul.append(li); });
Note: untested - grab the idea.

Categories

Resources