Things I Code

9Feb/120

Managing the iOS Keyboard

It seems every month or so I have a need to specifically configure the iOS keyboard displayed by the UIWebView component.  I spend several minutes using Google before I always end up at the correct page on the Apple site.  This post is an attempt to capture the core pieces of information I am generally after.

The <input> tag supports various parameters to control the type of keyboard displayed.

To display a telephone keypad, an email keyboard, or a URL keyboard, use the telemail, or url keywords for the type attribute on an input element, respectively. To display a numeric keyboard, set the value of the pattern attribute to "[0-9]*" or "\d*".

Auto-correct and Auto-capitalisation can be turned on and off with the attributes autocorrect and autocapitalize.

<input type="text" autocorrect="off" autocapitalize="on">



6Feb/120

Creating a pre-populated Vector

I came across a syntax for creating a pre-populated Vector object in ActionScript that I hadn't seen before.  It's all documented in the ActionScript reference, but I thought i'd make a note of it nevertheless.

Typically you create a new Vector object like this.

var v:Vector.<T> = new Vector.<T>();

Where you want to pre-populate the Vector at the time you create it, you can use this alternate syntax

var v:Vector.<String> = new <String>["item1","item2",];

Note the square brackets.  And that trailing comma is optional.

Just a neat bit of syntax that saves a bit of verbosity when setting up Vector objects.

6Feb/120

Launching the Control Panel/System Preferences using AIR

The AIR3 NativeProcess class allows you to run and monitor any arbitrary executable from your application.  A recent cross-platform application I was working on had the requirement to allow the user to adjust the system date/time by bringing up the appropriate Control Panel/System Preferences panel.

After a little investigation into how the Windows Control Panel and Mac OS System Preference panels work, i created the following code snippet to allow the launching of the System Date & Time settings on both Windows and Mac.

var startupInfo : NativeProcessStartupInfo =
	new NativeProcessStartupInfo();

if (Capabilities.os.toLowerCase().indexOf("win") != -1) {
	startupInfo.executable =
		new File('C:\\WINDOWS\\SYSTEM32\\CMD.EXE');
	startupInfo.arguments =
		new <String>['/c', 'C:\\WINDOWS\\SYSTEM32\\TIMEDATE.CPL'];
}
else if (Capabilities.os.toLowerCase().indexOf("mac") != -1) {
	startupInfo.executable =
		new File('/usr/bin/open');
	startupInfo.arguments =
		new <String>['/System/Library/PreferencePanes/DateAndTime.prefPane'];
}

np = new NativeProcess();
np.start(startupInfo);

This code determines what operating system the application is running, and then

  • On Windows, launches the Control Panel app (a .cpl file) using CMD.EXE.
  • On Mac, launches the System Preferences panel (a .prefPane application) using the open command

The exact same pattern can then be used to open any Control Panel/System Preferences panel.

 

 

30Jan/120

Comparing optional XML attributes using E4X

Dealing with XML using E4X is like a breath of fresh air compared with the old school methods of traversing using children() calls and all manner of other evils.

However, one particular oddity i came across was dealing with group of nodes that had an optional attributes in the source XML data.  For example,

var myData : XML =
<tasks>
    <task id="1"/>
    <task id="2" enabled="true" />
    <task id="3" enabled="false" />
</tasks>;

What i wanted to get a list of <task> nodes where enabled == "true".  I assumed the following would work

myData..task.(@enabled == 'true')

But how wrong I was.  This threw a run-time error.

ReferenceError: Error #1065: Variable enabled is not defined.  

A bit of searching around turned up the fact the @ attribute identifier and the attribute() function operate slightly differently.  The defined behaviour of attribute() when accessing a non-existent attribute is to return an empty XMLList while the @ attribute identifier throws an exception.

With this nugget of knowledge, simply refactoring to use the attribute() function got it working

myData..task.(attribute('enabled') == 'true')

Sure, slightly more verbose, but at least it works like you'd expect it to.

 

 

28Nov/110

Using Google Maps reliably in PhoneGap

A challenge I had recently when implementing Google Maps into a PhoneGap based app was the requirement for the app to continue working even when there wasn't a network connection.

