Introduction to using the StatefulToolkit

Tutorial Overview

In this tutorial I’ll take you through the basic premise behind the StatefulToolkit and demonstrate how it can be used in a project. If you haven’t heard of StatefulToolkit before then be sure to read up on it. This example will also demonstrate use of the FileUtil, which is a utility class included in the toolkit that allows the user to load and save states. The application is very basic and will allow the user to position four circles and to save and load the state of the circles (i.e. where the circles are positioned). The example can be seen here and source files are available.

Preparation

Before you being implementing this tutorial make sure you have the latest version of the StatefulToolkit SWC, and that you’re project is set to target Flash Player 10 (if you want to target FP9, simple replace the Vector with an array in the circle container) . This tutorial also uses Keith Peter’s Minimal Comps component set to create the buttons in the application so you may want to grab the SWC for that too, but you can use your own buttons if you prefer.

Creating the Statable Object

We’re going to start by creating the circles that the user can move. In order for the position of the circles to be saved the class must implement the IStatable interface. The class itself is very simple; the constructor creates the graphics of the circle, the retrieveState method returns the position of the circle as a String, and the setState method sets the position of the Circle based on the new state. The retrieveState and setState methods are both required by the IStatable interface. Note that I’ve put the class in the couk.markstar.examples.ui package, but you don’t have to. The full contents of this class can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package couk.markstar.examples.ui
{
	import couk.markstar.statefultoolkit.core.IStatable;
 
	import flash.display.Sprite;
 
	public class Circle extends Sprite implements IStatable
	{
		/**
		 * The constructor draws the circle with a grey outline
		 */
		public function Circle():void
		{
			super();
 
			drawCircle( 0x999999 );
		}
 
		/**
		 * Change the display of the circle to show it's selected
		 */
		public function select():void
		{
			drawCircle( 0x990000 );
		}
 
		/**
		 * Change the display of the circle to the default
		 */
		public function deselect():void
		{
			drawCircle( 0x999999 );
		}
 
		/**
		 * Retrieve the state of the circle storing the x and y properties
		 *
		 * @return The current state of the circle as an XML string
		 */
		public function retrieveState():String
		{
			var xml:String = '<object>';
			xml += '<x>' + x + '</x>';
			xml += '<y>' + y + '</y>';
			xml += '</object>';
			return xml;
		}
 
		/**
		 * Set the state of the circle by getting it's x and y values from the xml
		 *
		 * @param state The new state of the circle
		 */
		public function setState( state:XML ):void
		{
			x = Number( state.x );
			y = Number( state.y );
		}
 
		/**
		 * Used internally for creating the circles' graphics
		 *
		 * @param lineColor The color of the line around the circle
		 */
		protected function drawCircle( lineColor:uint ):void
		{
			graphics.clear();
			graphics.lineStyle( 1, lineColor )
			graphics.beginFill( 0xFFFFFFF );
			graphics.drawCircle( 0, 0, 10 );
			graphics.endFill();
		}
	}
}

Creating the Container

In this example the container is responsible for managing the circles. This includes creating and deleting circles, setting and retrieving the state of the circles and adding behaviours to the circles so the user can position them.

The circle container must also implement the IStatable interface ,so it can be used with the FileUtil. The container is set up to assume that an unknown amount of circles could be stored in the state, therefore there’s some additional code in the class to create and remove circles. Firstly, there’s the addObjectByState method, which takes the state of a circle as it’s parameter and creates a circle based on the state, and there’s the clearContainer method that loops through all of the circles and removes them from the stage.

