$('ul.mylist').on('click', 'li', function(){})
I want to transform this jQuery line into vanilla but how I can do this ? I looked at jQuery source but I'm confused. Is there simple way to do this in pure Javascript ?
Some features here are browser dependent, but this is basically how it goes...
// select all UL elements with the `mylist` class
var uls = document.querySelectorAll('ul.mylist');
// Iterate the collection, and bind the `click` handler
for (var i = 0; i < uls.length; i++)
uls[i].addEventListener('click', handler, false);
// The `click` handler iterates starting with `event.target` through its
// ancestors until the bound element is found. Each element matching the
// "LI" nodeName will run your code.
function handler(event) {
var node = event.target;
do {
if (node.nodeName === "LI") {
// RUN YOUR CODE HERE
}
} while (node !== this && (node = node.parentNode));
}
You can make the selector test more broad by using node.matchesSelector(".my.selector"), though that method is implemented using browser-specific property flags in some browsers, so you'd need to first expose it on the Element.prototype under the proper name.
You can bind click listener to any element as follows:
<ul id="mylist">
<li> item 1</li>
<li> item 2</li>
<li> item 3</li>
<li> item 4</li>
</ul>
<script type="text/javascript">
function click_handler(){
alert('you clicked a list');
}
var list=document.getElementById("mylist");
list.onclick=click_handler;
</script>
here is JSFiddle
Related
This is my HTML line:
<li onclick="return open_create_event_screen();">10</li>
I want to get the content of this < li > element (which is '10') through the JavaScript function (open_create_event_screen()) that opens once the < li > element is clicked.
Is it possible to do so?
Old way, where we put inline events into our HTML. The key is to pass this into the function, which is a reference to the element that was acted upon, you could also pass event, and determine the clicked element from the target property (two different approaches).
const open_create_event_screen = (obj) => {
console.log("opening create event screen");
console.log(obj.innerText);
}
<li onclick="open_create_event_screen(this);">10</li>
Here's a more modern approach, where our html is cleaner, and we do the work of assigning the event handlers in javascript.
const open_create_event_screen = (obj) => {
console.log("opening create event screen");
console.log(obj.innerText);
}
const lis = document.querySelectorAll(".eventThing>li");
lis.forEach(li =>
li.addEventListener("click", () => open_create_event_screen(li))
);
<ul class="eventThing">
<li>10</li>
<li>20</li>
</ul>
I am working with this:
<html>
<ol onclick="myevent(event);">
<li title="a">Test 1</p>
<li title="b">Test 2</p>
</ol>
<div id="a" style="display:none;">Text to show</div>
<div id="b" style="display:none;">Other text to show</div>
<script>
function myevent(event) {
var x, i, clk, res;
x = document.getElementsByTagName("DIV");
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
clk = event.target.title;
res = document.getElementById(clk);
res.style.display = "block";
}
</script>
</html>
Essentially click one line item, hide all blocks, and then show the named block. This should be simple but I have been researching for hours and getting nowhere.
Thanks in advance!
- Joe
You have some typos in your code.
Firstly, you are wrongly closing li element with </p>
Secondly, getElementsBytagname should be getElementsByTagName and getelementbyid should be getElementById:
function myevent(event){
var x, i, clk, res;
x = document.getElementsByTagName("DIV");
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
clk = event.target.title;
res = document.getElementById(clk);
res.style.display="block";
}
<ol onclick="myevent(event);">
<li title="a">Test 1</li>
<li title="b">Test 2</li>
</ol>
<div id="a" style="display:none;">Text to show</div>
<div id="b" style="display:none;">Other text to show</div>
You can also try using querySelectorAll() and forEach() that does not require the loop variable i:
function myevent(e){
var x, clk, res;
x = document.querySelectorAll("DIV");
x.forEach(div => div.style.display = "none");
clk = e.target.title;
res = document.getElementById(clk);
res.style.display="block";
}
<ol onclick="myevent(event);">
<li title="a">Test 1</li>
<li title="b">Test 2</li>
</ol>
<div id="a" style="display:none;">Text to show</div>
<div id="b" style="display:none;">Other text to show</div>
Event Handlers
There's three ways to handle an event (in this case the click event on <ol>):
Event Attribute (not recommended)
<ol onclick="eventHandler(); return false">...</ol>
Event Property
document.querySelector('ol').onclick = eventHandler;
Event Listener (recommended)
document.querySelector('ol').addEventListener('click', eventHandler);
For details refer to Introduction to Events and DOM Onevent Handlers
Event Delegation
The following demo uses a programming paradigm called Event Delegation. The advantages over the code provided in OP (Original Post) are:
Only one tag needs to be registered as the event listener (common ancestor tag of all targeted tags - ex. <ol> is an ancestor tag to all <li>).
There's no need to loop thru each targeted tag and register each one to an event (ex. <li> do not have to be in a for loop).
There can be an unlimited number of targeted tags and knowing how many is not required. (ex. there can be a single <li> to theoretically thousands of <li>).
The targeted tags can be added dynamically anytime -- during or after page load. If done any other way, any dynamically added tags would not work without reloading the page and somehow keeping them.
Refer to Event Delegation for details.
References
In the following demo these methods and properties were used:
DOM
Document.querySelector()
Element.tagName
Node.textContent
Event
EventTarget.addEventListener()
Event.currentTarget
Event.target
Event.stopPropagation()
Demo
Details are commented in demo
/* EVENT DELEGATION */
// Register Ancestor Tag to Event
/*
Register the click event to a common ancestor tag of all
tags you want to be clickable.
So in this instance the clickable tags are all <li>
Ancestor tags could be:
window
document
<body>
<main>
<ol>
Usually the closest tag is the best choice which is <ol>
*/
// ------ Breakdown ------
/*
document.querySelector('ol')
-- find <ol>
.addEventListener('click'...
-- register click event to <ol>
..., eventHandler);
-- run function eventHandler() when <ol> is clicked
*/
document.querySelector('ol').addEventListener('click', eventHandler);
// Event Object
/*
Pass event object which will have properties and methods that will allow us to:
1. find what tag is listening for click event
- event.currentTarget
- in this demo it is <ol>
2. find what tag the user actually clicked
- event.target
- in this demo only <li> will react to being clicked
3. stop the click event from "bubbling" up the event chain
- event.stopPropagation();
- this method is called at the end of eventHandler() so
that the click event doesn't trigger anything else
*/
function eventHandler(event) {
// Reference all tags concerned
// See Event Object #1
const listener = event.currentTarget;
// See Event Object #2
const clicked = event.target;
// Find <output>
const display = document.querySelector('output');
// ------ Breakdown ------
/*
if <ol> isn't the tag that the user clicked...
... and if the clicked tag is a <li>...
... get the text inside that <li>...
... and place it in <output>
(it gets overwritten on each click so there's no need for multiple tags)
*/
if (listener !== clicked) {
if (clicked.tagName === 'LI') {
let data = clicked.textContent;
display.textContent = data;
}
}
/*
Regardless of whether any <li> were clicked stop the click event
See Event Object #3
*/
event.stopPropagation();
}
main {
font: 700 1rem/1.5 Tahoma
}
li:hover {
cursor: pointer
}
<main>
<ol>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
</ol>
Item: <output></output>
</main>
I have the following structure:
<ul id="list-items">
<li class="item" data-id="123" data-title="Some Title">
<div class="block">
<img src="#"/>
Link
<p>Some excerpt</p>
</div>
</li>
</ul>
There are more than 1 <li> item
All data- attributes are on the <li> elements
Using jQuery, I would usually make use of event delegation instead of attaching event handlers on every <li> item:
$( document ).on( 'click', '.item', function() {
var id = $( this ).data( 'id' );
var title = $( this ).data( 'title' );
});
However, I am not able to replicate this using Pure JavaScript.
I want to be able to click on an <li> item without clicking any of its child elements.
I am also not at the liberty of using closest() since we have to provide support for IE11. Is there a simpler way to implement it?
EDIT:
I am avoiding attaching event listeners to each <li> item as it won't work for dynamically created <li> elements, and also for performance reasons.
You can disable the li's content from getting any mouse event by setting a pointer-events: none to it.
<li class="item" data-id="123" data-title="Some Title">
<div class="block" style="pointer-events: none">
<img src="#"/>
Link
<p>Some excerpt</p>
</div>
</li>
You can guarantee now that the event.target will always be the li
If you don't want to attach event handler on all the items directly. you can attach only one event handler on the parent like this
var element = document.querySelector("#vanilla-parent")
element.addEventListener("click", function(event){
event.composedPath().forEach(function(elm){
if (elm.tagName === 'LI') {
// do something
}
});
});
$("#jquery-parent").on("click", "li", function(event){
// do something
});
Pen demonstrating the same: https://codepen.io/kireeti-tiki/pen/EzPpqZ?editors=1010
I used composedPath on the event object to get to li, I wouldn't recommend this as this is a bit of hacky way to get to the solution. Also, it is not supported on IE and Edge. So stay away from that solution if you need support for those browsers. More on that subject here https://developer.mozilla.org/en-US/docs/Web/API/Event
If using jQuery is not a problem, Then I would recommend that approach.
Will something like get all li and attach an eventlistener do?
<script>
var li_list = document.getElementsByTagName('li');
for(var i = 0; i < li_list.length; i++) {
(function(index) {
li_list[index].addEventListener("click", function() {
event.preventDefault();
alert('clicked')
})
})(i);
}
</script>
Here's an example using Element.closest(), for which there is a polyfill.
function attachClickHandler() {
const list = document.querySelector('.list');
list.addEventListener('click', (item) => {
// console.log('Item:', item);
const closest = item.target.closest('li');
console.log('Closest li:', closest);
console.log('Data on closest li:', closest.tagName, closest.dataset && closest.dataset.id || 'no data');
// alternative
console.log(' === alt ===');
let elem = item.target;
while (elem.tagName.toLowerCase() !== 'li') {
elem = elem.parentNode;
if (elem.tagName.toLowerCase() === 'ul') {
break;
}
}
console.log('Manual version:', elem.tagName, elem.dataset && elem.dataset.id || 'no data');
});
console.log('Listening.');
}
attachClickHandler();
<h1>My list</h1>
<ul class="list">
<li data-id="1">Item 1</li>
<li data-id="2"><button>Item 2</button></li>
<li>Empty item</li>
</ul>
Edit: This won't work on IE as stackoverflow likely doesn't load the polyfill. But if you test it standalone (should be 3 seconds work) you can verify if it's good enough for you.
I need to create an event-listener which fires when a user clicks one of the list items in the HTML.The action should call a function named listItemText which returns the innerText of the list item which was clicked--ie: if they click the first li it should log "Walk the dog"
I've tried everything I can think of to get the correct corresponding innerText of the li item that is being clicked on. At best, I've gotten either the whole list back in the console.log, or the last element of the list.
I've tried so many things at this point it would be impossible to recall. More or less the code below is a variant of what I've attempted
<ul id="todo-app">
<li class="item">Walk the dog</li>
<li class="item">Pay bills</li>
<li class="item">Make dinner</li>
<li class="item">Code for one hour</li>
</ul>
var targetUl = document.getElementById('todo-app').childNodes;
this.addEventListener('click', listItemText);
function listItemText(event) {
var liClicked = event.target;
for (i=0; i<targetUl.length; i++) {
if (liClicked == 'LI') {
console.log(targetUl[i].innerText)
}
}
}
I expect to get the text content of the li tag but I keep getting undefined at this point. Help would be greatly appreciated.
If I understand correctly you want the console.log of the text of the li element you click, so I suppose you can try this code below:
var targetUl = document.getElementById('todo-app').addEventListener("click",listItemText);
function listItemText(event) {
var liClicked = event.target;
if(liClicked && liClicked.nodeName == "LI"){
console.log(liClicked.textContent)
}
}
<ul id="todo-app">
<li class="item">Walk the dog</li>
<li class="item">Pay bills</li>
<li class="item">Make dinner</li>
<li class="item">Code for one hour</li>
</ul>
This is the principle of event delegation, where you don't need to attach event listeners on all of the elements but only to the parent node, if the event happened it will bubble up to the parent and using the event.target you can get the reference of the child element which was clicked.
I just now ended up figuring it out. Did it slightly differently, but ultimately the same idea. Was having a hard time understanding event.target and how it works. Here's my answer--which is almost the same as your answer, good sir:
var toDoList = document.getElementById("todo-app");
toDoList.addEventListener('click', listItemText);
function listItemText(e) {
console.log(e.target.innerText);
return e.target.innerText;
}
I am quite new to manipulating elements in the DOM in JS so I am creating a simple to do list to get more comfortable and where I can add items using the input and remove items by clicking on the list item.
ALthough this may not be best practice and limitting I am just wanting to use create and remove elements rather than using objects or classes until I get more familar, also using plain/vanilla js so please keep this in mind when answering.
I am trying to add a click event which removes the <li> when the <li> is clicked.
My logic is...
When the page is loaded I can't just run a for loop over all of the <li>s and add event handlers as all of the <li>'s do not exist yet.
So my attempted solution is when the addTaskButton event is triggered, we get all of the <li> that are on the page at the time of the event, we loop through all of them and add an eventlistener to <li>'s that are waiting to be removed when clicked.
This doesn't seem to work and may be overly complicated.
Can someone please explan to me very simply like I'm 5 why this doesn't work or what a better way to do this would be?
Thank you in advance
HTML
<ul id="taskList">
<li>example</li>
</ul>
<input type="text" id="addTaskInput">
<button id="addTaskButton">Add Task</button>
JavaScript
const taskList = document.querySelector("#taskList");
const addTaskInput = document.querySelector("#addTaskInput");
const addTaskButton = document.querySelector("#addTaskButton");
let taskItem = document.querySelectorAll("li");
addTaskButton.addEventListener("click", () => {
let taskItem = document.createElement("li");
taskItem.textContent = addTaskInput.value;
for (let i = 0; i < taskItem.length; i++) {
taskItem[i].addEventListener("click", () => {
let taskItem = document.querySelectorAll("li");
taskList.removeChild(taskItem[i]);
});
}
taskList.appendChild(taskItem);
addTaskInput.value = " ";
});
Here is code i created for your requirement, this implement jQuery $(document).on mechanism in vanilla javascript, now where ever you create an li inside the document, on clicking that li it will be removed.
Explaination
What it does is on clicking the document it checks on which element is clicked (e.target is the clicked element, e is is the click event on document), then checks if the clicked item is an li tag (e.target.tagName will tell us the tag name if the item clicked), so if it is an li just remove it;
const taskList = document.querySelector("#taskList");
const addTaskInput = document.querySelector("#addTaskInput");
const addTaskButton = document.querySelector("#addTaskButton");
addTaskButton.addEventListener("click", () => {
let taskItem = document.createElement("li");
taskItem.textContent = addTaskInput.value;
taskList.appendChild(taskItem);
addTaskInput.value = " ";
});
document.onclick = function(e)
{
if(e.target.tagName == 'LI'){
e.target.remove();
}
}
<ul id="taskList">
<li>example</li>
</ul>
<input type="text" id="addTaskInput">
<button id="addTaskButton">Add Task</button>
Update your for loop like so:
for (let i = 0; i < taskItems.length; i++) {
taskItems[i].addEventListener("click", () =>
taskList.removeChild(taskItems[i]);
});
}
Also your initial taskItem variable should be taskItems and is reflected in the for loop above.
taskList.addEventListener("click", (event) => {
event.target.remove();
});
When the specified event occurs the event object is returned.
The event object has several properties, one of them being target which is the element which is the element which the event occured on. event.target is returned to us and we are applying the remove() method to event.target
because of event "bubbling" or "Event Propagation", we can attach the event handler to an ancestor. It's best to attach the event listener to the closest ancestor element that is always going to be in the DOM (won't be removed).
When an event is triggered-in this case the "click" event. All decending elements will be removed - which in our case as there are only <li>'s this would be fine. But we should be more specific as in a different case we could be attaching this event handler to a div which has several different elements.
To do this we add an if condition to check that the tagName is an <li>
if (event.target.tagName == "LI")
note that the element must be calpitalised
Solution is as follows
taskList.addEventListener("click", (event) => {
if(event.target.tagName == "LI"){
event.target.remove();
}});
Further reading:
Event object and its properties:
https://developer.mozilla.org/en-US/docs/Web/API/Event
Event Bubbling:
https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles
tagName:
https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName