PhoneGap and Jquery Mobile – A Truly Winning Combination

P

Any regular readers of jQuery tutorial may have noticed that I finally got around to updating and adding a new book under “My Books” on the right-hand side of the screen.  Yes, I not-so recently published my third book (and second with O’Reilly) earlier this year.

The book is called 20 Recipes for Programming PhoneGap – Cross-Platform Mobile Development for Android and iPhone.  Throughout this book, I get you up and running quickly using the PhoneGap API along with the Jquery Mobile API to take care of the pesky mobile design issues. On occasion you may get errors like How to check for undefined in JavaScript so hopefully this will help.

Much like I did when I released my MVC 3.NET book, I provided a free chapter from the book for my readers.  Well here is an excellent recipe sample from my PhoneGap book.  The goal of this recipe is to provide a standard approach to automatically fire (or trigger) a JavaScript function when a PhoneGap page has loaded – either on initial launch or after the user has navigated within the application.

Executing a Callback Function Once the Device Is Ready

Problem

After a page has loaded, you will want to execute some JavaScript code immediately without invoking it manually each time in your JavaScript code.

Solution

Because HTML doesn’t allow for a lot of dynamic features, a lot of code needs to be duplicated. To minimize the page load times, you should load the minimal amount of content each time while reusing as much code as you can. The common.js JavaScript file must be kept lightweight, and additional JavaScript files should be created for other new functionality. However, there currently is no process to allow for additional function calls once the application has determined that the device is ready.

You can update the common.js JavaScript file with an automatic callback function that will be executed once the common code to detect the device type, network connection, etc., has finished working. This will enable you to use the same process in many future recipes.

Discussion

Because the application is using jQuery mobile, you must reorganize some of the existing code in order to improve code loading. When you navigate between pages using jQuery mobile, it performs the request via AJAX and strips all of the content from the HTML file (unless it is within a div tag that contains a data-role of type page). This Executing a Callback Function Once the Device Is Ready means that the previous window.onload event will no longer trigger. Instead, a new event that is provided by the jQuery mobile library will be used.

The first thing to note is that the index.html page from “Creating a Persistent Navigation System” on page 5 requires some reformatting as follows:

[code]
<!DOCTYPE HTML>
<html>
<head>
<title>PhoneGap</title>
<link rel=”stylesheet” href=”css/jquery.mobile-1.0rc1.min.css” />
<script type=”text/javascript” charset=”utf-8″ src=”scripts/phonegap-1.0.0.js”></script>
</head>

<body>
<div data-role=”page” id=”index-page”>
<h1>Hello World!</h1>
<div data-role=”footer” data-position=”fixed”>
<div data-role=”navbar”>
<ul>
<li><a href=”index.html”>Home</a></li>
<li><a href=”about.html”>About</a></li>
</ul>
</div>
</div>
</div>

<script type=”text/javascript” charset=”utf-8″ src=”scripts/jquery-1.6.4.min.js”></script>
<script type=”text/javascript” charset=”utf-8″ src=”scripts/jquery.mobile-1.0rc1.min.js”></script>
<script type=”text/javascript” charset=”utf-8″ src=”scripts/common.js”></script>
</body>
</html>
[/code]

Several things have changed in this code. Most of the JavaScript files have been moved out of the head tag. I’ve left the main PhoneGap file there to ensure that it loads completely before the page does, because anything placed inside the head tag must fully finish loading before you continue. Next, a new div tag was added with a data-role of type page. Finally, the previous JavaScript files have been moved to the bottom and the order has been altered. These have also been placed outside of the page div tag, because they do not need to be loaded again if the user navigates back to the index page. The order of the files was altered because, in the next example, the common.js file will be updated to use elements of jQuery and the mobile library, which must load first.

Within the next example is an updated common.js file. It contains all of the code from the first several recipes that performs the following operations: device ready, device detection, and network detection, as well as the new callback feature. The key objective of this expanded code is to allow you to run custom code tied to the name of a particular page, when that page loads.

[code]
// Global variable that will tell us whether PhoneGap is ready
var isPhoneGapReady = false;
// Default all phone types to false
var isAndroid = false;
var isBlackberry = false;
var isIphone = false;
var isWindows = false;

// Store the device’s uuid
var deviceUUID;

// Store the current network status
var isConnected = false;
var isHighSpeed;
var internetInterval;

var currentUrl;

function init(url) {
if (typeof url != ‘string’) {
currentUrl = location.href;
} else {
currentUrl = url;
}

if (isPhoneGapReady) {
onDeviceReady();
} else {

// Add an event listener for deviceready
document.addEventListener(“deviceready”,onDeviceReady, false);
}
}

function onDeviceReady() {
// set to true
isPhoneGapReady = true;
deviceUUID = device.uuid;

// detect the device’s platform
deviceDetection();

// detect for network access
networkDetection();

// execute any events at start up
executeEvents();

// execute a callback function
executeCallback();
}

function executeEvents() {
if (isPhoneGapReady) {
// attach events for online and offline detection
document.addEventListener(“online”, onOnline, false);
document.addEventListener(“offline”, onOffline, false);

// set a timer to check the network status
internetInterval = window.setInterval(function() {
if (navigator.network.connection.type != Connection.NONE) {
onOnline();
} else {
onOffline();
}
}, 5000);
}
}

