KNOWLEDGE BASE

Sodality Simple Tutorial in Flash Builder (formerly Flex)


This tutorial goes through the building of a simple demo app with Sodality. If it’s moving too slow for you I’d recommend downloading the source and only going through the checkpoint steps of the tutorial (3, 5 & 7).

  1. Project Setup
  2. Basic Sodality interactions
  3. Checkpoint 1
  4. Inserting Asychronous behaviour
  5. Checkpoint 2
  6. Intercepting before and after
  7. Checkpoint 3
  8. Download Source

1. Project Setup

  • In Flash Builder, set up a new ActionScript project (i.e. NOT a Flex Project) named “SodalitySimpleDemo’.
  • Download the Sodality SWC and add it to your project, making sure you add it to your source (Project Properties > ActionScript Build Path > Library path > Add SWC).
  • Open the Compiler Settings (Project Properties > ActionScript Compiler) and uncheck “Copy non-embedded”, uncheck “Generate HTML Wrapper”, this is not required by the framework but will help keep things clean.
  • Add the compiler argument “-keep-as3-metadata+=Advice,Trigger,Property’ to the “Additional Compiler Arguments’ field (still in the Compiler Settings area). This tells Flash Builder to keep the metatags we use to make sodality work.

2. Basic Sodality interactions

First we must add a President to the project, the president executes the core logic of Sodality.

Within the SodalitySimpleDemo class (which flash builder created in the default package), add this private variable:

private var president:President;

Add this to the SodalitySimpleDemo constructor to instantiate the president:

president = new President(this);

Passing through the root object to the president tells it how much of the display hierarchy should be managed by this president (i.e. in this case the whole hierarchy).

Now we must create an Advice class, this class represents a certain action that is taking place within your application. For simplicity’s sake we will create an Advice class representing a mouse click and we’ll create it in the default package. The MouseClickAdvice class will contain a message which will be displayed later:

package
{

       
import org.farmcode.sodality.advice.Advice;
       
       
public class MouseClickAdvice extends Advice
       
{
               
[Property(toString="true",clonable="true")]
               
public var message:String;
               
               
public function MouseClickAdvice(message:String=null){
                       
super();
                       
this.message = message;
               
}
       
}
}

Consider this Advice class similar to an event, we’ll use it to run other code when something gets clicked on. You’ll notice the message property is annotated with the “Property’ metatag, this forces the value to be copied over when the MouseClickAdvice is cloned in the dispatching process, generally all public properties of Advice classes should be annotated in this way. You’ll also note that the constructor arguments must always be optional in Advice subclasses, this is again to allow the cloning behaviour that Advice undergoes.

Next, we’ll add a button to the SodalitySimpleDemo class which we'll use to dispatch our advice, add this to the constructor:

var button:Sprite = new Sprite();
button
.graphics.beginFill(0xff0000);
button
.graphics.drawRect(100,100,100,100);
button
.addEventListener(MouseEvent.CLICK, onMouseClick);
button
.buttonMode = true;
addChild
(button);

And then we’ll add the event handler which will dispatch the MouseClickAdvice:

protected function onMouseClick(e:MouseEvent):void{
        dispatchEvent
(new MouseClickAdvice("Button Clicked"));
}

Now the MouseClickAdvice is being dispatched but nothing is listening for it. Before we do that we'll add a text field to the SodalitySimpleDemo class that we can display the message in. Add this private property to the class:

private var resultField:TextField;

And add this to the constructor:

resultField = new TextField();
addChild
(resultField);

Now we'll add a function to the SodalitySimpleDemo class which will catch the advice (using the Sodality 'Trigger' Metadata), note that the method must be public for the President to find it:

[Trigger(triggerTiming="after")]
public function afterMouseClick(cause:MouseClickAdvice):void{
        resultField
.text = cause.message;
}

This is all fine except that Sodality won’t trigger functions within classes unless they’re tagged with the interface IAdvisor (this only works for DisplayObjects, more on that in Step 4).

So change the SodalitySimpleDemo class definition line to:

public class SodalitySimpleDemo extends Sprite implements IAdvisor

Back to top

3. Checkpoint 1

At this point your project should run happily and when you click on the red square, the text “Button Clicked’ should appear in the corner of the SWF.

Your classes should look like this:

SodalitySimpleDemo.as

