Replace jquery $("body").on for child elements with Vanilla Script - javascript

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);

Related

Removing an element works only sometimes

My click event works with jQuery but not with Vanilla JS.
This is how it works with jQuery:
$(document).on('click', '.mt-remove-keyword', function() {
alert('jQuery');
});
This is how I thought that it should work but it did not (maybe because my element is dynamic):
const mtRemoveKeywords = document.querySelectorAll('.mt-remove-keyword');
for (let mtRemoveKeyword of mtRemoveKeywords) {
mtRemoveKeyword.addEventListener('click', (e) => {
alert('JS');
});
}
So I have used document as the selector to bubble it down to the main selector:
document.addEventListener('click', (e) => {
if (e.target.classList.contains('mt-remove-keyword')) {
console.log(e.target.classList);
e.target.parentElement.parentElement.remove();
}
});
The goal is to remove the parent element of the target.
The problem is, sometimes it works, sometimes it doesn't. Feel free to test it on JSFiddle. Just add a couple of texts to the yellow section and then try to delete them.
The target might be different than what you think it is. In that case the parents might be off. So better to use closest to walk the tree.
document.addEventListener('click', e => {
var mtKeyword = e.target.closest('.mt-keyword');
mtKeyword && mtKeyword.remove();
});
It would be better to bind the event listener to the parent element so not every document click is tracked.
This is how I could resolve this issue (with help of epascarello):
document.addEventListener('click', (e) => {
if (e.target.classList.contains('mt-remove-keyword') || e.target.parentElement.classList.contains('mt-remove-keyword')) {
e.target.closest('.mt-keyword').remove();
}
});

Is there any way to make the onClick global or DRY?

$("input").on("keypress",function(e){
if(e.which===13){
$("ul").last().append("<li>"+$(this).val()+"</li>");
}
$("li").on("click",function(){
$(this).toggleClass("striked");
});
$("li").on("mouseenter",function(){
$(this).css("color","green");
});
});
$("li").on("click",function(){
$(this).toggleClass("striked");
});
$("li").on("mouseenter",function(){
$(this).css("color","green");
});
$("#slide").on("click",function(){
$("input").slideToggle();
});
Here, I have used the onClick event on<li> to apply the striked class two times just to make it work for both dynamic and non-dynamic elements on the page. But the code is replicated and seems long. Is there any way to shorten so that I can write it once and it gets activated for both types of elements?
Use event delegation instead, on the ul, so you only have to set up listeners once, rather than setting up multiple listeners for every element on load and on each .append. Also, save the ul and the input jQuery-wrapped elements in a variable once rather than selecting them and wrapping them with jQuery each time they're used:
const $ul = $("ul");
const $input = $("input");
$input.on("keypress", function(e) {
if (e.which === 13) {
$ul.last().append("<li>" + $(this).val() + "</li>");
}
});
$ul.on("click", 'li', function() {
$(this).toggleClass("striked");
});
$ul.on("mouseenter", 'li', function() {
$(this).css("color", "green");
});
$("#slide").on("click", function() {
$input.slideToggle();
});
A rather generic approach would be to capture the click event and check if it is from ul
document.body.onclick = function(e){
e = e || event;
var from = findParent('ul',e.target || e.srcElement);
if (from){
/* it's a link, actions here */
}
}
//find first parent with tagName [tagname]
function findParent(tagname,el){
while (el){
if ((el.nodeName || el.tagName).toLowerCase()===tagname.toLowerCase()){
return el;
}
el = el.parentNode;
}
return null;
}
now you can change the tagName passed to the findParent function and do accordingly
Read Here
You can try using the jquery all selector $('*'). For more information on this see
https://api.jquery.com/all-selector/.
Or you can add a specific class to every element you want to have an onClick action.

JavaScripts equivalent to jQuery document click with a class

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 });

JavaScript prevent touch move on body element, enable on other elements

but very simply, I'd like to prevent the touchmove event on the body element but leave it enabled for another element. I can disable fine... but I'm not sure how to re-enable it somewhere else!
I imagine that the below theoretically works because return true is the opposite of preventDefault, but it doesn't work for me. Might be 'cause $altNav element is in $bod?
JS:
$bod.bind('touchmove', function(event){
event.preventDefault();
});
$altNav.bind('touchmove', function(event){
return true;
});
I'm not sure what lib you're actually using, but I'll asume jQuery (I'll also post the same code in browser-native-js if you're using something other than jQ)
$bod.delegate('*', 'touchstart',function(e)
{
if ($(this) !== $altNav)
{
e.preventDefault();
//and /or
return false;
}
//current event target is $altNav, handle accordingly
});
That should take care of everything. The callback here deals with all touchmove events, and invokes the preventDefault method every time the event was triggered on an element other than $altNav.
In std browser-js, this code looks something like:
document.body.addEventListener('touchmove',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
//in case $altNav is a class:
if (!target.className.match(/\baltNav\b/))
{
e.returnValue = false;
e.cancelBubble = true;
if (e.preventDefault)
{
e.preventDefault();
e.stopPropagation();
}
return false;//or return e, doesn't matter
}
//target is a reference to an $altNav element here, e is the event object, go mad
},false);
Now, if $altNav is an element with a particular id, just replace the target.className.match() thing with target.id === 'altNav' and so on...
Good luck, hope this helps
Use a custom CSS class and test for it in the document handler, eg:
<div>
This div and its parents cannot be scrolled.
<div class="touch-moveable">
This div and its children can.
</div>
</div>
then:
jQuery( document ).on( 'touchmove', function( ev )
{
if (!jQuery( ev.target ).parents().hasClass( 'touch-moveable' ))
{
ev.preventDefault();
}
});
http://tinyurl.com/mo6vwrq
you can add a argument,like this
$bod.bind('touchmove', function(event,enable){
if(enable){
event.preventDefault();
}
});

Implementing jQuery's "live" binder with native Javascript

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.

Categories