How to select a nested element in pure JavaScript? - javascript

This may be a simple thing but I'm struggling on how to target an element on a click event. I got it working with jQuery but I want to do it in pure JavaScript. Basically I have this list:
<ul class= 'my-todo-list'>
<li id="todo-1" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
<li id="todo-2" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
<li id="todo-3" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
</ul>
and my JavaScript looks like this:
document.querySelector('.todo a.delete').addEventListener('click', function(e){
var listElement = this.parentNode.parentElement;
var todoId = listElement.getAttribute('id').replace('todo-','');
alert(todoId);
});
What I want is if I click on a delete link I should see the id for the todo. For example if I click on the second delete link I should alert "todo-2".
Note: I tried document.querySelector('.todo a.delete') but it didn't work either.
The easy solution with jQuery is
$('.todo a.delete').live('click', function(){......})
But I want to do it in pure JavaScript. How can I do that?

You can add an event listener on the ul element and check if the element that triggered the click event contains the delete class. If it does, get the id attribute from the li element wrapping that particular element which triggered the event
const $ul = document.querySelector('ul');
$ul.addEventListener('click', (e) => {
if (e.target.matches('a.delete')) {
const li = e.target.parentElement.parentElement;
const id = li.getAttribute('id');
alert(id);
}
});
<ul>
<li id="todo-1" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
<li id="todo-2" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
<li id="todo-3" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
</ul>

You can use querySelectorAll and map to map event as
[...document.querySelectorAll('.todo a.delete')].map((item) => {
item.addEventListener('click', function(e){
var listElement = this.parentNode.parentElement;
var todoId = listElement.getAttribute('id').replace('todo-','');
alert(todoId);
})
});
[...document.querySelectorAll('.todo a.delete')].map((item) => {
item.addEventListener('click', function(e){
var listElement = this.parentNode.parentElement;
var todoId = listElement.getAttribute('id').replace('todo-','');
alert(todoId);
})
});
<ul class= 'my-todo-list'>
<li id="todo-1" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
<li id="todo-2" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
<li id="todo-3" class="todo">
<div class="actions">
Edit
Delete
</div>
</li>
</ul>

Using the event target might not work sometimes when there is not a propagation.
Here is a script that I made and it works 100% with all browsers and easy to use too:
(It works even with dynamic elements just like jQuery)
// Advanced custom dynamic real time event handler listener supported 100% cross-browsers
String.prototype.addRTListener = function(events, renderingFunction){
let selectors = this.toString();
events = events.trim().split(' ');
for(let even of events){
even = even.trim();
if(even.length>0){
switch(even){
case "focus":
even = "focusin";
break;
case "blur":
even = "focusout";
break;
}
document.addEventListener(even, function(e){
const path = e.path;
document.querySelectorAll(selectors).forEach(function(element){
for(let target of path){
if( target == element ){
if( renderingFunction && typeof renderingFunction == "function" ){
renderingFunction(target, e);
}
break;
}
}
});
return;
});
}
}
}
<button id="testBtn">Click Me</button>
<script>
// We can add DOMContentLoaded eventListener just in case our script loads after the DOM other wise you could always load your script before and it should work just like a charm
document.addEventListener('DOMContentLoaded', ()=>{
// it supports multiple events too
"#testBtn".addRTListener('focus dblclick', (target, e)=>{
console.log( target );
});
});
</script>

Related

Can't get button to delete Closes list item

