I want to add mouse events on to the class .piece even if the divs are created after the DOM is loaded.
Here is my actual loop:
var piece = document.getElementsByClassName('piece');
function theLoop() {
for(var i=0; i<piece.length; i++) {
piece[i].addEventListener("mousemove", function(event) {
//do stuff
}
}
}
theLoop();
It works fine, but if I add new divs with the class .piece after the DOM loaded, the loop ignores them.
I add those new divs with .cloneNode() and .appendChild(), like this :
function createPiece(symbol, name) {
var clone = document.getElementById(symbol).cloneNode(true);
clone.setAttribute('id',symbol+'-'+name);
document.getElementById(name).appendChild(clone);
}
createPiece(symbol, name);
All the original cloned divs have the class .piece.
How can I add those new divs to my for loop, and have the mouse event attached to them?
Thanks.
If your loop runs on page load, it's not possible to make it account for elements added to the DOM dynamically afterwards. Usually, event delegation would be a solution, but it's not a good solution for the mousemove event (since it fires so often).
What you could do is create a function that adds the event handler to a passed element. You could then call that function from your current loop, and call it again after adding each new element to the DOM.
Put a mousemove event handler on a common parent of all the pieces and then examine e.target in the event data structure to see which piece the event actually occurred on.
This is called delegated event handling and allows you to install just one event handler on a static parent that will give you events from all children, even children that are dynamically added later.
The only other option is to install an event handler on each specific piece when it's later added to the page. You would need to trigger this from the actual code that adds the element to the page as there is no well-supported, cross-browser way to watch for DOM modifications. If you do it this way, you will need to make your event handling function a named function (rather than the anonymous function you are using now) so that you can use the same function in multiple places.
You don't need the loop for newly created elements. Nor do you need event delegation (though it can be useful).
Just make the handler a named function, and bind when you clone.
var piece = document.getElementsByClassName('piece');
function pieceHandler(event) {
//do stuff
}
function theLoop() {
for(var i=0; i<piece.length; i++) {
piece[i].addEventListener("mousemove", pieceHandler, false)
}
}
theLoop();
function createPiece(symbol, name) {
var clone = document.getElementById(symbol).cloneNode(true);
clone.addEventListener("mousemove", pieceHandler, false); // assign handler
clone.setAttribute('id',symbol+'-'+name);
document.getElementById(name).appendChild(clone);
}
createPiece(symbol, name);
Related
I would like to understand how to remove the event listeners which are added by class as shown below (remove event for div#mainMenu)
I have tried all available methods but none to my avail. Also I dont see this issue in the extJS4.x version but only in 3.4 version. Any plain JS code to handle this?
You can use getEventListeners function to get listeners map of an element. After that use removeEventListener method, to remove event from element. You can use this function to remove all events from an element.
function removeEventListeners(element, listenerMap) {
Object.keys(listenerMap).forEach(function (name) {
var listeners = listenerMap[name];
listeners.forEach(function (object) {
element.removeEventListener(name, object.listener);
});
});
}
removeEventListeners(elementRef,getEventListeners(elementRef));
Finally, I have figured out how to resolve this issue. The way to remove the listener is to go to parent div component & call removeAllListeners() method on the element. The element can be accessed in the afterRenderHandler function using this.el & then calling this.el.parent().removeAllListeners() so that it removes the un-required listeners interfering with the user experience. You could call the parent method in a chained fashion if the listener identified is a nth level parent (call the method n times).
Ex: this.el.parent().parent().removeAllListeners()
You could also try to access the element directly if you have the ID by calling: var el = Ext.get('mainMenu'); & then calling el.removeAllListeners();
Similar approach can be applied to any other JS frameworks by getting a handle to the element having an issue with the listeners.
I have several places throughout my code where I use .on to attach events (usually to delegate the events). We're changing around how we're doing a few things, and we're now wanting to add a .disabled class to the elements that we want to be disabled. I'd like to block all the events on disabled items without having to refactor each location, I'm wondering if it's possible.
Example code: I've added this to the top of my script
$('body').on('click', '.disabled', function(event){
console.log("blocked");
event.stopImmediatePropagation();
// event.preventDefault();
// event.stopPropogation();
return false;
});
And an example of my normal events:
$('.ActionsContainer').on('click', '.Link', functions.ClickAction);
Problem is that even with the return false and all the others it still runs both the "blocked" and functions.ClickAction
Is there anyway around refactoring every one? I mean I can change that line below to:
$('.ActionsContainer').on('click', '.Link:not(.disabled)', functions.ClickAction);
but that's really annoying, and feels brittle.
It's not too hard. You'll need to take advantage of jQuery's special events and basically override calls to any of the original event handlers setup in the existing code. jQuery's special events hooks let you override a number of features of the event system. jQuery essentially sets up it's own handler on an element the first time a listener is attached, and then adds the callback for the listener to its queue. As other listeners get attached to the element later, their callbacks get added to this queue as well.
Using the 'events.special.click' hook, we can add a function that gets called prior to any callbacks on that element's event queue which lets us intercept the call and check for, as you mentioned, that the element has a 'disabled' class and if so, stop the original callback from executing; or if it doesn't have the class, allow the original callback to execute normally.
I've put together a jsFiddle to show how it works. See if that solves your issue. The code for the override using special events is embedded below the link:
http://jsfiddle.net/datchley/bthcv/
// ADDED TO OVERRIDE CLICKS ON 'DISABLED' ELEMENTS
(function($) {
$.event.special.click = {
add: function(handle) {
// Save original handler
var orig_handlefn = handle.handler,
$el = $(this);
// Reassign our new handler to intercept here
handle.handler = function(ev) {
if ($el.hasClass('disabled')) {
// Don't allow clicks on disabled elements
$('.output').html('<b>Warning</b> You clicked a disabled element!');
ev.preventDefault();
}
else {
return orig_handlefn.apply(this, arguments);
}
};
}
};
})(jQuery);
Assuming every .Link has that container and you're handling all events at that container, this is the most straightforward way:
$('.disabled').click( function(e){ e.stopPropagation(); } );
stopProp prevents that event from ever bubbling up to the action containers.
Im using Javascript to build a button click and puzzle adventure game. The game will allow a series of button commands. When the "go" command is clicked, the buttons change to different exits, and the class is changed to exit, like so:
function setExitButtons(){
clearButtons();
for (var i = 0; i < player.currentRoom.exits.length; i++) {
var buttoni = button[i];
buttoni.className = "exit";
buttoni.innerHTML = player.currentRoom.exits[i].name;
$(buttoni).show();
}
}
Where clearButtons hides all of the buttons so only the correct ones show, and button[] is the nodelist for the buttons.
The class does change when this function is called.
I then have another jquery function with a class selector, like so:
$(".exit").click(function(){
//roomchangefunction
});
The .exit function is not activated when the button with exit class is clicked. I have a document ready function encompassing the whole part. Thoughts?
This only runs once, when the document loads:
$(".exit").click(function(){
//roomchangefunction
});
At that time, there are no matching elements for .exit. So no click handlers are assigned. After that, this never runs again.
Since the elements are dynamically changing, I recommend binding a click handler to a common parent element using .on() instead. Something like this:
$(document).on('click', '.exit', function () {
//roomchangefunction
});
The difference is that the click event is actually assigned to a common parent (in this case document, though any common parent element will work such as a div which always contains the .exit elements). When an element is clicked, the "click" event occurs on that element and all the way up the DOM. So this handler would be invoked. The second argument is a filter, so it looks for elements which match that filter when invoking the handler function.
That way the filter for .exit happens when the element is clicked, rather than when the document is loaded, so that elements which are dynamically changed during the life of the document are still handled.
You're adding the class name after the page has loaded so need to use .on() with delegated events. Basically it means, the event is bound to the parent (in this case document), but affects the designated children (in this case .exit).
$(document).on('click', '.exit', function(){
//roomchangefunction
});
You need to use .on() like so:
$(".exit").on("click", function(){
//roomchangefunction
});
I am using a third party script that I don't want to modify.
In this script, there is a function that I need to listen for that adds a class to a dom node.
function _showElement(targetElement) {
targetElement.element.className += 'this-class';
}
What I would like to do is listen for this element to be called and whenever it does add a class to its parent with pure javascript.
function addThis() {
var doc = document;
liveEl = doc.getElementsByName("this-class");
// Code to add class to parent
}
The simplest "brute force" solution is to hijack (redefine) the function:
function _showElement(targetElement) {
console.log('original');
}
_showElement(); // hijacked
function _showElement(targetElement) {
console.log('hijacked');
}
Unfortunately i don't think you can save the original behavior of the function, so you need to copy-paste it:
function _showElement(targetElement) {
targetElement.element.className += 'this-class';
addThis()
}
Another solution, more specific to your problem (do something when the className of an element changes) you can listen for mutation events on the DOM:
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Basically you create a MutationObserver and observe attribute changes on your element. It might not be such a great solution if you need to track all the nodes, but in some cases it might come in handy.
You can't do this directly. You can't listen event like dom attribute change. However, there is a way that you can identify the event which is trigger _showElement and attach a one more handler to the event which should call addThis function.
I've got a bunch divs which each contain a remove link attached with the click event below:
var observeRemoveRoom = function
$('.remove_room').click(function(){
$(this).parent().removeClass('active');
});
}
Clicking it removes the 'active' class of the parent (the div). I call this observeRemoveRoom function on window load which works fine.
The thing is, I have another function which adds more of the same divs. Since the a.remove_room links contained within the new divs weren't around on window.load I need to call observeRemoveRoom.
Am I somehow duplicating the event handlers? Does jQuery overwrite them? If so should I unbind the handlers?
Each time you call observeRemoveRoom jQuery will add a new unique event handler function for a click event.
So yes, you need to .unbind() either all currently bound handlers by just calling .unbind() without arguments, or be specific and pass in a function reference.
You can try a live query to keep them updated: http://docs.jquery.com/Plugins/livequery
Yes, you will be duplicating the event-handlers if you call observeRemoveRoom again, but it might not be noticeable since you are only calling the removeClass method which does nothing if the class is not found, which would be the case after the first listener is triggered.
Instead you can un-bind and re-bind the click event each time, like:
var observeRemoveRoom = function(){
var remove_class = function(){
$(this).parent().removeClass('active');
};
$('.remove_room').off('click', remove_class).on('click', remove_class);
}
But that said, it is recommended that you do this outside this function`, rather than binding and unbinding the event every time, like:
$(document).ready(function(){
var remove_class = function(){
$(this).parent().removeClass('active');
};
// If the element exists at dom ready, you can bind the event directly
$('.remove_room').on("click", remove_class);
// If the element is added in dynamically, you can [delegate][1] the event
$('body').on("click", '.remove_room', remove_class);
// Note: Although I've delegated the event to the body tag in this case
// I recommend that you use the closest available parent instead
});
http://api.jquery.com/on/#direct-and-delegated-events : [1]