Vampire Weekend

Vampire Weekend performed a pretty fun show at the Bluebird last night. People are going crazy over this band and now I see why. Tickets were originally going for $12 which must have sold out immediately cause up until last week people were willing to pay $100 for a pair.

Horrible blurry photo of Vampire Weekend

I still can’t figure out how to best describe their music , but whatever it is, it makes for a great live performance. I definitely recommend going if you can manage to find tickets still.

Blake’s got a new face!

Hacking width and height properties into Flex’s CSS model

As much as I love working with the Flex layout model, in many ways it feels inferior to its HTML cousin. I’ll take Flex’s container components over HTML tables and divs any day, but I’ve always been disappointed with the fairly weak CSS model found in Flex. From an architectural level the framework decisions makes sense and keeps internal logic fast and clean, but from an implementation standpoint, it becomes an annoying roadblock. Heres a quick example of the problem.

<mx:Style>
	.navItem {
		vertical-align: middle;
		corner-radius: 5;
		border-style: solid;
		border-color: #F3CB19;
		background-color: #A87500;
	}
</mx:Style>
<mx:HBox styleName="navItem" height="30"/>
<mx:HBox styleName="navItem" height="30"/>
<mx:HBox styleName="navItem" height="30"/>

In this case, since height is a class property, it must be defined on the HBox instance and can’t be moved into CSS. It makes the code less manageable and adds restrictions to changing themes at runtime.

In order to define class properties with CSS we need to break some of the framework rules. Since we can’t have class metadata defining a value as both a CSS and a class property, we need to leave all metadata as-is, and ‘foward’ the css definitions on to the class. In practical terms what this means is that changing the height in css will change the height on the component, but changing the height directly on the component wont update the css definition. The code below illustrates how this plays out inside a custom component.

package com.craftymind.controls
{
	import mx.containers.HBox;
	import mx.styles.StyleManager;

	public class extHBox extends HBox
	{
		override public function styleChanged(styleProp:String):void{
			super.styleChanged(styleProp);
			if(!styleProp || styleProp == "styleName"){ //if runtime css swap or direct change of stylename
				var classSelector:Object = StyleManager.getStyleDeclaration("." + styleName);
				if(classSelector != null){
					applyProperties(classSelector, ["width", "height", "percentWidth", "percentHeight", "x", "y", "visible"]);
				}
			}
		}
		private function applyProperties(styleObj:Object, arr:Array):void{
			for each (var item:String in arr){
				var prop:Object = styleObj.getStyle(item);
				if(prop != null) this[item] = prop;
			}
		}
	}
}

When the HBox detects that the styleName or runtime definition has changed, it scans the classSelector for width, height, x, y and visible properties to be applied. You can add whatever properties you want in there including text labels or image sources depending on the component being extended. I haven’t fully tested it so I’m not sure if type selectors work, and I know that calling obj.setStyle(”height”, 10); definitely wont work, but your better off calling obj.height = 10 in that case anyway.

Heres a complex menu system controlled completely with css including y positioning, height changes, and header visibility.


View Source

We now have separation of content from style thats alot closer to what HTML offers, and as a bonus, all property-based databinding continues to work on the values passed through CSS.

<cm:extHBox styleName="header">
	<mx:Label text="Header Text"/>
</cm:extHBox>
<cm:extHBox id="nav" styleName="navContainer">
	<cm:extHBox styleName="navItem">
		<mx:Label text="Nav Item 1"/>
	</cm:extHBox>
	<cm:extHBox styleName="navItem">
		<mx:Label text="Nav Item 2"/>
	</cm:extHBox>
	<cm:extHBox styleName="navItem">
		<mx:Label text="Nav Item 3"/>
	</cm:extHBox>
</cm:extHBox>
<mx:Button label="Switch CSS" click="switchCSS()" y="{nav.y+nav.height+2}"/>
.header {
	background-color: #000000;
	border-style: solid;
	border-color: #FFFFFF;
	border-thickness: 2;
	color: #FFFFFF;
	x: 0;
	y: 0;
	percent-width: 100;
	height: 20;
	visible: true;
}
.navContainer {
	background-color: #000000;
	border-style: solid;
	border-color: #FFFFFF;
	border-thickness: 2;
	vertical-align: bottom;
	horizontal-align: left;
	padding-left: 3;
	x: 0;
	y: 20;
	percent-width: 100;
	height: 50;
}
.navItem {
	vertical-align: middle;
	corner-radius: 5;
	border-style: solid;
	border-sides: "left top right";
	border-color: #FFFFFF;
	border-thickness: 2;
	background-color: #000000;
	padding-left: 3;
	padding-right: 3;
	color: #FFFFFF;
	height: 30;
}