To test this, I put my phone in Airplane mode and started my app. I immediately found the app didn't start up properly as it couldn't load the Google Maps Javascript API.

Typically when implementing Google Maps you add a script block similar to the following in your <head> section

<script type="text/javascript" src="http://maps.google.com/maps/api/js?v=3.7&sensor=true"></script>

Without a working network connection when the app started up, I was faced with having to identify and handle the non-availability of Google Maps to ensure the rest of the app continued to work sans the mapping functionality.

To begin, I switched from loading the Google Maps API from HTML to using JavaScript.  I called the following code from my app's setup() function that ran after the PhoneGap deviceready event.

var script = document.createElement("script");
script.src = "http://maps.google.com/maps/api/js?v=3.7&sensor=true";
script.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(script);

Google Maps also provides a callback hook to allow you be notified when the API is fully loaded. To implement this, you add the callback parameter to script.src

var script = document.createElement("script");
script.src = "http://maps.google.com/maps/api/js?v=3.7&sensor=true&callback=googleMapsReady";
script.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(script);

and then create a function to handle the callback.

function googleMapsReady() {
    alert('Google Maps is Ready');
}

With this in place, you can then track the availability of Google Maps and start to modify the behaviour of your app accordingly

var googleMapsState = "";

function setup() {
    ....
    loadGoogleMaps();
    ....
}

function loadGoogleMaps() {
    googleMapsState = "loading";

    var script = document.createElement("script");
    script.src = "http://maps.google.com/maps/api/js?v=3.7&sensor=true&callback=googleMapsReady";
    script.type = "text/javascript";
    document.getElementsByTagName("head")[0].appendChild(script);
}

function googleMapsReady() {
    googleMapsState = "ready";
}

Next there is a need to handle the situation where the Google Maps API couldn't be loaded.  This can be captured by adding an event listener to handle the error on the script element.  In the loadGoogleMaps() function, you add

script.addEventListener("error", function(e) {
    googleMapsState = "error";
}, false);

Finally there's the maybe theoretical edge case where the script is loaded successfully but the Google Maps callback function isn't called.  Perhaps the connection got interrupted half way through, or something went awry with your cellular carrier's network.  Never say never on a wireless connection.  To handle this, you can listen to the script element's load event and then set a time out for the callback handler to complete.

script.addEventListener("load", function(e) {
    setTimeout(function() {
        if (googleMapsState == "loading") googleMapsState = "error";
    }, 5000);
}, false);

If the callback handler hasn't executed within 5 seconds of the Google Maps API being loaded, then assume something went wrong.

With this now in place, i created a guard function that I can call before I attempt to use Google Maps in the app

function checkGoogleMapsAvailability() {
    var connectionState = navigator.network.connection.type;
    if (connectionState == Connection.NONE || connectionState == Connection.UNKNOWN) {
        return "No network connection available";
    }

    if (googleMapsState == "" || googleMapsState == "error") {
        return "Maps are not currently available";
    }

    if (googleMapsState == "loading") {
        return "Maps are loading, try again aoon";
    }

    return "";
}

This function also makes use of the PhoneGap navigator.network API to check that the device has network connection.  This is useful another check to help bullet-proof the app in case the users network availability changes while they are using the app.

Now prior to my app using Google Maps, my guard function handles checking its availability and returning a message to display to the user

var availabilityErrorMessage = checkGoogleMapsAvailability();

if (availabilityErrorMessage != "") {
    alert(availabilityErrorMessage);
    return;
}

var mymap = new google.maps.Map(document.getElementById("my-map"));

One final thing that PhoneGap provides is event notifications for when the device network connection changes.  What this allows the app to do it monitor for when a network connection becomes available and then load the Google Maps API on demand.  Add an event listener for the online event to the PhoneGap deviceready handler

document.addEventListener("online", function(e) {
    if (googleMapsState == "" || googleMapsState == "error") {
        loadGoogleMaps()
    }
}, false);

All in all, I ended up with a much more robust app that can gracefully handle the available or not of the Google Maps API over the wireless network.

Now if you just want some code you can easily copy and paste, here you go.

var googleMapsState = "";

document.addEventListener("deviceready", setup, false);

function setup() {
    loadGoogleMaps();

    document.addEventListener("online", function(e) {
        if (googleMapsState == "" || googleMapsState == "error") {
            loadGoogleMaps();
        }
    }, false);
}

