Related:
This article can also be found as a part of my blog
HERE.
$(document).on('pageinit') vs $(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. Because of this
$(document).ready()
will trigger before your first page is loaded and every code intended
for page manipulation will be executed after a page refresh. This can
be a very subtle bug. On some systems it may appear that it works fine,
but on others it may cause erratic, difficult to repeat weirdness to
occur.
Classic jQuery syntax:
$(document).ready(function() {
});
To solve this problem (and trust me this is a problem)
jQuery Mobile
developers created page events. In a nutshell page events are events
triggered in a particular point of page execution. One of those page
events is a
pageinit event and we can use it like this:
$(document).on('pageinit', function() {
});
We can go even further and use a page id instead of document selector. Lets say we have jQuery Mobile page with an id
index:
<div data-role="page" id="index">
<div data-theme="a" data-role="header">
<h3>
First Page
</h3>
<a href="#second" class="ui-btn-right">Next</a>
</div>
<div data-role="content">
<a href="#" data-role="button" id="test-button">Test button</a>
</div>
<div data-theme="a" data-role="footer" data-position="fixed">
</div>
</div>
To execute a code that will only available to the index page we could use this syntax:
$('#index').on('pageinit', function() {
});
Pageinit event will be executed every time page is
about be be loaded and shown for the first time. It will not trigger
again unless page is manually refreshed or ajax page loading is turned
off. In case you want code to execute every time you visit a page it is
better to use
pagebeforeshow event.
Here's a working example :
http://jsfiddle.net/Gajotres/Q3Usv/ to demonstrate this problem.
Few more notes on this question. No matter if you are using 1 html
multiple pages or multiple html files paradigm it is advised to
separate all of your custom javascript page handling into a single
separate js file. This will note make your code any better but you will
have much better code overview, especially while creating a
jQuery Mobile application.
There's also another special
jQuery Mobile event and it is called
mobileinit.When
jQuery Mobile starts, it triggers a
mobileinit event on the document object. To override default settings, bind them to
mobileinit. One of a good examples of
mobileinit usage is turning off ajax page loading, or changing default ajax loader behavior.
$(document).on("mobileinit", function(){
//apply overrides here
});
Page events transition order
First all events can be found here:
http://api.jquerymobile.com/category/events/
Lets say we have a page A and a page B, this is a unload/load order:
- page B - event pagebeforecreate
- page B - event pagecreate
- page B - event pageinit
- page A - event pagebeforehide
- page B - event pagebeforeshow
- page A - event pageremove
- page A - event pagehide
- page B - event pagebeforeshow
- page B - event pageshow
For better page events understanding read this:
pagebeforeload
, pageload
and pageloadfailed
are fired when an external page is loaded
pagebeforechange
, pagechange
and pagechangefailed
are page change events. These events are fired when a user is navigating between pages in the applications.
pagebeforeshow
, pagebeforehide
, pageshow
and pagehide
are page transition events. These events are fired before, during and after a transition and are named.
pagebeforecreate
, pagecreate
and pageinit
are for page initialization.
pageremove
can be fired and then handled when a page is removed from the DOM
Page loading jsFiddle example:
http://jsfiddle.net/Gajotres/QGnft/
If AJAX is not enabled, some events may not fire.
Prevent page transition
If for some reason page transition needs to be prevented on some condition it can be done with this code:
$(document).on('pagebeforechange', function(e, data){
var to = data.toPage,
from = data.options.fromPage;
if (typeof to === 'string') {
var u = $.mobile.path.parseUrl(to);
to = u.hash || '#' + u.pathname.substring(1);
if (from) from = '#' + from.attr('id');
if (from === '#index' && to === '#second') {
alert('Can not transition from #index to #second!');
e.preventDefault();
e.stopPropagation();
// remove active status on a button, if transition was triggered with a button
$.mobile.activePage.find('.ui-btn-active').removeClass('ui-btn-active ui-focus ui-btn');;
}
}
});
This example will work in any case because it will trigger at a
begging of every page transition and what is most important it will
prevent page change before page transition can occur.
Here's a working example:
Prevent multiple event binding/triggering
jQuery Mobile
works in a different way
then classic web applications. Depending on how you managed to bind
your events each time you visit some page it will bind events over and
over. This is not an error, it is simply how
jQuery Mobile
handles its pages. For example, take a look at this code snipet:
$(document).on('pagebeforeshow','#index' ,function(e,data){
$(document).on('click', '#test-button',function(e) {
alert('Button click');
});
});
Working jsFiddle example:
http://jsfiddle.net/Gajotres/CCfL4/
Each time you visit page
#index click event will is going to be bound to button
#test-button. Test it by moving from page 1 to page 2 and back several times. There are few ways to prevent this problem:
Solution 1
Best solution would be to use
pageinit
to bind events. If you take a look at an official documentation you will find out that
pageinit
will trigger ONLY once, just like document ready, so there's no way
events will be bound again. This is best solution because you don't
have processing overhead like when removing events with off method.
Working jsFiddle example:
http://jsfiddle.net/Gajotres/AAFH8/
This working solution is made on a basis of a previous problematic example.
Solution 2
Remove event before you bind it:
$(document).on('pagebeforeshow', '#index', function(){
$(document).off('click', '#test-button').on('click', '#test-button',function(e) {
alert('Button click');
});
});
Working jsFiddle example:
http://jsfiddle.net/Gajotres/K8YmG/
Solution 3
Use a jQuery Filter selector, like this:
$('#carousel div:Event(!click)').each(function(){
//If click is not bind to #carousel div do something
});
Because event filter is not a part of official jQuery framework it can be found here:
http://www.codenothing.com/archives/2009/event-filter/
In a nutshell, if speed is your main concern then
Solution 2 is much better then Solution 1.
Solution 4
A new one, probably an easiest of them all.
$(document).on('pagebeforeshow', '#index', function(){
$(document).on('click', '#test-button',function(e) {
if(e.handled !== true) // This will prevent event triggering more then once
{
alert('Clicked');
e.handled = true;
}
});
});
Working jsFiddle example:
http://jsfiddle.net/Gajotres/Yerv9/
Tnx to the
sholsinger for this solution:
http://sholsinger.com/archive/2011/08/prevent-jquery-live-handlers-from-firing-multiple-times/
pageChange event quirks - triggering twice
Sometimes pagechange event can trigger twice and it does not have anything to do with the problem mentioned before.
The reason the pagebeforechange event occurs twice is due to the
recursive call in changePage when toPage is not a jQuery enhanced DOM
object. This recursion is dangerous, as the developer is allowed to
change the toPage within the event. If the developer consistently sets
toPage to a string, within the pagebeforechange event handler,
regardless of whether or not it was an object an infinite recursive
loop will result. The pageload event passes the new page as the page
property of the data object (This should be added to the documentation,
it's not listed currently). The pageload event could therefore be used
to access the loaded page.
In few words this is happening because you are sending additional parameters through pageChange.
Example:
<a data-role="button" data-icon="arrow-r" data-iconpos="right" href="#care-plan-view?id=9e273f31-2672-47fd-9baa-6c35f093a800&name=Sat"><h3>Sat</h3></a>
To fix this problem use any page event listed in
Page events transition order.
Page Change Times
As mentioned, when you change from one JQuery Mobile page to
another, typically either through clicking on a link to another JQuery
Mobile page that already exists in the DOM, or by manually calling
$.mobile.changePage, several events and subsequent actions occur. At a
high level the following actions occur:
- A page change process is begun
- A new page is loaded
- The content for that page is “enhanced” (styled)
- A transition (slide/pop/etc) from the existing page to the new page occurs
This is a average page transition benchmark:
Page load and processing:
3ms
Page enhance:
45ms
Transition:
604ms
Total time:
670ms *These values are in milliseconds.
So as you can see a transition event is eating almost 90% of execution time.
Data/Parameters manipulation between page transitions
It is possible to send a parameter/s from one page to another during page transition. It can be done in few ways.
Reference:
http://stackoverflow.com/a/13932240/1848600
Solution 1:
You can pass values with changePage:
$.mobile.changePage('page2.html', { dataUrl : "page2.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : true, changeHash : true });
And read them like this:
$(document).on('pagebeforeshow', "#index", function (event, data) {
var parameters = $(this).data("url").split("?")[1];;
parameter = parameters.replace("parameter=","");
alert(parameter);
});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="widdiv=device-widdiv, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>
</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
<script src="http://www.dragan-gaic.info/js/jquery-1.8.2.min.js">
</script>
<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
<script>
$(document).on('pagebeforeshow', "#index",function () {
$(document).on('click', "#changePage",function () {
$.mobile.changePage('second.html', { dataUrl : "second.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : false, changeHash : true });
});
});
$(document).on('pagebeforeshow', "#second",function () {
var parameters = $(this).data("url").split("?")[1];;
parameter = parameters.replace("parameter=","");
alert(parameter);
});
</script>
</head>
<body>
<!-- Home -->
<div data-role="page" id="index">
<div data-role="header">
<h3>
First Page
</h3>
</div>
<div data-role="content">
<a data-role="button" id="changePage">Test</a>
</div> <!--content-->
</div><!--page-->
</body>
</html>
second.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="widdiv=device-widdiv, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>
</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
<script src="http://www.dragan-gaic.info/js/jquery-1.8.2.min.js">
</script>
<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
</head>
<body>
<!-- Home -->
<div data-role="page" id="second">
<div data-role="header">
<h3>
Second Page
</h3>
</div>
<div data-role="content">
</div> <!--content-->
</div><!--page-->
</body>
</html>
Solution 2:
Or you can create a persistent javascript object for a storage
purpose. As long ajax is used for page loading (and page is not
reloaded in any way) that object will stay active.
var storeObject = {
firstname : '',
lastname : ''
}
Example:
http://jsfiddle.net/Gajotres/9KKbx/
Solution 3:
You can also access data from the previous page like this:
$('#index').live('pagebeforeshow', function (e, data) {
alert(data.prevPage.attr('id'));
});
prevPage object holds a complete previous page.
Solution 4:
As a last solution we have a nifty HTML implementation of
localStorage. It only works with HTML5 browsers (including Android and
iOS browsers) but all stored data is persistent through page refresh.
if(typeof(Storage)!=="undefined") {
localStorage.firstname="Dragan";
localStorage.lastname="Gaic";
}
Example:
http://jsfiddle.net/Gajotres/J9NTr/
Probably best solution but it will fail in some versions of iOS 5.X. It is a well know error.
Don’t Use .live() / .bind() /.delegate()
I forgot to mention (and tnx
andleer for reminding me) use on/off for event binding/unbinding, live/die and bind/unbind are deprecated.
The .live() method of jQuery was seen as a godsend when it was
introduced to the API in version 1.3. In a typical jQuery app there can
be a lot of DOM manipulation and it can become very tedious to hook and
unhook as elements come and go. The .live() method made it possible to
hook an event for the life of the app based on its selector. Great
right? Wrong, the .live() method is extremely slow. The .live() method
actually hooks its events to the document object, which means that the
event must bubble up from the element that generated the event until it
reaches the document. This can be amazingly time consuming.
It is now deprecated. The folks on the jQuery team no longer
recommend its use and neither do I. Even though it can be tedious to
hook and unhook events, your code will be much faster without the
.live() method than with it.
Instead of
.live() you should use
.on().
.on() is about 2-3x faster then
.live(). Take a look at this event binding benchmark:
http://jsperf.com/jquery-live-vs-delegate-vs-on/34, everything will be clear from there.
Benchmarking:
There's an excellent script made for
jQuery Mobile page events benchmarking. It can be found here:
https://github.com/jquery/jquery-mobile/blob/master/tools/page-change-time.js. But before you do anything with it I advise you to remove its
alert notification system (each “change page” is going to show you this data by halting the app) and change it to
console.log function.
Basically this script will log all your page events and if you read
this article carefully (page events descriptions) you will know how
much time jQm spent of page enhancements, page transitions ....
Final notes
Always, and I mean always read official
jQuery Mobile
documentation. It will usually provide you with needed information, and
unlike some other documentation this one is rather good, with enough
explanations and code examples.
Changes:
- 30.01.2013 - Added a new method of multiple event triggering prevention
- 31.01.2013 - Added a better clarification for chapter Data/Parameters manipulation between page transitions
- 03.02.2013 - Added new content/examples to the chapter Data/Parameters manipulation between page transitions
- 22.05.2013 - Added a solution for page transition/change prevention
and added links to the official page events API documentation
- 18.05.2013 - Added another solution against multiple event binding