This is Part 3 of the walkthrough that demonstrates cross-platform mobile application development with PhoneGap, WP7 and .NET 4.0 WCF REST Services.
For a description of the PhoneGap, for a WP7 installation review Part 1. For a descripion of the .NET 4.0 WCF REST Template installation refer to Part 2.
Developing in a PhoneGap application is quite cumbersome. It takes too much time to start debugging and push the code to the phone emulator or the actual device. In order to expedite the process my suggestion is to develop and run web pages, css styles and javascript files in the WCF Rest Services application. That way there is no problem with AJAX and CORS (Cross-Origin Resource Sharing) when testing and all the code, with just few corrections, can be just copied and pasted to PhoneGap application (both for WP7 or iPad).
One problem that needs to be solved when using web pages running in the browser for PhoneGap development is the need to emulate device specific functionality, which is expected by the PhoneGap template code. This can be seen below in the copy of the index.html page supplied with the template. Notice PhoneGap specific event listener in init() function:
function init()
{
document.addEventListener("deviceready",
onDeviceReady,false);
}
The full index.html page:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>PhoneGap WP7</title>
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8"/>
<script type="text/javascript">
// provide our own console if it does not exist, huge dev aid!
if(typeof window.console == "undefined")
{
window.console = {log:function(str){window.external.Notify(str);}};
}
// output any errors to console log, created above.
window.onerror=function(e)
{
console.log("window.onerror ::" + JSON.stringify(e));
};
console.log("Installed console ! ");
</script>
<script type="text/javascript" charset="utf-8" src="phonegap-1.1.0.js"></script>
<script type="text/javascript">
function init()
{
document.addEventListener("deviceready",onDeviceReady,false);
}
// once the device ready event fires, you can safely do your thing! -jm
function onDeviceReady()
{
document.getElementById("welcomeMsg").innerHTML += "PhoneGap is ready!";
}
</script>
</head>
<body onLoad="init();">
<h1>Hello PhoneGap</h1>
<div id="welcomeMsg"></div>
</body>
</html>
A very good solution to it (which I borrowed) can be found at code project (author: Colin Eberhardt). PhoneGapMock.js is a javascript code file that does the trick.
You should also expect problems with the “windows.console” code in the index.html. It will work in Chrome for example but not in IE.
The next step is to separte javascript code into separate files. The final content of the www folder inside the WCF template project should look like this:
The content of the files:
index.html
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=320 user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>PhoneGap WP7</title>
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8" />
<script type="text/javascript" charset="utf-8" src="console.js"></script>
<script type="text/javascript" charset="utf-8" src="jquery-1.6.4.min.js"></script>
<script type="text/javascript" charset="utf-8" src="phonegapMock.js"></script>
<script type="text/javascript" charset="utf-8" src="init.js"></script>
</head>
<body>
<div>
<h1 id="welcomeMsg">Welcome</h1>
<p><a href="#" onclick="login(); return false;">log in</a></p>
<p><a href="#" onclick="getAjax(); return false;">Get Ajax</a></p>
<p><a href="#" onclick="postAjax(); return false;">Post Ajax</a></p>
<p><a href="#" onclick="getSingle(); return false;">Get Single Item</a></p>
<p><a href="#" onclick="deleteSingle(); return false;">Delete</a></p>
<p><a href="#" onclick="updateSingle(); return false;">Update</a></p>
<p><a href="#" onclick="identify(); return false;">Identify</a></p>
<p><a href="#" onclick="logViaForm(); return false;">Log via Form</a></p>
<p><a href="#" onclick="logout(); return false;">log out</a></p>
<p id="errorMessage" class="err"></p>
<p id="loginCall"></p>
<p id="ajaxCall"></p>
<p id="postAjaxCall"></p>
<p id="getSingleCall"></p>
<p id="deleteSingleCall"></p>
<p id="updateSingleCall"></p>
<p id="identifyCall"></p>
<p id="logViaFormCall"></p>
<p id="logoutCall"></p>
<input type="text" id="myTest" value="1" name="myTest" />
</div>
</body>
</html>
console.js
// provide our own console if it does not exist, huge dev aid!
if (typeof window.console == "undefined") {
window.console = { log: function (str) { window.external.Notify(str); } };
}
// output any errors to console log, created above.
window.onerror = function (e) {
console.log("window.onerror ::" + JSON.stringify(e));
};
console.log("Installed console ! ");
phoneGapMock.js
document.addEventListener = function (evt, handler, capture) {
$("body").bind(evt, handler);
};
$(document).ready(function () {
setTimeout(function () {
$("body").trigger("deviceready");
}, 100);
});
init.js
$(document).ready(function () {
document.addEventListener("deviceready", onDeviceReady, false);
});
// phonegap is initialised
function onDeviceReady() {
$("#welcomeMsg").append("...Ready");
}
function showAlert(msg) {
//alert(msg);
}
function showError(error, otherInfo) {
var element = document.getElementById('errorMessage');
element.innerHTML = "Errors: " + error.Message + "<br>" + (otherInfo ? otherInfo : "");
}
function getAjax() {
var jqxhr = $.ajax({
url: '../service1/',
//headers:
beforeSend: function (xhr) {
//xhr.overrideMimeType('text/plain; charset=x-user-defined');
},
dataType: 'json'
})
.done(function (data) {
var element = document.getElementById('ajaxCall');
element.innerHTML = JSON.stringify(data, null, "\t");
})
.fail(function (xhr, status, error) {
showError(error);
})
.always(function () { showAlert("complete"); });
}
function postAjax(parameters) {
var jqxhr = $.ajax({
url: '../service1/',
type: 'POST',
//headers:
//beforeSend: function (xhr) {
//},
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: '{ "Id":2, "StringValue": "jerry" }'
})
.done(function (data) {
var element = document.getElementById('postAjaxCall');
element.innerHTML = JSON.stringify(data, null, "\t");
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function login() {
var jqxhr = $.ajax({
url: '../login/',
type: 'POST',
//headers:
//beforeSend: function (xhr) {
//},
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: '{ "Username":"test", "Password": "test" }'
})
.done(function (data) {
var element = document.getElementById('loginCall');
element.innerHTML = "Login Succesfull ? " + data;
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function logout() {
var jqxhr = $.ajax({
url: '../login/logout',
type: 'POST',
//headers:
//beforeSend: function (xhr) {
//},
dataType: 'json',
contentType: 'application/json; charset=utf-8'
})
.done(function (data) {
var element = document.getElementById('logoutCall');
element.innerHTML = "Login Out Succesfull ? " + data;
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function getSingle(parameters) {
var jqxhr = $.ajax({
url: '../service1/88',
type: 'GET',
//headers:
beforeSend: function (xhr) {
//xhr.overrideMimeType('text/plain; charset=x-user-defined');
},
dataType: 'json',
contentType: 'application/json; charset=utf-8'
})
.done(function (data) {
var element = document.getElementById('getSingleCall');
element.innerHTML = JSON.stringify(data, null, "\t");
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function deleteSingle(parameters) {
var jqxhr = $.ajax({
url: '../service1/88',
type: 'DELETE',
//headers:
beforeSend: function (xhr) {
//xhr.overrideMimeType('text/plain; charset=x-user-defined');
},
dataType: 'json',
contentType: 'application/json; charset=utf-8'
})
.done(function (data) {
var element = document.getElementById('deleteSingleCall');
element.innerHTML = JSON.stringify(data, null, "\t");
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function updateSingle(parameters) {
var jqxhr = $.ajax({
url: '../service1/99',
type: 'PUT',
//headers:
beforeSend: function (xhr) {
//xhr.overrideMimeType('text/plain; charset=x-user-defined');
},
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: '{ "Id":99, "StringValue": "JERRY " }'
})
.done(function (data) {
var element = document.getElementById('updateSingleCall');
element.innerHTML = JSON.stringify(data, null, "\t");
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function identify(parameters) {
var jqxhr = $.ajax({
url: '../login/identify',
type: 'GET',
dataType: 'json',
contentType: 'application/json; charset=utf-8'
})
.done(function (data) {
var element = document.getElementById('identifyCall');
element.innerHTML = JSON.stringify(data, null, "\t");
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
function logViaForm() {
var jqxhr = $.ajax({
url: '../login.aspx',
type: 'GET',
dataType: 'html'
})
.done(function (data) {
var eventVal = $(data).find('#__EVENTVALIDATION').attr('value');
var viewState = $(data).find('#__VIEWSTATE').attr('value');
//build post data
var postData = { __VIEWSTATE: viewState, __EVENTVALIDATION: eventVal, UserName: "test", Password: "test", LoginButton: "Log In" };
var jqxhr1 = $.ajax({
url: '../login.aspx',
type: 'POST',
dataType: 'html',
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
data: postData
})
.done(function (data1, status, jqxhr1) {
//this works but we will get an error dues to the redirect to the home.aspx
//TODO: need to handle that
var element = document.getElementById('logViaFormCall');
element.innerHTML = "Login Succesfull ! " + jqxhr1.status;
})
.fail(function (xhr, status, error) {
showError(error, "TODO: Works but need to handle redirect!!");
//but it really works!
var element = document.getElementById('logViaFormCall');
element.innerHTML = "Login Succesfull ! Verify that Authenticated AJAX calls work!";
})
.always(function () { showAlert("complete login"); });
})
.fail(function (xhr, status, error) { showError(error); })
.always(function () { showAlert("complete"); });
}
Web Page Preview:
The Web Page View after testing all functionality:
To Note:
- AJAX requests for protected resources will succeed only after a call to the login service (“log in” link) or after a call to the login form (“Log via Form” link).
- The code will not handle the redirects for failed unauthenticated AJAX requests
- The code will not fully handle the login via the form (it will fail on the redirect to home.aspx), but the login will succeed and the subsequent AJAX requests will work. The error in red is the result of that redirect that is not beinh handled.
The Result:
The above results page demonstrates that GET, POST, PUT and DELETE AJAX requests are handled by .NET WCF REST Services hosted in the web application with FBA enabled.
Transferring HTML application to PhoneGap Application
- Copy the content of the www folder to PhoneGap’s www folder with an exception of the PhoneGapMock.js file (also remove the link to PhoneGapMock.js from index.html.
- Modify showAlert() function to use native dialogs. See below.
- Modify all urls in the code to use an absolute link (for example): url: ‘https://machine.domain.com/appName/service1′
Modified showAlert() function:
function showAlert(msg) {
navigator.notification.alert(
msg, // message
alertDismissed, // callback
'Alert', // title
'Done' // buttonName
);
}
That’s all that should be needed to run the application on iPad.
Windows Phone Only:
Add CORS support in jQuery by adding one line of the code to $(document).ready(function ()…
$(document).ready(function () {
document.addEventListener("deviceready", onDeviceReady, false);
jQuery.support.cors = true; //Cross-Origin Resource Sharing
});
Also (for Windows Phone 7), right click GapSourceDictionary.tt file and execute “Run Custom Tool” to make sure that all added files will be included in the build and transferred to the device.
Final PhoneGap application files:
Summary
This post demonstrates PhoneGap application code that runs on both iOS and Windows Phone 7 devices. The application also runs as a web application (client side code only) which makes development more robust. It’s easier to troubleshot and test functionality in the regular browser during the initial development phase than using a simulator/emulator or the physical device for that purpose.
Moreover, the post demonstrates how to create PhoneGap application utilizing .NET WCF REST Services hosted in a web application protected by Forms Based Authentication. This might be a likely scenario for legacy LOB applications.
Test PhoneGap application was developed for and deployed to iPad and WP7.




When i wasn’t logged in and I tested the service I was sent to login.aspx. I haven’t put your example on a device, but how does that work on a device. It can’t go to a go to the login.aspx and index.html. How would get the cookie on your device when you can’t log in?
Thanks and great example!
@Logan
On the device you actually have the index.html page (this is your phoneGap applicaiton: index.html + javascripts + css). The ajax call to the service runnng on the server will log you in and the browser on the device will get the cookie. So subsequent ajax calls will be authenticated.
I have followed your article and I am able to run the project in ASP.Net web application. But when I deploy everything to Winodw Phone 7 emulator it does not work and I get Log:”window.onerror ::\”‘alertDismissed’ is undefined\”"
Can you please share the sample project with JQuery 1.7.1 + PhoneGap 1.3
thanks