i am in a bit of a pickle.
I am trying to get a button to delete its parent when clicked.
But when i run the code it won't respond. in fact , it doesn't delete anything at all.
I have specified that IF() event.target === deleteButton{ remove parent}
but it either doesn't recognise the event, or it fails to find the parent.
a bit of help would be greatly apreciated!
I
const todoListElement = document.getElementById('ordered-todo-list');
const form = document.getElementById('todo-form');
const deleteButton = document.querySelector('.delete');
todoListElement?.addEventListener("click", todoListEraser)
function todoListEraser( event:MouseEvent) {
console.log(event.target)
if (event.target === deleteButton ){
(event.target as HTMLElement).closest('li')?.remove();
}
else {
return console.log('did not work, try again');
}
}
<template id="list-item-template">
<li class="list-item">
<input type="checkbox" class="status-toggle" name="form-checkbox">
<p class="task-text"></p>
<button class="delete" >X</button>
</li>
</template>
<ol id="ordered-todo-list">
</ol>
am working in typescript.
My answer might be completely wrong for what you need, but this doesn't seem to be your complete code so I had to take some guesses.
There are two ways I can see this being done. If the <li> elements in your template are added to the list before this JavaScript executes, you can use the first method (commented out). If the <li> elements from your template are added after this JavaScript executes, the second method should work just fine.
/*
// Method 1
document.querySelectorAll(".delete").forEach(el => {
el.addEventListener("click", e => {
e.target.parentElement.remove();
});
});
*/
// Method 2
document.querySelector("#ordered-todo-list").addEventListener("click", e=> {
if(e.target.className.includes("delete")) {
e.target.parentElement.remove();
}
});
<ol id="ordered-todo-list">
<li class="list-item">
<input type="checkbox" class="status-toggle" name="form-checkbox">
<p class="task-text"></p>
<button class="delete" >X</button>
</li>
<li class="list-item">
<input type="checkbox" class="status-toggle" name="form-checkbox">
<p class="task-text"></p>
<button class="delete" >X</button>
</li>
<li class="list-item">
<input type="checkbox" class="status-toggle" name="form-checkbox">
<p class="task-text"></p>
<button class="delete" >X</button>
</li>
</ol>
Method 1 adds event listeners to the delete buttons themselves. Then it simply removes the parentElement.
Method 2 adds the event listener to the <ol> element and checks to see if the element clicked (e.target) is a delete button by checking the className attribute.

Onclick button is working only in first list

I want to duplicate list item with all its child elements, here is the my sample code. only in first list, on click is working, duplicated list's button is not working
document.querySelectorAll(".btnMe").forEach(el => {
el.addEventListener('click', () => {
var myDiv = document.querySelector(".list");
var divClone = myDiv.cloneNode(true);
document.querySelector('#lists').appendChild(divClone);
})
})
<ul id="lists">
<li class="list">
<p>Test</p>
<button class="btnMe">Click Me</button>
</li>
</ul>
Instead of dealing with binding events, use event delegation.
var wrapper = document.querySelector("#lists")
wrapper.addEventListener('click', (evt) => {
var btnClicked = evt.target.closest("button.btnMe");
if (btnClicked) {
var myDiv = document.querySelector(".list");
var divClone = myDiv.cloneNode(true);
wrapper.appendChild(divClone);
}
})
<ul id="lists">
<li class="list">
<p>Test</p>
<button class="btnMe">Click Me</button>
</li>
</ul>
cloneNode copies all of the node's attributes and values but not their event listeners added using addEventListener(). So you need to add these event listeners again.
Your document.querySelectorAll() runs only the first time you load the page so a simple document.querySelector() should work and in that case you don't even need the forEach() method.
If I understand correctly, you want to clone the element the button you clicked is in. If the button is the child of the li, then you can clone node.parentNode. After you cloned it, you can add the event listener function to it.
function cloneParent() {
let clone = this.parentNode.cloneNode(true);
clone.querySelector('.btnMe').addEventListener('click', cloneParent);
document.querySelector('#lists').appendChild(clone);
}
document.querySelector('.btnMe').addEventListener('click', cloneParent);
<ul id="lists">
<li class="list">
<p>Test</p>
<button class="btnMe">Click Me</button>
</li>
</ul>
Adding only one event listener and using event delegation, as #epascarello described, should be the preferred way.

How to use jQuery toggle method dynamically?

