Archive for May, 2009

14
May

Utility class to help reading/writing files in Flash 10

Before the release of Flash 10 we needed to use some sort of a server side proxy or Javascript in order to read or write file in the user’s system. We would send the request to a proxy, which will handle the request and send it back to Flash once completed.

Flash Player 10 has exposed two new methods in FileReference: load and save.

The new methods allow you to read and write data right into the user’s local system. You get information about the files such as modify date, creator, size and other properties, however unlike AIR’s FileStream API the location of the files will not be visible to us and we can only do asynchronous calls.

Asynchronous operations perform operations in the background without waiting for the operation to complete, long processes such as uploading or downloading a large file will not impact the application and the user can keep using the application. Synchronous operations on the other hand, the operation is waiting for the original operation to complete before allowing the user to do any interactions with the application.

Recently, Adobe have closed a gap and forced the load and save methods to be used follow user interaction, such as clicking a button, so the user will be aware of being asked to save or load and not see a browse window comes out of nowhere.

The process of reading and writing files is easy, however I created a helper to make the process of reading and writing files even easier. The class called: LocalFileHelper.as and it uses the FileReference API.

Let’s take a look at the code that shows a simple implementation of loading and saving a file:


<FxApplication xmlns="http://ns.adobe.com/mxml/2009"
	minWidth="1024"
	minHeight="768" initialize="initializeHandler(event)">

	     <Script>
          <![CDATA[
	     		import com.elad.framework.utils.events.LocalFileErrorEvent;
	     		import com.elad.framework.utils.events.LocalFileLoadedEvent;
	     		import mx.controls.Alert;
	     		import com.elad.framework.utils.events.LocalFileEvent;
	     		import com.elad.framework.utils.enum.FileTypeFormat;
	     		import com.elad.framework.utils.LocalFileHelper;
	     		import mx.events.FlexEvent;

	     		private var localFileHelper:LocalFileHelper;

	     		protected function initializeHandler(event:FlexEvent):void
	     		{
	     			localFileHelper = new LocalFileHelper( FileTypeFormat.FILE_FILTER_TEXT_TYPE );

	     			localFileHelper.addEventListener(LocalFileEvent.FILE_LOAD_BROWSE, onFileSelect);
	     			localFileHelper.addEventListener(LocalFileEvent.FILE_SAVE_BROWSE, function():void { trace("Save browse complete"); } );
	     			localFileHelper.addEventListener(LocalFileEvent.FILE_SAVE_SUCCESSFULLY, function():void { trace("Save complete!"); } );
	     			localFileHelper.addEventListener(LocalFileEvent.FILE_CANCEL, function():void { Alert.show("Cancel"); } );
	     			localFileHelper.addEventListener(LocalFileErrorEvent.FILE_ERROR, function():void { trace("file error") } );
	     		}

	     		private function loadFile():void
	     		{
	     			localFileHelper.browse();
	     		}

				private function onFileSelect(event:LocalFileEvent):void
				{
					localFileHelper.addEventListener(LocalFileLoadedEvent.DATA_LOADED, onDataLoaded );
					localFileHelper.load();
				}

				private function onDataLoaded(event:LocalFileLoadedEvent):void
				{
					output.text = LocalFileHelper.convertByteArrayToText( event.byteLoaded );
				}

				private function saveFile():void
				{
					localFileHelper.save( output.text, "test.txt" );
				} 		

          ]]>
     </Script>

     <FxButton label="Load" click="loadFile()" />
     <FxButton label="Save" click="saveFile()"  x="84"/>
     <TextArea id="output" y="37" width="397" height="327"/>

</FxApplication>

Take a look at the utility class:


