Categories
Articles

ZEND AMF (Action Message Format) Minimalist Example Using RemoteObject and MySQL

I always liked and appreciated the AMFPHP implementation of Adobe Action Message Format. I have seen it implemented in some robust applications and it held its own. Among its best features is the browser which is very handy for testing. However Adobe has now gotten involved with Zend AMF and this is a quick shot at putting it together using the RemoteObject class.

The first example I looked at is from Lee Brimelow’s Introduction to ZendAMF at gotoAndLearn.com. This is a great example and worth the watch to get a rounded view of using Zend AMF. He uses NetConnection and Responder classes in Flash CS4. You can use his example in Flex and Air.

Adobe Flash Builder 4 and Flex 4 - Essential Training
Learn More About Flex 4

Eventually I put Zend AMF to practical use by revamping DefineIt.com to use it along with the Parsley framework.

Flash CS4 does not have a native RemoteObject class, so that leads us to using Flex.

Download files
You can build this with the free Flex SDK by using the code in the src folder. This example was built with Flex 4.

The following uses the code from the Flex example, but other than the Application and WindowedApplication tags the code is the same.

Application Class – ZendAMFRemoteObjectGetTable_Flex
Rather than use a services-config.xml file linked to the compiler line, I choose to include the channel information in the MXML. More on this method is detailed by Chris Callendar’s post Using Flex and AMFPHP without a services-config.xml file.

<?xml version="1.0" encoding="utf-8"?>
<!--
Demonstrates use of the RemoteObject to communicate with Zend AMF.
<p>Author: Lon Hosford https://www.lonhosford.com 908 996 3773</p>
-->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
	<fx:Declarations>
		<!-- Alternative to services-config.xml added to the compiler option services -->
		<s:ChannelSet id = "zend_amf_channel_set">
			<s:channels>
				<s:AMFChannel uri="{GATEWAY_URL}"/>
			</s:channels>
		</s:ChannelSet>
		<!-- MembershipService RemoteObject -->
		<s:RemoteObject	id="members_ro"
						destination="zend-amf"
						source="MembershipService"
						channelSet="{zend_amf_channel_set}"
						showBusyCursor="true"
						fault="membersError(event)">
			<s:method name="getAllMembers" result="getAllMembersResult(event)"/>
		</s:RemoteObject>
	</fx:Declarations>

[ad name=”Google Adsense”]
On line 33 you need to set the GATEWAY_URL to match your configuration. You might want to use a default file name such as index.php for the gateway script so you only need a path to the folder.

	<fx:Script>
		<!&#91;CDATA&#91;
			import mx.controls.Alert;
			import mx.events.FlexEvent;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			// &#91;ADD YOUR HTTP URL TO THE GATEWAY PHP SCRIPT&#93;
			private const GATEWAY_URL:String = "http://YOUR_DOMAIN/PATH_TO_GATEWAY SCRIPT/";
			/**
			 * Member value object. Not used in this example. Included for information only.
			 * Use this to convert the data received. In this example the DataGrid dataprovider
			 * property converted the incoming amf array to an ArrayCollection and we mapped
			 * using the DataGridColumn dataField property.
			 * */
			private var memberData:MemberData;
			&#91;Bindable&#93;
			private var lang_title:String = "Minimalist Zend AMF Example Using RemoteObject";
			/**
			 * Invoke RemoteObject members_ro getAllMembers method.
			 * */
			protected function getAllMembers():void
			{
				members_ro.getAllMembers();
			}
			/**
			 * Empties the member_dg DataGrid.
			 * */
			protected function clearDataGrid():void
			{
				member_dg.dataProvider = {};
			}
			/**
			 * RemoteObject members_ro ResultEvent handler for the remote getAllMembers method.
			 * <p>Data arrives as an array and dataProvider property converts to ArrayCollection.
			 * The member_dg DataGrid contains DataGridColumn to match the expected field using
			 * the dataField property.</p>
			 * */
			protected function getAllMembersResult(e:ResultEvent):void
			{
				member_dg.dataProvider = e.result; // ResultEvent result property is an array
			}
			/**
			 * RemoteObject members_ro default FaultEvent handler.
			 * */
			protected function membersError(e:FaultEvent):void
			{
				Alert.show(e.toString());
			}
		&#93;&#93;>
	</fx:Script>

[ad name=”Google Adsense”]
This is the UI. The member_dg DataGrid contains DataGridColumn to match the expected fields in the MemberData object using the dataField property. This is a one to one relationship, however you may find times where you need to construct your own fields such as a concatenation of first and last name and thus populate the DataGrid with your own collection object.

	<s:VGroup horizontalAlign="center" width="100%" paddingTop="25">
		<s:Label text="{lang_title}" fontSize="20"/>

		<mx:DataGrid  id="member_dg"  height="100">
			<mx:columns>
				<mx:DataGridColumn headerText="Member Key" dataField="memberKey" width = "100"/>
				<mx:DataGridColumn headerText="First Name" dataField="firstName"/>
				<mx:DataGridColumn headerText="Last Name" dataField="lastName"/>
				<mx:DataGridColumn headerText="Email Address" dataField="emailAddress" width="200"/>
			</mx:columns>
		</mx:DataGrid>
		<s:HGroup>
			<s:Button label="Get Members" click="{getAllMembers();}"/>
			<s:Button label="Clear" click="{clearDataGrid();}"/>
		</s:HGroup>
	</s:VGroup>

</s:Application>

MemberData Value Object Class for Actionscript
This is the value object on the client side to define the field names for a member object. This is mapped to the server side on line 37 of the Zend AMF gateway script.

package
{
	/**
	 * Value object defining the member data
	 * */
	[RemoteClass(alias="MemberData")]
	[Bindable]
	public class MemberData
	{
		public var memberKey:uint;
		public var firstName:String;
		public var lastName:String;
		public var emailAddress:String;
	}
}

Zend AMF Gateway PHP Script
This is the gateway program for the Zend Amf. This is the file that is referenced on line 13 of the MXML file. In this example it was named index.php.

<?php
/**
*  Sample Zend AMF gateway
*  @return Endpoint &#91;Zend Amf Endpoint&#93;
* */

// Configurable values
// Debugging values
$debug = true;                             // Debugging status
if ($debug)
{
	// Report all errors, warnings, interoperability and compatibility
	error_reporting(E_ALL|E_STRICT);
	// Show errors with output
	ini_set("display_errors", "on");
}
else
{
	error_reporting(0);
	ini_set("display_errors", "off");
}
// Add the Zend AMF installation folder to the include path.
// In this example the frameworks folder is a sibling folder to
// this application folder. The frameworks folder contains the Zend
// folder that is extracted from http://framework.zend.com/download/amf
ini_set("include_path", ini_get("include_path") . PATH_SEPARATOR . "..\\frameworks" );

// Instantiate the Zend Amf server
require_once 'Zend/Amf/Server.php';
$server = new Zend_Amf_Server();

// Register your service classes
require_once 'MembershipService.php';
$server->setClass("MembershipService");

//Map ActionScript value objects to the PHP value objects.
$server->setClassMap("MemberData", "MemberData");

// Return the handle.
echo ($server->handle());

?>

[ad name=”Google Adsense”]
MembershipService Class
This is the service class that contains remote methods. Generally a the database and business logic is delegated to another API you write. In this case they are all together for simplicity.

<?php
/**
*	Service class exposing the methods to deal with membership.
*   This example includes business logic for simplicity of study.
*/
require_once 'MemberData.php';
class MembershipService
{
	public function MembershipService()
	{
		// Connect to MySql database.
		// Supply your own MySQL access values.
		// These are defaults when running on your own private computer.
		mysql_connect("localhost", "root", "");
		// Select the database.
		// Supply your own database name.
		mysql_select_db("test");
	}
	/**
	*	Get all members and all fields.
	*/
	public function getAllMembers()
	{
		// Array of MemberData objects.
		$members = array();
		// Selecting all fields and all records from table.
		// Supply your own table name and optionally your own SQL statement.
		$result = mysql_query("SELECT * FROM zend_amf_members");
		// Assuming mysql_query success. Slog through records.
		while ($row = mysql_fetch_assoc($result))
		{
			// Create a MemberData value object and populate.
			$member = new MemberData();
			$member->memberKey = $row["memberKey"];
			$member->firstName = $row["firstName"];
			$member->lastName = $row["lastName"];
			$member->emailAddress = $row["emailAddress"];
			array_push($members, $member);
		}
		// Return the members array to client.
		return $members;
	}
}
?>

MemberData Value Object Class for PHP
This is the value object on the server side to define the field names for a member object. This is mapped to the client side on line 37 of the Zend AMF gateway script.

<?php
/**
 * Value object defining the member data
 * */
class MemberData
{
  public $memberKey;	// uint
  public $firstName;	// String
  public $lastName;		// String
  public $emailAddress;	// String
}
?>

SQL To Create Testing Table
The PHP script uses zend_amf_members for the table and this is the SQL to create that table. In this example the database was called test.

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

--
-- Database: `test`
--

-- --------------------------------------------------------

--
-- Table structure for table `zend_amf_members`
--

