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.
Related
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>
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>
I am working on a site with an accordion style script i've found. I'm quite new to jquery/javascript, so please bear with me...
My basic page opens an accordion, where the first one is locked open, and the other ones opens/closes if pressed. What keeps my top panel locked open is a
<li class="locked">.
The other panels have only
<li>
no class. I guess I can call them
<li class="somethingelse">
I want a button in my top panel that changes all the
to
<li class="locked">, so that the user can view the entire site.
I have tried:
<div>
<ul>
<li class="locked">Something
<button onclick="myFunction()">Click me</button>
</li>
<li id="abcd" class="somethingelse">somethingelse</li>
<li id="abcd"class="somethingelse">somethingelse</li>
</ul>
</div>
<script>
function myFunction() {
document.getElementById("abcd").class = "locked";
}
</script>
In plain JavaScript, I'd suggest:
function myFunction() {
// retrieves a NodeList of all <li> elements that do not
// have the 'locked' class-name:
var liElements = document.querySelectorAll('li:not(.locked)');
// uses Array.prototype.forEach to iterate over the array-like
// NodeList:
Array.prototype.forEach.call(liElements, function (li, index, list) {
// first argument ('li' ): the current array-element,
// second argument ('index'): unused, the index of the current
// array-element in the array,
// third argument ('list'): the array itself
// adding the 'locked' class-name to the list of classes
// of the current node:
li.classList.add('locked');
});
}
li {
opacity: 0.3;
}
li.locked {
opacity: 1;
}
<div>
<ul>
<li class="locked">Something
<button onclick="myFunction()">Click me</button>
</li>
<li class="somethingelse">somethingelse</li>
<li class="somethingelse">somethingelse</li>
</ul>
</div>
<script>
function myFunction() {
var liElements = document.querySelectorAll('li:not(.locked)');
Array.prototype.forEach.call(liElements, function (li, index, list) {
li.classList.add('locked');
});
}
</script>
Further, I'd suggest binding the event-handling in JavaScript, rather than using in-line HTML attributes (onclick, etc), which makes for easier long-term maintenance (since everything's updated in the same place, and you don't have to remember where all the event-handling was assigned):
function myFunction() {
var liElements = document.querySelectorAll('li:not(.locked)');
Array.prototype.forEach.call(liElements, function(li, index, list) {
li.classList.add('locked');
});
}
// finding the first element that matches the CSS selector.
// adding the named function (myFunction) as a 'click'
// event-handler:
document.querySelector('li.locked button').addEventListener('click', myFunction);
li {
opacity: 0.3;
}
li.locked {
opacity: 1;
}
<div>
<ul>
<li class="locked">Something
<button onclick="myFunction()">Click me</button>
</li>
<li class="somethingelse">somethingelse</li>
<li class="somethingelse">somethingelse</li>
</ul>
</div>
References:
CSS:
Negation (:not()) pseudo-class.
JavaScript:
document.querySelector().
document.querySelectorAll().
EventTarget.addEventListener().
Element.classList.
You should be changing element class as below
document.getElementById("abcd").className = "locked";
If you want to select more then one element use class name:
var nodes = document.getElementsByClassName("somethingelse");
So your final function will look like:
function myFunction() {
var nodes = document.getElementsByClassName("somethingelse");
var arr = Array.prototype.slice.call(nodes);
arr.forEach( function(node) {
node.className = "locked";
});
}
li {
color: #000000;
}
li.locked {
color: #ff0000;
}
<div>
<ul>
<li class="locked">Something
<button onclick="myFunction()">Click me</button>
</li>
<li id="abcd" class="somethingelse">somethingelse</li>
<li id="abcd" class="somethingelse">somethingelse</li>
</ul>
</div>
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/
So I've got 2 <ul> containers each with id's. Inside of them are a list of <li> elements.
The first <ul> is <ul id="coaches-list">. The second is <ul id="players-list">.
There are tags within each <li> that have an id called close (which is a link that I'm using as my selector), which will delete each <li> node once clicked. I'm trying to target each <ul> container to see where it is coming from.
My HTML is:
<!-- coaches box -->
<div class="box">
<div class="heading">
<h3 id="coaches-heading">Coaches</h3>
<a id="coaches" class="filter-align-right">clear all</a>
</div>
<ul id="coaches-list" class="list">
<li><span>Hue Jackson<a class="close"></a></span></li>
<li class="red"><span>Steve Mariuchi<a class="close"></a> </span></li>
</ul>
</div>
<!-- players box -->
<div class="box">
<div class="heading">
<h3 id="players-heading">Players</h3>
<a id="players" class="filter-align-right">clear all</a>
</div>
<ul id="players-list" class="list">
<li><span>Steve Young<a class="close"></a></span></li>
<li><span>Gary Plummer<a class="close"></a></span></li>
<li><span>Jerry Rice<a class="close"></a></span></li>
</ul>
</div>
My remove tag function in jQuery is:
function removeSingleTag() {
$(".close").click(function() {
var $currentId = $(".close").closest("ul").attr("id");
alert($currentId);
// find the closest li element and remove it
$(this).closest("li").fadeOut("normal", function() {
$(this).remove();
return;
});
});
}
Whenever I click on each specific tag, it's removing the proper one I clicked on, although when I'm alerting $currentId, if I have:
var $currentId = $(".close").closest("ul").attr("id");
It alerts 'coaches-list' when I'm clicking on a close selector in both <ul id="coaches-list" class="list"></ul> and <ul id="players-list" class="list"></ul>
If I change that to:
var $currentId = $(".close").parents("ul").attr("id");
It has the same behavior as above, but alerts 'players-list', instead.
So when using closest(), it's returning the very first <ul> id, but when using parents(), it's returning the very last <ul> id.
Anyone know what is going on with this whacky behavior?
It's expected behavior.
You should use:
var $currentId = $(this).closest("ul").attr("id");
$(this) points at the clicked .close.
$(".close") points at the first one found.
It's because you run that selector from click handler you should use this instead:
var $currentId = $(this).closest("ul").attr("id");
Try using this function to get the parent:
var $currentId = $(this).parents().first();
I've never used the .closest() function but according to jQuery what you have specified should work. Either way, try that out and tell me how it goes.
You also need to make it so that it selects the current element by using $(this)