Should I do it like this:
if (DeviceDisplay.IsSpeedAvailable == false){
DeviceDisplay.IsSpeedAvailable = true;
}
or
DeviceDisplay.IsSpeedAvailable = true;
I thought I saw that flex/as3 does an internal check on booleans before they are assigned to make sure you are re-assigning stuff but I can't find it to confirm.
I know this is small stuff but I'd like to know.
If you are using the [Bindable] tag for property notification, the event will not get fired if the value doesn't change. If you look at the generated code, it will check to see if the value has changed. If it has not changed, it does not fire an event. If it has changed, the event fires.
Because of this, you are only complicating your code (and being redundant) by checking before setting the value.
If you are simply setting a normal boolean (without change notification), then you don't gain anything by checking first, so you don't need to check.
If you are using some sort of custom mutator in which setting the value causes a side effect, then you should check it inside of your mutator, but never outside of it. Like this:
public function set foo(value):void {
if(value == _foo)
return;
_foo = value;
doSideEffect();
}
This is the pattern, for instance, that [Bindable] writes for you when you use it (where doSideEffect fires a change event).
In my opinion, this is the only time you should ever check the value of a bool before setting it.
AS doesn't do an internal check before setting a boolean, so if it is important not to reassign a value to it (such as avoiding a change listener from firing), then you should do the check first.
If it is that critical that the value not be re-assigned, you should consider writing your own setter method for it (or overriding an existing) which checks the value before setting the value. This way every assignment to that variable has the check - which means less assumptions and errors down the road.
Here's a test script to illustrate that a check isn't made - which makes sense considering that it would take extra processing to do that automatically.
<mx:Script>
<![CDATA[
import mx.controls.Alert;
private var _bool : Boolean = true;
private function set myBool ( val : Boolean ) : void {
_bool = val;
mx.controls.Alert.show('set!');
}
private function get myBool () : Boolean {
return _bool;
}
]]>
</mx:Script>
<mx:VBox>
<mx:Button label="set true" click="myBool = true;"/>
<mx:Button label="set false" click="myBool = false;"/>
</mx:VBox>
Related
I have a simple ItemRenderer that is causing strange behaviour when I use it in a Spark List of products. It is defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="setProduct()">
<fx:Script>
<![CDATA[
[Bindable]
private var p:MyProduct;
private function setProduct():void
{
p = data as MyProduct;
}
]]>
</fx:Script>
<s:Label text="{p.name}" paddingTop="6" paddingBottom="4"
color="{p.active ? Constants.BLACK : Constants.DISABLED_COLOR}"
fontStyle="{p.active ? 'normal' : FontStyle.ITALIC}"/>
</s:ItemRenderer>
It's basically the same as the default ItemRenderer for a List. I decided to use the variable p so that I wouldn't have to write (data as MyProduct) over-and-over within the Label bindings (this is especially cumbersome in larger, more complex ItemRenderers).
The behaviour it causes seems to "reverse" the items that get displayed, but the actual order of the items remains the same. This happens when a product gets updated with new values. For example, I'll have a list of products A B C D. If I update the properties of any one of them, the List will change the order to show D C B A. But if I click on product D in this reversed list, it will still "load" product A. This post describes a similar problem, but there is no code included, so I'm not sure if it's exactly the same issue.
I tracked this bug down to using the variable p. If I just use (data as MyProduct) within the Label bindings, it works fine. If I take the example from the above link and run the setProduct() method on the dataChange event, the bug goes away and works as expected. However, I'm not convinced that this isn't just coincidental.
Has anyone else experienced something like this? Is the problem directly related to using the creationComplete event? I was thinking that maybe creationComplete was only firing once and throwing everything out of whack. Not sure what any of this has to do with the List reversing the displayed products though.
This is probably due to item renderers being recycled. I would override the data setter instead of using creationComplete, that way you'll be sure you catch changes to data.
override public function set data(value : Object) : void {
super.data = value;
p = value as MyProduct;
}
There are a few things. First off, if you have a value object that you want to be set, then I would generally do it in my set data code.
override public function set data( value:OBject ):void
{
super.data = value;
p = value as MyProduct;
}
But that is relatively small beans compared to what I think the problem really is. The brackets means that the property is bound to that object/object chain. So we have to ask, what is the object bound to in this statement ?
"{p.active ? 'normal' : FontStyle.ITALIC}"
I'm not saying it shouldn't work, but we want to smoke out as many bugs as possible. So if I were you, my code would look like this.
[Bindable] public var fonstStyle:String;
[Bindable] public var color:int;
[Bindable] public var name:String;
private var _p:MyProduct;
override public function set data( value:OBject ):void
{
super.data = value;
_p = value as MyProduct;
fonstStyle = _p.active ? 'normal' : FontStyle.ITALIC;
color = _p.active ? Constants.BLACK : Constants.DISABLED_COLOR;
name = _p.name;
}
I would also get rid of that 'creationComplete' event listener. When working with ItemRenderers, any setting of data-based properties should be done through the 'set data' function.
Now that I look at your code, your renderer is probably setting those variables after being created through the 'creationComplete', and then they aren't being updated.
I need to know the value from which a combo box is changing, when it changes. I've been all through the documentation for the events, and none of them let you know what the value is before user interaction changes it. (currentStateChanging is a total red herring!) Handling the open event and saving the value isn't a solution, because there are other ways to change the value.
I'm using the Flex 3.5 SDK.
Something like this?
var currentVal : Object;
private function onChange(newVal) : void {
// currentVal stores your previous value - do something with it
currentVal = newVal;
}
<mx:ComboBox change="onChange(event.target.selectedItem)"/>
I just used the "changing" event on a Spark ComboBox to solve this very problem but it's not available on the mx version
Also - see this
I've come to the conclusion that there isn't an answer :( The best workaround is to override all possible ways there are to set the value of a combo box, plus handle any events that involve the user changing the value, back up that value and then you have a trail of previous values. Then, put a lot of comments saying
this is a 3.5-necessary kluge! If doing this on another SDK you might have to change it!
=======
I've come up w/a solution, but it's not perfectly reliable (since it makes assumptions about how it will work in other SDKs) and its elegance is wanting:
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml" valueCommit="OnChangeAnyway()" change="OnChange()">
<mx:Metadata>
[Event(name='traceable_change', type="assets.LineComboChangeEvent")]
</mx:Metadata>
<mx:Script><![CDATA[
public static const CHANGE:String = 'traceable_change';
private var m_oOld:Object;
private var m_oNew:Object;
private var m_bCallLaterPending:Boolean = false; //This is necessary, because I found OnChangeAnyway() could be called any number of times before OnChange() is
private function OnChange():void {
var oNewEvent:LineComboChangeEvent = new LineComboChangeEvent(CHANGE, m_oOld); //there's nothing special in this class
dispatchEvent(oNewEvent);
}
private function OnChangeAnyway():void {
if (!m_bCallLaterPending) {
m_bCallLaterPending = true;
callLater(function ():void { m_bCallLaterPending = false;}, []); //presumably, whatever is passed to callLater() will be executed after everything else currently queued
m_oOld = m_oNew;
m_oNew = value;
}
}
]]></mx:Script>
m_oNew is obviously redundant because that value will be available to whatever handles traceable_change, but it does explain why I have to barrel-shift these objects.
There are a couple of reasons why I don't consider this reliable:
It assumes that the valueCommit handler will be called ahead of the change one. On my system, it always seems to, but I don't see that promise anywhere.
It assumes that whatever callLater() calls will be called after change is. See concerns for 1.
How to filter an Arraycollection by "array of values" rather than a single value (simple comparision) , the below code snippet is for filtering by single value now I went into scenario like filter out only price [10,4,1,8] (some random values from the master collection). Is there any better way to do the second code snippet
Filter function simple comparision
private function filterForTestData(item:Object):Boolean{
if(item.price < slider.value) return true;
else return false;
}
Filter by array of values
private function filterForTestData(item:Object,filterBy:Array= [10,4,1,8]):Boolean{
for(randomprice in filterBy)
return item.price == randomprice;
}
[Edited]
Apply Filter
testData.filterFunction = filterForTestData;
[Edit]
One point I haven't mentioned is my items in the filterBy array are custom data not the basic datatypes.
I was going to answer this saying use an object as a lookup then use hasOwnProperty to determine if the property is set on the filter Object. From actually testing my method along with these two (creating a mock set of data with strings of numbers (100,000 of them) and the variance between the methods in terms of run-time is negligble (ranges between 130 and 160ms). I think basically all three of these have the same running time.
Here's my full code so you can mess around and point out if I did something that invalidates my testing somehow but this seems to make sense to me, using indexOf it needs to iterate across the set until it finds the object (assuming a purely linear collection not a tree) same with hasOwnProperty (needs to get list of all properties and iterate across them, unless there's a mechanism built-in to find the property more efficiently:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
height="100%"
width="100%"
xmlns:code="http://code.google.com/p/flex-iframe/"
creationComplete="application1_creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import flash.utils.getTimer;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
/* private function filterForTestData(item:Object):Boolean{
return filterBy.hasOwnProperty(item);
} */
/* private function filterForTestData(item:Object):Boolean{
return filterBy.indexOf(item) != -1;
} */
private function filterForTestData(item:Object):Boolean{
for(var randomprice in filterBy)
return item == randomprice;
return false;
}
[Bindable]
private var dp:ArrayCollection = new ArrayCollection(['1','2','3','4','5','6','7','8']);
private var filterBy:Object={'10':true,'4':true,'1':true,'8':true};
//private var filterBy:Array= ['10','4','1','8'];
protected function button2_clickHandler(event:MouseEvent):void
{
// TODO Auto-generated method stub
var startTime:int = getTimer();
trace(getTimer());
dp.filterFunction = filterForTestData;
dp.refresh();
var endTime:int = getTimer();
trace("total time: " + (endTime-startTime).toString());
}
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
// TODO Auto-generated method stub
for(var i:int=0;i<100000;i++)
{
dp.addItem(Math.floor(Math.random()*100).toString());
}
theList.dataProvider = dp;
}
]]>
</mx:Script>
<mx:List id="theList" width="100"/>
<mx:Button click="button2_clickHandler(event)"/>
</mx:Application>
I'm going to try and think this through from a pure data perspective and see if it's even possible to get better running time than this or if it's just the nature of the problem that necessitates a certain number of checks (asymptotic analysis).
Good question.
[Edit]
Okay after thinking this through I believe without using a more complex data structure (and incurring the processor time cost up front in creating/re-organizing the data structure... thinking of keeping a balanced tree for search) I'm pretty sure any method will result in an equivalent or worse running time than these methods (that is linear running time or worse O(n)). Using a Tree structure you would get running times of O(log n) for search but incur more processing overhead in keeping the tree balanced (doing rotations as necessary). If anyone can invalidate this statement I would be happy if you did so, but I believe this is all true.
http://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/binarySearchTree.htm
Why not use the indexof method from Array?
private function filterForTestData(item:Object,filterBy:Array= [10,4,1,8]):Boolean{
return filterBy.indexof(item.price) != -1;
}
(This checks to see if the item's price value is found in the filterBy array. If it is not, the indexof method returns -1. Otherwise, it will return the index of the array.)
I haven't tested this -- you might have to cast the item.price to a Number type, but I'm not certain.
I'm not sure if I'm misunderstanding the binding in Flex. I'm using Cairngorm framework. I have the following component with code like:
[Bindable]
var _model:LalModelLocator = LalModelLocator.getInstance();
....
<s:DataGroup dataProvider="{_model.friendsSearchResults}"
includeIn="find"
itemRenderer="com.lal.renderers.SingleFriendDisplayRenderer">
<s:layout>
<s:TileLayout orientation="columns" requestedColumnCount="2" />
</s:layout> </s:DataGroup>
in the model locator:
[Bindable]
public var friendsSearchResults:ArrayCollection = new ArrayCollection();
Inside the item renderer there is a button that calls a command and inside the command results there is a line like this:
model.friendsSearchResults = friendsSearchResults;
Putting break points and stepping through the code I confirmed that this like gets called and the friendsSearchResults gets updated.
To my understanding if I update a bindable variable it should automatically re-render the s:DataGroup which has a dataProvider of that variable.
There's nothing obviously wrong in the code sample. It should work so I think there's a problem elsewhere.
I would recommend setting a breakpoint where the dataProvider is assigned and also where model.friendsSearchResults is assigned. Make sure they're both pointing to the same object instance. Then step through the property assignment and corresponding event.
To make debugging easier you can switch to using a named event instead of the default. With a named event, only event listeners interested in your particular property are triggered instead of any listeners listening for any property change. This is easier to debug and will run faster. For example, change:
[Bindable]
public var results:ArrayCollection;
to
[Bindable("resultsChanged")]
private var _results:ArrayCollection;
public function get results():ArrayCollection {
return _results;
}
public function set results(value:ArrayCollection):Void {
_results = value;
dispatchEvent(new Event("resultsChanged"));
}
Another thing to keep in mind is that bindings hide certain errors like null reference exceptions. They assume the value simply isn't available yet and suppress the error. Stepping through the assignment and related bindings will help find a problem like this.
I have a custom actionscript object defined as bindable with a number of public properties.
[Bindable]
public class MyObject extends Object {
public var mobileNumber:String;
...
In my mxml I have:
<mx:Script><![CDATA[
import mx.binding.utils.BindingUtils;
import org.test.MyObject;
[Bindable]
private var obj: MyObject = new MyObject();
]]></mx:Script>
<mx:Label text="Mobile Number" id="mobileNumberLabel"/>
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
<mx:LinkButton label="Load" id="loadButton" enabled="true" click="obj = obj.load();"/>
<mx:LinkButton label="Save" id="saveButton" enabled="true" click="obj.write();"/>
My issue is that when I enter a new value in the field for mobile number and then click the save button, the value typed is not logged out... i.e.:
public function write():void {
var bytes:ByteArray = new ByteArray();
trace("write - mobile:" + this.mobileNumber);
bytes.writeObject(this);
EncryptedLocalStore.setItem(KEY, bytes);
}
I also tried adding in:
private function init():void {
BindingUtils.bindProperty(mobileNumberText, "text", obj, "mobileNumber");
}
but no luck with that either.
I'm probably missing something simple here, but not sure what it is. Hope you can help, thanks.
tst's answer is correct - bindings are one-way. I'm guessing you already knew that though, since you tried to setup the reverse binding in your init() method.
However, there's two problems with your init() method.
First, it's not clear where you put that init() method, or what calls it.
Second, you got the method parameters backwards.
What I typically do in situations like this is either use the mxml tag as the first responder suggested, or if I'm in AS3 code, I do something like this:
private function onCreationComplete(event:Event):void
{
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumberText, ["text"]);
}
Note a couple of points here:
1/ BindingUtils.bindProperty() is "left hand side = right hand side". Thus, it's kinda like writing
obj.mobileNumber = mobileNumberText.text;
Or, closer to what is "actually going on" inside the binding classes:
obj["mobileNumber"] = mobileNumberText["text"];
2/ BindingUtils.bindProperty() actually wants an array as the last param. This is so that you can do "chained properties" logically like:
obj.mobileNumber = mobileNumbersGrid.selectedItem.text;
which would be
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumbersGrid,
["selectedItem", "text"]);
3/ Best practice tip: if you're binding a property chain whose initial member is a property of yourself (this), then write it as:
BindingUtils.bindProperty(obj, "mobileNumber", this,
["mobileNumbersGrid", "selectedItem", "text"]);
This neatly handles the case where your "right hand side object" this.mobileNumbersGrid instance itself is replaced with a new instance object.
4/ If you ever reassign obj ("the left hand side"), you need to create a new binding to the new obj instance. Typically, you do this by turning the local obj property into a getter/setter pair like this:
public function get obj():MyObject
{
return _obj;
}
private var _obj:MyObject = new MyObject();
public function set obj(value:MyObject):void
{
_obj = value;
BindingUtils.bindProperty(_obj, "mobileNumber", mobileNumberText, ["text"]);
}
(Note: to be really careful, you'd stash the returned value from BindingUtils.bindProperty() which is a ChangeWatcher instance, and you'd unwatch() to prevent the old object from receiving property changes)
Hope this helps. Bindings are among the most powerful tools in the Flex framework, but can cause a lot of headaches when used poorly. In particular, be aware of memory leaks and "unexpected updates" when bindings are left "hanging around".
This code:
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
makes a one-directional binding between obj.mobileNumber and mobileNumberText.text. What you need is an additional reverse binding - add the following line to your code and it will work:
<mx:Binding source="mobileNumberText.text" destination="obj.mobileNumber"/>
Did you try implementing IEventDispatcher, or extending EventDispatcher?
Bindings work through dispatching of PropertyChangeEvents, so maybe this is the problem?
also, I am not sure, whether bindings work on variables, or whether they require properties (getters/setters).
You should try compiling with -keep-generated-actionscript and see if the resulting code makes any sense, i.e. dispatches any events, if the variable is accessed ...