I am building a Windows 8.1 Store application with WinJS. When the user queries some search results show up in a <p class="searchresults">content</p> tag.
I'd like to add an event handler to the .searchresults class. I've done the following:
$('.searchresults').on('click', function() {
console.log("clicked");
});
I've tried even without .on()
$('.searchresults').click(function() {
console.log("clicked");
});
However the event never gets fired. I've set up a breakpoint, so I can see when it fires - but that never happens
I've tried to add an event handler via the WinJS way:
Microsoft.Maps.Events.addHandler(document.getElementsByClassName("searchresults"), 'click', myfunc);
Without success.
Any ideas why this is happening?
I will guess that you are creating the <p class="searchresults">content</p> object AFTER you try to install the event handler (a common problem with dynamic content). That will not work with normal event handling because the DOM object does not exist when you try to add the event handler to it.
If this is the case, then you need to use delegated event handling like this:
$(document.body).on('click', '.searchresults', function() {
console.log("clicked");
});
This will allow you to dynamically create the searchresults content at any time and the event handler will still fire via event delegation (events propagate up to their parents).
You haven't shown the HTML around the search results content, but the most optimal way to do this is to select the closest static parent to the search results (a parent that is not dynamically created and already exists at the time you attach the event handler) and attach the event to that:
$(closest static parent selector).on('click', '.searchresults', function() {
console.log("clicked");
});
Related
To improve maintenability I'm trying to replace HTML that's being dynamically generated in script with AJAX query results. But there's rather uncommon problem to bind event handlers to dynamic content, although there's a lot of solution much of them work just partially. I mean that if I write
$("#m" + event.PeerId).live("hover", function (args) {
debugger;
}
it works but
$("#m" + event.PeerId + " sendMessage").live("click", function (args) {
debugger;
}
doesn't work, the event doesn't occurs if I click. Also I can trace hover event of the root downloaded element but I can't trace load, ready, etc. - they also never occur.
Tell me please how can I handle button click that is some subelement of the loaded content.
If you are using any recent version of jQuery you should use a delegated event handler:
$(document).on("click", "#m" + event.PeerId + " sendMessage", function (args) {
debugger;
});
If your ids all start with m, but the peerid is not know yet, use this:
$(document).on("click", "[id^=m] sendMessage", function (args) {
debugger;
});
Delegated event handlers should be attached to a non-changing ancestor (document being the default if nothing closer to the changing elements is handy).
They work by listening for the event bubbling up the ancestors, to the one the handler is attached to, then applies the jQuery filter, the calls the function for each matching element that caused the event.
Note: Never use 'body' for delegated event handlers as it has bugs relating to styling (which can cause events to not occur). Always use document if you do not have a closer element that does not change.
I have two jquery functions that work together, one depends on a class, another removes the class.
Once it is removed I would expect the functionality to stop working, but it carries on?
Whats going on?
Here is the fiddle, try it out for yourself.
<div class="container disabled">
Go to Google
</div>
<label>
<input type="checkbox" />Enable link</label>
The JS
$('.disabled > a').click(function (e) {
e.preventDefault();
e.stopPropagation();
alert('should stop working');
});
$('input[type=checkbox]').change(function () {
$('.container').removeClass('disabled');
});
It looks like you want to be using delegated event handlers rather than static event handlers. Let me explain.
When you run a line of code like this:
$('.disabled > a').click(function (e) {
this installs an event handler on any objects that match the selector at that moment in time. Those event handlers are then in place forever. They no longer look at what classes any elements have. So changing a class after you install a static event handler does not affect which elements have event handlers on them.
If you want dynanamic behavior where which elements respond to an event does depend upon what classes are present at any given moment, then you need to use delegated event handling.
With delegated event handling, you attach the event "permanently" to a parent and then the parent evaluates whether the child where the event originated matches the select each time the event fires. If the child no longer matches the select, then the event handler will not be triggered. If it does, then it will and you can add/remove a class to cause it to change behavior.
The general form of delegated event handlers are like this:
$("#staticParent").on("click", ".childSelector", fn);
You ideally want to select a parent that is as close to the child as possible, but is not dynamic itself. In your particular example, you don't show a parent other than the body object so you could use this:
$(document.body).on("click", ".disabled > a", function() {
e.preventDefault();
e.stopPropagation();
alert('should stop working');
});
This code will then respond dynamically when you add remove the disabled class. If the disabled class is present, the event handler will fire. If it is not present, the event handler will not fire.
Working demo: http://jsfiddle.net/jfriend00/pZeSA/
Other references on delegated event handling:
jQuery .live() vs .on() method for adding a click event after loading dynamic html
jQuery .on does not work but .live does
Should all jquery events be bound to $(document)?
JQuery Event Handlers - What's the "Best" method
jQuery selector doesn't update after dynamically adding new elements
Changing the class after the event handler is bound has absolutely no effect as the event handler is not suddenly unbound, it's still bound to the same element.
You have to check for the class inside the event handler
$('.container > a').click(function (e) {
if ( $(this).closest('.container').hasClass('disabled') ) {
e.preventDefault();
e.stopPropagation();
}
});
$('input[type=checkbox]').change(function () {
$('.container').toggleClass('disabled', !this.checked);
});
FIDDLE
When the selector runs, it gets a list of elements including the one in question and adds a click event handler to it.
Then you remove the class - so any subsequent jQuery selectors wouldn't get your element - but you have already attached the event so it will still fire.
The selector you have used runs on the line you declared it - it isn't lazily initialized when clicks happen.
I have a document with:
<button class="route" data-route-url="/about">About</button>
About
<div id="page-content">
</div>
$('.route').click(function() {
console.log('.route.click()');
var url = $(this).data('route-url');
$('#page-content').html('loading...');
$.get(url, function(data) { $('#page-content').html(data); });
return false;
});
It works perfectly until this point, and loads the about page.
The about page bring another button that also calls "route":
<button class="route" data-route-url="/contact">Contact</button>
But, when I click on it nothing happens, and I also dont get the console.log message, so seems that the page, that was first loaded inside the div-content can not see the parent function called route, is this working as expected?
How am I supposed to make this work?
You can do this by using Event Delegation with .on() since your button is added dynamically to DOM, The event registered before doesnot apply to the newly added button. SO you need to go with Event delegation, i.e attach the event to the parent container (which exists already) or document element(as i have used in the code) and set up for delegation once an element with the class '.route' is available anytime now or in future.
$(document).on('click','.route' ,function() {
console.log('.route.click()');
var url = $(this).data('route-url');
$('#page-content').html('loading...');
$.get(url, function(data) { $('#page-content').html(data); });
return false;
});
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers. This element could be the container element of a view in a Model-View-Controller design, for example, or document if the event handler wants to monitor all bubbling events in the document.
As Jquery Mobile keeps some pages in the DOM when navigating around, a page may be visited multiple times when going back and forth.
If I'm binding to a page like below and inside this binding perform all my page logic, which includes "nested element bindings":
// listener for the page to show:
$(document).on('pagebeforeshow.register', '#register', function() {
// stuff
// page event bindings:
$(document).on('click.register', '.registerSubmitter', function(e) {
// do something
});
});
Going back and forth causes my nested binding to be attached multiple times.
Right now trying to work around this like so (doesn't work...):
$(document).on('click', '.registrySubmitter', function(e) {
if ( $(this).attr('val') != true ) {
$(this).attr('val') == true;
// do something
}
});
So I'm only allowing the first binding to pass and then I block every other binding attempt that comes along.
While this works, it's far from optimal.
Question:
How and when should event bindings be properly unbound/offed? Is there a general way (kill all) or do I have to do this binding per binding? Maybe more importantly: Is it better performance-wise to do a binding once and keep it or bind/unbind when the user comes to/leaves the page?
Thanks for input!
EDIT:
So I'm namespacing all my events and then listen for pageHide like so:
$(document).on('pagehide.register', '#register', function(){
$(document).off('.registryEvents');
});
While this seems to unbind, it also fires when ever I close a custom dialog/selectmenu on the page, so I'm loosing my bindings before leaving the page. So partial answer, I should use off(), but how to bind to the page really being left vs. opening and closing a select menu?
When you use .on() like that, you are delegating the event handling to the document element, meaning you can setup that delegated event binding anytime you want because the document element is always available.
I've got two suggestions:
Use the pageinit or pagecreate event to only run the page-specific bindings when pages are added to the DOM and initialized. Using this method I would not delegate the event bindings within the pageinit or pagecreate event handlers because when they fire, all the elements on the pseudo-page are in the DOM:
.
$(document).on('pageinit', '#register', function() {
//note that `this` refers to the `#register` element
$(this).find('.registerSubmitter').on('click', function(e) {
// do something
});
});
Bind the delegated event handlers once and don't worry about when pages are actually in the DOM:
.
//this can be run in the global scope
$(document).on('click.register', '.registerSubmitter', function(e) {
// do something
});
Basically when you bind an event using delegation like you are, the actual CPU hit of adding the event handler is less but each time an event is dispatched (of any kind that bubbles) it has to be checked if it matches the delegated event handler's selector.
When you bind directly to elements it generally takes more time to do the actual binding because each individual element has to be bound to rather than binding once to the document element like with event delegation. This however has the benefit that no code runs unless a specific element receives a specific event.
A quick blurb from the documentation:
Triggered on the page being initialized, after initialization occurs.
We recommend binding to this event instead of DOM ready() because this
will work regardless of whether the page is loaded directly or if the
content is pulled into another page as part of the Ajax navigation
system.
Source: http://jquerymobile.com/demos/1.1.0/docs/api/events.html
I have created a new YAHOO.util.KeyListener to attach to a specific element and have also created another new YAHOO.util.KeyListener to attach to the entire document. They are both associated with the enter key (keys:13).
In the handler function for the listener attached to the specific element, I have the following code:
getDetailsLocalnameInput = function(e) {
getDetails(localnameInput.value);
YAHOO.util.Event.preventDefault(e);
YAHOO.util.Event.stopPropagation(e);
};
Yet, the event from the keypress continues to propagate up to the key listener attached to the entire document. I do not want the handler for the key listener attached to the entire document to get kicked off. I am sure that both handlers are being called, but only want the handler attached to the specific element to run.
Is it correct to use YAHOO.util.Event.stopPropagation with YAHOO.util.KeyListener?
Is there a different way I should go about preventing the keypress event from being propagated?
I have also tried using the function YAHOO.util.Event.stopEvent and setting e.cancelBubble with no success.
I have been testing all of this with Firefox 3.5. I cannot get stopPropagation() to work at all.
Try this:
getDetailsLocalnameInput = function(e) {
getDetails(localnameInput.value);
if(window.event){
e.cancelBubble=true;//In IE
}else{
evt.stopPropagation();//in Others
}
//YAHOO.util.Event.preventDefault(e);
//YAHOO.util.Event.stopPropagation(e);
};