The retrieveState and setState methods are also slightly different to the ones found in the Circle class. The retrieveState method loops through all circles and adding their states to the string to be returned, and the setState method clears any circles currently there and loops through the new state creating new circles as necessary. The last three methods of the class are event listeners that are there to enable the positioning of circles. The full contents of this class can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package couk.markstar.examples.ui
{
	import couk.markstar.statefultoolkit.core.IStatable;
	import couk.markstar.statefultoolkit.core.IStateSelectContainer;
 
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;
 
	public class CircleContainer extends Sprite implements IStateSelectContainer
	{
		protected var _circles:Vector.<Circle>;
 
		protected var _currentCircle:Circle;
		protected var _dragOffset:Point;
 
		/**
		 * The constructor the vector containing the circles shown in the container
		 */
		public function CircleContainer():void
		{
			_circles = new Vector.<Circle>();
			super();
		}
 
		/**
		 * Retrieve the selected object
		 *
		 * @return The selected object
		 */
		public function retrieveSelectedObject():IStatable
		{
			return _currentCircle;
		}
 
		/**
		 * Check whether the container has a selected object
		 *
		 * @return Boolean denoting if the container has a selected object
		 */
		public function hasSelectedObject():Boolean
		{
			return( _currentCircle ) ? true : false;
		}
 
		/**
		 * Retrieve the state of the container and all contained circles
		 *
		 * @return The current state of the container, including the state's of all circles
		 */
		public function retrieveState():String
		{
			var xml:String = "<container>";
 
			for( var i:uint = 0; i < _circles.length; i++ )
			{
				xml += _circles[ i ].retrieveState();
			}
			xml += "</container>";
			return xml;
		}
 
		/**
		 * Add an object to the container by it's state.
		 *
		 * @param state The state of the object to add, in this case the circle
		 */
		public function addObjectByState( state:XML ):void
		{
			var circle:Circle = new Circle();
			circle.addEventListener( MouseEvent.MOUSE_DOWN, circleDownListener );
			circle.setState( state );
			addChild( circle );
			_circles[ _circles.length ] = circle;
		}
 
		/**
		 * Set the state of the container by adding all circles contained within the state. The container will be cleared first.
		 *
		 * @param state The new state of the container
		 */
		public function setState( state:XML ):void
		{
			clearContainer();
 
			for( var i:uint = 0; i < state.object.length(); i++ )
			{
				addObjectByState( XML( state.object[ i ] ) );
			}
		}
 
		/**
		 * Clear the container of all circles
		 *
		 */
		protected function clearContainer():void
		{
			for( var i:uint = 0; i < _circles.length; i++ )
			{
				removeChild( _circles[ i ] );
			}
			_circles.splice( 0, _circles.length );
		}
 
		/*
		 * The following methods are all event listeners to enable the circles to be dragged
		 */
		protected function circleDownListener( e:MouseEvent ):void
		{
			// Deselect the current circle (if there is one)
			if( _currentCircle )
			{
				_currentCircle.deselect();
			}
 
			_currentCircle = Circle( e.currentTarget );
			_currentCircle.select();
 
			stage.addEventListener( MouseEvent.MOUSE_MOVE, circleMouseMoveListener );
			stage.addEventListener( MouseEvent.MOUSE_UP, circleMouseUpListener );
 
			_dragOffset = new Point( _currentCircle.x - mouseX, _currentCircle.y - mouseY );
		}
 
		protected function circleMouseMoveListener( e:MouseEvent ):void
		{
			_currentCircle.x = mouseX + _dragOffset.x;
			_currentCircle.y = mouseY + _dragOffset.y;
			e.updateAfterEvent();
		}
 
		protected function circleMouseUpListener( e:MouseEvent ):void
		{
			stage.removeEventListener( MouseEvent.MOUSE_MOVE, circleMouseMoveListener );
			stage.removeEventListener( MouseEvent.MOUSE_UP, circleMouseUpListener );
		}
	}
}

Creating the Document Class

The document class for this application is pretty simple. It’s responsible for creating the save and load buttons, creating the circle container, registering the circle container with the FileUtil and initialising the container with a default state. The save and load buttons are created using the Minimal Comps component set, you can replace these with buttons of your own if you choose not to use them. The full contents of this class can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package
{
	import com.bit101.components.PushButton;
 
	import couk.markstar.examples.ui.CircleContainer;
	import couk.markstar.statefultoolkit.utils.FileUtil;
	import couk.markstar.statefultoolkit.utils.IFileUtil;
 
	import flash.display.Sprite;
	import flash.events.MouseEvent;
 
	[SWF( backgroundColor='#F3F3F3',frameRate='30',width='592',height='300' )]
	public class SaveLoadExample extends Sprite
	{
		protected var _saveButton:PushButton;
		protected var _loadButton:PushButton;
 
		protected var _circleContainer:CircleContainer;
		protected var _fileUtil:IFileUtil;
 
		/**
		 * Create the save and load buttons to save and load the state. Then create the circle container and register it with the FileUtil,
		 * by registering the container with the FileUtil it means the FileUtil knows which statable object to retrieve and set the state on
		 * when save and load are executed.
		 */
		public function SaveLoadExample():void
		{
			// PushButton( parent, x, y, label, MouseEvent.CLICK event listener )
			_saveButton = new PushButton( this, 196, 275, "Save", saveClickListener );
			_loadButton = new PushButton( this, 295, 275, "Load", loadClickListener );
 
			_circleContainer = new CircleContainer();
 
			_fileUtil = new FileUtil();
			_fileUtil.registerObject( _circleContainer );
 
			addChild( _circleContainer );
 
			initDefaultState();
		}
 
		protected function saveClickListener( e:MouseEvent ):void
		{
			_fileUtil.save();
		}
 
		protected function loadClickListener( e:MouseEvent ):void
		{
			_fileUtil.load();
		}
 
		/**
		 * Initialise the circle container with four circles
		 */
		protected function initDefaultState():void
		{
			var defaultState:String = '<container>';
			defaultState += '<object><x>196</x><y>100</y></object>';
			defaultState += '<object><x>196</x><y>200</y></object>';
			defaultState += '<object><x>396</x><y>100</y></object>';
			defaultState += '<object><x>396</x><y>200</y></object>';
			defaultState += '</container>';
 
			_circleContainer.setState( new XML( defaultState ) );
		}
	}
}

Testing the application

You will now be able to run the application and test that the state of the application can be saved and loaded. The structure of the project can be seen below. If you do have any questions about the tutorial, or constructive criticism on the toolkit, don’t hesitate leave a comment below.

SaveLoadExample Project Structure

Compiled Application

Once you have compiled the application, you should get the same output as below. Try moving the circles, saving the state then moving the circles again and loading the state. Notice that the circles go back to the saved state when it’s loaded.

Source Files

Source files are available from GitHub, under the heading SaveLoadExample.

Posted by Mark on January 24th, 2010
 

4 Responses to “Introduction to using the StatefulToolkit”

Leave a Reply