Advertisement

Create a Flexible File Browser App in Flash: Wrapping Up

by

It’s time to wrap up the Active Premium series we started last week. This short series of two tutorials takes intermediate-to-advanced developers through the process of building an entire file browser app.

Regarding the demo: Please be aware that I'm using a shared server so the preview might not work at all times. Also, I've disabled deletion of files and directories.


Also available in this series:

  1. Create a Flexible File Browser App in Flash: UI and Managing Files
  2. Create a Flexible File Browser App in Flash: Wrapping Up

Step 56: Setting up the Manager

Let's continue from part one where we'd finished our UI and just completed our FileListManager class..

Now that we have a class to handle retrieving the contents of a directory let's use it. Open the FileBrowser class and add a new private property called _manager of type FileListManager.

 
private var _manager:FileListManager;

After that add a new private method called setupManager().

 
private function setupManager():void 
{ 
	 
}

In this method let's create a new instance of the FileListManager class and add some event listeners for it.

 
_manager = new FileListManager(); 
_manager.addEventListener(Event.CHANGE, onListChange); 
_manager.addEventListener(Event.INIT, onOpen);

Also we need to create the event handlers for those listeners so add the following empty event handlers:

 
private function onOpen(e:Event):void  
{ 
	 
} 
 
private function onListChange(e:Event):void  
{ 
	 
}

Tip: Did you know that in FlashDevelop you can generate event handlers automatically? Just click on the name of the handler you want to create, press Ctrl+Shift+1, and select "Generate Event handler".


The last thing you need to do is make a method call for the setupManager() function in the constructor.

 
public function FileBrowser() 
{ 
    configScripts(); 
    setupManager(); 
    setupButtons(); 
}

Important: Keep the order of the method calls as in my examples so that you don't get any errors later on.


Step 57: The FileItem Code

Remember the "FileItem" symbol created in Step 25 (the visual representation of an item from the server)? If yes (or no :)) create a new class in FlashDevelop called FileItem which extends MovieClip and set com.tutsplus.active.fileBrowser.ui as the package.

Also, add a parameter called data of type FileData to the constructor.

 
public function FileItem(data:FileData) 
{ 
	 
}

First of all let's declare the stage instances created in Flash. Above the constructor of this class add these two public properties:

 
public var labelField_txt    :TextField; 
public var state             :MovieClip;

We are going to need two more properties but these will be private.

 
private var _data    :FileData; 
private var _icon    :Bitmap;

The _data property holds a reference to a FileData instance, and the _icon variable will hold a bitmap representing the icon of a specific file type.


Step 58: Constructing the Item

In the constructor of this new FileItem class add the following lines of code:

 
state.gotoAndStop(1); 
doubleClickEnabled = true; 
init(); 
this.data = data;

In the first two lines of code we stop the playback of the state MovieClip at the first frame (remember that it has three frames for each state: normal, over, and selected) and enable double-click events for it.

After that we call a method named init() and use a setter to set the data for this object (next we'll create the method and the setter).

Now create an empty private method called init() and a private setter called data.

 
private function init():void 
{ 
	 
} 
private function set data(value:FileData):void  
{ 
	 
}

Step 59: The Initialization Method

In the init() method write the following snippet:

 
_icon = new Bitmap(); 
_icon.smoothing = true; 
mouseChildren = false; 
addChild(_icon); 
labelField_txt.selectable = false; 
addEventListener(MouseEvent.ROLL_OVER, changeState); 
addEventListener(MouseEvent.ROLL_OUT, changeState);

I think it's quite obvious what we obtain with this. To keep it short we instantiate the icon for the file item, disable all mouse children and add a couple of listeners for mouse events of type MouseEvent.ROLL_OVER and MouseEvent.ROLL_OUT.

Next create the event handler function for those listeners in which we change the state of the item from normal to over an the other way around. States are changed only if the item is not selected (in selected state):

 
private function changeState(event:MouseEvent):void 
{ 
	if (selected == false) 
	{ 
		if (event.type == MouseEvent.ROLL_OVER) 
			state.gotoAndStop(2); 
		else if (event.type == MouseEvent.ROLL_OUT) 
			state.gotoAndStop(1); 
	} 
}

The selected property above is actually a getter for the selected state. So create the following getter and setter:

 
public function set selected(value:Boolean):void 
{ 
	if (value) state.gotoAndStop(3); 
	else state.gotoAndStop(1); 
} 
 
public function get selected():Boolean  
{  
	return state.currentFrame == 3;  
}

The setter modifies the state of the item depending on the parameter value (true or false) and the getter returns true it the current frame of the item (state) is the third frame.


Step 60: Rendering the FileData Info

Inside the FileItem.data setter created in Step 58 add these three lines of code:

 
_data = value; 
labelField_txt.text = _data.name; 
getIcon();

In here we get the reference to the FileData object, add the name of the file in the text field and call a method getIcon() which will set the icon of the FileItem object depending on the file type. We'll go over this method in the next step.


Step 61: Getting the Proper Icon

Create a new private method in the FileItem class named getIcon().

 
private function getIcon():void 
{ 
	 
}

In this method we'll use a switch-case statement to get the proper icon depending on the file extension. Also an if-else statement is required to set the icon for an item of type "directory".

 
if (_data.type == FileData.DIR) 
	_icon.bitmapData = new FolderIco(96, 96); 
else  
	switch(_data.extension)  
	{ 
		case 'jpg': 
		case 'png': 
		case 'gif': 
			_icon.bitmapData = new ImageIco(96, 96); 
			break; 
		case 'mp3': 
		case 'flac': 
		case 'aac': 
			_icon.bitmapData = new AudioIco(96, 96); 
			break; 
		case 'avi': 
		case 'mp4': 
		case 'flv': 
			_icon.bitmapData = new VideoIco(96, 96); 
			break; 
		case 'php': 
		case 'php4': 
		case 'php5': 
			_icon.bitmapData = new PHPIco(96, 96); 
			break; 
		case 'txt': 
		case 'ini': 
		case 'inc': 
			_icon.bitmapData = new TextIco(96, 96); 
			break; 
		case 'html': 
		case 'htm': 
			_icon.bitmapData = new HTMLIco(96, 96); 
			break; 
		case 'swf': 
		case 'fla': 
			_icon.bitmapData = new SWFIco(96, 96); 
			break; 
		default: 
			_icon.bitmapData = new FileIco(96, 96); 
			break; 
	}

The classes are the icons assets imported in the library earlier. Feel free to modify this if you want to add or remove icon types.

Note: We use multiple case statements to set the same icon for similar file types. If a case statement does not have a break; it will execute the next case statement also.


Step 62: Required Getters

To complete our class we need to add some public getters for the FileData object held by the FileItem. These getters are used to return the file name, type, extension and file path.

Above all we need one more getter to return the unique ID of a file item. This unique ID will be the child index of the item in the display list so we don't need to create another property.

Now that I've explained what we need to add write the following getters in the FileItem class:

 
public function get extension():String 
{ 
	return _data.extension; 
} 
 
public function get path():String 
{  
	return _data.path;  
} 
 
public function get fileName():String 
{  
	return _data.name;  
} 
 
public function get type():String 
{  
	return _data.type;  
}

As I said these will return the file extension, file path, file name and file type of the _data property which is a reference to an instance of the FileData class.

Lastly type in the id getter:

 
public function get id():uint  
{  
	this.parent.getChildIndex(this);  
}

As you can see this will return the index of the object in its container. As explained above, this will give us a unique ID for the file item out of all items.


Step 63: Linking the Class

We will no longer modify this class so this is the complete code with comments for it:

 
package com.tutsplus.active.fileBrowser.ui  
{ 
	import com.tutsplus.active.fileBrowser.data.FileData; 
	import flash.display.Bitmap; 
	import flash.display.MovieClip; 
	import flash.events.MouseEvent; 
	import flash.text.TextField; 
	 
	/** 
	 * A visual representation of an item in the list. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FileItem extends MovieClip 
	{ 
		private var _data	:FileData; 
		private var _icon	:Bitmap; 
		 
		public var labelField_txt	:TextField; 
		public var state			:MovieClip; 
		 
		/** 
		 * Creates a new FileItem object 
		 *  
		 * @param	data	The FileData object to be represented. 
		 */ 
		public function FileItem(data:FileData)  
		{ 
			state.gotoAndStop(1); 
			doubleClickEnabled = true; 
			init(); 
			this.data = data; 
		} 
		 
		/** 
		 * Initialization function. 
		 */ 
		private function init():void 
		{ 
			_icon = new Bitmap(); 
			_icon.smoothing = true; 
			mouseChildren = false; 
			addChild(_icon); 
			labelField_txt.selectable = false; 
			addEventListener(MouseEvent.ROLL_OVER, changeState); 
			addEventListener(MouseEvent.ROLL_OUT, changeState); 
		} 
		 
		/** 
		 * Event handler for mouse events. It changes the state of the FileItem 
		 * to NORMAL, OVER or SELECTED depending on the event type. 
		 * @param	event 
		 */ 
		private function changeState(event:MouseEvent):void 
		{ 
			if (selected == false) 
			{ 
				if (event.type == MouseEvent.ROLL_OVER) 
					state.gotoAndStop(2); 
				else if (event.type == MouseEvent.ROLL_OUT) 
					state.gotoAndStop(1); 
			} 
		} 
		 
		/** 
		 * The <i>selected</i> state property. 
		 */ 
		public function set selected(value:Boolean):void 
		{ 
			if (value) state.gotoAndStop(3); 
			else state.gotoAndStop(1); 
		} 
		 
		/** 
		 * The <i>selected</i> state property. 
		 */ 
		public function get selected():Boolean  
		{  
			return state.currentFrame == 3;  
		} 
		 
		/** 
		 * The ID of the FileItem object. This is based 
		 * on the child index in the container. 
		 */ 
		public function get id():uint  
		{  
			return this.parent.getChildIndex(this);  
		} 
		 
		/** 
		 * The data to be represented by the FileItem object 
		 */ 
		private function set data(value:FileData):void 
		{ 
			_data = value; 
			labelField_txt.text = _data.name; 
			getIcon(); 
		} 
		 
		/** 
		 * The extension of the FileData being represented. 
		 */ 
		public function get extension():String 
		{ 
			return _data.extension; 
		} 
		 
		/** 
		 * The path of the FileData being represented. 
		 */ 
		public function get path():String 
		{  
			return _data.path;  
		} 
		 
		/** 
		 * The file name of the FileData being represented. 
		 */ 
		public function get fileName():String 
		{  
			return _data.name;  
		} 
		 
		/** 
		 * The type of the FileData being represented. 
		 */ 
		public function get type():String 
		{  
			return _data.type;  
		} 
		 
		/** 
		 * Gets the appropriate icon for the FileItem. 
		 * The icon is based on the file extension and type 
		 */ 
		private function getIcon():void 
		{ 
			if (_data.type == FileData.DIR) 
				_icon.bitmapData = new FolderIco(96, 96); 
			else  
				switch(_data.extension)  
				{ 
					case 'jpg': 
					case 'png': 
					case 'gif': 
						_icon.bitmapData = new ImageIco(96, 96); 
						break; 
					case 'mp3': 
					case 'flac': 
					case 'aac': 
						_icon.bitmapData = new AudioIco(96, 96); 
						break; 
					case 'avi': 
					case 'mp4': 
					case 'flv': 
						_icon.bitmapData = new VideoIco(96, 96); 
						break; 
					case 'php': 
					case 'php4': 
					case 'php5': 
						_icon.bitmapData = new PHPIco(96, 96); 
						break; 
					case 'txt': 
					case 'ini': 
					case 'inc': 
						_icon.bitmapData = new TextIco(96, 96); 
						break; 
					case 'html': 
					case 'htm': 
						_icon.bitmapData = new HTMLIco(96, 96); 
						break; 
					case 'swf': 
					case 'fla': 
						_icon.bitmapData = new SWFIco(96, 96); 
						break; 
					default: 
						_icon.bitmapData = new FileIco(96, 96); 
						break; 
				} 
		} 
	} 
 
}

Now that we have completed the class let's link it to the "FileItem" symbol created in the interface steps. Open your FLA and right-click on the "FileItem" symbol from the library, then select Properties. In the Properties window check the "Export for ActionScript" box and set the base class to com.tutsplus.active.fileBrowser.ui.FileItem.

You can press Ctrl+Enter to test the movie but you will notice no change yet.


Step 64: The Dialog Box

Now it's time to work on the dialog box we created earlier. This box is a popup window which will appear when an action like Delete or Rename is selected to ask us about it and display information about the process.

In FlashDevelop, create a new class named DialogBox, give it a package name of com.tutsplus.active.fileBrowser.ui and set its base class to MovieClip.

First of all, as done before, let's declare the stage instances that were created in Flash in Step 18 and 19 (the text field and the two buttons).

 
public var message_txt           :TextField; 
public var ok_btn, cancel_btn    :BasicButton;

For each of those buttons we'll need a callback function which we can easily change from outside the class, so add the following two properties:

 
public var onOK        :Function; 
public var onCancel    :Function;

I think it's obvious enough which function goes for which button.

Lastly we'll need two constants to refer to those buttons:

 
public static const OK		:uint = 1; 
public static const CANCEL	:uint = 0;

We'll use those constants to disable or enable the buttons through a specific method later on.


Step 65: The Constructor

Now that every property is in place, let's set up the constructor for this DialogBox class. Add the following lines of code in the constructor:

 
onOK = new Function(); 
onCancel = new Function(); 
ok_btn.addEventListener(MouseEvent.MOUSE_UP, onAccept); 
cancel_btn.addEventListener(MouseEvent.MOUSE_UP, onDeny);

The only thing that we need to do in the constructor of this class is instantiate the two functions and add event listeners for the buttons, so go ahead and create the two event handlers onAccept() and onDeny. In these handlers we will only make calls to the onOK() and onCancel() functions so add those too (remember that these functions will be defined at runtime depending on the type of action we perform).

 
private function onDeny(e:MouseEvent):void  
{ 
	onCancel(); 
} 
 
private function onAccept(e:MouseEvent):void  
{ 
	onOK(); 
}

Step 66: The hidden Property

Next let's add a public setter to change the visibility of the dialog box. In this setter we'll also reset the dialog box message and button functions.

 
public function set hidden(value:Boolean):void 
{ 
	visible = !value; 
	if (value == true) 
	{ 
		onOK = new Function(); 
		onCancel = new Function(); 
		ok_btn.enabled = cancel_btn.enabled = true; 
		message = ''; 
	} 
}

And because the message displayed will be set from outside the class we are going to create a setter and a getter for that value.

 
public function set message(value:String):void  
{ 
	message_txt.text = value; 
} 
 
public function get message():String  
{ 
	return message_txt.text; 
}

Step 67: Modifying a Button's State

We also have to add the possibility to modify the state of a button (by "state" I mean enabled or disabled) from outside the class so we'll add the modButton() method to do that for us.

 
public function modButton(button:uint, enabled:Boolean = false):void 
{ 
	switch(button) 
	{ 
		case OK: 
			ok_btn.enabled = enabled; 
			break; 
		case CANCEL: 
			cancel_btn.enabled = enabled; 
			break; 
		default: 
			throw new Error('Invalid button'); 
			break; 
	} 
}

When this method is called, a switch case statement is used to see if the button specified exists (we specify the button using the OK and CANCEL constants we created earlier) and the state is changed according to the value of the enabled parameter.


Step 68: Adding a Rename Mode

Because this dialog box will be used when renaming files the text field will need to be able to be changed to an input field. The renameMode will be handling the change between those two modes.

 
public function set renameMode(value:Boolean):void 
{ 
	message_txt.selectable = value; 
	message_txt.background = value; 
	message_txt.type = value ? TextFieldType.INPUT : TextFieldType.DYNAMIC; 
	message_txt.backgroundColor = 0x040912; 
	message_txt.restrict = value ? "a-zA-Z0-9 .,!~_&%$€#+*\\-":null; 
}

To keep it short, in this method we make the text field selectable and make its background visible (if value parameter is set to true) after which the type of the text field is changed accordingly. And because this will be a user input we need to restrict the accepted characters to safe ones.


Step 69: Linking the Class to the Symbol

