Creating Custom Spark Components

With the new changes in Spark components many blogs posts (mine included) have focused on how to create Spark skins, but if you want to create your own Spark components ready for custom Spark skinning where do you start? That is the focus of this post. How to properly create a Spark component ready for full Spark skinning.

Some of my posts on Spark Skinning…
Flex 4 Spark Skinning
Flash Catalyst – The Missing Visual Editor
The New Spark Skinning Workflow

In the past most of my development time on custom components was spend thinking up all of the different properties that may be required, specifically properties that would adjust the look and layout of the component.

Now with Spark Components this extra layout development isn’t necessary because of the new spark component separation of layout and logic.

This is going to be much more than a typical blog post with a simple little example that gives an idea of how to use the feature, this is going to end with a full component that you can use.

These days I’ve been working on little projects on the side of my own job – more for fun than profit – and come across components that I needed for my application. I initially thought in the Halo mode and had to turn my brain to think like a “spark”.

Click.

Okay, so now I knew we needed to make a component by laying out it’s required elements and adding in the logic behind the component. Everything else would need to be handled by the skin.

AutocompleteList Component

First I needed an autocomplete component to show a list of options for a user. Breaking this down I needed a List and a TextInput box to hold my user’s typing.

package com.unitedmindset.components
{
    import spark.components.List;
    import spark.components.TextInput;

    /**
     * Autocomplete component based on the List.
     * @author jonbcampos
     *
     */

    public class AutocompleteList extends List
    {
        public function AutocompleteList()
        {
            super();
        }

        //---------------------------------------------------------------------
        //
        //   Skin Parts
        //
        //---------------------------------------------------------------------

        [SkinPart(required="true")]
        /**
         * A skin part that defines a textinput that
         * you use to type in for the autocomplete search.
         */

        public var textInput:TextInput;

        //---------------------------------------------------------------------
        //
        //   Override Methods
        //
        //---------------------------------------------------------------------
        /**
         * @private
         */

        override protected function partAdded(partName:String, instance:Object):void
        {
            super.partAdded(partName, instance);
        }

        /**
         * @private
         */

        override protected function partRemoved(partName:String, instance:Object):void
        {
            super.partRemoved(partName, instance);
        }
}

Through some study of the ComboBox and DropDownList provided in the Flex 4 framework I determined that the best place to start was based off of the “List” component.

Again, don’t think how this component is going to look. The fact that parts are popping up doesn’t matter at this point – you’ll see later how this is accomplished through the skin.

Now I need to add my properties.

dataProvider
The first was making a change to the dataProvider. I needed to make sure that the dataProvider was based off the ICollectionView so that I could use the filter function. This was an easy addition…

        //---------------------------------
        // dataProvider
        //---------------------------------
        /**
         * @private
         */

        private var _oldDataProvider:IList;

        /**
         *  Makes sure to wrap the dataprovider in an ICollection
         *  implementation so that we have filterFunction ability.
         *
         *  @default true
         *
         *  @langversion 3.0
         *  @playerversion Flash 10
         *  @playerversion AIR 1.5
         *  @productversion Flex 4
         */

        override public function get dataProvider():IList
        {
            return super.dataProvider;
        }

        /**
         * @private
         */

        override public function set dataProvider(value:IList):void
        {
            if(_oldDataProvider==value)
                return;

            _oldDataProvider = value;

            //if IList is ICollection view, done
            if(value is ICollectionView)
            {
                super.dataProvider = value;
            }

            //make IList into ICollectionView
            if(value is IList)
            {
                super.dataProvider = new ArrayCollection(value.toArray());
            }
        }

compareLabel & compareFunction
The next properties were properties to help set how the AutocompleteList would filter it’s contents. Here I gave the future developer two options. First, an option for a property in the object to compare against. The second was a function that would be called when comparing the item vs the Autocomplete string. Between the two, I think most developers should be happy.

        //---------------------------------
        // compareLabel
        //---------------------------------
        /**
         * @private
         */

        private var _compareLabel:String = "";

        [Bindable(event="compareLabelChange")]
        /**
         * If the dataProvider is based on the
         * objects, the compareLabel is the property
         * on the object that will be used for the
         * compare function.
         */

        public function get compareLabel():String
        {
            if(_compareLabel==""||_compareLabel==null)
                return super.labelField;
            return _compareLabel;
        }

        /**
         * @private
         */

        public function set compareLabel(value:String):void
        {
            if (_compareLabel != value) {
                _compareLabel = value;
                dispatchEvent(new Event(COMPARELABEL_CHANGE_EVENT));
                if(isDropDownOpen)
                    invalidateList();
            }
        }
        //---------------------------------
        // compareFunction
        //---------------------------------
        /**
         * @private
         */

        private var _compareFunction:Function = null;

        [Bindable(event="compareFunctionChange")]
        /**
         * Custom function to call for filtering
         * the dataprovider.
         */

        public function get compareFunction():Function
        {
            return _compareFunction;
        }

        /**
         * @private
         */

        public function set compareFunction(value:Function):void
        {
            if (_compareFunction != value) {
                _compareFunction = value;
                dispatchEvent(new Event(COMPAREFUNCTION_CHANGE_EVENT));
                if(isDropDownOpen)
                    invalidateList();
            }
        }

Finally I wanted to use a controller created by Adobe for their DropDownList. After reading through the code I could tell that this would help with my component and save me from having to writing all the code myself. Bam! Integration FTW!

        //----------------------------------
        //  dropDownController
        //----------------------------------

        /**
         *  @private
         */

        private var _dropDownController:DropDownController;

        /**
         *  Instance of the DropDownController class that handles all of the mouse, keyboard
         *  and focus user interactions.
         *
         *  Flex calls the initializeDropDownController() method after
         *  the DropDownController instance is created in the constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10
         *  @playerversion AIR 1.5
         *  @productversion Flex 4
         */

        protected function get dropDownController():DropDownController
        {
            return _dropDownController;
        }

        /**
         *  @private
         */

        protected function set dropDownController(value:DropDownController):void
        {
            if (_dropDownController == value)
                return;

            _dropDownController = value;
        }

There are a few other smaller “niceties” that I added to the component, but those you can check out on your own.

Part Added & Part Removed
Now I needed to actually make changes to the Spark Component specific lifecycle parts – partAdded and partRemoved. These parts allow you to set up handlers or other properties when a visual component is added or removed through one simple set of functions.

Here I defined the stubs for where my interactions points will be.

        /**
         * @private
         */

        override protected function partAdded(partName:String, instance:Object):void
        {
            super.partAdded(partName, instance);

            if(instance == textInput)
            {
                textInput.addEventListener(TextOperationEvent.CHANGE, _onTextInput_TextChange);
                textInput.addEventListener(FocusEvent.FOCUS_OUT, _onTextInput_FocusOutHandler);
            }
            else if (instance == dropDown && dropDownController)
            {
                dropDownController.dropDown = dropDown;
            }
        }

        /**
         * @private
         */

        override protected function partRemoved(partName:String, instance:Object):void
        {
            if (dropDownController)
            {
                if (instance == dropDown)
                    dropDownController.dropDown = null;
            }

            super.partRemoved(partName, instance);

            if(instance == textInput)
            {
                textInput.removeEventListener(TextOperationEvent.CHANGE, _onTextInput_TextChange);
                textInput.removeEventListener(FocusEvent.FOCUS_OUT, _onTextInput_FocusOutHandler);
            }
        }

You’ll probably notice that my partAdded and partRemoved are almost the same. On add I attach my handlers, on remove I take them away. It’s that simple. That way everything cleans up nicely.

The next parts of development were specific to making an AutocompleteList component and not very helpful to review… mainly because I’m giving you the code.

But now we have some skinning to do so that future developers have a starting point for skinning.

AutocompleteListSkin
Making the skin was REALLY simple – again, code provided. The one thing that I felt was worth noticing was the PopUpAnchor. This is what you use to show where the List part actually appears. If I don’t use the Popup I can still add and remove the List from the display hierarchy, just the size of the list will effect it’s sibling components.

The rest of the post is finishing touches on your component, not specifically parts you need to do to get your component working.

Now with all the code and a working component I felt like it was time to “pretty up” my component. In doing this there was one main thing I wanted to accomplish – have a default skin included. I wanted this just incase someone dropped my component into their project and didn’t think of skinning. You can do this with a spark button, why not my component?

To get this accomplished “professionally” took a few more steps than I really wanted but it was all worth it. I started by defining a CSS sheet to use… and then realized I don’t have a namespace to reference my component in the CSS sheet. I could just use a simple namespace, but I wanted to make this component library 100%, so I added all the bells and whistles.

So I went back and took care of a few more steps.

1. Defined a manifest file
2. Specified a namespace
3. Used my namespace in the CSS sheet
4. Defined my CSS style with my default skin
5. Included my CSS sheet in the SWC build

To look at these in further detail please follow the next steps.

1 Define a Manifest File
Making a manifest file is easy, it’s just a list of components that you want under the same namespace. All of your library components will still be stored in the swc file (if you so chose), but these are components that specifically are under your own namespace.

Here is my manifest file and I placed it in main src directory.

<?xml version="1.0"?>
<componentPackage>
    <component id="AutocompleteList" class="com.unitedmindset.components.AutocompleteList"/>
</componentPackage>

2. Specify a namespace
You specify the namespace in the compiler arguments. Here is a quick capture of my compiler options.

Compiler Options

3. Use a namespace in the CSS sheet
And now my css sheet with my namespace.

/* CSS file */
@namespace unitedmindset "library://ns.unitedmindset.com";

There are some caveats to this css sheet that I found. Yes, these issues made me feel stupid.
Just as it says in the Livedocs:
1. the css file MUST be named “defaults.css”
2. the css file MUST be at your top level package
3. only one css sheet per SWC

4. Define a CSS style for a skin
And the css sheet with my skin included:

/* CSS file */
@namespace unitedmindset "library://ns.unitedmindset.com";

unitedmindset|AutocompleteList
{
    skinClass: ClassReference("com.unitedmindset.skins.AutocompleteListSkin");
}

5. Include a CSS sheet in the SWC build
And the final compiler options to include the stylesheet in the swc.
Compiler Options

That’s it! Now you have one pretty component that looks 100% straight from the source. Feel free to use the autocomplete component if you wish! :)

