How To Build a Behavior in Reflex

Update: If you don’t know what Reflex is, I’ve just introduced Reflex.

Building a component in Reflex consists of 3 things: making the component class, building the behaviors, and creating the skin. The component class is an extension of Component with your new component’s name and a few public properties that you feel might be important to that type of component. The real logic of the component goes into its behaviors, so that is where we are starting today.

What is a Behavior?

A behavior is a well defined piece of functionality that may be used by one or more components. Behaviors do not necessarily map directly to a component type. Sometimes they are planned and sometimes they are refactored into separate pieces for reuse. For example, in Reflex the ScrollBar uses a ScrollBehavior and a StepBehavior. Because the scroll bar’s data is similar to a numeric stepper, and because the buttons on a numeric stepper and a scroll bar have the same behavior, ScrollBehavior was broken down into a ScrollBehavior which handles track clicking and dragging the thumb, and a StepBehavior which handles clicking the up/down buttons.

Behaviors are made up of a target, data, skin parts, and event listeners. The target is defined on the base Behavior class and will get set automatically. The data is any properties that a behavior needs in order to function. It maps to the properties on the component that relate to this particular behavior and are bound to the target’s equivalent data. And listeners are set up to respond to the events on the component and parts of the skin. Behaviors make heavy use of data binding to wire everything together, reducing complexity and wiring code. This is one reason why optimizing data binding has been such a big focus for us.

Note that Reflex does not follow a purist MVC approach. Behaviors consist of the controller (“C” in MVC) functionality as well as duplicating the model (“M” in MVC) data. This allows our pieces to work independently with the added benefit of allowing simple Sprite’s and MovieClip’s to become components by virtue of the behaviors that target them. They don’t need to be a scroll bar in order to work like one.

Building a Behavior

We will use an example to build our behavior. Let’s say we have a button component, and we want it to vibrate when you click on it when it is disabled. Sort of a “no no, don’t do that” thing. Guess what, we don’t need to extend Button or even ButtonBehavior to do this. Avoid extending old functionality when adding new functionality. In fact, avoid extending as much as possible in Reflex except for the base classes. We want to stay away from inheritance trees.

First we create our DisabledBuzzBehavior class, extending Behavior to give us our target property and a couple of unnecessary but nice helper methods.

Then we want to add our data. The only thing we need to know about is whether the button is enabled or disabled. We don’t care what the label is or whether the button is selected (like checkbox or radio button). So at the top of the class add a bindable disabled property.

public class DisabledBuzzBehavior extends Behavior
{
	[Bindable]
	public var disabled:Boolean;

	public function DisabledBuzzBehavior(target:InteractiveObject=null)
	{
		super(target);
	}
}

We want our disabled property to always be the same as the component’s (or target’s) disabled property, so we will start setting up our bindings. We can do this in the constructor and use the little helper methods.

public function DisabledBuzzBehavior(target:InteractiveObject=null)
{
	super(target);
	bindProperty("disabled", "target.disabled");
}

Next we want to know whenever the component is clicked. We could override the target setter to add and remove listeners whenever it is set or unset, but we have binding methods for that too which is better.

public function DisabledBuzzBehavior(target:InteractiveObject=null)
{
	super(target);
	bindProperty("disabled", "target.disabled");
	bindEventListener(MouseEvent.CLICK, onClick, "target");
}

private function onClick(event:MouseEvent):void
{
}

Note that in Reflex we don’t worry so much about making everything “protected” because instead of subclassing everything you can simply replace it with your own version. Logic is broken up enough that you don’t have to make everything protected unless you know someone will want to subclass your behavior.

Now all we have to do is add the logic to our onClick method. This is stuff that isn’t Reflex specific and you will be determining on your own for your behaviors.

private var count:int;
private var originalX:Number;
private var intensity:Number = 4;

private function onClick(event:MouseEvent):void
{
	if (!disabled) return; // only react if we're disabled

	count = 0;
	originalX = target.x; // assuming if we got a click that target isn't null
	target.addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
	if (target == null) {
		// the behavior was removed in the middle of it's thing
		event.target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
	} else if (count >= 12) {
		target.x = originalX;
		target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
	} else if (count++ % 2) {
		target.x = originalX + intensity;
	} else {
		target.x = originalX - intensity;
	}
}

