Using OAuth 2.0 with Google API in Phonegap / ChildBrowser

This article explains how to implement OAuth 2 with Google API (gapi) on Phonegap using Childbrowser Plugin. We will authorize Google Tasks API from Phonegap and then retrieve a list of Tasks (of default list) from Google Server. Because the App is built using Phonegap, it can be deployed in iOS, Android, Blackberry, Windows Phone with no changes!

We have created 2 javascript files (liquid.js, liquid.helper.oauth.js) which facilitates the authorization process and I shall be using those 2 files in this example. However, once the process is understood, you can easily do it on your own.

1. The Output (on Android)

The output shall look like below. Upon initial launch, the user shall be required to authorize it. After the user authorizes, on next visit, the App shall automatically redirect to the Task List Page.

Working Demo: Download and install the app from Phonegap build and try on your devices!

2. Prerequisites: Before we begin

Basically, you shall need the following configured before proceeding:

  1. Phonegap/Cordova configured with
    ChildBrowser plugin
  2. jquery 1.7.2 or above
  3. jquery Mobile 1.1.1

  4. Google API Client

Here’s a screenshot of directory structure:



You won’t have the files underlined red. So, copy the following files from github:
liquid.js
helper/liquid.helper.oauth.js
model/liquid.model.tasks.js
As for, qtaskdemo.js, we will create it. So, create an empty file qtaskdemo.main.js (or anything) for storing js code for the index.html.

Now, register a OAuth access from your Google API Console. For the Client Application Type, choose ‘Installed Application’ and for Installed application type, choose ‘Other’. Here’s a screenshot:

OAuth 2.0 Access

Then, open up liquid.js and update the values for client_id and client_secret (line 65 onwards) received from Google API.

// The client_id obtained during application registration
client_id: "###",
				
// The client secret obtained during application registration
client_secret: "###", 	

3. Getting Started: Referencing the JS Files

I am assuming the html file to be used is index.html (from Activity). So, now add/reference the Javascript files. The head shall look like below. Also, add the qtaskdemo.main.js (or else) right before body ends

        < head >
        GTask Demo
        
                
        
        
        
        
        
                
        
        
        
        
        
        
    
    < /head >  
    < body >
     ....
     ....
    
   < /body >
  

On the section ‘JS to go here’, add the following lines:

$(document).bind("mobileinit", function() {            
            $.mobile.touchOverflowEnabled = true; // required
            $.mobile.defaultPageTransition = 'none'; // optional - for better performance on Android
            $.mobile.loadingMessageTextVisible = true; // optional - added to show text message
            $.mobile.buttonMarkup.hoverDelay = 20; // optional added to remove sluggishness - jqm default 200
});

4. Creating an Unauthorized Page

Now that JS has been referenced, lets create an unauthorized page (add the lines below inside your body):

   

GTask Demo

Getting Started

Authorize the App from your Google Account and you are ready to go!

Powered by Task

5. Creating a Tasklist Page

Lets create a simple Tasklist Page. Copy/Add the lines below inside your body:

	

Tasks Demo

Refresh Signout

6. Adding Authorization Process

So far, JS has been added, and 2 pages has been created. Add the following lines in qtaskdemo.main.js to prepare your app to start when device is ready.

$(document).ready(function() {
	
	/* startApp after device ready */
	document.addEventListener("deviceready", startApp, false);
});


/**
 * Start the App
 */
function startApp() {

}

Now, lets add an authorization feature. The Authorization process is completed in 3 steps.

  1. A New window is opened where user has to login to their google account and allow access to the App.
  2. After successful login, an authorization code is returned
  3. Using the authorization code, make another request to google’s server to get the refresh token and access token. The refresh token is permanent, however, the access Token expires in 1 hour. Thus, the refresh token needs to be stored in a local storage, as it can be used again to get new access token.

If its unclear, please spend some time on google’s oauth playground for a better understanding.

Now that OAuth process is clear, lets begin development. Initially, the App should show the Authorization page and upon clicking ‘Authorize &amp Go’, we want the authorization process to begin. Lets look at authorize function of liquid.helper.oauth.

