PhoneGap + jQuery Mobile for Windows Phone 8 Hybrid Applications

[Infragistics] Mihail Mateev / Wednesday, February 6, 2013

Many WEB developers use jQuery and jQuery Mobile in their applications. Probably most of these developers would like to carry these applications on any popular mobile platforms such as Windows Phone, Android, iOS. In fact it is very easy and quick if you considerate some rules.

In previous blogs “Using PhoneGap in Windows Store Applications” and “Using PhoneGap in Windows Phone 8 Applications” we discussed how to start with PhoneGap and Windows Store / Windows Phone 8 applications.

In this blog we will continue the topic of PhoneGap / Apache Cordova considering real life examples and in particular how to convert your mobile WEB apps  in hybrid* web applications.

* Hybrid apps: Hybrid apps are just web apps with native wrapper. Hybrid apps run in web view not in native browser. So you can download and install hybrid apps. There are many mobile development frameworks are available using them you can create hybrid apps. PhoneGap: is the most popular open-source mobile development framework for hybrid applications. Using PhoneGap you can create apps in HTML, CSS and JavaScript.  PhoneGap also gives access to the native features from JavaScript.

 

In this article you will learn how to create a hybrid PhoneGap application for Windows Phone 8 based on jQuery Mobile Twitter WEB application. It is a simple WEB application that uses twitter API to search for tweets containing a specific string.

 

Twitter search WEB application

 

 

The source code of the twitter WEB app is available below.

   1: <!DOCTYPE html>
   2:  
   3: <html>
   4:     <head>
   5:         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   6:         <meta name="format-detection" content="telephone=no" />
   7:         <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
   8:         <link rel="stylesheet" type="text/css" href="css/index.css" />
   9:         <link rel="stylesheet"  href="css/themes/default/jquery.mobile-1.2.0.css" />
  10:         <script src="js/jquery.js"></script>
   1:  
   2:         <script src="js/jquery.mobile-1.2.0.js">
   1: </script>  
   2:         <title>Hello World</title>
   3:     </head>
   4:     <body>
   5:  
   6:         <script type="text/javascript" src="cordova-2.3.0.js">
   1: </script>
   2:         <script type="text/javascript" src="js/index.js">
   1: </script>
   2:         <script type="text/javascript">
   3:  
   4:         