As shown above, here is the final component:

No View Source, the code is already shared.

Simply Implemented as such:

<unitedmindset:AutocompleteList dataProvider="{dataProvider}"/>
Share

Comments (11)

[...] This post was mentioned on Twitter by tony murphy. tony murphy said: Latest Adobe News – Creating Custom Spark Components http://feedproxy.google.com/~r/TheWorldInAStateOfFlex/~3/Wur0_B-rNYQ/ [...]

Erich CervantezMay 12th, 2010 at 1:07 pm

Nice post…FYI, the asdoc comments above the override in the source block for “dataProvider” looks like it might need to be updated.

Thanks for the source, is a live demo of the component available?

Jonathan CamposMay 12th, 2010 at 2:54 pm

Thanks Erich, changed the comment. I’ll add a live demo of the component here asap.

praksantJune 2nd, 2010 at 5:55 am

Hi, seems like link to the skin source doesn’t work

Jonathan CamposJune 2nd, 2010 at 8:37 am

Sorry about that, will check it immediately. Here is the link again to the AutocompleteList component.

[...] some new skinning workflows for Flash Builder 4 I’ve created a newer post that goes over creating your own Spark Component and how to package your custom Spark Skin with your own custom Spa…. var a2a_config = a2a_config || {}; a2a_config.linkname="Flex 4 Spark Skinning"; [...]

