Showing posts with label ActionScript. Show all posts
Showing posts with label ActionScript. Show all posts

Monday, August 25, 2008

Flex TabNavigator Image Snapshot Icons


Example files below illustrate generating thumbnail snapshots of tabs in a Flex TabNavigator. The thumbnails are shows as tab icons.
See this flexcoders thread.

ImgSnapshot.mxml - Main Application File:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()">

<mx:Script>
<![CDATA[
import mx.events.IndexChangedEvent;
import mx.containers.TabNavigator;

private var tabNav:TabNavigator = new TabNavigator();

private function init():void
{
// tab1
var tabChild1:MyComponent = new MyComponent(0);
this.tabNav.addChild(tabChild1);

// tab2
var tabChild2:MyComponent = new MyComponent(1);
this.tabNav.addChild(tabChild2);

this.tabNav.percentHeight = 100;
this.tabNav.percentWidth = 100;
this.addChild(this.tabNav);
}
]]>
</mx:Script>

</mx:Application>

MyComponent Class - MyComponent.as:
package
{
import flash.events.Event;

import mx.charts.PieChart;
import mx.charts.series.PieSeries;
import mx.collections.ArrayCollection;
import mx.containers.HBox;
import mx.containers.TabNavigator;
import mx.core.Container;
import mx.events.FlexEvent;

public class MyComponent extends Container
{
private var box:HBox = new HBox();
private var chart:PieChart = new PieChart();
private var index:int;

public function MyComponent(indx:int)
{
super();

this.index = indx;
this.label = "Tab " + this.index;
this.percentHeight = 100;
this.percentWidth = 100;

this.addEventListener(FlexEvent.UPDATE_COMPLETE, updateComplete);
}

override protected function createChildren():void
{
super.createChildren();
this.box.addChild(this.getPie());
this.addChild(this.box);
}

override protected function updateDisplayList(
unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
this.box.setActualSize(unscaledWidth, unscaledHeight);
this.chart.setActualSize(unscaledWidth, unscaledHeight);
}

private function updateComplete(event:Event):void
{
var tabNav:TabNavigator = TabNavigator(this.parent);
if (this.width == 0 && this.height == 0) {
var selTab:MyComponent = MyComponent(tabNav.getChildAt(tabNav.selectedIndex));

this.width = selTab.width;
this.height = selTab.height;
this.updateDisplayList(selTab.width, selTab.height);

return;
}
if (this.icon == null)
this.icon = IconUtil.getIconClass(this, tabNav.getTabAt(this.index), 150, 100);
}

private function getPie():PieChart
{
chart.percentWidth = 100;
chart.percentHeight = 100;
chart.showDataTips = true;
chart.dataProvider = this.getChartDataProvider();

var series:PieSeries = new PieSeries();
series.nameField = "label";
series.field = "data";
series.filters = [];
chart.series = [series];

return chart;
}

private function getChartDataProvider():ArrayCollection
{
var arr:Array = [];
arr.push({label:"East", data:24});
arr.push({label:"West", data:32});
arr.push({label:"North", data:22});
arr.push({label:"South", data:11});
arr.push({label:"Texas", data:5});
return new ArrayCollection(arr);
}
}
}
IconUtil.as:
package
{
import flash.display.BitmapData;
import flash.events.Event;
import flash.geom.Matrix;
import flash.utils.Dictionary;

import mx.core.BitmapAsset;
import mx.core.UIComponent;
import mx.graphics.ImageSnapshot;

public class IconUtil extends BitmapAsset
{
private static var dictionary:Dictionary = new Dictionary(true);

public function IconUtil()
{
addEventListener(Event.ADDED, addedHandler, false, 0, true);
}

private function addedHandler(event:Event):void
{
if (this.parent == null)
return;

var value:Object = dictionary[this.parent];
if (value == null)
return;

this.bitmapData = value.src;
UIComponent(this.parent).invalidateSize();
}

public static function getIconClass(source:UIComponent, target:UIComponent,
width:int, height:int):Class
{
if (source.width <= 0 || source.height <= 0)
return null;

var scaleWidth:Number = width/source.width;
var scaleHeight:Number = height/source.height;
var mtrx:Matrix = new Matrix(scaleWidth, 0, 0, scaleHeight);
var bitmap:BitmapData = ImageSnapshot.captureBitmapData(source, mtrx);
dictionary[target] = {src:bitmap, w:width, h:height};

return IconUtil;
}
}
}

