Writing components in Flex, part 3

On September 12, 2008, Components,Flex - 3 Comments

Right, it’s been more than a month now, time to round up this tutorial. If you haven’t read the previous parts, here’s part 1, and here’s part 2.

In this chapter we’re going to fine-tune our component. The basic functionality is in it, but there isn’t really much communication with the outside world going on. We can change that using events, meta-tags, asdoc and we are also going to do some styling.

Meta-tags

First off, meta tags. Meta tags are used to instruct the compiler to do certain things and to let your IDE (eclipse) know what properties and events are available.

The most well known tag is of course the [Bindable] tag. For instance, the set watermark function should have a bindable tag, so users can change the watermark dynamically. Just put the [Bindable] tag (case sensitive) in front or just above the function definition, like this:

?View Code ACTIONSCRIPT
[Bindable]
public function set watermark(value:String):void {
	this._watermark = value;
}

With getter / setters, you only need to make one function bindable, the other one will become bindable automatically. You can also send an event with the bindable tag. By default, the propertychange event gets fired, but you can customize it by doing this:

?View Code ACTIONSCRIPT
[Bindable(event="eventname")]

Don’t forget to fire the actual event from within the setter!

Another useful tag is the [inspectable] tag. With this tag, you can let the user know which entries are possible for a variable, and you can set the default value. I’ve added the tag to my setter:

?View Code ACTIONSCRIPT
[Inspectable(defaultValue="", type="String", name="Watermark", format="String")]
[Bindable]
public function set watermark(value:String):void {
	this._watermark = value;
}

For more information about the inspectable tag, see the docs.

With metatags, you can also manage which properties and method are shown in the code-hinting box and in the documentation. You can use the [Exclude] tag for that. This one can be handy if you have a multiclass component which some functions you only want to use internally. You can use the exclude tag in two ways.

  1. Place it directly in front of the method or property (or even class!)
  2. Place it on top of the class and specify the property inside the tag, like this: [Exclude(name="direction", kind="property")]

We don’t have anything to hide in our component, so we won’t use it

There’s also a style tag, we’ll explain that one later…

And there are many more tags, for more information, I can recommend The Flex Non-Docs.

Events

Almost every component is useless if it doesn’t fire some (sensible) events. In our example, it might be a good idea to send of an event when the watermark is changed, or when it’s visibility is changed. We can use the [Bindable(event)] notation for that, choose an event name and fire it, like this:

?View Code ACTIONSCRIPT
[Inspectable(defaultValue="", type="String", name="Watermark", format="String")]
[Bindable(event="watermarkChanged")]
public function set watermark(value:String):void {
	this._watermark = value;
	this.dispatchEvent(new Event("watermarkChanged"));
}

Now, you need a way to let users know which events are available for your component. You can do that with… another meta tag. Place an [Event] metatag in front of your class definition:

