It is tempting to write your own flex components that extend the base components (TextInput, Button, etc), because you want to add some functionality or just add your own zip. Having undertaken such a task many times, I can say that things become a mess. Upgrading to a new SDK breaks things, tempts you to update components to the new SDK, breaking more things, etc. Another problem is that once you make your own super awesome button override, you want to use it everywhere, even though a lot of places won’t use the new functionality you enabled. This complicates and causes things to break… But…
Something I prefer doing is the concept of adding ‘behavoirs’ that extend the functionality of a component, without actually extending it. Your code becomes standard <s:Button /> and you get to add new functionality. It is done simply by creating a class which contains the desired functionality you want to achieve.
Let’s say you want a TextInput component with prompt support. This means, if the TextInput is empty, it will instead show a ‘prompt’ giving the user a hint of what they should type in or labeling the field. A common one is a ‘Search’ field.
You could do one of a few things:
1. You can override the TextInput component with your own and name it ‘TextInput’. The problem with this is confusion, but the benefit is that it’s easy to use and if you need more functionality, you just keep tacking stuff on. It gets pretty ugly, pretty fast.
2. You could create a new component PromptTextInput. The issue with this is, what happens if you want to add another feature on to TextInput. Say built-in validation or maybe a list that pops up below the field with results. Do you make a new component, do you add the features to the PromptTextInput component you just made?
3. Create a ‘behavior’ that sits outside of the component and interacts with the component. This means, no overriding the component, no deciding between one or two, and add features ala-cart for the use case that you are attempting.
A behavior for a component is much like the default Flex validation framework. You write your views using the standard component set, but when you want to add some functionality, you add a behavior. A behavior sits in the <fx:Declerations> tags and references the target component.
Enough talking, here is some code…
<?xml version=“1.0” encoding=“utf-8”?>
<s:Application xmlns:fx=“http://ns.adobe.com/mxml/2009" xmlns:s=“library://ns.adobe.com/flex/spark”
xmlns:mx=“library://ns.adobe.com/flex/mx” minWidth=“955” minHeight=“600”
xmlns:behaviors=“behaviors.*” viewSourceURL=“srcview/index.html”>
<fx:Declarations>
<behaviors:PromptBehavior target=“{i_text}” promptText=“search…”/>
</fx:Declarations>
<s:TextInput id=“i_text” horizontalCenter=“0” verticalCenter=“-15”/>
<s:Button label=“Button” horizontalCenter=“0” verticalCenter=“15”/>
</s:Application>
And the actual Behavior class…
package behaviors {
import flash.events.FocusEvent;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import spark.components.TextInput;
import spark.events.TextOperationEvent;
public class PromptBehavior {
private var _target:TextInput;
private var _promptText:String;
private var _justFocusedIn:Boolean = false;
private var _originalColor:uint;
private var _originalStyle:String;
public function register():void {
if(!_target || !_promptText) {
return;// wait until both set…
}
var input:TextInput = _target;
// save original settings for later
_originalColor = input.getStyle(“color”);
_originalStyle = input.getStyle(“fontStyle”);
// on creation, check to see if we need to add prompt
input.addEventListener(FlexEvent.CREATION_COMPLETE, handleCreation);
// on focus, remove prompt
input.addEventListener(FocusEvent.FOCUS_IN, handleFocusIn);
input.addEventListener(FocusEvent.FOCUS_OUT, handleFocusOut);
//add change listener (if we fill in stuff behind the scenes, remove prompt styling)
input.addEventListener(TextOperationEvent.CHANGE, handleTextChange);
input.addEventListener(FlexEvent.VALUE_COMMIT, handleValueChange);
}
public function unregister():void {
var input:TextInput = _target;
input.removeEventListener(FlexEvent.CREATION_COMPLETE, handleCreation);
input.removeEventListener(FocusEvent.FOCUS_IN, handleFocusIn);
input.removeEventListener(FocusEvent.FOCUS_OUT, handleFocusOut);
input.removeEventListener(TextOperationEvent.CHANGE, handleTextChange);
input.removeEventListener(FlexEvent.VALUE_COMMIT, handleValueChange);
}
public function set target(value:TextInput):void {
_target = value;
register();
}
public function set promptText(value:String):void {
_promptText = value;
register();
}
private function handleCreation(event:FlexEvent):void {
var input:TextInput = event.currentTarget as TextInput;
if (isEmpty(input.text)) {
input.text = _promptText;
addPromptStyle(input);
}
}
private function handleFocusIn(event:FocusEvent):void {
var input:TextInput = event.currentTarget as TextInput;
removePromptStyle(input); //remove prompt if it is active and we just focused in
if (input.text == _promptText) {
_justFocusedIn = true;
input.text = “”;
}
}
private function handleFocusOut(event:FocusEvent):void {
var input:TextInput = event.currentTarget as TextInput;
if (isEmpty(input.text)) {
input.text = _promptText;
addPromptStyle(input);
}
}
private function handleTextChange(event:TextOperationEvent):void {
var input:TextInput = event.currentTarget as TextInput;
removePromptStyle(input);
}
private function handleValueChange(event:FlexEvent):void {
var input:TextInput = event.currentTarget as TextInput;
if (_justFocusedIn) {
_justFocusedIn = false;
return;
}
if (isEmpty(input.text)) {
input.text = _promptText;
addPromptStyle(input);
} else {
removePromptStyle(input);
}
}
private function addPromptStyle(input:TextInput):void {
input.setStyle(“color”, 0x999999);
input.setStyle(“fontStyle”, “italic”);
}
private function removePromptStyle(input:TextInput):void {
input.setStyle(“color”, _originalColor);
input.setStyle(“fontStyle”, _originalStyle);
}
private function isEmpty(value:String):Boolean {
if (value == null || value == “”) {
return true;
}
return false;
}
}
}
What I would like to see next from Adobe is inline support of this stuff, so instead of declaring behaviors in our <fx:Declaration />. We can declare them as children of components.
<s:TextInput>
<be:PromptBehavior />
</s:TextInput>
What I wonder is if we could rewrite many of these components and simply rely on behaviors to add functionality that isn’t absolutely crucial. Perhaps this would speed up the creation and efficiency of the view stack?