AIR Beta 3 Migration Tips

AIR Beta 3 is an exciting release that takes us one step closer to AIR 1.0. Unlike the myriad of changes introduced in the move from AIR Beta 1 to AIR Beta 2, the changes in AIR Beta 3 are far more subtle. While this stability indicates the growing maturity of the product, AIR Beta 3 is an update and will require some changes to your applications. You can read the release notes of course, but in the progress of updating my forty-plus AIR applications, here are the common changes that I encountered.

Application Descriptor File

The good news here is that most of the changes take place up front and are very easy to update. The XML attributes “version” and “id” used to be on the root “application” node, but have now gone solo and are nodes on their own. There also used to be a node called “title” that came before the “initialWindow” block. That node is now called “filename” which is used for the application filename and is required. Most of the other nodes are the same, or will work as is from AIR Beta 2.

<?xml version="1.0" encoding="utf-8" ?>
<application xmlns="http://ns.adobe.com/air/application/1.0.M6">

	<id>com.example.ExampleApplication</id>
	<name>Example Co. Example Application 1.0</name>
	<version>1.0</version>
	<filename>Example Application</filename>
	<description>This is a sample AIR application.</description>
	<copyright>(c) 2007 Example Co., Inc.</copyright>

	<initialWindow>
		<content>ExampleApplication.swf</content>
		<title>Example Application</title>
		<systemChrome>standard</systemChrome>
		<transparent>false</transparent>
		<visible>true</visible>
	</initialWindow>

</application>

Application Resource Directory

For security reasons, there were a number of changes introduced to the “application resource directory” in AIR Beta 2. One of those that you may have missed, especially during development with ADL, is that you cannot write to the resource directory. That directory is designed to contain only the core files that shipped with the application. This is really important if you’re shipping a database file or the likes with your application, as you’ll need to move it elsewhere for the “first run” of the application.

As far as the API goes, what was the File.applicationResourceDirectory property is now the File.applicationDirectory property. Also note that if you’re developing AIR applications with HTML, all this security goodness means that you likely use an IFRAME for most of your content. The sandbox bridge used to require that the “documentRoot” property had a value of “app-resource:/”. The value for AIR Beta 3 is simply “app:/” removing the redundancy of the “resource” term.

<iframe
	id="viewport"
	src="ui.html"
	sandboxRoot="http://SomeRemoteDomain.com/"
	documentRoot="app:/" />

HTML Control Moved to MX Package

Since most of my examples tend to cross liberally over the lines of JavaScript and Flash, I often found myself referring to the “window.htmlControl” property. The HTML proper has been moved over to the Flex SDK, which means it is now a part of the “MX” package and not included with the runtime itself. The good news is that there is a “window.htmlLoader” property that largely takes its place. Functionality such as the “stage” property, can now be found on the “window.htmlLoader” property from JavaScript.

function doSave()
{
	var png = null;
	var capture = null;
	var stream = new air.FileStream();
	var temp = air.File.applicationStorageDirectory.resolvePath( 'capture.png' );

	capture = new air.BitmapData( window.htmlLoader.stage.stageWidth,
		window.htmlLoader.stage.stageHeight );
	capture.draw( window.htmlLoader );

	// Encode image
	png = runtime.com.adobe.images.PNGEncoder.encode( capture );

	stream.open( temp, air.FileMode.WRITE );
	stream.writeBytes( png, 0, 0 );
	stream.close();
}

Creating a Native Window from HTML

When creating new native windows from JavaScript, I found it more effective to reach deeply into the Flash side of the house. The good news is that it is now much easier using the HTMLLoader.createRootWindow() method. You see that little “HTMLLoader” class part there? Well you know that there’s the new “window.htmlLoader” property, right? So I must be referring to that right? Nope! This one stumped me for a while, but the createRootWindow() is a static method and therefore does not require an instance of HTMLLoader to operate.

function doWindow()
{
	var init = new air.NativeWindowInitOptions();
	var bounds = null;
	var win = null;
	var login = air.File.applicationDirectory.resolvePath( 'login.html' );	

	bounds = new air.Rectangle( ( air.Capabilities.screenResolutionX - 325 ) / 2,
		( air.Capabilities.screenResolutionY - 145 ) / 2, 325, 145 );

	init.minimizable = false;
	init.maximizable = false;
	init.resizable = false;

	win = air.HTMLLoader.createRootWindow( true, init, false, bounds );
	win.load( new air.URLRequest( login.url ) );
}

XMLHttpRequest Limited in Classic Sandbox

This one is a real pain. Inside the classic sandbox the XMLHttpRequest object used to be capable of cross-domain requests. This is sadly no longer the case. By default, XMLHttpRequest is now limited to the originating domain for the document, or the domain specified in the “sandboxRoot” attribute in the application sandbox on the IFRAME. If you have constructive feedback on this change, I’d really like to hear it, so please leave a comment if you feel so compelled.

Update: Special thanks to Keeto O. who pointed out that you can change this behavior by setting the attribute “allowcrossdomainXMLHttpRequest” to “true” on the parent sandbox IFRAME (per the AIR FAQ).

Flash in HTML Now Supported

