I'm creating a template generator using hover and click events on added elements. Long story short: When I click or hover over a sibling of an element, it runs all events starting with the deepest sibling and ending up with the "highest" parent.
I've managed to find a solution even though I consider it pretty dirty:
var childclicked = false;
$('.container > .column').click(function(event) {
// do stuff
childclicked = true;
});
$('.container').click(function(event) {
// preventing code to be executed
if(!childclicked) {
// do stuff
}
});
Because the sibling's event handler is called first it sets a variable that has to be checked in the parent's event handler.
Isn't there a better way to just exclude siblings?
I guess you just need to stop the events from bubbling. You can easily achieve that by either calling explicitly
event.stopPropagation();
or by returning false from within the event handler itself. Returning false implicity calls .stopPropagation() and .preventDefault().
Related
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.
i'm trying to register the second click on a link, by adding a new class and finding it with jQuery. But it won't change the class after the 1st click.
Hope it makes sense and thank you in advance.
// Listen for when a.first-choice are being clicked
$('.first-choice').click(function() {
// Remove the class and another one
$(this).removeClass('first-choice').addClass('one-choice-made');
console.log('First Click');
// Some code goes here....
});
// Make sure the link isn't fireing.
return false;
});
// Listen for when a.one-choice-made are being clicked
$('.one-choice-made').click(function() {
// Remove the class and another one
$(this).removeClass('one-choice-made').addClass('two-choice-made');
console.log('Second Click');
// Some code goes here....
});
// Make sure the link isn't fireing.
return false;
});
At load, .one-choice-made does not exist, so when you call $('.one-choice-made'), it returns an empty jQuery object, hence the click() handler is not added to anything.
What you want to do is attach the handler to something that will always exist, which will respond to the click event (i.e. a parent/ancestor element). This is what $.on() will do for you when called in a delegated handler syntax (i.e. with a filter selector):
$(document).on('click', '.one-choice-made', function() {
// my second function
}
In this case, jQuery attaches a special handler to document, which watches for click events that propagate to it from children elements. When it receives a click, jQuery looks at the target of the click and filters it against the selector you provide. If it matches, it calls your function code. This way, you can add new elements with this class at any time, as long as they are children of the elements from the selector(s) you applied .on() to. In this case, we used document, so it will always work with new elements.
You can pare this down to a known permanent parent element to reduce click events, but for simple cases document is fine.
NOTE: In the same way, removing the class first-choice will not have any affect on whether the first click handler is called, because the handler is applied to the element. If you remove the class, the element will still have the handler. You will need to use a delegated handler for that as well:
$(document).on('click', '.first-choice', function() {
// my first function
}
Demo: http://jsfiddle.net/jtbowden/FxqX9/
Since you're changing the class you need to use .on()s syntax for delegated events.
Change:
$('.one-choice-made').click(function() {
to:
$(document).on('click', '.one-choice-made', function() {
Ideally you want to use an element already in the DOM that's closer than document, but document is a decent fallback.
I need some help with the callbacks. For some reason, they don't work really well.
I'm making a game with jQuery. I have a <div id='button'></div> for all the buttons that are going to be in the game. The game is going to have two buttons that make actions, and a question on top of it. The question is controlled by a <h3 id='text'></h3>. What I want to know, is that for some reason I can't set callback functions to the button's ID's. In example,
I'd have the yes or no, that have their own id's set through jQuery like this:
$('#button').html('<button id='yes'>Yes</button><button id='no'></button>');
But for some reason, I would be able to set this:
$('yes').click(function(){
//function I would want
});
Of course, that's not what my code has, that was just an example. Here's the real code:
$(document).ready(function(){
$('#main,#batman,#car,#cop,#hobo,#knife,#gangfight,#ganggun,#gangknife,#blood,#hr').hide(-100);
var hr=$('#hr');
var main=$('#main');
var batman=$('#batman');
var car=$('#car');
var hobo=$('#hobo');
var cop=$('#cop');
var knife=$('#knife');
var gangfight=$('#gangfight');
var ganggun=$('#ganggun');
var gangknife=$('#gangknife');
var blood=$('#blood');
var text=$('#text');
var button=$('#button');
$('#start').html('Are you ready to play?');
$('#button').html('<button id="yes">Yes</button><button id="no">No</button>');
$('#yes').click(function(){
$('#yes,#no').hide(function(){
$('#start').hide();
main.fadeIn(-100);
hr.fadeIn(-100,function(){
text.delay(1000).html("You were just wandering around in the streets of new york, when suddenly.. You see batman!! You've never really liked him, what do you do?")
button.html('<button id="fight">Fight</button><button id="leave">Leave</button>',function(){
batman.fadeIn(1000);
$('fight').click(function(){
});
$('leave').click(function(){
text.fadeOut(function(){
text.text('Good call. As you leave, you encounter a hobo. What do you do?');
});
});
});
});
});
});
$('#no').click(function(){
$('#yes,#no').hide();
$('#start').text('Oh, okay then. Come back later!');
});
});
I'm just wondering.. How can I set callback functions to the 'fight' and 'leave'.
If you're wondering why there's all these variables at the start, those are just the images and characters.
You can't set a click handler on an element that doesn't exist. What you should do is use .on to bind a element further up the tree. Something like:
$("#someparentelement").on("click", "#yes", function() {
// your code
});
Which version of jQuery are you using? You should probably use jQuery.on() in this situation since your click handler code probably gets executed before the button is actually available in the DOM.
$("#button").on("click", "#yes", function (event) {
// Your yes-button logic comes here.
});
For more details and possibilities, read about the .on(events [, selector ] [, data ], handler(eventObject)) method in the jQuery documentation:
If selector is omitted or is null, the event handler is referred to as direct or directly-bound. The handler is called every time an event occurs on the selected elements, whether it occurs directly on the element or bubbles from a descendant (inner) element.
When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.
In this case, you want to delegate the event since your element is not yet available in the DOM when you're binding the event.
Don't use the click(), use on('click') and attach it to the document.
Creating a handler this way, will ensure that any new elements will be able to trigger the event.
$('fight') selects fight tag, not the tag with fight id. Try to use $('#fight') instead.
Say I have structure like:
<ul id="a">text
<li id="b">text</li>
<li id="c">text</li>
</ul>
How can I assign different event handlers (say, onclick listener) to a, b and c? When I assign a handler to <ul>, it will be triggered when any of the <li> is clicked.
instead of setting a single handler for each element inside your list it's better to use a single event listener on the parent and, using event delegation, detect which is the id of the element the user clicked
$('ul').on('click', function(evt) {
id = evt.target.id;
switch (id) {
"a" : ... break;
"b" : ... break;
"c" : ... break;
default: ... ;
}
});
Whenever an event is fired, it "bubbles". This means it is called on the actual element associated with it, but it is also fired once for every parent element in the chain all the way up to the top. So in your case, whenever you click on b or c, the event will also be fired for a. To avoid this, you need to stop the bubbling.
Specifically, in your event handlers for b and c, you should stop the event bubbling by doing this:
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
Most browsers support stopPropagation on the event to end the bubbling. Older versions of IE use the cancelBubble property. So the above should be cross-browser compatible.
I know you didn't specify jQuery, but since it's so popular, it's worth noting that if you are using jQuery, this condition is hidden behind the framework, so you can just do:
event.stopPropagation()
Every event bubbles up the DOM tree (or at least "should", as there is IE). You may want to stop that bubbling, see Ben Lee's answer. But there is a better way:
Just check whether the event triggering your listener was fired for the element you're watching or not. Then execute your handler, or escape.
document.getElementById("a").addEventListener("click", function(e) {
var a = e.currentTarget; // reference to #a
var t = e.target; // this may be #b or #c or any of their children - or not
if (a != t)
return;
// else
// do what you wanted to do
}, false);
You can use this.
$('ul li#a').click(function(){
//some thing here...
});
$('ul li#b').click(function(){
//some thing here...
});
I also faced the same problem in one of my projects. What I did was first declared a global variable(for flag purpose and default value false). Then I triggered the onmouseover and onmouseout events on all the children and changed the flag value to true whenever the cursor is over a child ( and reverting it back to false whenever it leaves the child). So in declaring the onclick function for the parent, just put a condition to check the value of flag.
Assuming that there are a large number of elements throughout the site that have an unknown number and type of events bound to them.
If I need to override all of these events with one single bound event, and only that event will fire, what are some recommendations?
I would be binding the event to a click event handler, and I am using jQuery.
Thanks in advance.
You’re looking for jQuery#unbind.
To remove all event handlers on an element or a set of elements, just do:
$('.some-selector').unbind();
To unbind only click handlers, use unbind('click'):
$('.some-selector').unbind('click');
To unbind all click handlers and immediately bind your own handler after that, you can do something like this:
$('.some-selector').unbind('click').click(function(event) {
// Your code goes here
});
Note that this will only work for events bound using jQuery (using .bind or any jQuery method that uses .bind internally). If you want to remove all possible onclick events from a given set of elements, you could use:
$('.some-selector')
.unbind('click') // takes care of jQuery-bound click events
.attr('onclick', '') // clears `onclick` attributes in the HTML
.each(function() { // reset `onclick` event handlers
this.onclick = null;
});
I would like to provide a thought without removing all events all together (just override them).
If your new one single bound event (we call it "click" here) is specific to the element it binds to, then I believe you can ignore any other events simply by stopPropagation() function. Like this
$("specific-selector").on("click", ".specific-class", function (e) {
e.stopPropagation()
// e.stopImmediatePropagation()
/* your code continues ... */
});
It will stop events bubbles up, so your other events won't fire. use stopImmediatePropagation() to prevent other events attached onto the same elements as "click" does.
For example, if "mouseleave" event is also bind to $("specific-selector .specific-class") element, it won't fire, too.
At last, all other events won't fire on this element but your new "click" element.
The unsolved question is, what if other events also use stopPropagation()? ... Then I think the one with best specification wins, so try to avoid complex, too many events is final suggestion.
You can see "Direct and delegated events" on jQuery site for more information.
Looks like this is pretty simple actually:
$('#foo').unbind('click');
$('#foo').bind('click', myNewFunction);
Thanks for your responses though.
Try to use live instead of bind. Then you can easily remove live binding with die from selector which is fast operation and set another live equally fast.
$('selection here').live('..', .....); // multiple invocations
$('selection here').die();
$('selection here').live('click',.....);
DOM is not touched at all. Event condition is evaluated on event occurrence.
But generally if you just want to swap handler functions why not to do it this way:
var ahandler = function(evt) { /* first implementation */ }
$('.selector').bind('click', function(evt) { ahandler(evt); });
//and then if you want to change handlers
ahandler = function(evt) { /* new implementation */ };
This gives absolutely no cost of any changes, rebinding etc.