In the last step we added the last piece of our DialogBox class so let's link it to the symbol created in Flash. Open your FLA and set the base class for the "DialogBox" symbol in the library to com.tutsplus.active.fileBrowser.ui.DialogBox.

And the full code with comments for the DialogBox class is:

 
package com.tutsplus.active.fileBrowser.ui  
{ 
	import flash.display.MovieClip; 
	import flash.events.MouseEvent; 
	import flash.text.TextField; 
	import flash.text.TextFieldType; 
	 
	/** 
	 * A pop-up dialog box. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class DialogBox extends MovieClip 
	{ 
		/** 
		 * The function which will pe executed when the  
		 * OK button is pressed. 
		 */ 
		public var onOK		:Function; 
		 
		/** 
		 * The function which will pe executed when the  
		 * Cancel button is pressed. 
		 */ 
		public var onCancel	:Function; 
		 
		public var message_txt			:TextField; 
		public var ok_btn, cancel_btn	:BasicButton; 
		 
		public static const OK		:uint = 1; 
		public static const CANCEL	:uint = 0; 
		 
		public function DialogBox()  
		{ 
			onOK = new Function(); 
			onCancel = new Function(); 
			ok_btn.addEventListener(MouseEvent.MOUSE_UP, onAccept); 
			cancel_btn.addEventListener(MouseEvent.MOUSE_UP, onDeny); 
		} 
		 
		/** 
		 * The dialog box hidden property. 
		 * When setting it to <i>true</i> the <i>onOK</i> 
		 * and <i>onCancel</i> functions will be reset. 
		 */ 
		public function set hidden(value:Boolean):void 
		{ 
			visible = !value; 
			if (value == true) 
			{ 
				onOK = new Function(); 
				onCancel = new Function(); 
				ok_btn.enabled = cancel_btn.enabled = true; 
				message = ''; 
			} 
		} 
		 
		/** 
		 * Event handler for when the OK button is pressed. 
		 *  
		 * @param	event	MouseEvent. 
		 */ 
		private function onAccept(event:MouseEvent):void 
		{ 
			onOK(); 
		} 
		 
		/** 
		 * Event handler for when the Cancel button is pressed. 
		 *  
		 * @param	event	MouseEvent. 
		 */ 
		private function onDeny(event:MouseEvent):void 
		{ 
			onCancel(); 
		} 
		 
		/** 
		 * Modify the state of a button. 
		 * @param	button		Use DialogBox.OK or DialogBox.CANCEL. 
		 * @param	enabled		If not set it will default to false. 
		 */ 
		public function modButton(button:uint, enabled:Boolean = false):void 
		{ 
			switch(button) 
			{ 
				case OK: 
					ok_btn.enabled = enabled; 
					break; 
				case CANCEL: 
					cancel_btn.enabled = enabled; 
					break; 
				default: 
					throw new Error('Invalid button'); 
					break; 
			} 
		} 
		 
		/** 
		 * If set to true the dialog box will get into rename mode. 
		 * If set to true the text field becomes an input field. 
		 */ 
		public function set renameMode(value:Boolean):void 
		{ 
			message_txt.selectable = value; 
			message_txt.background = value; 
			message_txt.type = value ? TextFieldType.INPUT : TextFieldType.DYNAMIC; 
			message_txt.backgroundColor = 0x040912; 
			message_txt.restrict = value ? "a-zA-Z0-9 .,!~_&%$€#+*\\-":null; 
		} 
		 
		/** 
		 * The message of the dialog box. 
		 */ 
		public function set message(value:String):void  
		{ 
			message_txt.text = value; 
		} 
		 
		/** 
		 * The message of the dialog box. 
		 */ 
		public function get message():String  
		{ 
			return message_txt.text; 
		} 
	} 
 
}

Step 70: File Browser's Initialization Method

Up until now we haven't made any more "visual" changes to our interface so let's do some now. Open the FileBrowser class and add a new private method called init(), as we have in other classes.

 
private function init():void 
{ 
 
}

In this method let's make the preloader symbol invisible, hide the dialog box and - for the first time - get the list of files from the server. Type inside the init() method's body these lines of code:

 
preloader.visible = false;	// Makes the preloader symbol invisible 
dialog_box.hidden = true;	// Hides the dialog box instance 
 
_manager.getFileList();

In the last line we use the getFileList() method of the FileListManager class to get the list of files from the server. You will also need to add a call to this method in the constructor of the FileBrowser class just after the setupButtons() method call.

 
public function FileBrowser() 
{ 
	configScripts(); 
	setupManager(); 
	setupButtons(); 
	init(); 
}

Before going further we'll need to declare the instance of this symbol as a public property of the FileBrowser class.

 
public var dialog_box:DialogBox;

If you test the movie now in Flash you'll notice that the dialog box and the preloader have vanished. To see that actual data is received from the server you can add the following trace statement in the FileListManager class, on the last line of the createList method (after dispatching the event):

 
trace(_files);

This will trace the contents of the _files[] array which hold the file items received from the server. These items will be showed as a string representation thanks to the toString() method we added in the FileData class (told you it's good for debugging :) ).

If all things are in order and you have added some files and folders to the root folder for the file browser, you should see something like this in the output panel back in Flash:

 
[directory(Images directory)],[file(crank-ringtone.mp3)],[file(glowing balls.jpg)],[file(Imagine0115.jpg)],[file(index.html)]

Step 71: Responding to List Changes

Remember the two event listeners we added for the file list manager back in Step 56? Probably not, but now it's time to remind yourself because we'll add some functionality to those event listeners.

First of all we added an event handler for the INIT event fired by the FileListManager class. This event is fired when a request to the server has just been sent representing the start of a loading process so we need to show the preloader.

This is the only thing we'll add in the onOpen() event handler.

 
private function onOpen(e:Event):void  
{ 
	preloader.visible = true; 
}

The onListChange() event handler is executed when data has been received from the server meaning that the loading process is over so we'll need to hide the preloader and so set its visible property back to false.

In this event handler we'll also add some code to update those two text fields created in Step 13 (the one used to show the path of the browsed directory and the one used to show the number of items in that directory).

This is the whole code for the onListChange() event handler (for now):

 
var multiple:Boolean = _manager.numFiles != 1; 
 
preloader.visible = false; 
numItems_txt.text = multiple?_manager.numFiles + ' items':'1 item'; 
location_txt.text = 'Location: ' + _manager.currentPath; 
 
back_btn.enabled = _manager.hasHistory();

For the number of items we use a ternary operation to check the number of items in the directory so that we can change the text accordingly (if only one item exists it will display "1 item" else it will display "n items").

To find the total number of file items in the currently browsed directory (the total number of file items in the file list) we use the numFiles property of the FileListManager class.

If you test the movie now you'll notice the text fields updating after load is complete.



Step 72: The Visual List

By now it's time to code the last piece of the interface. Can you guess which is that? If not, I'm talking about the list.

So let's have a brief about what we want to achieve. Basically we need a grid list to display the file items in contained by the FileListManager. All the items will be added to a holder and positioned grid type. This holder will have a mask the size of the dark list background created in Flash and it will also have a scroll bar which will move it up or down.

This list also needs a reference to our file manager so that it can update its content correspondingly.

So let's start by creating the class in FlashDevelop. Name the class FilesList, set its package to com.tutsplus.active.fileBrowser.ui and as base class use flash.display.MovieClip.

All elements we talked about above will be added dynamically so there's no need to declare stage instances because we added none back in Flash. We are going to declare the elements of the list as private properties.

 
private var _sb            :SimpleScrollbar; 
private var _holder        :MovieClip; 
private var _mask          :MovieClip; 
private var _dataMan       :FileListManager; 
private var _items         :Array;

The _sb object is of type SimpleScrollbar which is a class I've created and use as a scroll bar in my applications. I will not explain how this scrollbar class works as there are many detailed tuts about that stuff out there.

Note: Make sure you have copied the class in your workspace as explained in Step 28 (the "alexxcz" folder from the "com" source).

The _holder MovieClip will be the holder for our items.

As said earlier we need a mask for the holder and that's the purpose of the _mask MovieClip.

The reference to the FileListManager instance will be held by the _dataMan property.

Lastly we'll need to have a reference to all added items in the list so we'll put them in the _items array.

Below these private properties also add these private constants which will help us in creating the mask and positioning the items:

 
private const LIST_WIDTH      :uint = 539;    // The width of the list background 
private const LIST_HEIGHT     :uint = 282;    // The height of the list background 
private const ITEM_SPACING    :uint = 10;     // Spacing added between items

Also add the following line in the constructor of our class:

 
_items = [];

Step 73: Creating the Holder

First of all we'll create the holder so add a new private method to our FilesList class named createHolder(). In this method we simply instantiate the holder, add it to the display list and position it.

 
private function createHolder():void 
{ 
	_holder = new MovieClip(); 
	addChild(_holder); 
	_holder.x = _holder.y = 4; 
}

We position it to an x and y of 4 because it need an offset from the background. If you look closely at the "list_bg.png" file you'll see that it has an outer glow of approximately 2 pixels and we add 2 more so that it won't be right on the edge.

After writing the method, add a call to it in the constructor just after instantiating the _items[] array.

 
_items = []; 
createHolder();

Step 74: Creating the Mask

Next step is to create the mask for the holder so type in the following private method in the FilesList class:

 
private function createMask():void 
{ 
	_mask = new MovieClip(); 
	_mask.x = _mask.y = 4; 
	addChild(_mask); 
     
	with(_mask){ 
		graphics.beginFill(0); 
		graphics.drawRect(0, 0, LIST_WIDTH, LIST_HEIGHT); 
		graphics.endFill(); 
	} 
	_holder.mask = _mask; 
}

As done with the holder we instantiate the mask, add it to the display list and set its x and y offset to 4.

Next we fill draw a black rectangle in it using the drawing API and the constants added earlier as sizes.

Tip: To access multiple properties or methods of the same object consecutively you can use an with statement. The following example:

 
with (object) 
{ 
	property = 100; 
    name = 'Object Name'; 
    someMethodCall(); 
}

is the short version of:

 
object.property = 100; 
object.name = 'Object Name'; 
object.someMethodCall();

Lastly we mask the holder using the mask property and this MovieClip.

Last step in creating the mask is adding a call to this method in the constructor. Add the method call immediately after the createHolder(); method call. As before, it's important to get the order right; in this case, we access a property in createMask() that is only instantiated in createHolder().


Step 75: Creating the Scrollbar

Last thing we need to do to complete the list is to create a scrollbar. We do this using the createScrollbar() private method:

 
private function createScrollbar():void 
{ 
	_sb = new SimpleScrollbar(); 
	_sb.setColors(0x163862, 0x06264d); 
	addChild(_sb); 
	_sb.setSize(10, height-8); 
	_sb.x = _mask.width + _sb.width; 
	_sb.y = 4; 
}

In this method we instantiate the SimpleScrollbar object, set its colors using the setColors() method (the first parameter in this method represents the color of the dragging thumb and the second parameter represents the color of the track), set it's size to 10 pixels width and the same height as the list (deducting 4 pixels from top and 4 from bottom because of the offset).

Lastly we add it to the display list and position it at the right edge of the list.

Also add a method call for this method in the constructor of the class just after the createMask() method call.


Step 76: Linking to the FileListManager

Our list elements are all in place and now we need to add a method to link the file list manager created in the FileBrowser class to this list. For that we'll use a public setter called dataManager:

 
public function set dataManager(value:FileListManager):void 
{ 
	_dataMan = value; 
	initDataMan(); 
}

After getting a reference to the manager we need to update this list and for that we use the initDataMan() method.

 
private function initDataMan():void 
{ 
	_dataMan.addEventListener(Event.CHANGE, onListChange); 
	_sb.addEventListener(Event.SCROLL, onScroll); 
}

In this method we simply add an event listener to the manager of type Event.CHANGE because it will dispatch this event when a new filelist has been retrieved from the server.

Also we add a event listener for the scroll bar of type Event.SCROLL. This event is dispatched whenever the position of the scrollbar's thumb is changed (using the mouse wheel, dragging it, clicking on the track) and we need to move the holder in sync with the thumb position. Create the two event handler functions, onListChange() and onScroll(), for these event listeners:

 
private function onScroll(e:Event):void  
{ 
	 
} 
 
private function onListChange(e:Event):void  
{ 
	 
}

Step 77: Scrolling the List

In the onScroll() event handler, add the following line:

 
_holder.y = -_sb.scrollValue * (_holder.height - _mask.height + ITEM_SPACING * 2) + 4;

This will move the holder vertically. We are using the scroll value of the scroll bar and the difference between the visible list height (mask height) and full list height (holder height) to calculate the movement percent. Also items will have a 10 pixel spacing on the Y axis and the Y offset so we'll need to take those into account.


Step 78: Updating the Content

The onListChange() event handler function is used to update the content of the list each time the manager's list is changed.

 
private function onListChange(e:Event):void 
{ 
	_holder.y = 4; 
	_sb.scrollValue = 0; 
	while (_items.length > 0)  
	{ 
		_holder.removeChild(_items[0]); 
		_items.shift(); 
	} 
	for (var i:int = 0; i < _dataMan.numFiles; i++)  
	{ 
		var fi:FileItem = new FileItem(_dataMan.getFileAt(i)); 
		_items.push(fi); 
	} 
	positionItems(); 
}

First of all before changing the content we need to reset the position of the holder and the scroll bar back to their default values. That's what the first two lines do.

After that we need to remove the existing items from the holder and from the _items[] array. We do that by using a while loop until no more items remain.

After cleaning the list a for loop is used to create FileItem objects for each of the FileData item in the manager's list.

Lastly, we make a call to the positionItems() method which positions all the items in our list holder.


Step 79: Positioning the Items

The next method may look a bit complicated if you haven't done much positioning by code until now but in fact it's quite easy and straightforward:

 
private function positionItems():void 
{ 
	var colNum:uint = LIST_WIDTH / 96; 
	var rowNum:uint = 0; 
	 
	for (var i:int = 0; i < _items.length; i++)  
	{ 
		_items[i].x = ITEM_SPACING + ITEM_SPACING * (i%colNum) + i%colNum * 96; 
		_items[i].y = ITEM_SPACING + rowNum * ITEM_SPACING + rowNum * 128; 
		if ((i + 1) % colNum == 0) 
			rowNum++; 
		_holder.addChild(_items[i]); 
	} 
	_sb.contentRatio = _mask.height / _holder.height; 
}

First of all we need to get the number of columns in the list (remember that we're doing a grid positioning) so we divide the list width by the width of an item. The rowNum variable will not hold the total number of rows but the current row, so it will start from 0 (meaning first row).

We use a single for loop to position each item in its corresponding column (the X position). We find its X position with this formula: x position = width of one item * remainder of item's position in the list / total number of columns.

For the current list width we'll have five columns and so the first five items will be positioned one after another in every column, the next 5 items will be positioned on the X axis beginning from the left side of the list (one on each column) and so on.

At every 5 items we change increment the row number as the next items will be placed on a new row. We multiply the row number with the height of an item (which is 128 pixels) to set the Y position of an item.

After positioning an item we add it to the holder.

The last line in this method sets the contentRatio property of the scrollbar to the content ratio between the mask (visible list) and the holder (full list). By setting this property the thumb of the scrollbar will scale accordingly to this ratio.

This is the last thing we needed to add to our list to complete it. Here's the full code with comments for the FilesList class:

 
package com.tutsplus.active.fileBrowser.ui 
{ 
	import com.tutsplus.active.fileBrowser.data.FileListManager; 
	import com.alexxcz.ui.SimpleScrollbar; 
	import flash.display.MovieClip; 
	import flash.events.Event; 
	 
	/** 
	 * A list of thumbnails used to display directory contents 
	 * received from the server. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FilesList extends MovieClip 
	{ 
		private var _sb			:SimpleScrollbar; 
		private var _holder		:MovieClip; 
		private var _mask		:MovieClip; 
		private var _dataMan	:FileListManager; 
		private var _items		:Array; 
		 
		private const LIST_WIDTH	:uint = 539; 
		private const LIST_HEIGHT	:uint = 282; 
		private const ITEM_SPACING	:uint = 10; 
		 
		public function FilesList() 
		{ 
			_items = []; 
			createHolder(); 
			createMask(); 
			createScrollbar(); 
		} 
		 
		/** 
		 * This method creates the holder of the list items. 
		 */ 
		private function createHolder():void 
		{ 
			_holder = new MovieClip(); 
			addChild(_holder); 
			_holder.x = _holder.y = 4; 
		} 
		 
		/** 
		 * This method creates the mask for the items holder. 
		 */ 
		private function createMask():void 
		{ 
			_mask = new MovieClip(); 
			addChild(_mask); 
			_mask.x = _mask.y = 4; 
			with(_mask){ 
				graphics.beginFill(0); 
				graphics.drawRect(0, 0, LIST_WIDTH, LIST_HEIGHT); 
				graphics.endFill(); 
			} 
			_holder.mask = _mask; 
		} 
		 
		/** 
		 * This method creates a scrollbar for the list. 
		 */ 
		private function createScrollbar():void 
		{ 
			_sb = new SimpleScrollbar(); 
			_sb.setColors(0x163862, 0x06264d); 
			addChild(_sb); 
			_sb.setSize(10, height-8); 
			_sb.x = _mask.width + _sb.width; 
			_sb.y = 4; 
		} 
		 
		/** 
		 * The dataManager property binds a FileListManager data model 
		 * with the list. 
		 */ 
		public function set dataManager(value:FileListManager):void 
		{ 
			_dataMan = value; 
			initDataMan(); 
		} 
		 
		/** 
		 * This method adds required event listeners to the data manager and 
		 * the scrollbar. 
		 */ 
		private function initDataMan():void 
		{ 
			_dataMan.addEventListener(Event.CHANGE, onListChange); 
			_sb.addEventListener(Event.SCROLL, onScroll); 
		} 
		 
		/** 
		 * Event handler for when the mouse wheel is scrolled. 
		 *  
		 * @param	event	Event of type SCROLL. 
		 */ 
		private function onScroll(e:Event):void 
		{ 
			_holder.y = -_sb.scrollValue * (_holder.height - _mask.height + ITEM_SPACING * 2) +4; 
		} 
		 
		/** 
		 * Event handler for when the list of files has changed. 
		 * @param	event 
		 */ 
		private function onListChange(e:Event):void 
		{ 
			_holder.y = 4; 
			_sb.scrollValue = 0; 
			while (_items.length > 0)  
			{ 
				_holder.removeChild(_items[0]); 
				_items.shift(); 
			} 
			for (var i:int = 0; i < _dataMan.numFiles; i++)  
			{ 
				var fi:FileItem = new FileItem(_dataMan.getFileAt(i)); 
				_items.push(fi); 
			} 
			positionItems(); 
		} 
		 
		/** 
		 * This method positions all items in the list. 
		 */ 
		private function positionItems():void 
		{ 
			var colNum:uint = LIST_WIDTH / 96; 
			var rowNum:uint = 0; 
			 
			for (var i:int = 0; i < _items.length; i++)  
			{ 
				_items[i].x = ITEM_SPACING + ITEM_SPACING * (i%colNum) + i%colNum * 96; 
				_items[i].y = ITEM_SPACING + rowNum * ITEM_SPACING + rowNum * 128; 
				if ((i + 1) % colNum == 0) 
					rowNum++; 
				_holder.addChild(_items[i]); 
			} 
			_sb.contentRatio = _mask.height / _holder.height; 
		} 
	} 
}

Step 80: Linking the Class to the List

Now that we have the code for the list we need to link it to the FilesList symbol from the library back in Flash. Go ahead and set the base class for that symbol to com.tutsplus.active.fileBrowser.ui.FilesList. Now that the list is linked to its class we need to declare the stage instance back into the FileBrowser class and link the list to the list manager.

Open the FileBrowser class and add a new public property called list of type FilesList (remember to import the class also import com.tutsplus.active.fileBrowser.ui.FilesList;):

 
public var list:	FilesList;

Next create a new private method and name it setupList().

 
private function setupList():void 
{ 
	list.dataManager = _manager; 
	list.addEventListener(MouseEvent.DOUBLE_CLICK, onItemOpen); 
}

In this method we set the dataManager property of the list (set the reference to the instance of the FileListManager class) and add a double-click event listener. Create the event handler for that listener and leave it empty as we'll talk about it in the next step.

 
private function onItemOpen(e:MouseEvent):void  
{ 
	 
}

And the last thing we need to do is call the setupList() method from the class constructor so add it between the setupButtons() and init() method calls.

 
public function FileBrowser() 
{ 
	configScripts(); 
	setupManager(); 
	setupButtons(); 
	setupList(); 
	init(); 
}

Now that all things are set you can go back in Flash and test the movie. You should get something similar to the image below, depending on what content you have in your root folder on the server.



Step 81: Opening Files and Folders

As I promised, in this step we will go through the onItemOpen() event handler. This handler is called whenever an item in the list is double-clicked.

In this method we need to define two different actions because there are two types of file items: directories and files. If a directory is double-clicked we want to get its contents, and if a file is double-clicked we want to download it.

Before everything else we need to see if the double-clicked object is a FileItem object. We do this by adding a simple if statement.

 
private function onItemOpen(e:MouseEvent):void  
{ 
	if (e.target is FileItem) 
	{ 
		 
	} 
}

If the targeted object is of type FileItem then we'll use an if-else statement to check if it's a directory or a file. Add the following lines inside the previously created if statement to do just that:

 
if (e.target is FileItem) 
{ 
	if (FileItem(e.target).type == FileData.DIR) {  
		_manager.getFileList(FileItem(e.target).path); 
	} 
	else if (FileItem(e.target).type == FileData.FILE) { 
		onDownload(); 
	} 
}

Note: Don't forget to import the used classes (FileData and FileItem) if you are just pasting in the code or if you don't use an editor that does the imports automatically.

OK. Basically this is what we do:

  • If the item is a directory, then we get the contents of that directory using the getFileList() method of the FileListManager class. We also need to pass in the path of the directory when using this method; if we don't, the root directory will be displayed.
  • If the item is a file, then we want to download it so we're going to call the onDownload() method which is the event handler for the download_btn button.

Because we are going to use the event handler of a button we need to change it a bit so that we don't need to pass a MouseEvent object as a parameter. To do this give the e parameter a default value of null.

 
private function onDownload(e:MouseEvent = null):void 
{ 
	 
}

Step 82: Selection System

Finally things are starting to take shape. Now that we have the list of files, before diving into the file operations we need a selection system. I have in mind a selection system similar to the one in Windows OS with multiple selection support using the Ctrl and Shift keys.

Open FlashDevelop and create a new class called SelectionManager, set its base class to flash.events.EventDispatcher and its package to com.tutsplus.active.fileBrowser.ui.

Now that we have the class let's add some properties to it:

 
private var _items    :Array; 
private var _last     :FileItem; 
private var _holder   :DisplayObject;
  • The _items[] array will hold the selected items.
  • The _last FileItem object will be a reference to the last item selected.
  • The _holder will be a reference to the holder of those items (in our case the files list).

The only thing we need to do in the constructor of this class is to instantiate the _items[] array, so let's do that now.

 
public function SelectionManager()  
{ 
	_items = []; 
}

Step 83: Setting the Holder

First let's add a public setter to get the reference to the list from outside the class, as this is our main object of interest. We also need to add an event listener for mouse events of type CLICK.

 
public function set holder(value:DisplayObject):void  
{ 
	_holder = value; 
	_holder.addEventListener(MouseEvent.CLICK, onClick); 
} 
 
private function onClick(e:MouseEvent):void  
{ 
	 
}

Step 84: Responding to CLICK Events

The first thing we need to add in the onClick() event handler is an if-statement to check whether the clicked item is of type FileItem.

 
private function onClick(e:MouseEvent):void 
{ 
	if (e.target is FileItem) 
	{ 
		if (e.shiftKey)	shiftSelect(FileItem(e.target)); 
		else if (e.ctrlKey) ctrlSelect(FileItem(e.target)); 
		else normalSelect(FileItem(e.target)); 
		 
		dispatchEvent(new Event(Event.SELECT)); 
	} 
	else deselect(); 
}

If the condition is true we are going to check if the Shift key or the Control key or none of them is pressed and call the respective method (and also dispatch an event of type SELECT to notify any listener that the selection has changed).

If the clicked item is not a FileItem it will deselect all selected items.

We don't have any of these methods so add the following shells for them and we'll go through each, one by one:

 
private function normalSelect(target:FileItem):void 
{ 
	 
} 
 
private function ctrlSelect(target:FileItem):void 
{ 
	 
} 
 
private function shiftSelect(target:FileItem):void 
{ 
	 
} 
 
public function deselect():void 
{ 
	 
}

Step 85: Deselecting Items

It might look awkward but we'll start first by completing the method used to deselect all items.

In the body of the deselect() method add the following two lines:

 
clear(); 
dispatchEvent(new Event(Event.SELECT));

As you can see we are making a call to another method called clear() and dispatching an event of type SELECT.

The clear() method is the actual method which deselects all items. We use the clear() method to do the deselection because later on we will need to deselect items without dispatching the SELECT event once more.

 
private function clear():void 
{ 
	while(_items.length > 0) 
	{ 
		_items[0].selected = false; 
		_items.shift(); 
	} 
}

In the clear() method we simply use a while loop to deselect all selected items and remove them from the _items[] array.


Step 86: Normal Selection

The normal selection is quite simple. If only one item is clicked and neither one of the Shift or Ctrl key are pressed all items are deselected (if any are selected) and that one item becomes selected. Add the following lines in the normalSelect() method to do just that.

 
clear(); 
target.selected = true; 
_items[0] = _last = target;

In this method we call the clear() method to clear the selection, we set the selected state of the target to true after which we add the item to the selection array and set the last item selected to it.

Note: The last item selected will be used in Shift selection to select all items between it and the newly clicked item.


Step 87: Control Selection

The control selection is a type of selection which adds to the selection the clicked item if this one is not selected or removes it from the selection if it's already selected.

This is exactly what we do in the ctrlSelect() method:

 
private function ctrlSelect(target:FileItem):void 
{ 
	if (target.selected == false) { 
		target.selected = true; 
		_items.push(target); 
	}else { 
		target.selected = false 
		_items.splice(_items.indexOf(target), 1); 
	} 
	_items.sortOn('id', Array.NUMERIC); 
	_last = target; 
}

We use an if statement to check if the item is not selected. If the condition is true we set its state to selected and add it to the list of selected items. If the item is already selected (is in selected state) we deselect it and remove it from the selection list.

In the last two lines we sort the array by the items id property (if you recall this reflects the index in the display list of the item which is a unique id) and set the last selected item to the current one.


Step 88: Shift Selection

The last type of selection we need to add is multiple selection using the Shift key. This one is a bit tricky so let me explain it to you.

The Shift selection selects all items from the last selected item to the currently clicked item (see the preview SWF included with the tutorial), so we need to find a way to do that.

 
private function shiftSelect(target:FileItem):void 
{ 
	if (_items.length == 0) normalSelect(target); 
	else { 
		var min:uint = Math.min(_last.id, target.id); 
		var max:uint = Math.max(_last.id, target.id); 
		var it:Object; 
		clear(); 
		 
		for (var i:int = min; i <= max; i++)  
		{ 
			it = target.parent.getChildAt(i) 
			if (it is FileItem)  
			{ 
				it.selected = true; 
				_items.push(it); 
			} 
		} 
	} 
}

First of all we need to check if there are any other items selected. If no item is selected than we do a normal selection on the target.

If any item is selected we first need to get the minimum and maximum id between the last selected item and the currently selected item (we can do this because all the items added to the list are added to the display list in the order this selection works - from left to right) and clear the selection.

Once the selection is empty we use a for loop to subsequently set the selected state to true for the items between the minimum and maximum index position in the display list and add them to the selection array.

Also we make a check if the items are of type FileItem to avoid any errors that might appear (display objects added between these indexes which are not of type FileItem).


Step 89: Completing the Selection System

Now that all selection types have been established we need to add just a few more details to complete the class.

First we need a way to get the selection array from outside this class. For this we add the following public getter:

 
public function get selection():Array  
{  
	return _items;  
}

Later when we'll talk about file downloading we'll need to check if the selection is downloadable, meaning that there's only one item selected and that item is a file. To achieve this we'll make use of the isDownloadable() public method:

 
public function isDownloadable():Boolean 
{ 
	if (isSingle()) 
		return _items[0].type == FileData.FILE; 
	else return false; 
}

Also notice that we are using the isSingle() method which returns true if the selection contains only one item. This method and the firstItem getter which we'll create next will be used when renaming a file.

 
public function isSingle():Boolean 
{ 
	return _items.length == 1; 
} 
 
public function get firstItem():FileItem  
{  
	return _items[0];  
}

These are the last modifications that we're going to make to the SelectionManager class so here's the complete code with comments to compare with your work:

 
package com.tutsplus.active.fileBrowser.ui  
{ 
	import com.tutsplus.active.fileBrowser.data.FileData; 
	import flash.display.DisplayObject; 
	import flash.events.Event; 
	import flash.events.EventDispatcher; 
	import flash.events.MouseEvent; 
	 
	/** 
	 * Dispatched when the selection has changed. 
	 *  
	 * @eventType flash.events.Event.SELECT 
	 */ 
	[Event(name = "select", type = "flash.events.Event")] 
	 
	/** 
	 * A basic selection manager sistem. 
	 * Can handle single selections by mouse and multiple selections 
	 * by using the SHIFT and CONTROL key. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class SelectionManager extends EventDispatcher 
	{ 
		private var _items	:Array; 
		private var _last	:FileItem; 
		private var _holder	:DisplayObject; 
		 
		public function SelectionManager()  
		{ 
			_items = []; 
		} 
		 
		/** 
		 * The holder for which to add select functionality. 
		 */ 
		public function set holder(value:DisplayObject):void  
		{ 
			_holder = value; 
			_holder.addEventListener(MouseEvent.CLICK, onClick); 
		} 
		 
		/** 
		 * Event handler for when the handler dispatches mouse events 
		 * of type MouseEvent.CLICK. 
		 *  
		 * @param	event	MouseEvent of type MouseEvent.CLICK. 
		 */ 
		private function onClick(e:MouseEvent):void 
		{ 
			if (e.target is FileItem) 
			{ 
				if (e.shiftKey)	shiftSelect(FileItem(e.target)); 
				else if (e.ctrlKey) ctrlSelect(FileItem(e.target)); 
				else normalSelect(FileItem(e.target)); 
				 
				dispatchEvent(new Event(Event.SELECT)); 
			}else deselect(); 
		} 
		 
		/** 
		 * Method used to select multiple items using the SHIFT key. 
		 *  
		 * @param	target	The currently clicked item. 
		 */ 
		private function shiftSelect(target:FileItem):void 
		{ 
			if (_items.length == 0) normalSelect(target); 
			else { 
				var min:uint = Math.min(_last.id, target.id); 
				var max:uint = Math.max(_last.id, target.id); 
				var it:Object; 
				clear(); 
				 
				for (var i:int = min; i <= max; i++)  
				{ 
					it = target.parent.getChildAt(i) 
					if (it is FileItem)  
					{ 
						it.selected = true; 
						_items.push(it); 
					} 
				} 
			} 
		} 
		 
		/** 
		 * Method used to select multiple items using the CONTROL key. 
		 *  
		 * @param	target	The currently clicked item. 
		 */ 
		private function ctrlSelect(target:FileItem):void 
		{ 
			if (target.selected == false) { 
				target.selected = true; 
				_items.push(target); 
			}else { 
				target.selected = false 
				_items.splice(_items.indexOf(target), 1); 
			} 
			_items.sortOn('id', Array.NUMERIC); 
			_last = target; 
		} 
		 
		/** 
		 * Method used to select only one item. 
		 *  
		 * @param	target The currently clicked item. 
		 */ 
		private function normalSelect(target:FileItem):void 
		{ 
			clear(); 
			target.selected = true; 
			_items[0] = _last = target; 
		} 
		 
		/** 
		 * Deselects all items. 
		 */ 
		public function deselect():void 
		{ 
			clear(); 
			dispatchEvent(new Event(Event.SELECT)); 
		} 
		 
		/** 
		 * Clears the current selection without dispatching 
		 * any event. 
		 */ 
		private function clear():void 
		{ 
			while(_items.length > 0) 
			{ 
				_items[0].selected = false; 
				_items.shift(); 
			} 
		} 
		 
		/** 
		 * This method is used to check if the selection holds only  
		 * one item and if the selected item is downloadable aka is of type 
		 * FileData.FILE. 
		 *  
		 * @return	Returns true if the item is downloadable and false otherwise. 
		 */ 
		public function isDownloadable():Boolean 
		{ 
			if (isSingle()) 
				return _items[0].type == FileData.FILE; 
			else return false; 
		} 
		 
		/** 
		 * This method checks if the selection holds only one item, 
		 * regardless of that item's type. 
		 *  
		 * @return	Returns true if it's the only selected item or false otherwise. 
		 */ 
		public function isSingle():Boolean 
		{ 
			return _items.length == 1; 
		} 
		 
		/** 
		 * This method returns the first item in the selection. 
		 */ 
		public function get firstItem():FileItem  
		{  
			return _items[0];  
		} 
		 
		/** 
		 * Returns an array of FileItem objects representing the currently 
		 * selected items. 
		 */ 
		public function get selection():Array  
		{  
			return _items;  
		} 
	} 
 
}

Step 90: Adding the Selection System

Now that we have completed the selection system we need to add it to the FileBrowser class and see it in action.

Open the FileBrowser class and add a new private property called _selectionMan of type SelectionManager.

 
private var _selectionMan	:SelectionManager;

At the end of the init() method add these three lines of code (just after calling the getFileList() method of the _manager instance):

 
_selectionMan = new SelectionManager(); 
_selectionMan.holder = list; 
_selectionMan.addEventListener(Event.SELECT, onSelect);

This will create a new instance of the SelectionManager class, set its holder property to the list and add an event listener for the SELECT event.


Step 91: The Selection Handler

In the last step an event listener has been added to the _selectionMan object so let's add the handler for this listener:

 
private function onSelect(e:Event):void 
{ 
	download_btn.enabled = _selectionMan.isDownloadable(); 
	delete_btn.enabled = copy_btn.enabled = (_selectionMan.selection.length > 0); 
	rename_btn.enabled = _selectionMan.isSingle(); 
}

This handler is used to enable or disable the buttons depending on the selection made.

If the selection contains only one item and that item is downloadable (it's a file, not a directory) than the download_btn button gets enabled.

If the selection has one or more items in it (regardless of their type) than the delete_btn and copy_btn buttons get enabled.

And lastly if the selection contains a single item (regardless of it's type) than the rename_btn button gets enabled.

Now that all things are in place you can open your FLA and test the movie to test the selection system. You should get something like this:


Step 92: Making Commands

So we have our browser which we can use to see the contents of the root directory and any other directory double clicked on, but this is not very practical, is it? We need to interact more with our files on the server and we need a way to go back into the parent of a visited directory.

Note: All commands are added in the FileBrowser class so make sure you have it opened and updated with all the above content.

We'll start off with the simplest command which requires no extra PHP code but just a line of ActionScript: the Back command. In the event handler called onBack() add this line of code:

 
_manager.goBack();

This is all there is for this command. Simple, isn't it? Now you can test the movie in Flash and use the arrow button to navigate back.

(If you don't remember what the goBack() method does refer to Step 53 in the previous part to refresh your memory.)


Step 93: Downloading Files

Before diving head-first into the download command we need to create a PHP script which streams a file back into Flash when requested using its path.

In the directory on your server create a new PHP file called "download_file.php" and in it add the following code:

 
<?php 
// Including the configuration file 
include_once('config.inc.php'); 
 
// Replacing the word 'root' with the real root path. 
$filepath = substr_replace($_POST['file'], $root, 0, 4); 
$file_ext = $_POST['ext']; 
 
// Array of known mime types 
$mime_types = array( 
	'gif' => 'image/gif', 
	'png' => 'image/png', 
	'jpeg'=> 'image/jpg', 
	'jpg' => 'image/jpg', 
	'php' => 'text/plain', 
 	'txt' => 'text/plain', 
	'pdf' => 'application/pdf', 
 	'html' => 'text/html', 
 	'htm' => 'text/html', 
	'exe' => 'application/octet-stream', 
	'zip' => 'application/zip' 
); 
// Setting the default mime type 
$type = 'application/force-download'; 
 
// Getting the mime type of the file by it's extension. If the 
// mime type is not in the array it will be set to the default 'application/force-download' 
$type = array_key_exists($file_ext, $mime_types)?$mime_types[$file_ext]:$type; 
 
// check that it exists and is readable 
if (file_exists($filepath) && is_readable($filepath)) { 
	// get the file's size and send the appropriate headers 
	$size = filesize($filepath); 
	header('Content-Type: '.$type); 
	header('Content-Length: '.$size); 
	header('Content-Disposition: attachment; filename='.basename($filepath)); 
	header('Content-Transfer-Encoding: binary'); 
     
	// open the file in binary read-only mode 
	// suppress error messages if the file can't be opened 
	$file = @ fopen($filepath, 'rb'); 
	if ($file) { 
		// stream the file and exit the script when complete 
		fpassthru($file); 
		exit; 
	} 
} 
?>

I'll break this down.

As in the "get_file_list.php" first of all we need to include the configuration file.

 
include_once('config.inc.php');

After that we need to get the data sent from Flash using the POST method, representing the path of the file we want to download and the file extension. Also we need to replace the word 'root' from the path with the real root path.

 
// Replacing the word 'root' with the real root path. 
$filepath = substr_replace($_POST['file'], $root, 0, 4); 
$file_ext = $_POST['ext'];

The $mime_types[] array contains the most probable mime-types for files you might have on your server. You can add more to this array if needed.

 
$mime_types = array( 
	'gif' => 'image/gif', 
	'png' => 'image/png', 
	'jpeg'=> 'image/jpg', 
	'jpg' => 'image/jpg', 
	'php' => 'text/plain', 
 	'txt' => 'text/plain', 
	'pdf' => 'application/pdf', 
 	'html' => 'text/html', 
 	'htm' => 'text/html', 
	'exe' => 'application/octet-stream', 
	'zip' => 'application/zip' 
);

In the next lines we set the default mime-type to application/force-download. This will be used if the specified extension (therefor the mime-type) does not exist in the $mime_types array.

 
$type = 'application/force-download';

After setting the default mime-type we check to see if the specified extension exists in the array and change the type to the correct one. This is done by using a ternary operation just like in Flash.

 
$type = array_key_exists($file_ext, $mime_types) ? $mime_types[$file_ext] : $type;

Lastly we check if the file exists and if it's readable. If the condition is met then we get the size of the file, set the appropriate headers using the mime-type, size and filename and stream the file in binary mode back into Flash.

 
if (file_exists($filepath) && is_readable($filepath)) { 
	// get the file's size and send the appropriate headers 
	$size = filesize($filepath); 
	header('Content-Type: '.$type); 
	header('Content-Length: '.$size); 
	header('Content-Disposition: attachment; filename='.basename($filepath)); 
	header('Content-Transfer-Encoding: binary'); 
	// open the file in binary read-only mode 
	// suppress error messages if the file can't be opened 
	$file = @ fopen($filepath, 'rb'); 
	if ($file) { 
		// stream the file and exit the script when complete 
		fpassthru($file); 
		exit; 
	} 
}

Note: To get more documentation about the used functions you can visit the official PHP website, php.net.

Basically this script gets a file from the specified path and streams it back to Flash just as you would access it through its URL.


Step 94: The Download Command

Now that we have the script in place we can create the download method in ActionScript. In the FileBrowser class go to the onDownload() and add the following code:

 
preloader.visible = true; 
dialog_box.hidden = false; 
dialog_box.message = 'Download selected item?'; 
dialog_box.onOK = downloadFile; 
dialog_box.onCancel = onDownloadOver;

When the download button is pressed first thing we do is show the preloader and the dialog box. After that we set the dialog box message to a question like "Download selected item?" and set the onOK and onCancel properties of the DialogBox instance (which, if you remember, are functions which are executed when the OK button and the Cancel button are pressed).

Note: In the rest of the commands we will have the following process flow:

  • Show the preloader above the list
  • Show the dialog box above the preloader
  • Set the corresponding functions and message for the dialog box
  • The command gets executed
  • Hide the dialog box and the preloader
  • Refresh the list

Step 95: Dialog box Functions

Next we need to create the functions we added to the dialog box's buttons, the downloadFile() and onDownloadOver() functions.

First we'll start by creating the onDownloadOver() function because it's shorter and it will be used as an event handler also. In it we simply hide the dialog box and the preloader.

 
private function onDownloadOver(e:Event = null):void 
{ 
	dialog_box.hidden = true; 
	preloader.visible = false; 
}

We set the default value of the parameter to null because it needs to be called without passing the event parameter when it's not used as an event handler.

The downloadFile() function is a bit larger but not very complex. Here we use a FileReference's download() method to download the selected file.

The download() method needs an URLRequest object containing the URL of the requested file. But we don't have the URL of the file so we need to send some variables to PHP to get that file from the server and stream it back to Flash.

 
private function downloadFile():void 
{ 
	dialog_box.message = 'Preparing download...'; 
	var fr:FileReference = new FileReference(); 
	var req:URLRequest = new URLRequest(_scriptMan.download); 
	var name:String = _selectionMan.firstItem.fileName; 
	var vars:URLVariables = new URLVariables(); 
	 
	vars.file = _selectionMan.firstItem.path; 
	vars.ext = _selectionMan.firstItem.extension; 
	req.method = URLRequestMethod.POST; 
	req.data = vars; 
     
	fr.addEventListener(ProgressEvent.PROGRESS, onDownloadProg); 
	fr.addEventListener(Event.COMPLETE, onDownloadOver); 
	fr.addEventListener(Event.CANCEL, onDownloadOver); 
	fr.download(req, name); 
     
	dialog_box.modButton(DialogBox.OK); 
	dialog_box.onCancel = function():void { 
		fr.cancel(); 
		onDownloadOver(); 
	} 
}

In the last lines of this method we disable the OK button using the modButton() method and we change the functionality of the cancel button so that if it's pressed it will also stop the download process.

Last thing we want to do is add the an event listener to check the progress of our download. As you can see I've already attached an event listener to the FileReference object called onDownloadProgress(). In the handler for this event listener we simply update the text in the dialog box using the download progress.

 
private function onDownloadProg(e:ProgressEvent):void 
{ 
	dialog_box.message = 'File downloaded ' + Math.round(e.bytesLoaded / e.bytesTotal * 100) +'%'; 
}

This is all there is to the download process. You can now test your movie, select a file and press the Download button. The dialog box should appear asking you to confirm the download.

Note: If you get an error or nothing happens check the path to the download script in the configScripts() method.


Step 96: Renaming Files

The next command that we'll attempt to make is the Rename command so let's create a PHP script to rename our files.

In the directory on your server create a new PHP script and name it "rename.php". Open it and add the following code:

 
<?php 
include_once('config.inc.php'); 
$old = substr_replace($_POST['oldName'], $root, 0, 4); 
$new = substr_replace($_POST['newName'], $root, 0, 4); 
rename($old,$new); 
?>

Brief, ain't it? Still, let me explain it a bit.

As in all scripts we need to include the configuration file because it holds the real root path. After that we get the old name and the new name from the POST array (the variables which we'll send from Flash.

The last step is to change the name of the file using PHP's built in function called rename().


Step 97: The Rename Command

Now that we have the script let's create the command in Flash.

In the event handler called onRename() add the following lines:

 
var ext:String = _selectionMan.firstItem.extension; 
var name:String = _selectionMan.firstItem.fileName; 
if (_selectionMan.firstItem.type == FileData.FILE) 
	name = name.slice(0, name.lastIndexOf(ext) - 1); 
 
preloader.visible = true; 
dialog_box.hidden = false; 
dialog_box.renameMode = true; 
dialog_box.message = name; 
 
dialog_box.onOK = renameItem; 
dialog_box.onCancel = function ():void 
{ 
	dialog_box.renameMode = false; 
	dialog_box.hidden = true; 
	preloader.visible = false; 
}

As you can see we get the extension of the item and its name. We use an if statement to check whether the item is of type FILE and remove the extension from its name.

In the following lines we make the preloader and dialog box visible and change the dialog box to renameMode as we'll need an input field to add the new name.

After that we set the functions for the OK and Cancel buttons.


Step 98: The Buttons Functions

As you can see we have set the function of the Cancel button without using a declared function as there was no need for it.

For the OK button we need to create a method called renameItem() so let's add it.

 
private function renameItem():void 
{ 
	 
}

To rename an item we need again an URLRequest and an URLLoader to send data to the server. Add these variables in the renameItem() method:

 
var req:URLRequest = new URLRequest(_scriptMan.rename); 
var vars:URLVariables = new URLVariables(); 
var loader:URLLoader = new URLLoader();

Next we set two URL variables with the old name and the new name. The new name is set using the text entered in the dialog box, appended to the current path. If the item is also a file we need to add its extension in the end.

 
vars.oldName = _selectionMan.firstItem.path; 
if (_selectionMan.firstItem.type == FileData.FILE) 
	vars.newName = _manager.currentPath +'/'+ dialog_box.message + '.' + _selectionMan.firstItem.extension; 
else vars.newName = _manager.currentPath + '/' + dialog_box.message; 
 
req.method = URLRequestMethod.POST; 
req.data = vars;

We set the method of the URLRequest object to POST and add the variables to it.

Last thing we need to do is add the event listener to the loader for when data is received from the server meaning that the rename process is complete and, of course, send the request.

 
loader.addEventListener(Event.COMPLETE, onRenameComplete); 
loader.load(req);

In the onRenameComplete() event handler we simply hide the dialog box (also turning off the Rename mode) and preloader and refresh the list.

 
private function onRenameComplete(e:Event):void  
{ 
	e.target.removeEventListener(Event.COMPLETE, onRenameComplete); 
	dialog_box.renameMode = false; 
	dialog_box.hidden = true; 
	preloader.visible = false; 
	_manager.refresh(); 
}

The Rename command is now complete. Test the movie in Flash to try it out.


Step 99: Deleting Files

Again, we'll start with the PHP script before writing any ActionScript code so create a new PHP file on your server and name it "delete.php". Open it and type in the following code:

 
<?php 
// Including the configuration file 
include_once('config.inc.php'); 
 
// Replacing the word 'root' with the real root path 
$item = substr_replace($_POST['item'], $root, 0, 4); 
delete_item($item); 
 
/** 
 * Deletes the specified item. The item can be a directory 
 * or a file on the server. 
 * 
 * @param $item		The path of the item to be deleted 
 */ 
function delete_item($item){ 
	if(is_dir($item)){ 
		$di = new DirectoryIterator($item); 
		while($di->valid()){ 
			if(false == $di->isDot()) 
			{ 
				if($di->isDir()) 
				{ 
					delete_item($di->getRealPath()); 
				} 
				if($di->isFile()) 
				{ 
					@unlink($di->getRealPath()); 
				} 
			} 
			$di->next(); 
		} 
	}else if(is_file($item)){ 
		@unlink($item); 
	} 
	@rmdir($item); 
} 
?>

Time to break it down:

As done in the other scripts we first include the configuration file, get the variables sent from Flash and add the real root path.

 
include_once('config.inc.php'); 
 
$item = substr_replace($_POST['item'], $root, 0, 4); 
delete_item($item);

After that we use the delete_item() recursive function to delete the specified item. Because there's no built in function to remove a directory and all its contents we first need to remove every file one by one if the specified item is a directory.

 
function delete_item($item){ 
	if(is_dir($item)){ 
		$di = new DirectoryIterator($item); 
		while($di->valid()){ 
			if(false == $di->isDot()) 
			{ 
				if($di->isDir()) 
				{ 
					delete_item($di->getRealPath()); 
				} 
				if($di->isFile()) 
				{ 
					@unlink($di->getRealPath()); 
				} 
			} 
			$di->next(); 
		} 
	}else if(is_file($item)){ 
		@unlink($item); 
	} 
	@rmdir($item); 
}

Note: A recursive function is a function which calls itself. It is used to traverse arborescent (tree-like) structures like directories or XML files.

In this method we check if the item is a directory or a file. If it's a directory we go through each of its items and remove it using the unlink() function, if it's a file, which is the built in function to delete a file, or we call again on the delete_item() function if the item is a directory.

After every item in a directory is deleted than the directory itself is deleted using the rmdir() function. You might ask yourself why all the fuss when there's an function to remove directories. The problem is that this function only works on empty directories.


Step 100: The Delete Command

Just as before, in the event handler for the delete button we make the dialog box and preloader visible and set the functions for the dialog box buttons.

 
private function onDelete(e:MouseEvent):void 
{ 
	_deleteCanceled = false; 
	preloader.visible = true; 
	dialog_box.message = 'Delete selected items?'; 
	dialog_box.hidden = false; 
	dialog_box.onOK = deleteItem; 
	dialog_box.onCancel = cancelDelete; 
}

There is one thing that I've added though: a private property called _deleteCanceled, so you'll need to declare it also.

 
private var _deleteCanceled:Boolean;

This boolean property will be used to stop the delete process if the user presses the Cancel button.

For the button functions we'll start with the cancelDelete() method because the only thing we need to do is hide the dialog box and preloader.

 
private function cancelDelete():void 
{ 
	_deleteCanceled = true; 
	preloader.visible = false; 
	dialog_box.hidden = true; 
}

Step 101: Deleting Items

Now it's time to create the deleteItem() function we've added to the OK button so add its shell. Also, we are going to use it as an event handler so add the e:Event parameter and set its default value to null.

 
private function deleteItem(e:Event = null):void 
{ 
	 
}

This method will delete all selected items one by one. This is the way it works:

  • When the user presses OK a request to delete the first selected item is sent to the server.
  • The PHP script deletes the item and triggers a response.
  • Back in Flash the event handler added for the URLLoader object is the same as the deleteItem() method so when the PHP script has finished deleting the file this method will be called again.
  • If there are more files selected then the above process repeats until there are no more selected files left.
  • When no more files are selected the dialog box and preloader are hidden and the list of files is refreshed.

The below code is what you need to add in the deleteItem() method to do the process described above.

 
if (e) e.target.removeEventListener(Event.COMPLETE, deleteItem); 
 
if (_selectionMan.selection.length > 0 && _deleteCanceled == false) 
{ 
	dialog_box.message = 'Deleting items... ' + _selectionMan.selection.length +' remaining.';  
	 
	var req:URLRequest = new URLRequest(_scriptMan.deleteScript); 
	var vars:URLVariables = new URLVariables(); 
	var loader:URLLoader = new URLLoader(); 
	 
	vars.item = _selectionMan.selection.shift().path; 
	req.method = URLRequestMethod.POST; 
	req.data = vars; 
	 
	loader.addEventListener(Event.COMPLETE, deleteItem); 
	loader.load(req); 
} else { 
	preloader.visible = false; 
	dialog_box.hidden = true; 
	_selectionMan.deselect(); 
	_manager.refresh(); 
}

Notice that when checking if the selection contains any files we also check if the delete operation has not been canceled (the _deleteCanceled property is set to false).

If the delete was canceled then the whole delete process is aborted and the rest of the selected items will no longer be deleted.

The rest of the process is basic variable sending from Flash to PHP as in the previous steps.

Note: The Delete operation can be canceled by pressing the Cancel button. That is when the _deleteCanceled property is set to true (described in the previous step). This won't restore any files that are already gone, but it will stop any more files being deleted.

You can now test the movie and try to delete some files.


Step 102: Copying Items

Until now we have added deletion, downloading and renaming functionality to our file browser. We need one more thing to complete it: copying files. The first part of the copy process is copying the items into an array which will represent our virtual clipboard (I will be referring from now on to the array as "the clipboard"). In it we'll store the paths of the items we want to copy.

After copying the items paths, whenever the user clicks the Paste button we need to copy all those stored files to the current path.

So the whole process will be divided into two: the copy process and the paste process.

There are three potential issues here:

  1. First of all if a user copies and item an than deletes it, when he presses the Paste button there will be no error, but nothing will happen so things may become confusing. To avoid this we are going to remove the deleted item from the clipboard.
  2. If the user copies one item and pastes it in the same directory (or in another directory which contains an item of the same type and has the same name) than the existing file will be overwritten without a warning. To avoid this we are going to append a number to the name of the copied item if there are any name conflicts (this will be done in PHP).
  3. If the user copies an folder and tries to paste it inside itself than the copy process will go to infinity (well, not infinity as the script will be most probably be stopped by the server).

Now that I've explained what we need to do let's get to work.


Step 103: Clipboard

As I've said when pressing the Copy button we need to save the selected items into an array or, as I've already said, clipboard. To make it easier we're only going to save the paths of those files as this is the only thing we need.

To start with, add the following private properties to the FileBrowser class:

 
private var _copiedFiles    :Array; 
private var _copiedCount    :uint; 
private var _copyCanceled   :Boolean;

The _copiedFiles[] array will be our "clipboard" which holds the paths of our copied items.

The _copiedCount unsigned integer will be used to count the number of pasted items.

The _copyCanceled boolean property will be used to cancel the pasting process which we'll discuss later on.

Now that we have the necessary properties let's add the code which will save the items into the clipboard. Add the below code into the onCopy() event handler.

 
var l:uint = _selectionMan.selection.length; 
_copiedFiles = []; 
 
for (var i:int = 0; i < l; i++) { 
	_copiedFiles[i] = _selectionMan.selection[i].path; 
} 
paste_btn.enabled = _copiedFiles.length > 0;

As you can see in here the clipboard (the _copiedFiles[] array) is emptied and the paths of the selected items are added into it using a simple for loop.

Also we enable the Paste button if there were any files copied.

This is all there is to the copy process. In the next steps we'll talk about the second process, pasting files.


Step 104: Copying Items on the Server

Just so I won't get you confused, when I've talked earlier about copying items I've meant saving them into our virtual clipboard (array) so that we can paste them later on. From now on I'll be referring to the copy word as making a copy of an item and moving that copy to another location (pasting).

For now we're going to make the PHP script which does the actual copying.

On your server create a new PHP file named "copy.php" and add the code presented below.

 
<?php 
// Including the configuration file 
include_once('config.inc.php'); 
 
// Replacing the word 'root' with the real path 
// in the source and destination 
$src = substr_replace($_POST['source'], $root, 0, 4); 
$dest = substr_replace($_POST['dest'], $root, 0, 4); 
 
copy_this($src,  $dest); 
 
/** 
 * Function used to copy an item from a location to another. 
 * The item can be either a file or directory. 
 * 
 * @param $source	The source path. 
 * @param $destination 	The destination path. 
 */ 
function copy_this( $src, $dest ) {	 
	$c = 0; 
	if(is_dir($src)) 
	{ 
		$new_dir = $dest.'/'.basename($src); 
		 
		while (file_exists($new_dir)) 
		{ 
			$c++; 
			$new_dir = $dest.'/'.basename($src).' ('.$c.')'; 
		} 
		 
		@mkdir($new_dir); 
		$dir = dir($src); 
		 
		while(false !== ($item = $dir->read())) 
		{ 
			if($item == '.' || $item == '..') 
				continue; 
				 
			$new = $src.'/'.$item; 
			 
			if(is_dir($new)) 
			{ 
				copy_this($new,  $new_dir.'/'.$item); 
				continue; 
			} 
			 
			copy_file($new, $new_dir.'/'.$item); 
		} 
	}else { 
		copy_file($src, $dest.'/'.basename($src)); 
	} 
} 
 
function copy_file($src, $dest) 
{ 
	$c = 0; 
	$new = $dest; 
	while(file_exists($new)) 
	{ 
		$c++; 
		$new = change_name($new, strip_ext($dest).' ('.$c.')'); 
	} 
	copy($src, $new); 
} 
 
/** 
 * Returns the name of a file without the extension. 
 * @param $filename The name of the file with extension. 
 */ 
function strip_ext($filename) 
{ 
    return substr($filename,0,strrpos($filename,'.')); 
} 
 
/** 
 * Renames a file. 
 */ 
function change_name($filename, $newName) 
{ 
    $extension = substr($filename,strrpos($filename,'.')); 
    return $newName.$extension; 
} 
?>

I'll break it down. As done in the other scripts we first include the configuration file and get the variables send from Flash using the POST method.

In this script we have four functions called change_name(), strip_ext(), copy_this() and copy_file().

The change_name($filename, $newName) function is used to change the name of a file to another name as it accepts two parameters (the old name with extension, and the new name without extension).

The strip_ext($filename) function will return the name of a given file without the extension.

I know that the previous two methods could have been avoided but I wanted to present something more detailed.

However, the copy_this($src, $dest) function and copy_file($src, $dest) functions are vital for our needs.

For each of the two functions the parameters have the same role: $src represents the source item to be copied and $dest represents the destination directory where the item should be copied (without the name of the actual item).

As in the case of the deletion script, the copy_this() function is recursive so it calls itself to copy every child of the item, if the item is a directory.

Also, we use a while loop to increment the value of a given integer ($c) so that any name conflict appear the files should be renamed using this index (ex: "file.txt", "file (1).txt", "file (2).txt" and so on). This addresses the second issue presented in Step 102, about overwriting existing files (the first and third issues will be resolved in Flash).


Step 105: The Paste Command

Now that we have our server-side script ready let's open FlashDevelop and write our last command.

Inside the FileBrowser class add the following code inside the onPaste() event handler:

 
_copiedCount = 0; 
_copyCanceled = false; 
 
removeSameFolder(); 
 
preloader.visible = true; 
dialog_box.hidden = false; 
dialog_box.message = 'Copying files...'; 
dialog_box.modButton(DialogBox.OK); 
dialog_box.onCancel = function():void 
{ 
	_copyCanceled = true; 
} 
pasteItem();

As you can see we first set the number of copied items (items that have been pasted to their new location), _copiedCount, to 0 and set the _copyCanceled property to false.

In the third line we call a function named removeSameFolder() which addresses the third problem that could occur in the copy process (pasting a folder inside itself) and we'll go over it in just a moment.

The rest of the code is just the same as in the case of the other commands (showing the preloader and dialog box, setting the message in the dialog box and the functions for its buttons) so I won't get through it again.

Also we make a call to a method named pasteItem() presented later on.


Step 106: Removing the Source Folder

The third issue is about copying a folder inside itself, thus creating an infinite process. A simple way to avoid this is to check wether the destination directory is found among the source items being copied and remove it. This is what the removeSameFolder() method does.

 
private function removeSameFolder():void 
{ 
	var l:uint = _copiedFiles.length; 
	for (var i:int = 0; i < l; i++)  
	{ 
		if (_copiedFiles[i] == _manager.currentPath) 
			_copiedFiles.splice(i, 1); 
	} 
}

This method uses a simple for loop to go through every item in the clipboard and remove it if that item is the same as the destination directory.


Step 107: Pasting Items

The last method that we need to add to complete the pasting process is the pasteItem() method called from the onPaste() event handler. This method is also an event handler and so we'll need to add a default value to the e:Event parameter.

 
private function pasteItem(e:Event = null):void 
{ 
	if (e)	e.target.removeEventListener(Event.COMPLETE, pasteItem); 
	 
	if (_copiedFiles.length > _copiedCount && _copyCanceled == false) 
	{ 
		dialog_box.message = 'Copying item ' + (_copiedCount+1) + ' out of ' + _copiedFiles.length + '...'; 
		var req:URLRequest = new URLRequest(_scriptMan.copy); 
		var vars:URLVariables = new URLVariables(); 
		var loader:URLLoader = new URLLoader(); 
		 
		vars.source = _copiedFiles[_copiedCount]; 
		vars.dest = _manager.currentPath; 
		 
		req.method = URLRequestMethod.POST; 
		req.data = vars; 
		_copiedCount++; 
		 
		loader.addEventListener(Event.COMPLETE, pasteItem); 
		loader.load(req); 
	} else { 
		dialog_box.hidden = true; 
		preloader.visible = false; 
		_manager.refresh(); 
	} 
}

This method is exactly the same as the deleteItem() method discussed in the previous steps, the only significant difference between them is the number of variables being sent to PHP (in this case two, the path and name of the item and the destination path).


Step 108: The First Issue

To conclude the copying process, and so the FileBrowser class and this tutorial, let's add a method which addresses the first issue presented in Step 102: trying to paste an item that has been deleted since it was copied to the clipboard.

 
private function removeCopied(item:String):void  
{ 
	for (var i:uint = 0; i < _copiedFiles.length; i++)  
	{ 
		if (_copiedFiles[i] == item) 
		{ 
			_copiedFiles.splice(i, 1); 
			paste_btn.enabled = _copiedFiles.length > 0; 
			return; 
		} 
	} 
}

Remember what I've said earlier about copied items (items in the clipboard) being deleted before pasted anywhere? Well, this method deletes the specified item from the clipboard (_copiedFiles[] array) using a for loop and a conditional statement.

Of course that in order to work we need to call it from somewhere. The best place to add it would be... you guessed it, the deleteItem() method. Add the following method call in the deleteItem() method, just after setting the data property of the URLRequest object.

 
req.data = vars; 
removeCopied(vars.item);

You can now test the movie and check the copy-paste functionality of our, now completed, file browser.


Step 109: Full Source Code

Here are all the classes we have created during this tutorial, including the FileBrowser class. The code is all commented for later reference and to explain things that I've may have omitted throughout these steps.


FileBrowser.as

 
package com.tutsplus.active.fileBrowser 
{ 
	import com.tutsplus.active.fileBrowser.data.FileData; 
	import com.tutsplus.active.fileBrowser.data.FileListManager; 
	import com.tutsplus.active.fileBrowser.data.ScriptManager; 
	import com.tutsplus.active.fileBrowser.ui.BasicButton; 
	import com.tutsplus.active.fileBrowser.ui.DialogBox; 
	import com.tutsplus.active.fileBrowser.ui.FileItem; 
	import com.tutsplus.active.fileBrowser.ui.FilesList; 
	import com.tutsplus.active.fileBrowser.ui.SelectionManager; 
	import flash.display.MovieClip; 
	import flash.events.Event; 
	import flash.events.MouseEvent; 
	import flash.events.ProgressEvent; 
	import flash.net.FileReference; 
	import flash.net.URLLoader; 
	import flash.net.URLRequest; 
	import flash.net.URLRequestMethod; 
	import flash.net.URLVariables; 
	import flash.text.TextField; 
	 
	/** 
	 * A simple file browser used to manage a directory from 
	 * the server. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FileBrowser extends MovieClip 
	{ 
		 
		public var dialog_box		:DialogBox; 
		public var list				:FilesList; 
		// The preloader 
		public var preloader		:MovieClip; 
		// The two info text fields 
		public var location_txt		:TextField,  
					numItems_txt	:TextField; 
		// Buttons instances 
		public var paste_btn		:BasicButton,  
					copy_btn		:BasicButton,  
					delete_btn		:BasicButton,  
					rename_btn		:BasicButton,  
					download_btn	:BasicButton,  
					back_btn		:BasicButton; 
					 
		private var _manager		:FileListManager; 
		private var _scriptMan		:ScriptManager; 
		private var _selectionMan	:SelectionManager; 
		private var _copiedFiles	:Array; 
		private var _copiedCount	:uint; 
		private var _copyCanceled	:Boolean; 
		private var _deleteCanceled	:Boolean; 
 
		public function FileBrowser() 
		{ 
			configScripts(); 
			setupManager(); 
			setupButtons(); 
			setupList(); 
			init() 
		} 
		 
		/** 
		 * Initialization method. 
		 */ 
		private function init():void 
		{ 
			preloader.visible = false; 
			dialog_box.hidden = true; 
			 
			_copiedFiles = []; 
			_manager.getFileList(); 
			_selectionMan = new SelectionManager(); 
			_selectionMan.holder = list; 
			_selectionMan.addEventListener(Event.SELECT, onSelect); 
		} 
		 
		/** 
		 * Adds the required event handlers to the buttons 
		 * and disables them all. 
		 */ 
		private function setupButtons():void 
		{ 
			back_btn.enabled = download_btn.enabled = rename_btn.enabled =  
			delete_btn.enabled = copy_btn.enabled = paste_btn.enabled = false; 
			 
			back_btn.addEventListener(MouseEvent.MOUSE_UP, onBack); 
			download_btn.addEventListener(MouseEvent.MOUSE_UP, onDownload); 
			delete_btn.addEventListener(MouseEvent.MOUSE_UP, onDelete); 
			copy_btn.addEventListener(MouseEvent.MOUSE_UP, onCopy); 
			paste_btn.addEventListener(MouseEvent.MOUSE_UP, onPaste); 
			rename_btn.addEventListener(MouseEvent.MOUSE_UP, onRename); 
		} 
		 
		/** 
		 * This method sets up the list of file items. 
		 */ 
		private function setupList():void 
		{ 
			list.dataManager = _manager; 
			list.addEventListener(MouseEvent.DOUBLE_CLICK, onItemOpen); 
		} 
		 
		private function onItemOpen(e:MouseEvent):void 
		{ 
			if (e.target is FileItem) 
			{ 
				if (FileItem(e.target).type == FileData.DIR)  
					_manager.getFileList(FileItem(e.target).path); 
				else if (FileItem(e.target).type == FileData.FILE) 
					onDownload(); 
			} 
		} 
		 
		private function setupManager():void 
		{ 
			_manager = new FileListManager(); 
			_manager.addEventListener(Event.CHANGE, onListChange); 
			_manager.addEventListener(Event.INIT, onOpen); 
		} 
		 
		private function onSelect(e:Event):void 
		{ 
			download_btn.enabled = _selectionMan.isDownloadable(); 
			delete_btn.enabled = copy_btn.enabled = _selectionMan.selection.length > 0; 
			rename_btn.enabled = _selectionMan.isSingle(); 
		} 
		 
		private function onOpen(e:Event):void 
		{ 
			preloader.visible = true; 
		} 
		 
		/** 
		 * Event handler for when the list of file changes. 
		 *  
		 * @param	e	Event of type Event.CHANGE 
		 */ 
		private function onListChange(e:Event):void 
		{ 
			var multiple:Boolean = _manager.numFiles != 1; 
			 
			preloader.visible = false; 
			numItems_txt.text = multiple?_manager.numFiles + ' items':'1 item'; 
			location_txt.text = 'Location: ' + _manager.currentPath; 
			_selectionMan.deselect(); 
			back_btn.enabled = _manager.hasHistory(); 
		} 
		 
		/** 
		 * Configures the paths to the required server-side scripts. 
		 */ 
		private function configScripts():void 
		{ 
			_scriptMan = ScriptManager.getInstance(); 
             
			_scriptMan.fileList = 'http://localhost/fileBrowser/get_file_list.php'; 
			_scriptMan.download = 'http://localhost/fileBrowser/download_file.php'; 
			_scriptMan.copy = 'http://localhost/fileBrowser/copy.php'; 
			_scriptMan.deleteScript = 'http://localhost/fileBrowser/delete.php'; 
			_scriptMan.rename = 'http://localhost/fileBrowser/rename.php'; 
			 
		} 
		 
		/** 
		 * Event handler for when the back button is released. 
		 * @param	e	MouseEvent of type MouseEvent.MOUSE_UP 
		 */ 
		private function onBack(e:MouseEvent):void 
		{ 
			_manager.goBack(); 
		} 
		 
		/** 
		 * Event handler for when the Donwload button is released. 
		 * @param	e	MouseEvent of type MouseEvent.MOUSE_UP 
		 */ 
		private function onDownload(e:MouseEvent = null):void 
		{ 
			preloader.visible = true; 
			dialog_box.hidden = false; 
			dialog_box.message = 'Download selected item?'; 
			dialog_box.onOK = downloadFile; 
			dialog_box.onCancel = onDownloadOver; 
		} 
		 
		/** 
		 * This method does the actual file download creating a request 
		 * and passing it to a file reference object. 
		 */ 
		private function downloadFile():void 
		{ 
			dialog_box.message = 'Preparing download...'; 
			var fr:FileReference = new FileReference(); 
			var req:URLRequest = new URLRequest(_scriptMan.download); 
			var name:String = _selectionMan.firstItem.fileName; 
			var vars:URLVariables = new URLVariables(); 
			 
			vars.file = _selectionMan.firstItem.path; 
			vars.ext = _selectionMan.firstItem.extension; 
			req.method = URLRequestMethod.POST; 
			req.data = vars; 
			trace('Path: ', vars.file); 
			trace('Ext: ', vars.ext); 
			 
			fr.addEventListener(ProgressEvent.PROGRESS, onDownloadProg); 
			fr.addEventListener(Event.COMPLETE, onDownloadOver); 
			fr.addEventListener(Event.CANCEL, onDownloadOver); 
			fr.download(req, name); 
			dialog_box.modButton(DialogBox.OK); 
			dialog_box.onCancel = function():void { 
				fr.cancel(); 
				onDownloadOver(); 
			} 
		} 
		 
		/** 
		 * Event handler for when the download progress is over or 
		 * when a download was canceled. 
		 * @param	e 
		 */ 
		private function onDownloadOver(e:Event = null):void 
		{ 
			dialog_box.hidden = true; 
			preloader.visible = false; 
		} 
		 
		/** 
		 * Event handler used to monitor the download progress. 
		 * @param	e	ProgressEvent of type ProgressEvent.PROGRESS. 
		 */ 
		private function onDownloadProg(e:ProgressEvent):void 
		{ 
			dialog_box.message = 'File downloaded ' + Math.round(e.bytesLoaded / e.bytesTotal * 100) +'%'; 
		} 
		 
		/** 
		 * Event handler for when the Paste button was released. 
		 * @param	e	MouseEvent of type MouseEvent.MOUSE_UP 
		 */ 
		private function onPaste(e:MouseEvent):void 
		{ 
			_copiedCount = 0; 
			_copyCanceled = false; 
			removeSameFolder(); 
			preloader.visible = true; 
			dialog_box.hidden = false; 
			dialog_box.message = 'Copying files...'; 
			dialog_box.modButton(DialogBox.OK); 
			dialog_box.onCancel = function():void 
			{ 
				_copyCanceled = true; 
			} 
			pasteItem(); 
		} 
		 
		/** 
		 * This method removes a copied folder if that is also  
		 * a destination folder. 
		 */ 
		private function removeSameFolder():void 
		{ 
			var l:uint = _copiedFiles.length; 
			for (var i:int = 0; i < l; i++)  
			{ 
				if (_copiedFiles[i] == _manager.currentPath) 
					_copiedFiles.splice(i, 1); 
			} 
		} 
		 
		/** 
		 * This method is used to copy a item from source to destination. 
		 * Also it is used as an e handler for when the item has been 
		 * copied. 
		 * @param	e	Event of type Event.COMPLETE 
		 */ 
		private function pasteItem(e:Event = null):void 
		{ 
			if (e)	e.target.removeEventListener(Event.COMPLETE, pasteItem); 
			 
			if (_copiedFiles.length > _copiedCount && _copyCanceled == false) 
			{ 
				dialog_box.message = 'Copying item ' + (_copiedCount+1) + ' out of ' + _copiedFiles.length + '...'; 
				var req:URLRequest = new URLRequest(_scriptMan.copy); 
				var vars:URLVariables = new URLVariables(); 
				var loader:URLLoader = new URLLoader(); 
				 
				vars.source = _copiedFiles[_copiedCount]; 
				vars.dest = _manager.currentPath; 
				trace('Source: ', vars.source); 
				trace('Destination: ', vars.dest); 
				req.method = URLRequestMethod.POST; 
				req.data = vars; 
				_copiedCount++; 
				 
				loader.addEventListener(Event.COMPLETE, pasteItem); 
				loader.load(req); 
			}else { 
				dialog_box.hidden = true; 
				preloader.visible = false; 
				_manager.refresh(); 
			} 
		} 
		 
		/** 
		 * Event handler for when the Copy button is released. 
		 * @param	e	MouseEvent of type MouseEvent.MOUSE_UP 
		 */ 
		private function onCopy(e:MouseEvent):void 
		{ 
			var l:uint = _selectionMan.selection.length; 
			_copiedFiles = []; 
			 
			for (var i:int = 0; i < l; i++)  
				_copiedFiles[i] = _selectionMan.selection[i].path; 
			paste_btn.enabled = _copiedFiles.length > 0; 
		} 
		 
		/** 
		 * Event handler for when the Delete button is released. 
		 *  
		 * @param	e	MouseEvent of type MouseEvent.MOUSE_UP. 
		 */ 
		private function onDelete(e:MouseEvent):void 
		{ 
			_deleteCanceled = false; 
			preloader.visible = true; 
			dialog_box.message = 'Delete selected items?'; 
			dialog_box.hidden = false; 
			dialog_box.onOK = deleteItem; 
			dialog_box.onCancel = cancelDelete; 
		} 
		 
		/** 
		 * Method called when detelion process was canceled. 
		 */ 
		private function cancelDelete():void 
		{ 
			_deleteCanceled = true; 
			preloader.visible = false; 
			dialog_box.hidden = true; 
		} 
		 
		/** 
		 * Method used to delete a single item from the selection. 
		 * This method is also used as an e handler for when 
		 * an item has been deleted. 
		 * @param	e 
		 */ 
		private function deleteItem(e:Event = null):void 
		{ 
			if (e) e.target.removeEventListener(Event.COMPLETE, deleteItem); 
			 
			if (_selectionMan.selection.length > 0 && _deleteCanceled == false) 
			{ 
				dialog_box.message = 'Deleting items... ' + _selectionMan.selection.length +' remaining.';  
				 
				var req:URLRequest = new URLRequest(_scriptMan.deleteScript); 
				var vars:URLVariables = new URLVariables(); 
				var loader:URLLoader = new URLLoader(); 
				 
				vars.item = _selectionMan.selection.shift().path; 
				req.method = URLRequestMethod.POST; 
				req.data = vars; 
				removeCopied(vars.item); 
				 
				trace('Deleted item: ', vars.item); 
				trace('Clipboard items: ', _copiedFiles); 
				loader.addEventListener(Event.COMPLETE, deleteItem); 
				loader.load(req); 
			}else { 
				preloader.visible = false; 
				dialog_box.hidden = true; 
				_selectionMan.deselect(); 
				_manager.refresh(); 
			} 
			 
		} 
		 
		/** 
		 * Removes the deleted item from the _copiedCount array if this exists. 
		 * @param	item	Item to be removed. 
		 */ 
		private function removeCopied(item:String):void  
		{ 
			for (var i:uint = 0; i < _copiedFiles.length; i++)  
			{ 
				if (_copiedFiles[i] == item) 
				{ 
					_copiedFiles.splice(i, 1); 
					paste_btn.enabled = _copiedFiles.length > 0; 
					return; 
				} 
			} 
		} 
		 
		/** 
		 * Event handler for when the Rename button is released. 
		 * @param	e	MouseEvent of type MouseEvent.MOUSE_UP 
		 */ 
		private function onRename(e:MouseEvent):void 
		{ 
			var ext:String = _selectionMan.firstItem.extension; 
			var name:String = _selectionMan.firstItem.fileName; 
			if (_selectionMan.firstItem.type == FileData.FILE) 
				name = name.slice(0, name.lastIndexOf(ext) - 1); 
			 
			preloader.visible = true; 
			dialog_box.hidden = false; 
			dialog_box.renameMode = true; 
			dialog_box.onOK = renameItem; 
			dialog_box.message = name; 
			 
			dialog_box.onCancel = function ():void 
			{ 
				dialog_box.renameMode = false; 
				dialog_box.hidden = true; 
				preloader.visible = false; 
			} 
		} 
		 
		/** 
		 * Method used to rename an item. 
		 */ 
		private function renameItem():void 
		{ 
			var req:URLRequest = new URLRequest(_scriptMan.rename); 
			var vars:URLVariables = new URLVariables(); 
			var loader:URLLoader = new URLLoader(); 
			 
			vars.oldName = _selectionMan.firstItem.path; 
			if (_selectionMan.firstItem.type == FileData.FILE) 
				vars.newName = _manager.currentPath +'/'+ dialog_box.message + '.' + _selectionMan.firstItem.extension; 
			else vars.newName = _manager.currentPath + '/' + dialog_box.message; 
			 
			req.method = URLRequestMethod.POST; 
			req.data = vars; 
			 
			loader.addEventListener(Event.COMPLETE, onRenameComplete); 
			loader.load(req); 
		} 
		 
		/** 
		 * Event handler for when the selected item has been renamed and a response 
		 * has been received from the server. 
		 * @param	e	Event of type Event.COMPLETE 
		 */ 
		private function onRenameComplete(e:Event):void 
		{ 
			e.target.removeEventListener(Event.COMPLETE, onRenameComplete); 
			dialog_box.renameMode = false; 
			dialog_box.hidden = true; 
			preloader.visible = false; 
			_manager.refresh(); 
		} 
	} 
}

BasicButton.as

 
package com.tutsplus.active.fileBrowser.ui 
{ 
	import flash.display.MovieClip; 
	import flash.events.MouseEvent; 
	 
	/** 
	 * A basic button class used to add functionality 
	 * to simple buttons. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class BasicButton extends MovieClip 
	{ 
		private var _enabled:Boolean; 
		 
		public function BasicButton() 
		{ 
			gotoAndStop(1); 
			init(); 
		} 
		 
		/** 
		 * Initialization method. 
		 */ 
		private function init():void 
		{ 
			addEventListener(MouseEvent.ROLL_OVER, onMouseAction); 
			addEventListener(MouseEvent.ROLL_OUT, onMouseAction); 
			addEventListener(MouseEvent.MOUSE_DOWN, onMouseAction); 
			addEventListener(MouseEvent.MOUSE_UP, onMouseAction); 
		} 
		 
		/** 
		 * Event handler for mouse events dispatched by the button. 
		 *  
		 * @param	event MouseEvent. 
		 */ 
		private function onMouseAction(event:MouseEvent):void 
		{ 
			switch(event.type) 
			{ 
				case MouseEvent.ROLL_OVER: 
					gotoAndStop(2); 
					break; 
				case MouseEvent.ROLL_OUT: 
					gotoAndStop(1); 
					break; 
				case MouseEvent.MOUSE_DOWN: 
					gotoAndStop(3); 
					break; 
				case MouseEvent.MOUSE_UP: 
					gotoAndStop(2); 
					break; 
				default: 
					gotoAndStop(1); 
					break; 
			} 
		} 
		 
		/** 
		 * A boolean value which represents if the button is enabled or not. 
		 */ 
		override public function set enabled(value:Boolean):void 
		{ 
			_enabled = value; 
			alpha = _enabled ? 1 : 0.5; 
			mouseEnabled = _enabled; 
		} 
		 
		/** 
		 * A boolean value which represents if the button is enabled or not. 
		 * A disabled button does not receive mouse events and it's faded out. 
		 */ 
		override public function get enabled():Boolean 
		{ 
			return _enabled; 
		}	 
	} 
}

DialogBox.as

 
package com.tutsplus.active.fileBrowser.ui  
{ 
	import flash.display.MovieClip; 
	import flash.events.MouseEvent; 
	import flash.text.TextField; 
	import flash.text.TextFieldType; 
	 
	/** 
	 * A pop-up dialog box. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class DialogBox extends MovieClip 
	{ 
		/** 
		 * The function which will be executed when the  
		 * OK button is pressed. 
		 */ 
		public var onOK		:Function; 
		 
		/** 
		 * The function which will pe executed when the  
		 * Cancel button is pressed. 
		 */ 
		public var onCancel	:Function; 
		 
		public var message_txt			:TextField; 
		public var ok_btn, cancel_btn	:BasicButton; 
		 
		public static const OK		:uint = 1; 
		public static const CANCEL	:uint = 0; 
		 
		public function DialogBox()  
		{ 
			onOK = new Function(); 
			onCancel = new Function(); 
			ok_btn.addEventListener(MouseEvent.MOUSE_UP, onAccept); 
			cancel_btn.addEventListener(MouseEvent.MOUSE_UP, onDeny); 
		} 
		 
		/** 
		 * The dialog box hidden property. 
		 * When setting it to <i>true</i> the <i>onOK</i> 
		 * and <i>onCancel</i> functions will be reset. 
		 */ 
		public function set hidden(value:Boolean):void 
		{ 
			visible = !value; 
			if (value == true) 
			{ 
				onOK = new Function(); 
				onCancel = new Function(); 
				ok_btn.enabled = cancel_btn.enabled = true; 
				message = ''; 
			} 
		} 
		 
		/** 
		 * Event handler for when the OK button is pressed. 
		 *  
		 * @param	event	MouseEvent. 
		 */ 
		private function onAccept(event:MouseEvent):void 
		{ 
			onOK(); 
		} 
		 
		/** 
		 * Event handler for when the Cancel button is pressed. 
		 *  
		 * @param	event	MouseEvent. 
		 */ 
		private function onDeny(event:MouseEvent):void 
		{ 
			onCancel(); 
		} 
		 
		/** 
		 * Modify the state of a button. 
		 * @param	button		Use DialogBox.OK or DialogBox.CANCEL. 
		 * @param	enabled		If not set it will default to false. 
		 */ 
		public function modButton(button:uint, enabled:Boolean = false):void 
		{ 
			switch(button) 
			{ 
				case OK: 
					ok_btn.enabled = enabled; 
					break; 
				case CANCEL: 
					cancel_btn.enabled = enabled; 
					break; 
				default: 
					throw new Error('Invalid button'); 
					break; 
			} 
		} 
		 
		/** 
		 * If set to true the dialog box will get into rename mode. 
		 * If set to true the text field becomes an input field. 
		 */ 
		public function set renameMode(value:Boolean):void 
		{ 
			message_txt.selectable = value; 
			message_txt.background = value; 
			message_txt.type = value ? TextFieldType.INPUT : TextFieldType.DYNAMIC; 
			message_txt.backgroundColor = 0x040912; 
			message_txt.restrict = value ? "a-zA-Z0-9 .,!~_&%$€#+*\\-":null; 
		} 
		 
		/** 
		 * The message of the dialog box. 
		 */ 
		public function set message(value:String):void  
		{ 
			message_txt.text = value; 
		} 
		 
		/** 
		 * The message of the dialog box. 
		 */ 
		public function get message():String  
		{ 
			return message_txt.text; 
		} 
	} 
 
}

FileItem.as

 
package com.tutsplus.active.fileBrowser.ui  
{ 
	import com.tutsplus.active.fileBrowser.data.FileData; 
	import flash.display.Bitmap; 
	import flash.display.MovieClip; 
	import flash.events.MouseEvent; 
	import flash.text.TextField; 
	 
	/** 
	 * A visual representation of an item in the list. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FileItem extends MovieClip 
	{ 
		private var _data	:FileData; 
		private var _icon	:Bitmap; 
		 
		public var labelField_txt	:TextField; 
		public var state			:MovieClip; 
		 
		/** 
		 * Creates a new FileItem object 
		 *  
		 * @param	data	The FileData object to be represented. 
		 */ 
		public function FileItem(data:FileData)  
		{ 
			state.gotoAndStop(1); 
			doubleClickEnabled = true; 
			init(); 
			this.data = data; 
		} 
		 
		/** 
		 * Initialization function. 
		 */ 
		private function init():void 
		{ 
			_icon = new Bitmap(); 
			_icon.smoothing = true; 
			mouseChildren = false; 
			addChild(_icon); 
			labelField_txt.selectable = false; 
			addEventListener(MouseEvent.ROLL_OVER, changeState); 
			addEventListener(MouseEvent.ROLL_OUT, changeState); 
		} 
		 
		/** 
		 * Event handler for mouse events. It changes the state of the FileItem 
		 * to NORMAL, OVER or SELECTED depending on the event type. 
		 * @param	event 
		 */ 
		private function changeState(event:MouseEvent):void 
		{ 
			if (selected == false) 
			{ 
				if (event.type == MouseEvent.ROLL_OVER) 
					state.gotoAndStop(2); 
				else if (event.type == MouseEvent.ROLL_OUT) 
					state.gotoAndStop(1); 
			} 
		} 
		 
		/** 
		 * The <i>selected</i> state property. 
		 */ 
		public function set selected(value:Boolean):void 
		{ 
			if (value) state.gotoAndStop(3); 
			else state.gotoAndStop(1); 
		} 
		 
		/** 
		 * The <i>selected</i> state property. 
		 */ 
		public function get selected():Boolean  
		{  
			return state.currentFrame == 3;  
		} 
		 
		/** 
		 * The ID of the FileItem object. This is based 
		 * on the child index in the container. 
		 */ 
		public function get id():uint  
		{  
			return this.parent.getChildIndex(this);  
		} 
		 
		/** 
		 * The data to be represented by the FileItem object 
		 */ 
		private function set data(value:FileData):void 
		{ 
			_data = value; 
			labelField_txt.text = _data.name; 
			getIcon(); 
		} 
		 
		/** 
		 * The extension of the FileData being represented. 
		 */ 
		public function get extension():String 
		{ 
			return _data.extension; 
		} 
		 
		/** 
		 * The path of the FileData being represented. 
		 */ 
		public function get path():String 
		{  
			return _data.path;  
		} 
		 
		/** 
		 * The file name of the FileData being represented. 
		 */ 
		public function get fileName():String 
		{  
			return _data.name;  
		} 
		 
		/** 
		 * The type of the FileData being represented. 
		 */ 
		public function get type():String 
		{  
			return _data.type;  
		} 
		 
		/** 
		 * Gets the appropriate icon for the FileItem. 
		 * The icon is based on the file extension and type 
		 */ 
		private function getIcon():void 
		{ 
			if (_data.type == FileData.DIR) 
				_icon.bitmapData = new FolderIco(96, 96); 
			else  
				switch(_data.extension)  
				{ 
					case 'jpg': 
					case 'png': 
					case 'gif': 
						_icon.bitmapData = new ImageIco(96, 96); 
						break; 
					case 'mp3': 
					case 'flac': 
					case 'aac': 
						_icon.bitmapData = new AudioIco(96, 96); 
						break; 
					case 'avi': 
					case 'mp4': 
					case 'flv': 
						_icon.bitmapData = new VideoIco(96, 96); 
						break; 
					case 'php': 
					case 'php4': 
					case 'php5': 
						_icon.bitmapData = new PHPIco(96, 96); 
						break; 
					case 'txt': 
					case 'ini': 
					case 'inc': 
						_icon.bitmapData = new TextIco(96, 96); 
						break; 
					case 'html': 
					case 'htm': 
						_icon.bitmapData = new HTMLIco(96, 96); 
						break; 
					case 'swf': 
					case 'fla': 
						_icon.bitmapData = new SWFIco(96, 96); 
						break; 
					default: 
						_icon.bitmapData = new FileIco(96, 96); 
						break; 
				} 
		} 
	} 
 
}

FilesList.as

 
package com.tutsplus.active.fileBrowser.ui 
{ 
	import com.tutsplus.active.fileBrowser.data.FileListManager; 
	import com.alexxcz.ui.SimpleScrollbar; 
	import flash.display.MovieClip; 
	import flash.events.Event; 
	 
	/** 
	 * A list of thumbnails used to display directory contents 
	 * received from the server. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FilesList extends MovieClip 
	{ 
		private var _sb			:SimpleScrollbar; 
		private var _holder		:MovieClip; 
		private var _mask		:MovieClip; 
		private var _dataMan	:FileListManager; 
		private var _items		:Array; 
		 
		private const LIST_WIDTH	:uint = 539; 
		private const LIST_HEIGHT	:uint = 282; 
		private const ITEM_SPACING	:uint = 10; 
		 
		public function FilesList() 
		{ 
			_items = []; 
			createHolder(); 
			createMask(); 
			createScrollbar(); 
		} 
		 
		/** 
		 * This method creates the holder of the list items. 
		 */ 
		private function createHolder():void 
		{ 
			_holder = new MovieClip(); 
			addChild(_holder); 
			_holder.x = _holder.y = 4; 
		} 
		 
		/** 
		 * This method creates the mask for the items holder. 
		 */ 
		private function createMask():void 
		{ 
			_mask = new MovieClip(); 
			addChild(_mask); 
			_mask.x = _mask.y = 4; 
			with(_mask){ 
				graphics.beginFill(0); 
				graphics.drawRect(0, 0, LIST_WIDTH, LIST_HEIGHT); 
				graphics.endFill(); 
			} 
			_holder.mask = _mask; 
		} 
		 
		/** 
		 * This method creates a scrollbar for the list. 
		 */ 
		private function createScrollbar():void 
		{ 
			_sb = new SimpleScrollbar(); 
			_sb.setColors(0x163862, 0x06264d); 
			addChild(_sb); 
			_sb.setSize(10, height-8); 
			_sb.x = _mask.width + _sb.width; 
			_sb.y = 4; 
		} 
		 
		/** 
		 * The dataManager property binds a FileListManager data model 
		 * with the list. 
		 */ 
		public function set dataManager(value:FileListManager):void 
		{ 
			_dataMan = value; 
			initDataMan(); 
		} 
		 
		/** 
		 * This method adds required event listeners to the data manager and 
		 * the scrollbar. 
		 */ 
		private function initDataMan():void 
		{ 
			_dataMan.addEventListener(Event.CHANGE, onListChange); 
			_sb.addEventListener(Event.SCROLL, onScroll); 
		} 
		 
		/** 
		 * Event handler for when the mouse wheel is scrolled. 
		 *  
		 * @param	event	Event of type SCROLL. 
		 */ 
		private function onScroll(e:Event):void 
		{ 
			_holder.y = -_sb.scrollValue * (_holder.height - _mask.height + ITEM_SPACING * 2) +4; 
		} 
		 
		/** 
		 * Event handler for when the list of files has changed. 
		 * @param	event 
		 */ 
		private function onListChange(e:Event):void 
		{ 
			_holder.y = 4; 
			_sb.scrollValue = 0; 
			while (_items.length > 0)  
			{ 
				_holder.removeChild(_items[0]); 
				_items.shift(); 
			} 
			for (var i:int = 0; i < _dataMan.numFiles; i++)  
			{ 
				var fi:FileItem = new FileItem(_dataMan.getFileAt(i)); 
				_items.push(fi); 
			} 
			positionItems(); 
		} 
		 
		/** 
		 * This method positions all items in the list. 
		 */ 
		private function positionItems():void 
		{ 
			var colNum:uint = LIST_WIDTH / 96; 
			var rowNum:uint = 0; 
			 
			for (var i:int = 0; i < _items.length; i++)  
			{ 
				_items[i].x = ITEM_SPACING + ITEM_SPACING * (i%colNum) + i%colNum * 96; 
				_items[i].y = ITEM_SPACING + rowNum * ITEM_SPACING + rowNum * 128; 
				if ((i + 1) % colNum == 0) 
					rowNum++; 
				_holder.addChild(_items[i]); 
			} 
			_sb.contentRatio = _mask.height / _holder.height; 
		} 
	} 
}

SelectionManager.as

 
package com.tutsplus.active.fileBrowser.ui  
{ 
	import com.tutsplus.active.fileBrowser.data.FileData; 
	import flash.display.DisplayObject; 
	import flash.events.Event; 
	import flash.events.EventDispatcher; 
	import flash.events.MouseEvent; 
	 
	/** 
	 * Dispatched when the selection has changed. 
	 *  
	 * @eventType flash.events.Event.SELECT 
	 */ 
	[Event(name = "select", type = "flash.events.Event")] 
	 
	/** 
	 * A basic selection manager sistem. 
	 * Can handle single selections by mouse and multiple selections 
	 * by using the SHIFT and CONTROL key. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class SelectionManager extends EventDispatcher 
	{ 
		private var _items	:Array; 
		private var _last	:FileItem; 
		private var _holder	:DisplayObject; 
		 
		public function SelectionManager()  
		{ 
			_items = []; 
		} 
		 
		/** 
		 * The holder for which to add select functionality. 
		 */ 
		public function set holder(value:DisplayObject):void  
		{ 
			_holder = value; 
			_holder.addEventListener(MouseEvent.CLICK, onClick); 
		} 
		 
		/** 
		 * Event handler for when the handler dispatches mouse events 
		 * of type MouseEvent.CLICK. 
		 *  
		 * @param	event	MouseEvent of type MouseEvent.CLICK. 
		 */ 
		private function onClick(e:MouseEvent):void 
		{ 
			if (e.target is FileItem) 
			{ 
				if (e.shiftKey)	shiftSelect(FileItem(e.target)); 
				else if (e.ctrlKey) ctrlSelect(FileItem(e.target)); 
				else normalSelect(FileItem(e.target)); 
				 
				dispatchEvent(new Event(Event.SELECT)); 
			}else deselect(); 
		} 
		 
		/** 
		 * Method used to select multiple items using the SHIFT key. 
		 *  
		 * @param	target	The currently clicked item. 
		 */ 
		private function shiftSelect(target:FileItem):void 
		{ 
			if (_items.length == 0) normalSelect(target); 
			else { 
				var min:uint = Math.min(_last.id, target.id); 
				var max:uint = Math.max(_last.id, target.id); 
				var it:Object; 
				clear(); 
				 
				for (var i:int = min; i <= max; i++)  
				{ 
					it = target.parent.getChildAt(i) 
					if (it is FileItem)  
					{ 
						it.selected = true; 
						_items.push(it); 
					} 
				} 
			} 
		} 
		 
		/** 
		 * Method used to select multiple items using the CONTROL key. 
		 *  
		 * @param	target	The currently clicked item. 
		 */ 
		private function ctrlSelect(target:FileItem):void 
		{ 
			if (target.selected == false) { 
				target.selected = true; 
				_items.push(target); 
			}else { 
				target.selected = false 
				_items.splice(_items.indexOf(target), 1); 
			} 
			_items.sortOn('id', Array.NUMERIC); 
			_last = target; 
		} 
		 
		/** 
		 * Method used to select only one item. 
		 *  
		 * @param	target The currently clicked item. 
		 */ 
		private function normalSelect(target:FileItem):void 
		{ 
			clear(); 
			target.selected = true; 
			_items[0] = _last = target; 
		} 
		 
		/** 
		 * Deselects all items. 
		 */ 
		public function deselect():void 
		{ 
			clear(); 
			dispatchEvent(new Event(Event.SELECT)); 
		} 
		 
		/** 
		 * Clears the current selection without dispatching 
		 * any event. 
		 */ 
		private function clear():void 
		{ 
			while(_items.length > 0) 
			{ 
				_items[0].selected = false; 
				_items.shift(); 
			} 
		} 
		 
		/** 
		 * This method is used to check if the selection holds only  
		 * one item and if the selected item is downloadable aka is of type 
		 * FileData.FILE. 
		 *  
		 * @return	Returns true if the item is downloadable and false otherwise. 
		 */ 
		public function isDownloadable():Boolean 
		{ 
			if (isSingle()) 
				return _items[0].type == FileData.FILE; 
			else return false; 
		} 
		 
		/** 
		 * This method checks if the selection holds only one item, 
		 * regardless of that item's type. 
		 *  
		 * @return	Returns true if it's the only selected item or false otherwise. 
		 */ 
		public function isSingle():Boolean 
		{ 
			return _items.length == 1; 
		} 
		 
		/** 
		 * This method returns the first item in the selection. 
		 */ 
		public function get firstItem():FileItem  
		{  
			return _items[0];  
		} 
		 
		/** 
		 * Returns an array of FileItem objects representing the currently 
		 * selected items. 
		 */ 
		public function get selection():Array  
		{  
			return _items;  
		} 
	} 
 
}

FileData.as

 
package com.tutsplus.active.fileBrowser.data  
{ 
	/** 
	 * Used for storing data about a file item  
	 * received from the server. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FileData 
	{ 
		private var _type	:String; 
		private var _name	:String; 
		private var _path	:String; 
		 
		public static const FILE	:String = 'file'; 
		public static const DIR		:String = 'directory'; 
		 
		/** 
		 * Create a new FileData item. 
		 *  
		 * @param	type	The file type. Can be FileData.FILE or FileData.DIR. 
		 * @param	name	The name of the file (includes the extension). 
		 * @param	path	The path of the file on the server. 
		 */ 
		public function FileData(type:String = null, name:String = null, path:String = null)  
		{ 
			_type = type; 
			_name = name; 
			_path = path; 
		} 
		 
		/** 
		 * The file type. 
		 * File types are <i>FileData.FILE</i> and <i>FileData.DIR</i>. 
		 */ 
		public function set type(value:String):void  
		{ 
			_type = value == FILE || value == DIR ? value:_type; 
		} 
		 
		/** 
		 * The name of the file including the extension. 
		 */ 
		public function set name(value:String):void  
		{ 
			_name = value.length > 0 ? value : _name; 
		} 
		 
		/** 
		 * The path of the file on the server. 
		 */ 
		public function set path(value:String):void  
		{ 
			_path = value.length > 0 ? value : _path; 
		} 
		 
		/** 
		 * The file type. 
		 * File types are <i>FileData.FILE</i> and <i>FileData.DIR</i>. 
		 */ 
		public function get type():String { return _type; } 
		 
		/** 
		 * The name of the file including the extension. 
		 */ 
		public function get name():String { return _name; } 
		 
		/** 
		 * The path of the file on the server. 
		 */ 
		public function get path():String { return _path; } 
		 
		/** 
		 * The extension of the file. 
		 */ 
		public function get extension():String  
		{  
			var exp:RegExp = /(\.[a-zA-Z0-9]+)$/; 
			if (_type == DIR) 
				return ''; 
			else return _name.slice(_name.search(exp)+1).toLowerCase();  
		} 
		 
		/** 
		 * Returns a string representation of the file. 
		 * @return	String 
		 */ 
		public function toString():String 
		{ 
			var s:String = '[' 
			s += _type + '('; 
			s += _name; 
			s += ')]'; 
			return s; 
		} 
	} 
 
}

FileListManager.as

 
package com.tutsplus.active.fileBrowser.data 
{ 
	import flash.events.Event; 
	import flash.events.EventDispatcher; 
	import flash.events.IOErrorEvent; 
	import flash.net.URLLoader; 
	import flash.net.URLRequest; 
	import flash.net.URLRequestMethod; 
	import flash.net.URLVariables; 
	 
	/** 
	 * Dispatched when the file list has changed. 
	 *  
	 * @eventType flash.events.Event.CHANGE 
	 */ 
	[Event(name = "change", type = "flash.events.Event")] 
	 
	/** 
	 * Dispatched when a request for directory contents  
	 * has been sent to the server. 
	 */ 
	[Event(name = "init", type = "flash.events.Event")] 
	 
	/** 
	 * The file list manager is a data model for  
	 * the file browser. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class FileListManager extends EventDispatcher 
	{ 
		private var _files			:Array; 
		private var _history		:Array; 
		private var _currentPath	:String; 
		private var _sm				:ScriptManager; 
		 
		public function FileListManager() 
		{ 
			_currentPath = 'root'; 
			_files = []; 
			_history = []; 
			_sm = ScriptManager.getInstance(); 
		} 
		 
		/** 
		 * Fetches the files from the specified path on the server. 
		 * If no path is specified it will get the list of files 
		 * from the root folder specified in the PHP script. 
		 *  
		 * @param	path	The path of the directory for which contents you want. 
		 */ 
		public function getFileList(path:String = 'root'):void 
		{ 
			var ul:URLLoader = new URLLoader(); 
			var req:URLRequest = new URLRequest(_sm.fileList); 
			var vars:URLVariables = new URLVariables(); 
			req.method = URLRequestMethod.POST; 
			vars.directory = path; 
			req.data = vars; 
			ul.addEventListener(Event.COMPLETE, onListLoad); 
			ul.addEventListener(IOErrorEvent.IO_ERROR, onListIOError); 
			dispatchEvent(new Event(Event.INIT)); 
			ul.load(req); 
		} 
		 
		/** 
		 * IOErrorEvent handler. 
		 * This method will be executed when the download script 
		 * was not found. 
		 *  
		 * @param	event IOErrorEvent of type IOErrorEvent.IO_ERROR 
		 * @see 	flash.events.IOErrorEvent 
		 */ 
		private function onListIOError(event:IOErrorEvent):void 
		{ 
			trace('Script not found!'); 
		} 
		 
		/** 
		 * Event handler for when the list has been loaded. 
		 *  
		 * @param	event	Event of type Event.COMPLETE 
		 * @see		flash.events.Event 
		 */ 
		private function onListLoad(event:Event):void 
		{ 
			var xml:XML = new XML(event.target.data); 
			 
			_currentPath = xml.@currentPath; 
			 
			setHistory(); 
			createList(xml.children()); 
		} 
		 
		/** 
		 * Updates the history path. 
		 */ 
		private function setHistory():void 
		{ 
			var l:uint = _history.length; 
			 
			if (_history[l-1] != _currentPath) 
				_history.push(_currentPath); 
		} 
		 
		/** 
		 * Goes one step back into history. 
		 */ 
		public function goBack():void 
		{ 
			var l:uint = _history.length; 
			if (l > 1) 
			{ 
				_history.pop(); 
				getFileList(_history[l - 2]); 
			} 
		} 
		 
		/** 
		 * Checks if the history path is empty or not. 
		 * History path is empty if the current directory is 
		 * root. 
		 *  
		 * @return	<i>true</i> if the history path is not empty   
		 * or false otherwise. 
		 */ 
		public function hasHistory():Boolean 
		{ 
			return _history.length > 1; 
		} 
		 
		/** 
		 * Populates the array of files using the received data 
		 * from the server. 
		 * This method dispatches an event of type <i>Event.CHANGE</i> 
		 * used to notify any listeners that the list of files has changed. 
		 *  
		 * @param	xmlList	An XML list representing the file list. 
		 */ 
		private function createList(xmlList:XMLList):void 
		{ 
			_files = []; 
			for (var i:uint = 0; i < xmlList.length(); i++) 
			{ 
				var fd:FileData = new FileData() 
				if (xmlList[i].name() == 'dir') 
					fd.type = FileData.DIR; 
				else if (xmlList[i].name() == 'file') 
					fd.type = FileData.FILE; 
				fd.name = xmlList[i].name; 
				fd.path = xmlList[i].path; 
				_files.push(fd); 
			} 
			_files.sortOn(['type', 'name'], Array.CASEINSENSITIVE); 
			dispatchEvent(new Event(Event.CHANGE)); 
		} 
		 
		/** 
		 * The current path of the directory which is browser. 
		 * This path has the root real path on the server replaced  
		 * with the word 'root'. 
		 */ 
		public function get currentPath():String 
		{  
			return _currentPath;  
		} 
		 
		/** 
		 * Reloads the file list using the current path. 
		 */ 
		public function refresh():void 
		{  
			getFileList(_currentPath); 
		} 
		 
		/** 
		 * The number of items in the current directory. 
		 */ 
		public function get numFiles():uint 
		{ 
			return _files?_files.length:0; 
		} 
		 
		/** 
		 * Returns a file (or directory) at the specified index. 
		 *  
		 * @param	index	The index of the file. 
		 * @return	A FileData object. 
		 */ 
		public function getFileAt(index:uint):FileData 
		{ 
			if (_files.length > index) 
				return FileData(_files[index]); 
			else throw new Error("There is no file at the specified index."); 
		} 
	} 
}

ScriptManager.as

 
package com.tutsplus.active.fileBrowser.data  
{ 
	/** 
	 * A dynamic class which holds the scripts used by the  
	 * file browser. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public dynamic class ScriptManager 
	{ 
		private static var _instance:ScriptManager; 
		 
		public function ScriptManager()  
		{ 
			if (_instance)	throw new Error('Sigleton: Use ScriptManager.getInstance()'); 
			else _instance = this; 
		} 
		 
		/** 
		 * Returns the existing instance of the ScriptManager object. 
		 * If no instance exists it will create a new one. 
		 * @return 
		 */ 
		public static function getInstance():ScriptManager 
		{ 
			if (_instance) 
				return _instance 
			else { 
				_instance = new ScriptManager(); 
				return _instance; 
			} 
		} 
	} 
 
}

Also here's my SimpleScrollbar class:


SimpleScrollbar.as

 
package com.alexxcz.ui 
{ 
	import flash.display.Sprite; 
	import flash.events.Event; 
	import flash.events.MouseEvent; 
	import flash.geom.Rectangle; 
	 
	/** 
	 * Dispatched when the <em>scrollValue</em> has changed and when  
	 * the mouse wheel has been used to move the thumb. 
	 *  
	 * @eventType flash.events.Event.SCROLL 
	 */ 
	[Event(name = "scroll", type = "flash.events.Event")] 
	 
	/** 
	 * A simple scrollbar. 
	 * ... 
	 * @author Alexxcz 
	 */ 
	public class SimpleScrollbar extends Sprite 
	{ 
		private var _thumb		:Sprite; 
		private var _track		:Sprite; 
		private var _enabled	:Boolean; 
		private var _rect		:Rectangle; 
		private var _scrollRect	:Rectangle; 
		private var _contRatio	:Number; 
		private var _w			:uint = 15; 
		private var _h			:uint = 200; 
		private var _thumbColor	:uint; 
		private var _trackColor	:uint; 
		 
		/** 
		 * Constructor. 
		 */ 
		public function SimpleScrollbar()  
		{ 
			_contRatio  = 0; 
			_trackColor = 0x163862; 
			_thumbColor = 0x06264d; 
			addEventListener(Event.ADDED_TO_STAGE, init); 
		} 
		 
		/** 
		 * Initialization method. 
		 *  
		 * @param	e	Event. 
		 */ 
		private function init(event:Event = null):void 
		{ 
			removeEventListener(Event.ADDED_TO_STAGE, init); 
			initGraphics(); 
			initLogic(); 
		} 
	 
		override public function set scrollRect(value:Rectangle):void  
		{ 
			_scrollRect = value; 
		} 
		override public function get scrollRect():Rectangle 
		{ 
			return _scrollRect; 
		} 
		/** 
		 * Adds events listeners and does all logical stuff. 
		 */ 
		private function initLogic():void 
		{ 
			_rect = new Rectangle(0, 0, 0, _h - _thumb.height); 
		} 
		 
		/** 
		 * Initializes all graphic stuff. 
		 */ 
		private function initGraphics():void 
		{ 
			_track = new Sprite(); 
			_thumb = new Sprite(); 
			 
			addChild(_track); 
			addChild(_thumb); 
			draw(); 
		} 
		 
		public function setColors(thumbColor:uint, trackColor:uint):void 
		{ 
			_thumbColor = thumbColor; 
			_trackColor = trackColor; 
		} 
		 
		/** 
		 * Specifies if the scroll bar is enabled. 
		 */ 
		public function set enabled(value:Boolean):void 
		{ 
			_enabled = value; 
			 
			if (_enabled === true) activate(); 
			else if (_enabled === false) deactivate(); 
		} 
		 
		/** 
		 * Specifies if the scroll bar is enabled. 
		 */ 
		public function get enabled():Boolean  
		{  
			return _enabled; 
		} 
		 
		/** 
		 * Deactivates the scrollbar. 
		 */ 
		private function deactivate():void 
		{ 
			alpha = 0.8; 
			_thumb.buttonMode = false; 
			 
			_thumb.removeEventListener(MouseEvent.MOUSE_DOWN, dragThumb); 
			_track.removeEventListener(MouseEvent.MOUSE_DOWN, onTrackPress); 
			stage.removeEventListener(MouseEvent.MOUSE_UP, stopDragThumb); 
			stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onWheel); 
		} 
		 
		/** 
		 * Activates the scrollbar. 
		 */ 
		private function activate():void 
		{ 
			alpha = 1; 
			_thumb.buttonMode = true; 
			 
			_thumb.addEventListener(MouseEvent.MOUSE_DOWN, dragThumb); 
			_track.addEventListener(MouseEvent.MOUSE_DOWN, onTrackPress); 
			stage.addEventListener(MouseEvent.MOUSE_UP, stopDragThumb); 
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, onWheel); 
		} 
		 
		/** 
		 * Mouse Wheel event handler. 
		 * @param	e	MouseEvent. 
		 */ 
		private function onWheel(event:MouseEvent):void 
		{ 
			var p:Number 	= (_contRatio) * event.delta / 10; 
			scrollValue 	-= p; 
		} 
		 
		/** 
		 * Mouse down on track handler. 
		 * @param	e	MouseEvent. 
		 */ 
		private function onTrackPress(event:MouseEvent):void 
		{ 
			var p:Number 	= _track.mouseY / _track.height; 
			scrollValue 	= p; 
		} 
		 
		/** 
		 * Mouse up handler. 
		 * @param	e	MouseEvent. 
		 */ 
		private function stopDragThumb(event:MouseEvent):void 
		{ 
			_thumb.stopDrag(); 
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveEv); 
		} 
		 
		/** 
		 * Mouse down on thumb handler. 
		 * @param	e	MouseEvent. 
		 */ 
		private function dragThumb(event:MouseEvent):void 
		{ 
			_thumb.startDrag(false, _rect); 
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveEv); 
		} 
		 
		/** 
		 * Mouse move handler. 
		 * @param	e	MouseEvent. 
		 */ 
		private function onMouseMoveEv(event:MouseEvent):void 
		{ 
			dispatchEvent(new Event(Event.SCROLL)); 
		} 
		 
		/** 
		 * Scroll position. 
		 */ 
		public function get scrollValue():Number 
		{ 
			return _thumb.y / (_track.height - _thumb.height); 
		} 
		 
		/** 
		 * Scroll position. 
		 */ 
		public function set scrollValue(value:Number):void  
		{ 
			if (value < 0) 		value = 0; 
			else if (value > 1) value = 1; 
			 
			_thumb.y = value * (_track.height - _thumb.height); 
			dispatchEvent(new Event(Event.SCROLL)); 
		} 
		 
		/** 
		 * Sets the size of the scrollbar. 
		 * @param	width	The width of the scrollbar. 
		 * @param	height	The height of the scrollbar. 
		 */ 
		public function setSize(width:Number, height:Number):void 
		{ 
			_w = width; 
			_h = height; 
			update(); 
		} 
		 
		/** 
		 * The content ratio used for resizing the thumb. 
		 */ 
		public function set contentRatio(value:Number):void 
		{ 
			_contRatio = value; 
			if (value > 1) _contRatio = 1; 
			else if (value < 0) _contRatio = 0; 
			enabled = (_contRatio > 0) && (_contRatio < 1); 
			update(); 
		} 
		 
		/** 
		 * The content ratio used for resizing the thumb. 
		 */ 
		public function get contentRatio():Number  
		{  
			return _contRatio; 
		} 
		/** 
		 * The width of the scrollbar in pixels. 
		 */ 
		override public function get width():Number  
		{  
			return _w; 
		} 
		 
		/** 
		 * The width of the scrollbar in pixels. 
		 */ 
		override public function set width(value:Number):void  
		{ 
			setSize(value, _h); 
		} 
		 
		/** 
		 * The height of the scrollbar in pixels. 
		 */ 
		override public function get height():Number  
		{  
			return _h; 
		} 
		 
		/** 
		 * The height of the scrollbar in pixels. 
		 */ 
		override public function set height(value:Number):void  
		{ 
			setSize(_w, value); 
		} 
		 
		/** 
		 * Draws the scrollbar. 
		 */ 
		private function draw():void 
		{ 
			drawTrack(); 
			drawThumb(); 
		} 
		 
		/** 
		 * Draws the thumb. 
		 */ 
		private function drawThumb():void 
		{ 
			var h:Number = (_contRatio>0&&_contRatio<1)?_contRatio * _h:0; 
			with (_thumb) { 
				graphics.clear(); 
				graphics.beginFill(_thumbColor); 
				graphics.drawRect(0, 0, _w, h); 
				graphics.endFill(); 
			} 
			if (_thumb.y > _track.height - _thumb.height) 
				_thumb.y = _track.height - _thumb.height; 
		} 
		 
		/** 
		 * Draws the track. 
		 */ 
		private function drawTrack():void 
		{ 
			with (_track) { 
				graphics.clear(); 
				graphics.beginFill(_trackColor); 
				graphics.drawRect(0, 0, _w, _h); 
				graphics.endFill(); 
			} 
		} 
		 
		/** 
		 * Updates the graphics of the scrollbar. 
		 */ 
		private function update():void 
		{ 
			draw(); 
			_rect = new Rectangle(0, 0, 0, _h - _thumb.height); 
		} 
	} 
 
}

Step 110: Conclusion

So here we are at the end of this tutorial. I hope you found it useful and educative at least as much I have. Thank you for having the patience to follow me through these steps!

It's not necessary but please leave your opinion on this tutorial and what you like/dislike about it to help me publish better tutorials in the future and improve the existing ones.

Advertisement