My last entry was a little light on the client APIs and mostly spent (unsuccessfully) wrestling my HTML markup around.  So,  to make up for it, I've implemented 2 different client APIs.  That's right, two for the price of one!  The first was notifications which was pretty straightforward, followed by contacts which is kind of complicated-- well, let's just say "involved".

You may notice in the screenshots that the app is no longer full screen.  I didn't care for that so I found the fullscreen preference under the project's PhoneGap properties and set it to false.  This setting does not appear to be stored anywhere in the web root though, so if you check out the code into a mobile project of your own you'll probably have to set it yourself too.

Organization

After my unsuccessful attempts at coming up with some conventions for organizing my code, I still just have everything in the index.cfm file.  I'm not a fan of this, and the amount of redundant code is beginning to grow.  Hopefully I'll have a chance to revisit this before I'm done.  I also pinged the CF team and asked for an engineer to weigh in.

Notification

The notification API is simple and was implemented quickly.  The biggest issue I had was some incorrect information on the docs as well as the docs having me use deprecated PhoneGap methods.  I was able to sort everything out with a bit of experimentation.  My page for this just consists of a series of buttons that, when pressed, will fire the selected notification.  I also included the built-in browser alert and confirm dialogs for comparison.

Alert

<button data-icon="alert" onClick="notifyAlert();">Alert</button>
function notifyAlert() {
	cfclient.notification.alert( "The British are coming!", "Don't just stand there!'", "Got it" );
}

Alert is like the typical browser alert, but on steroids.  In addition to setting the alert message, you get to customize the title of the window as well as the text on the button.  As you can see, I've done all three here.

Beep

<button data-icon="alert" onClick="notifyBeep();">Beep</button>
function notifyBeep() {
	cfclient.notification.beep( 1 );
}

"Beep" doesn't actually beep for me.  On my Android phone, it just plays the default notification sound effect, which is that silly Samsung "whistle".  The "1" I'm passing in is the number of times to play the sound.  Nothing much to see here.

Confirm

<button data-icon="alert" onClick="notifyConfirm();">Confirm</button>
function notifyConfirm() {
	// Limit 3 options.
	// Returns 1, 2, or 3 depending on answer
	// Returns 0 if no answer (back button on Android will bypass)
	var result = cfclient.notification.confirm( "Do you feel lucky?", "Talk to me", [ 'Um, yea', 'Wut?', 'No' ] );
	console.log( result );
	
	// Give humorous feedback
	if( result == 1 ) {
		alert( 'Good! Here, kiss my chips' );
	} else if( result == 2 ) {
		alert( 'You heard me...' );					
	} else if( result == 3 ) {
		alert( 'Stay away from the lottery today.' );					
	} else {
		alert( 'WHY U NO ANSWER?' );					
	}
}

Now we finally get something interesting.   Your browser's default confirm function just has Ok and Cancel buttons and returns true or false based on what you typed.  This confirm lets you have more than 2 options and tells you which button they clicked by returning a number representing the button (1-based).  

  • The docs say it returns nothing.  WRONG.  It returns the number that tells you what button they pressed.
  • buttonLabels is listed as a string in the docs that is a comma-delimited list.  This  obviously precludes the possibility of having a comma in your actual button text.  Not only that, when you use it, there is a deprecation warning that tells you to use an array instead.  I tried passing in an array instead of a string and it totally works.  I think they should update the docs to tell you to just use an array.
  • The docs don't imply that there is a limit on the number of buttons, but the confirm dialog appears to only show the first 3 buttons you define. 
  • The function returns 1 for the first button, 2 for the second, and 3 for the third.  If I just my Android's back button to skip the dialog, it returns 0.  I don't know if this is possible on iOS but I accounted for it in my code.

Vibrate

<button data-icon="alert" onClick="notifyVibrate();">Vibrate</button>
function notifyVibrate() {
	cfclient.notification.vibrate( 1000 );
}

This does exactly what it says.  Pass in a number of milliseconds to vibrate for.  I'm using 1000 ms which is one second.  It would be fun to write a library to "read" out your incoming messages in Morse Code via vibrations.  Ferb, I know what we're gonna do today!

Browser Alert

This is what the stock JavaScript alert looks like for me.

<button data-icon="info" data-inline="true" onClick="alert( 'Stop poking me' );">Browser Alert</button>

Browser Confirm

This is what the stock JavaScript confirm looks like for me.

<button data-icon="info" data-inline="true" onClick="confirm( 'You know this is inevitable, right?' );">Browser Confirm</button>

JavaScriptification

So, I've been basically treating all my cfclient code as JavaScript, using jQuery selectors, etc.  Since I've been just thinking "JavaScript" in my head, there's been a couple things that have thrown me off.  The first is that arrays still start at 1.  The second is that even though I'm accessing an object called cfclient.notification.etc no such JavaScript object actually exists.  This means you can't just paste cfclient.notification.alert( "My Alert" ); into the GapDebug console to test.  When you write that code in cfclient it's translated into something else for the actual JavaScript.  Honestly, I'd kind of just like it if the cfclient API wrappers were just straight up JavaScript libraries that I could use.  It gets a little hard to remember what code I type that will stay the same and what will be re-written.

The other issue I've run into is the ColdFusion Builder syntax parse isn't always happy with the JavaScript code I've been typing in my CFClient tags.  For the most part, CFML is very close to JavaScript, but not always.  For instance, look at this code block that uses the "null" keyword:

contacts = contacts.filter( function( element ) {
	return element.displayName != null;
});

