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); } } }
Barliesque says:
Hi Jeff,
Thanks for the heads up.
For users of CS3, I posted a work around HERE that does allow AS3 to be used for custom control panels.
There was another technique I’ve been meaning to look into using a LocalConnection to pass messages back and forth between the component and the control panel. As I say, I haven’t investigated the idea… Have you, by any chance?
All the best,
11 April 2009, 8:16 pmDavid Barlia
Craig Grummitt says:
Jeff – just wanted to say welcome to the blogosphere! Your article on creating ActionScript components was a life-saver, and this article comes through again! Great to know there’s someone combatting the dearth of documentation on this topic. Looking forward to seeing what’s to come…
13 April 2009, 3:06 amBarliesque says:
Jeff,
I’m going out of my mind with this. Sometimes it works, sometimes it doesn’t. At the moment, I’m struggling with a simple control panel that has two input boxes, one check box, and two combo boxes. Frankly, I didn’t really even want to do a custom UI, but the built-in parameters list malfunctions as well–if I try to enumerate one string-type parameter, then they *all* are locked into list selection.
So to get around that issue, I’ve made a custom UI, following your example. The combo boxes are unable to apply any changes–which is strange, because in another control panel I have a combo box working fine. Default values that I’ve set in the source (both in variable initialization and in Inspectable metadata tags) do not show up in the Component Definition panel in Flash.
It might be helpful if trace statements in the control panel code would show up in the output panel–and I could’ve sworn that did work–but no such luck.
I hope you’re able to help.
Thanks,
24 April 2009, 4:34 amDavid
Barliesque says:
>sigh< …I got it working. Mostly.
It works well enough that my previous distress call may be safely disregarded. 🙂
…the issue I raised about the Component Definition panel failing to register Inspectable meta-data tags properly, does still stand. If one parameter is enumerated, all strings are turned into list parameters, making free entry impossible.
24 April 2009, 5:02 amJeff Kamerer says:
Barliesque,
Well I’m glad you figured it out. I just responded to your comments over on your blog about the sad fact that a component that uses this technique will have a custom UI that doesn’t work with CS3. I guess if we had really been thinking, we could have added a way to make a custom UI that would work with CS4 but would be ignored by CS3. Ah well.
I’m not sure if I understand your complaint about the component definition dialog. If an inspectable property is specified to be an enumeration, then free entry is not supposed to be allowed. Instead you are required to select from one of the enumerated options. Are you saying that you would want the ability to have an enumerated String property that also allows you to type in arbitrary text? if this is the case, then I agree that you need to create a custom component UI to do this today.
29 April 2009, 9:56 amBarliesque says:
Hi again Jeff,
My post wasn’t so clear… let me try again:
I am defining parameters using inspectable meta data tags in the component source–as opposed to typing them straight into the component definition panel. Some parameters need to be enumerated, while others need to be free-entry strings. Setting a parameter as enumerated, naturally restricts that parameter to the specified values, just as it should. The problem is that there’s a side-effect that causes the other parameters that are *not* enumerated to also be restricted—but with no enumerated values, it becomes impossible to edit their values in the component inspector.
3 June 2009, 4:36 amEzine Articles says:
I thought it was going to be some boring old post, but it really compensated for my time. I will post a link to this page on my blog. I am sure my visitors will find that very useful.
10 December 2009, 3:09 pmclaudiu says:
jeff, your tutorials are really helpful but sadly one of few found over the internet.
i’m having trouble sending an array from custom ui to my component.
i’m building a flower component which builds up a flower (body shape, leaf types, flower type and sizes, colors etc).
i’m trying to send an array from the custom ui (a swf basicly) to the component
(starting from your code in updateAuthoring method) like this
“propertiesArray[i + 1] = shapearray.concat()” but when i trace it back in my component class setter
[Inspectable (defaultValue = null)]
public function set shape(value:Array):void {
trace(“value”, value)
}
the value is null. so how do i send arrays from a custom ui?
p.s (i’m using flash cs4 with fp 10)
18 December 2009, 7:07 pmMorgan says:
I’m currently trying to implement this kind of connection using MXML. Have you tried creating your UI using flex?
22 December 2009, 6:00 pmclaudiu says:
i noticed that if i don’t have the customUI swf in the same directory as the fla containing the component, i don’t get the customUI panel, but just an error message. am i doing something wrong or is this the way it should be.
i’m using “customUI with swf embeded in fla file option”
1 January 2010, 1:18 pmMorgan says:
I’ve posted a 4-part series on how to do this with Flex (including some useful changes)
4 January 2010, 9:03 pmhere
Wilson Thome says:
Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic. If possible, as you gain expertise, would you mind updating your blog with more information? It is extremely helpful for me.
9 January 2010, 5:55 pmjeff says:
@wilson thome: Well thanks for the comment! I have been VERY remiss in blogging, and intended to be much more diligent about it. Sadly I have been very low on time lately as I am an engineer on the Flash Pro team and we are quite busy working on CS5 at the moment. But I will try to find time to put out more information and maybe answer some of the questions and comments that have accumulated over time.
10 January 2010, 7:37 pmjeff says:
@morgan (should this be possible in mxml): I have not tried this, but it should in theory be possible.
10 January 2010, 7:37 pmclaudiul25 says:
does anybody know how to get rid of the authoring mask?
15 January 2010, 6:26 amjeff says:
@claudiul25: i don’t entirely understand your question. Maybe you could give a bit more detail?
15 January 2010, 9:34 amclaudiu says:
i have a plant component and i can modify the height, color, leaf size, type etc. if i set the height of the plant above a limit, the authoring representation is being cropped. i know that in as2 you could implement the onSizeMulti function to get rid of the mask, or whatever it was that cropped the component preview. but in as3 i couldn’t find this function. i hope i was clear enough.:)
15 January 2010, 9:15 pmjeff says:
@claudiu: ok i think i understand. You are saying that your component is trying to draw outside of its bounds in live preview and it is getting cropped. That is an annoying limitation, i agree. There is no way around that problem in Flash Pro CS4, but the behavior should be much better, although probably not perfect, in the next version.
I guess you should be able to work around the problem in CS4 by resizing the component to be big enough to contain everything you are drawing, assuming your component is like the components that come with flash in that they will not scale themselves when resized this way.
18 January 2010, 7:07 pmclaudiul25 says:
thanks jeff, i’ll try it. one more question though. do you think using shared objects, the connection between ui and component would be simplier ?
19 January 2010, 5:55 amjeff says:
@claudiul25: i’m not sure if shared objects can work for this purpose. It might work, but it would be pretty tricky. The nice thing about the method described in this article is that Flash Pro deals with identifying what object is selected and passing the information back and forth. It MIGHT be possible to have both the component and the component UI working together with a shared object (they run in different player instances, but that should be ok with shared objects). But if you had more than one instance of your component on stage, or even multiple FLAs open at the same time with the same component on stage, i’m not sure that with the shared object approach you would have a way of determining which object is selected. If you had access to the MMExecute command from either SWF, so that you could run JSAPI commands, then possibly you could, but i do not think that MMExecute will work from either the component properties custom UI SWF or from the component live preview on stage. Let me know if you are able to get it to work though!
19 January 2010, 1:58 pmclaudiul25 says:
MMExecute commands work in the UI swf, i’ve tested. my aproach would be like this, following your blog example:
when user modifies ui, it writes down a SO and calls updateproperties. in my component class i have only one parameter, “dummy”, which on live preview, when something is modified in the UI, reads the SO that the UI previosly modified. this is ok. i only work with one SO object for all my components.
the nasty thing is the other way around. when i deselect my component on stage and i select another one, that component has to write its data into the SO, and send it to the UI to refresh. Refreshing the UI i know how it’s done. the only thing left is to update my SO at the touch of the component.
how to i do that? so from the inside, the component has to listen when someone selected it in livepreview. kind of like onResize.
one way to solve this is for the UI to call its onUpdate function(which it does when you select a component) then to call a function/setter of the selected comp. (fl.getDocumentDOM().selection[0].parameters[“refreshSO”].value=…) to update the SO, and only then to refresh its data. i haven’t tried it. it might work but it’s ugly.
isn’t there a more cleaner way to do this? i have a lot of data to bounce back and forth and i can’t send objects/array through your method (i don’t know if there’s a bug, you should try it – for my component i use strings and parse them), so keeping track of bugs is really hard.
i wish there was a “onSelect” method…
20 January 2010, 7:11 amjeff says:
i do not know of a way for a component on stage to know it has been selected in stage live preview. I think that is where you hit a roadblock, unfortunately.
20 January 2010, 10:30 pmclaudiul25 says:
i tried last night to implement shared objects the way i thought it worked. it didn’t. 🙁
anyway thanks for your help, jeff. hope cs5 will be more careful with these issues.
21 January 2010, 5:32 ampatrick jansen says:
You can use AS3 with MMExecute(“fl.getDocumentDOM() to pass parameters from UI swf to .fla component on stage. It’s an alternative to your method and also works in CS3
18 August 2010, 4:22 amclaudiul25 says:
what if i want to send via collection method, an array of objects, more complex than {label, data}, something like {baseW, topW, baseColor, topColor, …} .
i’ve build a class similar to fl.data.SimpleCollectionItem, named BranchCollectionItem containing lots of Number and String attributes and in my component i changed to this …
[Collection(collectionClass=”fl.data.DataProvider”, identifier=”item”, collectionItem=”com.plant.component.BranchCollectionItem”)],
but when i want to update my authoring component (the rest of the code is similar to this example) it gives me an error
TypeError: Error #1034: Type Coercion failed: cannot convert Object@29ce14c1 to Array.
can you please tell me why? if you want the sources i can email them.
23 August 2010, 11:16 amwill says:
There does seem to be a bug with
22 August 2013, 12:00 pm[Inspectable(type="Array)]
where when you callExternalInterface.call("updateProperties", ["myArr", ["one", "two"]]);
it sets the values of the array to empty string. Next time you getonUpdate
call. Anybody have a way around this besides serializing them myself?