function executeCallback() {
if (isPhoneGapReady) {
// get the name of the current html page
var pages = currentUrl.split(“/”);
var currentPage = pages[pages.length – 1].slice(0, pages[pages.length – 1].indexOf(“.html”));

// capitalize the first letter and execute the function
currentPage = currentPage.charAt(0).toUpperCase() + currentPage.slice(1);

if (typeof window[‘on’ + currentPage + ‘Load’] == ‘function’) {
window[‘on’ + currentPage + ‘Load’]();
}
}
}

function deviceDetection() {
if (isPhoneGapReady) {
switch (device.platform) {
case “Android”:
isAndroid = true;
break;
case “Blackberry”:
isBlackberry = true;
break;
case “iPhone”:
isIphone = true;
break;
case “WinCE”:
isWindows = true;
break;
}
}
}

function networkDetection() {
if (isPhoneGapReady) {
// as long as the connection type is not none,
// the device should have Internet access

if (navigator.network.connection.type != Connection.NONE) {
isConnected = true;
}

// determine if this connection is high speed or not
switch (navigator.network.connection.type) {
case Connection.UNKNOWN:
case Connection.CELL_2G:
isHighSpeed = false;
break;
default:
isHighSpeed = true;
break;
}
}
}

function onOnline() {
isConnected = true;
}

function onOffline() {
isConnected = false;
}

// This gets called by jQuery mobile when the page has loaded
$(document).bind(“pageload”, function(event, data) {
init(data.url);
});

// Set an onload handler to call the init function
window.onload = init;
[/code]

There is quite a bit happening in the preceding code. I will start at the bottom with the two events that call the init function. The window.onload code remains as-is and will be called when the application first loads. By binding the pageload event to the document, I ensure that each time a user clicks a new link, this event will fire when that page has finished loading. It is also passing the current URL to the updated init function.

This will be used for implementing the callback function. The init function has been updated to accept this new url parameter. However, since this parameter is not passed in by the window.onload event, the code checks to see whether it is a string. When a string is not detected (i.e., on first load), the location.href is used and stored in the currentUrl global variable. In case you want to understand why that is used, check out difference between location.href and location.replace for more information. Then, if the variable isPhoneGapReady is already set and true, there is no need to add the listener and wait, so it just calls the onDeviceReady function.

The onDeviceReady function has been slightly reorganized and some of the previous work has been moved into new functions for later expansion, including the newly added executeCallback function.

The executeCallback function takes the currentUrl variable and splits it into parts to be able to retrieve just the filename, e.g., the index. This name is then used to check whether there is a function called onIndexLoad. If this function exists, it is executed.

When you add future pages, you can also add new functions that will be executed automatically once the page loads. These will perform any additional processing required by that page. For instance, if you add an onAboutLoad function, the app will execute it when about.html has finished loading.

See Also

jQuery Mobile Events

 

I hope you enjoyed this sample chapter from my latest book.  If you would like to read more, use the link on the right to purchase it.  It’s available either as a hard-copy or soft-copy.

Other useful articles

About the author

  • J.D. Schuitemaker

    This is something I was looking for. Great example. However, I never see an alert when I add this function to common.js:

    function onIndexLoad() {
    navigator.notification.alert(‘index.html’);
    }

    Also an alert at the first line of function executeCallBack is never shown (which means that executeCallBack is not fired).

    Looking forward to your comments.

    • endyourif

      Is this on the first page load or the second page load that it is not working? I’d also move up the callback to the “init” function to see if even the first function is getting executed.

      • J.D. Schuitemaker

        I have to investigate some more, but it turns out that in init() you check for isPhoneGapReady (in my case false) which causes this line to be executed:

        document.addEventListener(“deviceready”, vonDeviceReady, false);

        And ‘vonDeviceReady’ does not exist (typo I think, the leading ‘v’ had to be removed.

        Then in the first page (index) my function onIndexLoad() is executed without the need of your suggestion to move executeCallBack.

        The second page (categories.html) seems not to work, onCategoriesLoad is not executed.

        Even onDeviceReady() is never called on the second page.

        • endyourif

          thanks for pointing out the typo, i’ve updated the code above accordingly.

          which versions of phonegap, jquery, and jquery mobile are you using? newer versions have been released since my book has been published and i’m wondering if syntax has changed slightly.

          • J.D. Schuitemaker

            I am using:
            – Phonegap (Cordova) 2.0
            – jquery-1.7.2.min.js
            – jquery.mobile-1.1.0.min.js

            If you are experiencing the same problem, I will consider to not check if isPhoneGapReady is true when the current url is not equal to index.html. Is that a good idea?

          • Guest

            Until you come up with a better solution (or I run into troubles with my work around) I have changed this line in init():
            if (isPhoneGapReady)
            to:
            if (isPhoneGapReady || (currentUrl.toLowerCase().indexOf(‘/index.html’) == -1))

            This seems to work for me…

By Jamie

My Books