You can see that we may need to store additional information other than the component data in order to get our job done. But this doesn’t need to be public API or bindable. You can also see that I’m trying to account for any situations that might break the code to make my behavior as solid as possible (e.g. checking to see if target is still set on each enter frame).

The whole behavior looks like this:

public class DisabledBuzzBehavior extends Behavior
{
	[Bindable]
	public var disabled:Boolean;

	private var count:int;
	private var originalX:Number;
	private var intensity:Number = 4;

	public function DisabledBuzzBehavior(target:InteractiveObject=null)
	{
		super(target);
		bindProperty("disabled", "target.disabled");
		bindEventListener(MouseEvent.CLICK, onClick, "target");
	}

	private function onClick(event:MouseEvent):void
	{
		if (!disabled) return; // only react if we're disabled

		count = 0;
		originalX = target.x; // assuming if we got a click that target isn't null
		target.addEventListener(Event.ENTER_FRAME, onEnterFrame);
	}

	private function onEnterFrame(event:Event):void
	{
		if (target == null) {
			// the behavior was removed in the middle of it's thing
			event.target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
		} else if (count >= 12) {
			target.x = originalX;
			target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
		} else if (count++ % 2) {
			target.x = originalX + intensity;
		} else {
			target.x = originalX - intensity;
		}
	}
}

Update: The following is how you would do the same with metadata.

public class DisabledBuzzBehavior extends Behavior
{
	[Bindable]
	[Binding(target="target.disabled")]
	public var disabled:Boolean;

	private var count:int;
	private var originalX:Number;
	private var intensity:Number = 4;

	[EventListener(type="click", target="target")]
	public function onClick(event:MouseEvent):void
	{
		if (!disabled) return; // only react if we're disabled

		count = 0;
		originalX = target.x; // assuming if we got a click that target isn't null
		target.addEventListener(Event.ENTER_FRAME, onEnterFrame);
	}

	private function onEnterFrame(event:Event):void
	{
		if (target == null) {
			// the behavior was removed in the middle of it's thing
			event.target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
		} else if (count >= 12) {
			target.x = originalX;
			target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
		} else if (count++ % 2) {
			target.x = originalX + intensity;
		} else {
			target.x = originalX - intensity;
		}
	}
}

and can be added at runtime to a certain button or to all buttons by adding it to the stylesheet.

<rx:Button>
	<rx:behaviors>
		<rx:ButtonBehavior/>
		<behaviors:DisabledBuzzBehavior/>
	</rx:behaviors>
</rx:Button>

or

Button behaviors {
	button: ClassReference("reflex.skins.ButtonBehavior");
	disabledBuzz: ClassReference("my.behaviors.DisabledBuzzBehavior");
}

More about the weird CSS in a later post. And the skin parts hookup is still under review, so I didn’t include an example with that workflow yet until we have it solidified.

So now you can start building out behaviors without any skins or components. You have everything you need right inside a behavior.

5 Responses to “How To Build a Behavior in Reflex”

  1. sitron Says:

    man, this DOES look interesting! need to find some time to give it a try!

  2. codecraig Says:

    nice post, good to see some documentation rolling out. thanks!

  3. Robert Penner Says:

    Love it! This should help a lot with promoting composition over inheritance. The code structure reminds me a bit of a Robotlegs mediator using constructor injection.

  4. paddy Says:

    bindEventListener() looks like a perfect opportunity to use Mr.Penners NativeSignal instead? http://robertpenner.com/flashblog/2009/09/as3-signals-getting-stronger.html

  5. Jacob Wright Says:

    The bindEventListener method sets up a binding that adds the event listener once the binding resolves and removes the event listener if it changes. This is a very common situation which makes it a very useful addition to binding.

    The NativeSignal adds a nice API for adding listeners to, but doesn’t buy us anything in this situation. I like Signals. But behaviors are all about listening to user input, which is all done through Flash’s native event system. Adding Signals here would just add size and would need to be in addition to the binding anyways.

    We are allowing metadata to do the same thing too. I’ve added an example showing how it is done with metadata.