I’ve been making a lot of components lately and find myself constantly getting confused with the order of operations of Inspectable getters and setters. It seems that they work quite differently in Live Preview and in testing a movie. I’ve always gotten everything working eventually, but decided it was time I sat down and figured out what the heck was going on.
Let’s walk through a class. Here’s our simple class:
class TimingTest extends MovieClip {
public function TimingTest(){
trace("construct");
}
public function set prop1(p):Void {
trace("set1");
}
public function get prop1() {
trace("get1");
}
}
If we make a component that uses that class, and make a compiled clip of that, we get the following trace when we drag a copy to the stage:
construct
Test the movie and we get the same thing. Pretty straighforward.
Now let’s make prop1 Inspectable:
class TimingTest extends MovieClip {
public function TimingTest(){
trace("construct");
}
[Inspectable]
public function set prop1(p):Void {
trace("set1");
}
public function get prop1() {
trace("get1");
}
}
Recompile the clip and drag a copy to the stage. We get this trace:
set1
get1
set1
get1
construct
set1
get1
OK. So it’s calling the getter and setter twice each, then constructing, then calling the getter and setter again! Odd, but OK. But now we test the movie and get this:
set1
get1
construct
This is what messed me up the most. Since the setters always get called AFTER the constructor in Live Preview, I thought they would do the same thing in the movie. But obviously they do not.
It gets stranger. Say we add another Inspectable.
class TimingTest extends MovieClip {
public function TimingTest(){
trace("construct");
}
[Inspectable]
public function set prop1(p):Void {
trace("set1");
}
public function get prop1() {
trace("get1");
}
[Inspectable]
public function set prop2(p):Void {
trace("set2");
}
public function get prop2() {
trace("get2");
}
}
Now we get this in Live Preview:
set1
get1
set2
get2
set2
get2
set1
get1
construct
set2
get2
set1
get1
Flash calls the getter/setters in forward order, then reverse order. Finally it initializes the component and calls the setters again in reverse. Note that the order is the sorted order they appear in the Property Inspector or Component Inspector panel, not the order you define them.
And when we test the movie:
set1
get1
set2
get2
construct
We get the setters in forward order, then the constructor. The same pattern follows for additional Inspectables.
OK. Now what happens if we set a property in the Properties or Component Inspector. Whether we set prop1 or prop2, we get the following:
set2
get2
set1
get1
set2
get2
set1
get1
No matter what property you set, it will call ALL the getters and setters in reverse order. Twice! Hmmm….
But, say we use some ActionScript somewhere in the movie to set one prop1 of the component. We get:
set1
get1
It will only call the one you apply. But, it will call both the setter AND getter. However, if you just retrieve the value of the property, for example by saying:
someVar = myComp.prop1
it will just trace:
get1
It only calls the getter.
OK then. What does all this mean? Here’s what I learned from it:
You have to make setters work in two situations – before the component is initialized and after. Generally you want to store the value in a private variable and then make a call to some function that implements that value. Usually this is draw or invalidate, which redraws the component.
But, if the component has not been initialized yet, you probably don’t want to call any other functions yet, as those functions probably rely on values which are set or movie clips which are attached during initialization. So you can set up a __inited variable which is true after initialization is complete. In your getters and setters, you check __inited before calling draw, invalidate, or whatever function implements the value you just set. Thus, if the setter is running prior to initialization, all it will do is store its value.
Of course this means that during initialization, you must make a call to draw, invalidate or any other implementaion functions that the setters rely on.
Thus we get a very simplified class structure like this:
class TimingTest extends MovieClip {
private var __prop1;
private var __inited = false;
public function TimingTest(){
draw();
__inited = true;
}
public function draw(){
trace(__prop1);
}
[Inspectable (defaultValue="hello")]
public function set prop1(p):Void {
__prop1 = p;
if(__inited){
draw();
}
}
public function get prop1() {
return __prop1;
}
}
This ensures that nothing gets called before it is ready, and also ensures that the values assigned at author time are implemented at run time, AND allows the same properties to be assigned via ActionScript at run time, all pretty seamlessly.
Perhaps this was all pretty obvious, but it sure wasn’t to me!