function loadGoogleMaps() {
    googleMapsState = "loading";

    var script = document.createElement("script");
    script.src = "http://maps.google.com/maps/api/js?v=3.7&sensor=true&callback=googleMapsReady";
    script.type = "text/javascript";

    script.addEventListener("error", function(e) {
	    googleMapsState = "error";
	}, false);

    script.addEventListener("load", function(e) {
        setTimeout(function() {
            if (googleMapsState == "loading") googleMapsState = "error";
        }, 5000);
    }, false);

    document.getElementsByTagName("head")[0].appendChild(script);
}

function googleMapsReady() {
    googleMapsState = "ready";
}

function checkGoogleMapsAvailability() {
    var connectionState = navigator.network.connection.type;
    if (connectionState == Connection.NONE || connectionState == Connection.UNKNOWN) {
        return "No network connection available";
    }

    if (googleMapsState == "" || googleMapsState == "error") {
        return "Maps are not currently available";
    }

    if (googleMapsState == "loading") {
        return "Maps are loading, try again aoon";
    }

    return "";
}
26Nov/110

Using the PhoneGap SplashScreen plugin

One of the core plugins that PhoneGap provides for iOS is SplashScreen, that gives apps the ability to control when the launch image is displayed.

For apps that have a slow start up time, like pretty much any Sencha Touch app, this is really useful as it allows you to retain the launch image while the Sencha Touch interface is initialising and avoid the whitescreen that would otherwise display for several seconds.

Finding the documentation for the SplashScreen plugin wasn't easy and many examples I found were out of date. These are the steps I needed to get it working on my PhoneGap 1.2 based app.

First, edit the PhoneGap.plist file in XCode. Ensure that AutoHideSplashScreen is set to NO.  I prefer that ShowSplashScreenSpinner is set to NO but that is up to you.

With that change your app now needs to explicitly remove the Splash Screen before your user interface is displayed. The JavaScript to do this is

new SplashScreen().hide();

Based on the Sencha Touch sample MVC app, your mainLaunch() method would look someting like

mainLaunch: function() {
    if (!device || !this.launched) {return;}
    console.log('mainLaunch');
    new SplashScreen().hide();
}

This now gives your users a much nicer experience when starting your app.

25Nov/110

Compiling Java code with ColdFusion

As part of my recent session at CFObjective(ANZ) + Flex, i demonstrated using the MaxMind GeoIP API to get the geographic location of the user's IP address.

MaxMind provide their GeoIP API in several different languages, and of course i grabbed the Java version. A prebuilt JAR isn't supplied, so you're left to compile the java source on your own.

I decided to try the dynamic compilation available in JavaLoader. It is meant to compile .java source files on the fly and then load the compiled JAR and make them available to ColdFusion.

It turns out it works exactly as advertised. I was actually quite surprised at how simple the whole process was.

Here are the steps I took if you want to try it out yourself

1. Download JavaLoader and extract it into a folder

2. Download latest GeoIPJava API  (v 1.2.5 right now) and extract into the same folder

3. Download the GeoLite Country and GeoLite City data files and place them in the same folder

4. You should end up with a folder structure similar to the following


5. Create an index.cfm to bring it all together

<cfscript>
javaloader = createObject("component", "javaloader.JavaLoader").init(
    sourceDirectories=[expandPath("GeoIPJava-1.2.5/source/")],
    trustedSource=true
);

dbFile = expandPath("GeoIP.dat");
geoip = javaloader.create("com.maxmind.geoip.LookupService").init(dbFile);
country = geoip.getCountry(CGI.REMOTE_ADDR);

writeOutput("Country Code: " & country.code & " Country Code: " & country.name);
</cfscript>

And that's it!  Running the example should compile the GeoIPJava API, create the Lookup Service using the GeoIP Country datafile and then return the country information for CGI.REMOTE_ADDR.  Of course if you're accessing this on your local machine, a lookup against localhost won't provide anything meaningful, so just substitute a public IP address in there.

To get City level information, point the GeoIP API at the GeoCityLite.dat datafile and make the call geoip.getLocation(CGI.REMOTE_ADDR) - just be aware that this will return undefined if you try and look up localhost.