package
{
       
import flash.display.Sprite;
       
import flash.events.MouseEvent;
       
import flash.text.TextField;
       
       
import org.farmcode.sodality.President;
       
import org.farmcode.sodality.advisors.IAdvisor;
       
       
public class SodalitySimpleDemo extends Sprite implements IAdvisor
       
{
               
private var president:President;
               
private var resultField:TextField;
               
               
public function SodalitySimpleDemo()
               
{
                        president
= new President(this);
                       
                       
var button:Sprite = new Sprite();
                        button
.graphics.beginFill(0xff0000);
                        button
.graphics.drawRect(100,100,100,100);
                        button
.addEventListener(MouseEvent.CLICK, onMouseClick);
                        button
.buttonMode = true;
                        addChild
(button);
                       
                        resultField
= new TextField();
                        addChild
(resultField);
               
}
               
protected function onMouseClick(e:MouseEvent):void{
                        dispatchEvent
(new MouseClickAdvice("Button Clicked"));
               
}
               
[Trigger(triggerTiming="after")]
               
public function afterMouseClick(cause:MouseClickAdvice):void{
                        resultField
.text = cause.message;
               
}
       
}
}

MouseClickAdvice.as

package
{
       
import flash.display.DisplayObject;
       
       
import org.farmcode.sodality.advice.Advice;
       
       
public class MouseClickAdvice extends Advice
       
{
               
[Property(toString="true",clonable="true")]
               
public var message:String;
               
               
public function MouseClickAdvice(message:String=null){
                       
super();
                       
this.message = message;
               
}
       
}
}

Skip to Checkpoint 2

Back to top

4. Inserting Asychronous behaviour

You’ll be thinking to yourselves that this is just a convoluted event management system at this point, so now we’ll load a file in between when the button is clicked and when the handling function gets called (something that would be impossible with events).

We’ll change the demo so that when the red button is clicked another (non-visual) class loads a file before the “afterMouseClick’ handler function gets called.

First we'll create the file to load in, simply create a text file called 'file.txt' in the 'bin-debug' folder containing the words "File Loaded".

Now we’ll need to create a new interface representing an action (Advice) that needs to load a file. This represents all of the interactions that our file loading Advisor (which we’re about to build) will need from the advice:

package
{
       
import org.farmcode.sodality.advice.IAdvice;
       
       
public interface ILoadFileAdvice extends IAdvice
       
{
               
function get fileURL():String;
               
function set message(value:String):void;
       
}
}

Then we'll modify MouseClickAdvice to implement the new interface (and modify the constructor arguments to take a url):

package
{
       
import org.farmcode.sodality.advice.Advice;
       
       
public class MouseClickAdvice extends Advice implements ILoadFileAdvice
       
{
               
public function MouseClickAdvice(fileURL:String=null){
                       
super();
                       
this.fileURL = fileURL;
               
}
               
               
private var _message:String;
               
private var _fileURL:String;
               
               
[Property(toString="true",clonable="true")]
               
public function get fileURL():String{
                       
return _fileURL
               
}
               
public function set fileURL(value:String):void{
                        _fileURL
= value;
               
}
               
[Property(toString="true",clonable="true")]
               
public function get message():String{
                       
return _message
               
}
               
public function set message(value:String):void{
                        _message
= value;
               
}
       
}
}

Then we’ll need to create our new Advisor, whose responsibility it is to load a file whenever an action requires it:

package
{
       
import flash.events.Event;
       
import flash.net.URLLoader;
       
import flash.net.URLRequest;
       
       
import org.farmcode.sodality.advice.AsyncMethodAdvice;
       
import org.farmcode.sodality.advisors.DynamicAdvisor;
       
       
public class FileLoaderAdvisor extends DynamicAdvisor
       
{
               
private var lastCause:ILoadFileAdvice;
               
private var lastAdvice:AsyncMethodAdvice;
               
               
[Trigger(triggerTiming="before")]
               
public function beforeFileLoad(cause:ILoadFileAdvice,
                                        advice
:AsyncMethodAdvice, time:String):void{
                        lastCause
= cause;
                        lastAdvice
= advice;
                       
var loader:URLLoader = new URLLoader();
                        loader
.addEventListener(Event.COMPLETE, onLoadComplete);
                        loader
.load(new URLRequest(cause.fileURL));
               
}
               
protected function onLoadComplete(e:Event):void{
                        lastCause
.message = ((e.target as URLLoader).data as String);
                        lastAdvice
.adviceContinue();
               
}
       
}
}