</script>
   1:  
   2:         <!---- SEARCH PAGE ----->
   3:     <div data-role="page" id="page1">
   4:         <div data-role="header">
   5:             <h1>Twitter</h1>
   6:         </div><!-- /header -->
   7:  
   8:         <div data-role="content">
   9:             <div data-role="fieldcontain">
  10:                 <input type="search" name="search" id="search" value="" style="-webkit-user-select: text;" />
  11:                 <input type="button" name="searchButt" id="searchButt"  value="Search"/>
  12:             </div>
  13:                 <div data-role="content">    
  14:             <div id="twitList">
  15:                     
  16:             </div>
  17:         </div><!-- /content -->
  18:             <a href="#prefs" data-role="button" data-icon="gear" data-rel="dialog" data-transition="pop">Preferences</a> 
  19:         </div><!-- /content -->
  20:  
  21:         
  22:     </div><!-- /page -->
  23:  
  24:  
  25:         <!---- PREFERENCES ----->
  26:         <div data-role="page" id="prefs">
  27:             <div data-role="header">
  28:                 <h1>Preferences</h1>
  29:             </div><!-- /header -->
  30:  
  31:             <div data-role="content">
  32:                 <div data-role="fieldcontain">
  33:             
  34:                     <label for="slider">Number of results:</label>
  35:             
  36:                     <input type="range" name="slider" id="slider" value="15" min="0" max="100"  />
  37:                     <fieldset data-role="controlgroup">
  38:                     <legend>Result Type:</legend>
  39:                         <input type="radio" name="radio-choice-1" id="radio-choice-1" value="mixed" checked="checked" />
  40:                         <label for="radio-choice-1">Mixed</label>
  41:  
  42:                         <input type="radio" name="radio-choice-1" id="radio-choice-2" value="recent"  />
  43:                         <label for="radio-choice-2">Recent</label>
  44:  
  45:                         <input type="radio" name="radio-choice-1" id="radio-choice-3" value="popular"  />
  46:                         <label for="radio-choice-3">Popular</label>
  47:                     </fieldset>
  48:  
  49:                 </div>
  50:             </div><!-- /content -->
  51:  
  52:         </div><!-- /page -->
  53:  
  54:     </body>
  55: </html>
  56:  
  57: <script type="text/javascript">
  58:             $(function () {
  59:                 $('#searchButt').click(function () {
  60:                     doSearch();
  61:                 });
  62:             });
  63:  
  64:             function doSearch() {
  65:                 $.ajax({
  66:  
  67:  
  68:                     url: "http://search.twitter.com/search.json?q=" + $('#search').val() + "&result_type=" + $('input:radio[name=radio-choice-1]:checked').val() + "&rpp=" + $('#slider').val(),
  69:                     dataType: 'jsonp',
  70:                     success: function (json_results) {
  71:                         // Remove any list - so the new one can be added.
  72:                         $('#twitList ul').remove();
  73:                         // Need to add UL on AJAX call or formatting of userlist is not displayed
  74:                         $('#twitList').append('<ul data-role="listview"></ul>');
  75:                         listItems = $('#twitList').find('ul');
  76:                         $.each(json_results.results, function (key) {
  77:                             html = '<img src="' + json_results.results[key].profile_image_url + '"/>';
  78:                             html += '<h3><a href="#">' + json_results.results[key].text + '</a></h3>';
  79:                             html += '<p>From: ' + json_results.results[key].from_user + ' Created: ' + json_results.results[key].created_at + '</p>';
  80:                             listItems.append('<li>' + html + '</li>');
  81:                         });
  82:                         // Need to refresh list after AJAX call
  83:                         $('#twitList ul').listview();
  84:                     }
  85:                 });
  86:             }
</script>

 

Before to start with PhoneGap / Cordova

If you have no experience with PhoneGap in general or/and  with Cordova for Windows Phone 8 you could read the article “Using PhoneGap in Windows Phone 8 Applications”.

 

Some important thing that you need to know

 

  • Your web application is encapsulated in a custom web view (named CordovaView in this case)

This is a custom view that can load a specified html file

   1: <my:CordovaView HorizontalAlignment="Stretch" 
   2:                    Margin="0,0,0,0"  
   3:                    Name="PGView" 
   4:                    VerticalAlignment="Stretch" />

 

  • Where is your html file?

If you are using a standalone Cordova WP8 template you could take a look at the CordovaView source code:

[your WP8 PhoneGap project path]\cordovalib\CordovaView.xaml.cs

There is set a default html file under www folder: www/index.html

   1: protected Uri _startPageUri = null;
   2:  public Uri StartPageUri
   3:  {
   4:      get
   5:      {
   6:          if (_startPageUri == null)
   7:          {
   8:              // default
   9:              return new Uri(AppRoot + "www/index.html", UriKind.Relative);
  10:          }
  11:          else
  12:          {
  13:              return _startPageUri;
  14:          }
  15:      }
  16:      set
  17:      {
  18:          if (!this.IsBrowserInitialized)
  19:          {
  20:              _startPageUri = value;
  21:          }
  22:      }
  23:  }

 

If you have a different default html file: for example my-default.html you need to set it to StartpageUri property.

   1: </Grid.RowDefinitions>
   2: <my:CordovaView HorizontalAlignment="Stretch" 
   3:            Margin="0,0,0,0"  
   4:            Name="PGView" 
   5:            StartPageUri="www/my_default.html"
   6:            VerticalAlignment="Stretch" />

 

You need to add jQuery, jQuery Mobile and Cordova css and scripts. In this example we just will “wrap” our WEB application without usage of specific Cordova API, but often you will need to use it.

   1: <link rel="stylesheet" type="text/css" href="css/index.css" />
   2: <link rel="stylesheet"  href="css/themes/default/jquery.mobile-1.2.0.css" />
   3: <script src="js/jquery.js"></script>
   1:  
   2: <script src="js/jquery.mobile-1.2.0.js">
   1: </script>  
   2: <script type="text/javascript" src="cordova-2.3.0.js">
   1: </script>
   2: <script type="text/javascript" src="js/index.js">
   1: </script>
   2: <script type="text/javascript">
   3: app.initialize();