This ticks CFBuilder off and it tells me there's a syntax error, but the code still compiles fine and runs as JavaScript without issue.  I'm not always that lucky though, I was forced to move this chunk of code outside of cfclient and into a regular <script> tag because my app refused to compile it even though it's valid JavaScript.

$(document).on("pagecontainershow", function(e, ui) {
	var page = $('body').pagecontainer('getActivePage');
	var pageID = page.prop('id');
    console.log( 'Showing page ' + pageID + '.' );
    
    if( page.data( 'onPageLoad' ) ) {
		page.data( 'onPageLoad' )();
	}
});

The first issue is that CFML doesn't like anonymous functions to have arguments declared, and the second issue is calling a function reference that's returned from the data() method without an intermediate variable. (I think Railo actually allows this which is nice)  We're supposed to be be able to mix JavaScript code in with our CFML, but it only works when the syntax is agreeable with the CFMl parser.

Contactification

The second API I implemented is for managing contacts from your phone's address book.  This requires you to add the contacts feature in the PhoneGap settings of your project.  There are two ways to list contacts.  You can get all of them, or do a search.

  • When getting all, you can specify what properties you want returned, but when running a search, you just get the entire contact object.  
  • There's no difference I can find between getting all and searching for an empty string so I'm just using the latter.
  • There's no pagination built in.  Normally I wouldn't think it would matter, but it takes several seconds to get all 1000 or so of my contacts which is honestly kind of slow for an on-device query.  I can't help but wonder where the bottleneck is and if some native pagination would improve the performance.
  • I had to implement my own filter (to weed out annoying entries with "null" for a name), sorting, and pagination.  It would be nice if more of this was offered by the API

Older versions of jQuery Mobile used to let you bind to a specific page's load event but the latest versions only have a generic pagecontainershow where you have to dig a bit to see WHICH page was just shown.  Since I wanted to wait to load the contact list until the first time the user navigated to that screen, I implemented the code which is shown above in my example of code that the CFML parser refused to compile.  I also added this bit which sets a function reference into the data() object for my contact page.

$( '##contact' ).data( 'onPageLoad', function() {
	// First run
	if( !haveContactsBeenLoaded ) {
		loadContacts();
	}
});

This is a little convention I devised.  Basically, it allows me to set up an initialization method for any pages while only needing one generic listener on the pagecontainershow event that checks for an onPageLoad function and executes it if it exists.

Here is my loadContacts() method.  It will load the first 200 contacts randomly if a searchString is not passed in.  This is also called when the contacts page is loaded for the first time (the code directly above).

// Search for contacts. Loads all by default
// TODO: Pagination?
function loadContacts( searchString ) {
	// Default search string
	if( typeof( searchString ) == 'undefined' ) { searchString = ''; }
	
	console.log( 'Searching for contacts matching "' + searchString + '".' );
	
	// Show waitscreen
	$.mobile.loading( "show", { text: "Hold on a sec while we fetch your contacts", textVisible: true } );
	
	// Clear previous results
	var contactListEL = $( '##contactList' );
	contactListEL.html( '' );
	contactListEL.listview("refresh");
	
	// Actually search for the contacts
	var contacts = cfclient.contacts.find( searchString, [ 'displayName' ] );
	
	// I have a lot of E-mail-only contacts w/no name. Ignore them
	contacts = contacts.filter( function( element ) {
		return element.displayName != null;
	});
	
	// Grab the first 200 just to limit how much junk we show. 
	// Doing this before the sort just so we get a smattering of alphanetical representation
	if( contacts.length > 200 ) {
		contacts = contacts.splice( 0, 200 );					
	}
					
	// Sort by name
	contacts.sort( function( a, b ) {
		return ( a.displayName.toLowerCase() > b.displayName.toLowerCase() ) ? 1 : ( ( b.displayName.toLowerCase() > a.displayName.toLowerCase() ) ? -1 : 0  );
	});
					
	// Loop over and display
	contacts.each( function( contact, index ) { 
		contactListEL.append( 
			'<li><a href="##contactDetail" onClick="editContact( ' + contact.id + ' )">' + contact.displayName + '</a></li>'
		);					 
	} );
	
	// Refresh the UI element
	contactListEL.listview("refresh");
	
	// Hide the loader 
	$.mobile.loading('hide');
	haveContactsBeenLoaded = true;
		
}

The results are appended as list items to an unordered list that is formatted as a list view.  It is necessary to refresh the list view afterwards to apply the formatting.  You can type in a name to filter the list of contacts using the same method.  The data-autodividers="true" is a pretty cool attribute automatically adds alphabet dividers into my list.

When you click a name, I navigate to my detail page while loading the details for that into the form fields.  I won't show that code here.  It's pretty similar to the search function and you can check it out on GitHub.  The screen looks like this.  

 I had to make sure my "Done" button navigated "back" instead of directly to the contacts page.  Otherwise, hitting the back button after hitting "done" wouldn't do what you expected.  It would take you back to the details screen instead of the home page.  Making the "Done" button operate as a back button was as easy as adding the data-rel="back" attribute.

<a data-icon="back" data-rel="back" data-role="button" href="#">Done</a>

Endification

There's a lot more to the contacts API.  I haven't implemented the possibility of multiple numbers, E-mails, etc and I haven't even gotten into adding new contacts.  I'll come back to this one if I have time.  I want to make sure I get a chance to hit the other APIs too.

So, as usual, all my code along with the latest APK file is on GitHub.  I've tagged a new release.  Since I implemented two APIs I bumped the version clear to 0.4!


Want to read more in this series?  Pick one of the links below: