I feel like I've got a good handle on jQuery Mobile event handlers, but I just found myself making a mistake that led to some pretty severe performance issues. Assuming I'm not the only one to screw this up, I thought a good demo of my bug would be helpful.
One of the first things you run into when learning jQuery Mobile is how to replicate the jQuery behavior of $(document).ready. In fact, it's called out in the jQuery Mobile docs immediately:
Important: Use pageInit(), not $(document).ready()
The first thing you learn in jQuery is to call code inside the $(document).ready() function so everything will execute as soon as the DOM is loaded. However, in jQuery Mobile, Ajax is used to load the contents of each page into the DOM as you navigate, and the DOM ready handler only executes for the first page. To execute code whenever a new page is loaded and created, you can bind to the pageinit event. This event is explained in detail at the bottom of this page.
Makes sense, right? However, what if you want something to happen every time a page is shown? The pageinit event fires only when the page is created. If you want to run something everytime a page is displayed, you would use pageshow instead.
Ok... still simple, right? But let me show you how I screwed up. I needed to dynamically display some text whenever a page was shown. Here's what I did:
});
$("#page2").on("pageshow",function(e) {
$("#status").html("<p>Ran at "+ new Date() + "</p>");
So far so good. The page also contained a button that, when clicked, would perform an Ajax request. I updated my code to add a click listener. In the code below, I fake an Ajax request by using setTimeout.
$("#testButton").on("click", function(e) {
//fake a ajax hit
$("#status").html("<p>Loading...</p>");
console.log("Running click handler: "+Math.random())
setTimeout(function() {
$("#status").html("<p>Done: "+Math.random() + "</p>");
},400);
}); });
$("#page2").on("pageshow",function(e) {
$("#status").html("<p>Ran at "+ new Date() + "</p>");
If you know what's going to happen already - pat yourself on the back. If you don't - notice the console.log message. The first time I visit the page, I get one message in the console:
Ok, now I hit the Home button to return to the initial page in my jQuery Mobile app, returned to the page, and hit the button again:
I now had 2 console messages (plus the original) shown. If I left and came back a few times, it just got worse and worse. What makes this even more evil is that - without the console messages - you can't tell there's a problem at all. What happened though is that another tester ran the application a while and noticed worse and worse performance. The real code was doing an Ajax call retrieving a lot of data. Now imagine that running 8-10 times and the network traffic will grow incredibly.
You can demo this yourself here: http://raymondcamden.com/demos/2012/apr/1/test.html. I encourage you to try it without your console being open and see if - like me - you don't see an issue. Then open your console and the bug should then be obvious.
The solution was to simply use the right event for each of my features. Here's an updated version:
$("#testButton").on("click", function(e) {
//fake a ajax hit
$("#status").html("<p>Loading...</p>");
console.log("Running click handler: "+Math.random())
setTimeout(function() {
$("#status").html("<p>Done: "+Math.random() + "</p>");
},400);
}); }); $("#page2").on("pageshow",function(e) {
$("#status").html("<p>Ran at "+ new Date() + "</p>"); });
$("#page2").on("pageinit",function(e) {
You can demo this version here: http://raymondcamden.com/demos/2012/apr/1/test2.html
Archived Comments
Also, you can do something like this...
$("#testButton",$.mobile.activePage).on("click", function(e){...
Which has worked for me... although my coworker says there is an even better way.
Another way to do this would be to take advantage event bubbling and delegeated events in jQuery with .on().
Here is an example of your code using this method: http://jsfiddle.net/Drg62/3/
By binding your event a single time to the body, and passing the element that the event will fire on as the second argument, when you click the button, the click event will bubble up the DOM tree until it reaches the body. The body then will fire the event handler for any matched selectors.
The main benefit of handling events is only binding once to an existing element at runtime, then that event will be bound on any descendants that match the selector.
And you don't have to worry about stuff being bound multiple times.
For more information check out: http://api.jquery.com/on/
@Isaac: How does your code make things better? The only real difference is that you supplied a filter to the selector for the current page. Unless I'm misreading you.
@Mike: I kinda get your point there.
To be fair, I use this method on "live" and not with "on". When I was getting duplicate page loads and click events from bindings, selecting the current page stopped events from being bound to pages that were not visible. have not tried it with "on".