/**
 * Attempts to authorize user using OAuth 
 * Opens up Another window where user allows access or denies it.
 *   
 * @param {function} callBack   A callback function which is invoked
 */
authorize: function(callBack) {
..
}

This function does the authorization process with google server and after success/error of the process, it invokes the callback function. liquid.helper.oauth also keeps track of the the error or success in its variable status, and also stores the authorization code (auth_code) in its local variable authCode. The status are:

/** 
 * Enum for Status values
 * 
 * @enum {number}
 *  
 * SUCCESS - Successfully data received from server
 * ERROR - Error occurred when trying to receive from server
 * NOT_DETERMINED - undetermined
 */
status: { 
	SUCESS: 1, 
	ERROR: -1, 
	NOT_DETERMINED: 0 
},

So, we add the following code in our qtaskdemo.main.js file

function startApp() 
{	
   var oAuth = liquid.helper.oauth;
	
    $("#access-code").click(function(event) {
        oAuth.authorize(authorizeWindowChange);
        event.preventDefault();
    });
}

Now, we need to define authorizeWindowChange function, so we add the following function:


function authorizeWindowChange(uriLocation) {
    //console.log("Location Changed: " + uriLocation); 
    var oAuth = liquid.helper.oauth;
   
   // check the status, oAuth process is successful!	
    if (oAuth.requestStatus == oAuth.status.SUCCESS) {
        var authCode = oAuth.authCode;
        // Got the authCode, now use it to get the refresh token ?
    }
    else if (oAuth.requestStatus == oAuth.status.ERROR) 
    {
    	console.log("Error >> oAuth Processing");
    } 
    else {
        // do nothing I guess!
    }


}

We received the authorization code (authCode), now we need to use it to get refresh token and an initial access token, and also store the refresh token. The function saveRefreshToken in liquid.helper.oauth does that for you. Here’s what it looks like:

/**
 * Saves the Refresh Token in a local database or localStorage
 * This method shall be invoked from externally only once after an 
 * authorization code is received from google's server. This method 
 * calls the other method (getRefreshToken) to get the refresh Token and
 * then saves it locally on database and invokes a callback function
 * 
 * @param tokenObj A Object containing authorization code
 * @param {String} tokenObj.auth_code The authorization code from google's server
 * 
 * @param {Function} callback The function to be invoked with parameters
 */
saveRefreshToken: function(tokenObj, callback) {

..
}

So, back to the authorizeWindowChange function, we invoke the function saveRefreshToken and give the function startPageTaskList as a callback. startPageTaskList simply changes the current Page to a Task List Page as shown below.

function authorizeWindowChange(uriLocation) {
    //console.log("Location Changed: " + uriLocation); 
	var oAuth = liquid.helper.oauth;
	
	// oAuth process is successful!	
    if (oAuth.requestStatus == oAuth.status.SUCCESS) {
        var authCode = oAuth.authCode;

        // have the authCode, now save the refreshToken and start Page Task List
        oAuth.saveRefreshToken({ 
        	  	auth_code: oAuth.authCode
        	  }, startPageTaskList);
        
    } 
    else if (oAuth.requestStatus == oAuth.status.ERROR) 
    {
    	console.log("Error: oAuth Processing");
    } 
    else {
        // do nothing I guess!
    }
}

function startPageTaskList() {
    $.mobile.changePage("#page-tasklist", {
        transition : "none",
    });
}

So far we have completed the authorization process. However, what if the user authorizes the App, closes it, and then relaunches it. With our current approach, the user would have to reauthorize the app again. Fortunately liquid.helper.oauth has a method isAuthorized() which returns true if the App is already authorized, false otherwise. So, we simply add that logic in startApp():

/**
 * Start the App
 */
function startApp() {
   var oAuth = liquid.helper.oauth;
	
    $("#access-code").click(function(event) {
        liquid.helper.oauth.authorize(authorizeWindowChange);
        event.preventDefault();
    });

    
    if (oAuth.isAuthorized()) {
    	/* Start Page TaskList */
    	startPageTaskList();
    }
}