</script>

 

PhoneGap WP8 template includes also “index.js” file where you could attach your code to “deviceready” event. This event is fired when the whole Cordova script is loaded.

The index.js file source

   1: var app = {
   2:     // Application Constructor
   3:     initialize: function() {
   4:         this.bindEvents();
   5:     },
   6:     // Bind Event Listeners
   7:     //
   8:     // Bind any events that are required on startup. Common events are:
   9:     // `load`, `deviceready`, `offline`, and `online`.
  10:     bindEvents: function() {
  11:         document.addEventListener('deviceready', this.onDeviceReady, false);
  12:     },
  13:     // deviceready Event Handler
  14:     //
  15:     // The scope of `this` is the event. In order to call the `receivedEvent`
  16:     // function, we must explicity call `app.receivedEvent(...);`
  17:     onDeviceReady: function() {
  18:         app.receivedEvent('deviceready');
  19:  
  20:     },
  21:     // Update DOM on a Received Event
  22:     receivedEvent: function(id) {
  23:         //add your code here
  24:     }
  25: };

 

  • Using jQuery Mobile and PhoneGap together

What is the correct way  to use jQuery Mobile and PhoneGap together?

Both frameworks need to load before they can be used. There are several approaches to do this.

  • You can use deferred feature of JQuery.
   1: <!DOCTYPE html>
   2: <html>
   3:     <head>
   4:         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   5:         <meta name="format-detection" content="telephone=no" />
   6:         <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
   7:         <link rel="stylesheet" type="text/css" href="css/bootstrap.css" />
   8:         <title>InforMEA</title>
   9:     </head>
  10:     <body>
  11:         <script type="text/javascript" src="js/jquery-1.8.3.js"></script>
   1:  
   2:         <script type="text/javascript">
   3:             var dd = $.Deferred();
   4:             var jqd = $.Deferred();
   5:             $.when(dd, jqd).done(doInit);
   6:  
   7:             $(document).bind('mobileinit', function () {
   8:                 jqd.resolve();
   9:             });
  10:         
</script>
   1:  
   2:         <script type="text/javascript" src="js/jquery.mobile-1.2.0.js">
   1: </script>
   2:         <script type="text/javascript" src="cordova-2.3.0.js">
   1: </script>
   2:         <script type="text/javascript">
   3:             document.addEventListener('deviceready', deviceReady, false);
   4:             function deviceReady() {
   5:                 dd.resolve();
   6:             }
   7:  
   8:             function doInit() {
   9:                 alert('Ready');
  10:             }
  11:         
</script>
  12:     </body>
  13: </html>

 

  • Another approach is to add your code that handle both Cordova and jQuery Mobile after your html as in our sample application

 

Sample Application

Demo application contains only two pages – “search page” and “preferences page”. In the same file is included a jQuery function used to search for tweets via the twitter API.

Search page

   1: <!---- SEARCH PAGE ----->
   2: <div data-role="page" id="page1">
   3:     <div data-role="header">
   4:         <h1>Twitter</h1>
   5:     </div><!-- /header -->
   6:  
   7:     <div data-role="content">
   8:         <div data-role="fieldcontain">
   9:             <input type="search" name="search" id="search" value="" />
  10:             <input type="button" name="searchButt" id="searchButt"  value="Search"/>
  11:         </div>
  12:             <div data-role="content">    
  13:         <div id="twitList">
  14:         
  15:         </div>
  16:     </div><!-- /content -->
  17:         <a href="#prefs" data-role="button" data-icon="gear" data-rel="dialog" data-transition="pop">Preferences</a> 
  18:     </div><!-- /content -->
  19:  
  20:     
  21: </div><!-- /page -->

 

