jQuery Mobile: document ready vs. page events
jQuery Mobile 1.4 Update:
My original article was intended for old way of page handling, basically everything before jQuery Mobile 1.4. Old way of handling is now deprecated and it will stay active until (including) jQuery Mobile 1.5, so you can still use everything mentioned below, at least until next year and jQuery Mobile 1.6.
Old events, including pageinit don't exist any more, they are replaced with pagecontainer widget. Pageinit is erased completely and you can use pagecreate instead, that event stayed the same and its not going to be changed.
If you are interested in new way of page event handling take a look here, in any other case feel free to continue with this article. You should read this answer even if you are using jQuery Mobile 1.4 +, it goes beyond page events so you will probably find a lot of useful information.
Older content:
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. Let's 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 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 JavaScript 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 A - event pageremove
page A - event pagehide
page B - event pagebeforeshow
page B - event pageshow
For better page events understanding read this:
pagebeforeload
,pageload
andpageloadfailed
are fired when an external page is loadedpagebeforechange
,pagechange
andpagechangefailed
are page change events. These events are fired when a user is navigating between pages in the applications.pagebeforeshow
,pagebeforehide
,pageshow
andpagehide
are page transition events. These events are fired before, during and after a transition and are named.pagebeforecreate
,pagecreate
andpageinit
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 than 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 snippet:
$(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 than 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 than 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: 3 ms
Page enhance: 45 ms
Transition: 604 ms
Total time: 670 ms
*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: https://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);
});
Example:
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>
jQuery mobile $(document).ready equivalent
I spent some time on researching the same since JQM docs are not very detailed at this point. Solution below works fine for me:
<script type="text/javascript">
$('div:jqmData(role="page")').live('pagebeforeshow',function(){
// code to execute on each page change
});
</script>
You have to implement your own checking flow in order to prevent multiple initialization since the code above will run on every page change
What's the right way to do $(document).ready in jQuery Mobile?
Most likely the reason you read that $(document).ready won't work with jQuery Mobile is that it does not fire each time you view a pseudo-page. That said, it still triggers as it should when the html document is loaded.
If you want to run code that triggers each time you view a pseudo-page you can use this code:
$('[data-role="page"]').live('pageshow', function () {
//run your code here
});
NOTE: there are other hooks that you can bind to as well (pageshow, pagehide, pagebefoershow, pagebeforehide), documentation can be found here: http://jquerymobile.com/demos/1.0b1/docs/api/events.html
---------- EDIT ----------
I was thinking about this and the best analog to $(document).ready() is not binding to the "pageshow" event, it would be binding to the "pagecreate" event. $(document).ready() fires once per page load, and "pagecreate" does the same for pseudo-pages whereas "pageshow" fires each time a page is displayed.
So if a user clicked away from the home-screen and then clicked a back button to return to the home-screen, "pageshow" would fire on this second (and subsequent) "showing" of the home-screen.
Also, "pageshow" requires the user to navigate to the page to which it is bound.
Jquery Mobile $(document).ready();
I have had the same issue with JQM+bxSlider, and after investigating and testing a lot I found the solution for JQM.
You have to use $document.ready and $(document).on('pageshow'
take a look to this example. In this example you have a slider called bxslider in a page with ID "Quiz":
<script>
$(document).ready(function() {
myCarousel=$('.bxslider').bxSlider({
captions: false,
infiniteLoop: false,
hideControlOnEnd: true,
slideMargin: 10,
adaptiveHeight : true
});
});
$(document).on('pageshow', '#Quiz', function(){
myCarousel.reloadSlider();
});
</script>
Hope this helps.
jQuery Mobile - pageinit vs pageshow
Intro
All information found here can also be found in my blog ARTICLE, you will also find working examples.
Difference between pageinit and pageshow
Lets start from the beginning. As you already know jQuery Developers have provided us with page events to bridge a gap that document ready cant fulfil. While document ready can tell you that content is ready inside a DOM we need more then that because jQuery Mobile still needs to enhance content markup (enhance content style).
There are several page events and every and each of them has its purpose. Some will trigger before page content can be enhanced (like pagebeforecreate) so that dynamic content can be added. Some will trigger only during page change like pagebeforechange.
But let as get back to your question. Pageinit
is here to be a jQuery Mobile version of document ready
. While you can still use document ready
it is still logical to have same alternative among page events.
As you already told you are using pageinit
for event binding (like click or swipe events) and that is a good solution. Mainly because jQuery Mobile
suffers from a problem called "multiple event binding". If e.g. you have a click event bind to an element, nothing can prevent another click event from been bound to the same element. And that will happen if you use pageshow
event. If you use event binding during the pageshow
event, each time page is visited that same event will be bound over and over again. It can be easily prevented but that same prevention will take additional processor processing power, same power that can used to handle rest of web app.
Here we have another question (one of your questions), what is then purpose of pageshow
? Obvious answer would be to do something with a available and shown page. While correct answer it is not that important. Pageshow
is important because it is ONLY page event where page height can be calculated correctly and it is not 0. Now you can see why your carousel needs to be initialized at that point. Carousels like many other plugins (charts, image galleries) requires correct page height and if you initialize them before pageshow
they will have height 0, so they will exist but you will not be able to see them.
Regarding your last question, caching doesn't play a role if your application is built correctly. For a start you should always use delegated event binding because it will not care if page element exist or not. Basically if you bind your event to some parent element like document it doesn't matter if you page is cached or removed from the DOM. As soon as it is back that same event will work again.
Example:
$(document).on('click', '#some-button',function(){
});
This method will bind a click event to document but that same click event will only work on an element with an id 'some-button'
. It really doesn't matter if that element exist or not because document object will always exist.
This same logic is not that important if you work with normal web pages were page reload / refresh is a common thing. Or even here with jQuery Mobile if ajax is turned off so every page change is basically page refresh / reload.
I hope this answers all of your questions. If you need clarifications ask them in comment section.
EDIT :
- Where should you load you data depends on what do you need to load. If you want to do in only once then use
pageinit
. If you need it each time page is visited then usepagebeforeshow
(because if you usepageshow
that content will just show up out of nowhere and it can confuse the user). Dont usepagebeforecreate
because content will load after the event ends so no point in using it.
If you want to load content in some interval use pageinit
with setinterval function. Don't forget to manually enhance page content styles each time dynamic content is added.
Pageshow
is useful only from plugin initialization the requires page height. Nothing else in particular. From answer 1 you can see it is bad for dynamic content because it will show up from nowhere.
Pageinit
should be used for event binding and dynamic content generation.
- Tomorrow I will update my answer with use cases for every page event. I hope this will be enough for you.
jQuery Mobile : What is the order of page events triggering?
Intro
All information found here can also be found in my blog ARTICLE, you will also find working examples.
- A: Initialization
A1 - Phonegap app/framework initialization with the deviceReady event.
Example:
document.addEventListener("deviceReady", yourCallbackFunction, false);
function deviceReady() {
}
More about pause even can be found here: http://docs.phonegap.com/en/1.0.0/phonegap_events_events.md.html
A2 - jQuery Mobile app/framework initialization with the mobileinit event.
Example:
$(document).on("mobileinit", function () {
});
How to check if both frameworks are successfully loaded: https://stackoverflow.com/a/12821151/1848600
- B: Change page
First all events can be found here: http://jquerymobile.com/test/docs/api/events.html
Lets say we have a page A and a page B, this is a unload/load order:
1. page B - event pagebeforecreate
2. page B - event pagecreate
3. page B - event pageinit
4. page A - event pagebeforehide
5. page B - event pagebeforeshow
6. page A - event pageremove
7. page A - event pagehide
8. page B - event pageshow
- C: Minimize app
Phonegap handles this with a pause event.
Example:
document.addEventListener("pause", yourCallbackFunction, false);
More about pause even can be found here: http://docs.phonegap.com/en/1.0.0/phonegap_events_events.md.html
- D: Restore app
Phonegap handles this with a resume event.
Example:
document.addEventListener("resume", yourCallbackFunction, false);
More about pause even can be found here: http://docs.phonegap.com/en/1.0.0/phonegap_events_events.md.html
- Final words
There are few other phonegap and jQM events and you can find them in links mentioned above.
Something you should not use in jQM app:
$(document).ready(function(){
});
Reason:
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.
Related Topics
Displaying Pdf from Arraybuffer
How to Detect If JavaScript Is Disabled
How to Retrieve an HTML Element'S Actual Width and Height
What Is the Htmlspecialchars Equivalent in JavaScript
What Browsers Support Html5 Websocket API
Electron Require() Is Not Defined
How to Loop Through Selected Elements With Document.Queryselectorall
Removing All Script Tags from HTML With Js Regular Expression
Do You Need Text/JavaScript Specified in Your ≪Script≫ Tags
Retrieving the Text of the Selected ≪Option≫ in ≪Select≫ Element
How to Use a C++ Library from Node.Js
Communication Between Tabs or Windows
Show/Hide 'Div' Using JavaScript
How to Append Text to a '≪Div≫'
Changing CSS Values With JavaScript