JavaLoader is dead! (not entirely)

It turns out JavaLoader makes compiling and executing Java code directly within your ColdFusion applications ridiculously easy.  While Adobe are proclaiming the death of JavaLoader (see slide 51) in the next release of ColdFusion, it turns out their implementation will not support dynamic compilation.

While this isn't likely to ever be a concern for the vast majority of developers, the ease at which JavaLoader does allow it doesn't really make it much of a downside for the rest of us.

 

24Nov/110

Marco Polo – Navigating for Profit

I recently presented a session at CFObjective (ANZ) 2011 entitled Marco Polo - Navigating for Profit.

I'm making the slides from the presentation available for any who's interested.

Marco Polo - Presentation (7.6MB)

And the session blurb.

Geolocation can add tremendous value the user experience of many apps and websites by subtly improving the quality of information displayed to users. While you may not want to exclude anyone from Aruba from your checkout form, why do rest of us have to scroll past it to select Australia from your drop down list?

This session will explore the ins and outs of using Geolocation to enhance your users experience with more geographically relevant information. You will take some of the work away from your users by giving them the information they are more likely interested in and create a more polished experience for them.

We’ll work through the various geolocation options available, including:

  • Client-side geolocation features available in HTML5
  • Server-side geolocation options
  • Adding server-side geolocation to ColdFusion
  • User says no? Combining client-side and server-side geolocation information
  • Using Geolocation services in Flex and Flex mobile
  • The Ultimate Geolocation mashup? HTML5, Server-side geolocation and Flex UI
30Sep/110

Embedding fonts in Flash

Something that has caused me trouble time and time again is embedding fonts within AIR Actionscript apps.  While your app may display custom fonts fine in your local simulator, once you get it onto a device you may find that no text is displayed, or is not displayed with expected font.

As this has caught me out again, I'm documenting the process as much for my own benefit as any one else who comes across problem.

My platform is Flash Builder 4.5 on Mac OS X Lion.

Initially I tried embedding the font using the [Embed(...)] ActionScript syntax, however I could never get it to work reliably, so instead I've taken to packaging the Font as a SWC in Flash Pro and then linking my Flash Builder project against the SWC.

There's a great article on ADC covering how to embed fonts into Flash Pro CS4, and for the most part the process is exactly the same in CS5.5.

Embedding Fonts by Peter deHaan
http://www.adobe.com/devnet/flash/quickstart/embedding_fonts.html

 

However, once i'd added the font to the Library and set the Symbol and Linkage properties there was a bit of trial and error to get the Font into my Flash Builder project.  Here's the next set of steps I needed:

1. Export the SWC from Flash Pro

Set the Flash Project to Publish as a SWC and set the Output file to a location in your Flash Builder project.

 

2. Reference the SWC in Flash Builder 

In your Flash Builder project, add the SWC to the ActionScript Build Path > Library path properties page.

 

3. Register the Font in your Application

In your app's start up, you need to register your imported font with Flash.

Font.registerFont(classname);

Where classname is the Class Linkage property set in Flash Professional.  If you included a package name in your classname (i.e. package.classname) then don't forget add an import for it as well.

 

4. Set yourself up a default TextFormat

As i found i was creating many TextField objects with identical defaulTextFormat properties, I create a static TextFormat object that defined a common style for me.

var defaultTextFormat : TextFormat = new TextFormat("fontname", fontsize, color);

The Gotcha when creating a TextFormat is to ensure that the fontname value is the actual font name stored in the underlying Font.  This isn't necessarily the name that is displayed to you in Flash Pro, or even shown by Font Book (in Mac OSX) by default.

The value needed is the Full name as shown in the Font properties.

As an example, to use Arial Black Regular, you'd have to use the font name "Arial Black" - which is what you'd expect.

 



var arialBlackTextFormat : TextFormat = new TextFormat("Arial Black", 16, 0x000000);

However, with other fonts, this might not be the same.  For example to use the Coolvetica Regular, you have to use the font name "CoolveticaRg-Regular"

 



var coolveticaTextFormat : TextFormat = new TextFormat("CoolveticaRg-Regular", 16, 0x000000);

Using the wrong font name has been the cause of many hours of angst for me.

 

