Developing Windows Store Applications with PhoneGap and jQuery Mobile

[Infragistics] Mihail Mateev / Sunday, February 10, 2013

WEB developers very often prefer to use jQuery and jQuery Mobile. In previous my blogs “Using PhoneGap in Windows 8 Store Applications” and “PhoneGap + jQuery Mobile for Windows Phone 8 Hybrid Applications” you learned how to start with PhoneGap / Apache  Cordova for Windows Store applications and how to use PhoneGap and jQuery Mobile for Windows Phone 8 development.

If you try to create Cordova Windows Store applications with jQuery and jQuery Mobile it will be not so easy.

 

The very practical Javascript-library jQuery is the favored tool for lots of web developers. Since Windows Store applications support development using the standard webtechniques, jQuery immediately suggests itself. But because Microsoft has integrated a new security model in Windows Store Applications for Windows 8,  jQuery has to be modified slightly in order to be usable for app development.

Microsofts security model comes into play when one of the standard Javascript functions innerHTML, outerHTML or document.write are used. For example:

 

   1: <div id="test"></div>
   2: <script>
   3:     document.querySelector("#myselector").innerHTML = "<a onclick='document.body.style.backgroundColor=\"#F00\"'>Something dangerous</a>";
   4: </script>

 

There will be a JavaScript runtime error that terminates the app, because dynamic content is injected via JavaScript.

 

Microsoft provides the method execUnsafeLocalFunction with which you can explicitly disable the security model and tell Windows “I know what I’m doing”. This should only be done when you control the executed code and are sure that no security problems could potentially happen. You have to wrap the code within the execUnsafeLocalFunction construct and let it execute for you. This code works without problems:

 

   1: <script>
   2: MSApp.execUnsafeLocalFunction(function () {
   3:     document.querySelector("#myselector").innerHTML = "<a onclick='document.body.style.backgroundColor=\"#F00\"'>Something dangerous</a>";
   4: });
   5: </script>

 

When pulling in potentially unsafe content from an external source, you might want to consider using the toStaticHTML method or creating your nodes via DOM .

jQuery itself comes with lots of checks for the sake of browser compatibility. These checks contain code, that is considered to be potentially malicious and Visual Studio shows warnings when using jQuery 1.8.x

 

You can make jQuery ready for Windows 8 using instructions in this blog.

A patched version of jQuery 1.8.x for Windows 8 is available here.

 

In this article you will learn how to create a hybrid PhoneGap Windows Store application based on jQuery Mobile Twitter WEB application – same as in my previous post . 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 and Windows 8

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

 

PhoneGap for Windows Store applications is based on WinJS projects. WinJS API is handled from cordova script to be possible to have the same API for Cordova apps for all mobile platforms and Windows Store.

Cordova projects for Windows 8 have the same security restrictions like WinJS applications.  More information about Windows Store applications security you can find here.

We will use two different approaches to use our jQuery Mobile code in Windows Store applications.

  • Implementing the whole UI in iframe
  • Communication Between Contexts (exchange data between local and Web context)

 

The first approach is easier and you could use it when you just want to “move” your mobile WEB application to Windows 8.

The second approach ( using iframe only for Ajax requests and maintain data via messages between frames) provides possibility to have a fully functional Windows Store hybrid application .

 

Implementing the whole UI in iframe

 

It is possible to have only iframe in your main page (index.html). You have no need to add jQuery references in your index.html file.

<iframe id="inlineWebContentIframe" src="ms-appx-web:///www/twitterSearch.html" width="100%" height="700"></iframe>

 

Index.html file source code:

   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="js/jquery.js"></script>
   1:  
   2:         <script src="js/jquery.mobile-1.2.0.js">
</script>
  10:         <title>Hello World</title>
  11:     </head>
  12:     <body>
  13:          <div>
  14:              <iframe id="inlineWebContentIframe" src="ms-appx-web:///www/twitterSearch.html" width="100%" height="700"></iframe>
  15:          </div>
  16:     </body>
  17: </html>

 

twitterSearch.html file source code:

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

 

Twitter application (iframe implementation)

 

 

 

Using Communication Between Contexts(exchange data between local and Web context )

 

With Windows 8, JavaScript plays an important part in the overall security of your app by providing the tools necessary to secure data, validate input and separate potentially malicious content.

 

  • Communication Between Contexts

How does the top-level document communicate with an iframe running in the Web context? Using the postMessage features of HTML5, Windows Store apps can pass data between contexts. This allows developers to structure how the two origins communicate and to allow only known good providers (the allow list again) through to the local context. Pages that need to run in the Web context are referenced using an iframe with the src attribute set to http://, https:// or ms-appx-web://.

 

Sampe Windows Store twitter app uses twitterSearch.html page, which is used to communicate with the TwitterAPI via the jQuery $ajax object. Because of the remote resource, twitterSearch.html needs to execute in the Web context, which means that it needs to run within an iframe. Setting up the iframe isn’t different than in any other Web application except for how the page is referenced. Because the twitterSearch.html page is part of the local app package but needs to run in the Web context, you load it using ms-appx-web:

 

<iframe id="inlineWebContentIframe" src="ms-appx-web:///www/twitterSearch.html" width="1" height="1"></iframe>

 

The doSearch method processes the message from the local context.

   1: <script type="text/javascript">
   2:     $(function () {
   3:         $('#searchButt').click(function () {
   4:  
   5:                 doSearch();
   6:  
   7:         });
   8:     });
   9:  
  10:     function doSearch() {
  11:  
  12:  
  13:         var twitterOptions = $('#search').val() + "&result_type=" + $('input:radio[name=radio-choice-1]:checked').val() + "&rpp=" + $('#slider').val();
  14:  
  15:         var msg = {
  16:             opt: twitterOptions
  17:         }
  18:  
  19:         var msgData = JSON.stringify(msg)
  20:         var domain = "ms-appx-web://" + document.location.host;
  21:         try {
  22:             var iframe = document.getElementById("inlineWebContentIframe");
  23:             iframe.contentWindow.postMessage(msgData, domain);
  24:         }
  25:         catch (ex) {
  26:             document.getElementById("twitList").innerText = "Error has occurred!";
  27:         }
  28:     }
  29: </script>

 

The argument of receiveMsg (receiveMsg is implementer in twitterSearch.html) is the data provided to the postMessage event. This function (receiveMsg ) contains ajax call to the Twitter API. It returns data like a content of the listview component.

   1: <script>
   2:     function receiveMsg(e) {
   3:         if (e.origin === "ms-appx://" + document.location.host) {
   4:             var output = null;
   5:             var result = "";
   6:             var parameters = JSON.parse(e.data);
   7:  
   8:             var url = "http://search.twitter.com/search.json?q=" + parameters.opt;
   9:  
  10:             var options = {
  11:                 dataType: "jsonp",
  12:                 jsonpCallback: "jsonpCallback",
  13:                 success: function (json_results) {
  14:                     //result += '<ul data-role="listview">';
  15:                     $.each(json_results.results, function (key) {
  16:                         html = '<img src="' + json_results.results[key].profile_image_url + '"/>';
  17:                         html += '<h3><a href="#">' + json_results.results[key].text + '</a></h3>';
  18:                         html += '<p>From: ' + json_results.results[key].from_user + ' Created: ' + json_results.results[key].created_at + '</p>';
  19:                         result += '<li>' + html + '</li>';
  20:                     });
  21:  
  22:                     output = JSON.stringify(result);
  23:                     window.parent.postMessage(output, "ms-appx://"
  24:                     + document.location.host);
  25:                 },
  26:                 error: function (ex) {
  27:                     output = ex;
  28:                 }
  29:             };
  30:             $.ajax(url, options);
  31:         }
  32:     }
  33:  
  34:     function init() {
  35:         window.addEventListener('message', receiveMsg, false);
  36:     }
  37:  
  38:     window.onload = init;
  39: </script>

 

The processResult function to consume the data from the Web context back into the local context: it receives JSON as a string. The received data should be parsed an add as a content of the listview control.

   1: <script>
   2:     function processResult(e) {
   3:         if (e.origin === "ms-appx-web://" + document.location.host) {
   4:             var res = JSON.parse(e.data);
   5:             $('#twitList').append(res);
   6:             $('#twitList').listview('refresh');
   7:         }
   8:     }
   9:  
  10:     function init() {
  11:         window.addEventListener('message', processResult, false);
  12:     }
  13:  
  14:     window.onload = init;
  15: </script>

 

Twitter application (implementation with communication between frames / exchange data between local and Web context )

 

 

 

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

Source code version that is implemented entirely in iframe is available here.

Demo version that uses  communication between contexts   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!