What is the JavaScript equivalent to this jQuery:
$(document).on('click', '.add-star', function (event) {
//event will return the .add-star
})
Markup looks like this
<div class="add-star">
<svg>
<path />
</svg>
</div>
When I do document.addEventListener('click', function(e) {... the e.target gets me the path not the parent add-star. From what I know with the jQuery way it bubbles up on the event looking for the class specified and returns that in the event. But there is no class specified with the JS event, so it returns just the immediate clicked element, the path from the svg.
How would I return add-star from the js event?
It's pretty easy. You just use .matches() on each element starting at e.target, traversing through each .parentNode until the bound element. When/if a match is found, you call the function.
So create a function that receives the callback and returns a new function handles this operation.
function delegate(selector, handler) {
return function(event) {
var el = event.target;
do {
if (el.matches(selector)) {
handler.call(el, event);
}
} while ((el = el.parentNode) && el !== this);
};
}
Then call that function to create the handler.
document.addEventListener('click', delegate('.add-star', function (event) {
//event will return the .add-star
}));
You have two main ways of handling events here, the event delegation method is similar to what your jQuery example is doing so I'll make that #1. This method uses e.target.matches to accomplish checking for an element that might not exist. The second method is for more traditional elements and uses document.querySelector
Method 1 delegated events
document.addEventListener('click', e => {
if (!e.target.matches('.add-star')) { return }
// do stuff
});
Method 2 non-dynamic selectors
let ele = document.querySelector('.add-star');
ele.addEventListener('click', e => { // do stuff });
Related
how is it possible to replace this jQuery with Vanilla:
$( document ).ready(function() {
$('body').on('click', '.f_click', function (e) {
e.preventDefault();
alert("TEST");
});
});
My first try was:
document.addEventListener('click', function (e) {
console.log(e.target);
if (e.target.classList.contains('f_bme_start')) {
alert('open Search!');
return false;
}
}, false);
this works, but not on child elements.
Has somebody an idea how to solve this?
I want to replace all my jQuery code because of slow performance.....
THANKS
You're only checking the element that was actually clicked, not its ancestor elements.
In modern environments you can use the DOM's closest method (and that link has polyfills for older environments):
document.addEventListener('click', function (e) {
const target = e.target.closest(".f_bme_start");
if (target) {
alert('open Search!');
return false;
}
});
That searches through the ancestors of the clicked element for a match for a given CSS selector. If you were hooking the event on a container element other than the document or document.body, I'd also use contains to make sure the search through ancestors didn't go to an ancestor of the container element:
const target = e.target.closest(".f_bme_start");
if (target && e.currentTarget.contains(target)) {
alert('open Search!');
return false;
}
But there's no need if you're hooking the event on document or document.body.
THANKS a lot!
what is the best solution?
For performance and for compatibility?
I think this one is best?:
document.addEventListener('click', function (e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
console.log(target.classList);
if (target.classList.contains('f_click')) {
alert('open without jQuery!');
return false;
}
}
}, false);
I'm trying to convert jquery into javascript. My app is a simple to do list and I'm targeting a button with an id called #clear-completed. Whenever I click that button on my app, it deletes the completed todo items, but I don't understand where it is being targeting in my new Javascript code.
Here is the original Jquery code
$('#footer').on('click', '#clear-completed', this.destroyCompleted.bind(this));
So I changed it to Javascript and this code worked
var footer = document.getElementById('footer');
footer.addEventListener('click', this.destroyCompleted.bind(this))
What I don't understand is what happened the the #clear-completed id and how does my new javascript code still work, even though I am not specifying to target the #clear-completed button?
Here is the code for the destroyCompleted function
destroyCompleted: function () {
this.todos = this.getActiveTodos();
this.filter = 'all';
this.render();
},
In the debugger it runs through the activeTodos function, but I don't see anywhere where the id #clear-completed is targeted?
getActiveTodos: function () {
return this.todos.filter(function (todo) {
return !todo.completed;
});
},
getCompletedTodos: function () {
return this.todos.filter(function (todo) {
return todo.completed;
});
},
Did I write my Jquery into Javascript properly? Or did I miss something?
Also, if the id had more than one event listener how would you code that properly? for example
$('#todo-list')
.on('change', '.toggle', this.toggle.bind(this))
.on('dblclick', 'label', this.edit.bind(this))
.on('keyup', '.edit', this.editKeyup.bind(this))
.on('focusout', '.edit', this.update.bind(this))
.on('click', '.destroy', this.destroy.bind(this));
The equivalent JavaScript would be:
document.querySelector('#footer').addEventListener('click', event => {
const element = event.target.closest('#clear-completed');
if (
event.currentTarget !== element &&
event.currentTarget.contains(element)
) {
this.destroyCompleted(event);
}
});
The signature $(target).on(event, selector, handler) that you're using is called a delegated event handler, so the handler is invoked on the target element as you have correctly reproduced, but it is only invoked when the event targets an element matching selector which is descendant of target, not including target itself.
Matching the selector is reproduced above by checking that event.currentTarget .contains() the element returned by event.target .closest(selector).
You could even break this logic out into a helper function to make it more readable:
document.querySelector('#footer').addEventListener('click', event => {
const matches = selector => {
const element = event.target.closest(selector);
return (
event.currentTarget !== element &&
event.currentTarget.contains(element)
);
};
if (matches('#clear-completed')) {
this.destroyCompleted(event);
}
});
Since you need this pattern multiple times, it makes sense to move it into another reusable function:
function delegate (target, type, selector, handler) {
const matches = event => {
const element = event.target.closest(selector);
return (
event.currentTarget !== element &&
event.currentTarget.contains(element)
);
};
target.addEventListener(type, event => {
if (matches(event)) {
handler(event);
}
});
}
const element = document.querySelector('#todo-list');
delegate(element, 'change', '.toggle', e => this.toggle(e));
delegate(element, 'dblclick', 'label', e => this.edit(e));
delegate(element, 'keyup', '.edit', e => this.editKeyup(e));
delegate(element, 'focusout', '.edit', e => this.update(e));
delegate(element, 'click', '.destroy', e => this.destroy(e));
You targeted '#clear-completed' in the jquery function by passing the argument,'#clear-completed', to your on event handler. Jquery on says:
https://api.jquery.com/on/
.on( events [, selector ] [, data ], handler )
selector
Type: String
A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.
I have a listener which runs when I click on document.
document.addEventListener('click', print);
function print(element)
{
doSomething();
}
It creates div id=panel, where I print some information.
When I run the print function I would like to detect whether I clicked outside of the div#panel (The panel exists when I click second time).
I wish not to use the mouseout event listener because I think it is redundant to use listener for mouse movements when the event click is already fired.
How to detect when I clicked out of div#panel?
You can check the target of jQuery's click event, which element it was:
$(document).click(function(e) {
var target = $(e.target);
if( !target.is("#panel") && target.closest("#panel").length === 0 ) {
// click was not on or inside #panel
}
});
Your event handler gets passed an event object, not an element. Since you are listening for the click event, the event will be of type MouseEvent and that event object will have a target property which you can use to check if the target element matches your desired element.
function handler(event) {
if (event.target == document.getElementById("panel")) {
// Do stuff
}
}
document.addEventListener('click', handler);
Edit: I intentionally gave the vanilla JS answer since your own code fragments don't use jQuery. But jQuery wouldn't change anything as its event handling API is almost just a thin wrapper over JS.
I am just using event from the click. Here it is
var elem=document.getElementById("elem");
var rects=elem.getBoundingClientRect();//get the bounds of the element
document.addEventListener('click', print);
function print(e)
{
//check if click position is inside or outside target element
if(e.pageX<= rects.left +rects.width && e.pageX>= rects.left && e.pageY<= rects.top +rects.height && e.pageY>= rects.top){
console.log("Inside element");
}
else{
console.log("Outside element");
}
}
JS Bin link : https://jsbin.com/pepilehigo/edit?html,js,console,output
A different approach, using only javascript is:
function print(evt) {
if (!(evt.target.tagName == 'DIV' && evt.target.classList.contains('myDiv'))) {
var div = document.createElement('div');
div.classList.add('myDiv');
div.textContent="new div";
document.body.appendChild(div);
}
}
window.onload = function() {
document.addEventListener('click', print);
}
.myDiv {
border:1px solid green;
}
I have a bunch of elements that get three different classes: neutral, markedV and markedX. When a user clicks one of these elements, the classes toggle once: neutral -> markedV -> markedX -> neutral. Every click will switch the class and execute a function.
$(document).ready(function(){
$(".neutral").click(function markV(event) {
alert("Good!");
$(this).addClass("markedV").removeClass("neutral");
$(this).unbind("click");
$(this).click(markX(event));
});
$(".markedV").click(function markX(event) {
alert("Bad!");
$(this).addClass("markedX").removeClass("markedV");
$(this).unbind("click");
$(this).click(neutral(event));
});
$(".markedX").click(function neutral(event) {
alert("Ok!");
$(this).addClass("neutral").removeClass("markedX");
$(this).unbind("click");
$(this).click(markV(event));
});
});
But obviously this doesn't work. I think I have three obstacles:
How to properly bind the changing element to the already defined function, sometimes before it's actually defined?
How to make sure to pass the event to the newly bound function [I guess it's NOT accomplished by sending 'event' to the function like in markX(event)]
The whole thing looks repetitive, the only thing that's changing is the alert action (Though each function will act differently, not necessarily alert). Is there a more elegant solution to this?
There's no need to constantly bind and unbind the event handler.
You should have one handler for all these options:
$(document).ready(function() {
var classes = ['neutral', 'markedV', 'markedX'],
methods = {
neutral: function (e) { alert('Good!') },
markedV: function (e) { alert('Bad!') },
markedX: function (e) { alert('Ok!') },
};
$( '.' + classes.join(',.') ).click(function (e) {
var $this = $(this);
$.each(classes, function (i, v) {
if ( $this.hasClass(v) ) {
methods[v].call(this, e);
$this.removeClass(v).addClass( classes[i + 1] || classes[0] );
return false;
}
});
});
});
Here's the fiddle: http://jsfiddle.net/m3CyX/
For such cases you need to attach the event to a higher parent and Delegate the event .
Remember that events are attached to the Elements and not to the classes.
Try this approach
$(document).ready(function () {
$(document).on('click', function (e) {
var $target = e.target;
if ($target.hasClass('markedV')) {
alert("Good!");
$target.addClass("markedV").removeClass("neutral");
} else if ($target.hasClass('markedV')) {
alert("Bad!");
$target.addClass("markedX").removeClass("markedV");
} else if ($target.hasClass('markedX')) {
alert("Ok!");
$target.addClass("neutral").removeClass("markedX");
}
});
});
OR as #Bergi Suggested
$(document).ready(function () {
$(document).on('click', 'markedV',function (e) {
alert("Good!");
$(this).addClass("markedV").removeClass("neutral");
});
$(document).on('click', 'markedX',function (e) {
alert("Bad!");
$(this).addClass("markedX").removeClass("markedV");
});
$(document).on('click', 'neutral',function (e) {
alert("Ok!");
$(this).addClass("neutral").removeClass("markedX");
});
});
Here document can be replaced with any static parent container..
How to properly bind the changing element to the already defined function, sometimes before it's actually defined?
You don't bind elements to functions, you bind handler functions to events on elements. You can't use a function before it is defined (yet you might use a function above the location in the code where it was declared - called "hoisting").
How to make sure to pass the event to the newly bound function [I guess it's NOT accomplished by sending 'event' to the function like in markX(event)]
That is what happens implicitly when the handler is called. You only need to pass the function - do not call it! Yet your problem is that you cannot access the named function expressions from outside.
The whole thing looks repetitive, the only thing that's changing is the alert action (Though each function will act differently, not necessarily alert). Is there a more elegant solution to this?
Yes. Use only one handler, and decide dynamically what to do in the current state. Do not steadily bind and unbind handlers. Or use event delegation.
I am trying to figure out how to bind an event to dynamically created elements. I need the event to persist on the element even after it is destroyed and regenerated.
Obviously with jQuery's live function its easy, but what would they look like implemented with native Javascript?
Here's a simple example:
function live(eventType, elementId, cb) {
document.addEventListener(eventType, function (event) {
if (event.target.id === elementId) {
cb.call(event.target, event);
}
});
}
live("click", "test", function (event) {
alert(this.id);
});
The basic idea is that you want to attach an event handler to the document and let the event bubble up the DOM. Then, check the event.target property to see if it matches the desired criteria (in this case, just that the id of the element).
Edit:
#shabunc discovered a pretty big problem with my solution-- events on child elements won't be detected correctly. One way to fix this is to look at ancestor elements to see if any have the specified id:
function live (eventType, elementId, cb) {
document.addEventListener(eventType, function (event) {
var el = event.target
, found;
while (el && !(found = el.id === elementId)) {
el = el.parentElement;
}
if (found) {
cb.call(el, event);
}
});
}
In addition to Andrew's post and Binyamin's comment, maybe this is an option:
With this you can use 'nav .item a' as the selector.
Based on Andrew's code.
function live (eventType, elementQuerySelector, cb) {
document.addEventListener(eventType, function (event) {
var qs = document.querySelectorAll(elementQuerySelector);
if (qs) {
var el = event.target, index = -1;
while (el && ((index = Array.prototype.indexOf.call(qs, el)) === -1)) {
el = el.parentElement;
}
if (index > -1) {
cb.call(el, event);
}
}
});
}
live('click', 'nav .aap a', function(event) { console.log(event); alert('clicked'); });
The other solutions are a little overcomplicated...
document.addEventListener('click', e => {
if (e.target.closest('.element')) {
// .element has been clicked
}
}
There is a polyfill in case you need to support Internet Explorer or old browsers.
An alternative to binding an event to dynamically to a specific element could be a global event listener. So, each time you update the DOM with another new element event on that element will also the "catches". An example:
var mybuttonlist = document.getElementById('mybuttonlist');
mybuttonlist.addEventListener('click', e=>{
if(e.target.nodeName == 'BUTTON'){
switch(e.target.name){
case 'createnewbutton':
mybuttonlist.innerHTML += '<li><button name="createnewbutton">Create new button</button></li>';
break;
}
}
}, false);
ul {
list-style: none;
padding: 0;
margin: 0;
}
<ul id="mybuttonlist">
<li><button name="createnewbutton">Create new button</button></li>
</ul>
In this example I have an event listener on the <ul> for click events. So, an event happens for all child elements. From the simple event handler I created, you can see that it is easy to add more logic, more buttons (with different or repeating names), anchors etc.
Going all in, you could add the eventlistener to document instead of the list element, catching all click events on the page and then handle the click events in the event handler.