5. Create your TextField

Finally, you're ready to create and use your TextField.  Ensure you set it up with the correct embedFonts and defaultTextFormat properties.

var label : TextField = new TextField();
label.embedFonts = true;
label.defaultTextFormat = yourTextFormatObject;

If the Flash gods are smiling on you, you should find your fonts embedding correctly across platforms and devices.

 

 

27Sep/115

Building AirDBC

If you'd like to see the pieces that make up AirDBC, this guide will show you how to get started.

I would first like to thank Sean Fujiwara for the excellent tutorial he did on building an AIR3 native extension http://blog.magicalhobo.com/2011/09/12/air-3-native-extension-imageprocessor/.  His example got me further along the road of realising AirDBC than would have been possible otherwise.

You'll also notice that the AirDBC steps are very similar.

Requirements

  • Visual C++ 2010 Express
  • AIR3 SDK for Windows from Adobe Labs
  • Flash Builder 4 (any OS)
  • .NET 4.0 Framework

My build platform in a Mac, but I am using a Windows XP Parallels VM to do the Window specific features.  To actually complete the build I required both the Windows and Mac downloads of the AIR3 SDK.  If you're working entirely within Windows, then obviously you won't need the Mac stuff.

Flex 4.5.1 + AIR 3.0

As this is early days for Native Extensions, to build the Demo app requires that you overlay the AIR3 SDK over an existing Flex 4.51 SDK.  Do this on the Platform that you have Flash Builder running on.

Follow the instructions on this Forums posting

http://forums.adobe.com/message/3908256#3908256

The demo app is built with the SDK with the name "Flex 4.5.1 + AIR 3.0" - if you make your's different, you'll just need to update the Flex Builder projects later on.  Ensure you add your newly created SDK into Flash Builder.

The Code

All the code is on GitHub.  Pull it all from my repository https://github.com/philhaeusler/AirDBC

There are two placeholder files in the Native Extension that are copyright by Adobe. Once you get the project locally, you have to replace these placeholder files from the AIR3 SDK.

You can run the windows batch file AirDBC/AirDBCExtension/setup.bat which will copy the files from the AIR SDK. This script does the following:

  • Copy the file AIR_SDK/include/FlashRuntimeExtensions.h to AirDBC/AirDBCExtension/AirDBCExtension/FlashRuntimeExtensions.h
  • Copy the file AIR_SDK/lib/win/FlashRuntimeExtensions.lib to AirDBC/AirDBCExtension/AirDBCExtension/FlashRuntimeExtensions.lib

Once you've done that you should be ready to start building.

Building the Native Extension DLL for Windows

Open the Visual C++ 2010 Express project AirDBC/AirDBCExtension/AirDBCExtension.sln

Build the DLL (F7)

You should now find the file AirDBC/AirDBCExtension/Debug/AirDBCExtension.dll built successfully

Building the Flash Native Extension

Using Flash Builder import the AirDBC/AirDBC project.  This can be on Windows or Mac

Ensure the Project Properties > Flex Library Compiler > SDK is set to your freshly built "Flex 4.5.1 + AIR 3.0"  SDK

Build the SWC

Now outside of Flash Builder open the AirDBC.swc with a .zip editor, and extract library.swf to the same folder as the AirDBC.swc

From the command line, build the Native Extension by running AirDBC/AirDBC/bin/package.bat (windows) or AirDBC/AirDBC/bin/package.sh (mac)

You should now find the file AirDBC/AirDBC/bin/AirDBC.ane built successfully

Building the Demo App

Import the AirDBC/AirDBCDemo project into Flash Builder. This can be on Windows or Mac.

Again ensure the Project Properties > Flex Library Compiler > SDK is set to your freshly built "Flex 4.5.1 + AIR 3.0"  SDK

Build the SWF

From the command line, now build the native installer.  This step can only be done on Windows.  Run AirDBC/AirDBCDemo/bin-debug/package.bat.

You should now find the AirDBC/AirDBCDemo/bin-debug/InstallAirDBCDemo.exe

Run the installer and start the app.  Click to Run the sample query.  4 records are returned.  Feel free to modify the underlying demo.mdb database in Access and then querying it using the Demo app.

You're done!