Event fires for all items not just the current item - javascript

I am trying to attach my plugin to a input field, and it works somewhat. So, I am attaching an on() with onchange to the input field, and when it fires, it fires for each input, and not just the one that changed. What is causing this? Is it because I am using $(document).on()? I added it that way because items are dynamically added to the page.
(function($){
$.fn.itemsave = function(config){
return this.each(function(){
var item = $(this);
var opt = {
// The options
};
var e = $.extend(opt, config);
$(document).on("change", item, function(){
request(false, e);
});
});
})(jQuery);
How it is called:
$(".simplesave").itemsave(null);

You're passing a jQuery object as the second parameter of .on(). That's not an error, but the library will interpret that as being an object to be made available to the event handler as event.data. It also means that there is no event handling delegation going on. Thus, you're establishing a "change" event handler that will fire when ever any "change" event bubbles up to the document level.
You probably should just be attaching the handler directly to your element:
item.on("change", function() {
request(false, e);
});

I believe this line is causing an error as item should be a selector not an element:
$(document).on("change", item, function(){
The solution would be to put an even handler on the item
$(item).on("change", function(){
request(false, e);
})

Related

Rebind JavaScript events and addEventListener fires twice

I have a class method which defines the event listeners. Let us use the following code snippet for simplicity.
function bindEvents() {
document.querySelector('button').addEventListener('click', (e) => {
console.log('clicked!');
});
}
// Initial event binding
bindEvents();
// Rebind events at some point for dynamically created elements
bindEvents();
<button type="button">Click</button>
Everything works fine when using bindEvents() only once, however for example calling it again in ajax callback results in listener executed twice. So this means after second bindEvents(), clicking the button will console.log() twice and so on. Is there a way I can get around this behavior?
I know I can bind the events "dynamically" on the document and check with e.target, but there is a situation where I need mouseenter/mouseleave events and I don't think it's a good idea to always have those eventListeners on the document.
I've read somewhere the following, but it seems false...
The .addEventListener method ensures that the same function reference
won't be bound more than once to the same element/event/captures
combination.
Also I have played with the options parameter from here https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener, but without success.
Excuse me if this was answered somewhere, but I failed to find answer in SO and the search engines.
UPDATE: Is there a way to overwrite existing eventListeners or old ones should be removed with removeEventListener like kcp suggested below? Is there more elegant solution to this problem at all?
The .addEventListener method ensures that the same function reference won't be bound more than once to the same element/event/captures combination.
In your case, each time you execute bindEvents() a completely new handler is passed to the click event listener since you define new function (no matter it looks the same, it is different object). To use the same handler each time you must define it outside bindEvents and pass it by name (by reference). This works as expexted:
function clickHandler(e){
alert('clicked!');
}
function bindEvents() {
document.querySelector('button').addEventListener('click', clickHandler);
}
// Initial event binding
bindEvents();
// Rebind events at some point for dynamically created elements
bindEvents();
<button>click</button>
However with jQuery I use the following approach which allows me to specify that only elements in a specific container (context or ctx) will be bound:
$.fn.bindEvents = function bindEvents(ctx){
ctx = ctx || this;
$('button', ctx).on('click', function(event){
alert(1);
});
};
$('body').bindEvents();
$('div').bindEvents();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Click</button>
<div><button>in div </button></div>
In the example above bindEvents is executed twice but the first button is bound only once since it's not in a div. While if you click on the second button it alerts twice because satisfies both contexts.
addEventListener does not overwrite existing event listeners, it simply adds a new one as the method name implies. Existing listeners must be removed using the removeEventListener method.
function onClick($event) {
console.log('clicked!');
}
function bindEvents() {
/** Remove event listener first **/
document.querySelector('button').removeEventListener('click', onClick);
document.querySelector('button').addEventListener('click', onClick);
}
removeEventListener docs
Apart from removeEventListener, You can also use Event delegation. Using this mechanism event is handler by attaching event listener to parent element.
var elem = document.querySelector('div');
elem.addEventListener('click', function(e) {
e = e || event
var target = e.target;
if (target.nodeName != 'BUTTON')
return;
console.log('clicked ' + target.textContent);
});
//Simulate addition of dynamic elements
setTimeout(function() {
var newButton = document.createElement('button');
newButton.innerHTML = 'Click 2';
elem.appendChild(newButton)
}, 2000)
<div>
<button type="button">Click</button>
</div>

Make a Scroll Event Occur Once [duplicate]

I have a link that will load via ajax some content.
My problem is, I don't want to remove the text "Load comments", I just want to not allow more clicks in this class.
Load comments
Jquery
var Progressajax = false;
$(function() {
$(".showcomments").click(function(){
if(Progressajax) return;
Progressajax = true;
var element = $(this);
var id = element.attr("id");
Progressajax = false;
alert("ok");
$(data).hide().prependTo('.varload'+id).fadeIn(1000);
//$(element).remove();
$(element).removeAttr("href");
$(element).removeClass('showcomments');
});
});
I just want to see OK the first time. How can I remove this class?
$(element).removeClass('showcomments');
This is not working...
http://jsfiddle.net/qsn1tuk1/
Use jQuery's one() function
$(".showcomments").one("click", function() {
http://www.w3schools.com/jquery/event_one.asp
The one() method attaches one or more event handlers for the selected elements, and specifies a function to run when the event occurs.
When using the one() method, the event handler function is only run ONCE for each element.
When you bind an event handler, you bind to the element, not to the class. Removing a class from an element doesn't change which event handlers are bound to the element.
You could use off() to remove the event handler:
$(this).off('click');
http://jsfiddle.net/om6ggvyu/

Clone jQuery not work listener

I have a problem. I've a function dropcopy() that calls on a div for drag&drop.
function dropcopy(ev) {
ev.preventDefault();
elencoOggetti = new Array();
var copyimg = document.getElementById(data).cloneNode(true);
//copyimg.id = data;
ev.target.appendChild(copyimg);
}
This function copy the object but not work the listener on clone. The listener is this
$("#filtro").click(function() {
alert('ciao');
});
Why isn't it working? Please help me.
First of all, you shouldn't clone an element without changing its id. IDs must be unique.
Event listeners are binded to the first element when created. Clones doesn't have the listeners binded. You can bind it again, or bind the listeners to a parent object instead of the objects themselves. You can do that like this:
$('body').on('click', '#filtro', function(){
alert('ciao');
});
This means the listener is attached to the body instead of the element itself, and it'll be fired only if the event was fired by #filtro.
I belive that you are binding the element, when it is not existing, so add the guard:
$(document).ready(function(){
$("#filtro").click(function(){
alert('ciao');
});
}
If you wanna bind it after cloning, you can rerun this function above:
function resetUI(){
$("#filtro").click(function(){
alert('ciao');
});
}
Or overwrite the DOM, look on the docs of jquery if you wanna bind that to every name on update.

Call Click Event Once After Element Appears?

I use jQuery 1.6.2. I want to call a click event just for once when that element appears at page. This is my selector:
span[resizer='north'][title=''] :first-child"
This can work for triggering click event:
$("span[resizer='north'][title=''] :first-child").click();
How can I make it work only once or after that element appears on the page?
Answer as per how I understand your last comment: I mean I will call click event only once. Let's assume that there is a button. I will call click event of that button once. I mean I will programmatically click that button.
$('button').click(function() {
$('span[resizer='north'][title='']:first-child').click();
$(this).unbind('click');
});
You may use DOMNodeInserted event to detect when new element is inserted dynamically.
var alreadyclicked = false; // variable to check that it will be clicked just once
$(document).bind('DOMNodeInserted', function(e) {
if (!alreadyclicked){
$("span[resizer='north'][title=''] :first-child").click();
alreadyclicked = true;
}
});
Update
The DOMNodeInserted event is deprecated along with other mutation events. Some browsers support it, but looks like this won't last long.
You can use mutation observers instead to keep track of changes in DOM
var alreadyclicked = false;
var observer = new MutationObserver(function(mutations, observer){
//if new nodes were added
if( mutations[0].addedNodes.length && !alreadyclicked )
$("span[resizer='north'][title=''] :first-child").click();
alreadyclicked = true;
});
observer.observe( document, { childList:true, subtree:true });
Use a custom event, with a delegated handler attached using .one().
An event has 2 components, triggering and handling. Don't focus on how many times it's triggered, focus on how many times it's handled.
$("document").one("myclick", "span[resizer='north'][title=''] :first-child", function(e) {
do something...;
$(this).click(); //if you actually need the "click"
});
$("span[resizer='north'][title=''] :first-child").trigger("myclick");
This example will mean it's "handled" one time. The other answers on here can be used to ensure it's triggered when the element is added if it's after document ready.
You could also use a pub/sub which would make it even easier. Then you just have to trigger your custom event from the ajax or whatever code that would add the element and not worry about specifically targeting the element. Last line of code in the subscription would be to unsubscribe.

How to break event.stopImmediatePropagation

I have a code like this:
<td>
<span class='ui-button-next'>NEXT</span>
</td>
This application contains a library and it is not allowed to be edited. On clicking "ui-button-next", they are calling event.stopImmediatePropagation() to stop something.
I want to call a function when user clicking on this "span" without touching that Library.
My custom code is like:
$(".ui-button-next").click(function(){
});
$(".ui-button-next").bind("click",function(){
});
$(".ui-button-next").on("click",function(){
});
are not working due to event.stopImmediatePropagation() on the library.
any workaround?
You can access the underlying events list (array) and insert your new click handler at the first, it will be triggered normally, however note that the order of execution should not be a problem in your case:
//this is the inner handler
$('.ui-button-next').click(function(e){
e.stopImmediatePropagation();
});
//this is the handler of your own
$('.ui-button-next').click(function(e){
alert('OK');
});
//get click handlers list
var clicks = $._data($('.ui-button-next')[0], 'events')['click'];
//get the last added handler (which is your own handler) and put it at the beginning
clicks.unshift(clicks.pop());
Demo.
UPDATE: To keep the order of execution (the default handlers are executed first, all the additional added handlers are executed after), I think you have to modify the default handlers by removing all the e.stopImmediatePropagation() methods, we have to use the eval() method here. Note that using that method in this case is totally OK.
//get click handlers list
var clicks = $._data($('.ui-button-next')[0], 'events')['click'];
//remove the e.stopImmediatePropagation() in each handler
$.each(clicks, function(i,e){
var handlerText = e.handler.toString()
.replace(/e.stopImmediatePropagation\(\)/g,'');
eval("e.handler = " + handlerText);
});
Updated demo.

Categories

Resources