Tuesday, January 15, 2008

Enums in ActionScript 3

The introduction of Enums in Java 1.5 was a welcome addition. Prior to Java 1.5, one had to simulate Enums with some special patterns. In ActionScript 3 there is no native enum construct in the language. So we need to apply patterns similar to what we did in Java prior to 1.5. While similar, the pattern for ActionScript is a bit different.

Let's start with the basic Enum pattern in ActionScript:

package enumexample
{
public class Color
{
public var _value:String;

public function Color(val:String):void {
this._value = val;
}

public static const RED:Color = new Color("RED");
public static const BLUE:Color = new Color("BLUE");
public static const GREEN:Color = new Color("GREEN");
}
}


The issues with the above Enum implementation are:
  • The constructor is public. So one could do: var c:Color = new Color("FOO"); which violates the notion of the Color Enum constants being restricted to RED, GREEN or BLUE. ActionScript does not allow private constructors.
  • Color does not have a default no-arg constructor which is needed if an instance of Color is de-serialized by the Flash Player from data from the back-end e.g. when using LCDS's RemoteObject to invoke a Java method that returns an instance of Color (see this for LCDS Enum support). For default serialization based on property-value pairs, not only do we need a no-arg constructor but we also need to make _value public or provide a setter. Both these are undesirable because one could change an enum as follows: var c:Color = Color.RED; c._value = "MAROON";
  • If an Enum cannot guarantee uniqueness of enum constant instances, object comparisons based on == will fail. var c:Color = new Color("RED"); if (c == Color.RED); // false. ActionScript does not have the notion of operator overloading.
  • When a Color instance is de-serialized, an instance is first created using the default no-arg constructor and the _value property is then populated. There is no object substitution mechanism available in ActionScript like the readResolve() Java serialization mechanism. So created instances will fail the == comparison as Enum constant instance uniqueness cannot be guaranteed.

The solution I use in conjunction with LCDS RemoteObject is to have Color implement Externalizable and have readExternal() and writeExternal() methods read/write value for _value. In addition, the class is "locked" after the initial construction of the valid Enum constant instances - see the lock member in the Color class below. An exception is thrown when an attempt is made to construct an Enum instance after it is locked. However, since the de-serialization will construct an Enum instance with no args, the exception is not thrown if no args are specified to the constructor. Unfortunately there is no way to detect in the constructor that the instance is being created in the context of de-serialization. The _value member is made private. An equals() method is added which is the way to compare enum instances rather than using ==. However, this is just a guideline/convention and the use of == cannot be enforced. Here's what Color looks like finally - the best one can do given the limitations of the language.

package enumexample
{
[RemoteClass(alias="javaenumexample.Color")]
public class Color implements IExternalizable
{
private var _value:String;
private static var lock:Boolean=false;

public function Color(val:String=null):void {
if (lock && val != null)
throw new Error("Cannot instantiate Enum");

this._value = val;
}

public function get value():String {
return this._value;
}

public static const RED:Color = new Color("RED");
public static const BLUE:Color = new Color("BLUE");
public static const GREEN:Color = new Color("GREEN");

{
lock = true;
}

public function equals(enum:Color):Boolean {
if (enum == null)
return false;

return enum.value == this._value;
}

public function readExternal(input:IDataInput):void {
this._value = input.readUTF();
}

public function writeExternal(output:IDataOutput):void {
output.writeUTF(this._value);
}
}
}

Monday, December 3, 2007

"Tear-off" from online Flash App to offline AIR App

Say you have an interactive, connected, online, Browser-based Flex application that is used for scenario-based analysis. Users of this application need to be able to "tear-off" a scenario and work on it offline in an AIR application. How does one achieve this and re-use the same code written for the online application? The following outlines an approach:

  • Online Flash Client requests metadata and data from the server needed to render scenario view
  • ActionScript metadata and data objects are delivered to client from Java versions on server using FDS remoting (AMF3 over HTTP)
  • Client creates a byte array of these objects in some pre-defined sequence and sends this to the server using URLLoader
  • Server writes these bytes into a temporary file on server disk
  • Client uses FileReference::download() to download this file to the client machine

Once downloaded, the AIR application (containing the exact same rendering code as the online application) reads this data file to de-serialize the byte arrays as ActionScript metadata and data objects, and renders the scenario view.

Works, but ugly and convoluted! Got an alternative?