CREATE TABLE IF NOT EXISTS `zend_amf_members` (
  `memberKey` int(10) unsigned NOT NULL auto_increment,
  `firstName` varchar(30) NOT NULL default '',
  `lastName` varchar(30) NOT NULL default '',
  `emailAddress` varchar(50) NOT NULL default '',
  PRIMARY KEY  (`memberKey`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Zend Amf Examples' AUTO_INCREMENT=7 ;

--
-- Dumping data for table `zend_amf_members`
--

INSERT INTO `zend_amf_members` (`memberKey`, `firstName`, `lastName`, `emailAddress`) VALUES
(1, 'Donald', 'Duck', 'quacker@pond.com'),
(2, 'Daffy', 'Duck', 'daft_2x@farm.org'),
(3, 'Elmer', 'Fudd', 'elmer.fudd@hunters.net'),
(4, 'Bugs', 'Bunny', 'whats_up_doc@underground.org'),
(5, 'Yosemite', 'Sam', 'varmint_chaser@forest.com'),
(6, 'Wile', 'Coyote', 'ceo@acme.com');
Categories
Articles

Adobe AIR NativeProcess Silent Print PDF with BioPDF’s Acrobat Wrapper

By Lon (Alonzo) Hosford
A current Adobe Air Windows OS project I am developing requires printing a Adobe PDF file without user interaction or at the minimum pressing a simple print button without seeing a print dialog window. This is often called silent printing.

Adobe Reader prior to version 8 allowed silent printing from the command line. You find references throughout the internet AcroRd32.exe /N /T PdfFile PrinterName [PrinterDriver[PrinterPort]] However for security this was changed to require the user to finish the printing. If you are developing a kiosk as I am, we want to avoid users having to know how to use printer dialogs or Adobe Reader menus.

[ad name=”Google Adsense”]

Javascript Injection
In researching the alternatives I found the ability to insert Javascript into the PDF file you want to print. This requires Adobe Acrobat Pro and a manual effort of a highly technical nature. I tried this and for a once in while PDF file it is acceptable, but not suited for dynamically third party software generated PDF files or PDF files from multiple sources. Plus it still would require AIR showing the HTMLLoader wrapping the Adobe Reader. You can review this alternative in Adobe Cookbooks.

Other alternatives were command line implementations offered by PDF software companies. These would allow using the NativeProcess in Adobe AIR to execute the printing without user intervention or allow designing a simple user interface like a big print button.

PDFPrint
A decent command line solution was PDFPrint from VeryPDF. Price is $199 (US) per installation. The trial version seemed to work well cobbled by a watermark and page count limitation, but the price forced me to press on.

printto.exe
I came across printto.exe from BioPDF. The printto.exe program uses the default application set by the Windows OS which is Acrobat Reader for PDF. This actually works but it does leaves Acrobat Reader open. I did not try it with other Acrobat Reader clones. Still you can use it, works with any file and it is free free.

GhostScript
Another choice is GhostScript. This works well once you select a “device”. You need to install it and run from the installation directory. I put it on a Windows XP machine and the command line was "C:\Program Files\gs\gs8.71\bin\gswin32c.exe" "@printtestgscmdline.txt" The argument @printtestgscmdline.txt is a way to feed the arguments from a file. The arguments I used are -sDEVICE="printer-device-id" -dNOPAUSE "full-path-to-pdf/your.pdf" -c quit. To get the valid printer device id you can get a list by starting ghostscript interactive mode and typing devicenames ==. I passed on this as I could not get the silencing arguments to work if you copied the installation directory executable files to another directory or machine. I needed something more portable. Also I am not sure of the PDF currency of the software.

Acrobat Wrapper version 2.0.0.23
The one I settled on that is free for non-commercial use. The program file name is acrowrap.exe and called Acrobat Wrapper version 2.0.0.23. You can download and use it without any watermarks or page limits. For commercial use you need to buy PDF Writer at $29 (US) per user with price cuts starting with 2 users; but you do not have to install that. Acrobat Wrapper downloads and installs on its own. Once installed you can take the acrowrap.exe from the installation directory and place it in any folder or computer and it happily runs from the command line. Thus for our commercial use we will buy licenses of PDF Writer for each copy of acrowrap.exe we install on our kiosks. The one drawback is when the printing is delayed the Adobe Reader window appears in a ghostly fashion but not accessible by the user.

You folks at BioPDF should make Acrobat Wrapper a product and simplify so that you do not have to install to get the executable. Also if you can keep Acrobat Reader minimized, that would be sweet.

[ad name=”Google Adsense”]

Using Acrobat Wrapper
The following is a demonstration of how to use Acrobat Wrapper with a NativeProcess Adobe Air application. In this example the user will hit the print button. If your file is generated or downloaded automatically for the user, you can go directly to printing without further user interaction.

I added some options like full screen and always in front for a kiosk type of installation. See code lines 29 and 30 respectively for settings and 46 to 54 for the implementation.

Create an Air project in Flex Builder and paste the code below into the main application mxml.

You need to download and install Acrobat Wrapper on any Windows computer. Then create a folder in your Flex application under the “src” folder and name it the name “native_apps” shown on code line 19 or provide your own folder name. Copy to this folder the acrowrap.exe file from the installation directory that Acrobat Wrapper placed it. For example on a Windows XP computer it was C:\Program Files\bioPDF\Acrobat Wrapper.

Add a sub folder named “in” or a name of your choice on code line 24. The “in” folder will hold your PDF file named on code line 23.

Testing Hints
In testing you will find that if it fails there is no feedback from acrowrap.exe we can pickup from the exit event handler. The exit code is 0 when it prints or fails to print. Thus the best debugging is to create a batch file and mimic what is happening in the AIR application until you find the solution. For example double back slashing and having the “/t” typed as “\t” were problems I encountered.

Also you need Acrobat Reader installed.

Download Flex Builder 4 Project File

<?xml version="1.0" encoding="utf-8"?>
<!--
    Purpose: Demonstrate NativeProcess printing PDF with arcowrap from BioPDF
	Author: Lon Hosford www.lonhosford.com 908 996 3773
	Date: August 12, 2010

-->
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
					   xmlns:s="library://ns.adobe.com/flex/spark" 
					   xmlns:mx="library://ns.adobe.com/flex/mx"
					   height = "500"
					   applicationComplete="applicationCompleteHandler(event)"
					   >
	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.events.FlexEvent;
			import mx.managers.CursorManager;
			private const NATIVE_APP_REL_PATH:String = "native_apps"; 	// Relative to applicationDirectory
			private const PDF_PRINT_APP_FILE_NAME:String = "acrowrap.exe"; 
			private const PDF_PRINT_APP_SWITCH:String = "/t"; 			// Required switch for acrowrap.exe
																		// to close Adobe Reader.
			private const PDF_FILE_NAME:String = "readme.pdf";			// Const for demo only. Var is ok.
			private const PDF_FILE_REL_PATH:String = "in";				// Relative to applicationDirectory
			private const PRINTER_NAME:String = "";						// Blank will use default printer
																		// Network printer double backslash
																		// Ex:\\\\ServerName\\PrinterName
		
			private var displayFullScreen:Boolean = true;				// Full screen state
			private var displayAlwaysInFront:Boolean = true;			// Always in front state
			
			// Language localization
			private var lang_Attention:String = "Attention";
			private var lang_NativeNotSupported:String = "NativeProcess not supported. Flash Version: ";
			private var lang_IsWindowsOSOnly:String = " is a Windows OS only program.";
			[Bindable]
			private var lang_PrintPDFBtnLabel:String = "Print PDF";
			[Bindable]
			private var lang_ClearBtnLabel:String = "Clear";
			[Bindable]
			private var lang_ConsoleLabel:String = "Console:";
			protected function applicationCompleteHandler(event:FlexEvent):void
			{
				console(className + ".applicationCompleteHandler(...)");
				// Display full screen
				if (displayFullScreen)
				{
					stage.displayState = StageDisplayState.FULL_SCREEN;
				}
				// Make this application always in front 
				if (displayAlwaysInFront)
				{
					this.alwaysInFront = true;
				}
				// NativeProcess not supported
				if (!NativeProcess.isSupported)
				{
					showNativeProcessUnsupported();
				}
			}
			/* ========================================================================
				NativeProcess
			======================================================================== */
			/**
			 * Print the pdf
			 * */
			public function printPDF():void
			{	 
				var process:NativeProcess;
				var backSlashPattern:RegExp = /\\/g;
				var exeFileName:File;
				var printFileName:String;
				var processArgs:Vector.<String>;
				
				setUIStateToPrinting();
				
				// Windows OS
				if (Capabilities.os.toLowerCase().indexOf("win") > -1)
				{
					// Create File object of the application directory
					exeFileName = File.applicationDirectory;
					// Refine the file object to the NativeApps subdirectory of application directory
					exeFileName = exeFileName.resolvePath(NATIVE_APP_REL_PATH);
					// Refine the file object the application file name
					exeFileName = exeFileName.resolvePath(PDF_PRINT_APP_FILE_NAME);
					printFileName = exeFileName.nativePath.substr(0, exeFileName.nativePath.indexOf(exeFileName.name))  +  PDF_FILE_REL_PATH + "\\" + PDF_FILE_NAME;
					printFileName = printFileName.replace(backSlashPattern, "\\\\")	;
					console("Printing " + printFileName);
					processArgs = new Vector.<String>();
					processArgs.push(PDF_PRINT_APP_SWITCH); 
					processArgs.push(printFileName); 
					processArgs.push(PRINTER_NAME); 
						
					var nativeProcessStartupInfo:NativeProcessStartupInfo;
					nativeProcessStartupInfo = new NativeProcessStartupInfo();
					nativeProcessStartupInfo.arguments = processArgs ; 
					nativeProcessStartupInfo.executable = exeFileName  ;
					
					
					console("Executing " + nativeProcessStartupInfo.executable.nativePath);
					console("Arguments " + nativeProcessStartupInfo.arguments.toString());
					
					// Create NativeProcess, create listeners and start.
					process = new NativeProcess();
					process.addEventListener(NativeProcessExitEvent.EXIT, nativeProcessExitEventHandler);
					process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, ioEventHandler);
					process.start(nativeProcessStartupInfo);
					this.focusManager.showFocus();
				}
				else 
				{
					showUnsupportedOS();
				}
			}
			/**
			 * Signals the native process has exited. The exitCode property contains the 
			 * value the process returns to the host operating system on exit. 
			 * If the AIR application terminates the process by calling the exit() 
			 * method of the NativeProcess object, the exitCode property is set to NaN. 
			 * */
			public function nativeProcessExitEventHandler(event:NativeProcessExitEvent):void
			{
				console(className + ".nativeProcessExitEventHandler(...) - Process exited with " + event.exitCode);
				setUIStateToReady();
			}
			/**
			 * Signals that reading from the standard error (stderror) stream has failed. 
			 * */
			public function ioEventHandler(event:IOErrorEvent):void
			{
				console(className + ".ioEventHandler(...) - IOError - " + event.toString());
				setUIStateToReady();
			}
			/* ========================================================================
				UI 
			======================================================================== */
			/**
			 * Set the UI ready state. Not printing.
			 * */
			public function setUIStateToReady():void
			{
				print_btn.enabled = true;
				CursorManager.removeBusyCursor();
			}
			/**
			 * Set the UI printing state.
			 * */
			public function setUIStateToPrinting():void
			{
				print_btn.enabled = false;
				CursorManager.setBusyCursor();
			}
			/**
			 * Show unsupported OS.
			 * */
			public function showUnsupportedOS():void
			{
				Alert.show( PDF_PRINT_APP_FILE_NAME + lang_IsWindowsOSOnly, lang_Attention);
				setUIStateToReady()
			}
			/**
			 * Show native process unsupported.
			 * */
			public function showNativeProcessUnsupported():void
			{
				Alert.show( lang_NativeNotSupported + Capabilities.version, lang_Attention);
			}
			/**
			 * Clear the console
			 * */
			public function clearConsole():void
			{
				console_ta.text = "";
			}
			/**
			 * Append to the console
			 * */
			public function console(msg:String):void
			{
				trace(msg);
				console_ta.text += msg + "\n";
			}
		]]>
	</fx:Script>
	<s:VGroup width="100%" height = "100%" >
		<s:Button id = "print_btn" label="{lang_PrintPDFBtnLabel}" click="printPDF()"/>
		<s:Label text="{lang_ConsoleLabel}" textAlign="left"/>
		<s:TextArea id="console_ta" height = "100%" width="100%"/>
		<s:Button label="{lang_ClearBtnLabel}" click="clearConsole()"/>
	</s:VGroup>
