In my last post I played with the Media API to add sounds effects to the Roll The Ball game and made an Adobe CF Soundboard.  Today I'll be showing my work with the Geolocation API.  I wish I had some more time to do something more useful, but the CF mobile contest is drawing to a close tonight and I this will be the last feature I have time to put in.

Breaking Up Is Hard To Do

Before I get to that, I re-touched on some code organization.  If you've been following my series, you saw the troubles I had attempting to create any sort of framework or convention to organize my code a bit.  I had just left all my HTML and CFClient code in the index.cfm, but as the sample app grew, it started driving me crazy.  Short of using JavaScript's document.write() or using some JS templating solution I found no easy way to break up my HTML code into smaller files or to abstract things such as common headers or footers away for code re-use.  

CFClient also allows CFInclude tags, but ONLY with static values-- nothing dynamic.  I ended up breaking up  my CFClient code into a number of .cfm files in an includes/ directory and just including them all by name.  Primitive, but effective I suppose.

include "/includes/notificationAPI.cfm";
include "/includes/accelerometerAPI.cfm";
include "/includes/connectionAPI.cfm";
include "/includes/contactAPI.cfm";
include "/includes/eventAPI.cfm";
include "/includes/mediaAPI.cfm";
include "/includes/rollTheBallGame.cfm";
include "/includes/geolocationAPI.cfm";

Also, after pouring back through the allowed CFClient code, I did find a note that mentioned:

Non-<cfclient> custom tags cannot be called from caller CFMs of <cfclient>. Also, a client-side custom tag cannot have server-side ColdFusion tags outside the <cfclient> tag. This is true for client-side included-CFMs too.

There's still some bugginess there.  Try cfincluding an empty file and you'll get a null pointer exception on compilation.

java.lang.NullPointerException
    at coldfusion.compiler.ParseException.add_escapes(ParseException.java:297)
    at coldfusion.compiler.ParseException.getTokenText(ParseException.java:112)
    at coldfusion.rds.PGBuildServlet$JSBuildOperator.groupFilesAndErrorDescriptions(PGBuildServlet.java:478)
    at coldfusion.rds.PGBuildServlet$JSBuildOperator.processCmd(PGBuildServlet.java:360)
    at coldfusion.rds.PGBuildServlet.processCmd(PGBuildServlet.java:110)
    at coldfusion.rds.RdsServlet.doPost(RdsServlet.java:114)

Oh Where Is My Hairbrush

The Geolocation API is pretty straightforward.  It comes with a few options out of the box that you can set in the same manner as the accelerometer API.  

  • enableHighAccuracy
  • timeOut
  • maximumAge

To enable it, one would normally check the "Geolocation" box under their project's PhoneGap features, but due to compile errors regarding the Media API, I had two switch to a hard-coded config.xml.  I added the PhoneGap feature for Geolocation by adding the following line to my config.xml:

<feature  name="http://api.phonegap.com/1.0/geolocation"  />

Options are set into the options object and passed back in.  Note, I made the mistake of copying some code from the docs which didn't have the correct casing "enablehighaccuracy" instead of "enableHighAccuracy" which didn't work at first.  These options just pass through to PhoneGap, so I got to the point where I would just skip straight to those docs.

var geoOptions = cfclient.geolocation.getOptions();
geoOptions.enableHighAccuracy = $( '##highaccuracy' ).prop('checked');
cfclient.geolocation.setOptions( geoOptions );

With the Geolocation API, you can do a one-time query of the location, or you can register a callback to be notified anytime the location changes.  The data that comes back is easy to use, though I had null values for things like speed and altitude.  Perhaps because I was sitting at my desk?  If I did much more of this, I think I'd look into a templating solution so I wasn't just concatenating strings of HTML together.

var geoData = cfclient.geolocation.getCurrentPosition();
$( '##geolocationData' ).html( 
	'<b>Lat:</b> ' + geoData.coords.latitude + '<br>
	<b>Long:</b> ' + geoData.coords.longitude'
);

The checkbox enables high accuracy mode which uses your phone's GPS.  Otherwise you just get an approximate location based on the mobile network.

It's All In The Syntax

I've been writing a lot of JavaScript in my CFClient tags.  In fact, even though it's technically CFML I suppose, and run through a CFML parser, I've just been thinking of it as JavaScript in my header because it's simpler that way.  You can see how I have jQuery selectors mixed right in there.  One issue I ran into  is when you want to type something that is valid JavaScript, but it is NOT valid CFML.  For example, in my code above I had originally coded this:

geoOptions.enablehighaccuracy = $( '##highaccuracy' ).is( ':checked' );

An exception has occurred in the processing of CFClient code : Invalid CFML construct found on line 14 at column 75.

