I'm trying to identify a li element from a group of li inside of div
<div id="group">
<li></li>
<li></li>
<li></li>
</div>
its quite simple tho, i could give each li an unique id and this problem would be over. like
var listItem1,2,3 = document.getElementById('liItem1,2,3') etc
listItem1,2,3.addEventListener('click',function);
this might be handy when it comes to 1,2 or 3 elements but this is all static and when it start to scale its not possible anymore, Instead im trying to make use of NodeList.
var nodeList = document.getElementById('group').getElementsByTagName('li');
now i will have a NodeList with li 0, li 1, li 2
the problem comes now becouse i donĀ“t know how to trace which li is being clicked.
nodeList.addEventListener('click',function);
wont work here becouse it dosent know which one is being clicked at here.
nodeList[0].addEventListener('click',function);
is the same solution as above. How can i trace which of the li is being clicked at? only plain/raw javascript
To find the index of an element in response to an event, I'd suggest delegating the event-handling to an ancestor (rather than individually binding an event-handler to multiple child-elements):
// 'event' is passed in automagically (in non IE browsers, haven't tested IE):
function getIndexFrom(event){
// event.target is the element upon which the event was triggered:
var clicked = event.target,
// finding all the children of the parent of the clicked-element
// (could use 'this.children', as 'this' will be the 'ul' in this demo):
children = clicked.parentNode.children;
// iterating over those child elements:
for (var i = 0, len = children.length; i < len; i++){
// if the clicked element is the current element:
if (children[i] === clicked){
console.log('index is: ' + i)
// we return 'i' as the index:
return i;
}
}
// this shouldn't happen, assuming we're looking at the right group
// of elements, but it's there as an in-case and for debugging:
return false;
}
document.getElementById('group').addEventListener('click', getIndexFrom);
JS Fiddle demo.
References:
EventTarget.addEventListener().
for... loop.
ParentNode.children.
Related
I am dynamically appending to the main to-do "list" ul.
<li>
<div class="description">
<input class="descriptionInput" type="text">
</div>
</li>
How do I know I'm inside the 2nd li when performing an event on the description input? (trying to retrieve the text of the li the input is inside of)
I can't for the life of me figure out how to sort of select "backwards".
Would appreciate a few hints or terms to google.
Thanks!
Finding the LI element
the this value inside an event handler is the object on which the handler has been attached, whether by addEventListener or by assigning a function to an "onEventName" property/attribute. But if event handling is delegated, this may not be the object on which the event was fired.
the target property of theevent object is the object on which the event was fired. (srcElement is a proprietary and non standard alias of target.)
So if you are listening for an event fired on the input, e.g. "change", the li element is in the parent node chain, which you could code directly in a handler as
function (event) {
let li = event.target.parentNode.parentNode;
//... do stuff with li
which is brittle by assuming a particular element structure, or by searching the parent node chain:
function (event) {
let li;
for( li = event.target; li = li.parentNode;) {
if( li.tagName == "LI") {
break;
}
}
// process li ....
which finds the LI element or leaves li set to null.
Finding its position in the List
If the unordered or ordered element does not have its own id, you can find it in the parent node chain of the input element in similar fashion to finding the LI element above (i.e. by coding directly or searching).
The position of the LI element in the OL or UL list entries can be found by searching and matching it with an element in the children property of the OL/UL element.
Note that the node.children collection is live and should not be used while sorting LI elements by dynamically moving their positions around in the DOM.
You can use index() function for this.
If you are working with the input field under the list item. Then just select like this:
$("input.descriptionInput").parents("li").index();
I think it will work for you.
Your event should have a srcElement property, which contains the element from which the element originated. From there you can traverse through the DOM to get to the grand-parent element.
for example:
myElement.addEventListener('keyup',function(event){
let input=event.srcElement;
let div=input.parentElement;
let li=div.parentElement;
console.log(li.textContent);
//or just (event.srcElement.parentElement.parentElement.textContent)
})
Of course this only works if you're sure that the input will always be the grand-child of the li. If another element gets added for whatever reason, your code will break. It's safer to search for the element
I would just search for the element using a while loop.
let element=event.srcElement;
//while the element isn't an li, and it has a parentElement;
while(element.tagName !=='LI' && element.parentElement){
element=element.parentElement;
}
console.log(element) //should have found the first li ancestor
Try using .closest function to navigate to the nearest li and then use index() to determine the index. Also, since you are dynamically adding li, you might need to add change listener to the DOM.
<ul>
<li>
<div class="description">
<input class="descriptionInput" type="text">
</div>
</li>
<li>
<div class="description">
<input class="descriptionInput" type="text">
</div>
</li>
</ul>
<script>
$(document).on('change','ul li input.descriptionInput', function(){
var index = $(this).closest('li').index();
alert(index);
});
</script>
Example : https://jsfiddle.net/xpvt214o/874826/
Im trying to attach an event listener to each li element, but instead I want to attach it to the parent ul, to avoid confusion, if I remove or add li elements.
var theParent = document.getElementById("parent-list");
theParent.addEventListener("click", function(e) {
// e.target is the clicked item!
if(e.target && e.target.nodeName == "LI") {
console.log("li has been clicked, cool!");
console.log("is this George or Smith!");
}
});
This works fine. However the way I add my li elements to the parent is by creating a custom object, as I need to hold some state information:
var Person = function(name, favColor){
var elem = document.createElement('li');
theParent.appendChild(elem);
this.name = name;
this.color = favColor;
};
var listItems = [];
listItems.push(new Person("Smith", "blue"));
listItems.push(new Person("George", "red"));
One of the solutions I was thinking is to add state information to the li elements by using data- attribute, but I feel like thats going to get messy If I am going to have so many properties.
right now your JavaScript object and dom elements are not connected at all. you are just using a JavaScript object to create the dom element.
data attribute is a good way to go, you can maybe just have a data attribute with an object index, and save all your objects in a JavaScript array. than on click, retrieve the JavaScript object with the correct index from the array.
Please let me know if this is enough to get you going or you need more.
I'm trying get the index of a tab in YUI using delegate along with the indexOf method - specifically I want to remove a tab on the click of an image housed inside of an li element:
var tabView = new Y.TabView({
srcNode: '#data-table'
});
...
//creating tabs
...
var removeTab = function(e) {
var tabList = e.container.all('li'); //returns a list of the 'li' elements/tabs
tab = tabView.indexOf(e.currentTarget);
alert(tab); //returns -1 (node not found)
//tabView.remove(tab);
}
Y.one('#data-table').delegate('click', removeTab, 'img');//on click of the img, launch removeTab
I don't believe YUI has a simpler way of doing this-most guides I've found are outdated or don't implement removing tabs the same way.
The problem is that the delegate handler is set on img element - so e.currentTarget will refer to an <img> element (that was clicked).
With e.container.all('li'), however, you retrieve the collection of <li> elements. No element in this collection, naturally, can be equal to <img>. That's why indexOf fails.
One possible solution is finding the <li> parent of <img> element first, then check its index:
var liClicked = e.currentTarget.ancestor('li');
var tabIndex = tabList.indexOf(liClicked);
I have an requirement in JS where, i want to get the count of li element which has the same inner value. What is the best way to do it ? I know we can achieve this in Jquery easily using find and contains but i don't want to use jquery.
I want the length of li elements has the Same Value.
For Eg: Say i want to find out how many LI has the value 'A'.
Below is the JS i have tried, which i think is not the best cos if i have say around 10,000 LI then i will have to loop through all the elements get their values and check if its what i want or no, which will surely hit the performance.
Note : LI element is added runtime with their Value.
HTML
<ul class="s_1" id="ULE">
<li class="r1">A</li>
<li class="r1">A</li>
<li class="r1">B</li>
<li class="r1">A</li>
</ul>
JS
var LI = document.getElementsByClassName('r1');
var cnt = 0;
for(var i=0;i<LI.length;i+=1){
if(LI[i].innerHTML == 'A'){
cnt += 1;
}
}
if(cnt === 4)
alert('working good!!');
JS Fiddle
I don't think there is a better way to improve the performance. If you have nothing but the DOM to work with (e.g. if the data is from user input), and not the underlying data structure from which you created the data, AFAICT there is no other structure to collect all of the elements than into an array-type structure, which will then require O(n) time to check every element.
Rather than have a count target, which is therefore dependent on the amount of list elements, try instead a function to handle the data, which increases the convenience somewhat:
function isEveryElementEqual(nodeList) {
val = nodeList[0].innerHTML;
for (var i=1; i<nodeList.length; i++) {
if (nodeList[i].innerHTML !== val) return false;
}
return true;
}
var LI = document.getElementsByClassName('r1');
console.log(isEveryElementEqual(LI)); // Returns false with the above HTML
You don't have to search all <li> in the document. getElementsByClassName can be applied to an element, it will then only search within that element. So you can do:
var LI = document.getElementById('ULE').getElementsByClassName('r1');
DEMO
You can also simplify this with querySelectorAll:
var LI = docuement.querySelectorAll('#ULE .r1');
I'm trying to remove the first li in an ol using the DOM removeChild(). But for some reason it doesn't work.
This is my javascript:
document.getElementById('queue').removeChild(
document.getElementById('queue').childNodes[0]
);
And this is my HTML:
<ol id="queue">
<li>Surprised Kitty (Original)<span class="nodisplay">0Bmhjf0rKe8</span></li></ol>
I tried alerting the childNodes[0], and it returns [Object Text], which seems a bit weird, when I was expecting just the object.
Hope I've been clear.
Try this one-liner:
document.getElementById('queue').removeChild(document.getElementById('queue').getElementsByTagName('li')[0]);
With expanded explanation:
var queue = document.getElementById('queue'); // Get the list whose id is queue.
var elements = queue.getElementsByTagName('li'); // Get HTMLCollection of elements with the li tag name.
queue.removeChild(elements[0]); // Remove the child from queue that is the first li element.
Between the <ol id="queue"> and the <li> tag are spaces and a line break. These make up a text node. The first child of the #queue element is therefore a text node.
You can use the .children property instead of .childNodes, it only considers element nodes, or iterate over all child nodes until you find the first li node, like suggested by dystroy.
remove all lists contained by queue:
var list=document.getElementById('queue');
list.removeChild(list.getElementsByTagName('li')[0]);
Child nodes aren't just the elements you think about but also the text nodes. You can iterate over the nodes and remove the first LI.
var p = document.getElementById('queue');
for (var i=0; i<p.childNodes.length; i++) {
if (p.childNodes[i].tagName=='LI') {
p.removeChild(p.childNodes[i]);
break;
}
}
Demonstration
Note that this is more an explanation than the most practical solution. Javascript has other iteration solutions for you, among them getElementsByTagName, so you may do this :
var p = document.getElementById('queue');
p.removeChild(p.getElementsByTagName('li')[0]);
Demonstration
You can use this single line of code. Which will delete always first child element of the parent.
JavaScript:
document.querySelector('#queue').firstElementChild.remove();
HTML:
<ol id="queue">
<li>
Surprised Kitty (Original)
<span class="nodisplay">0Bmhjf0rKe8</span>
</li>
</ol>