</s:WindowedApplication>



Categories
Articles

Adobe AIR (P2P) Peer To Peer Text Chat with NetGroup

By Lon (Alonzo) Hosford

This is a minimalist example of an Adobe AIR peer to peer text chat application using the NetGroup class for peer communication. Create an new AIR application and replace all the code with the code below.

[ad name=”Google Adsense”]

You need to change line 15 to include your own Adobe Stratus developer key. You can use the Adobe Stratus server by obtaining a developer key at Adobe Labs.

On line 16 you need to create a name for your group. You want something unique so consider a reverse domain prefix such as com.yourdomain plus an application name and something unique. For example com.bugsbunny.carrot-chat.b7-4d;8k9.

You can also make this a Flex application. Create a flex application and paste all but the WindowedApplication MXML tags between your Application MXML tags.

The application starts once the user clicks the connect button. This establishes a connection with the Adobe Stratus server on line 51. Once the connection is successful, ncNetStatus(…) handler receives a NetConnection.Connect.Success event code and calls the setupGroup() method on line 100. The setupGroup() method creates the requisite specification to join the group you identified in line 16.

Once the user selects the user name and clicks connect, the user name cannot be changed. That is an application design choice on line 190 with the enabled property for the user_ti TextInput component. However you might notice the new Flex 4 two way binding used for the userName variable and the user_ti TextInput component text property on line 190. If you want to change the user name after the connection just remove the enabled property on line 190.

.
.
.
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/mx"
					   creationComplete="creationCompleteHandler(event)"
					   height="600" width="800"
					   >
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
			private const SERVER:String = "rtmfp://stratus.adobe.com/";
			private const DEVKEY:String = "{YOUR ADOBE STRATUS DEVELOPER KEY}";
			private const NETGROUP:String = "{YOUR NETGROUP NAME}";
			private var _nc:NetConnection;
			private var _netGroup:NetGroup;

			// NetGroup specifier for NETGROUP
			[Bindable]
			private var groupSpecWithAuthorizations:String;

			// Connected to Stratus server and to NETGROUP
			[Bindable]
			private var _connected:Boolean = false;

			// _userName name in chat.
			[Bindable]
			private var _userName:String;

			// Used to store our P2P Peer ID for binding to UI.
			[Bindable]
			private var _nearID:String;

			// Counter to make every NetGroup post message unique
			private var _msgOutCount:int = 0;

			/**
			 * CreationComplete event handler for WindowedApplication
			 * */
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				console (className + ".creationCompleteHandler(...) - Capabilities.version:" + Capabilities.version);
				// Generate a default user name;
				_userName = "user" +  Math.round(Math.random()*10000);
			}
			/**
			 * Connect with Stratus server
			 * */
			private function connect():void
			{
				_nc = new NetConnection();
				_nc.addEventListener(NetStatusEvent.NET_STATUS,ncNetStatus);
				_nc.connect(SERVER+DEVKEY);
			}
			/**
			 * NetStatusEvent.NET_STATUS event handler for _nc and _netGroup
			 * */
			private function ncNetStatus(event:NetStatusEvent):void
			{
				console( className + ".ncNetStatus(...) - event.info.code:" + event.info.code);

				switch(event.info.code)
				{
					case "NetConnection.Connect.Success":
						_nearID = _nc.nearID;// or you can use event.target.nearID;
						setupGroup();
						break;
					case "NetGroup.Connect.Success":
						_connected = true;
						break;
					case "NetGroup.Posting.Notify":
						receivePostNotifyMessage(event.info.message);
						break;
					// FYI: More NetGroup event info codes
					case "NetGroup.Neighbor.Connect":
					case "NetGroup.LocalCoverage.Notify":
					case "NetGroup.SendTo.Notify": // event.info.message, event.info.from, event.info.fromLocal
					case "NetGroup.MulticastStream.PublishNotify": // event.info.name
					case "NetGroup.MulticastStream.UnpublishNotify": // event.info.name
					case "NetGroup.Replication.Fetch.SendNotify": // event.info.index
					case "NetGroup.Replication.Fetch.Failed": // event.info.index
					case "NetGroup.Replication.Fetch.Result": // event.info.index, event.info.object
					case "NetGroup.Replication.Request": // event.info.index, event.info.requestID
					default:
					{
						break;
					}
				}
			}

			// ========================================
			//  NETGROUP Methods
			// ========================================

			/**
			 * Connect with the NETGROUP
			 * */
			private function setupGroup():void
			{
				console( className + ".setupGroup()");
				var groupspec:GroupSpecifier = new GroupSpecifier(NETGROUP);
				// Allow group members to open channels to server
				groupspec.serverChannelEnabled = true;
				// Allow group members to post
				groupspec.postingEnabled = true;

				// Create the group specifi
				groupSpecWithAuthorizations = groupspec.groupspecWithAuthorizations();

				// Join the group specified by groupspec
				_netGroup = new NetGroup(_nc, groupSpecWithAuthorizations);

				// Register listener for NetGroup NetStatus events
				_netGroup.addEventListener(NetStatusEvent.NET_STATUS, ncNetStatus);
			}
			/**
			 * Post a message to NETGROUP;
			 * @param messageText String. Text message to send.
			 * */
			private function sendMessageToGroup(messageText:String):void
			{
				console( className + ".sendMessageToGroup(...) - messageText:" + messageText);
				// Construct message object
				var netGroupMessage:Object = new Object();
				netGroupMessage.sender = _netGroup.convertPeerIDToGroupAddress(_nc.nearID);
				netGroupMessage.user = _userName;
				netGroupMessage.text = messageText;
				netGroupMessage.sequence = ++ _msgOutCount;// Only unique message objects are sent.
				// Send netGroupMessage object to all members of the NETGROUP
				_netGroup.post(netGroupMessage);
			}
			/**
			 * Receive a NetGroup.Posting.Notify message from NETGROUP
			 * @param netGroupMessage Object. NetGroup post message object.
			 * */
			private function receivePostNotifyMessage(netGroupMessage:Object):void
			{
				updateUI(netGroupMessage.user, netGroupMessage.text)
 			}

			// ========================================
			//  UI Methods
			// ========================================

			/**
			 * Join chat group
			 * */
			private function joinChat():void
			{
				// Connect to server
				connect();
			}
			/**
			 * Post a message to NETGROUP;
			 * */
			private function sendMessage():void
			{
				// Send message to NetGroup
				sendMessageToGroup(message_ti.text);
				// Update local view of message sent
				updateUI(_userName, message_ti.text);
			}
			/**
			 * Update UI with message object received from NETGROUP
			 * @param userName String. Name of user sending message.
			 * @param messageText String. Text of message to sent.
			 * */
			private function updateUI(userName:String, messageText:String):void
			{
				chat_ta.appendText( userName + ": " + messageText + "\n");
			}

			private function console(msg:String):void
			{
				trace(msg);
				out_ta.appendText(msg + "\n");
			}
		]]>
	</fx:Script>
	<s:layout >
		<s:VerticalLayout horizontalAlign="left"
						  paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10"/>
	</s:layout>
	<!--UI For Chat-->
	<s:Label text = "Chat log:" />
	<s:TextArea id="chat_ta" height = "120" width="100%"/>
	<s:HGroup width="100%">
		<s:TextInput  id="user_ti" text="@{_userName}" enabled="{!_connected}"/>
		<s:Button label="Connect" click="joinChat()" enabled="{!_connected}" />
		<s:TextInput  id="message_ti"  width="70%" enter="sendMessage()" enabled="{_connected}"/>
		<s:Button label="Send" click="sendMessage()" enabled="{_connected}" />
		<s:Button label="Clear" click="chat_ta.text = '';"   />

	</s:HGroup>

	<!--UI For Tracing P2P-->
	<s:Label text = "P2P Information" paddingTop="20"/>

	<s:HGroup horizontalAlign="left" width="100%" >
		<s:Label text = "Peer Id: " />
		<s:Label id = "peerId_lbl" text = "{_nearID}"/>
	</s:HGroup>

	<s:HGroup horizontalAlign="left" width="100%" >
		<s:Label text = "Group Id: " />
		<s:Label id = "groupId_lbl" text = "{groupSpecWithAuthorizations}" />
	</s:HGroup>

	<s:Label text = "Trace log:" paddingTop="20"/>

	<s:TextArea id="out_ta"  height = "250" width="100%" lineBreak="explicit" fontFamily="_typewriter"/>
	<s:HGroup horizontalAlign="center" width="100%">
		<s:Button label="Clear" click="out_ta.text = '';"   />
	</s:HGroup>
</s:WindowedApplication>

I had experienced some problems with launching the application in Eclipse Flex Builder 4. I had published the application and launched on other computers. The Flex Builder 4 launch would not post to the NetGroup or receive from it if launched before other application instances on other machines. However if it was launched after another application instance was launched, it seemed to post and receive posts.

Categories
Articles

Adobe AIR Native Drag and Drop File Upload With ProgressBar and PHP

This is an example of using Adobe AIR native drag and drop to upload a file to a web server using PHP5. The application only uploads one file at a time and assumes the drag operation is anywhere over the application.

This is the PHP code that will place the file into a folder named upload. The upload folder is a child folder under the folder for the PHP script. On line 13 you need to replace YOUR APPFOLDER with the folder you plan to install the PHP script. This also must be matched in the AIR MXML application.

You also need to set permissions on the upload folder to allow uploading.

The PHP script returns XML.The result.status node provide a true or false value for the success feedback.
file_upload.php

.
.
.
<?php
	$returnXML = "";
	$returnLog = "Receiving upload...\n";
    // Filedata is the default name used in uploading
	$returnLog .= "temporary file name = " . $_FILES['Filedata']['tmp_name']."\n";
	$returnLog .= "file name = " . $_FILES['Filedata']['name']."\n";
	$returnLog .= "file size = " . $_FILES['Filedata']['size']."\n";
	$file_temp = $_FILES['Filedata']['tmp_name'];
	$file_name = $_FILES['Filedata']['name'];
	$file_path = $_SERVER['DOCUMENT_ROOT']."/YOUR APPFOLDER/upload";
	$returnStatus = "false";
	$returnLog .= "attempting to move file...\n";
	if(  move_uploaded_file( $file_temp, $file_path . "/" . $file_name) )
	{
		$returnStatus = "true";
	}

	$returnLog .= "file move results = " . $returnStatus . "\n";
	$returnXML .= "<return>";
	$returnXML .= "<log>";
	$returnXML .= $returnLog;
	$returnXML .= "</log>";
	$returnXML .= "<status>";
	$returnXML .= $returnStatus;
	$returnXML .= "</status>";

	$returnXML .= "</return>";
	echo $returnXML ;

?>

This is the AIR main MXML code. You need to modify line 21 to include the URL to where you want to place the PHP script to handle the file upload.

.
.
.
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/mx"
					   creationComplete="creationCompleteHandler(event)"
					   >
	<fx:Script>
		<![CDATA[
			import flash.desktop.ClipboardFormats;
			import flash.desktop.NativeDragManager;
			import flash.events.DataEvent;
			import flash.events.NativeDragEvent;
			import flash.filesystem.File;

			import mx.controls.Alert;
			import mx.events.FlexEvent;

			private const UPLOAD_SCRIPT_URL:String = "http://www.YOURDOMAIN.com/YOUR APPFOLDER/file_upload.php"

			protected function creationCompleteHandler(event:FlexEvent):void
			{
				console(className + ".creationCompleteHandler(...) - url:" + url );
				addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, onDragEnterHandler);
				addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, onDragDropHandler);
			}
			/**
			 * Event handler for NativeDragEvent.NATIVE_DRAG_ENTER.
			 * May be called multiple times as file object is dragged over the application.
			 * <p>
			 * Check that there are file objects being dragged and only one file is being dragged.
			 * </p>
			 * */
			private function onDragEnterHandler(e:NativeDragEvent):void
			{
				console(className + ".onDragIn(...)");
				//Does the clipboard have an array of File objects (AIR only)
				if(e.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT))
				{
					//Get the array of File objects
					var files:Array = e.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;

					//Allow only one file as only supporting one in this application
					if(files.length == 1)
					{
						//Triggers NativeDragEvent.NATIVE_DRAG_DROP event.
						NativeDragManager.acceptDragDrop(this);
					}
				}
			}
			/**
			 * Event handler for NativeDragEvent.NATIVE_DRAG_DROP
			 * Occurs when the file object is dropped over the application
			 * */
			private function onDragDropHandler(e:NativeDragEvent):void
			{
				var urlRequest:URLRequest = new URLRequest();
				urlRequest.url = UPLOAD_SCRIPT_URL + "?cache=" + new Date().getTime();
				console(className + ".onDragDropHandler(...) - urlRequest.url:" + urlRequest.url);
				//Get the array of File objects. The array should only have one entry per onDragEnterHandler(...)
				var files:Array = e.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;

				//Create a File object and register events.
				var file:File = File(files[0]);
				file.addEventListener(Event.COMPLETE, file_CompleteHandler);
				file.addEventListener(Event.OPEN, file_OpenHandler);
				file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, file_UploadCompleteDataHandler);
				file.addEventListener(IOErrorEvent.IO_ERROR, file_IOErrorHandler);
				file.addEventListener(HTTPStatusEvent.HTTP_STATUS , file_HTTPStatusHandler);
				file.addEventListener(SecurityErrorEvent.SECURITY_ERROR, file_SecurityErrorHandler);
				file.addEventListener(ProgressEvent.PROGRESS, file_progressHandler);

				console(className + ".onDragDropHandler(...) - file.url:" + file.url);
				console(className + ".onDragDropHandler(...) - file.name:" + file.name);
				console(className + ".onDragDropHandler(...) - file.nativePath:" + file.nativePath);

				try {
					// Start the file upload
					console(className + ".onDragDropHandler(...) - uploading " + file.name + " ...\n" );
					file.upload(urlRequest, "Filedata", false);
				}
				catch (error:Error) {
					console(className + ".onDragDropHandler(...) - Unable to upload file." + file.name);
					console(className + ".onDragDropHandler(...) - error." + error.toString());
				}
			}
			private function file_progressHandler(event:ProgressEvent):void {
				var file:FileReference = FileReference(event.target);
				console(className + ".progressHandler(...) - name=" + file.name + " bytesLoaded=" + event.bytesLoaded + " bytesTotal=" + event.bytesTotal);
				progressBar.setProgress(event.bytesLoaded, event.bytesTotal);
				progressBar.label = "CurrentProgress: " + Math.round( (event.bytesLoaded /event.bytesTotal * 100)) + "%";
			}

			/**
			 * Dispatched when an upload or download operation starts.
			 */
			private function file_OpenHandler(event:Event):void
			{
				console(className + ".file_OpenHandler(...) - event:" + event );
			}
			/**
			 * Dispatched when an upload fails and an HTTP status code is available to describe the failure.
			 */
			private function file_HTTPStatusHandler(event:HTTPStatusEvent):void
			{
				console(className + ".file_HTTPStatusHandler(...) - event:" + event );
			}
			/**
			 * Dispatched when the upload fails for various I/O reasons.
			 */

			private function file_IOErrorHandler(event:IOErrorEvent):void
			{
				console(className + ".file_IOErrorHandler(...) - event:" + event );
			}
			/**
			 * Dispatched when an operation violates a security constraint.
			 */

			private function file_SecurityErrorHandler(event:SecurityErrorEvent):void
			{
				console(className + ".file_SecurityErrorHandler(...) - event:" + event );
			}

			/**
			 * Dispatched when download is complete or when upload generates an HTTP status code of 200.
			 */
			public function file_CompleteHandler(event:Event):void
			{
				console(className + ".file_CompleteHandler(...) file uploaded complete");
			}

			/**
			 * Dispatched after data is received from the server after a successful upload.
			 */
			public function file_UploadCompleteDataHandler(event:DataEvent):void
			{
				var result:XML = new XML(event.data);
				console(className + ".file_UploadCompleteDataHandler(...) - STATUS:" + result.status );
				console(className + ".file_UploadCompleteDataHandler(...) - LOG:\n" + result.log );

			}

			/**
			 * Update the UI trace log
			 */
			private function console(msg:String):void
			{
				trace(msg);
				console_ta.appendText(msg + "\n");
			}

		]]>
	</fx:Script>
	<s:layout>
		<s:VerticalLayout paddingLeft="10" paddingTop="10"/>
	</s:layout>
	<s:Label  text="Adobe AIR Drag and Drop File Upload" fontSize="20" fontWeight="bold"/>
	<s:Line yFrom="10" yTo = "100" xFrom="0" xTo="0" width="100%" height="0"  >
		<!-- Define the border color of the line. -->
		<s:stroke>
			<s:SolidColorStroke color="0x000000" weight="1" caps="square"/>
		</s:stroke>
	</s:Line>

	<mx:ProgressBar id="progressBar" labelPlacement="bottom" minimum="0" visible="true" maximum="100"
					color="0x323232" label="CurrentProgress 0%" direction="right" mode="manual" width="100%"/>

	<s:TextArea  width="100%" height="100%" id="console_ta" fontFamily="_typewriter" lineBreak="explicit"/>
	<s:HGroup horizontalAlign="center" width="100%">
		<s:Button   label="Clear" click="{console_ta.text = '';}"/>
	</s:HGroup>
</s:WindowedApplication>

The AIR application and the PHP script do not check for maximum upload file size allowed. A typical upload limit number imposed by web hosting is the default 2 megabytes. You may be able to increase that number in the php.ini file or the .htaccess file. The latter is the alternative is you have no control over the php.ini file and the code is included here to bump the size up. In any case you are at the mercy of your hosting provider.

.htaccess

php_value upload_max_filesize 10M
php_value post_max_size 10M
php_value memory_limit 128M

In the AIR application you can create a FILE object in the onDragEnterHandler in the same way as the onDragDropHandler and use the file.size parameter for a check. For the PHP script you can use the $_FILES[‘Filedata’][‘size’] value.

Categories
Articles

Adobe Peer to Peer Basic Example For Connecting to Stratus Server

By Lon Hosford

This is the bare bones example to set up a Flex application for connecting to the Adobe Stratus Server and obtaining a unique 256-bit peer id.

[ad name=”Google Adsense”]

The peer id is shared with others using the application for communication as well as their peer id shared with you. This example does not go into those steps but basically you need a means to do that such as using the FMS server or any web server.

The Adobe Stratus Server handles the peer id handshaking between the peer to peer members. The data such as video or files are shared directly between he peers. The Adobe Stratus server is free and various bloggers have indicated its future is becoming part of the FMS server product. You can use the Adobe Stratus server by obtaining a developer key at Adobe Labs. Insert that key in the code at line 16.

This is the code for an Air application which can also be used for a Flex application.

.
.
.
<?xml version="1.0" encoding="utf-8"?>
<!-- Bare bones Adobe Stratus P2P connection and retrieval of an id -->
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   creationComplete="creationCompleteHandler(event)">

	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;

			private const SERVER:String = "rtmfp://stratus.adobe.com/";
			private const DEVKEY:String = "{YOUR ADOBE STRATUS DEVELOPER KEY}";
			private var stratusConnection:NetConnection;
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				console("============================================");
				console("This is a bare bones example of connecting ");
				console("to the adobe status peer to peer server ");
				console("and obtaining an id. ");
				console("============================================");
				console("creationCompleteHandler") ;

				stratusConnection = new NetConnection();
				stratusConnection.addEventListener(NetStatusEvent.NET_STATUS, stratusConnectionStatusHandler);
				stratusConnection.connect(SERVER+DEVKEY);
			}

			private function stratusConnectionStatusHandler(event:NetStatusEvent):void{
				console("stratusConnectionStatusHandler - event.info.code:" + event.info.code);

				switch (event.info.code)
				{
					case "NetConnection.Connect.Success":
						console("stratusConnectionStatusHandler - nearID:" + stratusConnection.nearID);
						break;
					case "NetConnection.Connect.Failed":
						console("stratusConnectionStatusHandler - connection failed");
						break;
					default:
						console("stratusConnectionStatusHandler - uncaught event.info.code");
						break;
				}
			}
			private function console(msg:String):void
			{
				trace(msg);
				out_ta.appendText(msg + "\n");

			}

		]]>
	</fx:Script>

	<s:TextArea id = "out_ta"
				width = "100%" height = "100%"
				lineBreak="explicit"
				fontFamily="_typewriter"/>
</s:WindowedApplication>

Categories
Articles

AIR Flex Custom Events in a Component

A common error in creating custom events inside of a component is the dispatching of the event using the event type you have created in the the custom event.

In an application you may be dealing with events that are solely called from Actionscript and have no UI and event that are part of a UI.

In the case of events that have a UI you may create a component the UI and want to show the event in the code help and as part of the MXML tag. This is where your thinking can get crossed where you think in terms of event types in Actionscript only events.

Consider this custom event.

package com.who.events
{
	import flash.events.Event;
	
	public class MyCustomEvent extends Event
	{
		public static const STOP:String = "com.who.events.MyCustomEvent.stop";
		public function MyCustomEvent(type:String = STOP, bubbles:Boolean=false, cancelable:Boolean=false) 
		{
			super(type, bubbles, cancelable);
			
			
		}
		override public function clone():Event 
		{
			return new MyCustomEvent(type, bubbles, cancelable);
		}
	}
}

The event type is a package path name giving it a unique name.

To dispatch the event you might use this:

 
...
var myCustomEvent:MyCustomEvent= new MyCustomEvent(MyCustomEvent.STOP);
dispatchEvent(mainViewCallEvent);
...

This is the same as

	
...	
var myCustomEvent:MyCustomEvent= new MyCustomEvent("com.who.events.MyCustomEvent.stop");
dispatchEvent(mainViewCallEvent);
...

The code for listening to the event would be

	
...			
componentId.addEventListener(MyCustomEvent.STOP, eventHandlerMethodName,true,0,true);
...

Now you want include the event so it is part of the MXML of a component:

...
	<fx:Metadata>
		[Event(name="stop", type="com.who.events.MyCustomEvent")] 
		
	</fx:Metadata>
...

The name can be anything you want but the inclination is to use the name appended to the end of the type in the custom event class. In this case stop. However the event will not longer be seen. The fix is to change the creation of the event instance for dispatching.

...		
var myCustomEvent:MyCustomEvent= new MyCustomEvent("stop");
dispatchEvent(mainViewCallEvent);
...
Categories
Articles

Actionscript 3.0 Debug Console Lite

By Lon (Alonzo) Hosford

I created this debug console for use in learning and teaching Actionscript programming. This is developed in pure Actionscript without component libraries. For that reason it can be used in Flash, AIR, and Flex. It will work in Flash IDE like Flash CS4 or Flex Builder. It also works creating Flash, Flex or AIR from the Flex SDK command line.

[ad name=”Google Adsense”]

The debug console plays in the published movie. It also outputs the results to the consoles in the respective IDEs: Flex Builder, Flash CS4, etc. You can turn that feature off and make the DebugConsole invisible when you publish if you wish.

This is an ActionScript project created in Flex Builder and updated to Flex Builder 4. Download the example code. You can build this with the free Flex SDK by using the code in the src folder. Same for Flash CS3 and CS4. You need to create a Flash Document in the src folder and set the document class to DebuggerLite.

If you want to use the DebugConsole in a Flash CS4 or CS3 you do not need to set the document class to DebuggerLite. Rather in Actionscript you include this code:

import com.lonhosford.util.debug.lite.DebugConsole
var debugConsole:DebugConsole = DebugConsole.getInstance();
stage.addChild(debugConsole);
debugConsole.width = 200;
debugConsole.height = 200;
debugConsole.write("Hello World");

For your convenience you can download a Flash CS4 ready to go example.

Testing Application In Flex Builder
This is a testing Actionscript application in Flex Builder. No Flash or Flex components are used so there is not need to import libraries. The DebugConsole class is instantiated as _debugConsole on line 23. It is a singleton design pattern so only one exists for the application.

A Timer is used to generate entries to the DebugConsole. The Timer is instantiated on line 24 as _testTimer.

The _testTimeCount variable is used to determine how many generated entries to the DebugConsole. General purpose is to see when vertical scroll bar appears.

The _showWideLineAt variable on line 25 is used add a very wide line of text to trip showing the horizontal scroll bar. The _showWideLineAt variable trips this when it equal _testTimer.currentCount value.

The _testTimeDelay variable on line 26 is the millisecond value for the _testTimer. Just depends on how fast you want to see the automated updates to the DebugConsole.

/**
 *  Purpose: Test Debugger Lite
	<p>Author: Lon Hosford www.lonhosford.com 908 996 3773</p>
	<p>Date: March 12, 2010</p>
 * */
package
{
	import com.lonhosford.util.debug.lite.DebugConsole;
	
	import flash.display.Sprite;
	import flash.events.KeyboardEvent;
	import flash.events.TimerEvent;
	import flash.ui.Keyboard;
	import flash.utils.Timer;
	
	[SWF(width=500, height = 300, frameRate = 30)]
	/**
	 * Testing application for DebugConsole.
	 * @see com.lonhosford.util.debug.lite.DebugConsole  
	 * */
	public class DebuggerLite extends Sprite
	{
		private var _debugConsole:DebugConsole = DebugConsole.getInstance();
		private var _testTimeCount:Number = 15; 
		private var _showWideLineAt:Number = 2; 
		private var _testTimeDelay:Number = 250; 
		private var _testTimer:Timer; 
		public function DebuggerLite()
		{
			stage.addChild(_debugConsole);
			_debugConsole.width = stage.stageWidth;
			_debugConsole.height = stage.stageHeight;
			
			_testTimer = new Timer(_testTimeDelay,_testTimeCount); 
			_testTimer.addEventListener(TimerEvent.TIMER,testTimerEventHandler);
			_testTimer.addEventListener(TimerEvent.TIMER_COMPLETE,testTimerCompleteEventHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, stageKeyUpEventHandler);
			start();
		
		}
		/**
		 * Starts the automated testing.
		 * */
		private function start():void
		{
		_debugConsole.write("Debugger Lite Tester");
		_debugConsole.write("Hit Space Bar to Clear.");
		_debugConsole.write("Hit Ctr+ Space Bar to Repeat.");
		_debugConsole.write("\n");
		_testTimer.start();
			
		}
		/**
		 * Handles the KeyboardEvent.KEY_UP event for the stage.
		 * */
		private function stageKeyUpEventHandler(event:KeyboardEvent):void 
		{
			// Space bar
			if ( event.keyCode == Keyboard.SPACE )
			{
				// Control key
				if (event.ctrlKey)
				{
					_testTimer.stop();
					_testTimer.reset();
					_debugConsole.clear();
					start();
				}
				// No control key
				else
				{
					_debugConsole.clear();
				}
				
			}
		}
		/**
		 * Handles the TimerEvent.TIMER event.
		 * */
		private function testTimerEventHandler(event:TimerEvent):void 
		{
			_debugConsole.write(new Date().toTimeString());
			if (_testTimer.currentCount == _showWideLineAt)
			{
				_debugConsole.write("START Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. END");
				
			}
		}
		/**
		 * Handles the TimerEvent.TIMER_COMPLETE event.
		 * */
		private function testTimerCompleteEventHandler(event:TimerEvent):void 
		{
			_debugConsole.write("LAST LINE");
		}
	}
}

[ad name=”Google Adsense”]
DebugConsole Class
This is the DebugConsole class. It is a singleton and as such can be instantiated anywhere in your code to use the write() method on line 291. The DebugConsole write() method calls the Debugger class write()method. The Debugger class write()method dispatches a DebuggerEvent.WRITE which the DebugConsole listens in the consoleUpdate() on line 308. For this reason you can also instantiate the Debugger class in code and use its write() method if you prefer. You only need to instantiate the DebugConsole where you intend to add it to your display.