Wut??  I was taken aback by the sheer helpfulness of the message, but once the shock wore off, it occurred to me that "is" is a CFML keyword even though I'm in script and I always use the script operators.  When in Rome...  Luckily, jQuery has another easy syntax that I was able to use.

geoOptions.enablehighaccuracy = $( '##highaccuracy' ).prop('checked');

I also found what I believe to be a bug in the Geolocation API (and yes, I'll be logging this).  At first I had actually forgotten to save my config.xml file so the Geolocation API wasn't enabled.  Instead of gettinga proper error message, the following message was thrown:

Uncaught ReferenceError: PositionError is not defined 

It appears the error handling has its own errors.  A quick glance at the code showed it was trying to build an access denied error.  

Randomness

I also mentioned a random error I had been seeing where  my app would not initialize.  Instead, a JavaScript error would show in the GapDebug console and the PhoneGap libraries would fail to load.  This would render my app useless as none of the CFClient goodness would operate.  This has continued to happen-- like a lot.  I swear I was getting it 1 out of 3 times I would open my app.  Closing and re-opening usually fixed it but this continues to worry me.   The error was not always the same, but I captured a couple of the stacks.

Uncaught ReferenceError: WCamera is not defined
_DNFICameraManager cfclient_pg_plugin.js:6
__dnfiReadyCallback cfclient_pg_plugin.js:13
runCallBacks dnfimain.js:23
init2 dnfimain.js:11
checkLoadCompletion dnfimain.js:12

Uncaught ReferenceError: WAudio is not defined 
_DNFIAudioManager cfclient_pg_plugin.js:9
__dnfiReadyCallback cfclient_pg_plugin.js:14
runCallBacks dnfimain.js:23
init2 dnfimain.js:11
checkLoadCompletion dnfimain.js:12

The errors always seemed to reference an API I hadn't added to my app.  

This next one was particularly interesting. I've  been using PhoneGap's build server to create my builds via CF Builder and so far it's been one of the truly painless parts of the process (other than the inability to automated it).  Something happened and I don't know what it is, but I now get the following error message EVERY TIME I try to create a build:

I had gotten this twice before.  Once when I tried to actually compile a second project, and once when I changed the name of the app.  However, I had done neither of those when it started happening every time.  Literally the only way I was able to get the Geolocation code working was to delete my entire project from the PhoneGap build server between every compile and let it recreate itself again.  I have no clue what caused this to start happening, but I probably would have played a bit more with Geolocation if it hadn't been for this.  It kind of pushed the build process over the edge for me.  

I did Google for a while and found that the unique key for an app is the app ID, which is shown on the build server site as well as in the PhoneGap status view in CFBuilder and in some of the messages in the console.  However  I can't figure out how or where the app ID is assigned.  Of course, for normal PhoneGap users there's a config.json file where this gets set.  I searched my entire hard drive and turned up no such file.  I think this is another good reason for CFBuilder to expose a bit more of the underlying build process to me since it was sort of a blackbox sort of brick wall for me.  

The "Finished" Product

I'm pleased with the amount of stuff I was able to play with, though I'm a little disappointed to have run out of time.  Of course, not waiting until a couple weeks ago to start would have given me more time :)

There's a bunch more things that honestly are probably a better representation of what CFClient is good for that I haven't even touched. 

  • Client-side CFCs.  I find this particular interesting because although the prototypal nature of JavaScript's OO is bearable, the horrid hoops one must jump through to make methods and properties public, private, or protected is not.   Depending on how well client-side CFCs are implemented, they might actually be really handy
  • Remote proxies for server-side CFCs.  I assume this works the same as the CFAjaxproxy stuff, which is one of the only "front end" tags I actually used and found useful in CFML.
  • Nice client detection libraries (Part of modernizr.com)
  • Client-side Custom tags (might help with my organization issues)
  • Client side database queries (quite possibly one of the most compelling uses of CFML since cfquery is so darn nice
  • Local Storage
  • Camera API (I was kind of scared of this one after the issues I had in the Expense Tracker sample app)
  • Compass API
  • File API
  • Splashscreen API
  • Custom PhoneGap plugins (Like Couchbase Lite)

So much to play with, so little time.  I'd like to say I'll continue playing CFClient, but to be honest, I've spent over 40 hours in the last two weeks on this project and it's taken quite a chunk out of my billable time.  If I win, I'll be compensated for a bit of that, if not I'll have had a fun, expensive experiment :)  I will gladly accept any pull requests for sure from anyone who wants to contribute to this community sampler app.

I might post some overview thoughts on CFClient, but for the most part I'll let my readers make their own decisions on whether it looks like something that will help them make mobile apps.  I will happily answer questions though.  I'll be cutting a new 1.0 release of my code on GitHub to officially submit.  As usual, all the Git tags that correspond with each blog entry can be found here:

And the final release that matches the code talked about in this blog can be found here, including a pre-compiled APK file for you to install on your Android device:


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