Just recently got a question from someone on how to work around the fact that you cannot create custom component UI for Flash Authoring components with AS3. We are all enjoying this new world of AS3, so what a pain to have to create custom comonent UI, even for your AS3 components, using AS2!
Now it is very reasonable to assume that it isn’t possible to create custom component UI with AS3, because there is no documentation and no article, no nothing, saying how to do this. And why would we go to all the effort to code and test a feature and not TELL anyone about it. We wouldn’t do that, right?
Well, we did that.
Note that this stuff doesn’t work in Flash CS3, but it will work in CS4. So if someone tries to use a component using an AS3 custom UI, the UI will show up in CS3 but it won’t actually work. So that is a pain.
OK, let’s back up. What is a custom component UI. This ancient article explains the very basic idea of it. Basically it is a SWF that shows up in the component inspector instead of the list of name/value pairs. You connect it to your component via the Component Definition dialog, which I’m sure you are already familiar with if you create components.
I made a sample AS3 custom component UI and you can download the zip. In the zip, you’ll find
- CustomUIAS3.fla and .as, the source for the custom UI.
- CustomUIAS3TestComponent.fla and .as, the source for the component that uses the custom UI.
- ComponentTest.fla, a test file with the final published component on stage and the custom UI working.
You can immediately open ComponentTest.fla and play around with the interface. It looks like this:
Mine is a little ugly in that it requires you to click the Update button at the bottom for the version on stage to be updated. However, there is no reason that a real UI needs to do this. It can use all the events available in AS3 to be clever about when to update the instance on stage.
The basics are pretty basic, and I think definitely cleaner and nicer than the old AS3 xch way. Instead, it uses ExternalInterface
.
To get your custom UI updating based on selection in authoring, you need to implement a public function onUpdate(...updateArray:Array):Boolean
and expose it via ExternalInterface
. You can either do this by making connecting a custom class to you main timline (which is what i did) or you can put the functions in a frame script on your main timeline. This function should always return true
.
The updateArray
that is passed in is an alternating list of name/value pairs. So for example if your component has two properties, label:String
and num:Number
, you might get the array: [ "label", "asdf", "num" 0 ]
. You just need to step through this array and update your UI as appropriate.
Passing the updated information back uses an array formatted the same way. In fact in my example, I found it useful to stash the array passed into onUpdate()
, update that array and then pass it back. The way you call back into Flash with your update is by calling ExternalInterface.call("updateProperties", propertiesArray)
.
You can even support complex types this way. In my example, I set a dataProvider
property on a ComboBox component. With a collection type like this, instead, of getting a simple value in the array, you get an Object
with a collectionArray:Array
on it. I think my example spells out how to work with it pretty well.
I’ll leave you with the code for the CustomUIAS3
class. Hope you found this helpful!
package { import fl.controls.*; import fl.events.*; import flash.display.*; import flash.events.*; import flash.external.*; public class CustomUIAS3 extends Sprite { public var label_txt:TextInput; public var toggle:CheckBox; public var color:ColorPicker; public var dataGrid:DataGrid; public var num_txt:TextInput; public var plus_btn:Button; public var minus_btn:Button; public var submit_btn:Button; private var propertiesArray:Array; public function CustomUIAS3() { stage.scaleMode = StageScaleMode.NO_SCALE; if (ExternalInterface.available) { ExternalInterface.addCallback("onUpdate", onUpdate); submit_btn.addEventListener(MouseEvent.CLICK, updateAuthoring); } plus_btn.addEventListener(MouseEvent.CLICK, doPlus); minus_btn.addEventListener(MouseEvent.CLICK, doMinus); } private function doPlus(e:Event):void { dataGrid.dataProvider.addItem({label: "label", data: "data"}); } private function doMinus(e:Event):void { dataGrid.dataProvider.removeItemAt(dataGrid.selectedIndex); } public function onUpdate(...updateArray:Array):Boolean { propertiesArray = updateArray; for (var i:int = 0; i + 1 < propertiesArray.length; i += 2) { var name:String = String(propertiesArray[i]); var value:* = propertiesArray[i+1]; switch (name) { case "label": label_txt.text = value; break; case "toggle": toggle.selected = value; break; case "color": color.selectedColor = value; break; case "num": num_txt.text = String(value); break; case "dataProvider": var collectionArray:Array = value.collectionArray; var dp:DataProvider = dataGrid.dataProvider; dp.removeAll(); for (var j:int = 0; j < collectionArray.length; j++) { var item:Object = collectionArray[j]; dp.addItem({label: item.label, data: item.data }); } break; } } return true; } private function updateAuthoring(e:Event):void { for (var i:int = 0; i + 1 < propertiesArray.length; i += 2) { var name:String = String(propertiesArray[i]); switch (name) { case "label": propertiesArray[i+1] = label_txt.text; break; case "toggle": propertiesArray[i+1] = toggle.selected; break; case "color": propertiesArray[i+1] = color.selectedColor; break; case "num": propertiesArray[i+1] = Number(num_txt.text); break; case "dataProvider": var propObj:Object = propertiesArray[i+1]; var collectionArray:Array = new Array(); var dp:DataProvider = dataGrid.dataProvider; for (var j:int = 0; j < dp.length; j++) { var item:Object = dp.getItemAt(j); collectionArray.push({label: item.label, data: item.data}); } propObj.collectionArray = collectionArray; break; } } ExternalInterface.call("updateProperties", propertiesArray); } } }