Home Blog jQuery document ready vs jQuery Mobile page events

jQuery document ready vs jQuery Mobile page events

 
17 12471
The first thing you learn in  jQuery  is to call code inside the  $(document).ready()  so everything inside can execute as soon as DOM is loaded. However, in  jQuery Mobile , Ajax is used to load the content of each page into the DOM as you navigate.
 
Because of this  $(document).ready()  will/may trigger before your first page is loaded and every code intended for a page manipulation will executed only after a page refresh, which brakes the purpose of using an ajax for page handling. This can be a very subtle problem. On some systems it may appear that it works fine, but on others it may cause erratic, difficult to repeat weirdness to occur.
 
This is a classic jQuery syntax:
 
$(document).ready(function() {

});
 
To solve this problem (and trust me this is a problem) jQuery Mobile developers have created something called page events. In a nutshell, page events are loading states triggered during the particular point of page execution. One of those page events is a  pageinit  event and it can be used 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">
        <h4>
            First Page
        </h4>
        <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 would use this syntax:
 
$(document).on('pageinit', '#index' ,function(){
    // Code goes here
});
 
 Pageinit  event will be trigger only when page is about to be 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  or  pageshow  event.
 
Here’s a working example : http://jsfiddle.net/Gajotres/Q3Usv/ to demonstrate this case.
 
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 gather all of your custom javascript page handling into a single separate js file. This will not make your code any better but you will have much better code overview, especially while creating a jQuery Mobile application.
 
While not directly related to this topic it is good to understand how jQuery Mobile handles its javascript. When multiple HTML template us used only first HTML page will be fully loaded into the DOM, every other page will be loaded only partially, basically only DIV containing page content will be loaded and everything else, including the HEAD will be discarded. This is the main reason why it is good to gather all of your js into a one single javascript file loaded into the first HTML file.
 
Of course there are some other solutions to this problem and they can be found in my other article.
 
There’s also one special jQuery Mobile event called  mobileinit . When jQuery Mobile initializes, it requires  mobileinit  event triggered just after jQuery is initialized. It is used if some default settings needs to be overriden. One of a good examples of its usage is turning off classic ajax page loading, or changing default ajax loader behavior.
 
$(document).on("mobileinit", function(){
  //apply overrides here
});
 
This is when  mobileinit  event must be initialized:
 
<script src="jquery.js"></script>
<script>
$(document).on("mobileinit", function(){
  //apply overrides here
});
</script>
<script src="jquery-mobile.js"></script>
 

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:
 

 
For better page events understanding read this:
 
 pagebeforeload ,  pageload  and  pageloadfailed  are fired when an external page is loaded with function called  $.mobile.loadPage   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 used for page initialization, and they will trigger ONLY once, unless page is refreshed. First two are excellent for dynamic content generation because jQuery Mobile will style page content only after  pagecreate  is executed.  pageremove  can be fired and then handled when a page is removed from the DOM
 
Page loading jsFiddle example: http://jsfiddle.net/Gajotres/QGnft/
 
It is good to know that if AJAX is not enabled, some events will not fire.
 

Prevent page transition


 
When talking about page events it is good to know how someone can prevent page transition, for every reason it may be:
 
$(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.
 

Prevent multiple event binding/triggering


 
Because of interesting jQM loading architecture, multiple event triggering is a constant problem. 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 . There are few ways to prevent this problem:
 

Solution 1:

 
Remove event before you bind it:
 
$('#index').live('pagebeforeshow',function(e,data){    
    $('#test-button').die().live('click', function(e) {
        alert('Button click');
    });    
});
 
Working jsFiddle example: http://jsfiddle.net/Gajotres/K8YmG/
 
In case you have different events bound to an object:
 
$('#index').live('pagebeforeshow',function(e,data){    
    $('#test-button').die('click').live('click', function(e) {
        alert('Button click');
    });    
});
 

Solution 2:

 
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 3:

 
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&amp;name=Sat"><h4>Sat</h4></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);
});
 
Example can be found here.
 
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">
            <h4>
                First Page
            </h4>
        </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>  
    <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">
            <h4>
                First Page
            </h4>
        </div>
        <div data-role="content">
          <a data-role="button" id="changePage">Test</a>
        </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()


 
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 jQuery Mobile spent of page enhancements, page transitions ….
 

Reading Material


 
A lot of books are written on jQuery Mobile topic, unfortunately only few are worth mentioning. A In any point I will not recommend books I don’t own.
 
jQuery Mobile Web Development Essentials, Second Edition Yet another book from the Packt Publishing, second installment of this series. Originally I have bought first installment of this book which was outdated so you should also be careful. This one is currently 2 months old (26.11.2013) so it should satisfy you in coming months. Second edition is excellent all around book, covering everything from API to working examples. It is also an excellent book if you want to learn everything that needs to be know about jQuery Mobile API (Chapter 10), something event official product web page is lacking.
 

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.
 
 

17 COMMENTS

  1. You’ve really made my day! I’m working with jQuery Mobile as one possible framework in a designer tool for SAP. That means that i don’t work with jQuery Mobile every day, just now and then. It has cost me days to figure out when to use which event and why. Your post clears everything up and the examples are really great. Many thanks!

  2. This is an awesome post. It clearly describes how these features of JQuery Mobile interact with the DOM and gives practical advice on how to use them. This is not that clear from the JQuery Mobile documentation, this post should be linked to in that verbatim. Thanks.

  3. In the section “page events transition order”, event pagebeforeshow of page B occurs twice! Something wrong?

  4. Hey man, just stumbled on this article via Stack Overflow. Thank you so much, I was having crazy scripting problems in the first app I’m writing and this totally solved it – good work!

  5. I have 3 pages with the same structure and two footers that go to id=1 and id=2 for each page. How would this code:

    $(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);
    });

    work for 3 pages? (Unfortunately I don’t know Javascript, least not yet.)
    Thanks so much for any help you can give me!!!!!
    Janell

  6. Congratulations for this amazing post, with information I couldn’t find anywhere else. Very clear, thank you !

  7. Fantastic stuff!
    Especially the explanations of page events and related issues.
    I’ve been beating my head against the wall trying to figure out why forms keep executing, and alert boxes are stuck in loops!
    LOL
    VERY helpful.
    THANKS!

  8. Thanks a lot for all your explanations… very clear to understand…. more than JqueryMobile official web site

    I wondered why my javascript where not ran into my Head section and it is clear now

    Thanks

    David

Leave a Reply


nine − two =