Preferences page

   1: <div data-role="page" id="prefs">
   2:     <div data-role="header">
   3:         <h1>Preferences</h1>
   4:     </div><!-- /header -->
   5:  
   6:     <div data-role="content">
   7:         <div data-role="fieldcontain">
   8:     
   9:             <label for="slider">Number of results:</label>
  10:     
  11:             <input type="range" name="slider" id="slider" value="15" min="0" max="100"  />
  12:             <fieldset data-role="controlgroup">
  13:             <legend>Result Type:</legend>
  14:                 <input type="radio" name="radio-choice-1" id="radio-choice-1" value="mixed" checked="checked" />
  15:                 <label for="radio-choice-1">Mixed</label>
  16:  
  17:                 <input type="radio" name="radio-choice-1" id="radio-choice-2" value="recent"  />
  18:                 <label for="radio-choice-2">Recent</label>
  19:  
  20:                 <input type="radio" name="radio-choice-1" id="radio-choice-3" value="popular"  />
  21:                 <label for="radio-choice-3">Popular</label>
  22:             </fieldset>
  23:  
  24:         </div>
  25:     </div><!-- /content -->
  26:  
  27: </div><!-- /page -->

 

Twitter search function:

   1: <script type="text/javascript">
   1:  
   2:             $(function () {
   3:                 $('#searchButt').click(function () {
   4:                     doSearch();
   5:                 });
   6:             });
   7:  
   8:             function doSearch() {
   9:                 $.ajax({
  10:  
  11:  
  12:                     url: "http://search.twitter.com/search.json?q=" + $('#search').val() + "&result_type=" + $('input:radio[name=radio-choice-1]:checked').val() + "&rpp=" + $('#slider').val(),
  13:                     dataType: 'jsonp',
  14:                     success: function (json_results) {
  15:                         // Remove any list - so the new one can be added.
  16:                         $('#twitList ul').remove();
  17:                         // Need to add UL on AJAX call or formatting of userlist is not displayed
  18:                         $('#twitList').append('<ul data-role="listview"></ul>');
  19:                         listItems = $('#twitList').find('ul');
  20:                         $.each(json_results.results, function (key) {
  21:                             html = '<img src="' + json_results.results[key].profile_image_url + '"/>';
  22:                             html += '<h3><a href="#">' + json_results.results[key].text + '</a></h3>';
  23:                             html += '<p>From: ' + json_results.results[key].from_user + ' Created: ' + json_results.results[key].created_at + '</p>';
  24:                             listItems.append('<li>' + html + '</li>');
  25:                         });
  26:                         // Need to refresh list after AJAX call
  27:                         $('#twitList ul').listview();
  28:                     }
  29:                 });
  30:             }
  31:  
</script>

 

The whole index.html page

   1: <!DOCTYPE html>
   2: <html>
   3:     <head>
   4:         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   5:         <meta name="format-detection" content="telephone=no" />
   6:         <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
   7:         <link rel="stylesheet" type="text/css" href="css/index.css" />
   8:         <link rel="stylesheet"  href="css/themes/default/jquery.mobile-1.2.0.css" />
   9:         <script src="http://debug.phonegap.com/target/target-script-min.js#devil-mike"></script>
   1:  
   2:         <script src="js/jquery.js">
   1: </script>
   2:         <script src="js/jquery.mobile-1.2.0.js">
   1: </script>  
   2:         <title>Hello World</title>
   3:     </head>
   4:     <body>
   5:         <script type="text/javascript" src="cordova-2.3.0.js">
   1: </script>
   2:         <script type="text/javascript" src="js/index.js">
   1: </script>
   2:         <script type="text/javascript">
   3:             app.initialize();
   4:         
</script>
   1:  
   2:         <script type="text/javascript">
   3:  
   4:         