Much of this should be familiar, the class loads in a file and gets it's content. Have a look at the beforeFileLoad function’s parameters though, it is noteworthy that this class never references the MouseClickAdvice class, meaning that any other Advice class that implements ILoadFileAdvice will now be picked up by this class and load a file. Also of note is that the second parameter of the beforeFileLoad function is an AsyncMethodAdvice instance, this tells Sodality to wait until the “adviceContinue’ method is called on this instance before other listeners to this advice continue getting called.

The order in which the listeners get called is based on the “triggerTiming’ property within the “Trigger’ metatag. This is the reason that the 'afterMouseClick' method on the SodalitySimpleDemo will wait until after the file is loaded (and lastAdvice.adviceContinue() is called in 'onLoadComplete') before it gets called and sets the message into the TextField.

Before the new Advisor will do anything though, it must be added to the president. You can see it extends DyamicAdvisor instead of implementing IAdvisor (like SodalitySimpleDemo did), this is because non-visual Advisors need a little more logic to be added to the President. The first way a DynamicAdvisor can be added to the President is using the President's addAdvisor method, this is not the recommended way of doing it because it means references to the president must be passed around your application, tangling your logic. The preferrable way is to set the advisorDisplay property of the new Advisor to a DisplayObject which is added to the display hierarchy (beneath the original display you passed into the President when you created it).

Add this to the constructor of SodalitySimpleDemo to add the new Advisor:

var fileLoaderAdvisor:FileLoaderAdvisor = new FileLoaderAdvisor();
fileLoaderAdvisor
.advisorDisplay = this;

The final touch is to modify the onMouseClick method to use the file's URL (as opposed to "Button Clicked"):

protected function onMouseClick(e:MouseEvent):void{
        dispatchEvent
(new MouseClickAdvice("file.txt"));
}

Back to top

5. Checkpoint 2

Now, when you run the app, click the red box, the file will be loaded in and it’s contents will be shown in the TextField.

The FileLoaderAdvisor class and the ILoadFileAdvice should be as shown in Step 4 above (they won’t change throughout the tutorial).

Your classes should look like this:

SodalitySimpleDemo.as

package
{
       
import flash.display.Sprite;
       
import flash.events.MouseEvent;
       
import flash.text.TextField;
       
       
import org.farmcode.sodality.President;
       
import org.farmcode.sodality.advisors.IAdvisor;
       
       
public class SodalitySimpleDemo extends Sprite implements IAdvisor
       
{
               
private var president:President;
               
private var resultField:TextField;
               
               
public function SodalitySimpleDemo()
               
{
                        president
= new President(this);
                       
                       
var button:Sprite = new Sprite();
                        button
.graphics.beginFill(0xff0000);
                        button
.graphics.drawRect(100,100,100,100);
                        button
.addEventListener(MouseEvent.CLICK, onMouseClick);
                        button
.buttonMode = true;
                        addChild
(button);
                       
                        resultField
= new TextField();
                        addChild
(resultField);
                       
                       
var fileLoaderAdvisor:FileLoaderAdvisor = new FileLoaderAdvisor();
                        fileLoaderAdvisor
.advisorDisplay = this;
               
}
               
protected function onMouseClick(e:MouseEvent):void{
                        dispatchEvent
(new MouseClickAdvice("file.txt"));
               
}
               
[Trigger(triggerTiming="after")]
               
public function afterMouseClick(cause:MouseClickAdvice):void{
                        resultField
.text = cause.message;
               
}
       
}
}

MouseClickAdvice.as

package
{
       
import org.farmcode.sodality.advice.Advice;
       
       
public class MouseClickAdvice extends Advice implements ILoadFileAdvice
       
{
               
public function MouseClickAdvice(fileURL:String=null){
                       
super();
                       
this.fileURL = fileURL;
               
}
               
               
private var _message:String;
               
private var _fileURL:String;
               
               
[Property(toString="true",clonable="true")]
               
public function get fileURL():String{
                       
return _fileURL
               
}
               
public function set fileURL(value:String):void{
                        _fileURL
= value;
               
}
               
[Property(toString="true",clonable="true")]
               
public function get message():String{
                       
return _message
               
}
               
public function set message(value:String):void{
                        _message
= value;
               
}
       
}
}