package com.lonhosford.util.debug.lite
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	
	/**
	 *  UI for debug.lite package
	 * */
	public class DebugConsole extends Sprite
	{
		private static var _instance:DebugConsole;					// Singleton instance
		private var _debugger:Debugger;								
		private var _content_tf:TextField;							// The console content
		private var _lineCount_tf:TextField;						// The line count					
		private var _width:Number = 0;								// Width
		private var _height:Number = 0;								// Height
		private var _scrollVContainer:Sprite;						// Vertical scroller container
		private var _scrollHContainer:Sprite;						// Horizontal scroller container
		private var _scrollLeftButton:DebugConsoleArrowButton;		// Scroll left button
		private var _scrollRightButton:DebugConsoleArrowButton;		// Scroll right button
		private var _scrollUpButton:DebugConsoleArrowButton;		// Scroll up button
		private var _scrollDownButton:DebugConsoleArrowButton;		// Scroll down button
		private var _horizontalScrollIncrement:int = 10;			// Character count increment for horizontal scrolling
		private var _verticalScrollIncrement:int = 1;				// Line count increment for vertical scrolling.
		private var _autoScroll:Boolean = true;						// Autoscroll to last line
		
		/**
		 *  Constructor
		 * 
		 * @param pvt Enforces a singleton pattern.
		 * @see #getInstance() 
		 * */
		public function DebugConsole(pvt:DebugConsolePrivateClass)
		{
			_content_tf = new TextField();
			_lineCount_tf = new TextField();
			
			var format:TextFormat = new TextFormat();
			format.font = "_typewriter";
			format.color = 0x000000;
			format.size = 12;
			format.indent = 2;
			_content_tf.defaultTextFormat = format;
			_content_tf.background= true;
			_content_tf.backgroundColor = 0xffffff;
			
			var format2:TextFormat = new TextFormat();
			format2.font = "_typewriter";
			format2.color = 0x000000;
			format2.size = 12;
			format2.leftMargin = 1
			format2.align = TextFormatAlign.RIGHT;
			//format2.indent = 1;
			
			_lineCount_tf.defaultTextFormat = format2;
			_lineCount_tf.background= true;
			_lineCount_tf.backgroundColor = 0xcccccc;
	
			_content_tf.addEventListener(Event.SCROLL, _console_tfScrollHandler);

			_scrollHContainer = new Sprite();
			_scrollVContainer = new Sprite();
			
			_scrollLeftButton = new DebugConsoleArrowButton();
			_scrollLeftButton.addEventListener(MouseEvent.CLICK,scrollLeftButtonMouseClick);
			_scrollRightButton = new DebugConsoleArrowButton();
			_scrollRightButton.addEventListener(MouseEvent.CLICK,scrollRightButtonMouseClick);
			
			_scrollUpButton = new DebugConsoleArrowButton();
			_scrollUpButton.addEventListener(MouseEvent.CLICK,scrollUpButtonMouseClick);
			_scrollDownButton = new DebugConsoleArrowButton();
			_scrollDownButton.addEventListener(MouseEvent.CLICK,scrollDownButtonMouseClick);
			
			addChild(_content_tf);
			addChild(_lineCount_tf);
			addChild(_scrollHContainer);
			addChild(_scrollVContainer);
			_scrollHContainer.addChild(_scrollLeftButton);
			_scrollHContainer.addChild(_scrollRightButton);
			_scrollVContainer.addChild(_scrollUpButton);
			_scrollVContainer.addChild(_scrollDownButton);
			_debugger = Debugger.getInstance();
			_debugger.addEventListener(DebuggerEvent.WRITE,consoleUpdate);
			
		}
		/**
		 * Singleton instantiation method
		 * */
		public static function getInstance():DebugConsole
		{
			if (DebugConsole._instance == null)
			{
				DebugConsole._instance = new DebugConsole(new DebugConsolePrivateClass());
			}
			return DebugConsole._instance;
		}
		/**
		 * Redraws the components
		 * */
		private function draw():void
		{
			var rightPanelHeight:Number;
			var bottomPanelWidth:Number;
			var lineCount_tf_width:Number = _lineCount_tf.textWidth + 12;// Extra for TextField overhead
			var bottomPanelHeight:Number = 20;
			var rightPanelWidth:Number = 20;
				
			var tfHeight:Number;// = _height;
			var tfWidth:Number = _width;
			// Component border
			graphics.clear();
			graphics.beginFill(0xffffff);
			graphics.lineStyle(2, 0x000000);
			graphics.drawRect(0, 0, _width, _height);
			graphics.endFill();
			_scrollLeftButton.draw(
				bottomPanelHeight,
				bottomPanelHeight,
				0x00ffff,
				1,
				0x000000,
				0x666666,
				90
			);
			_scrollRightButton.draw(
				bottomPanelHeight,
				bottomPanelHeight,
				0x00ffff,
				1,
				0x000000,
				0x666666,
				-90
			);
			
			_scrollUpButton.draw(
				rightPanelWidth,
				rightPanelWidth,
				0x00ffff,
				1,
				0x000000,
				0x666666,
				0
			);
			_scrollDownButton.draw(
				rightPanelWidth,
				rightPanelWidth,
				0x00ffff,
				1,
				0x000000,
				0x666666,
				180
			);
			tfHeight = height
			rightPanelHeight = height-3;
			bottomPanelWidth = width - 3 - lineCount_tf_width;
			if (_content_tf.textHeight > _content_tf.height)
			{
				bottomPanelWidth -= rightPanelWidth - 1;
			}
			if (_content_tf.textWidth > _content_tf.width)
			{
				rightPanelHeight -= bottomPanelHeight - 1;
			}
			// Right scrollbar panel
			if (_content_tf.textHeight > _content_tf.height)
			{
				_scrollUpButton.x = 0;
				_scrollUpButton.y = 0;
				_scrollDownButton.x = 0;
				_scrollDownButton.y = 0;
				_scrollVContainer.graphics.clear();
				
				_scrollVContainer.graphics.beginFill(0xcccccc);
				_scrollVContainer.graphics.lineStyle(1, 0x000000);
				_scrollVContainer.graphics.drawRect(0, 0, rightPanelWidth - 1, rightPanelHeight);
				_scrollVContainer.graphics.endFill();
				_scrollVContainer.x = _width - _scrollVContainer.width + .5;
				_scrollVContainer.y = .5;

				
				_scrollDownButton.x = (_scrollVContainer.width - _scrollDownButton.width) ;
				_scrollDownButton.y = (_scrollVContainer.height - _scrollDownButton.height) ;
				_scrollUpButton.x = _scrollDownButton.x;
				_scrollUpButton.y = 0;
				
				tfWidth -= _scrollVContainer.width - 1;
				
				_scrollVContainer.visible = true;
			}
			else
			{
				_scrollVContainer.visible = false;
			}
			// Bottom scrollbar panel
			if (_content_tf.textWidth > _content_tf.width)
			{
				_scrollLeftButton.x = 0;
				_scrollLeftButton.y = 0;
				_scrollRightButton.x = 0;
				_scrollRightButton.y = 0;
				_scrollHContainer.graphics.clear();
				_scrollHContainer.graphics.beginFill(0xcccccc);
				_scrollHContainer.graphics.lineStyle(1, 0x000000);
				_scrollHContainer.graphics.drawRect(0, 0, bottomPanelWidth, bottomPanelHeight - 1);
				_scrollHContainer.graphics.endFill();
				_scrollHContainer.y = _height - _scrollHContainer.height + .5;
				_scrollHContainer.x = lineCount_tf_width;
				_scrollLeftButton.x = (_scrollHContainer.width - _scrollLeftButton.width) ;
				_scrollLeftButton.y = (_scrollHContainer.height - _scrollLeftButton.height) ;
				_scrollRightButton.x = 0;
				_scrollRightButton.y = _scrollLeftButton.y;

				_scrollHContainer.visible = true;
				tfHeight -= _scrollHContainer.height -1;
			}
			else
			{
				_scrollHContainer.visible = false;
			}
			
			// Left bottom rectangle if horizontal and vertical scroll bars are visible.
			if (_scrollHContainer.visible && _scrollVContainer.visible)
			{
				graphics.beginFill(0xcccccc);
				graphics.lineStyle(0, 0x000000,0);
				graphics.drawRect(_scrollHContainer.x + _scrollHContainer.width, _scrollVContainer.height, bottomPanelHeight-2, bottomPanelHeight-2);
				graphics.endFill();
			}
			// Left bottom rectangle if horizontal scroll bar is visible.
			if (_scrollHContainer.visible)
			{
				graphics.beginFill(0xcccccc);
				graphics.lineStyle(1, 0x000000,100);
				graphics.drawRect( 0, _scrollHContainer.y, lineCount_tf_width, bottomPanelHeight-1);
				graphics.endFill();
			}
			
			// Position and resize line count text field.
			_lineCount_tf.width = lineCount_tf_width;	
			_lineCount_tf.height = tfHeight - 4;			
			_lineCount_tf.x = 1;
			_lineCount_tf.y = 1;
			
			_lineCount_tf.scrollV = _content_tf.scrollV;
			
			// Position and resize line content text field.
			_content_tf.width = tfWidth - 2 - lineCount_tf_width;
			_content_tf.height = _lineCount_tf.height;
			_content_tf.x = _lineCount_tf.x + _lineCount_tf.width ;
			_content_tf.y = _lineCount_tf.y;
			
			_scrollUpButton.enabled =  _content_tf.scrollV != 1;
			_scrollDownButton.enabled =  _content_tf.scrollV != _content_tf.maxScrollV;
			_scrollLeftButton.enabled =  _content_tf.scrollH != _content_tf.maxScrollH;
			_scrollRightButton.enabled =  _content_tf.scrollH != 0;
		}
		/**
		 * Change width
		 * */
		override public function set width(width:Number):void
		{
			_width = width;
			draw();
		}
		/**
		 * Change height
		 * */
		override public function set height(height:Number):void
		{
			_height = height;
			draw();
			
		}
		/**
		 * Scroll to last line. 
		 * @default true  
		 * */
		public function set autoScroll(autoScrollEnabled:Boolean):void
		{
			_autoScroll = autoScrollEnabled;
		}
		/**
		 * Adds one line. Multiple lines can be added inserting \n. A blank line can be added with \n.
		 * <p>The writing is delegated to the Debugger class.</p>
		 * @see com.lonhosford.util.debug.lite.Debugger.write()
		 * */
		public function write(msg:String):void 
		{
			_debugger.write(msg);
		}
		/**
		 * Clears the display. Line count is not reset to 0 and continues to increment.
		 * */
		public function clear():void 
		{
			_lineCount_tf.text = "";
			_content_tf.text = "";
			draw();
		}
		/**
		 * Handles the DebuggerEvent.WRITE event.
		 * */
		private function consoleUpdate(e:DebuggerEvent):void
		{
			var debugMessage:DebugMessage = e.debugMessage;
			
			_lineCount_tf.appendText(debugMessage.lineNumber + "." + "\n");
			if (debugMessage.text == "\n")
			{
				debugMessage.text = "";
			}
			_content_tf.appendText( debugMessage.text + "\n");
			if(_autoScroll)
			{
				_content_tf.scrollV = _content_tf.maxScrollV;
			}			
			draw();
		}
		/**
		 * Handler for the scrollUpButton MouseEvent.CLICK event.
		 * */

		private function scrollUpButtonMouseClick(e:MouseEvent):void
		{
			if (_content_tf.scrollV <= _verticalScrollIncrement)
			{
				_content_tf.scrollV = 1;
			}
			else
			{
				_content_tf.scrollV -= _verticalScrollIncrement;
			}
			draw();
		}
		/**
		 * Handler for the scrollRightButton MouseEvent.CLICK event.
		 * */
		private function scrollRightButtonMouseClick(e:MouseEvent):void
		{
			if (_content_tf.scrollH <= _horizontalScrollIncrement)
			{
				_content_tf.scrollH = 0;
			}
			else
			{
				_content_tf.scrollH -= _horizontalScrollIncrement;
			}
			draw();
		}
		/**
		 * Handler for the scrollDownButton MouseEvent.CLICK event.
		 * */
		private function scrollDownButtonMouseClick(e:MouseEvent):void
		{
			
			if (_content_tf.scrollV + _verticalScrollIncrement >= _content_tf.maxScrollV)
			{
				_content_tf.scrollV = _content_tf.maxScrollV;
				
			}
			else
			{
				_content_tf.scrollV += _verticalScrollIncrement;
			}
			draw();
		}
		/**
		 * Handler for the scrollLeftButton MouseEvent.CLICK event.
		 * */
		private function scrollLeftButtonMouseClick(e:MouseEvent):void
		{
			
			if (_content_tf.scrollH + _horizontalScrollIncrement >= _content_tf.maxScrollH)
			{
				_content_tf.scrollH =_content_tf.maxScrollH;
				
			}
			else
			{
				_content_tf.scrollH += _horizontalScrollIncrement;
			}
			draw();
		}
		/**
		 * Handler for the _content_tf Event.SCROLL event.
		 * */
		public function _console_tfScrollHandler(event:Event):void
		{
			draw();
		}
	}
}
/**
 * Singleton enforcer class
 * */
class DebugConsolePrivateClass
{
	public function DebugConsolePrivateClass()
	{
		
	}
}

Debugger Class
This class receives write messages and dispatches them. It also will write to the IDE debug console on line 60.