Image smoothing in Flex

The Flex Image control doesn’t expose Bitmap smoothing by default but can easily be added in through subclassing the component. Smoothing is a nice feature for removing jaggies from images that have been scaled either up or down, and in practice hasn’t caused any noticable cpu hangups to do the post processing. I’m writing up a patch to submit to the Flex Open Source initiative, but in the meantime, heres a quick and dirty hack to enable it in the Flex 2 and Flex 3 SDK.

Create a new MXML component and name it SmoothImage.mxml, then add the following code.

<?xml version="1.0" encoding="utf-8"?>
<mx:Image xmlns:mx="http://www.adobe.com/2006/mxml">
	<mx:Script>
		<![CDATA[
			import flash.display.Bitmap;
			override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void{
				super.updateDisplayList(unscaledWidth, unscaledHeight);
				if(content is Bitmap){
					var bmp:Bitmap = Bitmap(content);
					if(bmp && bmp.smoothing == false){
						bmp.smoothing = true;
					}
				}
			}
		]]>
	</mx:Script>
</mx:Image>

Now just use <local:SmoothImage source=”myimage.jpg”/> the same way you’d use a normal image component and you’re all set. This code will take care of smoothing both dynamically loaded images as well as embedded images in one simple script. It also handles broken images gracefully. Heres a quick sample showing the affects of scaling the Google logo with and without smoothing.

SmoothImage test case


And the code used to create this…

<!-- Top Images -->
<mx:Image source="{googlelogo}" width="60" height="25"/>
<mx:Image source="{googlelogo}" width="200" height="90"/>
<mx:Image source="{googlelogo}" width="400" height="180"/>

<!-- Bottom Images -->
<local:SmoothImage source="{googlelogo}" width="60" height="25"/>
<local:SmoothImage source="{googlelogo}" width="200" height="90"/>
<local:SmoothImage source="{googlelogo}" width="400" height="180"/>

Saving class data to disk in AIR

For eBay Desktop we wrote a simple utility for reading and writing classes directly to disk. Using this method we’re able to load the users existing data from disk, or if its unavailable, create a new class with all the defaults.

First up is the utility class AIRUtils

package com.effectiveui.util
{
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;

	public class AIRUtils
	{
		public static function readFileObject(fil:File):Object{
			var amf:Object;
			if(fil.exists){
				var stream:FileStream = new FileStream();
				stream.open(fil, FileMode.READ);
				amf = stream.readObject();
				stream.close();
			}
			return amf;
		}
		public static function writeFileObject(fil:File, obj:Object):Object{
			var stream:FileStream = new FileStream();
			stream.open(fil, FileMode.WRITE);
			stream.writeObject(obj);
			stream.close();
			return obj;
		}
	}
}

These 2 static methods act as simple helper wrappers to read and write classes out to disk.

The example class we want to save below looks very similar to the type of class we use inside eBay Desktop.

package com.ebay.model
{
	import com.ebay.model.SearchPreferences;

	[Bindable]
	[RemoteClass(alias="com.ebay.model.UserPreferencesModel")]
	public class UserPreferencesModel
	{
		public static const STANDARD:String = "standard";
		public static const SKINNY:String = "skinny";

		public var logoutOnClose:Boolean = false;
		public var viewState = STANDARD;
		public var globalSearchFilters:SearchPreferences = new SearchPreferences();

		private var _maxHistoryItems:uint = 1000;
		public function set maxHistoryItems(max:uint):void{
		   _maxHistoryItems = max;
		}
		public function get maxHistoryItems():uint{
		   return _maxHistoryItems;
		}
	}
}

In order to write a class out to disk and read it back in, you have to add RemoteClass metadata to it. It doesn’t matter what the value is it just needs to be unique to the application. This provides Flash with an identifier for linking amf data to class definitions when its loaded back in. It doesn’t matter what value you put in the RemoteClass tag, but the best practice is to use the class name of the model you’re saving. Additionally, in this example we’d need to make sure both UserPreferencesModel and SearchPreferences model have RemoteClass metadata, since caching the UserPrefencesModel will automatically attempt to cache the SearchPreferences as a child model.
Only public variables will be cached to disk, so both the static values and the private var will be thrown out, but the public getter/setters will be cached.

