In an AIR application, I have a private variable and a setter:
private var _saveResult
public function set saveResult( result:String ):void
{
_saveResult = result;
dispatchEvent( new resultUpdatedEvent( _saveResult ));
}
The first time that I set "saveResult" the event fires. But it will never fire again unless I restart the application.
If I change the setter to:
public function set saveResult( result:String ):void
{
_saveResult = result;
if ( result != null)
{
dispatchEvent( new resultUpdatedEvent( _saveResult ));
}
}
The problem goes away, I can set the variable many times and the event fires every time.
My question:
Am I doing something wrong here? If not, can anyone explain to me whats happening? If so, what SHOULD I be doing?
Thanks!
It looks like you're constructing your event incorrectly. The first parameter of an Event object should always be a string. So in this case you'd want to always use the same string so you could listen for the event. What does your resultUpdatedEvent class look like? You'll want it to look something like this:
package myEvents
{
import flash.events.Event;
public class PropertyChangeEvent extends Event
{
public static const PROPERTY_CHANGE:String = "propertyChange";
public var result:String = "";
// Public constructor.
public function PropertyChangeEvent (type:String,
result:String="") {
// Call the constructor of the superclass.
super(type);
// Set the new property.
this.result= result;
}
// Override the inherited clone() method.
override public function clone():Event {
return new PropertyChangeEvent(type, result);
}
}
}
That way, when you go to dispatch your event, you can construct the event as follows:
new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE, result);
That way, you're listening for the event "PropertyChangeEvent.PROPERTY_CHANGE", which never changes. The problem is now your event listener is probably listening for an event represented by the string saved in result, and obviously, this changes after the first time it's set, so there's no way to assign a listener to it.
For more information about how events work in AS3: http://livedocs.adobe.com/flex/3/html/help.html?content=events_02.html
Per the comments...
There was no event dispatcher problem.
I misdiagnosed the problem, the REAL problem was that if you have a [Bindable] property and you use a setter, and you set it for the current value, flex will ignore it. SO, you have several choices:
1) give the getter and setter different names. Seems like a "bad idea" but it does fix the problem.
2) remove [Bindable] from either the class (my problem) or the property. If the class does not implement IEventDispatcher, you will need to do so. You can simply "extends Sprite" to see it work, but that seems like a "bad idea" as a solution, so I implemented IEventDispatcher per the example at the end of this page: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/IEventDispatcher.html
3) I am sure that there is a way to get around this bug, but I don't actually NEED the class to be [Bindable] so I did not find a work around.
Related
Help me understand data Binding
When I create a variable in a class:
[Bindable] private var _name:String;
and then generate getters and setters, I get:
private var _name:String;
[Bindable]
public function get name():String
{
return _name;
}
public function set name(value:String):void
{
_name = value;
}
Why does it generate the tag '[Bindable]' only on the get function?
To me, it would seem that it should be on the set function, as I would want to know when the
value changes, not when the value is just read.
What might help to understand what is going on here is the code that the MXML compiler will generate for you when you make something [Bindable]. The MXML compiler wraps your [Bindable] property in it's own getter/setter. It does this so that the wrapper setter method can dispatch a "propertyChange" event when a new value is set. This event notifies the parties binding to the property that the value has changed.
Getters/setters in Actionscript are considered to be properties of the object (they are not methods of the object). So it doesn't matter whether your annotate the getter or the setter as [Bindable], the generated code does the right thing.
It's worth noting that you can avoid the generated code and optimize the situation by dispatching your own event when your property changes. To do this, your [Bindable] metadata tag needs to include the event name that will be dispatched when the property changes:
private var _name:String;
[Bindable("nameChanged")]
public function get name():String
{
return _name;
}
public function set name(value:String)
{
if (_name == value)
return;
_name = value;
dispatchEvent(new Event("nameChanged"));
}
Because the bindable metadata contains an event string, no extra code is generated. Note, the compiler won't warn you if you forget to dispatch the event from the setter. In fact, you can dispatch your custom binding event from anywhere in your class (this can be useful with functions that are bindable).
In Flex (Flash Builder 4), I need a way to know if something in an array collection has changed.
I have a custom object:
[Bindable]
public var _myobject:MyObject;
It's basically just a class containing a bunch of different String and Number properties.
I need a reliable way to know if any of the properties have been changed. For example, I am binding the properties to a user interface (fields), but it's also possible for some of the properties to change through code.
Is there a way to do this? I found ChangeWatcher, but that looks like it only looks at a single simple property, such as a String or Number. I need to watch or detect changes in all the properties in my object, hopefully without having to add ChangeWatcher events to every property. Is this possible?
You're probably better off just dispatching binding events on the specific properties you want bindable. Better yet, dispatch a custom binding event, so that all of the things that are bound don't have to filter for "is this the property I care about?" It's really easy with Flash Builder 4.5 to do this, just select your variable name and press Ctrl-1, select "Create getter and setter," select getter and setter and check "Bindable" and "create custom event."
This will create code for you that looks something like this:
private var _yourProperty:String;
[Bindable (event='yourPropertyChange')]
public function get yourProperty():String {
return _yourProperty;
}
public function set yourProperty(value:String):void {
if (value !=== _yourProperty) {
_yourProperty = value;
dispatchEvent(new Event('yourPropertyChange'));
}
}
This will be much less verbose and more performant than the code that Flash Builder generates for you behind the scenes when you just use the Bindable tag on its own.
If you use defined classes as VO/DAO and apply the [Bindable] tag to the class, this will do binding on all properties for you (so long as they are read/write).
Anonymous object detection is difficult at best, let alone adding additional headaches of loosing compiler type checking.
Super basic example: - the key is to tie it to the dispatcher, so internally it can send out the PropertyChangeEvent.
[Bindable]
public class Actor extends EventDispatcher
{
public var fName:String;
public var lName:String;
public var age:uint;
public function get displayName():String
{
return lName +', '+ fName;
}
public function Actor()
{
super();
}
}
public class BindableDictionary extends EventDispatcher {
public function BindableDictionary() {
super();
}
public var dictionary:Dictionary = new Dictionary();
[Bindable("change")]
public function get(key:Object):Object {
return dictionary[key];
}
public function put(key:Object, value:Object):void {
dictionary[key] = value;
dispatchEvent(new Event(Event.CHANGE));
}
}
maybe this class will give you some new idea
Ok this probably sounds dumb but Im a complete beginner in Flex programming.
I have an application with a main .mxml file, and a certain class Foo that I call from the .mxml
In Foo, I make a URLRequest and listen for the Complete event. Then I found myself with the returned data in a Foo function, but I have no idea how to communicate it to the .mxml part of the applicaton ! I looked into ArrayCollections but I can't seem to understand how they work and whether it might help. Isn't there a way to modify, from inside the class, a variable with a global scope ?
This sounds like a small application, but if it's a large application you might want to look at a micro-framework like RobotLegs
If you have your Foo class extend EventDispatcher then it will be able to send events and have the main MXML app listen for said events.
package com.example
{
import flash.events.EventDispatcher;
import com.example.events.MyEvent;
public class Foo extends EventDispatcher
{
public function doAction():void
{
var someData:String = "blah";
dispatchEvent(new MyEvent(MyEvent.SOMETHING_HAPPENED, someData));
}
}
}
A Custom event with a payload (in this case a string)
package com.example.events
{
import flash.events.Event;
public class MyEvent extends Event
{
static public const SOMETHING_HAPPENED:String = "somethingHappened";
private var _myData:String;
public function get myData():String
{
return _myData;
}
public function MyEvent(type:String, myData:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
_myData = myData;
super(type, bubbles, cancelable);
}
override public function clone():Event
{
return new MyEvent(type, myData, bubbles, cancelable);
}
}
}
Working with your Foo class from the main file:
public function EventDispatcherExample() {
var foo:Foo = new Foo();
foo.addEventListener(MyEvent.SOMETHING_HAPPENED, actionHandler);
foo.doAction();
}
private function actionHandler(e:MyEvent):void {
trace("my data is: " + e.myData);
}
import mx.core.FlexGlobals;
FlexGlobals.toplevelApplication.varName;
Your Foo class can dispatch an event and have something in you main.mxml listen for that event. I am sure that I could create an example. I think it is under customer events in Flex documentation. This is assuming I understand the question.
As John said, an event is your best choice.
If you'd like some example code, I provided some for a similar question here: Data from Popup to Main Application?
An event might be the best way to do it as it has been stated.
Another approach is to dispatch an event like this
dispatchEvent(new Event('somethingHappened'));
and also create a get method in your class for the data you need to get.
Then all you have to do in your main app is this
var foo:Foo = new Foo();
foo.addEventListener('somethingHappened', actionHandler);
private function actionHandler(e:Event):void
{
trace(foo.memberData);
}
This way might be more suitable if the data should be a class member anyway and if you would like to avoid creating a new event class.
I'm testing some event dispatch code in a Flex app, using FlexUnit's addAsync method for testing that events are dispatched. Great so far, I can ensure that at least one event was fired. However, I want to be a bit more detailed; I want to ensure that exactly the set of events I'm expecting are dispatched. Is there a useful test pattern (or, even, different test framework -- I'm flexible!) to accomplish this?
I tried this code, but it doesn't seem to get invoked the second time:
protected function expectResultPropertyChange(event: Event, numberOfEvents: int = 1): void {
trace("Got event " + event + " on " + event.target + " with " + numberOfEvents + " traces left...");
assertTrue(event.type == ResponseChangedEvent.RESPONSE_CHANGED);
if (numberOfEvents > 1) {
event.target.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, addAsync(expectResultPropertyChange, 1000, numberOfEvents - 1));
}
}
public function testSomething(): void {
requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, addAsync(expectResultPropertyChange, 1000, 2));
requiredQuestion.responseSelected("1", true);
requiredQuestion.responseSelected("2", true);
}
In response to the comment...
What if the event is dispatched
directly? responseSelected doesn't
trigger an asynchronous event on a
composite object, it simply dispatched
the RESPONSE_CHANGED event itself
directly. I'm not seeing how this
approach can be mocked using your
method. Mind you, I'm fuzzy on the
mock testing practice as-is, so I'm
probably missing a simple solution
here.
..in that case you don't need to use a mock or addAsync. Something like this will do:
public function testSomething(): void
{
var requiredQuestion : RequiredQuestion = new RequiredQuestion();
var callCount : int = 0;
requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, function(event : ResponseChangedEvent)
{
callCount++;
});
requiredQuestion.responseSelected("1", true);
requiredQuestion.responseSelected("2", true);
assertEquals(2, callCount);
}
This is going to be a high level example of how a similar problem could be solved using a mocked out object of whatever it is that's doing the asynchronous call. Obviously i can't see your code so i can't give you a precise example.
So, as i said in the comment, you can mock out a dependency in a class to fake asynchronous calls so that they become synchronous. Take the below class
public class RequiredQuestion extends EventDispatcher
{
private var someAsynchronousObject : IAsynchronousObject;
public function RequiredQuestion(someAsynchronousObject : IAsynchronousObject = null)
{
someAsynchronousObject = someAsynchronousObject || new AsynchronousObject();
someAsynchronousObject.addEventListener(Event.COMPLETE, asyncCallComplete);
}
public function responseSelected(id : String, flag : Boolean) : void
{
//Will asynchronously fire the Event.COMPLETE event
someAsynchronousObject.startAsynchrounsCall();
}
protected function asyncCallComplete(event : Event) : void
{
dispatchEvent(new ResponseChangedEvent(ResponseChangedEvent.RESPONSE_CHANGED));
}
}
So by default you are using the concrete class that you want to use unless someAsynchronousObjec is injected into the class via the constructor. AsycnhronousObject probably has it's own unit tests or it's in an external class so you don't really want, or need to be testing its functionality. What you can now do is create a mock object that implements IAsynchronousObject that can be used to fake its behavior. Using the ASMock framework the test could look something like this:
public function testSomething(): void
{
var mockIAsycnhronousObject : IAsynchronousObject =
IAsynchronousObject(mockRepository.createStrict( IAsynchronousObject));
SetupResult.forEventDispatcher(mockIAsycnhronousObject);
SetupResult.forCall(mockIAsycnhronousObject.startAsynchronousCall())
.dispatchEvent(new Event(Event.COMPLETE)); // all calls to the startAsynchronousCall method and dispatch the complete event everytime it's called.
mockRepository.replayAll();
var requiredQuestion : RequiredQuestion = new RequiredQuestion(mockIAsycnhronousObject);
var callCount : int = 0;
requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, function(event : ResponseChangedEvent)
{
callCount++;
});
requiredQuestion.responseSelected("1", true);
requiredQuestion.responseSelected("2", true);
assertEquals(2, callCount);
mockRepository.verifyAll();
}
This is just one example of how mocking can help you unit tests. There's a whole wealth of info out there on mocking although it is still very new to ActionScript (released in December). ASMock is based on the .net Rhino mocks so searching for Rhino mocks should throw up a lot more results if you need help.
Definitely a different way of thinking but once you get into it you tend to wonder how you got by in unit testing without them.
I am rather new to flex and am trying to figure out how to listen for a custom event I have created. Let me give you a little background on my app first.
My directory structure is as follows.
assets
components
control
events
model
util
In the model class (which is bindable) I have an ArrayCollection which will get loaded with data via a web service call and a datagrid in a specific component will bind to that ArrayCollection and display the data. The web service is invoked via a button press which initiates the search through an event.
model/ApplicationModel.as
[Bindable]
public class ApplicationModel
{
//put specific model components here
public var data:ArrayCollection;
//the one instance in this Singleton
private static var _instance:ApplicationModel;
public function ApplicationModel()
{
if (_instance != null) throw new Error("ApplicationModel already exists!");
}
public static function getInstance():ApplicationModel
{
if (_instance == null) _instance = new ApplicationModel();
return _instance;
}
}
components/SearchBox.mxml
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import com.homedepot.di.tp.SCVTools.events.*;
private function doSearch():void
{
var query:String = searchTI.text;
//only want to dispatch an event if there is something to query
if (query) dispatchEvent(new CustomEvent(CustomEvent.UPDATE_START,query));
}
]]>
</mx:Script>
<mx:Label text="Enter Search Term"/>
<mx:TextInput id="searchTI" enter="doSearch()" />
<mx:Button label="Search" click="doSearch()"/>
</mx:HBox>
This search functionality works fine and will return the data that I want. What I need to be able to know is when this web service call is done so the view component can update other aspects of the view accordingly (hide columns of datagrid based on the data returned as an example).
My Application.mxml file will wire up my controller to listen for the CustomEvent. The controller will then delegate the work to actually call the web service and get the data. The delegate will create an HTTPService object and get the data. It will also process results of the HTTPService. I am currently trying to dispatch a new event in the function that handles the result of the HTTPService call. This does not seem to be working and it makes sense since the event will never bubble to my view component.
snippet of control/DataUpdateDelegate.as
public override function parse(event:ResultEvent):void
{
super.parse(event);
try
{
Application.debug("DataUpdateDelegate:parse");
var data:Object = event.result.records.record;
model.data = data as ArrayCollection;
dispatchEvent(new CustomEvent(CustomEevent.UPDATE_END) );
}
catch (error:Error)
{
handleError(error);
}
}
I have tried to wire this UPDATE_END event up in the Application.mxml file but that does not seem to work either.
Does anyone have any suggestions on how my view to listen to this event that is not dispatched from a child component but rather from an ActionScript class that knows nothing about the view component?
Thanks,
Thomas
You could bind a specific function to the data property using BindingUtils when your ApplicationModel is first set in your view:
public function set model(applicationModelInstance:ApplicationModel):void
{
_applcationModelInstance = applicationModelInstance;
BindingUtils.bindSetter(dataRefreshed, _applicationModelInstance, "data");
}
protected function dataRefreshed(value:Object):void
{
// do whatever you need to do here.
// value==applicationModelInstance.data
}
De-registering a binding like this to avoid memory leaks is a bit tricky, but since your ApplicationModel is a singleton you don't need to anyways.