Skip to CheckPoint 3

Back to top

6. Intercepting before and after

For the final step, we build an (incredibly rudimentary) load monitor which outputs the time that outputs the time before and after the file loads.

Lets create our Load Monitor class:

package
{
       
import flash.display.Sprite;
       
import flash.text.TextField;
       
import flash.utils.getTimer;
       
       
import org.farmcode.sodality.advisors.IAdvisor;
       
       
public class LoadMonitor extends Sprite implements IAdvisor
       
{
               
private var textField:TextField;
               
               
public function LoadMonitor(){
                        textField
= new TextField();
                        textField
.y = 100;
                        addChild
(textField);
               
}
               
               
[Trigger(triggerTiming="before")]
               
public function beforeFileLoad(cause:ILoadFileAdvice):void{
                        textField
.text = "Load begun: "+getTimer();
               
}
               
[Trigger(triggerTiming="after")]
               
public function afterFileLoad(cause:ILoadFileAdvice):void{
                        textField
.appendText("\nLoad finished: "+getTimer());
               
}
       
}
}

Notice that it has two triggered functions; one before the Advice is executed and one after the Advice executes. Also note that it implements the IAdvisor interface so that the President manages it.

Add these lines to the SodalitySimpleDemo constructor to add the LoadMonitor to our app:

var loadMonitor:LoadMonitor = new LoadMonitor();
addChild
(loadMonitor);

Back to top

7. Checkpoint 3

Now when you run the app and click the red button, several things happen in this order:

  • The LoadMonitor outputs the time before any other Advisors see the advice.
  • The FileLoaderAdvisor begins loading the file, at this point the president waits for confirmation to continue.
  • The file finishes loading, the FileLoaderAdvisor calls the adviceContinue method, telling the President to continue.
  • The SodalitySimpleDemo sets the text in it’s TextField to the contents of the file.
  • The LoadMonitor outputs the time after the advice has been to all Advisors.

This process is done without the Advisors being aware of each other, allowing for a discreet relationship between code frameworks. The only thing tying the operations together is the Advice class which simply stores and transfers data.

NOTE: You may be wondering why the LoadMonitor’s beforeFileLoad function gets called before the FileLoaderAdvisor’s beforeFileLoad method, this is based on the order in which the Advisor’s are added. This is not an issue with the prebuilt Advisors and more control over ordering is coming in version 4 (see the roadmap in the repository).

Your main class should look like this (the MouseClickAdvice class didn’t change in this step):

SodalitySimpleDemo.as

package
{
       
import flash.display.Sprite;
       
import flash.events.MouseEvent;
       
import flash.text.TextField;
       
       
import org.farmcode.sodality.President;
       
import org.farmcode.sodality.advisors.IAdvisor;
       
       
public class SodalitySimpleDemo extends Sprite implements IAdvisor
       
{
               
private var president:President;
               
private var resultField:TextField;
               
               
public function SodalitySimpleDemo()
               
{
                        president
= new President(this);
                       
                       
var button:Sprite = new Sprite();
                        button
.graphics.beginFill(0xff0000);
                        button
.graphics.drawRect(100,100,100,100);
                        button
.addEventListener(MouseEvent.CLICK, onMouseClick);
                        addChild
(button);
                       
                        resultField
= new TextField();
                        addChild
(resultField);
                       
                       
var fileLoaderAdvisor:FileLoaderAdvisor = new FileLoaderAdvisor();
                        fileLoaderAdvisor
.advisorDisplay = this;
                       
                       
var loadMonitor:LoadMonitor = new LoadMonitor();
                        addChild
(loadMonitor);
               
}
               
protected function onMouseClick(e:MouseEvent):void{
                        dispatchEvent
(new MouseClickAdvice("file.txt"));
               
}
               
[Trigger(triggerTiming="after")]
               
public function afterMouseClick(cause:MouseClickAdvice):void{
                        resultField
.text = cause.message;
               
}
       
}
}

 

8. Download Source


Need an Umbraco Master?

Here at Simon Antony, we have an in house certified Umbraco Grand Master available for hire. Got a problem with your site, need architecture advice, give us a call to speak to Simon directly and see how we can help

Contact Simon Today!