Interacting with the cached data using AIRUtils looks like this.

private var fileRef:File = File.applicationStorageDirectory.resolvePath("UserPreferences.dat");
private var userPrefs:UserPreferencesModel;

private function loadPrefs():void{
	userPrefs = AIRUtils.readFileObject(fileRef) as UserPreferencesModel || new UserPreferencesModel();
}
private function savePrefs():void{
	AIRUtils.writeFileObject(fileRef, userPrefs);
}

When loadPrefs() is called, the cached class is read in and cast as a UserPreferencesModel. If no file exists in the users cache, we end up creating a new class and the defaults defined in the class will be used. This ensures we have something to work with, all within 1 tidy line of code. To save out the file we just pass the file reference and the class instance to AIRUtils.writeFileObject() and we’re done.

New site design

Part of my “blog more” plan has always been to finalize a design for the site which I’m rolling out right now. Textpattern had been giving me some problems with post formatting and performance, so thats been tossed as well and everything’s been moved over to Wordpress. So far I’ve been much happier with the features, although I’m not a big fan of having to litter my root directory with dozens of ‘wp-*’ files.

eBay Desktop released!

After more then a year and a half of development, we’ve finally been able to release eBay Desktop 1.0. After starting as a simple prototype and slowly growing into a viable product, its been a tremendous effort to create. Having lead the development of the app, I’m relieved to see this beast finally released and I’m pretty excited to see how useful it becomes to the eBay user community. I’m hoping to spend the next months worth of posts discussing some of the Flex and AIR techniques used in the app.

Actionscript 3.0 prototyping, viable but costly

Back in the Flash 6 – Flash 8 world, I was a big fan of prototyping, mostly because of the limitations of MovieClip inheritance. I haven’t needed to use it at all in Actionscript 3 but I was still curious about how to accomplish it nowadays. Here’s the example I was able to create in Flex.

Sprite.prototype.spriteHelper = new SpriteHelper();
Sprite.prototype.rotateTo = function(deg:int):void{
	var sh:SpriteHelper = this["spriteHelper"];
	sh.target = this;
	sh.rotateTo(deg);
}
package
{
	import flash.display.Sprite;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	public class SpriteHelper
	{
		public var _degreeTo:int = 0;
		public var _degCount:int = 0;
		public var _degTimer:Timer = null;
		public var target:Sprite;
		public function rotateTo(deg:int):void{
			_degreeTo = deg;
			_degCount = 0;
			_degTimer = new Timer(10);
			_degTimer.addEventListener(TimerEvent.TIMER, doAnimateRotation);
			_degTimer.start();
		}
		public function doAnimateRotation(event:TimerEvent):void{
			_degCount++;
			if(_degCount <= 40){
				target.rotation = _degreeTo * (_degCount/40);
			}else{
				_degTimer.removeEventListener(TimerEvent.TIMER, doAnimateRotation);
				_degTimer = null;
			}
		}
	}
}
<mx:Box id="myContainer" backgroundColor="#FF0000" width="100" height="100" right="40" y="0" creationComplete="myContainer['rotateTo'](50)"/>

You’ll notice 2 things in the implementation here. Since Sprite is a statically defined class, accessing the prototype chain requires bracket syntax ala myContainer[‘rotateTo’](50), and secondly, most of the functionality of rotateTo has been moved into a separate class. The reason for the latter point is because accessing the prototype chain is costly, as it has to recurse through the inheritance chain until it finds the class with the property. As a result I’ve limited the prototype lookups to just spriteHelper and I move all the functionality into there.

Another thing I found in the docs is that every class creates a prototype chain by default, so merely adding prototype definitions has no effect on the creation of that object.

RegExp pattern for creating file safe names

Different operating systems restrict specific characters from being used in file and folder names. The following code snippet will allow you to trim those special characters when creating File references in AIR.

var fileSafePattern:RegExp = new RegExp('["\\\\ *?<>|:]', 'gi');
var fileSafeName:String = myString.replace(fileSafePattern, "_");
var myFile:File = File.applicationStorageDirectory.resolvePath(fileSafeName);

eBay Desktop demo

I’ve been doing a few presentations on Apollo the past few months centered around accessing eBays web services. While the app shown has remained just a “prototype”, I’m finally able to talk openly about the product and the fact that we’re moving into an official eBay application with a beta program coming soon.

