Okay so a simple thing that I don't know how to search for, wasn't able to get an answer anywhere...
Basically, I'm using XML to populate a list. What happens is when clicking on the first tier li, it adds all the different suburbs to the child ul. This generates then the structure below:
<li class="province-active" style="background-image: url("img/minus-icon-storefinder.png");">
<p>Gauteng</p>
<ul class="province-sub-menu">
<li class="province-sub-nav-item" data-suburb="Strijdom Park">Strijdom Park,Randburg</li>
<li class="province-sub-nav-item" data-suburb="Edenvale">Edenvale,Eastrand</li>
<li class="province-sub-nav-item" data-suburb="Strubens Valley">Strubens Valley,Roodepoort</li>
<li class="province-sub-nav-item" data-suburb="Centurion">Centurion,Pretoria</li>
</ul>
</li>
Okay the problem is, using the JQuery below, it first triggers the click event for "province-sub-nav-item" and then triggers the click event for "province-active" (the parent li). This is the JS I'm using for the children li.
$(document).on("click", ".province-sub-nav-item", function(e){
e.stopPropagation();
$shop = $(this).attr("data-suburb");
loadShop($shop);
});
And this is the function for the parent li.
$(".province-active").click(function(e) {
$li = $(this);
$province = $li.children("p").text();
$(".store-locations").css("width", 375);
getMap($li, $province, 15);
});
So basically, I just need to not fire the click event for "province-active" without firing the click event for "province-active" and stopPropagation(); isn't working.
Thanks guys.
It is because you have used used event delegation and bounded the handler to teh document object.
So when event bubbles up the province-active elements handler will be triggered before the document objects handler is triggered so your stop propagation does not have any effect.
Demo: Fiddle - see the order of logging in the console
One possible solution is to use event delegation for the province-active element also
$(document).on("click", ".province-active", function (e) {
var $li = $(this);
var $province = $li.children("p").text();
$(".store-locations").css("width", 375);
getMap($li, $province, 15);
});
Demo: Fiddle - see the order of logging in the console
Alternatively this works too:
$(".province-sub-menu").on("click", ".province-sub-nav-item", function(e){
e.stopPropagation();
$shop = $(this).attr("data-suburb");
loadShop($shop);
});
Related
I'm trying to disable a li click event after it has clicked the first time. Essentially to stop the array data being doubled. The click is working fine for each time. My current method doesn't appear to be working. I also need to disable the other li's from being clicked once the first one has :)
Thanks
JS code is:
$('#eventType ul li').click(function(e) {
e.preventDefault();
var value = $(this).attr('value');
answers.push(value);
// Below isn't working
$(this).click(function() {
return false;
});
console.log(answers);
});
you need to use one:
$('#eventType ul li').one('click',function(){
//your code here
});
this event will be fired only once
UPDATE
you can do that using $.off()
$('#eventType ul li').one('click',function(){
//your code here
$('#eventType ul li').off('click');
});
jQuery is just JavaScript so you can easily add behaviors that you want
// basic jQuery plugin boilerplate
$.fn.once = function once(eventType, f) {
// this = the selected elements
return this.each(idx, elem) {
// create reference to jQuery-wrapped elem
var $elem = $(elem);
// add event listener for eventType
$elem.on(eventType, function(event) {
// call the event handler
return f(event);
// remove the event handler
$elem.off(eventType, f);
});
});
};
Usage would look like this
$('#eventType ul li').once('click', function(event) {
console.log("you will only see this once");
});
However, this is obviously a common need so it exists in jQuery already. It's called $.one. As APIs grow, you may not know about the existence of such procedures. This answer exists to show you that you can use your brain to program the things that you want or that might be missing from a particular library. This lessens your dependence on the creator's of the lib to introduce the functionality you need.
EDIT
In a comment, you ask if the event handler can be disabled for all other LI elements after the first LI is clicked. The trouble here is that jQuery uses implicit iteration, which means that when you call $('li').on('click', ...), jQuery will bind an onclick event handler for each LI.
A better solution to this problem would be to use jQuery's event delegation
// only fire event handler for the first LI clicked
$('ul').one('click', 'li', function(event) {
console.log($(this).text());
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
This will delegate the event listener to the children LI, but once one of the LI is clicked, the event handler will be removed (because we delegated using the $.one procedure).
Try clicking one LI, you will see a message in the console. When you click the second LI, nothing will happen because the event handler was removed.
var used = false;
$('#eventType ul li').click(function(e) {
if (used == false) {
used = true;
e.preventDefault();
var value = $(this).attr('value');
answers.push(value);
console.log(answers);
}
});
the way you did it was just adding another on click handler, not removing or overriding the old ond.
You can use CSS classes; add the class 'disabled' to elements you don't need, and avoid adding elements that have the classe 'disabled'.
https://plnkr.co/edit/6aloNPETHGxfiP5oYZ9f?p=preview
$('ul li').click(function(e) {
if(!$(this).hasClass('disabled')) {
var value = $(this).text();
answers.push(value);
$('li').addClass('disabled');
}
console.log(answers);
});
I've been trying to get a simple vertical navigation to display a list item's children on click.
However when you then click any of those nested, children list items you are in essence 'clicking' the parent list item again. Which triggers my click function to remove '.active' and closes the parent list item right before the user is redirected to their chosen page. This looks bad and is totally annoying.
Any advice on how to get a click function to not affect the children list items?
$('#sidebar > li.parent').click(function(){
if($(this).hasClass('active')){
$(this).removeClass('active');
}else{
$(this).addClass('active');
}
});
HTML
<aside id="tertiary" class="tertiary">
<ul id="sidebar" class="sidebar">
<li class="active">Portal</li>
<li>Start Here</li>
<li>Cards</li>
<li class="parent">Programs
<ul>
<li>LOS</li>
<li>Safety</li>
<li>Retirement</li>
<li>Wellness</li>
<li>Investors</li>
</ul>
</li>
<li>Marketplace</li>
<li>Reporting</li>
</ul>
</aside>
You would need to stop propagation in the children... not the parent
http://jsfiddle.net/TrueBlueAussie/g6a496Lf/
$('#sidebar > li.parent').click(function (e) {
e.preventDefault();
$(this).toggleClass('active');
}).find("ul li").click(function(e){
e.stopPropagation();
});
The e.preventDefault() is just there to stop the link clicking on the parent in the example. e.stopPropagation() stops the child click bubbling up the DOM.
I chained the two handlers together, so that you did not need a separate selector like this:
$('#sidebar > li.parent ul li").click(function(e){
e.stopPropagation();
});
You could add a click handler for the child elements that uses stopPropagationto prevent the click event bubbling to parent elements:
$('#sidebar > li li').click(function(e){
e.stopPropagation();
});
Further reading: MDN docs for Event.
Before you stop propagation of events, take some time to spin up on why you shouldn't do so.
From css-tricks.com, "The Dangers of Stopping Event Propagation", very briefly:
"Modifying a single, fleeting event might seem harmless at first, but it comes with risks. When you alter the behavior that people expect and that other code depends on, you're going to have bugs. It's just a matter of time."
"A much better solution is to have a single event handler whose logic is fully encapsulated and whose sole responsibility is to determine whether or not the menu should be hidden for the given event."
With that being said, you could do something like
$('#sidebar').on('click', function( e ) {
var $target = $(e.target).closest('li');
if ( $target.hasClass('parent') ) {
$target.toggleClass('active');
}
});
Working example:
http://jsfiddle.net/g6a496Lf/1/
I am stuck at very normal scnerio. I have HTML code generated by YII CLINK PAGER Pagination widget :
<ul class="yiiPager" id="yw0">
<li class="first hidden"><< First</li>
<li class="previous hidden">< Previous</li>
<li class="page selected">1</li>
<li class="page">2</li>
</ul>
and I want AJAX pagination for my requirement, so that I have wrote Jquery code :
$("ul.yiiPager li.page a").on('click',function (e){
e.preventDefault();
alert($(this).attr('href'));
return false;
loadlistData($(this).attr('href'));
});
But by clicking on any of the <a> tag it is redirecting to the LINK given in href for preventing that i have used e.preventDefault(); but still it is not coming in JQuery code and alert not showing.
Please help. Thanks in advance.
Since your anchor tags are created dynamically. You need to use event delegation. Because the elements should be present on the dom at the time of event binding. In the case of event delegation events are binded to the document or parent element which is presented on the dom
$(document).on('click', "ul.yiiPager li.page a", function(e) {
e.preventDefault();
alert($(this).attr('href'));
loadlistData($(this).attr('href'));
});
Case 1 (direct):
$("ul.yiiPager li.page a").on("click", function() {...});
I want every ul.yiiPager li.page a inside ul.yiiPager li.page to listen up: when you get clicked on, do something.
Case 2 (delegated):
$("ul.yiiPager li.page").on("click", "a", function() {...});
ul.yiiPager li.page When any of your child elements which are "a" get clicked, do something with them.
Summary
In case 1, each of those spans has been individually given instructions. If new spans get created, they won't have heard the instruction and won't respond to clicks. Each a is directly responsible for its own events.
In case 2, only the container has been given the instruction; it is responsible for noticing clicks on behalf of its child elements. The work of catching events has been delegated.
I have this code that for now creates a Alert() with the value of that list item.
Now when i click a list item that has another list item as parent. it will alert() them both.
$("#nav li").click(function() {
$LiValue = this.value;
alert($LiValue);
});
Example. here is the HTML
<li value="1">Home</li>
<li value="2">Information
<ul class="subs">
<li value="3">History</li>
<li value="4">Present</li>
<li value="5">Future</li>
</ul>
</li>
Now when i click on list item "Information" it will return with value 2
When i click on list item "Present" it will return value 4 and then 2.
How can i only return the list item i click on and not the parent?
--------->>>> SOLVED!! (can't accept answer yet)
Thank you all for helping me. i will accept the answer as soon as i can. thank you!
Events in JavaScript naturally bubble up the DOM tree, from child elements to their ancestors. You can stop this behavior by stopping the event propagation.
$("#nav li").click(function(e) {
e.stopPropagation();
$LiValue = this.value;
alert($LiValue);
});
The fix to your problem is stopPropagation(). The jQuery documentation tells you that this function "Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event."
Basically, the event will not be fired for any of the parent elements. In order to use this method, handle the first parameter that your click function provides you. Then you call the method inside the function. Your method should look like this
$("#nav li").click(function(e) {
e.stopPropagation()
$LiValue = this.value;
alert($LiValue);
});
$("#nav li").click(function(e) {
$LiValue = this.value;
alert($LiValue);
return false;
});
or try to add return false.
Explanation : The difference is that return false; takes things a bit further in that it also prevents that event from propagating (or "bubbling up") the DOM. The you-may-not-know-this bit is that whenever an event happens on an element, that event is triggered on every single parent element as well.
So in other words:
function() {
return false;
}
// IS EQUAL TO
function(e) {
e.preventDefault();
e.stopPropagation();
}
see this link for further explanation
I am trying to code a cancel button for user input. The user will be able to edit an item after double-clicking on it and the cancel button will allow the user to cancel the action.
The double-clicking part of the code works great, as a text-input box appears with cancel button attached. But now since the DOM has changed, jQuery no longer select the new element, and therefore when the cancel button is clicked the event is not fired. To illustrate, the code are following:
<div id="main">
<ul class="todoList">
<li id="todo-1" class="todo">
<div class="text">New Todo Item. Doubleclick to Edit
</div>
<div class="actions"> Edit</div>
</li>
</ul>
</div>
var currentTODO;
$('.todo').on('dblclick', function () {
$(this).find('a.edit').click();
});
$('.todo a').on('click', function (e) {
e.preventDefault();
currentTODO = $(this).closest('.todo');
});
$('.todo a.edit').on('click', function () {
var container = currentTODO.find('.text');
if (!currentTODO.data('origText')) {
// Saving the current value of the ToDo so we can
// restore it later if the user discards the changes:
currentTODO.data('origText', container.text());
} else {
// This will block the edit button if the edit box is already open:
return false;
}
$('<input type="text">').val(container.text()).appendTo(container.empty());
container.append(
'<div class="editTodo">' +
'<a class="cancel" href="#">Cancel</a>' +
'</div>');
});
// The cancel edit link:
$('.cancel').on('click', function () {
alert("oops");
});
Or here: http://jsfiddle.net/S7f83/42/
My question is therefore, how can I 'bind' the event after DOM has changed? Thank you very much
In your example the events are bound to the controls which exist at the moment of binding, and match the selector. If you would like the actions to apply for newly created controls, you shall bind the event to the document itself. As it is the highest level DOM element, all the events from lower levels will propagate up to it.
At the second parameter you give the context, so only if the event is received from that context will the function fire.
$(document).on('dblclick', '.todo', function () {
$(this).find('a.edit').click();
});
I won't cite your whole code, but you will get the idea from this one.
Binding listeners to the document is encouraged as it doesn't create as many bindings as many controls you have, just one top level binding, which waits for events to propagate up on the DOM tree. More info on optimalization: http://24ways.org/2011/your-jquery-now-with-less-suck/
Use
$('.todo').on('click', '.cancel', function () {
alert("oops");
});
so you delegate the event to an existing element (the .todo)
instead of
$('.cancel').on('click', function () {
alert("oops");
});
2 Methods
Bind the event to the cancel button after it is created http://jsfiddle.net/S7f83/44/
Bind a live (delegated) event to the cancel button http://jsfiddle.net/S7f83/43/
In the 2nd case, I bind the event delegation to the nearest parent, so the event doesn't need to bubble to the document element to be executed. This could improve some performance. It depends on the use whether you want to place the cancel button anywhere else or not.
EDIT: each li has a different id, so it's better to attach to the class .todo
$('.todo').on('click', 'a.cancel', function () {
alert("oops");
});