So I had a requirement to add an undo function to my app and I discovered this nifty ChangeWatcher class, but it's not doing exactly what I want and I'm hoping someone knows how.
ChangeWatcher seems to monitor assignments to the variable reference. So when I say:
myXML = <xml/>
it fires off it's function just fine and dandy, but when I say:
myXML.appendChild(thisOtherXMLVar)
I get nothing, even though the variable changes the reference doesn't so ChangeWatcher doesn't fire off the function.
So does anyone know how to get ChangeWatcher to pick up on all changes to a variable?
ChangeWatcher is a really cool class. But it's not going to work unless the property you're trying to watch change is [Bindable].
If you're myXML variable is Bindable, then when you set it to the XML value, it will dispatch an event (default is propertyChange). ChangeWatcher adds an event listener to the object for whatever [Bindable] events have been declared. It does this by getting the Bindable metadata for that variable.
But the XML class itself doesn't have anything Bindable, so calling appendChild won't dispatch any events and ChangeWatcher wont work.
Updating based on your comment, you can just make all of your changes to the XML and then reset the variable. That will then cause the ChangeWatcher to register the update.
Hope that helps,
Lance
Related
Using "introspection" In Flex I can say:
var classInfo:XML=describeType(SomeObject);
Which will list for me the Accessors, Methods And Variables.
(http://livedocs.adobe.com/flex/3/html/help.html?content=usingas_8.html)
But what is the equivalent to programmatically inspect all of an object's possible EVENTS?
(NOT JUST the events for which event listeners have been set, but to somehow step through a list of all VALID EVENTS for which event listeners may POTENTIALLY be set -- I realize that such lists are readily available online, and that's great for cases when I know the object's type at design type, but I require some way to inspect any given displayobject programmatically at runtime, and determine what events, if any, are or may be associated with it.)
There isn't a way.
The Event information defined using the event metadata tag is done only for the purposes of code hinting and documentation via ASDocs. It has no relevance to events that the component may or may not be firing.
At any point in time, a portion of code can call "dispatchEvent" and dispatch that event. This is used very commonly for "somePropertyChanged" events which are used for bindings. These events rarely, if ever, get documented using the event metadata. But, the component still fires them.
Bubbling confuses the issue; because if a component's child fires an event where the bubble property is true; then it is almost the same as that component firing the event. How, programmatically, would you figure that out without some sort of code analyzer?
That said, there is a compiler argument to retain metadata ( keep-as3-metadata at http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_14.html and http://jacwright.com/blog/72/using-your-own-custom-metadata-in-as3/ ).
I believe if you do that, then there is a way to get the event metadata from the component; I thought using describeType. But, remember that metadata is not the whole story in terms of events.
I would like to put an eventListener on an ArrayCollection's length proprety, but I don't know how to go about this.
I want to do this because I only want code to execute when a certain number of things are in a certain ArrayCollection. I want Flex to wait to execute this code over the next object until that length property drops back to an acceptable level. I think I should do this with events instead of a while loop that sits there spouting NOOPs forever (which I don't know how to do either).
Please help, and thanks in advance. SO has been a great help so far.
ArrayCollection will dispatch a "collectionChange" event when its items changed. So you can listen to that event and check the "length" property each time the event is dispatched. Alternatively you can also just bind to the length property via BindingUtils.bindSetter();
You could just add a 'collectionChange' event listener and check if the ArrayCollection.length has changed since the previous event. (You'd need to keep a static or instance copy of the ArrayCollection's length, for comparison.)
The other thought is to create your own lengthChangedEvent, extend ArrayCollection, override the length property and look for then call any lengthChangedEvent listeners in the setter. Then use your extended ArrayCollection in place of the base class wherever you need to listen for the event. This won't work though because the length property is read-only and there is no setter to override. You would have to override every method that could add or remove items from the ArrayCollection and test/call your lengthChangedEvent listener in each case.
I'm re-writing an MXML item renderer in pure AS. A problem I can't seem to get past is how to have each item renderer react to a change on a static property on the item renderer class. In the MXML version, I have the following binding set up on the item renderer:
instanceProperty={callInstanceFunction(ItemRenderer.staticProperty)}
What would be the equivalent way of setting this up in AS (using BindingUtils, I assume)?
UPDATE:
So I thought the following wasn't working, but it appears as if Flex is suppressing errors thrown in the instanceFunction, making it appear as if the binding itself is bad.
BindingUtils.bindSetter(instanceFunction, ItemRenderer, "staticProperty");
However, when instanceFunction is called, already initialized variables on the given instance are all null, which was the cause of the errors referenced above. Any ideas why this is?
You have 2 options that I am aware of:
Option 1
You can dig into the code that the flex compiler builds based on your MXML to see how it handles binding to static properties. There is a compiler directive called -keep-generated-actionscript that will cause generated files to stick around. Sleuthing through these can give you an idea what happens. This option will involve instantiating Binding objects and StaticPropertyWatcher objects.
Option 2
There is staticEventDispatcher object that gets added at build time to classes containing static variables see this post http://thecomcor.blogspot.com/2008/07/adobe-flex-undocumented-buildin.html. According to the post, this object only gets added based on the presence of static variables and not getter functions.
Example of Option 2
Say we have a class named MyClassContainingStaticVariable with a static variable named MyStaticVariable and another variable someobject.somearrayproperty that we want to get updated whenever MyStaticVariable changes.
Class(MyClassContainingStaticVariable).staticEventDispatcher.addEventListener(
PropertyChangeEvent.PROPERTY_CHANGE,
function(event:PropertyChangeEvent):void
{
if(event.property == "MyStaticVariable")
{
someobject.somearrayproperty = event.newValue as Array;
}
});
I think you need to respond to the "PropertyChanged" event.
If you're going to do that, use a singleton instead of static. I don't think it will work on a static. (If you have to do it that way at all, there are probably a couple ways you could reapproach this that would be better).
var instance:ItemRenderer = ItemRenderer.getInstance();
BindingUtils.bindProperty(this, "myProperty", instance, "theirProperty");
After fiddling with this for a while, I have concluded that this currently isn't possible in ActionScript, not even with bindSetter. It seems there are some MXML-only features of data bindings judging by the following excerpt from the Adobe docs (though isn't it all compiled to AS code anyways)?
You cannot include functions or array
elements in property chains in a data
binding expression defined by the
bindProperty() or bindSetter() method.
For more information on property
chains, see Working with bindable
property chains.
Source: http://livedocs.adobe.com/flex/3/html/help.html?content=databinding_7.html
You can create a HostProxy class to stand in for the funciton call. Sort of like a HostFunctionProxy class which extends from proxy, and has a getProperty("functionInvokeStringWithParameters") which will invoke the function remotely from the host, and dispatch a "change" event to trigger the binding in typical [Bindable("change")] Proxy class.
You than let the HostProxy class act as the host, and use the property to remotely trigger the function call. Of course, it'd be cooler to have some TypeHelperUtil to allow converting raw string values to serialized type values at runtime for method parameters (splitted by commas usually).
Example:
eg.
var standInHost:Object = new HostFunctionProxy(someModelClassWithMethod, "theMethodToCall(20,11)");
// With BindingUtils.....
// bind host: standInHost
// bind property: "theMethodToCall(20,11)"
Of course, you nee to create such a utlity to help support such functionality beyond the basic Flex prescription. It seems many of such (more advanced) Flex bindings are usually done at compile time, but now you have to create code to do this at runtime in a completely cross-platform Actionscript manner without relying on the Flex framework.
I have a CircleButton class in Actionscript.
I want to know when someone externally has changed the 'on' property.
I try listening to 'onChange' but it never hits that event handler.
I know I can write the 'on' property as a get/setter but I like the simplicity of just using [Bindable]
Can an object not listen to its own events?
public class CircleButton extends UIComponent
{
[Bindable]
public var on:Boolean;
public function CircleButton()
{
this.width = 20;
this.height = 20;
graphics.beginFill(0xff6600, 1);
graphics.drawCircle(width/2, height/2, width/2);
graphics.endFill();
this.addEventListener(MouseEvent.ROLL_OVER, rollover);
this.addEventListener(MouseEvent.ROLL_OUT, rollout);
this.addEventListener('onChange', onOnChange);
}
private function onOnChange(event:PropertyChangeEvent):void {
If you use the [Bindable] tag without specifying an event type, then when the property changes its value, an event of type: PropertyChangeEvent.PROPERTY_CHANGE, which is the string 'propertyChange', will be dispatched.
Therefore, to be able to register to listen to that event, you need to say:
this.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onOnChange);
The reason why your listener function was never called is that the event type was not correct.
Note that the listener method will be called when any of the variables marked as Bindable in your class changes, not only 'on'. This event comes with a property called 'property' that indicates which variable was changed.
To avoid being called on each Bindable variable, you need to specify an event in the [Bindable] tag:
[Bindable(event="myOnChangeEvent")]
and dispatch that event manually when you consider that the property is changing (ie: in the setter), though that didn't seem to be what you wanted to do.
You could use BindingUtils.bindSetter()
An example is found here.
Just because it is possible for something to bind to the variable, doesn't mean something is bound to the variable. It's a bit like the event system - just because something can dispatch an event doesn't mean anything is listening.
The Classes around which the Flex binding is based are BindingUtils and ChangeWatcher. When you bind directly in MXML (which just gets converted to AS3 by the compiler) it uses these classes behind the scene to actually establish the binding. I've dug around in ChangeWatcher before and when it looks through the list of potentially bindable items it only dispatches if some object is actually listed as a listener. The whole binding system is a smart wrapper around the event system actually.
The semantics of binding in AS3 instead of MXML are different. Subtle differences (like chaining to child properties of Objects) that just work in MXML require work in AS3 to duplicate the behaviour (probably a result of the code generation between MXML to AS3).
Have a look at this Adobe doc for a little info on ChangeWatcher in AS code.
Personally - I do not use binding outside of MXML since I feel it is clumsy. I would suggest you write a setter function instead since it is more predictable (and very likely performant). I also suggest you read through the source code for ChangeWatcher and BindingUtils. That is definitely some of the most advanced AS3 you are likely to read.
One of my favorite approaches is the Observe class which is found here. It is essentially using a setter but it is a nice repeatable approach.
I have a ComboBox that I bind to a standard HTTPService, I would like to add an event listener so that I can run some code after the ComboBox is populated from the data provider.
How can I do this?
Flex doesn't have a specific data-binding events in the way that say ASP .Net does. You have to watch for the dataProvider property like John says in the first answer, but not simply to the combobox or its dataProvider property. Let's say you have a setup like this:
<!-- Assume you have extracted an XMLList out of the result
and attached it to the collection -->
<mx:HttpService id="svc" result="col.source = event.result.Project"/>
<mx:XMLListCollection id="col"/>
<mx:ComboBox id="cbProject" dataProvider="{col}"/>
Now if you set a changewatcher like this:
// Strategy 1
ChangeWatcher.watch(cbProject, "dataProvider", handler) ;
your handler will not get triggered when the data comes back. Why? Because the dataProvider itself didn't change - its underlying collection did. To trigger that, you have to do this:
// Strategy 2
ChangeWatcher.watch(cbProject, ["dataProvider", "source"], handler) ;
Now, when your collection has updated, your handler will get triggered. If you want to make it work using Strategy 1, don't set your dataProvider in MXML. Rather, handle the collectionChange event of your XMLListCollection and in AS, over-write the dataProvider of the ComboBox.
Are these exactly the same as a databound event? No, but I've used them and never had an issue. If you want to be absolutely sure your data has bound, just put a changeWatcher on the selectedItem property of your combobox and do your processing there. Just be prepared to have that event trigger multiple times and handle that appropriately.
You can use a mx.binding.utils.ChangeWatcher as described here.
You can use BindingUtils to get notified when the dataProvider property of the combo box changes:
BindingUtils.bindSetter(comboBoxDataProviderChanged, comboBox, "dataProvider");
BindingUtils lives in the mx.binding.utils package.
I have a longer description of how to work with BindingUtils here: Does painless programmatic data binding exist?
You can also listen for the ResultEvent.RESULT on the HTTPService, that would be called slightly before the combo box got populated I guess, but it might be good enough.
Where are you adding the listener compared to the loading of the data? Is it possible the data is being loaded, and the event fired, before you've added your listener?
#Herms
The listener is definitely added before the web service call, here is an example of what my code look like (I simplified lots of things...):
I have this flex component:
public class FooComboBox extends ComboBox
{
private var service:HTTPService = null;
public function ProjectAutoComplete()
{
service = new HTTPService();
service.url = Application.application.poxmlUrl;
service.addEventListener(FaultEvent.FAULT,serviceFault);
service.addEventListener(ResultEvent.RESULT,resultReturned);
this.addEventListener(FlexEvent.DATA_CHANGE,dataChange);
}
public function init():void
{
var postdata:Object = {};
postdata["key"] = "ProjectName";
postdata["accountId"] = Application.application.accountId
service.send(postdata);
}
private function resultReturned(event:ResultEvent):void
{
this.dataProvider = service.lastResult.Array.Element;
// thought I could do it here...but no luck...
}
private function dataChange(e:FlexEvent):void
{
// combobox has been databound
mx.controls.Alert.show("databound!");
}
...
}
and then in a mxml file I have the FooComboBox with id "foo" and I call:
foo.init();
I need to execute some code after the combobox is completely databound...any ideas?
Maybe the event doesn't trigger when the data provider is first set? Try setting the data provider to an empty array in the constructor, so that it's definitely changing instead of just being initially assigned later in your resultReturned() method. I've no clue if that will help, but it's worth a shot.
Also, you're setting the provider to lastResult.Array.Element. That looks a little suspicious to me, as the data provider should probably be an array. Granted, I have no clue what your data looks like, so what you have could very well be correct, but it's something I noticed that might be related. Maybe it should just be lastResult.Array?
In your example code, try running validateNow() in the resultReturned method. That will force the combo box to commit its properties. The thing is that even though the property is set the new value isn't used until commitProperties is run, which it will do at the earliest on the next frame, validateNow() forces it to be done at once.