The beta signup is public now so if your interested in getting involved early on and getting your feedback heard, you can sign up over at http://www.sandimasproject.com . I’m planning on watching the results of the beta very closely and trying to interact with a lot of users to get their opinions. We’ve got an amazing opportunity to rebuild eBays interface from the ground up and want to make sure every aspect of using eBay gets improved in the process. So feel free to send me your thoughts through this blog or on the beta site.

If you want to see a demo of the application, I gave a presentation recorded at Adobes ApolloCamp back in March. http://video.onflex.org/2007/03/26/apollo-camp-ebay-and-effectiveui-and-artemis-sean-christmann/

Find as you type sorting on large record sets

One of the sorting methods I’ve been working on this week is implementing a fast find as you type method for close to 30,000 records in AS3. The feature works exactly like iTunes library search, just with a lot more data (unless you’re rocking 1800 hours worth of music). The only rule with implementing this kind of feature is that it has to feel instant to the end user, otherwise we’ll have to switch to a Find-after-pressing-search method.

Setup
The records are stored locally as a flat xml structure.

<entry id="20081" name="Antiques" fullName="Antiques" />
<entry id="37903" name="Antiquities (Classical, Amer.)" fullName="Antiques|Antiquities (Classical, Amer.)" />
<entry id="37905" name="Egyptian" fullName="Antiques|Antiquities (Classical, Amer.)|Egyptian" />
<entry id="37906" name="Greek" fullName="Antiques|Antiquities (Classical, Amer.)|Greek" />
<entry id="37907" name="Roman" fullName="Antiques|Antiquities (Classical, Amer.)|Roman" />

The data is then loaded in as an XMLList and bound to a datagrid. From there, searches should match keywords in the fullName attribute and the datagrid will show only the entries matched.

Method #1 - Iteration
There are 2 ways to iterate the search, either use AS3’s built in E4X expressions to generate unique xml lists, or use collection filtering on the existing list. The first method flexes the power of E4X expressions, but takes a hit on swapping out the datagrid with a new dataprovider, the second works on the existing dataprovider but lacks any indexing power xml might provide.
E4X expression filtering

filteredXMLList = rawXMLList.(@fullName.indexOf(searchTerm) > -1);

Filter Time: 200 ms
Collection Filtering

filteredXMLListCollection = new XMLListCollection(rawXMLList);
filteredXMLListCollection.filterFunction = collectionFilter;
private function collectionFilter(item:XML):Boolean{
	return item.@fullName.indexOf(searchTerm) > -1;
}

Filter Time: 300-200 ms
Using E4X expressions is more powerful then filtering, but the total time for both methods still feels sluggish in the UI.

Method #2 - Pattern matching full text.
Again, there are 2 approaches to pattern matching strings in AS3, RegExp and String.indexOf(). The idea is to apply pattern matching on the raw string representation of the xml to create a new xml string containing only the nodes matched. The new string is then cast back into an XMLList to be placed in the datagrid.
RexExp matching

var pattern:RegExp = new RegExp("<[^<]*" + searchTerm + "[^>]*>", "ig");
var result:Array = rawXMLString.match(pattern);
filteredXMLList = new XMLList(result.join(""));

Filter Time: 31000-14000 ms (31-14 seconds)

IndexOf matching

private function searchStringFor(source:String, term:String):String{
	var lowersource:String = source.toLowerCase();
	var lowerterm:String = term.toLowerCase();
	var i:Number = 0;
	var out:String = "";
	var pos:Number = 0;
	while(i > -1){
		pos = lowersource.indexOf(lowerterm, i);
		if(pos > -1){
			out += source.substring(source.lastIndexOf("<", pos), source.indexOf(">", pos)+1);
			i = pos+term.length;
		}else{
			i = pos;
		}
	}
	return out;
}
filteredXMLList = new XMLList(searchStringFor(rawXMLString, searchTerm));

Filter Time: 200-50 ms

While the RegExp and indexOf are performing the same match, RegExp clearly has a long way to go before being viable in AS3. Even using simply pattern matches with String.search() during Method #1 above was 3 times slower then an indexOf during iteration. IndexOf in Method #2 on the other hand has gotten us down to a 200-50 ms filter time. 50 ms is decent but 200 ms still feels slow.

Final Method - Key search + reference copy
At this point, indexOf searches on the full string block appear to be the fastest way of filtering, and it provides the most room for refinement. Two aspects are contributing to the slowdown at this point: 1. unneccessarily searching the full xml structure, 2. constructing a new xml string and casting it to XMLList after each search. We can cover both issues by initially loading the xml string into an XMLList and iterating that list to generate a second string containing just the fullName field and the index that fullName belongs to in the original XMLList