7. Setting up the Task List Page (optional)

At this point, authorization is complete. I simply decided to add another extra page which lists all the tasks of the user. We
would like to check on the start of page if the user is Authorized is not. If the user is not authorized, we simply direct to the Home (Unauthorized page). Also add listeners for the 2 menu buttons (refresh and signout). Also, we want to populate the page with the Tasks as well (which is achieved by populateTaskList). So, we add the lines in gtaskdemo.main.js.

(function() {

	$('#page-tasklist').live('pageshow', function(event) {
		
		if (!liquid.helper.oauth.isAuthorized()) {
			goHome();
			return;
		}
		
		$('#btn-refresh').click(function(event) {
			populateTaskList();
			event.preventDefault();
		});
		
		$('#btn-hide-error').click(function(event) {
			
			$('#qt-listview-error').hide();			
			event.preventDefault();
		});
		
		/* populateTaskList on page init */
		populateTaskList();
	
		/**
		 * Add the listeners for each of the components
		 * Listeners are for:
		 * - Title bar refresh btn (#head-menu-refresh)
		 */
		$('#head-menu-refresh').click(function(event) {
		    populateTaskList();
		    event.preventDefault();
		});
		
		
		$('#head-menu-signout').click(function(event) {
		    liquid.helper.oauth.unAuthorize();
		    goHome();
		    event.preventDefault();
		});
	
	});
})();

8. Putting it all together

So the entire code for the qtaskdemo.main.js looks like:


$(document).ready(function() {
	
	/* startApp after device ready */
	document.addEventListener("deviceready", startApp, false);
});


/**
 * Start the App
 */
function startApp() {
	
	var oAuth = liquid.helper.oauth;
	
    $("#access-code").click(function(event) {
        liquid.helper.oauth.authorize(authorizeWindowChange);
        event.preventDefault();
    });

    
    if (oAuth.isAuthorized()) {
    	/* Start Page TaskList */
    	startPageTaskList();
    }
}


function startPageTaskList() {
    $.mobile.changePage("#page-tasklist", {
        transition : "none",
    });
}



function authorizeWindowChange(uriLocation) {
    //console.log("Location Changed: " + uriLocation); 
	var oAuth = liquid.helper.oauth;
	
	// oAuth process is successful!	
    if (oAuth.requestStatus == oAuth.status.SUCCESS) {
        var authCode = oAuth.authCode;

        // have the authCode, now save the refreshToken and start Page TaskList
        oAuth.saveRefreshToken({ 
        	  	auth_code: oAuth.authCode
        	  }, function() {
        		  startPageTaskList();
        	  });
        
    } 
    else if (oAuth.requestStatus == oAuth.status.ERROR) 
    {
    	console.log("Error >> oAuth Processing");
    } 
    else {
        // do nothing I guess!
    }
}


/**
 * Populates the list of Tasks
 */