Javier JulioJuly 2nd, 2010 at 4:33 pm

Jonathan,

Great post, thanks! Putting this to use. I’m wondering if you know how to specify anywhere in the library project the default namespace prefix? For example, in your screenshots it says “unitedmindset”. Is it possible to specify what that is? I’m trying to do that for my library. The URI is library://ns.9mmedia/flex/spark and I’d like to specify what namespace prefix shows up in a CSS or MXML file. Say a shorthand like “nms” so in my css I’d see nms|CheckList or in MXML nms:CheckList. Is this doable?

Jonathan CamposJuly 2nd, 2010 at 6:27 pm

It is semi possible. You would need to change your mxml templates in flash builder to include your custom namespace. Adobe has their namespaces in the template, but if you remove the namespace and then add a spark button, tje default namespaces come up for even adobe components.

Danny KoppingMay 4th, 2011 at 8:23 am

“Required skin part textInput cannot be found.”
:(

Any ideas?

Danny KoppingMay 4th, 2011 at 8:27 am

I used the SWC and it’s working fine now :) strange though… still curious about that issue.

jonbcamposMay 4th, 2011 at 9:28 am

@Danny I’m not sure what you code is, but it seems that you are missing the textInput component. It has to be explicitly id’ed “textInput”

Leave a comment

Your comment