rawXMLList = XMLList(urlLoader.data);
var count:int = 0;
for each(var node:XML in rawXMLList){
	fullNameIndex += node.@fullName.toLowerCase() + "<" + count + ">";
	count++;
}

This will generate a string looking like

Antiques<0>
Antiques|Antiquities (Classical, Amer.)<1>
Antiques|Antiquities (Classical, Amer.)|Egyptian<2>
Antiques|Antiquities (Classical, Amer.)|Greek<3>
Antiques|Antiquities (Classical, Amer.)|Roman<4>

Now when we do an indexOf search on the above string, we can look ahead to the index inside the < > tags to find which reference in the original rawXMLList to use. Those references are then set into a new XMLList to display.

private function searchStringInAttribute(list:XMLList, attributelist:String, term:String):XMLList{
	var output:XMLList = new XMLList();
	var i:Number = 0;
	var pos:Number = 0;
	var index:int = 0;
	var count:int = 0;
	while(i > -1){
		pos = attributelist.indexOf(term, i);
		if(pos > -1){
			index = int( attributelist.substring(attributelist.indexOf("<", pos)+1, attributelist.indexOf(">", pos)) );
			output[count] = list[index];
			count++;
			i = pos+term.length;
		}else{
			i = pos;
		}
	}
	return output;
}
filteredXMLList = searchStringInAttribute(rawXMLList, fullNameIndex, searchTerm.toLowerCase());

Filter Time: 70-10 ms

These filtering times are excellent and feel extremely smooth. I was a bit surprised to see that performing an indexOf search on a large block of text like this, then looking for the XMLList key in string form, was faster then performing a search inside an array iteration and mapping the array key to the XMLList key. The final source is listed below.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
	<mx:Script>
		<![CDATA[
			import flash.utils.getTimer;

			[Bindable]
			private var filteredXMLList:XMLList;
			private var rawXMLList:XMLList;
			private var fullNameIndex:String;
			private var urlLoader:URLLoader;

			private function init():void{
				urlLoader = new URLLoader();
				urlLoader.addEventListener(Event.COMPLETE, entriesLoaded);
				urlLoader.load(new URLRequest("entryList.xml"));
			}
			private function entriesLoaded(e:Event):void{
 				rawXMLList = new XMLList(urlLoader.data);
 				var count:int = 0;
 				var node:XML;
 				for each(node in rawXMLList){
 					fullNameIndex += node.@fullName.toLowerCase()+"<"+count+">";
 					count++;
 				}
 				applyFilter();
 				urlLoader.removeEventListener(Event.COMPLETE, entriesLoaded);
 				urlLoader = null;
			}
			private function searchStringInAttribute(list:XMLList, attributelist:String, term:String):XMLList{
				var output:XMLList = new XMLList();
				var i:Number = 0;
				var pos:Number = 0;
				var index:int = 0;
				var count:int = 0;
				while(i > -1){
					pos = attributelist.indexOf(term, i);
					if(pos > -1){
						index = int( attributelist.substring(attributelist.indexOf("<", pos)+1, attributelist.indexOf(">", pos)) );
						output[count] = list[index];
						count++;
						i = pos+term.length;
					}else{
						i = pos;
					}
				}
				return output;
			}
			private function applyFilter():void{
				if(filtertext.text.length < 2){
					filteredXMLList = rawXMLList;
				}else{
					var searchTerm:String = filtertext.text;
					var s:int = getTimer();
					filteredXMLList = searchStringInAttribute(rawXMLList, fullNameIndex, filtertext.text.toLowerCase());
					trace("filter time: "+(getTimer()-s));
				}
			}
		]]>
	</mx:Script>
	<mx:VBox width="100%" height="100%">
		<mx:TextInput id="filtertext" width="200" keyUp="applyFilter()"/>
		<mx:DataGrid id="category_list" width="100%" height="100%" dataProvider="{filteredXMLList}">
			<mx:columns>
				<mx:DataGridColumn id="dateCol" dataField="@id" headerText="Category ID" width="100"/>
				<mx:DataGridColumn id="titleCol" dataField="@name" headerText="Category Name"/>
				<mx:DataGridColumn id="idCol" dataField="@fullName" headerText="Category Path"/>
			</mx:columns>
		</mx:DataGrid>
	</mx:VBox>
</mx:Application>