</script>
   1:  
   2:         <!---- SEARCH PAGE ----->
   3:         <div data-role="page" id="page1">
   4:             <div data-role="header">
   5:                 <h1>Twitter</h1>
   6:             </div><!-- /header -->
   7:  
   8:             <div data-role="content">
   9:                 <div data-role="fieldcontain">
  10:                     <input type="search" name="search" id="search" value="" />
  11:                     <input type="button" name="searchButt" id="searchButt"  value="Search"/>
  12:                 </div>
  13:                     <div data-role="content">    
  14:                 <div id="twitList">
  15:                 
  16:                 </div>
  17:             </div><!-- /content -->
  18:                 <a href="#prefs" data-role="button" data-icon="gear" data-rel="dialog" data-transition="pop">Preferences</a> 
  19:             </div><!-- /content -->
  20:  
  21:     
  22:         </div><!-- /page -->
  23:  
  24:  
  25:         <!---- PREFERENCES ----->
  26:         <div data-role="page" id="prefs">
  27:             <div data-role="header">
  28:                 <h1>Preferences</h1>
  29:             </div><!-- /header -->
  30:  
  31:             <div data-role="content">
  32:                 <div data-role="fieldcontain">
  33:             
  34:                     <label for="slider">Number of results:</label>
  35:             
  36:                     <input type="range" name="slider" id="slider" value="15" min="0" max="100"  />
  37:                     <fieldset data-role="controlgroup">
  38:                     <legend>Result Type:</legend>
  39:                         <input type="radio" name="radio-choice-1" id="radio-choice-1" value="mixed" checked="checked" />
  40:                         <label for="radio-choice-1">Mixed</label>
  41:  
  42:                         <input type="radio" name="radio-choice-1" id="radio-choice-2" value="recent"  />
  43:                         <label for="radio-choice-2">Recent</label>
  44:  
  45:                         <input type="radio" name="radio-choice-1" id="radio-choice-3" value="popular"  />
  46:                         <label for="radio-choice-3">Popular</label>
  47:                     </fieldset>
  48:  
  49:                 </div>
  50:             </div><!-- /content -->
  51:  
  52:         </div><!-- /page -->
  53:  
  54:     </body>
  55: </html>
  56:  
  57: <script type="text/javascript">
  58:             $(function () {
  59:                 $('#searchButt').click(function () {
  60:                     doSearch();
  61:                 });
  62:             });
  63:  
  64:             function doSearch() {
  65:                 $.ajax({
  66:  
  67:  
  68:                     url: "http://search.twitter.com/search.json?q=" + $('#search').val() + "&result_type=" + $('input:radio[name=radio-choice-1]:checked').val() + "&rpp=" + $('#slider').val(),
  69:                     dataType: 'jsonp',
  70:                     success: function (json_results) {
  71:                         // Remove any list - so the new one can be added.
  72:                         $('#twitList ul').remove();
  73:                         // Need to add UL on AJAX call or formatting of userlist is not displayed
  74:                         $('#twitList').append('<ul data-role="listview"></ul>');
  75:                         listItems = $('#twitList').find('ul');
  76:                         $.each(json_results.results, function (key) {
  77:                             html = '<img src="' + json_results.results[key].profile_image_url + '"/>';
  78:                             html += '<h3><a href="#">' + json_results.results[key].text + '</a></h3>';
  79:                             html += '<p>From: ' + json_results.results[key].from_user + ' Created: ' + json_results.results[key].created_at + '</p>';
  80:                             listItems.append('<li>' + html + '</li>');
  81:                         });
  82:                         // Need to refresh list after AJAX call
  83:                         $('#twitList ul').listview();
  84:                     }
  85:                 });
  86:             }
  87:  
</script>

 

Build and deploy your Windows Phone 8 application. It looks like a native WP app (if it is styled), but it is just a wrapped via Cordova mobile WEB application.

 

The app behavior also looks  like a native app behavior.

 

 

You can use PhoneGap to package this application for different platforms.
This version of the application doesn’t use any of the PhoneGap native APIs (GPS, camera, accelerometer, notification, etc).

Demo source is available here.

You can find version of the same application built using plain jQuery & jQuery Mobile here.

Expect next blogs where you will learn more about Windows Store PhoneGap applications and Cordova extensions for Windows Phone.

 

Follow news from Infragistics for more information about new Infragistics events.

As always, you can follow us on Twitter @mihailmateev and @Infragistics and stay in touch on Facebook, Google+andLinkedIn!