?View Code ACTIONSCRIPT
[Event(name="watermarkChanged", type="flash.events.Event")]
[Event(name="watermarkVisibilityChanged", type="flash.events.Event")]
public class WatermarkedTextbox extends TextInput {

and provide the name of the event, and the type. Now, the events will show up in intellisense and in MXML. Every component has other demands, and thus other events. Take some time to think over which events are likely to be used in your component, and which data should be send with the event. Sometimes you’ll have to extend the default events of flex and create your own.

Styles

Next up, styles, lets get rid of that ugly default style of flex and add some groovy colors. I’ve made it easy for myself, since the watermarkedinput extends the normal textInput, it will inherit it styles also.

The first style we’ll define is the watermarkColor, so we can change that in mxml, or by CSS. Add the style tag just before the class definition, the same place as the event metatags:

?View Code ACTIONSCRIPT
[Event(name="watermarkChanged", type="flash.events.Event")]
[Event(name="watermarkVisibilityChanged", type="flash.events.Event")]
[Style(name="watermarkColor",type="Number",format="Color",inherit="yes")]
public class WatermarkedTextbox extends TextInput {

Next, we’ll need to listen to the style changes. Flex has a build in method for that, called styleChanged. Let’s override that and add some logic to it:

?View Code ACTIONSCRIPT
override public function styleChanged(styleProp:String):void {
 
	super.styleChanged(styleProp);
 
	if(styleProp == "watermarkColor") {
		_bWatermarkColor = true;
		invalidateDisplayList();
		return;
	}
}

I’ve added an if statement to check if the style that has been changed is my custom style. If so, than set _bWatermarkColor, a new private property of our component, to true, and call the invalidateDisplayList method. The invalidateDisplayList calls the updateDisplayList method, where we will add some logic to update the component. We’ve allready extended the updateDisplayList method, let’s go ahead and add some lines of ceudth to it.

?View Code ACTIONSCRIPT
if(_bWatermarkColor) {
	_bWatermarkColor = false;
	this._label.setStyle("color", this.getStyle("watermarkColor"));
}

This checks if there were changes to the style, and if there were, update the _label. Next we need to set the default styles of the component. I’ve ripped this part from the documentation. Basically, what it does is just checking if there’s a style defined for our lovely Watermarked textbox, and if that;s the case, it will use that one. If not, create a new style definition and use that one. I think the code speaks for itself:

?View Code ACTIONSCRIPT
// Define a static variable.
private static var classConstructed:Boolean = classConstruct();
 
// Define a static method.
private static function classConstruct():Boolean {
	if (!StyleManager.getStyleDeclaration("WatermarkedTextbox"))
	{
		// If there is no CSS definition for StyledRectangle,
		// then create one and set the default value.
		var newStyleDeclaration:CSSStyleDeclaration =
			new CSSStyleDeclaration();
		newStyleDeclaration.setStyle("watermarkColor", 0x333333);
		StyleManager.setStyleDeclaration("WatermarkedTextbox", newStyleDeclaration, true);
	}
	return true;
}

Now, we’re almost there… The default style is still not showing up, because the _label (our watermark) doesn’t know it has to listen to the property watermarkColor. We’ve set it in the stylechange method, but apparently that method is not called at initialization. To solve this, set the _bWatermarkColor default value to true. That way the first time the displayList is updated, the styles are also updated.

Well, that’s it for now. I’ll move the part about documenting to part four. We now have a watermarked textbox which sends out events, is stylable and is better accessible by mxml or the design view.
I hope you enjoyed this chapter, and see you soon for part four!

Cheers!

p.s. Almost forgot, the component so far is here:

Right click for source code of the example, the source code of the component itself is here:

?View Code ACTIONSCRIPT
package nl.flexperiments.components.text {
 
	import flash.events.Event;
	import flash.events.FocusEvent;
 
	import mx.controls.Label;
	import mx.controls.TextInput;
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;
 
	[Style(name="watermarkColor",type="Number",format="Color",inherit="no")]
	[Event(name="watermarkChanged", type="flash.events.Event")]
	[Event(name="watermarkVisibilityChanged", type="flash.events.Event")]
	public class WatermarkedTextbox extends TextInput {
 
		// Define a static variable.
        private static var classConstructed:Boolean = classConstruct();
 
        // Define a static method.
        private static function classConstruct():Boolean {
            if (!StyleManager.getStyleDeclaration("WatermarkedTextbox"))
            {
                // If there is no CSS definition for StyledRectangle,
                // then create one and set the default value.
                var newStyleDeclaration:CSSStyleDeclaration =
                    new CSSStyleDeclaration();
                newStyleDeclaration.setStyle("watermarkColor", 0xbbbbbb);
                StyleManager.setStyleDeclaration("WatermarkedTextbox", newStyleDeclaration, true);
            }
            return true;
        }
 
		private var _label:Label = new Label();
 
		private var _bWatermarkColor:Boolean = true;
 
		public function WatermarkedTextbox() {
			super();
			this.addEventListener(FocusEvent.FOCUS_IN, _hideWatermark);
			this.addEventListener(FocusEvent.FOCUS_OUT, _showWatermark);
		}
 
		private function _hideWatermark(event:FocusEvent):void {
			this.watermarkVisible = false;
		}
 
		private function _showWatermark(event:FocusEvent):void {
			this.watermarkVisible = true;
		}
 
		override protected function createChildren():void {
			super.createChildren();
			this.addChild(this._label);
			this._label.text = this.watermark;
		}
 
		private var _watermark:String;
 
		[Inspectable(defaultValue="", variable="_watermark", type="String", name="Watermark", format="String")]
		[Bindable(event="watermarkChanged")]
		public function set watermark(value:String):void {
			this._watermark = value;
			if(this._label) this._label.text = this._watermark;
			this.dispatchEvent(new Event("watermarkChanged"));
		}
 
		public function get watermark():String {
			return this._watermark;
		}
 
		private var _watermarkVisible:Boolean;
 
		[Inspectable(defaultValue=true, variable="_watermarkVisible", type="Boolean", name="Watermark is visible", format="Boolean")]
		[Bindable(event="watermarkVisibilityChanged")]
		public function set watermarkVisible(value:Boolean):void {
			this._watermarkVisible = value;
			this._label.visible = (this.text == "" && value);
		}
 
		public function get watermarkVisible():Boolean {
			return this._watermarkVisible;
		}
 
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			this._label.height = this.height;
			this._label.width = this.width;
			if(_bWatermarkColor) {
				_bWatermarkColor = false;
				this._label.setStyle("color", this.getStyle("watermarkColor"));
			}
		}
 
		override public function set text(value:String):void {
			super.text = value
			this.watermarkVisible = (!value);
		}
 
		override public function set htmlText(value:String):void {
			super.htmlText = value
			this.watermarkVisible = (!value);
		}
 
		override public function styleChanged(styleProp:String):void {
 
			super.styleChanged(styleProp);
			if(styleProp == "watermarkColor") {
				_bWatermarkColor = true;
				invalidateDisplayList();
				return;
			}
		}
	}
}


What others have to say:

1

Don’t you need to also dispatch a watermarkVisibilityChanged event in the setter for watermarkVisible in order to actually get the custom Bindable event working?

2

Yeah you’re right, I forgot that. Tnx!

3

v. impressive work :)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Submit comment