I think the header says it all - Flash content is finally supported inside the HTML engine! It’s true! Take a pass on by the hotly debated new Adobe web site to see for yourself with your own AIR application. What? You haven’t built your own little browser? Well fine then, here’s a simple one for you! If you get so excited about this that you want to send a screen capture to your friends, just drag the button labled “Drag” from the AIR application to the desktop.

Better than just Flash however, this is the latest and greatest version of the Player to include H.264 support for your high definition viewing, to include hardware acceleration. I have an example showing how to build a video player from JavaScript using the Flash Video class. This of course is now no longer necessary. If you’re a JavaScript developer looking to easily leverage Flash video, then take a look at the Flash-Ajax Video Component, which I’ll be covering in more detail in a future post.

There is a negative side effect to all this goodness however, namely that Flash support has been accomplished by including a complete version of the Flash Player itself, and not by using the latent one in the AIR runtime. Yes, the product team understands the dependency injection. Yes, the product team understands this increases the overall size of the runtime itself. Rest assured that the product team appreciates the potential problems by taking this approach, but after deep research and heated debate, they felt this was best for the long term.

Opening a Database Connection

This one is far more subtle, but you’ll run into it wherever you use the embedded SQLite database. In AIR Beta 2, the SQLConnection constructor took a boolean argument to specify if the connection was synchronous or asynchronous. In AIR Beta 3 the SQLConnection constructor no longer takes any arguments. Just like with file IO, you specify which mode you’re opening the database connection by calling the respective open method (i.e. SQLConnection.open() or SQLConnection.openAsync()).

One of the other subtle changes of the SQLConnection.open() methods (either one) is that the arguments have changed slightly. The second argument used to be a Boolean value indicating whether or not AIR should automatically create the database if it doesn’t exist. This has been replaced by various constants on the new SQLMode class.

The SQLMode constants are SQLMode.CREATE, SQLMode.READ and SQLMode.UPDATE. The SQLMode.CREATE option is what the old Boolean value used to represent, and specifies that the database should be created if it doesn’t exist and made available for updates. The SQLMode.READ option indicates that a database should be opened only for read operations. Last but not least is the SQLMode.UPDATE option which will expect an existing database to be opened for updates.

function doLoad()
{
	var file = air.File.applicationDirectory.resolvePath( 'crm.db' );

	db = new air.SQLConnection();
	db.addEventListener( air.SQLEvent.OPEN, doDbOpen );
	db.open( file, air.SQLMode.CREATE );
}

WebKit Drag and Drop

This one really turned into a time suck when I was porting over all my sample applications, as I had previously leveraged only the Flash drag and drop event handlers on HTMLControl. As mentioned previous, that control doesn’t exist and has been replaced by an HTMLLoader property. You can try to hook the old listeners to that property, and while you won’t get an error, you won’t get a drag and drop operation either. The way to accomplish this in AIR Beta 3 is to use the drag and drop hooks that WebKit itself provides.

In the browser world, I’ve found that most developers don’t use this Safari feature because a) it is generally a Safari feature and b) the documentation on the WebKit project site is atrociously lacking. I intend to cover this in more depth in a future blog post, but in the meantime I’ve attached a drag-and-drop example built by Dragos Georgita on the AIR product team. It covers most everything you’ll need to get the idea of the internals.

At a high level, if you are accepting drag content, then you add the onDragOver and onDrop event handlers to whatever elements you want to accept the operation. This is much better than my previous Flash implementation as it allows accepting a drag and drop operation on a per element basis (i.e. drag a photo to a DIV or IMG that represents a personalized frame). You can accept individual content types by preventing the default event behavior (i.e. e.preventDefault()) in the onDragOver event handler.

function doDrop( e )
{
	var elem = document.createElement( 'div' );

	elem.innerText = e.dataTransfer.getData( 'text/plain' );
	elem.className = 'drag';

	document.body.appendChild( elem );
}

function doOver( e )
{
	e.preventDefault();
}

If you are dragging content out of AIR, then you’ll first need to add the style “-khtml-user-drag” for the elements you want to handle drag and drop. The next step is to listen for the onDragStart event and handle it by adding the desired content to the event. If you’re going to use a custom image for the drag thumbnail you have two options. The first option is to point to an IMG element. If you want to use a BitmapData object, then you’ll ignore all this and still just use the NativeDragManager. Oh, and that has changed too - used to be simply DragManager.

function doStart( e )
{
	e.dataTransfer.setData( 'text/plain', 'testing' );
	e.dataTransfer.effectAllowed = 'copy';
	e.dataTransfer.setDragImage( dragImage, 24, 24 );
	e.stopPropagation();
}

This isn’t intended to be a replacement for either the documentation, or the release notes, so please be sure you read those thoroughly. These are simply the changes that I’ve found and wanted to share with the community. I’ve tried to put code wherever I could to help you get started, and cover everything pretty thoroughly. If you have additional questions, don’t hesitate to leave a comment and I’ll get an answer for you. In the meantime, enjoy AIR Beta 3.

WordPress database error: [Can't open file: 'wp_comments.MYI' (errno: 145)]
SELECT * FROM wp_comments WHERE comment_post_ID = '136' AND comment_approved = '1' ORDER BY comment_date

Leave a Reply