package com.elad.framework.utils
{
	import com.elad.framework.utils.enum.FileTypeFormat;
	import com.elad.framework.utils.enum.InteractionStates;
	import com.elad.framework.utils.events.LocalFileErrorEvent;
	import com.elad.framework.utils.events.LocalFileEvent;
	import com.elad.framework.utils.events.LocalFileLoadedEvent;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.net.FileReference;
	import flash.utils.ByteArray;

	public class LocalFileHelper extends EventDispatcher
	{

	    //--------------------------------------------------------------------------
	    //
	    //  Variables
	    //
	    //--------------------------------------------------------------------------

		/**
		 * @private
		 *
		 */
		private var fileType:Array;

		/**
		 * @private
		 *
		 */
		private var fileReference:FileReference;

		/**
		 * @private
		 *
		 */
		private var interactionState:String;

	    //--------------------------------------------------------------------------
	    //
	    //  Constructor
	    //
	    //--------------------------------------------------------------------------

		/**
		 * Default constructor, which call the <code>reset</code> method to set the file reference and type.
		 *
		 * @param fileType	you can set the type of files you want to use.  The file types are listed in <code>FileTypeFormat</code>
		 * @see	com.elad.framework.utils.enum.FileTypeFormat
		 *
		 */
		public function LocalFileHelper(fileType:Array=null)
		{
			reset(fileType);
		}

		/**
		 * Reset method will allow the implementation to reset the file reference and set again the file type filter.
		 * The default file type is <code>FileTypeFormat.FILE_FILTER_ALL_FILES_TYPE</code> which will allow selecting
		 * any file type.
		 *
		 * @param fileType	you can set the type of files you want to use.  The file types are listed in <code>FileTypeFormat</code>
		 * @see	com.elad.framework.utils.enum.FileTypeFormat
		 *
		 */
		public function reset(fileType:Array=null):void
		{
			if (fileType == null)
			{
				fileType = FileTypeFormat.FILE_FILTER_ALL_FILES_TYPE;
			}

			this.fileType = fileType;
			fileReference = new FileReference();
		}

		/**
		 * The browse method is useful when you need to load a file, you first browse for the file and than you load the file.
		 * When you browse for the file the file type that was selected is used.
		 *
		 */
		public function browse():void
		{
			this.interactionState = InteractionStates.BROWSE;

			fileReference.addEventListener(Event.SELECT, onFileSelectEventHandler);
            fileReference.addEventListener(Event.CANCEL, onFileCancelEventHandler);

			fileReference.browse(fileType);
		}

		/**
		 * Static method to convert the <code>ByteArray</code> data into a string.
		 *
		 * @example
		 * <listing version="3.0">
		 * 	var text:String = LocalFileHelper.convertByteArrayToText( event.byteLoaded );
		 * </listing>
		 *
		 * @param data
		 * @return
		 *
		 */
		public static function convertByteArrayToText(data:ByteArray):String
		{
			var retVal:String;
			retVal = data.readUTFBytes(data.bytesAvailable);

			return retVal;
		}

		/**
		 * Method to load a file.  The method add event listeners and use the <code>fileReference.load</code>
		 * method to do the loading.  To load a file you must first call the <code>browse</code> method.
		 *
		 */
		public function load():void
		{
			if (this.interactionState != InteractionStates.BROWSE)
			{
				this.dispatchEvent( new LocalFileErrorEvent( "You must browse before trying to load a file!" ) );
			}

			this.interactionState = InteractionStates.LOAD_FILE;

            fileReference.addEventListener(Event.COMPLETE, onCompleteEventHandler);
            fileReference.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

            fileReference.load();
		}

		/**
		 * Save method can be used to save data into a file.  The method must follow a user interaction.
		 *
		 * @param data	Any type of data
		 * @param fileName	You can set the file name, ie: "test.txt"
		 *
		 */
		public function save(data:*, fileName:String):void
		{
			this.interactionState = InteractionStates.SAVE_FILE;

			fileReference.addEventListener(Event.COMPLETE, onCompleteEventHandler);
			fileReference.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

			fileReference.addEventListener(Event.SELECT, onFileSelectEventHandler);
			fileReference.addEventListener(Event.CANCEL, onFileCancelEventHandler);

			fileReference.save(data, fileName);
		}

	    //--------------------------------------------------------------------------
	    //
	    //  Event handlers
	    //
	    //--------------------------------------------------------------------------

		/**
		 * Method to handle the two cases where the browse gets calls:
		 *
		 * <ul>
		 * 	<li>Once you browse to load a file</li>
		 * 	<li>Once browse is used when saving a file</li>
		 * </ul>
		 *
		 * <p>The internal <code>InteractionStates</code> is used to know which case we are dealing with.
		 *
		 * @param event
		 *
		 */
		private function onFileSelectEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.SELECT, onFileSelectEventHandler);
			fileReference.removeEventListener(Event.CANCEL, onFileCancelEventHandler);

			if (this.interactionState == InteractionStates.BROWSE)
				this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_LOAD_BROWSE ) );
			else
				this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_SAVE_BROWSE ) );
		}

		/**
		 * Method to be called in case the used decide to cancel the option to save or load a file.
		 * The method is related to the browse window.
		 *
		 * @param event
		 *
		 */
		private function onFileCancelEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.SELECT, onFileSelectEventHandler);
			fileReference.removeEventListener(Event.CANCEL, onFileCancelEventHandler);

			this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_CANCEL ) );
		}

		/**
		 * In case an IOError gets called when trying to load or save a file this method will
		 * be dispatched.
		 *
		 * @param event
		 *
		 */
		private function onIOErrorEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.COMPLETE, onCompleteEventHandler);
			fileReference.removeEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

			this.dispatchEvent( new LocalFileErrorEvent( "FileReference: IOErrorEvent.IO_ERROR received: "+event.toString() ) );
		}

		/**
		 * Method to handle the two cases where the complete gets calls:
		 *
		 * <ul>
		 * 	<li>Once complete loading a file</li>
		 * 	<li>Once coomplete saving a file</li>
		 * </ul>
		 *
		 * <p>The internal <code>InteractionStates</code> is used to know which case we are dealing with.
		 *
		 * @param event
		 *
		 */
		private function onCompleteEventHandler(event:Event):void
		{
			fileReference.removeEventListener(Event.COMPLETE, onCompleteEventHandler);
			fileReference.removeEventListener(IOErrorEvent.IO_ERROR, onIOErrorEventHandler);

			if (this.interactionState == InteractionStates.LOAD_FILE)
				this.dispatchEvent( new LocalFileLoadedEvent( fileReference.data ) );
			else
				this.dispatchEvent( new LocalFileEvent( LocalFileEvent.FILE_SAVE_SUCCESSFULLY ) );
		}
	}
}

Example:

  • To load a file: select the load button, which will prompt you to select a text file.
  • To save a file: type in the box and select the save button. You will be prompt to browse and select a place to save the file under.

5

11
May

I am humbled, proud and thankful for being featured in this month’s Developer Spotlight

What is the Developer Spotlight anyway?

The Developer Spotlight program is where Adobe periodically feature one developer out of the entire community, who is pushing the limits of Adobe’s technologies, or contributing to the larger Adobe developer community in a significant way.

I was picked for my usage of Flex and Flash Catalyst to create applications for the web, desktop, and mobile devices. See the post here.

In our field we hardly get recognized, it’s nice to be in the limelight and get noticed. I am humbled, proud and thankful for being featured in this month’s Developer Spotlight on Adobe Developer Connection. Thank you Adobe!

09
May

Skinning FxVideoDisplay Flash 10 component

In Flash 10 Adobe have added a component for Flex Gumbo called FxVideoDisplay class, which is an addition to the VideoDisplay component in Flex and the main features is that the new video player component supports skinning, progressive download, multi-bitrate, and streaming of video right out of the box.

The MXML code to create the FxVideoDisplay component looks as follow:


<FxVideoDisplay id="videoDisplay" />

To create a video player you create the component and set the video file like so:


<FxApplication xmlns="http://ns.adobe.com/mxml/2009" minWidth="1024" minHeight="768">

    <FxVideoDisplay id="videoDisplay" source="videofile_H264.mp4" />

</FxApplication>

Once you compile and run the application, you can see the result, below;

FxVideoDisplay deployed in the browser screen shot

As you can see the component includes toolbar for common operations such as pause, stop, mute, fullscreen, volume and seek. The component can be skinned easily using VideoElement. Let’s take a look how you can skin the component:

Create a new Flex project and call it VideoPlayerGumboExample. In the entry point, VideoPlayerGumboExample.mxml paste the following code:


<FxApplication xmlns="http://ns.adobe.com/mxml/2009" minWidth="1024" minHeight="768">
     <Script>
          <![CDATA[
               import components.VideoSkin;
          ]]>
     </Script>

     <FxVideoDisplay skinClass="{components.VideoSkin}"/>

</FxApplication>

Notice that we included the FxVideoDisplay component and we set the skinClass property to point to a class we will create. The VideoSkin class we will be creating includes the VideoElement and the toolbar.


<Skin xmlns="http://ns.adobe.com/mxml/2009" width="400" height="520">

    <Metadata>
         [HostComponent("mx.components.FxVideoDisplay")]
    </Metadata> 

     <VideoElement  id="videoElement" autoPlay="true"
          source="assets/videofile_H264.mp4">
     </VideoElement>

     <HBox x="5" y="485">
          <FxButton id="playButton" skinClass="{PlayButton}" />
          <FxButton id="stopButton" skinClass="{StopButton}" />
     </HBox>

</Skin>

Let’s examine the code. The component we are using is a Skin component, which is common when we create skins for Flex 4 components.


<Skin xmlns="http://ns.adobe.com/mxml/2009" width="400" height="520">

The HostComponent tag point to the component we are skining.

    <Metadata>
         [HostComponent("mx.components.FxVideoDisplay")]
    </Metadata> 

The VideoElement is necessary to skin the FxVideoDisplay component and we point to the video file we are using where the location of the folder is relative to the FxVideoDisplay component that defines the skin, not to the skin.


     <VideoElement  id="videoElement" autoPlay="true"
          source="assets/videofile_H264.mp4">
     </VideoElement>

Next we can define all the sub classes that are being used by the FxVideoDisplay component such as the pause, play, seek and volume as well as the different video states. The way the sub components are mapped is by the id name, each sub component needs to correspond to the expected id. Additionally, notice that each button point to another skin class such as the playButton point to PlayButton skin.


     <HBox x="5" y="485">
          <FxButton id="playButton" skinClass="{PlayButton}" />
          <FxButton id="stopButton" skinClass="{StopButton}" />
     </HBox>

</Skin>

Lastly, take a look at the playButton skin for the FxButton. We can define all the different states and the pixel (graphic). For graphic we used a simple box with border and a text field that holds the text play.


<Skin xmlns="http://ns.adobe.com/mxml/2009"
     xmlns:d="http://ns.adobe.com/fxg/2008/dt"
     xmlns:ai="http://ns.adobe.com/ai/2008">

     <states>
          <State name="up"/>
          <State name="over"/>
          <State name="down"/>
          <State name="disabled"/>
     </states>

     <Metadata>[HostComponent("mx.components.FxButton")]</Metadata>
     <Rect y="1.5" height="27" width="75" x="1.5" d:userLabel="playButton">
          <fill>
               <SolidColor color="0x3d3d3d"/>
          </fill>
          <stroke>
               <SolidColorStroke color="0xa5a7aa"
                    caps="none" joints="miter" miterLimit="4" weight="3"/>
          </stroke>
     </Rect>
     <TextGraphic width="44" height="14"
          fontFamily="Myriad Pro" lineHeight="120%"
          color="0xffffff" whiteSpaceCollapse="preserve"
          kerning="on" x="20" y="10" ai:knockout="0"
          d:userLabel="Play" id="labelElement" text="Play">
          </TextGraphic>
</Skin>

Create the same skin for the stopButton and name the class StopButton. The only difference is going to be the text field text property will be set to Stop.
Once complete you can compile and run and see the result, below:

FxVideoDisplay skinned

To view and download the complete example, click here.