function populateTaskList() {
	$.mobile.showPageLoadingMsg("a", "Loading Tasks...");
	
	/* hide all the request Info blocks/divs */
	$('.request-info').hide();
	
	liquid.model.tasks.getList(function(data) {
        $('#qt-listview-tasks').empty();
        
        console.log(JSON.stringify(data));
        
        /* check if there's an error from server, then display
         * error and retry
         */
        if (data.error) {
        	console.log('Unable to load Task List >> ' + data.error.message);
        	$.mobile.hidePageLoadingMsg();   
            return;        	
        }
        
        /* if there are no elements in it, then
         * display the info message, and return */
        if (!data.items) {
        	$('#qt-listview-info').show();
            $.mobile.hidePageLoadingMsg();        	
            return;
        }
        
        
        for (var i = 0; i < data.items.length; i++) {
        	
        	var item = data.items[i];
        	
        	$('#qt-listview-tasks')
        		.append('
  • ' + item.title + '
  • '); } $('#qt-listview-tasks').listview('refresh'); $.mobile.hidePageLoadingMsg(); }); } function goHome() { $.mobile.changePage("#page-unauthorized", { transition : "none", reverse: false, changeHash: false }); } (function() { $('#page-tasklist').live('pageshow', function(event) { if (!liquid.helper.oauth.isAuthorized()) { goHome(); return; } $('#btn-refresh').click(function(event) { populateTaskList(); event.preventDefault(); }); $('#btn-hide-error').click(function(event) { $('#qt-listview-error').hide(); event.preventDefault(); }); /* populateTaskList on page init */ populateTaskList(); /** * Add the listeners for each of the components * Listeners are for: * - Title bar refresh btn (#head-menu-refresh) */ $('#head-menu-refresh').click(function(event) { populateTaskList(); event.preventDefault(); }); $('#head-menu-signout').click(function(event) { liquid.helper.oauth.unAuthorize(); goHome(); event.preventDefault(); }); }); })();

    9. Final Thoughts

    The above was a rough demonstration of how to use google oauth on phonegap with childbrowser. For a production, it is better to combine/merge the Javascript file into 2 or 3 files, and also JS library such as backbone.js may be considered to avoid messy JS code

    Download from Github

    Download & Install App

    Good Javascript Practices

    I decided to share some of the best practices of Javascript which I have learned from various blogs and books over the years. Although, I used Javascript back in 2003, I didn’t learn Javascript properly and just happened to use it for some fun webpages. However, as I revisited Javascript from 2007 onwards, I have started to use Javascript as an OO language and try to follow the best practices.

    Below are a list of 7 such Best Practices of Javascript:

    #1. Making Scripts External

    Making Javascripts External keeps it separate from the HTML file and as such is easier to maintain. Also one can set an expiry date for the external js file (In Apache, expiry can be set using mod_expire), and thus the page loading would be much faster.

    
    

    #2. Placing Scripts at the bottom

    Javascript code should be placed at the bottom of the page right before the ending </body> tag. Script blocks parallel downloads, hence keeping them at the top of the page would block the downloading of the page until the Javascript has been completely downloaded. So, its best to put Javascript at the bottom. Read more on Yahoo Developer Network

    So, typically scripts code block should be like below:

    
    < /body>
    

    #3. Avoiding too many Global Variables

    Instead of declaring multiple variables like below:

    var name = "Alexander";
    var gender = "male";
    var phone  = "416-999-9999";
    
    function updatePerson(n,g,p) {
    name = n;
    gender=g;
    phone=p;
    }
    

    Its better to re-organize variable and declare it like below in JSON Format:

    var Person = { 
     name: "Alexander", 
     gender: "male", 
     phone: "416-999-9999"
     update: function(info) {
     // info is a object containing name, gender, phone. 
     // process accordingly
     }
    };
    

    #4. Using Shortcuts

    Instead of declaring arrays like:

    var nameList = new Array();
    nameList[0] = "Alif"; 
    nameList[1] = "Alexander";
    nameList[2] = "Totem";
    

    Its better to use:

    var nameList = ["Alif", "Alexander","Totem"];
    

    Using ternary operator instead of if-else, depending on circumstances. (Some developers I have worked with on projects, have asked me not to use ternary operator as it complicates the readability of coding, so this point is debatable, and hence its upto individuals to decide).

    Instead of using:

    var x;
    if(cond == true) {
    x = "A"; 
    } else {
    x = "C";
    }
    

    Use:

    var x = cond ? "A" : "C";
    

    #5. Unobtrusive Scripting

    Instead of using inline Javascript, the Javascript code should be separated out from the HTML codes. From the Javascript, a specific tag/node should be grabbed to attach an event or do something else. For example, instead of using:

    Show Users
    

    Grab the element/node from the Javascript separately and attach the function with it:

    document.getElementById("showUsers").onclick = function() {
    showUsersList();
    return false; // or event.preventDefault() for non-IE;
    };
    

    #6. Progressive Enhancement

    Progressive Enhancement technique starts with offering a minimal level of user experience that all browsers will be able to display. Then, on top that, more advanced functionality are built to enrich user experience.

    For example, when the following code block is used:

    Show Users
    

    It is assumed that the visitor/user has a Javascript enabled Browser.

    But, what if the user doesn’t have Javascript enabled? and even worse, what if the user doesn’t know or do not have permission to “enable/allow” javascript?. For that case, the above script will not work, and non-JS users will not be able to see the functionality of “Show Users”.
    So, the following technique should be used:

    Show Users
    

    From the Javascript, the element should be grabbed and onclick event should be attached as described on #5. And a functionality of ‘url.php?s=all’ should be added to the page to ensure non-JS users gets to see the same feature at a degraded user experience.

    #7. Minify Javascript

    Javascript should be minified whenever applicable (minify means to strip out comments and white spaces, carriage returns etc.). There are some great tools online to minify a Javascript.

    My favorite one is YUI Compressor, an online version is here. Minifying scripts reduces the file size significantly (depending on your code). As a rough example, jQuery 1.4 actual is 155kb, whilst minified version is ~74kb.

    Thats about it !.

    Please note that I am still learning as time progresses, as a result, if you find any of the above practices as ‘not the best practice of JS’, then notify me.

    For more reading, please visit the following resources:

    Opera Developers | Javascript Best Practices

    ezMark: jQuery Checkbox & Radiobutton Plugin

    ezMark is a jQuery Plugin that allows you to stylize Radio button and Checkbox easily. Its very small (minified version is ~1.5kb) compared to other similar scripts. It has been tested and works on all major browsers (IE 6/7/8, Firefox, Safari, Chrome) and it gracefully degrades.

    It has the following Method:

    $('selector').ezMark( [options] ); 
    

    Basic Usage:

    Include the CSS file:

    
    

    Then include the jquery.js file and the plugin file:

    
    
    

    Write the HTML tags normally, as you would type them

    
    

    Now, call the following method in your JS (to apply on all checkbox & radiobutton across the page):

    $('input').ezMark();
    

    Likewise, you can also use custom selectors for checkbox or radiobuttons:

    // to apply only to checkbox use:
    $('input[type="checkbox"]').ezMark();
    
    // for only radio buttons:
    $('input[type="radio"]').ezMark();
    

    Parameters:

    options parameter accepts the following JSON properties:

    Parameter’s (JSON) Properties: Explanation/Details of the Property
    checkboxCls The Checkbox Class as per declaration on CSS.
    checkedCls The Checkbox Class on Checked State
    radioCls The Radio button’s Class as per CSS
    selectedCls The Radio Button’s Class on selected State

    To customize the default checkbox/radiobutton image, change the background image (checkbox-black.png/radio-black.png) and CSS (ez-checkbox/ez-radio) and (ez-checked/ez-selected) accordingly.

    Customized Usage:

    After you have customized/created your own css class. Simply call the ezmark method like below:

    // to apply only to checkbox use:
    $('input[type="checkbox"]').ezMark({
     checkboxCls: 'your-default-checkbox-class-name' ,
     checkedCls: 'your-checkbox-class-in-checked-state'
    });
    
    // for only radio buttons:
    $('input[type="checkbox"]').ezMark({
     radioCls: 'your-default-radiobutton-class-name' ,
     selectedCls: 'your-radiobutton-class-in-selected-state'
    });
    
    

    View Demo

    Download from Github

    Download from jQuery Server

    Canada Postal Code & US Zip Code Regex

    I decided to write a simple Canadian Postal Code and US Zip Code Regex. There are already good ones on the web, like here for example. But, I decided to write my own to make it slightly more accurate.

    Canadian Postal Code Regex

    Canada’s Postal Code format is ‘A1A 1X1’ or ‘a1a1x1’. Its made up of two parts. Forward Sortation Area (FSA) and Local Delivery Unit (LDU). Read more on wikipedia. The letters D, F, I, O, Q, or U are not used on postal Code. So, the Postal Code would be roughly (note that when applying the regex, the case-insensitive (i) modifier should be used):

    US Zip Code Regex

    US Zip Code format seems to be straightforward. It needs to have 5 digits followed by an optional 4 digit. So, the Zip Code would be roughly:

    Merging US & Canadian Postal Code

    Now, both Postal Code and Zip Code could be merged, using an OR (|) operator, into one regex: