logo
468x60-2-495


  • Home
  • Privacy Policy
  • About
search
top
Oct 13, 2011 Posted on Oct 13, 2011 in Hints and Tips | 10 comments

Create a Flexible XMPP Chat for a Member-Based Website With Flash and PHP

We’ll be looking at how to create a XMPP chat application that can be used in many different scenarios. You’ll learn how to integrate an external database with Ignite Realtime’s Openfire Jabber Server and how to use the XIFF library to create custom XMPP extensions that can be used to send custom data across a network.

You could use this to build a standard standard chat room app, with a page devoted to it, or you could run it alongside another piece of Flash content, like Kongregate does with its games.



Final Result Preview

Let’s take a look at the interface of the final result we will be working towards (this demo does not function as an actual chat client):

Here’s a video demo that shows it in action:



Step 1: Prerequisites

This tutorial assumes that you have some experience with PHP. You must be have Wamp Server installed on your system and you should also be somewhat familiar with PhpMyAdmin. If you do not have WAMP you can download it here. You will also need to download the Openfire Jabber Server and the XIFF API library from the Ignite Realtime website. I will walk you through the Openfire installation process. Finally you will need the latest version of Java installed on your operating system and Flash CS4 or later. I will be using Flash CS5 Professional in this tutorial. If you do not have the latest version of Java, you can download the latest version here.



Step 2: Setting Up Our Database

Make sure that Wamp Server is running on your computer and navigate to http://localhost/phpmyadmin/ in your web browser.

Create a new database called MyContentSite using PhpMyAdmin.

create the main database

Create a new table in the MyContentSite database called myMembers with eight fields.

the mymembers table

After the myMembers table has been sucessfully created by PhpMyAdmin, create the following fields:

  • uid
  • first_name
  • last_name
  • my_username
  • my_password
  • email
  • status_message
  • country

Your screen should look as follows:

members fields 1

Let me break down each field. The uid field should be of type INT. You can change the field’s type from the Type column. Make this field the primary index and set this field to auto-increment itself. Do this by selecting the PRIMARY option underneath the INDEX column within this fields row. Then check the checkbox under the Auto-Increment column. This field represents the userID of the current member of our website. The first_name, last_name, my_username, my_password, email, and country fields should be have the datatype or VARCHAR and the Length/Value should be set to 255.

Note: The my_password fields shown in the images hold an MD5 of the user’s password. You may choose to store passwords plain without any encryption but for this tutorial I will be using hashed passwords.

members fields 2

Finally, the status_message field should be of the of type MEDIUMTEXT.

Once you have created the all of the fields click the save button.

ssaved members fields

Now we are ready to create two dummy accounts that we will use to login to our website and join chat rooms later in this tutorial. Click on the Insert tab. You will be presented with a form for creating a new row in the table.

Leave the uid field empty because we want this field to automatically increment itself. Set the first_name to Jane and the last_name field to Doe. Set the my_username to janedoe with all lower cass letters. For the my_password field, we’ll be using the hashed value for our password which is tutsplus in all lower case letters. Type ca28ad0733a3dde9dc1f30e32718d209 into the my_password field. You can set the email field to an email address of your choosing and the status_message field to whatever you’d like. Set the country field to whatever country you’d like as well. When you are finished click on the save button . Repeat this process to create an account for a John Doe with the my_username field set to johndoe123 in all lower case letters. Use the same password as before.

jane doe account
john doe account
saved accounts


Step 2: Installing Openfire

Once you have downloaded Openfire from the Ignite Realtime website, run the installation exe file (or dmg file if you are using a Mac).

initializing installer

Select your langauge.

choose your language

Click Next to continue.

begin installation

Accept the License Agreement.

terms of use

Choose a directory and click Next to continue.

installation directory

Select a Start Menu folder.

start menu folder

Click Next to begin the installation.

extraction

Once the installation has completed, click Finish to to run Openfire. The Openfire service will start automatically when the program is run. Click the Launch Admin button when Openfire has finished booting.

installation complete
Openfire starting up
Openfire running

Now we will set up Openfire. Select your preferred language and click Continue.

choose language

Leave the Admin Console Port field and Secure Admin Console Port field to their default values. For this tutorial leave the Domain field to it’s default value as well. You can change this later to your website’s domain. Click Continue.

server settings

Select the Standard Database Connection option and click Continue.

database settings

Under Database Driver Presets, choose MySQL. Type com.mysql.jdbc.Driver in the JDBC Driver Class field. Change [host-name] to localhost and change [database-name] to mycontentsite in the Database URL field. Set Username and Password to your MySQL database’s username and password. For this tutorial I used the default username for MySQL which is root and the Password field remains blank. Click Continue to move on.

mysql setup 1
mysql setup 2

Leave the Profile Settings to Default. Click Continue to move on.

profile settings

Choose an email address for your Administrator Account and a password then continue.

admin password

We are now done the setup process. You may now login to the admin console. Use the default username admin and the password the you chose during setup.


Step 3: Setting Up the Chat Rooms

Our application allow users to communicate within chat rooms. But for this to happen our users must have chat rooms to join. Let’s create the chat rooms using the Openfire Admin Console. If you haven’t already, start Openfire Server. Log in the Openfire’s Admin Console. Navigate to the Group Chat Rooms page by clicking on the Group Chat tab.

chat room home

Click Create New Room on the left hand side of the screen. Fill out the details as you see them in the image below.

new chat room

When you are finished, click on the Save Changes button. If the room was created successfully, you should see a message and a Green check.

room creation

Follow the same steps to create two more chat rooms.

another room
last room
all chat rooms

Step 4: Integrating Openfire with MySQL

In this tutorial, our make-believe website uses a MySQL database to store data about each user. Openfire can be integrated with an external database, a MySQL database in this case. First we must configure Openfire to do this.

Open the openfire.xml file using Notepad or preferrably a rich text editor such as Notepad++ as I mentioned before. The file will be located in the Openfire/conf/ folder within the Program Files directory folder on your PC.

<?xml version="1.0" encoding="UTF-8"?>

<!--
    This file stores bootstrap properties needed by Openfire.
    Property names must be in the format: "prop.name.is.blah=value"
    That will be stored as:
        <prop>
            <name>
                <is>
                    <blah>value</blah>
                </is>
            </name>
        </prop>

    Most properties are stored in the Openfire database. A
    property viewer and editor is included in the admin console.
-->
<!-- root element, all properties must be under this element -->
<jive>
  <adminConsole>
    <!-- Disable either port by setting the value to -1 -->
    <port>9090</port>
    <securePort>9091</securePort>
  </adminConsole>
  <locale>en</locale>
  <!-- Network settings. By default, Openfire will bind to all network interfaces.
      Alternatively, you can specify a specific network interfaces that the server
      will listen on. For example, 127.0.0.1. This setting is generally only useful
       on multi-homed servers. -->
  <!--
    <network>
        <interface></interface>
    </network>
    -->
  <connectionProvider>
    <className>org.jivesoftware.database.EmbeddedConnectionProvider</className>
  </connectionProvider>
  <database>
    <defaultProvider>
      <driver>com.mysql.jdbc.Driver</driver>
      <serverURL>jdbc:mysql://localhost:3306/mycontentsite</serverURL>
      <username>root</username>
      <password/>
      <testSQL>select 1</testSQL>
      <testBeforeUse>true</testBeforeUse>
      <testAfterUse>true</testAfterUse>
      <minConnections>5</minConnections>
      <maxConnections>25</maxConnections>
      <connectionTimeout>1.0</connectionTimeout>
    </defaultProvider>
  </database>
  <setup>true</setup>
</jive>

This is what my openfire.xml file looks like. Your openfire.xml file should look similar to mine. Here is a link to the Openfire Custom Database Integration Guide on the Ignite Realtime website. You will noticed that you are instructed to make changes directly to the openfire.xml configuration file in this guide.

Do not make any changes to this file unless it does not resemble mine.

Note: It is very likely that your openfire.xml file will be using DefaultConnectionProvider. If it is, you may have trouble logging in to the Admin Console. Try to log in with the default first. If the password you specified during setup doesn’t work, use the default password to login. The default username is admin and the default password is admin as well.

If you can’t log in, change DefaultConnectionProvider to EmbeddedConnectionProvider. Then restart Openfire and try to log in again. If you are still having trouble, run the setup to Openfire again. Change the setup tag’s value from false to true within the openfire.xml file. Then restart Openfire to run the setup again. Do this as a last resort – this shouldn’t be necessary.

I have followed the steps on the Ignite Realtime website countless times only to find myself in a hole later on. One of the problems I faced was that users couldn’t connect to the server and when I tried to fix the problem in the admin console, I couldn’t log in. In fact the only thing I can conceive of that might be more frustrating than the problems that I faced was being stuck inside of a Saw trap.

I don’t want you to go through what I had to so please follow the following steps carefully. Openfire has a brilliant way of editing and creating properties that I find to be a lot more efficient than having to edit an xml file on your system.

Log into Openfire’s Admin Console. Click the System Properties link on the right hand side of the main page.

system properties

Your server’s System Properties page should look something like this.

Important: If your System Properties page is missing some or all of the properties within the image below, you can add the properties in manually. When we modify a property in the tutorial, if you don’t have the property that we are modifying, just use the same steps that you would to modify a property to create the property instead. Otherwise, if you already have a property that we are creating, just modify the property with the values that I specify.

Towards the bottom of the screen you will see a section with the title Add new property. It has two fields. The first field Property Name. The second field is Property Value. Within the Property Name field, type in jdbcProvider.driver and within the Property Value field, type com.mysql.jdbc.Driver into the field. Click on the Save Property button when you are finished. You will be following these steps to create more properties as well as to modify existing properties.

jdbcProvider driver

Create a property called jdbcProvider.connectionString with the value jdbc:mysql://localhost/mycontentsite?user=root&password=.

jdbcProvider connection string

Now we are going to make our first modification to an existing property. Click on the Edit link that correspondes to the provider.auth.className property. Change its value to org.jivesoftware.openfire.auth.JDBCAuthProvider using the Edit property table. Click the Save Property button when you are finished.

provider auth classname

Create a new property with the name jdbcAuthProvider.passwordSQL. Give it a value of SELECT my_password FROM mymembers WHERE my_username=?. This property’s value is the MySQL query string that will be used to authenticate a user.

Note: Notice that it contains a question mark (?). The question mark will be replaced with the value inside the username field.

jdbcAuthProvider passwordSQL

Create a new property called jdbcAuthProvider.passwordType. Give it a value of md5.

jdbcAuthProvider passwordType

Note: The jdbcAuthProvider properties will be hidden if you have followed the steps correctly.

property saved

Create a new property called admin.authorizedUsernames. The value should be the jid of the usernames that you would like to be able to log into the Admin Console with.

Note: Look at the image below. Notice that the Jane and John Doe’s jid’s are their usernames concatenated with an @ sign and the server’s XMPP domain.

authorized usernames

Modify the property provider.user.className by changing its value to org.jivesoftware.openfire.user.JDBCUserProvider.

provider user className

Create a new property called jdbcUserProvider.loadUserSQL with the value SELECT first_name,email FROM mymembers WHERE my_username=?.

jdbcUserProvider loadUserSQL

Create a new property called jdbcUserProvider.userCountSQL and give it the value SELECT COUNT(*) FROM mymembers.

jdbcUserProvider userCountSQL

Create a new property called jdbcUserProvider.allUsersSQL. Set the value to SELECT my_username FROM mymembers.

jdbcUserProvider allUsersSQL

Create a new property called jdbcUserProvider.searchSQL. Give it the value SELECT my_username FROM mymembers.

jdbcUserProvider searchSQL

Create a new property called usernameField. Set its value to my_username.

username field

Create a new property called nameField. Set its value to first_name.

name field

Create a new property called emailField. Set its value to email.

email field

Now that we have added and modified the properties needed we can log out of the Admin Console. Restart Openfire and attempt to log back into the Admin Console with an actual user.

Access denied!

Now try to log in with the username, admin.

Access denied again! What’s going on here?

Let’s take a look at the openfire.xml file. Yours should look the same as it did before. We need to add the modifications to the xml file. I have found that by making modifications in the Admin Console first, then changing the openfire.xml file after, is more consistent than just making changes to the xml. As I described before, I just couldn’t log in using a client or into the Admin Console after I had made these modifications.

Change your openfire.xml file so that it looks like this.

<?xml version="1.0" encoding="UTF-8"?>

<!--
    This file stores bootstrap properties needed by Openfire.
    Property names must be in the format: "prop.name.is.blah=value"
    That will be stored as:
        <prop>
            <name>
                <is>
                    <blah>value</blah>
                </is>
            </name>
        </prop>

    Most properties are stored in the Openfire database. A
    property viewer and editor is included in the admin console.
-->
<!-- root element, all properties must be under this element -->
<jive>
  <adminConsole>
    <!-- Disable either port by setting the value to -1 -->
    <port>9090</port>
    <securePort>9091</securePort>
  </adminConsole>
  <locale>en</locale>
  <!-- Network settings. By default, Openfire will bind to all network interfaces.
      Alternatively, you can specify a specific network interfaces that the server
      will listen on. For example, 127.0.0.1. This setting is generally only useful
       on multi-homed servers. -->
  <!--
    <network>
        <interface></interface>
    </network>
    -->
  <connectionProvider>
    <className>org.jivesoftware.database.DefaultConnectionProvider</className>
  </connectionProvider>
  <database>
    <defaultProvider>
      <driver>com.mysql.jdbc.Driver</driver>
      <serverURL>jdbc:mysql://localhost:3306/mycontentsite?user=root&amp;password=</serverURL>
      <username>root</username>
      <password/>
      <testSQL>select 1</testSQL>
      <testBeforeUse>true</testBeforeUse>
      <testAfterUse>true</testAfterUse>
      <minConnections>5</minConnections>
      <maxConnections>25</maxConnections>
      <connectionTimeout>1.0</connectionTimeout>
    </defaultProvider>
  </database>
  <jdbcProvider>
    <driver>com.mysql.jdbc.Driver</driver>
    <connectionString>jdbc:mysql://localhost/mycontentsite?user=root&amp;password=</connectionString>
  </jdbcProvider>
  <provider>
    <auth>
      <className>org.jivesoftware.openfire.auth.JDBCAuthProvider</className>
    </auth>
    <user>
      <className>org.jivesoftware.openfire.user.JDBCUserProvider</className>
    </user>
  </provider>
  <jdbcAuthProvider>
    <passwordSQL>SELECT my_password FROM mymembers WHERE my_username=?</passwordSQL>
    <passwordType>md5</passwordType>
  </jdbcAuthProvider>
  <jdbcUserProvider>
    <loadUserSQL>SELECT first_name,email FROM mymembers WHERE my_username=?</loadUserSQL>
    <userCountSQL>SELECT COUNT(*) FROM mymembers</userCountSQL>
    <allUsersSQL>SELECT my_username FROM mymembers</allUsersSQL>
    <searchSQL>SELECT my_username FROM mymembers WHERE</searchSQL>
    <usernameField>my_username</usernameField>
    <nameField>first_name</nameField>
    <emailField>email</emailField>
  </jdbcUserProvider>
  <setup>true</setup>
  <admin>
	<authorizedUsernames>janedoe, johndoe123</authorizedUsernames>
  </admin>
</jive>

Make sure to change you are using the DefaultConnectionProvider instead of the EmbeddedConnectionProvider then close Openfire and restart it. Attempt to log into the Admin Console as a member from your website’s database. I logged in as John Doe. If done correctly, you should be back in the Admin Console and the username should be in the top right hand corner of the home page.

Note: Before moving on, make sure that all of the properties within the openfire.xml file are showing up on the System Properties of the Admin Console. If they aren’t you now know how to add them in manually.

logged in

Step 5: PHP

We need to use PHP to grab data from a MySQL database and present the data to Flash. For those of you who are new to PHP I will briefly explain what each script accomplishes. Let’s start with the MySQLConnection class.

The MySQLConnection class connects to and disconnects from a MySQL database.


	class MySQLConnection {

		private $db_host = "localhost"; // Your Websites domain
		private $db_user = "root"; // Your databases username
		private $db_pass = ""; // Your databases password
		private $db_name = "mycontentsite"; // The name of your database
		private $connected = 0;

		public function connect() {

			mysql_connect($this->db_host, $this->db_user, $this->db_pass) or die ( "Error: Script aborted. Could not connect to database." );
			mysql_select_db($this->db_name) or die ( "Error: Script aborted. No database selected." );
			$this->connected = 1;
			session_start();
		}

		public function close() {

			mysql_close();
			$this->connected = 0;
		}

		public function get_connected() {

			return $this->connected;
		}
	}

The LoginManager class handles user logins. A user can be authenticated then logged in and out with this class.


	require_once "MySQLConnection.php";

	class LoginManager {

		public function __construct() {

		}

		public function login( $username, $password ) {

			$username = strip_tags( $username );

			$username = stripslashes( $username );

			$username = mysql_real_escape_string( $username );

			$passHash = md5( $password ); // Applies MD5 encoded hash to the password

			$connection = new MySQLConnection();
			$connection->connect();

			$sql = "SELECT * FROM mymembers WHERE my_username = '$username' AND my_password = '$passHash' LIMIT 1";
			$query = mysql_query( $sql );

			if ($query) {

				$count = mysql_num_rows( $query );
			}
			else {

				die ( mysql_error() );
			}

			if ( $count > 0 ) {

				while ( $row = mysql_fetch_array( $query ) ) {

					$_SESSION['username'] = $username;
					$_SESSION['pw'] = $password;
					$uid = $row['uid'];
					session_name( $username . $uid );
					setcookie( session_name(), '', time() + 42000, '/' );
					$connection->close();
					die ( "login=1" );
				}

				die ( "login=0&error=Invalid username or password" );

			}
			else {

				$connection->close();
				die ( "login=0&error=Invalid username or password" );
			}
		}

		public function checkLogin() {

			if ( isset ( $_SESSION['username'] ) && isset ( $_SESSION['pw'] ) ) {

				$user = $_SESSION['username'];
				$pw = $_SESSION['pw'];
				die ( "login=1&username=$user&password=$pw" );
			}
			else {

				die ( "login=0" );
			}
		}

		public function logout() {

			setcookie(session_name(), '', time() - 42000, '/');
			if ( isset( $_SESSION['username'] ) ) unset( $_SESSION['username'] );
			if ( isset( $_SESSION['pw'] ) ) unset( $_SESSION['pw'] );

			//Destroy session
			session_destroy();

			//return result to Flash (swf)
			die ("logout=1");
		}
	}

We call upon login.php to log the user in and logout.php to log the user out using the LoginManager class. To check to see if a user is logged in we call the check_login.php script.


    // login.php

	require_once "classes/LoginManager.php";

	if (isset($_POST['username']) && $_POST['password']) {

		login();
	}

	function login() {

		$username = $_POST['username'];
		$password = $_POST['password'];
		unset($_POST['username']);
		unset($_POST['password']);

		$login = new LoginManager();
		$login->login( $username, $password) ;
	}

    // logout.php

	require_once "classes/LoginManager.php";

	$login = new LoginManager();
	$login->logout();

    // check_login.php

	require_once "classes/LoginManager.php";

	session_start();

	$login = new LoginManager();
	$login->checkLogin();
	exit();

The final script that is called from ActionScript is the grab_user_data.php script that is used to select the user’s data from our MySQL database.


	require_once "classes/MySQLConnection.php";

	if ( isset( $_POST['username'] ) ) {

		$connection = new MySQLConnection();
		$connection->connect();
		$username = $_POST['username'];
		$sql = "SELECT * FROM mymembers WHERE my_username = '$username' LIMIT 1";
		$query = mysql_query( $sql );

		while ( $row = mysql_fetch_array( $query ) ) {

			$uid = $row['uid'];
			$xml = '<user id="' . $uid . '">' . "\n";
			$xml .= "	<firstName>" . $row['first_name'] . "</firstName>\n";
			$xml .= "	<lastName>" . $row['last_name'] . "</lastName>\n";
			$xml .= "	<country>" . $row['country'] . "</country>\n";
			$xml .= "	<statusMessage>" . $row['status_message'] . "</statusMessage>\n";
			$xml .= "</user>\n";
		}

		echo $xml;
		$connection->close();
		exit();
	}

These PHP scripts serve a very important role in our application but are very basic.


Step 6: Setting Up Flash

Open up Flash Professional. Set the document class to ChatApp. Set the size of the stage to 550 x 400.

setup_flash

Note: I like to use a framerate of 30 FPS but our application doesn’t have any animation so you can use whatever framerate works best for you.


Step 7: Build the Client’s User Interface

From the Components panel, select and drag a Button component on to the stage. Position the button so that it sits in the top right corner of the stage. Set the instance name of the button to logoutBtn. Add another button in the bottom right of the stage and set its instance name to sendBtn.

button component
buttons on stage

Add a List compenent to the stage. Position it directly beneath logoutBtn and resize the component so that it fits nicely between both buttons. Set its instance name to list.

list on stage

We’ll be using logoutBtn to logout of the user’s session and sendBtn to allow our users to send messages. The List will display all of the online users within the current chat room. When an item in the list is clicked, the user’s profile will be loaded.

Now we need a component that will display incoming and outgoing chat messages as well as a textfield that our users can use to input new messages. Add a TextArea component to the stage. Resize and position it to take up most of the remainder of the stage, leaving room for an input textfield at the bottom that is the same height as sendBtn. Set the instance name to displayTxt.

text area component
text area on stage

Finally we need to add a TextInput component to the stage. Position the component directly beneath displayTxt and to the left of sendBtn. Set the instance name to inputTxt.

text input component

Select all of the components on the stage. Convert the selection to a symbol. The symbol should be a MovieClip named UserInterface. Select the Export for ActionScript option. The Class field should read UserInterface. Set the instance name to our new symbol to ui. Finally name the current layer of the main timeline interface. This will help you organize your project better.

all components on stage
user interface symbol
ui instance
interface layer

Step 8: The Login Screen

Our chat application would be useless if user’s couldn’t log in to our application. Let’s build the login screen. Create a new layer on the Main Timeline. Name the layer login.

login layer

Using the Rectangle tool, draw a rectangle on the stage that is the same size as the stage. The rectangle’s stoke should be set to 0. The rectangle should not have a line and should be filled black with a tranparency of 50%.

darkbox on stage
color picker 1
color picker 2

Highlight the black rectangle and convert it to a new MovieClip symbol called DarkBox. We will use this object to darken the screen while the log in components are being displayed. Set the instance name of the DarkBox object to darkBox.

darkbox symbol
darkbox instance

Add two InputText components, two Label components, and a Button component to the stage. Make sure you aren’t adding these objects on top of the interface layer. Position the components as they are in the image below with the username field first then the password field.

Set the instance name of the first lable to userLabel and set the instance name of the second label to passLabel. Set the instance name for the first input text field to usernameTxt and set the instance name of the second input text field to passwordTxt. Set the instance name of the button to loginBtn.

Use the Text tool to add a Dynamic TextField to the stage. Set the text size to 18 and make the text color Red. Set the instance name to errorTxt. Position errorTxt beneath loginBtn as seen below.

login screen elements

We are going to convert everything on the login layer to a single new MovieClip symbol named LoginScreen but before we can do this we need to lock everything on the interface layer so we do not accidently select an object of that layer. Lock the interface layer by selecting the Lock Layer button next to the layer. When the layer is locked you will see a symbol of a lock next to the layer.

You may now safely select all of the objects on the login layer that we just created, and convert the selection to a symbol with a linkage of LoginScreen. Set the instance name to loginScreen.

login screen component
stage with elements

Step 9: Creating the Profile Window

Lock and hide all of the current layers on the Main Timeline. Create a new layer and call it profile info.

Note: This is just a layer that we will use for development. You may delete this layer at the end of this step if you wish.

profile info layer

Using the Rectangle tool, draw a rectangle with the radius of each corner set to 10.00. The rectangle should have a fill identical to the DarkBox object (Black with a transpareny of 50%) and a fully opaque White line with a stroke value of 4.00. I set the color of the stage to a Burnt Orange in the image below so that you can see how everything should look more clearly.

rounded rectangle

Add a Dynamic Text to the stage directly on top of the rectangle. Position and resize the text field so that it takes up most of the rectangle’s area but leaves room for a button. Set the instance name of the text field to txt. Make sure that the text color is White and the size of the text is at least 18px.

text field
unfinished profile window
an added button

Add a new Button component on top of the rectangle and set the instance name to closeBtn.

highlighted profile window

Select all of the object’s on the profile info layer and convert them to a MovieClip symbol named ProfileWindow. Check the field Export for ActionScript so that this symbol has a linkage of ProfileWindow. Now remove the ProfileWindow object from the stage. We will be instantiating this object with code.

profile window symbol

Step 10: Our First ActionScript Code

Create a new ActionScript file and name it ChatApp.as. Add the following lines of code to the class.

package {

	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.ErrorEvent;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.system.Security;
	import flash.external.ExternalInterface;
	import org.igniterealtime.xiff.core.XMPPConnection;
	import org.igniterealtime.xiff.events.ConnectionSuccessEvent;
	import org.igniterealtime.xiff.events.LoginEvent;
	import org.igniterealtime.xiff.events.DisconnectionEvent;
	import org.igniterealtime.xiff.events.XIFFErrorEvent;
	import org.igniterealtime.xiff.events.RoomEvent;
	import org.igniterealtime.xiff.events.IncomingDataEvent;
	import org.igniterealtime.xiff.events.OutgoingDataEvent;

	public class ChatApp extends Sprite {

		private static const SERVER:String = "tutorial-f5d57edaa";//= "[host name] // Your server's host name here
		private static const PORT:Number = 5222; // Your servers public port here
		private static const RESOURCE:String = "MyContentSite";//[resource name] // Resource name	ex.: ==> MyJabberApp
		private static const DEFAULT_ROOM:String = "Main Lobby";

		private var grabber:LoginCredentialsGrabber;
		private var userData:UserDataGrabber;
		private var connection:XMPPConnection;
		private var requireLogin:Boolean;
		private var roomName:String;

		public function ChatApp() {

			super();
			if (stage) init()
			else addEventListener(Event.ADDED_TO_STAGE, onAdded);
		}

        private function init():void {

			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			loginScreen.visible = false;
			loginScreen.userLabel.text = "username";
			loginScreen.passLabel.text = "password";
			ui.visible = false;

			UserDataExtension.enable();
			grabber = new LoginCredentialsGrabber();
			userData = new UserDataGrabber();

			var flashVars:Object = this.loaderInfo.parameters;

			if ( flashVars.hasOwnProperty( "room" ) ) {

				roomName = flashVars.room;
			}

			checkLogin();
		 }

        private function onAdded( e:Event ):void {

			removeEventListener(Event.ADDED_TO_STAGE, onAdded);
			init();
		}
	}
}

Within the code above we check to see if the stage exists in the class constructor. If the stage does not exist, we listen for the ADDED_TO_STAGE event and the onAdded event handler method is called when the stage is available. The onAdded method simply stops listening for the ADDED_TO_STAGE event and calls the init method. If the stage exists we skip this first step and just call the init method which initialized our application.

We initialize the stage and the loginScreen, and we make the UserInterface object (ui) invisible. You may notice the enable method from the UserDataExtension class being called. We will write this class later but for now just know that it is very important to remember to always call this method when instantiating the application. The enable method registers our custom extension(the class) with the ExtensionClassRegistry class in the XIFF library. We’ll talk more about this later.

Instantiate a new instance of the LoginCredentialsGrabber class and assign it to the grabber variable. Also instantiate a new instance of the UserDataGrabber class and assign it to the userData variable. We will write these classes later also. When our SWF file is embeded in a web page we want our application to connect to a specific chat room that is related to the content on the page. Later we are going to pass the name of the chat room, that our app should connect to, into the flashVars parameter at embed time. But for now we’ll just first check to see if the variable exists and then we grab the value and assign it to the roomName variable. Finally we run the checkLogin method which is self-explanatory.


Step 11: Checking Whether the User Is Logged In

Write the checkLogin method in the Document Class( ChatApp ).

private function checkLogin():void {

	grabber.addEventListener( Event.COMPLETE, onLoginCredentials );
	grabber.grab();
}

As you can see the method is very simple. This is because all of the functionality is encapsulated within the LoginCredentialsGrabber class. Listen for the COMPLETE event to be dispatched so that the onLoginCredentials event handler method can be called. Call the grab method on LoginCredentialsGrabber object. This methods checks to see whether the user is logged in – or, more specifically, it checks to see whether the user’s session exists.

Next, we’ll write the onLoginCredentials method.

(Note: We are still in the Document class.)

private function onLoginCredentials( e:Event ):void {

	grabber.removeEventListener( Event.COMPLETE, onLoginCredentials );

	if ( grabber.isLoggedIn ) {

		// Connect to Openfire
		ui.visible = true;
		connect( grabber.username, grabber.password );
	}
	else {

		// Display login
		displayLogin();
	}
}

This method is also really simple. The LoginCredentialsGrabber checks to see if the user is logged in by grabbing the session cookies using PHP. PHP renders out data to the LoginCredentialsGrabber object and the data is parsed.

Note: We will be writing the LoginCredentialsGrabber class next. All we need to do now is check to see if the user is logged in. If they are we display the user interface and call the connect method to connect to Openfire. We pass the user’s username and password into the connect method as required parameters. If the user is not logged in we display the Login Screen.


Step 12: Grabbing Login Credentials

Write the LoginCredentialsGrabber class.

package {

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.net.URLRequestMethod;

	public class LoginCredentialsGrabber extends EventDispatcher {

		private static const PASSCODE:String = "letmein123";
		private static const SOURCE:String = "http://localhost/mycontentsite/scripts/check_login.php";

		private var _data:*;
		private var _username:String;
		private var _password:String;
		private var _isLoggedIn:Boolean;

		public function LoginCredentialsGrabber() {

			super();
		}

		public function grab():void {

			var loader:URLLoader = new URLLoader();
			var req:URLRequest = new URLRequest( SOURCE + "?cb=" + new Date().time );

			loader.addEventListener( Event.COMPLETE, onComplete );
			loader.load( req );
		}

		private function onComplete( e:Event ):void {

			e.target.removeEventListener( Event.COMPLETE, onComplete );
			_data = e.target.data;
			var results:URLVariables = new URLVariables( _data.toString() );

			if ( results.login == "1" ) {

				_isLoggedIn = true;
				_username = results.username;
				_password = results.password;
			}
			else {

				_isLoggedIn = false;
			}

			dispatchEvent( new Event( Event.COMPLETE ) );
		}

		public function get data():* {

			return _data;
		}

		public function get isLoggedIn():Boolean {

			return _isLoggedIn;
		}

		public function get username():String {

			return _username;
		}

		public function get password():String {

			return _password;
		}
	}
}

We have two constants and four read-only properties before the constructor method. The SOURCE constant is a String that represents the location of the PHP script that checks to see if the user is logged in. We also store the passcode needed to execute the PHP script in a constant. When the grab method is called the URLLoader object loads the PHP script passing the passcode with the URLRequest and the script returns data back to flash. The data is a set of url variables that we can parse using the URLVariables class. If a user is logged in, the PHP script will give us the user’s username and password so that we can use this information to connect the user to Openfire. Finally we provide getter methods to grant read-only access to outside code.


Step 13: Display the Login Screen

Write the displayLogin method in the Document class(ChatApp.as).

private function displayLogin():void {

	// Displays the login screen
	loginScreen.visible = true;
	loginScreen.addEventListener( LoginManager.LOGIN, onLoggingIn );
}

We set the loginScreen‘s visible property to true and wait for the loginScreen to dispatch LOGIN event. Then the onLoggingIn method is called. Let’s write this method now.

private function onLoggingIn( e:Event ):void {

	ui.visible = true;
	connect( loginScreen.manager.username, loginScreen.manager.password );
}

We make the user interface visible and then we call the connect method.

Important: Notice that we are using the username and password from the loginScreen‘s manager object(loginScreen.manager) instead of the grabber object’s username and password as we did in the onLoginCredentials method.


Step 14: Connecting to Openfire

At last, we can write the connect method. The method accepts two required parameters: the first is the user’s username and the second is the users’s password.

private function connect( username:String, password:String ):void {

	connection = new XMPPConnection();
	connection.username = username;
	connection.password = password;
	connection.server = SERVER;
	connection.port = PORT;
	connection.resource = RESOURCE;
	connection.addEventListener( ConnectionSuccessEvent.CONNECT_SUCCESS, onConnected );
	connection.addEventListener( LoginEvent.LOGIN, onLogin );
	connection.addEventListener( DisconnectionEvent.DISCONNECT, onDisconnected );
	connection.addEventListener( XIFFErrorEvent.XIFF_ERROR, onXiffError );
	connection.addEventListener( IncomingDataEvent.INCOMING_DATA, onIncomingData );
	connection.addEventListener( OutgoingDataEvent.OUTGOING_DATA, onOutgoingData );
	connection.connect( XMPPConnection.STREAM_TYPE_FLASH );
}

Instantiate the XMPPConnection object and assign it to the connection variable. Set the user’s username and password along with the server(SERVER) and the resource(RESOURCE). Then add event listeners to the connection object. Finally we call the connect method on the XMPPConnection object.

Write the following methods:

private function onConnected( e:ConnectionSuccessEvent ):void {

	trace( "connected" );
}

private function onLogin( e:LoginEvent ):void {

	trace( "logged in" );
	ui.connection = connection;
	grabUserData();
	startTimer();
}

private function onDisconnected( e:DisconnectionEvent ):void {

	trace( "disconnected" );
	loginScreen.visible = true;
	ui.visible = false;
	loginScreen.displayError( "disconnected" );
}

private function onXiffError( e:XIFFErrorEvent ):void {

	trace("Error: " + e.errorMessage);
	if ( loginScreen.visible ) loginScreen.displayError( e.errorMessage );
}

private function onIncomingData( e:IncomingDataEvent ):void {

	trace( e.data.toString() );
}

private function onOutgoingData( e:OutgoingDataEvent ):void {

	trace( e.data.toString() );
}

The onConnected, onIncomingData, and onOutgoingData methods can have many different uses but for this tutorial we will only use them to trace output so that we can debug our application when and if we need to(specifically if their is a problem connecting to the server). The onDisconnected method makes the loginScreen visible and displays an error to the user notifying them that their connection was lost. The onLogin method prepares the User Interface for XMPP chat by assigning the XMPPConnection object to the connection property within the ui object. This allows the UserInterface object to call methods directly from the XMPPConnection object through a reference. Now that the user is logged into the Jabber server(Openfire), we can start working toward logging the user into a chat room but first we need to identify exactly who our user is. We call the grabUserData method to do so. Finally we call the startTimer method.


Step 15: Grabbing User Data

By now the user’s session cookie is stored in the user’s browser and the user is logged into the Jabber server. Now we need to grab basic information about our user. We know the user’s username, so we can use it to access additional information about the user that is stored in our MySQL database. Create the grabUserData method in the Document class.

private function grabUserData():void {

	userData.addEventListener( Event.COMPLETE, joinRoom );
	userData.grab( connection.username );
}

All of the magic happens within the UserDataGrabber class. All we have to do is call the grab method and listen for the COMPLETE event. Note that the grab method on the UserDataGrabber object accepts one parameter: the user’s username. Use the user’s username from the connection instance.

This class wouldn’t have any magic right now because it doesn’t exist yet. Let’s write this class now. Create a new class called UserDataGrabber that extends flash.events.EventDispatcher.

package {

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLVariables;

	public class UserDataGrabber extends EventDispatcher {

		private static const SOURCE:String = "http://localhost/mycontentsite/scripts/grab_user_data.php"; // Replace with your own php script

		private var _data:*;
		private var _uid:String;
		private var _firstName:String;
		private var _lastName:String;
		private var _username:String;
		private var _country:String;
		private var _statusMessage:String;

		public function UserDataGrabber() {

			super();
		}

		public function grab( username:String ):void {

			var loader:URLLoader = new URLLoader();
			var req:URLRequest = new URLRequest( SOURCE + "?cb=" + new Date().time );
			var vars:URLVariables = new URLVariables();

			_username = username;
			vars.username = username;
			req.data = vars;
			req.method = "POST";
			loader.addEventListener( Event.COMPLETE, onComplete );
			loader.load( req );
		}

		private function onComplete( e:Event ):void {

			e.target.removeEventListener( Event.COMPLETE, onComplete );
			_data = e.target.data;
			trace( "User Data:\n" + data );
			var user:XML = new XML( _data );
			_uid = user.@id.toString();
			_firstName = user.firstName.toString();
			_lastName = user.lastName.toString();
			_country = user.country.toString();
			_statusMessage = user.statusMessage.toString();
			dispatchEvent( new Event( Event.COMPLETE ) );
		}

		public function get data():* {

			return _data;
		}

		public function get uid():String {

			return _uid;
		}

		public function get firstName():String {

			return _firstName;
		}

		public function get lastName():String {

			return _lastName;
		}

		public function get username():String {

			return _username;
		}

		public function get country():String {

			return _country;
		}

		public function get statusMessage():String {

			return _statusMessage;
		}

	}
}

First we create a const that stores the location to the PHP script as a string. We created the grab_user_data.php script earlier. This is the script that performs a query in the database using a specified username to fetch and echo out the user’s data as xml.

Next we create our variables. I always place an underscore (_) in front of the name of any private or protected variable (property) that will be read-only – o,r in some rare cases, write-only. All of the variables in this class read-only. We use getter methods to permit read-only access to each variable.

All variables are set when the xml data, which is rendered out from the php file, is parsed with the exception of the _username variable which is set from the username parameter of the grab method.

Now for the grab method. Nothing complicating here. Create a new URLLoader object, a new URLRequest object and a new URLVariables object. The URLRequest constructor accepts one parameter, the url that you would like to load data from. In this case, the url is stored in the SOURCE constant. I’m sure by now you’ve noticed that the the string ?cb= concatenated with the current time has been concatenated with the SOURCE. The cb stands for cache buster. This prevents our script from being loaded from out of a cache (memory).

Initialize URLRequest object and the URLVariables object. The URLVariables object holds that the username variable that the php script needs to perform a query in the database. This variable is passed along with the URLRequest. Call the URLLoader‘s load method and listen for the COMPLETE event to be dispatched from loader so that the onComplete event handler method can be called.

In the onComplete method, create a new XML object. Pass the data assigned from the URLLoader object(e.target in this case) into the constructor’s parameter. Set the class’s variables and dispatch the COMPLETE event.


Step 16: The LoginScreen Class

Up to this point, we have assumed that the user is already logged in. If the user isn’t logged in we display the loginScreen. The LoginScreen class will have methods encapsulated within it that handle the user’s login status. Create the LoginScreen class. The class must extend the MovieClip class since it is linked to a library symbol of that type.

package {

	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.events.ErrorEvent;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;

	public class LoginScreen extends MovieClip {

		public var manager:LoginManager;

		public function LoginScreen() {

			super();
			manager = new LoginManager();
			init();
		}

		public function init():void {

            userLabel.text = "username:";
			passLabel.text = "password:";

			errorTxt.selectable = false;
			passwordTxt.displayAsPassword = true;

			loginBtn.label = "Login";
			loginBtn.addEventListener( MouseEvent.CLICK, login );
			stage.addEventListener( KeyboardEvent.KEY_DOWN, login );
			stage.addEventListener( Event.RESIZE, onStageResize );
		}

		private function login( e:Event ):void {

			loginBtn.removeEventListener( MouseEvent.CLICK, login );
			stage.removeEventListener( KeyboardEvent.KEY_DOWN, login );

			if ( e is KeyboardEvent ) {

				var ke:KeyboardEvent = e as KeyboardEvent;

				if ( ke.keyCode != Keyboard.ENTER ) {

					loginBtn.addEventListener( MouseEvent.CLICK, login );
					stage.addEventListener( KeyboardEvent.KEY_DOWN, login );
					return;
				}
			}

			if ( usernameTxt.length > 0 && passwordTxt.length > 0 ) {

				if (!manager) manager = new LoginManager();
				manager.addEventListener( Event.COMPLETE , onLogin );
				manager.addEventListener( ErrorEvent.ERROR , onLoginError );
				manager.login( usernameTxt.text, passwordTxt.text );
			}
			else if ( usernameTxt.length == 0 ) {

				// Display error
				errorTxt.text = "Please enter your username";
			}
			else {

				// Display error
				errorTxt.text = "Please enter your password";
			}

			loginBtn.addEventListener( MouseEvent.CLICK, login );
			stage.addEventListener( KeyboardEvent.KEY_DOWN, login );
		}

		private function onLogin( e:Event ):void {

			manager.removeEventListener( Event.COMPLETE , onLogin );
			manager.removeEventListener( ErrorEvent.ERROR , onLoginError );
			stage.removeEventListener( KeyboardEvent.KEY_DOWN, login );
			visible = false;
			dispatchEvent( new Event( LoginManager.LOGIN ) );
		}

		private function onLoginError( e:ErrorEvent ):void {

			manager.removeEventListener( Event.COMPLETE , onLogin );
			manager.removeEventListener( ErrorEvent.ERROR , onLoginError );
			errorTxt.text = e.text;
			loginBtn.addEventListener( MouseEvent.CLICK, login );
			stage.addEventListener( KeyboardEvent.KEY_DOWN, login );
		}

		private function onStageResize( e:Event ):void {

			darkBox.width = stage.stageWidth;
			darkBox.height = stage.stageHeight;
		}

		public function displayError( error:String ):void {

			errorTxt.text = error;
		}
	}
}

We start the class of by creating a new LoginManager object. We’ll write this class in the next step but for now just know that this class manages logging the user in.

The functionally of this class is quite simple. We initialize the display and when a user pressed the Enter key or clicks loginBtn we check to see if they have typed anything into the text fields. If so we use manager to log them in. Otherwise we display an error. We display errors to the user by calling the displayError method and passing in a message as a String in the method’s parameter. If the LoginManager object dispatches an ErrorEvent, the error’s message will be displayed to the user. On the contrary if the LoginManager object dispatches the COMPLETE event, we dispatch the LOGIN event and we make our LoginScreen invisible.


Step 17: The LoginManager Class

The LoginManager class logs the user in and out. Create the LoginManager class.

package {

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ErrorEvent;
	import flash.net.*;
	import org.igniterealtime.xiff.bookmark.UrlBookmark;

	public class LoginManager extends EventDispatcher {

		private static const LOGIN_SOURCE:String = "http://localhost/mycontentsite/scripts/login.php"; // Your website's login script location
		private static const LOGOUT_SOURCE:String = "http://localhost/mycontentsite/scripts/logout.php";
		private static const INDEX:String = "http://localhost/mycontentsite/index.php";

		private var _data:*;
		private var _isLoggedIn:Boolean;
		private var _username:String;
		private var _password:String;

		public static const LOGIN:String = "login";

		public function LoginManager() {

			super();
		}

		public function login( username:String, password:String ):void {

			var loader:URLLoader = new URLLoader();
			var req:URLRequest = new URLRequest( LOGIN_SOURCE + "?cb=" + new Date().time );
			var vars:URLVariables = new URLVariables();

			_username = username;
			_password = password;
			vars.username = username;
			vars.password = password;
			req.data = vars;
			req.method = URLRequestMethod.POST;
			loader.addEventListener( Event.COMPLETE, onLoginComplete );
			loader.load( req );
		}

		public function logout():void {

			var loader:URLLoader = new URLLoader();
			var req:URLRequest = new URLRequest( LOGOUT_SOURCE );
			loader.addEventListener( Event.COMPLETE, onLogoutComplete );
			loader.load( req );
		}

		private function onLoginComplete( e:Event ):void {

			e.target.removeEventListener( Event.COMPLETE, onLoginComplete );
			_data = e.target.data;
			var results:URLVariables = new URLVariables( _data.toString() );

			if ( results.login == "1" ) {

				_isLoggedIn = true;
			}
			else {

				_isLoggedIn = false;
				dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false, false, results.error ) );
				return;
			}

			dispatchEvent( new Event( Event.COMPLETE ) );
		}

		private function onLogoutComplete( e:Event ):void {

			e.target.removeEventListener( Event.COMPLETE, onLogoutComplete );
			var req:URLRequest = new URLRequest();
			navigateToURL( new URLRequest( INDEX ), "_self" );
		}

		public function get data():* {

			return _data;
		}

		public function get isLoggedIn():Boolean {

			return _isLoggedIn;
		}

		public function get username():String {

			return _username;
		}

		public function get password():String {

			return _password;
		}

	}
}

Create the class constants and instance variables but do not assign the variables any values yet. The login method should have two parameters: the first is a String that represents the user’s username and the second is another String that represents the user’s password. Create and initialize a new URLLoader object, a new URLRequest object and a new URLVariables object.

Initialize URLRequest object and the URLVariables object. The URLVariables object holds the username POST variable and the password POST variable that the php script requires to execute. These variables are passed along with the URLRequest. Call the URLLoader‘s load method and listen for the COMPLETE event to be dispatched from loader so that onLoginComplete event handler method can be called.

The login.php script renders out a url variable that specifies a Boolean (0 or 1). A 1 represents true, of course. If that value is a 1, than the user has been successfully logged in. If that value is a 0, an error has occurred and we dispatch an ERROR event containing the error message echoed out by our PHP script. In all such cases the error will be invalid login data.

The logout method works in the same way except the user’s browser will navigate to the main index page of our website when logout.php successfully logs the user out.


Step 18: Keep Alive

An XMPP server can be configured to terminate any idel connections that may still be open. By default, Openfire will do this. In order to prevent this from occuring, a chat client must send the ping to the server. Within the XIFF library, this is done by calling the sendKeepAlive method on a XMPPConnection object. This method should be called at least once a minute. You may remember that we called a upon a method within the Document class(ChatApp) called startTimer. This method was undefined at the time. Let’s create it now.

private function startTimer():void {

	var aliveTimer:Timer = new Timer( 1000 * 60, 0 );
	aliveTimer.addEventListener( TimerEvent.TIMER, keepAlive );
	aliveTimer.start();
	trace( "starting alive timer" );
}

The startTimer method creates a new Timer object, listens for the TIMER event, then calls the start method on the new Timer object.

Now let’s create the keepAlive method which is called when the aliveTimer‘s TIMER event is dispatched.

private function keepAlive( e:TimerEvent ):void {

	connection.sendKeepAlive();
}

Just call the XMPPConnection object’s keepAlive method. Nothing more.


Step 19: The UserInterface Class

Create the joinRoom method within the Document class.

private function joinRoom( e:Event ):void {

	userData.removeEventListener( Event.COMPLETE, joinRoom );
	if ( !roomName ) roomName = DEFAULT_ROOM;
	ui.joinRoom( connection, userData, roomName );
}

If you can’t remember, the joinRoom method is called when the user’s data has been loaded(when we know who the user is). The UserInterface class will also have a method called joinRoom that we will call from the Document class’s joinRoom method. Before we call the method on the ui object. We check to make sure that the roomName variable has been set. If it hasn’t for whatever reason, we set it to our default room, which in this case is the main lobby.

Create a new class and name it, UserInterface. Make sure it extends the MovieClip class. Add the following to the class path.

import flash.display.MovieClip;
import flash.events.Event;
import fl.data.DataProvider;
import org.igniterealtime.xiff.core.XMPPConnection;
import org.igniterealtime.xiff.core.EscapedJID;
import fl.controls.List;
import org.igniterealtime.xiff.events.RoomEvent;
import org.igniterealtime.xiff.conference.Room;
import org.igniterealtime.xiff.core.UnescapedJID;
import flash.events.MouseEvent;
import org.igniterealtime.xiff.data.Message;
import org.igniterealtime.xiff.data.Presence;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.display.MovieClip;

Define the following variables.

private var dp:DataProvider;
private var profileData:Vector.<String>;
private var names:Vector.<String>;
private var usernames:Vector.<String>;
private var items:Vector.<Object>;
private var darkBox:DarkBox;
private var room:Room;

public var connection:XMPPConnection;

public static const SERVER_NAME:String = "[ your server's name ]";

We need a DataProvider to display any users that are in the current chat room within the List object. Will be storing different sets of data in Vector objects. We created a DarkBox object earlier that we will display when a profile window is open. The last private variable we need is an org.igniterealtime.xiff.conference.Room object which in fact is, if you haven’t already guessed it, our chat room.

The XMPPConnection object(connection) was set within the Document class’s onLogin event handler method. It is just a reference to the XMPPConnection object within the Document class.

Create the class constructor and the init method.

public function UserInterface() {

	super();
	dp = new DataProvider();
	profileData = new Vector.<String>();
	names = new Vector.<String>();
	usernames = new Vector.<String>();
	items = new Vector.<Object>();
	darkBox = new DarkBox();
	init();
}

private function init():void {

	UserDataExtensionManager.onUserData = onUserDataExtension;
	disable();
	list.dataProvider = dp;
	list.addEventListener( Event.CHANGE, onGuestSelected );
	displayTxt.editable = false;
	sendBtn.label = "Send";
	logoutBtn.label = "Logout";
	darkBox.visible = false;
	addChild( darkBox );
	positionContents();
	stage.addEventListener( Event.RESIZE, onStageResize );
	logoutBtn.addEventListener( MouseEvent.CLICK, logout );
}

Everything’s pretty simple in the constructor. Just instantiate new instances of each object and assign them to the appropriate variable. The init method accomplishes several tasks. The first is that we have to tell the UserDataExtensionManager class that we would like to recieve and handle the User Data Extension within the current instance of the UserInterface class. There are different ways to handle incoming custom extensions but I like letting a class that is associated with the custom extension, handle the custom extension. You can also listen for incoming data so that you can parse the data yourself or you can check to see if a particular extension has been attached to an incoming XMPPStanza(e.g. Message Stanza), and take the extension directly out of its parent XMPPStanza object. Here’s an example.

Important: The two methods that follow are just hypothetical examples. Implement them into your own scripts as you see fit.

// example
private function onMessage( e:MessageEvent ):void {

    // Some block of code to display the message

	var msg:Message = e.data as Message;
    var extensions:Array = msg.getAllExtensions();

	if ( extensions ) {

    	for each( var ext:Extension in extensions ) {

        	switch ( ext.get_NS() ) {

            	case "mysite:extensions:mycustomextension" :
                break;
                default :
                	// Handle invalid extension
            }
        }
    }
}
// another example
private function onMessage( e:MessageEvent ):void {

    // Some block of code to display the message

	var msg:Message = e.data as Message;
    var ext:Extension = msg.getExtension( "myCustomExtension" );

    if ( ext ) {

        // Handle extension code
    }
}

But again for this tutorial we’ll be using my express delivery method since there will only be one connection going on at a time. Otherwise we’d be implementing one of the above methods. We will be writing the UserDataExtension and UserDataExtensionManager classes within the next step.

Initially within we want the ui to be disabled so we call the disable method, which disables all components within the interface, from the init method. Last we initialize the display and the DataProvider object.

Write the following methods in the UserInterface class.

private function positionContents():void {

	displayTxt.width = stage.stageWidth - list.width - displayTxt.x - 10 - 10 - 10;
	list.x = displayTxt.y + displayTxt.width + 10;
	inputTxt.width = displayTxt.width;
	sendBtn.x = list.x;
	logoutBtn.x = list.x;

	displayTxt.height = stage.stageHeight - inputTxt.height - 10 - 10 - 10;
	list.height = displayTxt.height - logoutBtn.height - 10;
	inputTxt.y = displayTxt.height + displayTxt.y + 10;
	sendBtn.y = inputTxt.y;
	logoutBtn.y = displayTxt.y;

	darkBox.width = stage.stageWidth;
	darkBox.height = stage.stageHeight;
}

private function onStageResize( e:Event ):void {

	positionContents();
}

public function joinRoom( connection:XMPPConnection, userData:UserDataGrabber, roomName:String ):void {

	if ( connection.isLoggedIn() ) {

		trace( "joining room..." );
		var id:String = roomName.toLowerCase().replace( " ", "" );
		var ext:UserDataExtension = new UserDataExtension( null, userData );
		room = new Room( connection );
		room.roomJID = new UnescapedJID( id + "@conference." + SERVER_NAME );
		room.nickname = userData.username;
		room.addEventListener( RoomEvent.ROOM_JOIN, onRoomJoin );
		room.addEventListener( RoomEvent.USER_JOIN, onUserJoined );
		room.addEventListener( RoomEvent.GROUP_MESSAGE, onGroupMessage );
		room.addEventListener( RoomEvent.USER_DEPARTURE, onUserLeave );
		room.join( false, [ ext ] );
	}
	else {

		trace ( "Must be logged in to enter a chat room." );
	}
}

private function enable():void {

	list.enabled = true;
	sendBtn.enabled = true;
	inputTxt.enabled = true;
	displayTxt.enabled = true;
	list.dataProvider = dp;
}

private function disable():void {

	list.enabled = false;
	sendBtn.enabled = false;
	inputTxt.enabled = false;
	displayTxt.enabled = false;
}

All of the methods above are pretty self-explanatory. We talked about the joinRoom method earlier. Now that we have this method written we can take a closer look. After we check to confirm that the connection is still logged in, we join the room. First we modify the roomName so that we can make a valid jid so that we can connect to the proper room. Then we create a new UserDataExtension object. Again we haven’t created it yet but the constructor will accept a UserDataGrabber object as a required parameter. More about this later.

Instantiate the Room object passing the connection in as the parameter that is required by the constructor. The room’s UnescapedJID must be set and the nickname of the current user. You can use any name like the user’s first and last name but I chose to just use the username. After we listen for various events, we call the join method on the Room object.

The method accepts two parameters. The first is a Boolean representing whether or not you would like to create and configure a reserved room. Set this value to false for because have already created and configured our chat rooms in Openfire. The second parameter is an Array containing any custom extensions that you would like to pass along with the user’s Presence when entering the chat room. Pass an array containing the instance of the UserDataExtension object, that we just, created into this parameter.

Let’s finish up the class.

private function addMessage( msg:String, from:String ):void {

	var now:Date = new Date();
	var nHours:Number = now.hours;
	var sMin:String = now.minutes.toString();
    if ( sMin.length == 1 ) sMin = "0" + sMin;
	var ampm:String = "AM";

	if ( nHours > 12 ) {

		nHours -= 12;
		ampm = "PM";
	}
	var time:String = String( nHours ) + ":" + sMin + " " + ampm;
	var txt:String = "[ " + from + " ] " + time + " ==> " + msg + "\n";
	displayTxt.appendText( txt );
}

private function sendMessage( e:Event ):void {

	if ( !visible ) return;
	if ( inputTxt.length > 0 ) {

		if ( e is KeyboardEvent ) {

			var ke:KeyboardEvent = e as KeyboardEvent;

			if ( ke.keyCode != Keyboard.ENTER ) {

				return;
			}
		}

		addMessage( inputTxt.text , room.nickname );
		room.sendMessage( inputTxt.text );
		inputTxt.text = "";
	}
}

private function onRoomJoin( e:RoomEvent ):void {

	trace( "joined room" );
	enable();
	sendBtn.addEventListener( MouseEvent.CLICK, sendMessage );
	stage.addEventListener( KeyboardEvent.KEY_DOWN, sendMessage );
}

private function onUserJoined( e:RoomEvent ):void {

	var p:Presence = e.data as Presence;
	trace( "user joined" );
	trace( "Presence: " + p.getNode().toString() );
	trace( e.nickname );
}

private function onGroupMessage( e:RoomEvent ):void {

	var msg:Message = e.data as Message;

	for each( var user:String in usernames ) {

		if ( e.nickname == user ) {

			addMessage( msg.body, e.nickname );
			return;
		}
	}
}

private function addToList( item:Object ):void {

	dp.addItem( item );
	trace( "adding " + name + " to list" );
}

private function removeFromList( username:String ):void {

	var index:int = usernames.indexOf( username );

	if ( index > -1 ) {

		trace( "removing " + names[ index ] + " from the list" );
		dp.removeItem( items[ index ] );
		profileData[ index ] = null;
		names[ index ] = null;
		usernames[ index ] = null;
		items[ index ] = null;
	}
}

private function onGuestSelected( e:Event ):void {

	var user:String = list.selectedItem.value.toString();
	var index:int = usernames.indexOf( user );
	trace( "Selected: " + user );

	if ( index > -1 ) {

		// Display Member Information
		darkBox.visible = true;
		var data:String = profileData[ index ];
		var window:ProfileWindow = new ProfileWindow();
		window.text = data;
		window.addEventListener( ProfileWindow.DESTROYED, onDestroyed );
		addChild( window );
	}

	function onDestroyed( e:Event ):void {

		window.removeEventListener( ProfileWindow.DESTROYED, onDestroyed );
		window = null;
		darkBox.visible = false;
	}
}

private function onUserLeave( e:RoomEvent ):void {

	var p:Presence = e.data as Presence;
	var username:String = p.from.toString().replace( room.roomName + "@" + room.conferenceServer + "/", "" );
	removeFromList( username );
}

private function onUserDataExtension( ext:UserDataExtension ) {

	if ( usernames.indexOf( ext.username ) > -1 || ext.username == connection.username ) return;

	var name:String = ext.firstName + " " + ext.lastName;
	var profileText:String = name + "\n\n";
	profileText += "Username: " + ext.username + "\n";
	profileText += "Country: " + ext.country + "\n";
	profileText += "Status: " + ext.statusMessage + "\n";

	var item:Object = {};
	item.label = name;
	item.value = ext.username
	profileData.push( profileText );
	names.push( name );
	usernames.push( ext.username );
	items.push( item );
	addToList( item );
}

private function logout( e:MouseEvent ):void {

	var manager:LoginManager = new LoginManager();
	manager.logout();
}

Okay, let’s take a look at what we just wrote. The first method is the addMessage method. This method is called whenever the user send’s or recieves a message. The message is displayed within the textfield and is concatentated with the time that the message was sent or recieved. The sendMessage method just sends the message typed out by the user to all of the users in the current chat room. It accepts two parameters: the message as a String, and the name of whoever the message is from as another String. We display the time the message was sent or recieved and the recipent with the message. Nothing too complicated, I hope.

Now for those mysterious event handler methods that are called in accordance to which RoomEvent has been dispatched. The onRoomJoined method is called whenever the current user successfully joins a chat room. All we do is call the enable method which re-enables all of the previously disabled components so that the user can interact with them.

The onUserJoined method is called whenever a user successfully joins the current room. I created this method for debugging so it only traces data. Normally you’d listen for incoming presences and respond to the change in data but here we want to respond to incoming user data extensions through our express delivery service. Again I found express delivery a lot simpler here, but you could try and extract the custom extensions directly from the Presence object from the onUserJoined method.

Note: The presence object is e.data as Presence in this case. You could also get more creative with this method, if you like: maybe have a particular sound play or a notification message pop up for the user whenever another user enters the room. Just some food for thought.

The onGroupMessage method is dispatched when an incoming message is recieved from another user. The room itself (the XMPP server) may even send messages. These are usually configuration messages or error messages. You may want to treat these messages as administration messages and forbid that the be displayed as a regular chat message in the TextArea component. Maybe you can display these messages in a message window of some sort. Just some more food for thought. In our onGroupMessage method we actually filter out any messages coming from the server. We check that the message’s sender, e.nickname (the sender’s username in this case), is in the usernames Vector, and then we display the message using the addMessage method.

Now for the addToList method. Add a new item to the List object using the DataProvider. Call the addItem method on the DataProvider object and pass an item into the parmeter. The item object should be a primitive Object and have a property called label (a String). The value should also be a String. We’ll use the corresponding user’s username as the value here. The removeFromList method does just what you think. It removes the specified user from the list. Nothing complicated at all – just the complete opposite of the addItem method.

Whenever our user clicks on an item in the list, we wan’t to display a profile window containing information about the user that was selected. The onUserSelected method does just that. The selected user’s username is the value of the selected item in the list. We use their username to grab the location (index) of their data String in the profileData Vector.

Note: We’ll be adding data into this Vector object within the onUserDataExtension method later.

The index of the username in the usernames Vector is the same as the index of user’s data stored in the profileData Vector. You can see why in the onUserDataExtension method if you look ahead. Next a new ProfileWindow is created and displayed to the user. We listen for the window to dispatch the DESTROYED event so that we can remove it from the display. We’ll need to write the ProfileWindow class later but as of now the ProfileWindow class is simply the linkage to the object in the library that we created earlier.

The onUserLeave method is dispatched when a user leaves the chat room. When a user leaves we remove them from the list because we only want to display current users. We call the removeFromList method to accomplish this.

Now for the moment we’ve all been waiting for. Now we get to look at the method that recieves the package delivered by our express delivery service. The onUserDataExtension method accepts one parameter. The deserialized UserDataExtension object delivered fresh from the UserDataExtensionManager class. The UserDataExtension object contains all of data that we need about the user that has just joined the room. We construct the profileData String ahead of time so that we don’t have to store the extensions. You can store the extensions instead if you’d like but I constructed the data into a String before the fact. Next we need to push all of the data to the appropriote Vector‘s and display the user in the list. It’s that simple.

The last method is the logout method. This method creates a new LoginManager object and calls it’s logout method which logs the current user out (destroying their session cookies) and then navigates to the main index page of the site.

Wow! That was pretty heavy. But not too heavy, I hope. If you found any of this confusing, look over the code carefully until you have a clear understanding of what’s going on before you move on. I’m saying this because this is gravy compared to the next sections which cover serializing and deserializing data. I’ll try to make it as simple as possible.


Step 20: A Brief Overview of Data Serialization

Before we create our UserDataExtension class which deals with data serialization and deserialization, we need to talk a little about the topic. Serialization in this context refers to taking specific data and converting it into a form that we can use to send the data across our network through XMPP. We can’t just take the UserDataGrabber object and send it across the network as a UserDataGrabber object and expect the data to be recieved on the other end in tact. In fact if we even tried to send the UserDataGrabber object using the XMPPConnection object’s send method, we just get an argument error because it only excepts an XMPPStanza as a parameter. The method only accepts XMPPStanza objects (Message, Presence, IQ) as a parameter.

So how do we solve this problem? The answer is with data serialization. We must convert an object or data into a format that can be sent along with an XMPP Stanza. So what format do we have to convert the UserDataGrabber object into so that we can send it across our network and recieved by another user? Well the answer is XML. XMPP is just streaming XML. It’s not only easy to parse – it’s extensible too. But it’s up to us to package (serialize) the user’s data into XML format so that it can be sent across the network and received by other users.

In summary, serialization is the process of converting data into a format that can be stored/saved, and/or sent across a network. Techically speaking we won’t actually be serializing the UserDataGrabber object, but instead we’ll be serializing the data that it contains but we could very well do so if we needed to. All we need for this project is the user’s data. I said I would keep this as simple as possible and I am. We’ll just be serializing primitive data. The more primitive the data, the easier it is to serialize. If we were trying to searialize an object in the library, let’s say a MovieClip with a lot of graphics and instance-specific properties, serialization would be a lot more complicated.


Step 21: How We Will Deserialize Data

It is up to the recieving end to take the serialized data (the XML) and convert it back into its original format. We must reverse the process. In our project, the data is represented by String values so it will actually be easy for us to deserialize.

Deserialization is a little more complicated than serialization, though, because we have to make a clone of the data that is received.When an incoming extension is received (as XML data), the UserDataExtension object deserializes (converts) the XML data back into String objects so that we can access them (read-only) through the UserDataExtension object via dot syntax. We will also need to check and make sure the data recieved is valid.

Here’s the basic run down of what we will be doing:

  • Recieve incoming data(XML in this case)
  • Check to see if the data matches the type of data UserDataExtension serializes (in other words check to see if it is a valid User Data Extension)
  • Parse the data and make a clone of the remote UserDataExtension object
  • Send the extension on an express delivery to the current instance of the UserInterface class making the data available to the user.

With that all said and done we can now move on to writing the UserDataExtension class.


Step 22: The UserDataExtension Class

No introduction is needed here. Let’s dig in. Create a new class and call it UserDataExtension. The class must extend org.igniterealtime.xiff.data.Extension. The class must also implement org.igniterealtime.xiff.data.IExtension and org.igniterealtime.xiff.data.ISerializable. Go ahead and import the XMLNode class and the org.igniterealtime.xiff.data.ExtensionClassRegistry class along with the previous classes.

import flash.xml.XMLNode;
import org.igniterealtime.xiff.data.Extension;
import org.igniterealtime.xiff.data.IExtension;
import org.igniterealtime.xiff.data.ISerializable;
import org.igniterealtime.xiff.data.ExtensionClassRegistry;

public class UserDataExtension extends Extension implements IExtension, ISerializable {

Create the following variables and constants.

private var data:UserDataGrabber;
private var _uid:String;
private var _firstName:String;
private var _lastName:String;
private var _username:String;
private var _country:String;
private var _statusMessage;
private var _isDeserialized:Boolean;

public static const NS:String = "mycontentsite:xmpp:extensions:userdata";
public static const ELEMENT_NAME = "userData";

We need variables to store the data recieved from the other user and two class constants. One to represent a unique namespace (NS) for the extension, and another that represents the name of the XML element.

Create the constructor.

public function UserDataExtension( parent:XMLNode = null, userData:UserDataGrabber = null ) {

	super( parent );

	if ( userData ) data = userData;
}

Important: Make sure to give a null default value to any parameters in the constructor because when we register our Custom Extension with the ExtensionClassRegistry, an instance of the class will be instantiated and will not recieve any arguments. If the constructor is not prepared to not recieve any parameters, an error will be thrown and your application may not function correctly.

Also, pass the parent (XMLNode) into the constructor of the base class (org.igniterealtime.xiff.data.Extension). If userData is not null, assign it to the data property. We will be serializing the information contained within this object in a moment.

Before I forget, let’s create the enable method.

public static function enable():void {

	ExtensionClassRegistry.register( UserDataExtension );
}

As you can see, the enable method is staight forward. It is a static method that registers the class/extension with the ExtensionClassRegistry, thus enabling the class/extension.

We will be serializing the data into XML format as I explained earlier. To be more specific, we will be creating XMLNode objects out of our data. Let’s create a method to simplify this process for us. Create the generateNode method.

private function generateNode( nodeName:String, nodeValue:String = null, attributes:Object = null ):XMLNode {

	var nameNode:XMLNode = new XMLNode( 1, nodeName );
	var valueNode:XMLNode;

	if ( nodeValue ) {

		valueNode = new XMLNode( 3, nodeValue );
		nameNode.appendChild( valueNode );
	}

	if ( attributes ) {

		nameNode.attributes = attributes;
	}

	return nameNode;
}

This is another self-explanatory method. Based on the parameters given it creates an XMLNode object already packaged and ready to go. Let’s start off with the parameters. The first parameter is the nodeName which is a String that represents the element name of the node we want to create. The second is another String that represents the value that the node is to contain. The last parameter is an Object that contains any attributes (as Strings) that the node should contain. The method puts the node together for us so that we don’t have to repeat these actions continuously.

Now for the fun part. Create the serialize method.

public function serialize( parent:XMLNode ):Boolean {

	var attributes:Object = {};
	attributes.xmlns = NS;
	attributes.id = data.uid
	var firstNode:XMLNode = generateNode( "firstName", data.firstName );
	var lastNode:XMLNode = generateNode( "lastName", data.lastName );
	var userNode:XMLNode = generateNode( "username", data.username );
	var countryNode:XMLNode = generateNode( "country", data.country );
	var statusNode:XMLNode = generateNode( "statusMessage", data.statusMessage );
	var mainNode:XMLNode = generateNode( "userData", null, attributes );
	mainNode.appendChild( firstNode );
	mainNode.appendChild( lastNode );
	mainNode.appendChild( userNode );
	mainNode.appendChild( countryNode );
	mainNode.appendChild( statusNode );
	setNode( mainNode );

	var node:XMLNode = this.getNode();

	if ( node.parentNode != parent ) {

		parent.appendChild( node );
	}

	return true;
}

When our Extension is added to an XMPPStanza, this method is called. The first thing we need to do is package our data into a main XMLNode. Notice that all of the nodes are children of mainNode. Also, if you haven’t noticed already, we have just put our generateNode method to good use. We have named the elements and set the values of each element. The firstName element contains the user’s first name, the statusMessage element contains the user’s status message, and so on. We have just taken the user’s data and stored(serialized) it inside of xml.

To complete the serialization process call the setNode method, which is a method of the base class, to set the node. Also check to see if our parentNode is the same as the paremeter parent XMLNode. If it is not make the our node a child of the parameter parent XMLNode.

Note: The serialize method is an interface method that requires a return type(Boolean). If the data was successfully serialized, you will want to return true. Unless for some awkward reason you are dealing with data that may not be serialized correctly, this method should always return true. Otherwise it should return false.

Write the deserialize method:

public function deserialize( node:XMLNode ):Boolean {

	if ( node.nodeName == ELEMENT_NAME && node.attributes.hasOwnProperty( "xmlns" ) && node.attributes.xmlns == NS ) {

		if ( node.attributes.hasOwnProperty( "id" ) ) {

			_uid = node.attributes.id;
		}
		else {
			trace("invalid node xmlns: " + node.nodeName );
			return false;
		}

		for each( var child:XMLNode in node.childNodes ) {

			switch ( child.nodeName ) {

				case "firstName" :
					var first:XML = new XML( child.toString() );
					_firstName = first;
					break;
				case "lastName" :
					var last:XML = new XML( child.toString() );
					_lastName = last;
					break;
				case "username" :
					var user:XML = new XML( child.toString() );
					_username = user;
					break;
				case "country" :
					var c:XML = new XML( child.toString() );
					_country = c;
					break;
				case "statusMessage" :
					var msg:XML = new XML( child.toString() );
					_statusMessage = msg;
					break;
				default :
					trace("invalid node child: " + node.nodeName );
					return false;
			}
		}

		if ( _firstName && _lastName && _username && _country && _statusMessage ) {	

			// Notify the UserDataExtensionManager Class
			setNode ( node );
			_isDeserialized = true;
			UserDataExtensionManager.registerData( this );
			return true;
		}
		else {

			trace("invalid missing data: " + node.nodeName );
			var a:Array = [firstName, lastName, username, country, statusMessage];
			for each(var el:* in a) {

				trace(el);
			}
			return false;
		}
	}

	return false;
}

Unlike the serialize method – which is called when the data is added to an XMPPStanza – the deserialize method is called when a custom extension is recieved on the other end. The method accepts one parameter, an XMLNode that was a child of the XMPPStanza that was recieved. It is our job to check whether the node was packaged by our own mechanism. So if the node is our custom extension, proceed to deserializing the node’s data, otherwise return false.

We use a for each loop to iterate through each child node in the parameter XMLNode. You can choose how strict your extension will be. I chose to make our extension fairly strict. If all of the data isn’t recieved, the extension returns false, telling the XIFF library that the node isn’t valid and was not packaged by our code.

After the XML data is parsed, we check to see whether all of the properties have been set; if they have been set, we call the registerData method on the UserDataExtensionManager class – a class we will create next – to send the extension on an express delivery trip to our user. Finally we return true upon success.

Two interface methods down, and two to go. Here they are:

public function getNS():String {

	return NS;
}

public function getElementName():String {

	return ELEMENT_NAME;
}

Very, very, very, very, simple. These methods are required by the IExtension interface. The getNS() method returns the unique names of the extension and the getElementName() returns the extension’s element name. Like I said: very, very, very, very, simple.

Were almost done. Finish up the class by granting read-only access to the user’s data and the _isDeserialized property.

public function get uid():String {

	if ( data ) {

		return data.uid;
	}
	else {

		return _uid;
	}
}

public function get firstName():String {

	if ( data ) {

		return data.firstName;
	}
	else {

		return _firstName;
	}
}

public function get lastName():String {

	if ( data ) {

		return data.lastName;
	}
	else {

		return _lastName;
	}
}	

public function get username():String {		

    if ( data ) {

		return data.username;
	}
	else {

		return _username;
	}
}

public function get country():String {

	if ( data ) {

		return data.country;
	}
	else {

		return _country;
	}
}

public function get statusMessage():String {

	if ( data ) {

		return data.statusMessage;
	}
	else {

		return _statusMessage;
	}
}

public function get isDeserialized():Boolean {

	return _isDeserialized;
}

Step 23: Express Delivery Service

As I explained in earlier steps, there may be different ways to handle incoming extensions but I prefer a method that I call the express delivery method. The concept is simple. A Helper class recieves the extension and sends the extension directly to a specific method(function), that is used for handling the extension, but only if the method has been registered for this action. Let’s create the UserDataExtensionManager class.

package {

	public class UserDataExtensionManager {

		public static var onUserData:Function;

		public static function registerData( data:UserDataExtension ) {

			if ( data.isDeserialized ) {

				if ( onUserData != null ) {

					onUserData( data );
				}
			}
		}
	}
}

This class is very light: only 18 lines of code. The registerDate method is called, accepting the deserialized UserDataExtension as a parameter, then the onUserData method is called if it exists. In this case the onUserData method is the onUserDataExtension method from the UserInterface class.


Step 24: The Profile Window Class

In order for a user to view information about another user we need a way to display the information to the user. I chose to use a simple window that contains a text field that we can use to display the info to the user. Let’s create the ProfileWindow class.

Remember: This class is linked to a MovieClip symbol in the library.

package {

	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.display.DisplayObject;

	public class ProfileWindow extends MovieClip {

		public static const DESTROYED:String = "destroyed";

		public function ProfileWindow() {

			super();
			addEventListener( Event.ADDED_TO_STAGE, onAdded );
		}

		private function init():void {

			txt.selectable = false;
			txt.wordWrap = true;
			closeBtn.label = "Close";
			closeBtn.addEventListener( MouseEvent.CLICK, destroy );
			stage.addEventListener( Event.RESIZE, onStageResize );
			center();
		}

		private function center():void {

			x = ( stage.stageWidth - width ) / 2;
			y = ( stage.stageHeight - height ) / 2;
		}

		private function onAdded( e:Event ):void {

			removeEventListener( Event.ADDED_TO_STAGE, onAdded );
			init();
		}

		private function onStageResize( e:Event ):void {

			center();
		}

		private function destroy( e:Event ) {

			removeEventListener( MouseEvent.CLICK, destroy );
			stage.removeEventListener( Event.RESIZE, onStageResize );

			if ( this.parent ) {

				for ( var i:int = 0; i < numChildren; i++ ) {

					removeChild( getChildAt( i ) );
				}

                this.parent.removeChild( this );
				dispatchEvent( new Event( DESTROYED ) );
			}
		}

		public function set text( value:String ):void {

			txt.text = value;
		}
	}
}

The constructor method adds and event listener that listens for the ADDED_TO_STAGE event. When the window is added to the stage, the onAdded method is called, then listener is removed and the init method is called.

The init method initialized the text field and the close button. It also ensures that the window will always rest in the center of the stage.

You may notice a class constant at the beginning of the script. The DESTROYED constant is a type of event that the window will dispatch when the user clicks on closeBtn. When closeBtn is clicked, the destroy method is called, all of the children are removed and the window is removed from it’s parent. We could have made a custom event class such as ProfileWindowEvent that extends flash.events.Event, but that wasn’t nessassary. Instead we dispatch an Event object with the type DESTROYED. This is simple and serves its purpose well.

Finally we have our setter method set text. This could also have been a readable and writable property but again it wasn’t necessary. Instead it is write-only because we never need to read this property in any of the rest of our code. The method sets the text of the textfield so that the text is displayed to the user.


Step 25: The Final Step

The last thing we need to do in order for our application to work is set up the HTML page. Our application is a dynamic web application. Earlier we grabbed the roomName variable that had been passed into Flash; the only problem here is that we haven’t passed this variable into Flash yet. You’ll need to implement this into your code on your own. Here is my code:

<?php

	$flashVars = '"room=mainlobby"';
	$image = '"mainlobby.png"';

	if ( isset( $_GET['content'] ) ) {

		$content = $_GET['content'];

		switch ( $content ) {

			case "box2dgame" :
				$roomName = "Box2d Game";
				break;
			case "platformgame" :
				$roomName = "Platform Game";
				break;
			case "mainlobby" :
			case "" :
			case null :
			default :
				$roomName = "Main Lobby";
				break;
		}

		$flashVars = '"room=' . $roomName . '"';
		$image = '"' . $content . '.png"';
	}

?>
<? xml version="1.0" encoding='utf-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
	<head>
		<title>My Content Site</title>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<style type="text/css" media="screen">
		//html, body { height:100%; background-color: #ffffff;}
		//body { margin:0; padding:0; overflow:hidden; }
		#flashContent { width:100%; height:100%; align:middle; }
		</style>
	</head>
	<body>

		<div id="myContent">
			<image src=<?php echo $image; ?> alt="game content" align="middle"/>
		</div>

		<div id="flashContent">
			<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100%" height="50%" id="chat" align="middle">
				<param name="movie" value="chat.swf" />
				<param name="quality" value="high" />
				<param name="bgcolor" value="#ffffff" />
				<param name="play" value="true" />
				<param name="loop" value="true" />
				<param name="wmode" value="window" />
				<param name="scale" value="showall" />
				<param name="menu" value="true" />
				<param name="devicefont" value="false" />
				<param name="salign" value="" />
				<param name="allowScriptAccess" value="sameDomain" />
				<param name="flashVars" value=<?php echo $flashVars; ?> />
				<!--[if !IE]>-->
		  <object type="application/x-shockwave-flash" data="chat.swf" width="100%" height="50%">
					<param name="movie" value="chat.swf" />
					<param name="quality" value="high" />
					<param name="bgcolor" value="#ffffff" />
					<param name="play" value="true" />
					<param name="loop" value="true" />
					<param name="wmode" value="window" />
					<param name="scale" value="showall" />
					<param name="menu" value="true" />
					<param name="devicefont" value="false" />
					<param name="salign" value="" />
					<param name="allowScriptAccess" value="sameDomain" />
					<param name="flashVars" value=<?php echo $flashVars; ?> />
				<!--<![endif]-->
					<a href="http://www.adobe.com/go/getflash">
						<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
					</a>
				<!--[if !IE]>-->
				</object>
				<!--<![endif]-->
			</object>
		</div>
</body>
</html>

The above PHP script is just an example. Your site will most likely be a lot more inituitive and creative. The GET content variable specifies which chat room to join. Then we pass this information into Flash so that our code knows which chat room to join.


Step 26: Test Our Application

Make sure that WAMP and Openfire are running on your system. If all went well, your application should be runnning fine. Test the application in multiple browsers as different users. Have one user follow another into a chat room to see how well our application handles the change in data. Then chat with yourself. Have fun with it.


Conclusion

Well, we learned a lot and I’m glad to have shared this time with you. The XIFF library is absolutely amazing. I hope this tutorial has encouraged you to explore this incredible library in it’s entirety. There’s so much you can do with it. You can create implement private chat and rosters into our current application or create an entirely new application that connects to another XMPP server such as one of the servers listed on xmpp.org. Thanks for tuning in. See you next time.

Important: The source files were configured based on my own machine. Remember to reload the XIFF.swc file into the chat.fla file. Also remember to set your server settings within the constants of the Document class (ChatApp.as).



View full post on Activetuts+

banner ad

10 Responses to “Create a Flexible XMPP Chat for a Member-Based Website With Flash and PHP”

  1. Jaron Franklin Fort says:
    October 13, 2011 at 7:58 pm

    We’ll be looking at how to create a XMPP chat application that can be used in many different scenarios. You’ll learn how to integrate an external database with Ignite Realtime’s Openfire Jabber Server and how to use the XIFF library to create custom XMPP extensions that can be used to send custom data across a network.

    You could use this to build a standard standard chat room app, with a page devoted to it, or you could run it alongside another piece of Flash content, like Kongregate does with its games.



    Final Result Preview

    Let’s take a look at the interface of the final result we will be working towards (this demo does not function as an actual chat client):

    Here’s a video demo that shows it in action:



    Step 1: Prerequisites

    This tutorial assumes that you have some experience with PHP. You must be have Wamp Server installed on your system and you should also be somewhat familiar with PhpMyAdmin. If you do not have WAMP you can download it here. You will also need to download the Openfire Jabber Server and the XIFF API library from the Ignite Realtime website. I will walk you through the Openfire installation process. Finally you will need the latest version of Java installed on your operating system and Flash CS4 or later. I will be using Flash CS5 Professional in this tutorial. If you do not have the latest version of Java, you can download the latest version here.



    Step 2: Setting Up Our Database

    Make sure that Wamp Server is running on your computer and navigate to http://localhost/phpmyadmin/ in your web browser.

    Create a new database called MyContentSite using PhpMyAdmin.

    create the main database

    Create a new table in the MyContentSite database called myMembers with eight fields.

    the mymembers table

    After the myMembers table has been sucessfully created by PhpMyAdmin, create the following fields:

    • uid
    • first_name
    • last_name
    • my_username
    • my_password
    • email
    • status_message
    • country

    Your screen should look as follows:

    members fields 1

    Let me break down each field. The uid field should be of type INT. You can change the field’s type from the Type column. Make this field the primary index and set this field to auto-increment itself. Do this by selecting the PRIMARY option underneath the INDEX column within this fields row. Then check the checkbox under the Auto-Increment column. This field represents the userID of the current member of our website. The first_name, last_name, my_username, my_password, email, and country fields should be have the datatype or VARCHAR and the Length/Value should be set to 255.

    Note: The my_password fields shown in the images hold an MD5 of the user’s password. You may choose to store passwords plain without any encryption but for this tutorial I will be using hashed passwords.

    members fields 2

    Finally, the status_message field should be of the of type MEDIUMTEXT.

    Once you have created the all of the fields click the save button.

    ssaved members fields

    Now we are ready to create two dummy accounts that we will use to login to our website and join chat rooms later in this tutorial. Click on the Insert tab. You will be presented with a form for creating a new row in the table.

    Leave the uid field empty because we want this field to automatically increment itself. Set the first_name to Jane and the last_name field to Doe. Set the my_username to janedoe with all lower cass letters. For the my_password field, we’ll be using the hashed value for our password which is tutsplus in all lower case letters. Type ca28ad0733a3dde9dc1f30e32718d209 into the my_password field. You can set the email field to an email address of your choosing and the status_message field to whatever you’d like. Set the country field to whatever country you’d like as well. When you are finished click on the save button . Repeat this process to create an account for a John Doe with the my_username field set to johndoe123 in all lower case letters. Use the same password as before.

    jane doe account
    john doe account
    saved accounts


    Step 2: Installing Openfire

    Once you have downloaded Openfire from the Ignite Realtime website, run the installation exe file (or dmg file if you are using a Mac).

    initializing installer

    Select your langauge.

    choose your language

    Click Next to continue.

    begin installation

    Accept the License Agreement.

    terms of use

    Choose a directory and click Next to continue.

    installation directory

    Select a Start Menu folder.

    start menu folder

    Click Next to begin the installation.

    extraction

    Once the installation has completed, click Finish to to run Openfire. The Openfire service will start automatically when the program is run. Click the Launch Admin button when Openfire has finished booting.

    installation complete
    Openfire starting up
    Openfire running

    Now we will set up Openfire. Select your preferred language and click Continue.

    choose language

    Leave the Admin Console Port field and Secure Admin Console Port field to their default values. For this tutorial leave the Domain field to it’s default value as well. You can change this later to your website’s domain. Click Continue.

    server settings

    Select the Standard Database Connection option and click Continue.

    database settings

    Under Database Driver Presets, choose MySQL. Type com.mysql.jdbc.Driver in the JDBC Driver Class field. Change [host-name] to localhost and change [database-name] to mycontentsite in the Database URL field. Set Username and Password to your MySQL database’s username and password. For this tutorial I used the default username for MySQL which is root and the Password field remains blank. Click Continue to move on.

    mysql setup 1
    mysql setup 2

    Leave the Profile Settings to Default. Click Continue to move on.

    profile settings

    Choose an email address for your Administrator Account and a password then continue.

    admin password

    We are now done the setup process. You may now login to the admin console. Use the default username admin and the password the you chose during setup.


    Step 3: Setting Up the Chat Rooms

    Our application allow users to communicate within chat rooms. But for this to happen our users must have chat rooms to join. Let’s create the chat rooms using the Openfire Admin Console. If you haven’t already, start Openfire Server. Log in the Openfire’s Admin Console. Navigate to the Group Chat Rooms page by clicking on the Group Chat tab.

    chat room home

    Click Create New Room on the left hand side of the screen. Fill out the details as you see them in the image below.

    new chat room

    When you are finished, click on the Save Changes button. If the room was created successfully, you should see a message and a Green check.

    room creation

    Follow the same steps to create two more chat rooms.

    another room
    last room
    all chat rooms

    Step 4: Integrating Openfire with MySQL

    In this tutorial, our make-believe website uses a MySQL database to store data about each user. Openfire can be integrated with an external database, a MySQL database in this case. First we must configure Openfire to do this.

    Open the openfire.xml file using Notepad or preferrably a rich text editor such as Notepad++ as I mentioned before. The file will be located in the Openfire/conf/ folder within the Program Files directory folder on your PC.

    
    
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
        This file stores bootstrap properties needed by Openfire.
        Property names must be in the format: "prop.name.is.blah=value"
        That will be stored as:
            <prop>
                <name>
                    <is>
                        <blah>value</blah>
                    </is>
                </name>
            </prop>
    
        Most properties are stored in the Openfire database. A
        property viewer and editor is included in the admin console.
    -->
    <!-- root element, all properties must be under this element -->
    <jive>
      <adminConsole>
        <!-- Disable either port by setting the value to -1 -->
        <port>9090</port>
        <securePort>9091</securePort>
      </adminConsole>
      <locale>en</locale>
      <!-- Network settings. By default, Openfire will bind to all network interfaces.
          Alternatively, you can specify a specific network interfaces that the server
          will listen on. For example, 127.0.0.1. This setting is generally only useful
           on multi-homed servers. -->
      <!--
        <network>
            <interface></interface>
        </network>
        -->
      <connectionProvider>
        <className>org.jivesoftware.database.EmbeddedConnectionProvider</className>
      </connectionProvider>
      <database>
        <defaultProvider>
          <driver>com.mysql.jdbc.Driver</driver>
          <serverURL>jdbc:mysql://localhost:3306/mycontentsite</serverURL>
          <username>root</username>
          <password/>
          <testSQL>select 1</testSQL>
          <testBeforeUse>true</testBeforeUse>
          <testAfterUse>true</testAfterUse>
          <minConnections>5</minConnections>
          <maxConnections>25</maxConnections>
          <connectionTimeout>1.0</connectionTimeout>
        </defaultProvider>
      </database>
      <setup>true</setup>
    </jive>
    

    This is what my openfire.xml file looks like. Your openfire.xml file should look similar to mine. Here is a link to the Openfire Custom Database Integration Guide on the Ignite Realtime website. You will noticed that you are instructed to make changes directly to the openfire.xml configuration file in this guide.

    Do not make any changes to this file unless it does not resemble mine.

    Note: It is very likely that your openfire.xml file will be using DefaultConnectionProvider. If it is, you may have trouble logging in to the Admin Console. Try to log in with the default first. If the password you specified during setup doesn’t work, use the default password to login. The default username is admin and the default password is admin as well.

    If you can’t log in, change DefaultConnectionProvider to EmbeddedConnectionProvider. Then restart Openfire and try to log in again. If you are still having trouble, run the setup to Openfire again. Change the setup tag’s value from false to true within the openfire.xml file. Then restart Openfire to run the setup again. Do this as a last resort – this shouldn’t be necessary.

    I have followed the steps on the Ignite Realtime website countless times only to find myself in a hole later on. One of the problems I faced was that users couldn’t connect to the server and when I tried to fix the problem in the admin console, I couldn’t log in. In fact the only thing I can conceive of that might be more frustrating than the problems that I faced was being stuck inside of a Saw trap.

    I don’t want you to go through what I had to so please follow the following steps carefully. Openfire has a brilliant way of editing and creating properties that I find to be a lot more efficient than having to edit an xml file on your system.

    Log into Openfire’s Admin Console. Click the System Properties link on the right hand side of the main page.

    system properties

    Your server’s System Properties page should look something like this.

    Important: If your System Properties page is missing some or all of the properties within the image below, you can add the properties in manually. When we modify a property in the tutorial, if you don’t have the property that we are modifying, just use the same steps that you would to modify a property to create the property instead. Otherwise, if you already have a property that we are creating, just modify the property with the values that I specify.

    Towards the bottom of the screen you will see a section with the title Add new property. It has two fields. The first field Property Name. The second field is Property Value. Within the Property Name field, type in jdbcProvider.driver and within the Property Value field, type com.mysql.jdbc.Driver into the field. Click on the Save Property button when you are finished. You will be following these steps to create more properties as well as to modify existing properties.

    jdbcProvider driver

    Create a property called jdbcProvider.connectionString with the value jdbc:mysql://localhost/mycontentsite?user=root&password=.

    jdbcProvider connection string

    Now we are going to make our first modification to an existing property. Click on the Edit link that correspondes to the provider.auth.className property. Change its value to org.jivesoftware.openfire.auth.JDBCAuthProvider using the Edit property table. Click the Save Property button when you are finished.

    provider auth classname

    Create a new property with the name jdbcAuthProvider.passwordSQL. Give it a value of SELECT my_password FROM mymembers WHERE my_username=?. This property’s value is the MySQL query string that will be used to authenticate a user.

    Note: Notice that it contains a question mark (?). The question mark will be replaced with the value inside the username field.

    jdbcAuthProvider passwordSQL

    Create a new property called jdbcAuthProvider.passwordType. Give it a value of md5.

    jdbcAuthProvider passwordType

    Note: The jdbcAuthProvider properties will be hidden if you have followed the steps correctly.

    property saved

    Create a new property called admin.authorizedUsernames. The value should be the jid of the usernames that you would like to be able to log into the Admin Console with.

    Note: Look at the image below. Notice that the Jane and John Doe’s jid’s are their usernames concatenated with an @ sign and the server’s XMPP domain.

    authorized usernames

    Modify the property provider.user.className by changing its value to org.jivesoftware.openfire.user.JDBCUserProvider.

    provider user className

    Create a new property called jdbcUserProvider.loadUserSQL with the value SELECT first_name,email FROM mymembers WHERE my_username=?.

    jdbcUserProvider loadUserSQL

    Create a new property called jdbcUserProvider.userCountSQL and give it the value SELECT COUNT(*) FROM mymembers.

    jdbcUserProvider userCountSQL

    Create a new property called jdbcUserProvider.allUsersSQL. Set the value to SELECT my_username FROM mymembers.

    jdbcUserProvider allUsersSQL

    Create a new property called jdbcUserProvider.searchSQL. Give it the value SELECT my_username FROM mymembers.

    jdbcUserProvider searchSQL

    Create a new property called usernameField. Set its value to my_username.

    username field

    Create a new property called nameField. Set its value to first_name.

    name field

    Create a new property called emailField. Set its value to email.

    email field

    Now that we have added and modified the properties needed we can log out of the Admin Console. Restart Openfire and attempt to log back into the Admin Console with an actual user.

    Access denied!

    Now try to log in with the username, admin.

    Access denied again! What’s going on here?

    Let’s take a look at the openfire.xml file. Yours should look the same as it did before. We need to add the modifications to the xml file. I have found that by making modifications in the Admin Console first, then changing the openfire.xml file after, is more consistent than just making changes to the xml. As I described before, I just couldn’t log in using a client or into the Admin Console after I had made these modifications.

    Change your openfire.xml file so that it looks like this.

    
    
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
        This file stores bootstrap properties needed by Openfire.
        Property names must be in the format: "prop.name.is.blah=value"
        That will be stored as:
            <prop>
                <name>
                    <is>
                        <blah>value</blah>
                    </is>
                </name>
            </prop>
    
        Most properties are stored in the Openfire database. A
        property viewer and editor is included in the admin console.
    -->
    <!-- root element, all properties must be under this element -->
    <jive>
      <adminConsole>
        <!-- Disable either port by setting the value to -1 -->
        <port>9090</port>
        <securePort>9091</securePort>
      </adminConsole>
      <locale>en</locale>
      <!-- Network settings. By default, Openfire will bind to all network interfaces.
          Alternatively, you can specify a specific network interfaces that the server
          will listen on. For example, 127.0.0.1. This setting is generally only useful
           on multi-homed servers. -->
      <!--
        <network>
            <interface></interface>
        </network>
        -->
      <connectionProvider>
        <className>org.jivesoftware.database.DefaultConnectionProvider</className>
      </connectionProvider>
      <database>
        <defaultProvider>
          <driver>com.mysql.jdbc.Driver</driver>
          <serverURL>jdbc:mysql://localhost:3306/mycontentsite?user=root&amp;password=</serverURL>
          <username>root</username>
          <password/>
          <testSQL>select 1</testSQL>
          <testBeforeUse>true</testBeforeUse>
          <testAfterUse>true</testAfterUse>
          <minConnections>5</minConnections>
          <maxConnections>25</maxConnections>
          <connectionTimeout>1.0</connectionTimeout>
        </defaultProvider>
      </database>
      <jdbcProvider>
        <driver>com.mysql.jdbc.Driver</driver>
        <connectionString>jdbc:mysql://localhost/mycontentsite?user=root&amp;password=</connectionString>
      </jdbcProvider>
      <provider>
        <auth>
          <className>org.jivesoftware.openfire.auth.JDBCAuthProvider</className>
        </auth>
        <user>
          <className>org.jivesoftware.openfire.user.JDBCUserProvider</className>
        </user>
      </provider>
      <jdbcAuthProvider>
        <passwordSQL>SELECT my_password FROM mymembers WHERE my_username=?</passwordSQL>
        <passwordType>md5</passwordType>
      </jdbcAuthProvider>
      <jdbcUserProvider>
        <loadUserSQL>SELECT first_name,email FROM mymembers WHERE my_username=?</loadUserSQL>
        <userCountSQL>SELECT COUNT(*) FROM mymembers</userCountSQL>
        <allUsersSQL>SELECT my_username FROM mymembers</allUsersSQL>
        <searchSQL>SELECT my_username FROM mymembers WHERE</searchSQL>
        <usernameField>my_username</usernameField>
        <nameField>first_name</nameField>
        <emailField>email</emailField>
      </jdbcUserProvider>
      <setup>true</setup>
      <admin>
    	<authorizedUsernames>janedoe, johndoe123</authorizedUsernames>
      </admin>
    </jive>
    

    Make sure to change you are using the DefaultConnectionProvider instead of the EmbeddedConnectionProvider then close Openfire and restart it. Attempt to log into the Admin Console as a member from your website’s database. I logged in as John Doe. If done correctly, you should be back in the Admin Console and the username should be in the top right hand corner of the home page.

    Note: Before moving on, make sure that all of the properties within the openfire.xml file are showing up on the System Properties of the Admin Console. If they aren’t you now know how to add them in manually.

    logged in

    Step 5: PHP

    We need to use PHP to grab data from a MySQL database and present the data to Flash. For those of you who are new to PHP I will briefly explain what each script accomplishes. Let’s start with the MySQLConnection class.

    The MySQLConnection class connects to and disconnects from a MySQL database.

    
    
    
    	class MySQLConnection {
    
    		private $db_host = "localhost"; // Your Websites domain
    		private $db_user = "root"; // Your databases username
    		private $db_pass = ""; // Your databases password
    		private $db_name = "mycontentsite"; // The name of your database
    		private $connected = 0;
    
    		public function connect() {
    
    			mysql_connect($this->db_host, $this->db_user, $this->db_pass) or die ( "Error: Script aborted. Could not connect to database." );
    			mysql_select_db($this->db_name) or die ( "Error: Script aborted. No database selected." );
    			$this->connected = 1;
    			session_start();
    		}
    
    		public function close() {
    
    			mysql_close();
    			$this->connected = 0;
    		}
    
    		public function get_connected() {
    
    			return $this->connected;
    		}
    	}
    

    The LoginManager class handles user logins. A user can be authenticated then logged in and out with this class.

    
    
    
    	require_once "MySQLConnection.php";
    
    	class LoginManager {
    
    		public function __construct() {
    
    		}
    
    		public function login( $username, $password ) {
    
    			$username = strip_tags( $username );
    
    			$username = stripslashes( $username );
    
    			$username = mysql_real_escape_string( $username );
    
    			$passHash = md5( $password ); // Applies MD5 encoded hash to the password
    
    			$connection = new MySQLConnection();
    			$connection->connect();
    
    			$sql = "SELECT * FROM mymembers WHERE my_username = '$username' AND my_password = '$passHash' LIMIT 1";
    			$query = mysql_query( $sql );
    
    			if ($query) {
    
    				$count = mysql_num_rows( $query );
    			}
    			else {
    
    				die ( mysql_error() );
    			}
    
    			if ( $count > 0 ) {
    
    				while ( $row = mysql_fetch_array( $query ) ) {
    
    					$_SESSION['username'] = $username;
    					$_SESSION['pw'] = $password;
    					$uid = $row['uid'];
    					session_name( $username . $uid );
    					setcookie( session_name(), '', time() + 42000, '/' );
    					$connection->close();
    					die ( "login=1" );
    				}
    
    				die ( "login=0&error=Invalid username or password" );
    
    			}
    			else {
    
    				$connection->close();
    				die ( "login=0&error=Invalid username or password" );
    			}
    		}
    
    		public function checkLogin() {
    
    			if ( isset ( $_SESSION['username'] ) && isset ( $_SESSION['pw'] ) ) {
    
    				$user = $_SESSION['username'];
    				$pw = $_SESSION['pw'];
    				die ( "login=1&username=$user&password=$pw" );
    			}
    			else {
    
    				die ( "login=0" );
    			}
    		}
    
    		public function logout() {
    
    			setcookie(session_name(), '', time() - 42000, '/');
    			if ( isset( $_SESSION['username'] ) ) unset( $_SESSION['username'] );
    			if ( isset( $_SESSION['pw'] ) ) unset( $_SESSION['pw'] );
    
    			//Destroy session
    			session_destroy();
    
    			//return result to Flash (swf)
    			die ("logout=1");
    		}
    	}
    

    We call upon login.php to log the user in and logout.php to log the user out using the LoginManager class. To check to see if a user is logged in we call the check_login.php script.

    
    
    
        // login.php
    
    	require_once "classes/LoginManager.php";
    
    	if (isset($_POST['username']) && $_POST['password']) {
    
    		login();
    	}
    
    	function login() {
    
    		$username = $_POST['username'];
    		$password = $_POST['password'];
    		unset($_POST['username']);
    		unset($_POST['password']);
    
    		$login = new LoginManager();
    		$login->login( $username, $password) ;
    	}
    
    
    
    
        // logout.php
    
    	require_once "classes/LoginManager.php";
    
    	$login = new LoginManager();
    	$login->logout();
    
    
    
    
        // check_login.php
    
    	require_once "classes/LoginManager.php";
    
    	session_start();
    
    	$login = new LoginManager();
    	$login->checkLogin();
    	exit();
    

    The final script that is called from ActionScript is the grab_user_data.php script that is used to select the user’s data from our MySQL database.

    
    
    
    	require_once "classes/MySQLConnection.php";
    
    	if ( isset( $_POST['username'] ) ) {
    
    		$connection = new MySQLConnection();
    		$connection->connect();
    		$username = $_POST['username'];
    		$sql = "SELECT * FROM mymembers WHERE my_username = '$username' LIMIT 1";
    		$query = mysql_query( $sql );
    
    		while ( $row = mysql_fetch_array( $query ) ) {
    
    			$uid = $row['uid'];
    			$xml = '<user id="' . $uid . '">' . "\n";
    			$xml .= "	<firstName>" . $row['first_name'] . "</firstName>\n";
    			$xml .= "	<lastName>" . $row['last_name'] . "</lastName>\n";
    			$xml .= "	<country>" . $row['country'] . "</country>\n";
    			$xml .= "	<statusMessage>" . $row['status_message'] . "</statusMessage>\n";
    			$xml .= "</user>\n";
    		}
    
    		echo $xml;
    		$connection->close();
    		exit();
    	}
    

    These PHP scripts serve a very important role in our application but are very basic.


    Step 6: Setting Up Flash

    Open up Flash Professional. Set the document class to ChatApp. Set the size of the stage to 550 x 400.

    setup_flash

    Note: I like to use a framerate of 30 FPS but our application doesn’t have any animation so you can use whatever framerate works best for you.


    Step 7: Build the Client’s User Interface

    From the Components panel, select and drag a Button component on to the stage. Position the button so that it sits in the top right corner of the stage. Set the instance name of the button to logoutBtn. Add another button in the bottom right of the stage and set its instance name to sendBtn.

    button component
    buttons on stage

    Add a List compenent to the stage. Position it directly beneath logoutBtn and resize the component so that it fits nicely between both buttons. Set its instance name to list.

    list on stage

    We’ll be using logoutBtn to logout of the user’s session and sendBtn to allow our users to send messages. The List will display all of the online users within the current chat room. When an item in the list is clicked, the user’s profile will be loaded.

    Now we need a component that will display incoming and outgoing chat messages as well as a textfield that our users can use to input new messages. Add a TextArea component to the stage. Resize and position it to take up most of the remainder of the stage, leaving room for an input textfield at the bottom that is the same height as sendBtn. Set the instance name to displayTxt.

    text area component
    text area on stage

    Finally we need to add a TextInput component to the stage. Position the component directly beneath displayTxt and to the left of sendBtn. Set the instance name to inputTxt.

    text input component

    Select all of the components on the stage. Convert the selection to a symbol. The symbol should be a MovieClip named UserInterface. Select the Export for ActionScript option. The Class field should read UserInterface. Set the instance name to our new symbol to ui. Finally name the current layer of the main timeline interface. This will help you organize your project better.

    all components on stage
    user interface symbol
    ui instance
    interface layer

    Step 8: The Login Screen

    Our chat application would be useless if user’s couldn’t log in to our application. Let’s build the login screen. Create a new layer on the Main Timeline. Name the layer login.

    login layer

    Using the Rectangle tool, draw a rectangle on the stage that is the same size as the stage. The rectangle’s stoke should be set to 0. The rectangle should not have a line and should be filled black with a tranparency of 50%.

    darkbox on stage
    color picker 1
    color picker 2

    Highlight the black rectangle and convert it to a new MovieClip symbol called DarkBox. We will use this object to darken the screen while the log in components are being displayed. Set the instance name of the DarkBox object to darkBox.

    darkbox symbol
    darkbox instance

    Add two InputText components, two Label components, and a Button component to the stage. Make sure you aren’t adding these objects on top of the interface layer. Position the components as they are in the image below with the username field first then the password field.

    Set the instance name of the first lable to userLabel and set the instance name of the second label to passLabel. Set the instance name for the first input text field to usernameTxt and set the instance name of the second input text field to passwordTxt. Set the instance name of the button to loginBtn.

    Use the Text tool to add a Dynamic TextField to the stage. Set the text size to 18 and make the text color Red. Set the instance name to errorTxt. Position errorTxt beneath loginBtn as seen below.

    login screen elements

    We are going to convert everything on the login layer to a single new MovieClip symbol named LoginScreen but before we can do this we need to lock everything on the interface layer so we do not accidently select an object of that layer. Lock the interface layer by selecting the Lock Layer button next to the layer. When the layer is locked you will see a symbol of a lock next to the layer.

    You may now safely select all of the objects on the login layer that we just created, and convert the selection to a symbol with a linkage of LoginScreen. Set the instance name to loginScreen.

    login screen component
    stage with elements

    Step 9: Creating the Profile Window

    Lock and hide all of the current layers on the Main Timeline. Create a new layer and call it profile info.

    Note: This is just a layer that we will use for development. You may delete this layer at the end of this step if you wish.

    profile info layer

    Using the Rectangle tool, draw a rectangle with the radius of each corner set to 10.00. The rectangle should have a fill identical to the DarkBox object (Black with a transpareny of 50%) and a fully opaque White line with a stroke value of 4.00. I set the color of the stage to a Burnt Orange in the image below so that you can see how everything should look more clearly.

    rounded rectangle

    Add a Dynamic Text to the stage directly on top of the rectangle. Position and resize the text field so that it takes up most of the rectangle’s area but leaves room for a button. Set the instance name of the text field to txt. Make sure that the text color is White and the size of the text is at least 18px.

    text field
    unfinished profile window
    an added button

    Add a new Button component on top of the rectangle and set the instance name to closeBtn.

    highlighted profile window

    Select all of the object’s on the profile info layer and convert them to a MovieClip symbol named ProfileWindow. Check the field Export for ActionScript so that this symbol has a linkage of ProfileWindow. Now remove the ProfileWindow object from the stage. We will be instantiating this object with code.

    profile window symbol

    Step 10: Our First ActionScript Code

    Create a new ActionScript file and name it ChatApp.as. Add the following lines of code to the class.

    
    
    package {
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.ErrorEvent;
    	import flash.display.StageScaleMode;
    	import flash.display.StageAlign;
    	import flash.events.TimerEvent;
    	import flash.utils.Timer;
    	import flash.system.Security;
    	import flash.external.ExternalInterface;
    	import org.igniterealtime.xiff.core.XMPPConnection;
    	import org.igniterealtime.xiff.events.ConnectionSuccessEvent;
    	import org.igniterealtime.xiff.events.LoginEvent;
    	import org.igniterealtime.xiff.events.DisconnectionEvent;
    	import org.igniterealtime.xiff.events.XIFFErrorEvent;
    	import org.igniterealtime.xiff.events.RoomEvent;
    	import org.igniterealtime.xiff.events.IncomingDataEvent;
    	import org.igniterealtime.xiff.events.OutgoingDataEvent;
    
    	public class ChatApp extends Sprite {
    
    		private static const SERVER:String = "tutorial-f5d57edaa";//= "[host name] // Your server's host name here
    		private static const PORT:Number = 5222; // Your servers public port here
    		private static const RESOURCE:String = "MyContentSite";//[resource name] // Resource name	ex.: ==> MyJabberApp
    		private static const DEFAULT_ROOM:String = "Main Lobby";
    
    		private var grabber:LoginCredentialsGrabber;
    		private var userData:UserDataGrabber;
    		private var connection:XMPPConnection;
    		private var requireLogin:Boolean;
    		private var roomName:String;
    
    		public function ChatApp() {
    
    			super();
    			if (stage) init()
    			else addEventListener(Event.ADDED_TO_STAGE, onAdded);
    		}
    
            private function init():void {
    
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			loginScreen.visible = false;
    			loginScreen.userLabel.text = "username";
    			loginScreen.passLabel.text = "password";
    			ui.visible = false;
    
    			UserDataExtension.enable();
    			grabber = new LoginCredentialsGrabber();
    			userData = new UserDataGrabber();
    
    			var flashVars:Object = this.loaderInfo.parameters;
    
    			if ( flashVars.hasOwnProperty( "room" ) ) {
    
    				roomName = flashVars.room;
    			}
    
    			checkLogin();
    		 }
    
            private function onAdded( e:Event ):void {
    
    			removeEventListener(Event.ADDED_TO_STAGE, onAdded);
    			init();
    		}
    	}
    }
    

    Within the code above we check to see if the stage exists in the class constructor. If the stage does not exist, we listen for the ADDED_TO_STAGE event and the onAdded event handler method is called when the stage is available. The onAdded method simply stops listening for the ADDED_TO_STAGE event and calls the init method. If the stage exists we skip this first step and just call the init method which initialized our application.

    We initialize the stage and the loginScreen, and we make the UserInterface object (ui) invisible. You may notice the enable method from the UserDataExtension class being called. We will write this class later but for now just know that it is very important to remember to always call this method when instantiating the application. The enable method registers our custom extension(the class) with the ExtensionClassRegistry class in the XIFF library. We’ll talk more about this later.

    Instantiate a new instance of the LoginCredentialsGrabber class and assign it to the grabber variable. Also instantiate a new instance of the UserDataGrabber class and assign it to the userData variable. We will write these classes later also. When our SWF file is embeded in a web page we want our application to connect to a specific chat room that is related to the content on the page. Later we are going to pass the name of the chat room, that our app should connect to, into the flashVars parameter at embed time. But for now we’ll just first check to see if the variable exists and then we grab the value and assign it to the roomName variable. Finally we run the checkLogin method which is self-explanatory.


    Step 11: Checking Whether the User Is Logged In

    Write the checkLogin method in the Document Class( ChatApp ).

    
    
    private function checkLogin():void {
    
    	grabber.addEventListener( Event.COMPLETE, onLoginCredentials );
    	grabber.grab();
    }
    

    As you can see the method is very simple. This is because all of the functionality is encapsulated within the LoginCredentialsGrabber class. Listen for the COMPLETE event to be dispatched so that the onLoginCredentials event handler method can be called. Call the grab method on LoginCredentialsGrabber object. This methods checks to see whether the user is logged in – or, more specifically, it checks to see whether the user’s session exists.

    Next, we’ll write the onLoginCredentials method.

    (Note: We are still in the Document class.)

    
    
    private function onLoginCredentials( e:Event ):void {
    
    	grabber.removeEventListener( Event.COMPLETE, onLoginCredentials );
    
    	if ( grabber.isLoggedIn ) {
    
    		// Connect to Openfire
    		ui.visible = true;
    		connect( grabber.username, grabber.password );
    	}
    	else {
    
    		// Display login
    		displayLogin();
    	}
    }
    

    This method is also really simple. The LoginCredentialsGrabber checks to see if the user is logged in by grabbing the session cookies using PHP. PHP renders out data to the LoginCredentialsGrabber object and the data is parsed.

    Note: We will be writing the LoginCredentialsGrabber class next. All we need to do now is check to see if the user is logged in. If they are we display the user interface and call the connect method to connect to Openfire. We pass the user’s username and password into the connect method as required parameters. If the user is not logged in we display the Login Screen.


    Step 12: Grabbing Login Credentials

    Write the LoginCredentialsGrabber class.

    
    
    package {
    
    	import flash.events.Event;
    	import flash.events.EventDispatcher;
    	import flash.net.URLLoader;
    	import flash.net.URLRequest;
    	import flash.net.URLVariables;
    	import flash.net.URLRequestMethod;
    
    	public class LoginCredentialsGrabber extends EventDispatcher {
    
    		private static const PASSCODE:String = "letmein123";
    		private static const SOURCE:String = "http://localhost/mycontentsite/scripts/check_login.php&quot;;
    
    		private var _data:*;
    		private var _username:String;
    		private var _password:String;
    		private var _isLoggedIn:Boolean;
    
    		public function LoginCredentialsGrabber() {
    
    			super();
    		}
    
    		public function grab():void {
    
    			var loader:URLLoader = new URLLoader();
    			var req:URLRequest = new URLRequest( SOURCE + "?cb=" + new Date().time );
    
    			loader.addEventListener( Event.COMPLETE, onComplete );
    			loader.load( req );
    		}
    
    		private function onComplete( e:Event ):void {
    
    			e.target.removeEventListener( Event.COMPLETE, onComplete );
    			_data = e.target.data;
    			var results:URLVariables = new URLVariables( _data.toString() );
    
    			if ( results.login == "1" ) {
    
    				_isLoggedIn = true;
    				_username = results.username;
    				_password = results.password;
    			}
    			else {
    
    				_isLoggedIn = false;
    			}
    
    			dispatchEvent( new Event( Event.COMPLETE ) );
    		}
    
    		public function get data():* {
    
    			return _data;
    		}
    
    		public function get isLoggedIn():Boolean {
    
    			return _isLoggedIn;
    		}
    
    		public function get username():String {
    
    			return _username;
    		}
    
    		public function get password():String {
    
    			return _password;
    		}
    	}
    }
    

    We have two constants and four read-only properties before the constructor method. The SOURCE constant is a String that represents the location of the PHP script that checks to see if the user is logged in. We also store the passcode needed to execute the PHP script in a constant. When the grab method is called the URLLoader object loads the PHP script passing the passcode with the URLRequest and the script returns data back to flash. The data is a set of url variables that we can parse using the URLVariables class. If a user is logged in, the PHP script will give us the user’s username and password so that we can use this information to connect the user to Openfire. Finally we provide getter methods to grant read-only access to outside code.


    Step 13: Display the Login Screen

    Write the displayLogin method in the Document class(ChatApp.as).

    
    
    private function displayLogin():void {
    
    	// Displays the login screen
    	loginScreen.visible = true;
    	loginScreen.addEventListener( LoginManager.LOGIN, onLoggingIn );
    }
    

    We set the loginScreen‘s visible property to true and wait for the loginScreen to dispatch LOGIN event. Then the onLoggingIn method is called. Let’s write this method now.

    
    
    private function onLoggingIn( e:Event ):void {
    
    	ui.visible = true;
    	connect( loginScreen.manager.username, loginScreen.manager.password );
    }
    

    We make the user interface visible and then we call the connect method.

    Important: Notice that we are using the username and password from the loginScreen‘s manager object(loginScreen.manager) instead of the grabber object’s username and password as we did in the onLoginCredentials method.


    Step 14: Connecting to Openfire

    At last, we can write the connect method. The method accepts two required parameters: the first is the user’s username and the second is the users’s password.

    
    
    private function connect( username:String, password:String ):void {
    
    	connection = new XMPPConnection();
    	connection.username = username;
    	connection.password = password;
    	connection.server = SERVER;
    	connection.port = PORT;
    	connection.resource = RESOURCE;
    	connection.addEventListener( ConnectionSuccessEvent.CONNECT_SUCCESS, onConnected );
    	connection.addEventListener( LoginEvent.LOGIN, onLogin );
    	connection.addEventListener( DisconnectionEvent.DISCONNECT, onDisconnected );
    	connection.addEventListener( XIFFErrorEvent.XIFF_ERROR, onXiffError );
    	connection.addEventListener( IncomingDataEvent.INCOMING_DATA, onIncomingData );
    	connection.addEventListener( OutgoingDataEvent.OUTGOING_DATA, onOutgoingData );
    	connection.connect( XMPPConnection.STREAM_TYPE_FLASH );
    }
    

    Instantiate the XMPPConnection object and assign it to the connection variable. Set the user’s username and password along with the server(SERVER) and the resource(RESOURCE). Then add event listeners to the connection object. Finally we call the connect method on the XMPPConnection object.

    Write the following methods:

    
    
    private function onConnected( e:ConnectionSuccessEvent ):void {
    
    	trace( "connected" );
    }
    
    private function onLogin( e:LoginEvent ):void {
    
    	trace( "logged in" );
    	ui.connection = connection;
    	grabUserData();
    	startTimer();
    }
    
    private function onDisconnected( e:DisconnectionEvent ):void {
    
    	trace( "disconnected" );
    	loginScreen.visible = true;
    	ui.visible = false;
    	loginScreen.displayError( "disconnected" );
    }
    
    private function onXiffError( e:XIFFErrorEvent ):void {
    
    	trace("Error: " + e.errorMessage);
    	if ( loginScreen.visible ) loginScreen.displayError( e.errorMessage );
    }
    
    private function onIncomingData( e:IncomingDataEvent ):void {
    
    	trace( e.data.toString() );
    }
    
    private function onOutgoingData( e:OutgoingDataEvent ):void {
    
    	trace( e.data.toString() );
    }
    

    The onConnected, onIncomingData, and onOutgoingData methods can have many different uses but for this tutorial we will only use them to trace output so that we can debug our application when and if we need to(specifically if their is a problem connecting to the server). The onDisconnected method makes the loginScreen visible and displays an error to the user notifying them that their connection was lost. The onLogin method prepares the User Interface for XMPP chat by assigning the XMPPConnection object to the connection property within the ui object. This allows the UserInterface object to call methods directly from the XMPPConnection object through a reference. Now that the user is logged into the Jabber server(Openfire), we can start working toward logging the user into a chat room but first we need to identify exactly who our user is. We call the grabUserData method to do so. Finally we call the startTimer method.


    Step 15: Grabbing User Data

    By now the user’s session cookie is stored in the user’s browser and the user is logged into the Jabber server. Now we need to grab basic information about our user. We know the user’s username, so we can use it to access additional information about the user that is stored in our MySQL database. Create the grabUserData method in the Document class.

    
    
    private function grabUserData():void {
    
    	userData.addEventListener( Event.COMPLETE, joinRoom );
    	userData.grab( connection.username );
    }
    

    All of the magic happens within the UserDataGrabber class. All we have to do is call the grab method and listen for the COMPLETE event. Note that the grab method on the UserDataGrabber object accepts one parameter: the user’s username. Use the user’s username from the connection instance.

    This class wouldn’t have any magic right now because it doesn’t exist yet. Let’s write this class now. Create a new class called UserDataGrabber that extends flash.events.EventDispatcher.

    
    
    package {
    
    	import flash.events.Event;
    	import flash.events.EventDispatcher;
    	import flash.net.URLLoader;
    	import flash.net.URLRequest;
    	import flash.net.URLVariables;
    
    	public class UserDataGrabber extends EventDispatcher {
    
    		private static const SOURCE:String = "http://localhost/mycontentsite/scripts/grab_user_data.php&quot;; // Replace with your own php script
    
    		private var _data:*;
    		private var _uid:String;
    		private var _firstName:String;
    		private var _lastName:String;
    		private var _username:String;
    		private var _country:String;
    		private var _statusMessage:String;
    
    		public function UserDataGrabber() {
    
    			super();
    		}
    
    		public function grab( username:String ):void {
    
    			var loader:URLLoader = new URLLoader();
    			var req:URLRequest = new URLRequest( SOURCE + "?cb=" + new Date().time );
    			var vars:URLVariables = new URLVariables();
    
    			_username = username;
    			vars.username = username;
    			req.data = vars;
    			req.method = "POST";
    			loader.addEventListener( Event.COMPLETE, onComplete );
    			loader.load( req );
    		}
    
    		private function onComplete( e:Event ):void {
    
    			e.target.removeEventListener( Event.COMPLETE, onComplete );
    			_data = e.target.data;
    			trace( "User Data:\n" + data );
    			var user:XML = new XML( _data );
    			_uid = user.@id.toString();
    			_firstName = user.firstName.toString();
    			_lastName = user.lastName.toString();
    			_country = user.country.toString();
    			_statusMessage = user.statusMessage.toString();
    			dispatchEvent( new Event( Event.COMPLETE ) );
    		}
    
    		public function get data():* {
    
    			return _data;
    		}
    
    		public function get uid():String {
    
    			return _uid;
    		}
    
    		public function get firstName():String {
    
    			return _firstName;
    		}
    
    		public function get lastName():String {
    
    			return _lastName;
    		}
    
    		public function get username():String {
    
    			return _username;
    		}
    
    		public function get country():String {
    
    			return _country;
    		}
    
    		public function get statusMessage():String {
    
    			return _statusMessage;
    		}
    
    	}
    }
    

    First we create a const that stores the location to the PHP script as a string. We created the grab_user_data.php script earlier. This is the script that performs a query in the database using a specified username to fetch and echo out the user’s data as xml.

    Next we create our variables. I always place an underscore (_) in front of the name of any private or protected variable (property) that will be read-only – o,r in some rare cases, write-only. All of the variables in this class read-only. We use getter methods to permit read-only access to each variable.

    All variables are set when the xml data, which is rendered out from the php file, is parsed with the exception of the _username variable which is set from the username parameter of the grab method.

    Now for the grab method. Nothing complicating here. Create a new URLLoader object, a new URLRequest object and a new URLVariables object. The URLRequest constructor accepts one parameter, the url that you would like to load data from. In this case, the url is stored in the SOURCE constant. I’m sure by now you’ve noticed that the the string ?cb= concatenated with the current time has been concatenated with the SOURCE. The cb stands for cache buster. This prevents our script from being loaded from out of a cache (memory).

    Initialize URLRequest object and the URLVariables object. The URLVariables object holds that the username variable that the php script needs to perform a query in the database. This variable is passed along with the URLRequest. Call the URLLoader‘s load method and listen for the COMPLETE event to be dispatched from loader so that the onComplete event handler method can be called.

    In the onComplete method, create a new XML object. Pass the data assigned from the URLLoader object(e.target in this case) into the constructor’s parameter. Set the class’s variables and dispatch the COMPLETE event.


    Step 16: The LoginScreen Class

    Up to this point, we have assumed that the user is already logged in. If the user isn’t logged in we display the loginScreen. The LoginScreen class will have methods encapsulated within it that handle the user’s login status. Create the LoginScreen class. The class must extend the MovieClip class since it is linked to a library symbol of that type.

    
    
    package {
    
    	import flash.display.MovieClip;
    	import flash.events.MouseEvent;
    	import flash.events.Event;
    	import flash.events.ErrorEvent;
    	import flash.events.KeyboardEvent;
    	import
  2. Carlos Yanez says:
    October 13, 2011 at 8:37 pm

    It’s Premium tutorial time! In this tutorial, available exclusively to members of Tuts+ Premium, you’ll learn how to create your own version of the classic Whack-a-Mole game – only, our unfortunate creatures of choice will be worms. You’ll be able to modify the speed of the game and the hit boxes of the worms.


    Preview

    Let’s take a look at the final result we will be working towards:

    Click the worms to whack ‘em! You can alter the speed and the hit box size in the Options menu. Of course, if you follow the tutorial, you’ll have much more control over these settings.


    Active Premium Membership

    Activetuts+ Premium Membership

    We run a Premium membership system which periodically gives members access to extra tutorials, like this one! You’ll also get access to Psd Premium, Vector Premium, Audio Premium, Net Premium, Ae Premium, Cg Premium, Photo Premium, and the new Mobile Premium too. If you’re a Premium member, you can log in and download the tutorial. If you’re not a member, you can of course join today!

    Also, don’t forget to follow @envatoactive on twitter and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.


  3. Joseph Clover says:
    October 13, 2011 at 9:32 pm

    In this tutorial, we will draw a selection rectangle with the mouse (as seen in strategy games such as StarCraft and Command and Conquer), and we will also learn how to select units with the rectangle!


    Final Result Preview

    Let’s take a look at the final result we will be working towards:

    Click and drag with your mouse to draw a rectangle that will select any soldier that it touches.


    Step 1: The Setup

    If you are using Flash, create a new ActionScript 3.0 file with the size ’550 x 400′. However, if you are not using the Flash IDE and are using another such as FlashDevelop or Flash Builder, this tutorial contains the SWC files so you can use MovieClips from within your IDE of preference. If you are curious on how to import MovieClips with your IDE, check out the Beginner’s Guide to FlashDevelop and Beginner’s Guide to FDT!

    I should also note that I have included the FLA file in case you do not wish to draw any of your own material.


    Step 2: Creating the Document Class

    Ok, now you may be a little confused if you haven’t really worked with classes before. If you wish to learn more about why classes are important in programming, check out this article by kirupa, or this guide to the document class.

    Create a new ‘ActionScript 3.0 Class’ and give it the name ‘SelectionDemo’. When the file has been created, save it as ‘SelectionDemo.as’. You should save files all the time. I can not stress this enough but the amount of times I have forgot to save work that I have done and lost it all doesn’t bear thinking about. So please, do save the files!

    If you are using an IDE that generates the code for you when you create the class, you should have most of the code below. However, you must still add the lines that I have highlighted:

    
    
    package
    {
    
    	import flash.display.MovieClip;
    
    	public class SelectionDemo extends MovieClip
       	{
    
    		public function SelectionDemo()
            	{
    
    		}
    
    	}
    
    }
    

    We are not done yet however! If you are using the Flash IDE, navigate to the ‘Properties Panel’ and set the ‘DocumentClass’ to ‘SelectionDemo’. If you are wondering what that does, it means that when your application/game is run by the Flash Player, this Class will be the Main class that manages the game. Cool, huh?

    Exporting the Unit

    Run the program; if you get no errors then you should be good to go!


    Step 3: Creating the Rectangle

    Now we should be ready to make the Rectangle! This part will contain a few functions, that’s all. Below is the code for drawing the rectangle:

    
    
    package  {
    
    	// IMPORTING THE CLASSES WE NEED
    	import flash.display.MovieClip;
    	import flash.events.MouseEvent;
    	import flash.geom.Rectangle;
    	import flash.display.Sprite;
    
    	public class SelectionDemo extends MovieClip {
    
    		public var selectionRect:Rectangle; // Will hold the data for our rectangle.
    		public var selectionSprite:Sprite = new Sprite(); // Making a new Sprite to draw the rectangle.
    
    		public function SelectionDemo() {
    
    			//Adding listeners
    			stage.addEventListener(MouseEvent.MOUSE_DOWN, SetStartPoint);
    
    		}
    
    		public function SetStartPoint( me:MouseEvent ):void
    		{
    
    			selectionRect = new Rectangle( stage.mouseX, stage.mouseY ); // Creating the selection rectangle.
    
    		}
    
    	}
    
    }
    

    Now, it’s kind of useless having a rectangle that we cannot see, right? Exactly, so let’s get started!


    Step 4: Drawing the Rectangle

    Great, now we must draw the rectangle to the screen using the selectionSprite variable you seen in the last snippet. Why use a sprite, you ask? All Sprites contain a graphics object, which in turn contains a method called drawRect() this allows us to easily draw a rectangle dynamically in AS3.

    Below, I have placed the code for drawing the rectangle, with comments:

    
    
    package  {
    
    	// IMPORTING THE CLASSES WE NEED
    	import flash.display.MovieClip;
    	import flash.events.MouseEvent;
    	import flash.geom.Rectangle;
    	import flash.display.Sprite;
    	import flash.events.Event;
    
    	public class SelectionDemo extends MovieClip {
    
    		public var selectionRect:Rectangle; // Will hold the data for our rectangle.
    		public var selectionSprite:Sprite = new Sprite(); // Making a new Sprite to draw the rectangle.
    		public var isMouseHeld:Boolean; // Will tell us whether the mouse button is Up/Down
    
    		public function SelectionDemo() {
    
    			//Initializing
    
    			isMouseHeld = false; // The mouse is not held yet.
    			stage.addChild(selectionSprite); // Adding the selectionSprite to the stage.
    
    			stage.addEventListener(MouseEvent.MOUSE_DOWN, SetStartPoint); // Listen for mouse hold.
    			stage.addEventListener(MouseEvent.MOUSE_UP, RemoveRectangle); // Listen for mouse release.
    			stage.addEventListener(Event.ENTER_FRAME, UpdateGame); // Run this function every frame (24 FPS).
    
    		}
    
    		public function SetStartPoint( me:MouseEvent ):void
    		{
    
    			selectionRect = new Rectangle( stage.mouseX, stage.mouseY ); // Creating the selection rectangle.
    			isMouseHeld = true; // The mouse is now held.
    
    		}
    
    		public function RemoveRectangle( me:MouseEvent ):void
    		{
    
    			isMouseHeld = false; // The mouse is no longer held.
    
    		}
    
    		public function UpdateGame( e:Event ):void
    		{
    
    			selectionSprite.graphics.clear(); // Clear the rectangle so it is ready to be drawn again.
    
    			if( isMouseHeld )
    			{
    				selectionRect.width = stage.mouseX - selectionRect.x; // Set the width of the rectangle.
    				selectionRect.height = stage.mouseY - selectionRect.y; // Set the height of the rectangle.
    				selectionSprite.graphics.lineStyle(3, 0x3B5323, 0.6); // Set the border of the rectangle.
    				selectionSprite.graphics.beginFill( 0x458B00, 0.4 ); // Set the fill and transparency of the rectangle.
    				selectionSprite.graphics.drawRect( selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height ); // Draw the rectangle to the stage!
    				selectionSprite.graphics.endFill(); // Stop filling the rectangle.
    			}
    
    		}
    
    	}
    
    }
    

    If you have that code, run your application and watch it work!


    Step 5: Draw a Unit

    In flash, create a new MovieClip and draw a Unit. In the first frame, just draw a unit; in the second frame, add a green circle under the unit or anything that lets the player know that the unit has been selected. It should look something like this:

    Unit MovieClips

    I also just drew a quick grassy background on the stage to make it look nice :)


    Step 6: Exporting the Unit

    Now you have created the MovieClip, right-click the symbol in your Library and select Properties. Check the boxes that say ‘Export to ActionScript’ and ‘Export in Frame 1′. Then, give it the name ‘Unit’. Your properties should look something like this:

    Exporting the Unit

    Note: when you click ‘OK’, you may get a warning because no such class “Unit” exists yet. If so, click OK and we shall fix this now by making a new class!


    Step 7: Creating the Unit Class

    Remember before when you exported the Unit MovieClip? This is where we create the class for that MovieClip. So create a new ActionScript class file named ‘Unit.as’ and place this code within the class:

    
    
    package
    {
    
    	import flash.display.MovieClip;
    
    	public class Unit extends MovieClip
    	{
    
    		public var isActive:Boolean; // Tells us whether the unit is selected or not.
    
    		public function Unit()
    		{
    
    			isActive = false; // The unit has not been selected yet.
    			gotoAndStop(1); // Go to and stay on the first frame ( no selection ring ).
    
    		}
    
    	}
    
    }
    

    Ahead, comrades!


    Step 8: Placing the Units

    Now it is time to add the Units to the stage and give them a position. Also, we are going to place each Unit in an ‘Array’. An array is basically a list which allows us to access the things inside it using an index. A great example of arrays is right at Republic of Code; they’ve also been explained here in AS3 101: Arrays.

    Here is the updated code for ‘SelectionDemo.as’. First, we add a new public Array called unitList just after the other variables:

    
    
    public var selectionRect:Rectangle; // Will hold the data for our rectangle.
    public var selectionSprite:Sprite = new Sprite(); // Making a new Sprite to draw the rectangle.
    public var isMouseHeld:Boolean; // Will tell us when the mouse is Up/Down
    public var unitList:Array; // All the units will be held in here
    

    Then, we update the Main function by placing a function called PlaceUnits(15);. We will create this in a moment.

    
    
    public function SelectionDemo() {
    
    	//Initializing
    	isMouseHeld = false; // The mouse is not held yet.
    	stage.addChild(selectionSprite); // Adding the selectionSprite to the stage.
    	PlaceUnits(15); // Calling a function to place the units on the stage.
    
    	//Adding listeners
    	stage.addEventListener(MouseEvent.MOUSE_DOWN, SetStartPoint); // Listen for mouse hold.
    	stage.addEventListener(MouseEvent.MOUSE_UP, RemoveRectangle); // Listen for mouse release.
    	stage.addEventListener(Event.ENTER_FRAME, UpdateGame); // Run this function every frame (24 FPS).
    
    }
    

    Time to make the function! Ok, we will place this function after the UpdateGame(e:Event):void function and what this function will do is add the amount of units you specified in the brackets to the stage. We will also add the units to the list and give them random positions on the stage while making sure they cannot spawn off the stage.

    
    
    public function PlaceUnits( amount:int ):void
    {
    
    	unitList = new Array(); //Making a new Array(list) to hold all the Units.
    
    	for( var i:int = 0; i < amount; i++ ) // Run whatever is inside the brackets 'amount' times.
    	{
    		var unit:Unit = new Unit(); // Creating a new unit.
    		unit.x = Math.random() * (550 - unit.width); // Setting a random X Position.
    		unit.y = Math.random() * (400 - unit.height); // Setting a random Y Position.
    		stage.addChild(unit); // Adding the new unit to the stage.
    		unitList.push( unit ); // Placing the unit in the Array(list).
    	}
    
    }
    

    When you run this, you should have 15 units randomly placed. Time to move on and program the unit selection.


    Step 9: Z-Sorting!

    When you run the game, you will probably see that there is a strange overlap of the units. Let’s fix it! This is extremely easy and will only require a small change to the PlaceUnits() function.

    Basically, what we need to do is add all the units to an Array (list) and then sort the list based on the Y (vertical position) of the units. The lower the Y property, the further backwards it should be. We will change the PlaceUnits() function to:

    
    
    public function PlaceUnits( amount:int ):void
    {
    
    	unitList = new Array(); //Making a new Array(list) to hold all the Units.
    
    	for( var i:int = 0; i < amount; i++ ) // Run whatever is inside the brackets 'amount' times.
    	{
    		var unit:Unit = new Unit(); // Creating a new unit.
    		unit.x = Math.random() * (550 - unit.width); // Setting a random X Position.
    		unit.y = Math.random() * (400 - unit.height); // Setting a random Y Position.
    		unitList.push( unit ); // Placing the unit in the Array(list).
    	}
    
    	unitList.sortOn("y", Array.NUMERIC); // Sorting the list in order of the 'y' properties!
    
    	for( var j:int = 0; j < amount; j++ ) // We will run through this loop again to add the units.
    	{
    		stage.addChild( unitList[j] ); // This adds the 'sorted' unit to the stage.
    	}
    
    }
    

    There we have it… no more overlaps!


    Step 10: Selecting Units

    Now, each frame we will check whether any units have been selected; if they have then we will make their selection ring appear.

    Edit the UpdateGame() function to the following:

    
    
    public function UpdateGame( e:Event ):void
    {
    
    		selectionSprite.graphics.clear(); // Clear the rectangle so it is ready to be drawn again.
    
    		if( isMouseHeld )
    		{
    			selectionRect.width = stage.mouseX - selectionRect.x; // Set the width of the rectangle.
    			selectionRect.height = stage.mouseY - selectionRect.y; // Set the height of the rectangle.
    			selectionSprite.graphics.lineStyle(3, 0x3B5323, 0.6); // Set the border of the rectangle.
    			selectionSprite.graphics.beginFill( 0x458B00, 0.4 ); // Set the fill and transparency of the rectangle.
    			selectionSprite.graphics.drawRect( selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height ); // Draw the rectangle to the stage!
    			selectionSprite.graphics.endFill(); // Stop filling the rectangle.
    			CheckForSelection(); // We will check to see if any units have been selected.
    		}
    
    }
    

    As you can see, we added a function called CheckForSelection(). Let’s create that function after the others:

    
    
    public function CheckForSelection():void
    {
    
    	for each( var unit:Unit in unitList ) // For every unit that is in the Unit Array(list)...
    	{
    
    		if( unit.hitTestObject( selectionSprite ) ) // If the selectionSprite is touching the Unit.
    		{
    			unit.select(); // Make the unit selected.
    		}
    		else
    		{
    			unit.deselect(); // De-select the unit.
    		}
    
    	}
    
    }
    

    As you can see in the highlighted lines, the select() and deselect() functions do not exist. Open up ‘Unit.as’ and let’s put them in:

    
    
    package
    {
    
    	import flash.display.MovieClip;
    
    	public class Unit extends MovieClip
    	{
    
    		public var isActive:Boolean; // Tells us whether the unit is selected or not.
    
    		public function Unit()
    		{
    
    			isActive = false; // The unit has not been selected yet.
    			gotoAndStop(1); // Go to and stay on the first frame ( no selection ring ).
    
    		}
    
    		public function select():void
    		{
    			isActive = true; // The unit is selected.
    			gotoAndStop(2); // Show the ring.
    		}
    
    		public function deselect():void
    		{
    			isActive = false; // The unit is not selected.
    			gotoAndStop(1); // Do not show the ring.
    		}
    
    	}
    
    }
    

    Run the game and all should be working!


    Step 11: Challenges

    Now that you have sucessfully completed this tutorial, I now have some challenges for you to follow. Feel free to skip them, but following them will help you learn.

    Beginner:

    • Spawn 25 units instead of 15
    • Change the colour and border of the rectangle

    Intermediate:

    • All of the above
    • Add a TextField under the unit and make it display the unit’s name ONLY when selected.
    • Play a sound when a unit is selected

    Advanced:

    • All of the above
    • When the player clicks a position, make selected units move to that position. (Hint: use an Array to know what units are selected.)

    Only do the challenges you feel comfortable with!


    Conclusion

    Thankyou for reading this tutorial and I hope you learned something new. Also, I would also like to thank Tomas Banzas for the art he did!

    If you have completed some of the challenges and would like to show off the results, please post a link in the comments – I’d love to see them!


  4. David Appleyard says:
    October 13, 2011 at 10:17 pm

    We’re immensely pleased to announce that, after months of waiting, a new member of the AppStorm network has landed: Windows.AppStorm! Read on to find out more about what this fantastic new site has to offer…

    Complimenting Mac.AppStorm, Web.AppStorm, iPhone.AppStorm, iPad.AppStorm, and Android.AppStorm, our new Windows site will be offering reviews and roundups covering the entire Windows ecosystem, including Windows Phone 7, and games, along with tips and tricks to get the most from Windows.

    We know that many Tuts+ readers use Windows devices every day and want to provide you with a fantastic resource for everything Windows related. We’re incredibly excited to bring you excellent Windows content of the high quality you’ve come to expect from AppStorm – daily reviews, how-to’s, roundups, news, and opinion.


    Subscribe and Stay Up-to-Date!

    We have some absolutely fantastic posts lined up over the coming weeks, and we’d hate for you to miss out… There are a few different ways to subscribe to Windows.AppStorm – hopefully one of the following options will work for you!

    • Subscribe to our RSS feed
    • Follow @windowsappstorm on Twitter
    • Sign up for Email Updates
    • Stay up-to-date on Facebook

    Get Stuck In

    Windows Reviews

    As a treat for all newcomers to Windows.AppStorm here are a some links to articles published in the interim, you won’t be disappointed!

    • 90+ Incredibly Useful Windows 7 Apps & Tips
    • 25 Absolutely Gorgeous Windows Phone 7 Apps
    • 12 Fantastic Windows Alternatives to Mac & iLife Apps
    • 25 Beautifully Designed Windows Apps
    • The AppStorm PC Builder’s Guide – Summer 2011

    Head over to Windows.AppStorm…



  5. Activetuts+ Editor says:
    October 13, 2011 at 10:36 pm

    Are you an RIA developer? Do you make Flash games or Unity games? Do you know some cool tips or techniques that you’re always surprised your colleagues don’t know? You sound like the perfect person to contribute to Activetuts+!

    We’re on the lookout for new writers and screencasters, so read on to find out more…


    What We’re Looking For

    Right now, we’re putting out a general call for Quick Tips.

    A Quick Tip is a short article or screencast – we’re talking about a thousand words, five steps, or a few minutes – that covers one bite-size subject.

    For example, a Quick Tip could cover a small library, one or two components, or a technique – each a useful topic for which a full tutorial would be overkill.

    We’ve also published Quick Tips that cover individual design patterns or error messages. You can see a full list on our Tips page.

    As you can see, we’re really low on Unity, Silverlight, and HTML5 Quick Tips, and even our Flash stockpile has dried up over the last few weeks. We pay $50 for every Quick Tip that we publish, and if you submit an idea now there’s a good chance we’ll even be able to publish it this month!


    Pitch a Quick Tip

    Got an idea? Great! Pitch it to us (in about a paragraph or so) using the following form:

    Pitch your Quick Tip idea

    You can read more about the sort of topics we cover on Activetuts+ in our Author Guidelines – though if you’ve been reading the site for a while, you’ve probably got a good idea already.

    As long as you’ve got an original, useful idea and can string a decent English sentence together, there’s a high chance your Quick Tip pitch will be accepted, so don’t be shy.

    (For longer-form tutorial pitches, we do prefer to see some example of your previous work: projects you’ve worked on, blog posts you’ve written, presentations you’ve given, and so on. For Quick Tips, this isn’t necessary – although we’re still interested!)

    We’re looking forward to reading your pitches.


  6. Jason McElwaine says:
    October 13, 2011 at 11:22 pm

    ActiveDen’s latest community-organised contest has just taken place. The theme was “Widget/Apps” and we had a number of outstanding entries, with $1,450 prize money up for grabs. We had a lot of fun with the contest and are thrilled to be offering the entries for free exclusively here on Activetuts+!


    The Winner: Lion’s NeatList AIR App

    Lion’s NeatList is a cool AIR app to keep track of your daily tasks. It also took home $1,000 for first prize!

    NeatList AIR App
    Download This Entry
    Demo View It Online

    Check out Lion’s ActiveDen Portfolio


    RimV’s Flickr Box AIR App

    Flickr Box is a cross platform AIR mobile app that runs on Android, iOS and RIM Playbook. Built by 3D legend RimV.

    Flickr Box AIR App
    Download This Entry
    Demo View It Online

    Check out RimV’s ActiveDen Portfolio


    FlashEdge’s 3D Flickr Gallery

    Coming in third was this amazing 3D gallery built by FlashEdge.

    3D Flickr Gallery
    Download This Entry
    Demo View It Online

    Check out FlashEdge’s ActiveDen Portfolio


    Kontrast’s Survey/Feedback/Quiz Form

    Build your own interactive form with this great entry from Kontrast.

    Flash Feedback Form
    Download This Entry
    Demo View It Online

    Check out Kontrast’s ActiveDen Portfolio


    RubenBristian’s Media Love Widget

    Create an online YouTube Poll with this awesome widget by RubenBristian.

    Flash Poll Widget
    Download This Entry
    Demo View It Online

    Check out Ruben’s ActiveDen Portfolio


    PezFlash’s Snakes & Apples

    Catch the dreaded Apples with this fun game by PezFlash.

    Flash Game
    Download This Entry
    Demo View It Online

    Check out PezFlash’s ActiveDen Portfolio


    RabidFlash’s YouTube Comment Widget

    Load YouTube comments with this cool widget by RabidFlash.

    Youtube Comments
    Download This Entry
    Demo View It Online

    Check out RabidFlash’s ActiveDen Portfolio


    Zefs’ Envato API AIR App

    View ActiveDen forum threads, the latest featured files and so much more with this awesome AIR app from Zefs.

    Envato API AIR App
    Download This Entry
    Demo View It Online

    Check out Zefs’ ActiveDen Portfolio


    ZoomIt’s Flash Testimonial Rotator

    Create a cool Flash Testimonial Rotator with this XML driven widget from Zoomit.

    Flash Testomonials
    Download This Entry
    Demo View It Online

    Check out ZoomIt’s ActiveDen Portfolio


    Sergibh’s Flash Horoscope Widget

    Present daily horoscopes with this neat component from Sergibh. Mobile App included.

    Flash Horoscope Component
    Download This Entry
    Demo View It Online

    Check out Sergibh’s ActiveDen Portfolio


    CodingJack’s ChangeStar Envato API AIR App

    Track your Envato marketplace file ratings with this easy to use AIR app.

    Envato API AIR App
    Download This Entry
    Demo View It Online

    Check out CodingJack’s ActiveDen Portfolio


    More Info

    Some entries do not include ActionScript source. Feel free to use the entries in your client projects but resale of any kind is not permitted.

    Want to read more about the fun we had? Check out the competition threads over on the ActiveDen forums here and here.


    The Competition Sponsors

    Big thanks to all of these guys for sponsoring the competition:

    Envato

    BitFade   PezFlash   ZoomIt

    LGLab   FlashEdge   Parker and Kent

    Special thanks to Zefs and PatrickJansen for their amazing work on the competition banner!


  7. Michael James Williams says:
    October 14, 2011 at 12:11 am

    Last reminder: These courses start tomorrow!

    Artificial intelligence is a popular programming topic, with obvious applications in game development, and machine learning is a branch of AI focused on creating code that can learn based on past experiences. Databases are perhaps not as stereotypically cool to study, but still very important for any programmer. Stanford University is offering free, online, undergraduate-level courses in each subject. Read on to find out how you can enroll…


    Are These Proper University Courses?

    Yes and no. The courses are online versions of those which actual Stanford students will take (other than being delayed by a couple of weeks), and so will be taught and graded at university level – but studying online won’t get you a Stanford certificate or university credit, so you won’t be able to put “attended Stanford university” on your CV.

    Still, you’ll get the lectures (in video format) and the homework and exams (which you can submit and have graded electronically), so you’ll be learning the same material as the Stanford students!


    What’s Being Taught?

    Three courses are on offer:

    Introduction to Artificial Intelligence

    You’ll need to brush up on your probability and linear algebra skills (perhaps you could try Khan Academy).

    For more information, and to enroll, visit ai-class.com.

    Machine Learning

    Again, you’ll need to be familiar with basic probability theory and linear algebra.

    For more information, and to enroll, visit ml-class.com.

    Introduction to Databases

    This isn’t just about relational databases and SQL; it also covers XML (including XPath, XQuery and XSLT), UML, and “NoSQL” systems.

    For more information, and to enroll, visit db-class.com.


    Is There a Deadline?

    Yes. This isn’t like Tuts+ Premium, where you sign up and have access to courses and tutorials to read at your leisure; everybody taking the online course starts at the same time, with new lectures released each week and homework assignments due in on certain dates.

    The courses all start on the 10th of October, and end in mid-December, so you should enroll today if you are interested at all.

    If you don’t have time, or simply don’t want, to do the homework assignments, you can opt to take the “basic track” – in the AI course at least – which has the same material, but without the homework or exams. You can downgrade to this at any time, so there’s no harm in signing up to the “advanced track” for now.

    If you’re interested in programming, I hope you’ll sign up to at least one of the courses – not just because of the content, but because this should be a really interesting experiment in online learning.


  8. John Reyes says:
    October 14, 2011 at 12:26 am
    This entry is part 2 of 2 in the series The Power of Finite State Machines

    Here’s the concluding part of our tutorial about creating a multi-state car using a finite state machine. In this part, you will see how easy it is to add more states as well as new features to the car. When finished, you’ll know exactly how to structure control for your FSM Object, which will be demonstrated when we add animation and sound.


    Final Result Preview

    Check out the final result we’ll be working towards:


    Step 1: Continuing Where We Left Off – Additional States

    Here’s the State Transition Table (STT) we drew in the first part of the tutorial:

    Finite state machine design pattern in AS3

    When you need to add more states, always start with your STT. Here are the new states we’ll be working towards, with room to add the new actions required:

    Finite state machine design pattern in AS3

    Try putting in the right actions for the newly added states.

    I assume it’s been a while since you read the first part of this tutorial, so take your time. Once you finish, compare your STT with the image shown below.

    Finite state machine design pattern in AS3

    Step 2: Retouching the IState Interface

    The very next thing you need to do is add the two new state actions to your IState interface.

    Open IState.as in FD and include the code below. I would add it just after the driveForward() method.

    
    
    function driveBackward ():void;
    function driveReallyFast ():void;
    

    Now run the application and see what happens next.

    Finite state machine design pattern in AS3

    Any modification to the IState inteface needs to be reflected in all the classes that implement it. This is the great advantage of using an interface; I really like how it reminds you to add them for each existing state class.

    Add the following code to all the existing state classes. Put it right below the driveForward() method. Let’s have them call print(this + "apply action here") to remind us that we need to get back to them after creating the other two state classes.

    
    
    public function driveBackward ():void
    {
    	_car.print (this + " apply action here");
    }
    
    public function driveReallyFast ():void
    {
    	_car.print (this + " apply action here");
    }
    

    You shouldn’t get anymore errors when you run the application again.


    Step 3: Creating the Two State Classes

    Open the Car class, navigate to the initializeStates() method and add the code below.

    
    
    _engineDriveBackwardState = new EngineDriveBackward (this);
    _engineDriveReallyFastState = new EngineDriveReallyFast (this);
    

    Neither the variables nor the classes exist but we’ll fix that. First, get inside the word “EngineDriveBackward”, then press “CTRL + SHIFT + 1″; highlight “Create new class” then press “ENTER”.

    Finite state machine design pattern in AS3

    Make sure to match all the info above before you click “OK”. You should have something similar to the code below. Don’t forget to add the _car variable before the constructor and add that “$” symbol for the parameter. Then do the assignment inside the constructor.

    
    
    package com.activeTuts.fsm
    {
    	public class EngineDriveBackward implements IState
    	{
    
    		private var _car:Car;
    
    		public function EngineDriveBackward($car:Car)
    		{
    			_car = $car;
    		}
    
    		/* INTERFACE com.activeTuts.fsm.IState */
    
    		public function turnKeyOff():void
    		{
    
    		}
    
    		public function turnKeyOn():void
    		{
    
    		}
    
    		public function driveForward():void
    		{
    
    		}
    
    		public function driveBackward():void
    		{
    
    		}
    
    		public function driveReallyFast():void
    		{
    
    		}
    
    		public function reFuel():void
    		{
    
    		}
    
    		public function update($tick:Number):void
    		{
    
    		}
    
    		public function toString():String
    		{
    
    		}
    
    	}
    
    }
    

    Okay. Now go back to the Car class where we left off and put your cursor inside the _engineDriveBackwardState variable and press “CTRL + SHIFT + 1″; choose “Declare private variable” then hit “ENTER”. This will add it as the last variable before the constructor. Change its type to “IState”. I would move it to where the other state variables are but that’s optional.

    Follow the same procedure to create the EngineDriveReallyFast class.

    You’ll get an error if you run the application about the toString() method for the two new classes not returning a value.

    The Car will also need to provide external access to control for the two new actions. Add the code below after the driveForward() method.

    
    
    public function driveBackward (e:Event = null):void
    {
    	_currentState.driveBackward ();
    }
    
    public function driveReallyFast (e:Event = null):void
    {
    	_currentState.driveReallyFast ();
    }
    

    Lastly, add the explicit getters at the lower section of your Car class just above the changeState() method.

    
    
    public function getEngineDriveBackwardState ():IState { return _engineDriveBackwardState; }
    
    public function getEngineDriveReallyFastState ():IState { return _engineDriveReallyFastState; }
    

    Step 4: The EngineDriveBackward State

    Refer back to your STT and start adding code from the top of the IState method implementations. You’ll run into a snag somewhere but try to do it anyway. You may also use your own words for the print() method. Once you finish, compare your code with the class listing below.

    
    
    package com.activeTuts.fsm
    {
    	public class EngineDriveBackward implements IState
    	{
    		private var _car:Car;
    
    		public function EngineDriveBackward($car:Car)
    		{
    			_car = $car;
    		}
    
    		/* INTERFACE com.activeTuts.fsm.IState */
    
    		public function turnKeyOff ():void
    		{
    			_car.print ("click... rolling to a stop in reverse...the engine has been turned off.");
    			_car.changeState (_car.getEngineOffState ());
    		}
    
    		public function turnKeyOn ():void
    		{
    			_car.print ("you're driving in reverse, no need to crank the ignition!");
    		}
    
    		public function driveForward ():void
    		{
    			_car.print ("click, switching from reverse to drive...");
    			_car.changeState (_car.getEngineDriveForwardState ());
    		}
    
    		public function driveBackward ():void
    		{
    			_car.print ("...already driving in reverse.");
    		}
    
    		public function driveReallyFast ():void
    		{
    			_car.print ("click, changing gears from reverse to turbo!!!");
    			_car.changeState (_car.getEngineDriveReallyFastState ());
    		}
    
    		public function reFuel ():void
    		{
    			if (_car.hasFullTank () == false)
    			{
    				_car.print ("stopping the car from driving in reverse, changing gear to park, turning the key to the off position, getting out of the car and adding "
    				+ Number (_car.refillWithFuel ()).toFixed (2) + " gallon(s) of fuel.");
    				_car.changeState (_car.getEngineOffState ());
    			}
    		}
    
    		public function update ($tick:Number):void
    		{
    			_car.engineTimer += $tick;
    
    			if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    			{
    				_car.engineTimer -= Car.ONE_SIXTH_SECONDS;
    
    				_car.consumeFuel (Car.REVERSE_FUEL_CONSUMPTION);///25 seconds gas supply
    			}
    		}
    
    		public function toString ():String
    		{
    			return 'driving backward';
    		}
    
    	}
    
    }
    

    The update() method requires an additional property for the Car. Place your cursor inside the REVERSE_FUEL_CONSUMPTION, press “CTRL + SHIFT + 1″, choose “Declare constant”, then hit “ENTER”. This adds the constant at the top of the Car class with the default type of String. Change its type to Number and assign it a value of .0066. This gives 25 seconds of driving in reverse with a full tank of one gallon. You may move it to be with the other fuel consumption constants.

    While you’re there, you might as well add the TURBO_FUEL_CONSUMPTION constant with the value of .016 (10 seconds on turbo for one gallon). We’ll use that later for the EngineDriveReallyFast state class.

    It’s that simple. Go ahead and finish up the EngineDriveReallyFast class. If you get stuck, go back and check with your STT. Or you can just open the completed class inside the “StatePatternPartial2″ folder included with the source download and see what you missed.


    Step 5: More Testing

    Add the new state actions (_car.driveBackward() and _car.driveReallyFast() into the test sections in your “Main” class then run the app.

    Check your print out against the test actions if they match.

    Notice where it says “off apply action here”? That tells us we forgot to get back and add behavior for the newly added Car functions of the other state classes. Go back to each of them and apply the behavior based on your STT.

    The app should run perfectly now. If you like, you can still match your work with the classes included with the source download. As mentioned earlier, they’re inside the “StatePatternPartial2″ folder.


    Step 6: Adding Media Assets

    With the source download is a folder named “media”, drag that into the “bin” folder of your CarFSM project. It contains all the sound effects and graphics.

    Next, also with the source download, you’ll find a folder named “code”, get inside that and into the “StatePatternComplete” folder. Don’t touch anything except the “Media.as” class. Drag it into your “com.activeTuts.fsm” folder together with the rest of the classes you’ve made.

    It’s important you know how this class works so you can add to it later should you choose to try the homework of adding one other car functionality of returning back to park. So go over it at least once; I’ll also explain its responsibilities:

    It takes care of pretty much everything media-wise, from embedding media assets to allowing control over them. The first thing it does is embed all graphic and sound files inside the “media” folder. Animation, visual output, and sound are all controlled by the different state classes through the car instance which in turn passes the request to the media instance. When instantiated, this class does five things – It adds itself as a child of the car, it creates the car’s visual assets, sounds, buttons, and then visual output.

    Sound manipulation is usually handled with two functions; for example, when the car is running, if you select the “drive” radio button, the car calls playParkToDrive() which plays the sound of the car increasing power, then playPeakDrive() when the sound finishes for constant speed driving sound.


    Step 7: Extending the Car’s Responsibilities – Variables

    Starting at the top of the Car class, add the following code after the _currentState variable declaration. These properties came up as features were added to the Car. For example, I wanted to bring back the gear to park after one second when the car is either off or out of fuel so there it is. Another example would be cleaning up the visual output display after 10 seconds of printing a statement.

    
    
    public static const ONE_SECOND:int = 1; //used to reposition the gear to park when off/out of fuel after one second
    
    private static const TEN_SECONDS:int = 10; //triggers clean up for visual output
    
    private var _cleanUpTimer:Number = 0; //visual output clean up timer
    
    private var _angle:Number = 0; //for the car body animation when engine is on and parked
    
    private var _reallyFastRollSpeed:Number = 63; //roll speed when on turbo
    private var _driveRollSpeed:Number = 35; //roll speed when on drive
    private var _reverseRollSpeed:Number = -15; //roll speed when on reverse
    private var _currentRollSpeed:Number = 0; //changes based on target roll speed see last three properties above, used for wheel rotation animation
    
    private var _carBlurEffect:Sprite; //turbo speed blur effect
    
    private var _media:Media; //reference to the Media class for visual/output/sound manipulation
    

    Be sure to add _media = new Media (this); inside the init() method right after the initializeStates() method call.


    Step 8: Extending the Car’s Responsibilities – Car Functions

    Now get inside the update() method and change it to match the code that follows.

    
    
    public function update ($tick:Number):void
    {
    	_currentState.update ($tick);
    
    	_media.setTitle (_currentState.toString ());
    
    	if (_currentState == _engineOutOfFuelState || _currentState == _engineOffState)
    	{
    		if (_currentRollSpeed == 0) //or add this test for the update method of the two states
    		{
    			_media.stopRollSound ();
    		}
    	}
    
    	//clean up the output panel after 10 seconds
    	if ((_cleanUpTimer += $tick) >= TEN_SECONDS)
    	{
    		_cleanUpTimer -= TEN_SECONDS;
    		_media.cleanUp ();
    	}
    }
    

    Remember – the changes in the methods, however complex, only manipulate sound and animation. If you plan on doing the homework later (allowing the car to return to park from any of the drive states), here’s the method you need to work on. Paste it before the turnKeyOn() method. This method gets called when the “park” radio button is clicked (if enabled). See addButtons() in the Media class.

    
    
    public function returnToPark (e:Event = null):void
    {
    	//homework
    }
    

    From turnKeyOn() all the way down to reFuel() add _cleanUpTimer = 0 before the current state delegation. And that’s how the visual output panel gets cleared after 10 seconds.

    consumeFuel() and refillWithFuel() also need to be updated, as shown here:

    
    
    public function consumeFuel ($consumption:Number):void
    {
    	if ((_fuelSupply -= $consumption) <= 0)
    	{
    		_fuelSupply = 0;
    		_cleanUpTimer = 0;
    
    		stopEngine ();
    
    		if (currentRollSpeed != 0)
    		{
    			playRoll ();
    			print ("the engine has stopped, no more fuel to run...rolling to a stop.");
    		}
    		else print ("the engine has stopped, no more fuel to run...");
    
    		_media.setColor (0xFF0000);
    
    		changeState (_engineOutOfFuelState);
    	}
    }
    
    public function refillWithFuel ():Number
    {
    	if (_currentRollSpeed != 0) playRoll ();
    
    	var neededSupply:Number = _fuelCapacity - _fuelSupply;
    	_fuelSupply += neededSupply;
    	_media.setColor ();
    	return neededSupply;
    }
    

    I just kept adding whatever feature I could think of to show the State Pattern’s flexibility.

    Replace what’s inside the print() method with _media.print ($text);.


    Step 9: Extending the Car’s Responsibilities – Sound Control

    Go one line after the toString() method at the bottom of the class and paste the list below.

    
    
    ///Sound control
    public function startEngine ():void
    {
    	_media.startEngine ();
    }
    
    public function startEngineNoFuel ():void
    {
    	_media.startEngineNoFuel ();
    }
    
    public function startEngineWhileRunning ():void
    {
    	_media.startEngineWhileRunning ();
    }
    
    public function keyOffClick ():void { _media.keyOffClick (); }
    
    public function stopEngine ():void
    {
    	_media.stopEngine ();
    }
    
    public function playParkToDrive (e:Event = null):void
    {
    	_media.playParkToDrive (e);
    }
    
    public function playDriveToTurbo (e:Event = null):void
    {
    	_media.playDriveToTurbo (e);
    }
    
    public function playParkToReverse (e:Event = null):void
    {
    	_media.playParkToReverse (e);
    }
    
    public function playReverseToDrive ():void
    {
    	_media.playReverseToDrive ();
    }
    
    public function playDriveToReverse ():void
    {
    	_media.playDriveToReverse ();
    }
    
    public function playReverseToTurbo ():void
    {
    	_media.playReverseToTurbo ();
    }
    
    public function playTurboToReverse ():void
    {
    	_media.playTurboToReverse ();
    }
    
    public function playTurboToDrive ():void
    {
    	_media.playTurboToDrive ();
    }
    
    public function playRoll ():void
    {
    	_media.playRoll ();
    }
    

    Since properties don’t change when playing sound, we just delegate the work from car to media. For animation, the properties are manipulated inside the state classes which is then applied via access of the media through the car.

    It might start to get a little confusing but just think of it this way – the Car is your central control hub with its logic contained inside the state classes. The current state has its own specific behavior for the called action, in response, it then calls specific functions in the Car to accomplish the required result.

    Next comes control for the “park” radio button. We need these methods to automatically return the gear to park if the engine is either off or out of fuel (after one second). Don’t get it confused with the returnToPark() method which is triggered by the radio button (see Step 29). Paste the code next. You’ll see how these methods were conceived when we start adding to the state classes.

    
    
    public function switchToPark ():void
    {
    	_media.parked = true;
    }
    
    public function isParked ():Boolean
    {
    	return _media.parked;
    }
    

    Step 10: Extending the Car’s Responsibilities – Animation Control

    The code below completes the class. Add it at the end and save your work.

    
    
    ///Animation control
    public function get currentRollSpeed ():Number { return _currentRollSpeed; }
    
    public function set currentRollSpeed ($value:Number):void
    {
    	_currentRollSpeed = $value;
    }
    
    public function get carFrontWheel ():Sprite { return _media.carFrontWheel; }
    
    public function get carRearWheel ():Sprite { return _media.carRearWheel; }
    
    public function get carBody ():Sprite { return _media.carBody; }
    
    public function get carBlurEffect ():Sprite { return _media.carBlurEffect; }
    
    public function get angle ():Number { return _angle; }
    
    public function set angle (value:Number):void { _angle = value; }
    
    public function get reallyFastRollSpeed ():Number { return _reallyFastRollSpeed; }
    
    public function get reverseRollSpeed ():Number { return _reverseRollSpeed; }
    
    public function get driveRollSpeed ():Number { return _driveRollSpeed; }
    

    That was a lot of added code. Compare your classes with the ones included with the source download if you run into errors when testing the app later.


    Step 11: Controlling Media From EngineDriveReallyFast

    For adding effects (in this case, handling visual manipulation), I’ve learned to always start where things change most. We could start working on the default state EngineOff – but then how do we know what animation should happen there unless we know what animation it might be coming back from? If we start there, we only know that the wheels shouldn’t be rolling at all. But the Car may be coming from turbo.

    Review and compare the IState method implementations for your current EngineDriveReallyFast version with the code below.

    
    
    public function turnKeyOff ():void
    {
    	_car.print ("click... rolling to a stop from turbo...the engine has been turned off.");
    	_car.stopEngine ();
    	_car.keyOffClick ();
    	if (_car.currentRollSpeed != 0) _car.playRoll ();
    	_car.changeState (_car.getEngineOffState ());
    }
    
    public function turnKeyOn ():void
    {
    	_car.print ("man, we're on turbo, don't crank the ignition!");
    	_car.startEngineWhileRunning ();
    }
    
    public function driveForward ():void
    {
    	_car.print ("slowing down to regular driving speed...");
    	_car.playTurboToDrive ();
    	_car.changeState (_car.getEngineDriveForwardState ());
    }
    
    public function driveBackward():void
    {
    	_car.print ("click, switching the gears from turbo to reverse...");
    	_car.playTurboToReverse ();
    	_car.changeState (_car.getEngineDriveBackwardState ());
    }
    
    public function driveReallyFast ():void
    {
    	_car.print ("We can't drive any faster than this, no changes...");
    }
    
    public function reFuel ():void
    {
    	if (_car.hasFullTank () == false)
    	{
        	_car.stopEngine ();
    		_car.print ("hitting the brakes!, stopping the car, switching gear to park, turning the key to the off position, getting out of the car and adding "
    		+ Number (_car.refillWithFuel ()).toFixed (2) + " gallon(s) of fuel.");
    		_car.changeState (_car.getEngineOffState ());
    	}
    }
    
    public function update ($tick:Number):void
    {
    	_car.engineTimer += $tick;
    
        //if the speed goes over regular driving speed, show the turbo blur effect
    	if (_car.currentRollSpeed > _car.driveRollSpeed)
    	{
    		if (_car.carBlurEffect.alpha < .6) _car.carBlurEffect.alpha += .01;
    		if (_car.carBlurEffect.x > -30) _car.carBlurEffect.x += -.01;
    
            //rotate the car body clockwise a little
    		if (_car.carBody.rotation < 7) _car.carBody.rotation += .05;
    
            //move the car body a little to the left
    		if (_car.carBody.x > -5) _car.carBody.x += -.02;
    	}			
    
        //slowly increase speed if not maxed
    	if (_car.currentRollSpeed < _car.reallyFastRollSpeed) _car.currentRollSpeed += .2;
    	else _car.currentRollSpeed = _car.reallyFastRollSpeed;
    
    	_car.carFrontWheel.rotation += _car.currentRollSpeed;
    	_car.carRearWheel.rotation += _car.currentRollSpeed;
    
        //bobbing motion
    	_car.carBody.y = Math.sin (_car.angle += .05) * 2;
    
    	if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    	{
    		_car.engineTimer -= Car.ONE_SIXTH_SECONDS;
    
    		_car.consumeFuel (Car.TURBO_FUEL_CONSUMPTION);
    	}
    }
    

    I’ve excluded the constructor as well as the toString() method since they’re not doing anything with the media.

    We’re not making any changes to the state classes, we’re only adding features for controlling sound and animation. You can copy the listing and replace all those methods in your EngineDriveReallyFast state or you can just add the missing pieces. It’s up to you. The important part is we’ve abstracted the logic away from the Car object. Now we can make it as complex as we want. The focus is sharp. We know what we’re working on and there’s no mix-up.

    Animation is handled through the update() method. The rest just takes care of playing sound.


    Step 12: Animation for EngineDriveBackward

    Now for the opposite end.

    Review, compare then make changes just like we did for the EngineDriveReallyFast state.

    
    
    public function turnKeyOff ():void
    {
    	_car.print ("click... rolling to a stop in reverse...the engine has been turned off.");
    	_car.stopEngine ();
    	_car.keyOffClick ();
    	if (_car.currentRollSpeed != 0) _car.playRoll ();
    	_car.changeState (_car.getEngineOffState ());
    }
    
    public function turnKeyOn ():void
    {
    	_car.print ("you're driving in reverse, no need to crank the ignition!");
    	_car.startEngineWhileRunning ();
    }
    
    public function driveForward ():void
    {
    	_car.print ("click, switching from reverse to drive...");
    	_car.playReverseToDrive ();
    	_car.changeState (_car.getEngineDriveForwardState ());
    }
    
    public function driveBackward ():void
    {
    	_car.print ("...already driving in reverse.");
    }
    
    public function driveReallyFast ():void
    {
    	_car.print ("click, changing gears from reverse to turbo!!!");
    	_car.playReverseToTurbo ();
    	_car.changeState (_car.getEngineDriveReallyFastState ());
    }
    
    public function reFuel ():void
    {
    	if (_car.hasFullTank () == false)
    	{
        	_car.stopEngine ();
    		_car.print ("stopping the car from driving in reverse, changing gear to park, turning the key to the off position, getting out of the car and adding "
    		+ Number (_car.refillWithFuel ()).toFixed (2) + " gallon(s) of fuel.");
    		_car.changeState (_car.getEngineOffState ());
    	}
    }
    
    public function update ($tick:Number):void
    {
    	_car.engineTimer += $tick;
    
    	///visual
    	if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.01;
    	if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
    
    	if (_car.carBody.rotation > -5) _car.carBody.rotation += -.03;
    	if (_car.carBody.x < 3) _car.carBody.x += .02;
    
    	if (_car.currentRollSpeed > _car.reverseRollSpeed) _car.currentRollSpeed += -.4;
    	else _car.currentRollSpeed = _car.reverseRollSpeed;
    
    	_car.carFrontWheel.rotation += _car.currentRollSpeed;
    	_car.carRearWheel.rotation += _car.currentRollSpeed;
    
    	_car.carBody.y = Math.sin (_car.angle += .1) * 1;
    
    	if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    	{
    		_car.engineTimer -= Car.ONE_SIXTH_SECONDS;
    
    		_car.consumeFuel (Car.REVERSE_FUEL_CONSUMPTION);
    	}
    }
    

    Nothing fancy, we’re just making it seem like the car’s going in reverse. Since the previous state could be EngineDriveReallyFast, we have to make sure that the turbo blur effect is taken away (lines 51-52).


    Step 13: The EngineDriveForward State

    
    
    public function turnKeyOff ():void
    {
    	_car.print ("click... rolling to a stop...the engine has been turned off.");
    	_car.stopEngine ();
    	_car.keyOffClick ();
    	if (_car.currentRollSpeed != 0) _car.playRoll ();
    	_car.changeState (_car.getEngineOffState ());
    }
    
    public function turnKeyOn ():void
    {
    	_car.print ("you're driving so don't crank the ignition!");
    	_car.startEngineWhileRunning ();
    }
    
    public function driveForward ():void
    {
    	_car.print ("already driving - no need to change anything...");
    }
    
    public function driveBackward():void
    {
    	_car.print ("click, changing the gear from drive to reverse...");
    	_car.playDriveToReverse ();
    	_car.changeState (_car.getEngineDriveBackwardState ());
    }
    
    public function driveReallyFast ():void
    {
    	_car.print ("switching from drive to turbo!!!");
    	_car.playDriveToTurbo ();
    	_car.changeState (_car.getEngineDriveReallyFastState ());
    }
    
    public function reFuel ():void
    {
    	if (_car.hasFullTank () == false)
    	{
        	_car.stopEngine ();
    		_car.print ("stopping the car, changing gears to park, turning the key to the off position, getting out of the car and adding "
    		+ Number (_car.refillWithFuel ()).toFixed (2) + " gallon(s) of fuel.");
    		_car.changeState (_car.getEngineOffState ());
    	}
    }
    
    public function update ($tick:Number):void
    {
    	_car.engineTimer += $tick;
    
    	///visual
    	if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.01; //if coming from turbo, remove blur effect
    	if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
    
    	if (_car.carBody.rotation < 5) _car.carBody.rotation += .01;
    	else if (_car.carBody.rotation > 5) _car.carBody.rotation += -.01;
    
    	if (_car.carBody.x > -3) _car.carBody.x += -.01;
    	else if (_car.carBody.x < -3) _car.carBody.x += .01;
    
    	if (_car.currentRollSpeed < _car.driveRollSpeed) _car.currentRollSpeed += .2; //from reverse or park
        else if (_car.currentRollSpeed > _car.driveRollSpeed) _car.currentRollSpeed += -.2; //from turbo
    	else _car.currentRollSpeed = _car.driveRollSpeed;
    
    	_car.carFrontWheel.rotation += _car.currentRollSpeed;
    	_car.carRearWheel.rotation += _car.currentRollSpeed;
    
    	_car.carBody.y = Math.sin (_car.angle += .1) * 2;
    
    	if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    	{
    		_car.engineTimer -= Car.ONE_SIXTH_SECONDS;
    
    		_car.consumeFuel (Car.DRIVE_FUEL_CONSUMPTION);
    	}
    }
    

    Here, we check whether the Car came from the reverse, park, or turbo state and adjust the rolling of the wheels appropriately. The rest of the changes are similar to the other states.


    Step 14: The EngineOn State

    
    
    public function turnKeyOff ():void
    {
    	_car.print ("click... the engine has been turned off from park.");
    
    	///visual
    	_car.carBody.rotation = 0;
    	_car.carBody.x = 0;
    
    	_car.stopEngine ();
    	_car.keyOffClick ();
    	_car.changeState (_car.getEngineOffState ());
    }
    
    public function turnKeyOn ():void
    {
    	_car.print ("the engine's already running you didn't have to crank the ignition!");
    	_car.startEngineWhileRunning ();
    }
    
    public function driveForward ():void
    {
    	_car.print ("click, changing gears to drive ...now were going somewhere...");
    	_car.playParkToDrive ();
    	_car.changeState (_car.getEngineDriveForwardState ());
    }
    
    public function driveBackward():void
    {
    	_car.print ("click, changing gears from park to reverse...");
    	_car.playParkToReverse ();
    	_car.changeState (_car.getEngineDriveBackwardState ());
    }
    
    public function driveReallyFast ():void
    {
    	_car.print ("click, going on turbo...woohoo!!!");
    	_car.playDriveToTurbo ();
    	_car.changeState (_car.getEngineDriveReallyFastState ());
    }
    
    public function reFuel ():void
    {
    	if (_car.hasFullTank () == false)
    	{
        	_car.stopEngine ();
    		_car.print ("turning the car off, getting out and adding " + Number (_car.refillWithFuel ()).toFixed (2) + " gallon(s) of fuel.");
    		_car.changeState (_car.getEngineOffState ());
    	}
    }
    
    public function update ($tick:Number):void
    {
    	_car.engineTimer += $tick;
    
    	///visual
    	_car.angle += 2;
    
        //vibration effect
    	_car.carBody.rotation = Math.cos (_car.angle) * .8; 
    
    	if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.005;
    	if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
    
    	if (_car.currentRollSpeed > .3) _car.currentRollSpeed += -.3;
    	else if (_car.currentRollSpeed < -.3) _car.currentRollSpeed += .3;
    	else _car.currentRollSpeed = 0;
    
    	if (_car.currentRollSpeed == 0) _car.switchToPark ();
    
    	_car.carFrontWheel.rotation += _car.currentRollSpeed;
    	_car.carRearWheel.rotation += _car.currentRollSpeed;
    
    	if (_car.engineTimer >= Car.ONE_SIXTH_SECONDS)
    	{
    		_car.engineTimer -= Car.ONE_SIXTH_SECONDS;
    
    		_car.consumeFuel (Car.IDLE_FUEL_CONSUMPTION);
    	}
    }
    

    A pattern’s starting to show. Get to know the changes then apply them to your class.


    Step 15: Out of Fuel Animation

    
    
    public function turnKeyOff ():void
    {
    	_car.print ("you already did this when the fuel ran out earlier...");
    }
    
    public function turnKeyOn ():void
    {
    	_car.print ("no fuel - the car will not start, get some fuel before anything. Returning the key to the off position.");
    	_car.startEngineNoFuel ();
    }
    
    public function driveForward ():void
    {
    	_car.engineTimer = 0;
    	_car.print ("click, changing the gear to drive doesn't do anything...the car has no fuel, returning the gear to park...");
    }
    
    public function driveBackward ():void
    {
    	_car.engineTimer = 0;
    	_car.print ("click, changing the gear to reverse won't do anything either...the car has no fuel, returning the gear to park...");
    }
    
    public function driveReallyFast ():void
    {
    	_car.engineTimer = 0;
    	_car.print ("click, changed gear to turbo, no change, the car is not running, try to get som fuel, returning the gear to park.");
    }
    
    public function reFuel ():void
    {
    	if (_car.hasFullTank () == false)
    	{
    		_car.print ("getting out of the car and adding " + _car.refillWithFuel () + " gallon(s) of fuel.\n");
    		_car.changeState (_car.getEngineOffState ());
    	}
    }
    
    public function update ($tick:Number):void
    {
    	if (_car.currentRollSpeed == 0)
    	{
    		if (! _car.isParked ())
    		{
    			_car.engineTimer += $tick;
    
    			if (_car.engineTimer >= Car.ONE_SECOND)
    	 		{
    				_car.switchToPark ();
    			}
    		}
    	}
    
    	if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.005;
    	if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
    
    	if (_car.carBody.rotation > 0) _car.carBody.rotation += -.02;
    	else if (_car.carBody.rotation < 0) _car.carBody.rotation += .02;
    
    	if (_car.carBody.x < 0) _car.carBody.x += .02;
    	else if (_car.carBody.x > 0) _car.carBody.x += -.02;
    
    	if (_car.currentRollSpeed > 0.3) _car.currentRollSpeed += -.3;
    	else if (_car.currentRollSpeed < -0.3) _car.currentRollSpeed += .3;
    	else _car.currentRollSpeed = 0;
    
    	_car.carFrontWheel.rotation += _car.currentRollSpeed;
    	_car.carRearWheel.rotation += _car.currentRollSpeed;;
    
    }
    

    One more State… =)


    Step 16: Animation When the Car Is Turned Off

    
    
    public function turnKeyOff ():void
    {
    	_car.print ("The car's already off, you can't turn the key counter-clockwise any further...");
    }
    
    public function turnKeyOn ():void
    {
    	_car.print ("Turning the car on...the engine is now running!");
    	_car.startEngine ();
    	_car.changeState (_car.getEngineOnState ());
    
    }
    
    public function driveForward ():void
    {
    	_car.engineTimer = 0;
    	_car.print ("click, changing the gear to drive doesn't do anything...the car is not running, returning the gear to park...");
    }
    
    public function driveBackward ():void
    {
    	_car.engineTimer = 0;
    	_car.print ("click, changing the gear to reverse does nothing, the car is not running, returning the gear to park...");
    }
    
    public function driveReallyFast ():void
    {
    	_car.engineTimer = 0;
    	_car.print ("click, changing the gear to turbo, nothing happens, the engine's off, returning the gear to park...");
    }
    
    public function reFuel ():void
    {
    	if (_car.hasFullTank () == false)
    	{
    		_car.print ("getting out of the car and adding " + Number (_car.refillWithFuel ()).toFixed (2) + " gallon(s) of fuel.");
    	}
    }
    
    public function update ($tick:Number):void
    {
    	if (_car.currentRollSpeed == 0)
    	{
    		if (! _car.isParked ())
    		{
    			_car.engineTimer += $tick;
    
    			if (_car.engineTimer >= Car.ONE_SECOND)
    	 		{
    				_car.switchToPark ();
    			}
    		}
    	}
    
    	if (_car.carBlurEffect.alpha > 0) _car.carBlurEffect.alpha += -.005;
    	if (_car.carBlurEffect.x < 0) _car.carBlurEffect.x += .01;
    
    	if (_car.carBody.rotation > 0) _car.carBody.rotation += -.02;
    	else if (_car.carBody.rotation < 0) _car.carBody.rotation += .02;
    
    	if (_car.carBody.x < 0) _car.carBody.x += .02;
    	else if (_car.carBody.x > 0) _car.carBody.x += -.02;
    
    	if (_car.currentRollSpeed > 0.3) _car.currentRollSpeed += -.3;
    	else if (_car.currentRollSpeed < -0.3) _car.currentRollSpeed += .3;
    	else _car.currentRollSpeed = 0;
    
    	_car.carFrontWheel.rotation += _car.currentRollSpeed;
    	_car.carRearWheel.rotation += _car.currentRollSpeed;
    }
    

    The last two state classes have the same response when you try to switch gears. Also, you know what possible animation it could be coming from and apply adjustment for it.


    Step 17: Final Changes to Main

    Replace the contents of your Main.as class with the code below.

    
    
    package  com.activeTuts.fsm
    {
    
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.utils.getTimer;
    
    	[SWF (width = 500, height = 350, frameRate = 60, backgroundColor = 0xFFFFFF)]
    
    	public class Main extends Sprite
    	{
    		private var _past:Number;
    		private var _present:Number;
    		private var _tick:Number;
    		private var _car:Car;
    
    		public function Main ()
    		{
    			init ();
    		}		
    
    		private function init():void
    		{
    			_present = getTimer ();
    			_past = _present;
    
    			addCar ();
    
    			addEventListener (Event.ENTER_FRAME, update);
    		}
    
    		private function addCar ():void
    		{
    			_car = new Car;
    			_car.x = stage.stageWidth * .5;
    			_car.y = stage.stageHeight * .5;
    			addChild (_car);
    		}
    
    		private function update (e:Event):void
    		{
    			_present = getTimer ();
    			_tick = (_present - _past) * .001; ///converted to 1/1000
    			_past = _present;
    
    			_car.update (_tick);
    		}
    
    	}
    
    }
    

    All we do is add the car and call its update() method at every enter frame event. You should see the car and the visual output when you run the application.

    You’ll notice the Car runs out of gas too soon. Change the value of _fuelCapacity to 3 inside Car (variable declaration), and then test the Car thoroughly.

    Congratulations! You finished the tutorial.


    Summary

    Let’s now go over the whole procedure of creating a full featured FSM object from scratch.

    1. Sketch up the State Transition Table for your object.
    2. Create your Procedural FSM object.
    3. Once Procedural FSM works, if you need to add a lot more features and/or states, convert it to the State Pattern.
    4. Build your IState Interface first.
    5. Create the first/default state class (consult State Transition Table and Procedural FSM actions).
    6. Duplicate a copy of your Procedural FSM object, then allow public access to all properties the state classes need to control.
    7. Create the rest of the state classes.
    8. Add features/states as per your requirements. These usually present themselves while you’re working on your State actions.
    9. When adding new state/s, start with your State Transition Table.
    10. Program the state/s action/s into the IState interface first, then create the state/s.
    11. Add the new action/s to the already existing states manually.
    12. When adding effects (logic) start with the state with biggest change. Then work on the opposite end so you can expect what could happen in the states between.
    13. Polish until logic for all the states meet your specifications.

    Care to do the homework? Hint: You won’t need to create another state.

    Until next time! Thank you so much for reading!! =)

    Comments are always welcome. Please post them below.


  9. Franci Zidar says:
    October 14, 2011 at 1:06 am

    MAX is Adobe’s annual conference, where they announce their new products and acquisitions, and give us all an idea of their current strategy. Last year they announced Flash’s 3D API, codenamed Molehill (now called Stage3D); AIR for TV; Edge, their new HTML5 animation tool; and more. There were also puppets. This year, long-time Activetuts+ writer Franci Zidar is at the event and will be filling us in on the details and sharing lots of photos. The second day’s keynote was aimed at developers – that means Flash and HTML5 news. Read on to find out more…


    The Future of Flash

    Flash is taking on a whole new life. With new technologies like HTML5 and CSS3 and advances in JavaScript, a lot of rich media delivery is going to be taken over by these standards. Adobe unveiled a demo of Unreal Tournament 3 working in Flash inside the browser (see below) and talked extensively about Flash as a gaming platform on all devices in 2D and 3D. The idea is to make Flash platform the “gaming console for the web”.

    Adobe MAX 2011
    Adobe MAX 2011
    Editor’s note: Mouse-lock at last, hooray!

    Flash’s Stage3D (previously codenamed Molehill – check out an introductory tutorial here) provides GPU acceleration for 3D and now through Starling Framework for 2D graphics within Flash Player.

    Adobe MAX 2011

    The next iteration of Flash Professional (codenamed Ruben) will also let you create sprite sheets from vector animations to get GPU accelerated animations and provide some other GPU acceleration focused improvements.


    Check out Unreal Tournament running in the Flash Player (sadly not ported to AS3!)

    Other Technologies

    A lot of the focus at MAX this year was on alternative technologies to Flash for rich media delivery. My guess is that you are already better off building sites requiring simple multimedia experiences with these other web standards since you’ll have a much easier job deploying them for mobile devices or they might, to some extent, work on them by default.

    Adobe MAX 2011

    Adobe has also proposed two new features for CSS called CSS Regions and CSS Shaders. Regions allow the creation of scalable text regions of any shape that let text flow through or around them depending on screen size and Shaders create great looking effects and transitions for HTML elements.

    Here’s some more info on CSS Shaders, including videos of them in action: http://www.adobe.com/devnet/html5/articles/css-shaders.html

    Adobe MAX 2011

    While visually more intensive web experiences would still require Flash to be built in reasonable time, since the workflows are already established, and perform well at runtime, with software like Adobe Edge and HTML5 and JavaScript rendering getting better each month this could also be changing soon. Having played a bit with Edge and seeing some demonstrations it really seems this software is currently heading in the direction of a mix between After Effects and Flash. It’s still in preview stage and has limited scripting capabilities but in some ways it is quite similar to Flash.


    The Future

    The web is shifting again. New devices are changing the landscape for web deployment and there are a bunch of new solutions to deliver the content across all of them. HTML5 has been the buzz word for quite a while now, and while it does provide some welcome improvements and new features what is really helping developers deliver content across devices either online or offline is a combination of CSS, JavaScript and HTML. There are now many solutions for creating online content and offline apps and websites but the combination of CSS, JavaScript and HTML allows you to apply an already mastered set of skills to any of these methods of deployment. You could learn native development for Android, iOS, Windows Phone and all the other mobile platforms but that is unrealistic from the perspective of a freelance developer and hard even for a medium sized company.

    Adobe MAX 2011

    So for anyone who finds this fragmented solution too much to handle, you can resort to new technologies like jQuery Mobile, PhoneGap, jQTouch, CSS3, HTML5 and others. All of these are incorporated in the new Dreamweaver so you can build websites for multiple platforms test them at different resolutions and orientations (landscape and portrait) and even deploy applications directly to Android or iOS.

    Adobe MAX 2011

    PhoneGap is especially interesting since it allows developers to tap into native extensions of all major mobile platforms giving you access to things like geolocation, accelerometer, camera, dialer and many other phone features that were until now only available to native applications. Using PhoneGap it’s now easier than ever to build cross platform apps and websites that take full advantage of the mobile capabilities all with JavaScript, CSS and HTML.


    Get Your Hands on the New Toys

    Adobe MAX 2011

    For more information about everything announced on Day Two – as well as download links for AIR 3 and Flex 4.6 pre-release – head to http://adobe.com/go/maxday2


  10. Eugene Potapenko says:
    October 14, 2011 at 1:33 am

    Good news, everyone. Everybody who works with arrays and vectors – so, all developers – will enjoy the new opportunities: since the late August 2011 build of Flash, Realaxy ActionScript Editor (RASE) supports the new Collections AS3 language extension. In this post we’ll show you what it can do.

    Realaxy logo

    A collection is a general term that means, roughly, “a bunch of similarly typed objects that are grouped together”. Building a collection in ActionScript 3.0 can be done by using arrays, vectors, dictionaries, or objects; they each have some of the makings of a perfect concept. However, if you have even a basic acquaintance with any modern and trendy language like Scala, Groovy or Ruby, you’ll definitely feel the lack of a functional approach in the pure AS3-way of processing Collections.


    The Collections Language

    Well, let’s introduce the Collections AS3 language extension that is available in RASE Beta 10, build 8177+.

    A complete vocabulary of methods supplemented with samples is available here: one for lists and another one for maps (these are images; scroll them down, they are really HUGE).

    In order to not get lost in this jungle, let’s take a look at some simple use cases. The first true-life sample demonstrates the conciseness of collection code:

    We create a list, which can contain only int values.

    1. Then, we select only those that fulfil a condition (“where”).
    2. We do something with every picked element (“select”).
    3. We convert them to Strings (“select”).
    4. Finally, we cycle through the list and trace the result.

    Where, select, selectMany – these operations are easy to use when you’re building a query.

    Operations like all, any, containsAll, and contains work perfectly in conditional phrases (“if” statements, etc).

    To modify a list, we have a wide range of weapons: remove, removeAll, removeWhere, removeHead, removeTail, etc.

    For those persons who definitely have an eye for perversion we have prepared a bunch of operations like foldLeft/foldRight, reduceLeft/reduceRight, intersect, etc.

    Simply said, there are plenty of operations suited to every fancy and almost every task. In some operations you just transmit one or more values to it, in some other ones you add a closure.


    Lists and Maps

    The collection language is intentionally simple. It supports two types: List and Map. Map is quite similar to a trivial Dictionary that holds some useful methods – keys, values, containsKey, containsValue (useful for checks and conditions), pushMap (to merge values), removeKey, removeValue, etc.

    Maps are smart and fail-safe. They won’t let you add an incorrect key or value:

    Maps work well with any lists and queries.

    There are also conversion operations that can be utilized to facilitate embedding the new Collections extensions to your actual pure-ActionScript project. Just take a trivial array (or vector) and apply the .toList operation. After processing a list (or a map) you can always convert it back to the old-school AS3 style using .toArray or .toVector.


    A Real World Example

    To demonstrate how to get started with these collections, here’s a step-by-step guide based on a trivial situation. Suppose your task is to create a “suggested users” list for a Twitter account. We have to process a very long collection of hundreds or thousands of objects, to retrieve a short list (that match a number of criteria) and to apply some operation on every item in that short list.

    We’ll not turn our attention to interacting with Twitter API, since our goal is only to show how to get started with the Collections language and to show advantages of a functional approach in working with collections in AS3.


    Step 1: Creating a Project

    Create a new project from scratch and name it Collections. If this is your first experience with the editor, we recommend you to read the Realaxy HelloWord Tutorial and/or Realaxy Overview For Beginners.


    Step 2: Creating a Class (Beginning)

    Now we need to create three classes to store the data structure: User, TwitterAccount and Message. We can do it from the Generate menu, which is available through right-clicking the code or pressing Ctrl+N.


    Step 3: Creating a Class (In Progress)

    Type the class name in a pop-up box.


    Step 4: Creating a Class (Adding Fields)

    Jump to the <<Field>> position and hit Enter.


    Step 5: Creating a Class (More Fields)

    Add the following fields: username, surname and id. The code will look like this:


    Step 6: Adding Getters and Setters

    Invoke the Ctrl+N menu again.


    Step 7: Still Adding Getters and Setters

    A new pop-up window will appear. Select all newly created fields and press OK.


    Step 8: Adding a .toString() Method

    Don’t forget to add a text presentation to the User class. Add .toString() method — item 5 from the Ctrl-N menu (same as on screenshot to Step 2 and 6).


    Step 9: Code Overview

    The User class is ready. Its code will look like this:


    Step 10: TwitterAccount and Message Classes

    Using the same process as Steps 2-9 you have to create TwitterAccount and Message classes.

     

    NB: To avoid a type error (like the one shown in the image above), you must import the Collections language from the Ctrl+L menu:

    NB2: The TwitterAccount and Message classes should be cross-linked. In order for this to happen, after importing the Collections language, you have to create the Message class, jump back to the TwitterAccount class and complete the line that caused an error.

    NB3: Don’t forget to add getters, setters and a .toString() method.


    Step 11: Main()

    Now it’s time to write some code in Main(). First, we need to add some fields. 

    NB: Use the Smart Complete keyboard shortcut (Ctrl-Shift-Space) to save a little time while typing these phrases:

    Since our tutorial is just a demo that shows a how to work with collections in AS3, we’ll skip the part that refers how to get this data from Twitter API.

    Let’s just imagine that we already have:

    1. a list of our followers, 
    2. a list of users followedBefore,
    3. a very long list of potential candidates for following — candidatesLongList,
    4. and, of course, a candidatesShortList, which is empty at the moment.

    The third collection can be extremly large, containing hundreds or even thousands of items. Our goal is to apply some sophisticated query and thus to cut off the needless items according to Buonarotti’s principle “I saw the angel in the marble and carved until I set him free.”


    Step 12: Building the Query

    Jump to Main() constructor, and enter the candidatesLongList with its method “where” (hit Ctrl-Space to use autocompletion like in the screenshot below):

    The following phrase will appear:

    Don’t be surprised, it’s just a Closure, and “it” is just its parameter.


    Closures (a small lyrical digression)

    A closure is, in fact, the same anonymous function, but with a number of small differences.

    First, the Closure has a very concise syntax. Parameters don’t have a type declaration (to be more precise, they have it, but such declarations are hidden). The Closure has a special behavior – “the last statement is a return value&rdquo – which means you should use “1;” (in the last line) instead of “return 1;”

    Second, it has a special view for single-line closures – in such a closure a semi-colon on the end is omitted (for two reasons: readability and brevity).

    Unlike an anonymous function (and also as a counter to the aforementioned Arrays and Vectors), a closure is type safe. That means that autocomplete and type-checking will work in closures.

    To conclude, a Closure is a kind of function on steroids. It has a lot of tasty features that helps us to write everything quick and concise.


    Step 13: Building the Query

    Let’s return to our unfinished closure. Add some code that will implement our “rules”. This code will return a cut-down version of candidatesLongList that doesn’t include any of our followers:

    Then add another criterion:

    Theoretically, the Collections language allows you to nest a lot of different conditions one by one. Let’s add one more criterion (include users that have “Flash”, “ActionScript” or “Adobe” in their Biography field) using a regular expression:


    Step 14: Obtaining the Result

    Select the whole query and press Ctrl-Alt-V. A new variable will be introduced.

    Now we are able to do whatever we want:

    We would then copy the contents of result into candidatesShortList.


    Step 15: Generating Pure AS3 Code

    Build a module with Ctrl-F9 and take a look at the Output window. The generated pure AS3 code of Main() will look like this:

    
    
    package com.example{
    
      import com.realaxy.actionScript.collections.util.CollectionsLanguageUtil;
      import flash.display.Sprite;
    
      public class Main extends Sprite {
    
        private var followers : Array = new Array() ;
        private var followedBefore : Array = new Array() ;
        private var candidatesLongList : Array = new Array() ;
        private var candidatesShortList : Array = new Array() ;
    
        public function Main(){
    
          //exclude our followers and users followed by us before
          //include people with "Flash", "ActionScript" and "Adobe" in their bio
          //add them all to the recommendations shortlist
    
          this.candidatesShortList = CollectionsLanguageUtil.where(CollectionsLanguageUtil.where(candidatesLongList,  
    
            function ( n : TwitterAccount, stops : Object ) : Boolean {
    
              return !CollectionsLanguageUtil.any(followers,      
    
                function ( f : TwitterAccount, stops : Object ) : Boolean {
                  return f.user.id != n.user.id ;
                },
    
                this, false) && 
    
              !CollectionsLanguageUtil.any(followedBefore,     
    
                function ( f : TwitterAccount, stops : Object ) : Boolean {
                  return f.user.id != n.user.id ;
                }, 
    
                this, false);
            }, 
    
            this, false),   
    
              function ( it : TwitterAccount, stops : Object ) : Boolean {
                return /Flash|ActionScript|Adobe/.test(it.bio) ;
              }, 
    
            this, false);
    
          var names : Array = CollectionsLanguageUtil.select(candidatesShortList,       
    
            function ( it : TwitterAccount, stops : Object ) : String {
              return it.user.username + ", " + it.user.surname ;
            }, 
    
            this, false);
    
          CollectionsLanguageUtil.forEach(names,
    
            function ( m : String, stops : Object ) : void {
            //TODO: do something with 'm'
            }, 
    
            this, false);
        }
      }
    }

    Seems a little bit unreadable, eh? Especially comparing with our DSL code:


    Conclusion

    The new Collections language extension allows you to:

    • improve your AS3 code making it more concise,
    • make your code more human-readable, and
    • facilitate turning your sequential code into parallel code.


Leave a Reply

Click here to cancel reply.

search search search search search
Find an Article
Categories
  • Flash Video Training
  • Hints and Tips
  • Recommended
Please Support Our Sponsors
Recent Posts
  • Workshop Coding Challenge: Fix This Breakout Game
  • Enable the Latest AIR SDK in Flash Professional CS5.5+
  • Quick Tip: Versioning Your Files With Dropbox (via Webdesigntuts+)
  • Workshop: Nuclear Outrun – Critique
  • Understanding Variables, Arrays, Loops, and Null: The Post-it Note Analogy
Tag Cloud
2011 ActionScript Active Activetuts+ Adobe animation Basic Basix Best Build Button Character Create Creating Critique Custom design Effect Effects Files Flash from Game Guide HTML5 Introduction Macromedia Motion Muzzle part Player Premium Professional Quick Silverlight Simple Text Tool Tutorial Tuts+ Tween Using Video website Workshop
About Our Site:

Hey there and welcome to "Flash Video Training Source", a resource for anybody interested in learning more about Adobe's great tool. We feature educational videos, which will help you master Adobe Flash and help you get to know all of its features. We at "Flash Video Training Source" believe that video training and video... more

Why don't you follow us on Twitter and get the latest video tutorials twitted to your account. Just click on the floating twitter bar to your right!

Go Back In Time
May 2012
M T W T F S S
« Apr    
 123456
78910111213
14151617181920
21222324252627
28293031  
Pretty Blank Box
top

Blogroll

  • Development Blog
  • Documentation
  • Plugins
  • Suggest Ideas
  • Support Forum
  • Themes
  • WordPress Planet

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Archives

  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • August 2011
  • July 2011
  • June 2011
  • May 2011
  • April 2011
  • March 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • August 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
Powered by WordPress  |  Designed by Elegant Themes  |  Lightning Fast Hosting by Site 5 Hosting