In my project, there are so many jQuery toggles needed for changing text and icons. Now I’m doing that using:
$("#id1").click(function () {
//Code to toggle display and change icon and text
});
$("#id2").click(function () {
//Same Code to toggle display and change icon and text as above except change in id
});
The problem is that I got so many to toggle, the code is quite long but all I change for each one is the id. So I was wondering if there is any way to make this simple.
Below is a sample pic. I got so many more in single page.
There are two issues here.
How to run the same action on multiple elements
How to know which element you've clicked so that you can run a relevant action on it. (most of the existing answers skip this part).
The first is to use a class for each of the elements you want to click, rather than wire up via an id. You can use a selector similar to [id^=id] but it's just cleaner to use a class.
<div id="id1" class="toggler">...
which allows you to:
$(".toggler").click(function() ...
the second is it associate the clickable with the item you want to toggle. There are many ways to do this, my preferred option is to associate them with data- attributes, eg:
<div class="togger" data-toggle="#toggle1">...
which allows you to:
$(".toggler").click(function() {
$($(this).data("toggle")).toggle();
});
The key here is that this is the element being clicked, so you can do anything else with this such as show/hide an icon inside or change colour.
Example:
$(".toggler").click(function() {
$($(this).data("toggle")).toggle();
$(this).toggleClass("toggled");
});
.toggler { cursor: pointer }
.toggled { background-color: green }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="toggler" data-toggle="#t1">T1</div>
<div class="toggler" data-toggle="#t2">T2</div>
<div class="toggler" data-toggle="#t3">T3</div>
<hr/>
<div id="t1" style='display:none;'>T1 content</div>
<div id="t2" style='display:none;'>T2 content</div>
<div id="t3" style='display:none;'>T3 content</div>
Oh,Can you use a class instead of id?
<ul>
<li class="idx">A</li>
<li class="idx">B</li>
<li class="idx">C</li>
</ul>
$(".idx").click(function(e){
//Code to toggle display and change icon and text
let target = e.target;
//You can do all what you want just base on the `target`;
});
You can store the queries in an array, and iterate over them to perform the same JQuery operation on all of them
let ids = ["#id1", "#id2", "#id3", "#randomID"]
ids.forEach((id) => {
console.log($(id).html())
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li id="id1">A</li>
<li id="id2">B</li>
<li id="id3">C</li>
<li id="randomID">D</li>
</ul>
Or (If like your example) and all of the id's are actually id1, id2, id3, ... etc.
let id = "id";
let n = 3; //amount of id's
for (let i = 1; i <= n; i++) {
console.log($("#" + id + i).html())
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
<li id="id1">A</li>
<li id="id2">B</li>
<li id="id3">C</li>
</ul>
You can try the below code.
var num = $("#myList").find("li").length;
console.log(num)
for(i=0;i<num;i++){
$("#id"+ i).click(function(e){
let target = e.target;
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<ul id="myList">
<li id="id1">A</li>
<li id="id2">B</li>
<li id="id3">C</li>
</ul>

Show divs with jQuery

I have this code http://jsfiddle.net/cwahL1tz/
My HTML
<ul class="myFilters">
<li data-type="A">A</li>
<li data-type="B">B</li>
<li data-type="C">C</li>
</ul>
<div class="filter">
<ul class="title">
<li>Assurance</li>
<li>Couverture</li>
<li>Banque</li>
<li>Alimentation</li>
</ul>
<div id="Assurance" class="category">
<ul>
<li>Groupama</li>
</ul>
</div>
<div id="Couverture" class="category">
<ul>
<li>Try it !</li>
</ul>
</div>
<div id="Alimentation" class="category">
<ul>
<li>AN example</li>
</ul>
</div>
</div>
Here's my JS script
jQuery(function ($) {
$('.myFilters li').click(function(){
$(".category").hide();
var v = $(this).text()[0]
$('.title li').hide().filter(function(){
return $(this).text().toUpperCase()[0] == v;
$(".category:first").show();
}).show()
})
$("a[data-toggle]").on("click", function(e) {
e.preventDefault(); // prevent navigating
var selector = $(this).data("toggle"); // get corresponding element
$(".category").hide();
$(selector).show();
});
});
It works fine but I trying to arrange some stuff but I'm stuck.
When I load the page all the links and divs appears, I just only want the divs of the first letter appear.
And when I click on C for example, I want the first div to show from the first link.
Thanks for your help !
EDITED:
On load:
$('.myFilters li:first').trigger('click');
And inside its click:
.first().find('a[data-toggle]:first').trigger('click');
jsfiddle DEMO
You can simply do that with first selecting the element with the right selector and then you can trigger the click event manually :
Show the Assurance content on load :
$('a[data-toggle="#Assurance"]').click();
Show first content on click :
$('.myFilters li').click(function(){
$(".category").hide();
var v = $(this).text()[0]
$('.title li').hide().filter(function(){
return $(this).text().toUpperCase()[0] == v;
}).show()
$('a[data-toggle]:visible:first').click();
})
Updated jsFiddle
Without going into any more javascript, you can display the content the way you would like using css selectors:
.category {display:none;}
.category:nth-of-type(1) {
display:block;
}
http://jsfiddle.net/5ageasm4/1/
Is this what you want. Check the Fiddle
I am basically just triggering the first item in the li
$(".title > li:first").find("a[data-toggle]").trigger("click");
and i am doing the same with the category.
EDIT
I updated my Fiddle
NEW EDIT
Created a new Fiddle, this one just removes all the duplicate code.
So basically i created these 2 functions
that.selectFirstElem = function(selector){
selector.find("a[data-toggle]").trigger("click");
};
that.loadFirstDataToggle = function(input){
return $('.title li').hide().filter(function(){
return $(this).text().toUpperCase()[0] == input;
}).show();
};
And those 2 functions you can just call in the places where you need it.

Javascript event handler on list item li parent node

I'm new to javascript and I wanted to create an event onclick to list items. The problem is that I want to create an event to the li tag, but it keeps firing when I click the descendent ul's.
Here goes part of my code:
<li id="1660761" class="HTMLFirstLevel HTMLHorizontalArrowDown">
<ul id="ul1223945" class="HTMLItem">
<li id="1490659" class="HTMLRemainingLevels"></li>
<li id="483463" class="HTMLRemainingLevels"></li>
<li id="80919" class="HTMLRemainingLevels"></li>
<li id="1280053" class="HTMLRemainingLevels"></li>
<li id="1799353" class="HTMLRemainingLevels"></li>
<li id="1882209" class="HTMLRemainingLevels"></li>
<li id="462917" class="HTMLRemainingLevels"></li>
</ul>
</li>
<li id= ......>
<ul....>
<ul...>
</li>
and my javascript:
var parentNode = document.getElementById('1660761');
parentNode.addEventListener("click",function(e) {
alert('Hi There');
});
}
Now I only want it to fire on the item li with the id 1660761, and not the items inside the list.
The list is an imported component and I can't create events inside the html, that's why I'm accessing it outside with javascript.
Now here's how I've done it by scaning the div by tag name and then adding a "click" event listener if the content equals the tag inner html that I was searching for.
I leave the rest of the html that it's important to this aproach:
<div id="MainMenu" class="HTMLMenuContainer HTMLMenuHorizontal">
<ul id="ul1351387" class="HTMLMenu">
<li id="1660761" class="HTMLFirstLevel HTMLHorizontalArrowDown">
<a href="#">
<span>Back Office</span>
</a>
<ul id="ul1172716" class="HTMLItem">
<li id="1490659" class="HTMLRemainingLevels">
<a href="#">
<span>
Some submenu Here
</span>
</a>
</li>
.....
and the code:
var divs = document.getElementsByClassName('HTMLMenuHorizontal');
var span = divs[0].getElementsByTagName('span');
//I iterate till 19 cause its more than all the spans in the page.
for(var i=0; i<20; i++) {
var sp= span[i];
if(sp.innerHTML==('Back Office')){
sp.addEventListener("click",function back(){
//do something here like
alert('Back Office');
});
}
}
This works fine and it doesn't fire on the itens inside.
This works because in my case the itens doesn't change the content, only the visibility.
I do the same for all the other itens that have descendents.
Thank you all.
Below is my jQuery code for this problem:
$(function(){
$("li.1660761").live("click", onListItemLink);
}
function onListItemLink(){
alert('Hello World!');
}
This one is for JavaScript:
var parentNode = document.getElementById('1660761');
parentNode.onclick = onListItemLink;
function onListItemLink(){
alert('Hello World!');
}
take a look at this page to undersand correctly:
capture event
and what's function(e-->??)
I hope it helps.
$('#1660761').unbind('click').click(function(e) {
if (e.target !== this) return;
alert('Hey There!');
});
Try This code : http://jsfiddle.net/sd5LZ/

Categories