package com.lonhosford.util.debug.lite
{
	import flash.events.EventDispatcher;
	/**
	 * Singleton for receiving and dispatching debugging messages
	 * */
	public class Debugger extends EventDispatcher
	{
		private static var _instance:Debugger;
		private var _msg:String;
		private var _isTracing:Boolean = true;		// State of using the trace() function.
		private var _lineCount:Number = 0;			// Line count of tracing messages
		private var _lang_productName:String = "Actionscript 3 Debugger Lite";
		private var _lang_productVersion:String = "Version";
		/**
		 *  Constructor
		 * 
		 * @param pvt Enforces a singleton pattern.
		 * @see #getInstance() 
		 * */
		public function Debugger(pvt:DebuggerPrivateClass)
		{
		}
		/**
		 * Singleton instantiation method
		 * */
		public static function getInstance():Debugger
		{
			if (Debugger._instance == null)
			{
				Debugger._instance = new Debugger(new DebuggerPrivateClass());
			}
			return Debugger._instance;
		}
		/**
		 * Turns on or off tracing to the Flash or Flex IDE console.
		 * @default true
		 * */
		public function set isTracing(value:Boolean):void
		{
			_isTracing = value;
		}
		/**
		 * Adds one line. Multiple lines can be added inserting \n. A blank line can be added with \n.		 * <p>The writing is delegated to the Debugger class.</p>
		 * */
		public function write(msg:String):void 
		{
			var messageLines:Array = new Array();
			if (msg == "\n") 
			{
				messageLines.push("");
			}
			else
			{
				messageLines = msg.split("\n")
			}
			
			
			if (_isTracing)
			{
				trace(msg);
			}
			if ( _lineCount == 0 )
			{
				messageLines.splice(0,0,_lang_productName + "\t" + _lang_productVersion + " " + DebugVersion.VERSION);
				messageLines.splice(1,0, DebugVersion.AUTHOR + "\t" + DebugVersion.AUTHOR_WEB_SITE);
				messageLines.splice(2,0,"\n");
			}
			for (var msgLinesIndex:uint = 0; msgLinesIndex <= messageLines.length - 1; msgLinesIndex++)
			{
				dispatchMessageEvent(messageLines[msgLinesIndex]);
			}
		}
		/**
		 * Dispatches a DebuggerEvent.WRITE
		 * @see DebuggerEvent
		 * @see DebugMessage
		 * */
		private function dispatchMessageEvent(msg:String):void
		{
			var debugMessage:DebugMessage = new DebugMessage();
			debugMessage.text = msg;
			debugMessage.lineNumber = ++_lineCount;
			var e:DebuggerEvent = new DebuggerEvent(DebuggerEvent.WRITE,  debugMessage);
			dispatchEvent(e);
		}
	}
}
/**
 * Singleton enforcer class
 * */
class DebuggerPrivateClass
{
	public function DebuggerPrivateClass()
	{
		
	}
}

[ad name=”Google Adsense”]
DebugConsoleArrowButton Class
UI for the scroll buttons. Simple shapes using a triangle to indicate direction of scroll.

package com.lonhosford.util.debug.lite
{
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	/**
	 * Arrow button UI and logic
	 * */
	public class DebugConsoleArrowButton extends Sprite
	{
		private var _container:Sprite = new Sprite();
		private var _triangle:DebugTriangleShape = new DebugTriangleShape();
		private var _backgroundRect:Sprite = new Sprite();
		private var _width:Number;
		private var _height:Number;
		private var _color:int;										// Color of arrow.
		private var _borderWidth:Number;							// Border width of button.
		private var _borderColor:int;								// Border color of button.
		private var _backgroundColor:int;							// Background color of button.
		private var _direction:Number;								// Rotation of the UI.
		private var _mouseDownStartTimeDelay:Number = 350; 			// Initial delay before starting repeating MouseEvent.CLICK events.
		private var _mouseDownTimeDelay:Number = 100; 				// Delay between each MouseEvent.CLICK event.
		private var _mouseDownTimer:Timer; 							// Timer for repeating MouseEvent.CLICK events.
		/**
		 * Constructor
		 * */
		public function DebugConsoleArrowButton()
		{
			_mouseDownTimer = new Timer(_mouseDownTimeDelay,0); 
			_mouseDownTimer.addEventListener(TimerEvent.TIMER,mouseDownTimerEventHandler);
			_container.addChild(_backgroundRect);			
			_backgroundRect.addChild(_triangle);
			addChild(_container);
		}
		/**
		 * Handler for TimerEvent.TIMER event. Resets the delay interval once the default delay 
		 * is reached.
		 * */
		private function mouseDownTimerEventHandler(event:TimerEvent):void 
		{
			if (_mouseDownTimer.delay == _mouseDownStartTimeDelay)
			{
				_mouseDownTimer.delay = _mouseDownTimeDelay;
			}
			
			var e:MouseEvent = new MouseEvent(MouseEvent.CLICK);
			dispatchEvent(e);
		}
		/**
		 * Sets enabled state.
		 * */
		internal function set enabled(enabledState:Boolean):void
		{
			alpha = enabledState ? 1 : .25;
			if (enabledState)
			{
				addEventListener(MouseEvent.MOUSE_UP,mouseUpEventHandler);
				addEventListener(MouseEvent.MOUSE_DOWN,mouseDownEventHandler);
				addEventListener(MouseEvent.MOUSE_OUT,mouseOutEventHandler);
			}		
		}
		/**
		 * Handler for MouseEvent.MOUSE_OUT event. Stops the mouse down repeat timer.
		 * */
		private function mouseOutEventHandler(e:MouseEvent):void
		{
			_mouseDownTimer.stop();		
		}
		/**
		 * Handler for MouseEvent.MOUSE_UP event. Stops the mouse down repeat timer.
		 * */
		private function mouseUpEventHandler(e:MouseEvent):void
		{
			_mouseDownTimer.stop();		
		}
		/**
		 * Handler for MouseEvent.MOUSE_DOWN event. Starts mouse down timer.
		 * */
		private function mouseDownEventHandler(e:MouseEvent):void
		{
			_mouseDownTimer.delay = _mouseDownStartTimeDelay;
			_mouseDownTimer.start();
		}
		/**
		 * Draw the button UI.
		 * */
		internal function draw(
			p_width:Number, 
			p_height:Number, 
			color:int,
			borderWidth:Number,
			borderColor:int,
			backgroundColor:int,
			direction:Number = 0):void
		{
			_width = p_width;
			_height = p_height;
			_color = color;
			_borderWidth = borderWidth;
			_borderColor = borderColor;
			_backgroundColor = backgroundColor;
			_direction = direction;
		
			_backgroundRect.graphics.clear();
			_backgroundRect.graphics.beginFill(_backgroundColor);
			_backgroundRect.graphics.lineStyle(_borderWidth, _borderColor); 
			_backgroundRect.graphics.drawRect(0, 0, _width - _borderWidth, _height - _borderWidth); 
			_backgroundRect.graphics.endFill();
			
			_triangle.draw( _width  , 0,_color, _color);
			_triangle.scaleX = _triangle.scaleY = .6;
			_triangle.x =  (_triangle.width / 2) + ((_width - _triangle.width - _borderWidth) / 2) ;
			_triangle.y =  (_triangle.height / 2) + ((_height - _triangle.height - _borderWidth) / 2);
			_triangle.rotation = _direction;
		}
	}
}

DebugTriangleShape Class
Utility to draw a rectangle shape with a center registration point.

package com.lonhosford.util.debug.lite
{
	import flash.display.Shape;
	import flash.display.Sprite;
	/**
	 * Utility to draw a triangle shape with a center registration point.
	 * */	
	public class DebugTriangleShape extends Sprite
	{
		
		public function DebugTriangleShape(
		)
		{
		}
		internal function draw(
			size:Number, 
			borderWidth:Number,
			borderColor:int,
			backgroundColor:int
				):void
		{
			graphics.clear();
			graphics.beginFill(backgroundColor);
			graphics.lineStyle(borderWidth, borderColor);
			size = size / 2;
			graphics.moveTo(-size, size);
			graphics.lineTo(-0, -size);  
			graphics.lineTo(size, size );
			
			graphics.endFill();
			
		}
	}
}

DebuggerEvent Class
Events for the Debugger class.

package com.lonhosford.util.debug.lite
{import flash.events.Event;
	/**
	 * Events for the Debugger
	 * @see DebugMessage
	 * @see Debugger
	 * */
	public class DebuggerEvent extends Event
	{
		public static const WRITE:String = "debug.DebuggerEvent.Write";
		public var debugMessage:DebugMessage;
		public function DebuggerEvent(type:String, debugMessage:DebugMessage)
		{
			super(type, bubbles);
			this.debugMessage = debugMessage
		}
		override public function clone():Event 
		{
			return new DebuggerEvent(type, debugMessage);
		}
	}
}

DebugMessage Class
The data values sent with the DebuggerEvent.

package com.lonhosford.util.debug.lite
{
	/**
	 * Data for a DebuggerEvent.
	 * @see DebuggerEvent
	 * */
	public class DebugMessage
	{
		public var text:String;
		public var lineNumber:Number;
		public function DebugMessage()
		{
		}
	}
}

DebugVersion Class
A place to hold the static values for the code.

package com.lonhosford.util.debug.lite
{
	/**
	 * Common version data.
	 * */	
	public final class DebugVersion 
	{
		public static const VERSION:String = "1.00.01";
		public static const AUTHOR:String = "Lon (Alonzo) Hosford";
		public static const AUTHOR_WEB_SITE:String = "https://www.lonhosford.com";
	}
}
Categories
Articles

Adobe Flex 3 Runtime Language Localization Embedded Locals

I started to review the Adobe Flex 3 language localization. The site I used to learn is at Adobe Labs written by Gordon Smith September 25, 2007.

My opinion is the localization implementation suffers a geekish solution. So even Gordon’s super effort, you get lost in the terminology geeks have used to define the elements of localization features. So I did eventually get through to make my own examples based on Gordon’s work.

This example shows how to embed the locale language in the Flex swf. This of course make the Flex swf potentially larger. However if you have a small amount of language or only a few languages, you might find this solution more expedient.

Look it over and download at Adobe Flex 3 Basic Embedded Runtime Localization Locales.

I also followed Gordon’s modular version of localization. This allows loading localization data during runtime. Gordon’s example has problems with Flex timing when you go to put it to work. I solved the timing issues and will post on that as well with the fixes. But also there are even more geekish aspects to get Flex even set up to do this.