Jquery Mobile: Document Ready Vs. Page Events

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:

  1. page B - event pagebeforecreate

  2. page B - event pagecreate

  3. page B - event pageinit

  4. page A - event pagebeforehide

  5. page A - event pageremove

  6. page A - event pagehide

  7. page B - event pagebeforeshow

  8. 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 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 :

  1. 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 use pagebeforeshow (because if you use pageshow that content will just show up out of nowhere and it can confuse the user). Dont use pagebeforecreate 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.


  1. 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.


  1. 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



Leave a reply



Submit