flex databinding with selectedItem property of the combobox update many times problem - apache-flex

well, I have a combobox which I have bind his selectedItem property to a value object object, like this
<fx:Binding source="styles_cb.selectedItem.toString()" destination="_uniform.style"/>
<fx:Declarations>
<fx:XML id="config_xml" xmlns="" source="config.xml" />
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<mx:ComboBox x="66.15" y="63.85" editable="false" id="styles_cb" dataProvider="{config_xml.styles.style}" />
the value object is a custom class with some setters and getters, and I want to set a property based of the value of the selectedItem of the combo, so inside the value object I have something like this
[Bindable]
public function set style(value:String):void
{
_style = value;
trace(value);
}
my problem is that each time I change the combobox selection which in fact change the style property of the value object it does it 3 times, if I trace the value of the setter it actually do the trace 3 times, why?? how can I avoid this? I'm doing something wrong? or there is a better way to do it, please help, thanks for any help

It's common for data binding expressions to fire many times more than one would expect. I don't know the exact reason. If this causes issue for your app, then don't bind the source directly to the target, instead use invalidation. Bind the source to set a flag, stylesSelectedItemChanged and call invalidateProperties(). Then override commitProperties() and inside your commitProperties(), check if stylesSelectedItemChanged is true, and if so, propagate the new value forward to the destination and reset the flag to false. Be sure to also call super.commitProperties() or else many things would break.
Invalidation is extremely common in the Flex framework, every component uses it internally, and it helps a lot with these kinds of issues.

wow!!, some times writing a question let you think about it twice and let you find the answer by yourself, so I find my own solution, in the documentation said I can make all the properties of an object bindables if I put [Bindable] in the class declaration, so I did it like this
[Bindable]
[RemoteClass(alias='Uniform')]
public class Uniform extends Object implements IEventDispatcher
however when I was trying to dispatch an event in the setters I found in the docs that I must add the event name like this
[Bindable("styleChanged")]
public function get style():String
{
return _style;
}
public function set style(value:String):void
{
_style = value;
dispatchEvent(new Event("styleChanged"));
}
now I found that doing this, mark the property with a double bind and that was making me set the property many times, hugg!, but now I know I can avoid using the second [Bindable] and still the event get dispatch, so now I wonder why I need to use [Bindable("styleChanged")] in the first place if I still can dispacth the event with only [Bindable] and the dispatch method?, weird
hope this help to someone

Related

Is there a way to make a bindable mxml object?

I have an MXML object and I want it to trigger its binding every time one of its properties is updated:
<fx:Object id="myObject">
<fx:prop1>{myButton.selected}</fx:prop1>
<fx:prop2>value</fx:prop2>
<fx:prop3>value</fx:prop3>
</fx:Object>
<Button id="myButton" toggle="true" />
<myComponent obj="{myObject}" />
What is the best way to accomplish this?
You can't really do what you want. The best way I know how to describe it is that binding is only a single level deep; whereas you are trying to make binding two levels deep.
This code:
<myComponent obj="{myObject}" />
Tells Flex to look at the variable myObject and whenever myObject is changed thenthe obj property on myComponent will also be changed. It is look at the variable that myObject points to.
This code:
<fx:prop1>{myButton.selected}</fx:prop1>
Changes a property on myObject; but does not change myObject. myObject is still pointing to the same memory space / variable space.
To get myComponent to change whenever properties on myObject change; I would use a completely different approach. I would create myObject as an ActionScript class. Make use of get/set properties and dispatch events whenever a property changes.
Inside of myComponent, you can implement obj as a get/set method. In your set method add an event listener to the property changes you set up in myObject. In your event listener method; implement the appropriate changes.
It is not a trivial change from what you're doing; but it shouldn't be too hard either.
Use an ObjectProxy.
Example: Detecting changes to an Object using the Flex ObjectProxy class
private var object:Object = {};
private var objectProxy:ObjectProxy;
objectProxy = new ObjectProxy(object);
objectProxy.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateChange);
Then, make assignments directly to the proxy:
objectProxy.name = "My Object";

flex Property ignored

I'm trying to get an itemrenderer to display the text from a tweet, and (if there is one) the uploaded image. Text displays fine, but no matter what I do, the image doesn't show. Could it be that because I'm refering to a property that does not always exist, in this case "data.entities.media.creative.media_url" (it only exists if there is an image uploaded with the tweet) that the itemrenderer just ignores the property for all tweets?
Here's my code:
[Bindable(event="dataChange")]
public function get data():Object
{
return _data;
}
public function set data(value:Object):void
{
_data = value;
dispatchEvent(new Event("dataChange"));
}
]]>
</fx:Script>
<s:Label text="{data.text}"
width="100%"
styleName="tweetlist"/>
<s:Image id="tweetImage" source="{data.entities.media.creative.media_url}"/>
I am quite new to Flex, and so far I managed to get things working by pasting code together from different sources. So, any help would be greatly appreciated.
I see that you are binding to a deeply nested property: data.entities.media.creative.media_url. If you want the binding to work properly, you must make sure that each and every property on that path is made Bindable.
Concretely:
data must be Bindable on the ItemRenderer; it is by default when you extend ItemRenderer, so I would remove your custom Bindable declaration and event dispatching (or even remove the data getter/setter override altogether)
the entities property of the data instance must be declared Bindable
the media property of the entities instance must be declared Bindable
and so forth all the way to the media_url property
However it is not considered good coding practice (in general, not just in Flex) to access deeply nested properties like that, so I would advise you to encapsulate the property that you need so that you can bind to it like this:
<s:Image id="tweetImage" source="{data.media_url}" />
This will not only be better from a "clean code" point of view, but also for performance if you have a lot of items in the List. (Because the number of bindings will descrease by factor 5)
An alternative solution is to not use data binding and simply set tweetImage's source property:
override public function set data(value:Object):void {
super.data = value;
tweetImage.source = data.entities.media.creative.media_url;
}

Giving Flex MXML views their dependencies

I'm used to building applications using pure AS3. I always pass dependencies into the constructor of classes I make, but this method seems to not work out well for Flex MXML views.
It seems like I should define setters on the MXML class, which map to attributes in the tag/class instantiation. But using this method I cannot specify which properties are required and in what order I expect them etc.
What is the preferred method to give a Flex view it's dependencies?
A pattern I've used a couple times was to define a public init() method in the MXML which takes the argument that would normally have gone in the constructor. Then, whatever instantiates that MXML component is responsible for calling init() before using it.
Another way would be to create setters for the properties like you mentioned. In those setters store the values that are passed, then call invalidateProperties(). Then override the commitProperties() method in the MXML, and the first time that's called do your initialization (and maybe throw an exception if the needed properties weren't provided). As long as the user of your class sets all the properties before adding the component to the display list then it will work fine (I don't believe commitProperties() is called until after a component is added to the display list, either by being declared in MXML or by passing it to an addChild() call).
I haven't ever tried that second method (only just thought of it now), but it should work.
You can't force people to use parameters in the constructor, but you can force then to set properties before adding the item to the stage.
How's this:
<mx:HBox
added="{checkProps()}">
<mx:Script>
<![CDATA[
public var prop1:String;
public var prop2:String;
private function checkProps():void
{
if( !( prop1 && prop2 ) )
{
throw new Error( "Prop1 and prop2 must be set before "+
"adding this to the stage" );
}
}
]]>
</mx:Script>
</mx:HBox>
Realistically, if you're interested in forcing people to do something before adding it to the display list, then you're going to have to do something like this anyway.
There are a few things in Flex that you can override or listen to that are really important.
FlexEvent.CREATION_COMPLETE - set an eventListener for this (I usually do it in the constructor but you could do it in MXML as creationComplete attribute) and it acts like your constructor. Use getters and setters to pass through references to your dependencies as MXML attributes and store them locally then inside this handler you will apply them.
override protected function createChildren - this is called when it is time to add display list items to the component. You shouldn't do that during the constructor or creationComplete handlers. It is always tempting to addChild outside of this function but Adobe best practice is only to do so directly in this function.
override protected function updateDisplayList - this is where your drawing logic should happen (if there is any) or positioning/alpha/rotation/etc of children. This will get called if a CSS property changes, a child changes size or position or anything else that the Flex framework thinks may cause you to want to redraw the screen. You can force an updateDisplayList to get called by calling invalidateDisplayList
override protected function commitProperties - this is called when the dataProvider for a class is changed. Any time data within the component means you want to update internal data structures this should be called. You can force this to be called using invalidateProperties.
FlexEvent.ADDED_TO_STAGE - If you need to know when the component is actually added to the stage you can listen for this. In practice I can't remember ever actually using it ...
Always remember to call the super equivalents -- forgetting to do so will often cause the component to fail to appear at all (this happens to me at least 4 or 5 times a project). Also be aware that if you first invalidateProperties and then commitProperties and then invalidateDisplayList and then updateDisplayList you may see some jerkyness ... that is, invalidateDisplayList as soon as you know you'll want a redraw to avoid delay.
Also don't get too invested in Flex 3 since Flex 4 is just around the corner and it is quite a bit different. I have a feeling that much of this will no longer apply in the new component framework (names Spark).
edit a typical class stub:
package
{
import mx.containers.Canvas;
import mx.events.FlexEvent;
public class TestComponent extends Canvas
{
public function TestComponent()
{
super();
addEventListener(FlexEvent.CREATION_COMPLETE, init);
}
// acts as constructor
private function init(event:FlexEvent):void
{
// might as well be clean
removeEventListener(FlexEvent.CREATION_COMPLETE, init);
// do init stuff here
}
override protected function createChildren():void
{
super.createChildren();
// do any addChilds here that are necessary
}
override protected function commitProperties():void
{
super.commitProperties();
// update internal state when data changes
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
// do any drawing, positioning, rotation etc.
}
}
}

Flex - Sending a parameter to a custom ItemRenderer?

What I am trying to accomplish to to get financial data in my Flex Datagrid to be color-coded--green if it's positive; red if it's negative. This would be fairly straightforward if the column I want colored was part of the dataProvider. Instead, I am calculating it based on two other columns that are part of the dataProvider. That would still be fairly straightforward because I could just calculate it again in the ItemRenderer, but another part of the calculation is based on the value of a textBox. So, what I think I need to be able to do is send the value of the textBox to the custom ItemRenderer, but since that value is stored in the main MXML Application, I don't know how to access it. Sending it as a parameter seems like the best way, but perhaps there's another.
Here is the current code for my ItemRenderer:
package {
import mx.controls.Label;
import mx.controls.listClasses.*;
public class PriceLabel extends Label {
private const POSITIVE_COLOR:uint = 0x458B00 // Green
private const NEGATIVE_COLOR:uint = 0xFF0000; // Red
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
/* Set the font color based on the item price. */
setStyle("color", (data.AvailableFunding >= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
}
}
(data.AvailableFunding doesn't exist)
So does anyone know how I would go about accomplishing this?
You may want to look into ClassFactory from the Flex APIs:
This allows you to set a prototype Object with arbitrary types / values each of which will be passed to the item renderer. From the sample:
var productRenderer:ClassFactory = new ClassFactory(ProductRenderer);
productRenderer.properties = { showProductImage: true };
myList.itemRenderer = productRenderer;
The above code assumed that "ProductRenderer" has a public property called "showProductImage" which will be set with a value of "true."
Ah, so I knew about outerDocument but not parentDocument. I was able to just use parentDocument.*whatever I want from the main App and I can access it as long as it's public.
Example:
setStyle("color", (parentDocument.availableFunding >= 0) ? POSITIVE_COLOR : NEGATIVE_COLOR);
Sweet! :)
You can access the value of the TextBox directly, if you need to, by using the static Application.application object, which is accessible from anywhere in your application.
For example, if you wanted the renderers to be notified when the value of the TextInput control changes, you could do something like this (from within your ItemRenderer, and where myTextInput is the ID of the control defined in your main MXML class):
<mx:Script>
<![CDATA[
import mx.core.Application;
private function creationCompleteHandler(event:Event):void
{
Application.application.myTextInput.addEventListener(TextEvent.TEXT_INPUT, handleTextInput, false, 0, true);
}
private function handleTextInput(event:TextEvent):void
{
if (event.currentTarget.text == "some special value")
{
// Take some action...
}
}
]]>
</mx:Script>
With this approach, each item-renderer object will be notified when the TextInput's text property changes, and you can take appropriate action based on the value of the control at that time. Notice as well that I've set the useWeakReference argument to true in this case, to make sure the listener assignments don't interfere unintentionally with garbage collection. Hope it helps!
There's another technique, which, while it initially feels a little hacky is perhaps less cumbersome and cleaner in actual use.
It involves the little-observed fact that an event dispatch is, of course, synchronous and the event object can be treated as a value object populated by any event handler.
i.e. the ItemRenderer can do something like:
...
var questionEvt:DynamicEvent = new DynamicEvent('answerMeThis', true, true);
if (dispatchEvent(questionEvt))
{
if (questionEvent.answer == "some value")
....
With a corresponding handler somewhere up the view hierarchy above the renderer that has a listener on the event and does something like:
function handleAnswerMeThis(event:DynamicEvent):void
{
event.answer = "another value";
event.dataHelper = new DataHelperThingy();
}
etc.
It need not be a DynamicEvent - I'm just using that for lazy illustrative purposes.
I vote up for cliff.meyers' answer.
Here's another example on setting the properties of an itemRenderer from MXML by building a function that wraps a ClassFactory around the itemRenderer class and that injects the necessary properties.
The static function:
public static function createRendererWithProperties(renderer:Class,
properties:Object ):IFactory {
var factory:ClassFactory = new ClassFactory(renderer);
factory.properties = properties;
return factory;
}
A simple example that adds a Tooltip to each item in a list:
<mx:List dataProvider="{['Foo', 'Bar']}" itemRenderer="{createRendererWithProperties(Label, {toolTip: 'Hello'})}"/>
Reference:
http://cookbooks.adobe.com/post_Setting_the_properties_of_an_itemRenderer_from_MXM-5762.html
You use outerDocument property. Please see the fx:Component reference.
You could create an 'AvailableFunding' static variable in the ItemRenderer and then set it in the parent document.
public class PriceLabel extends Label {
public static var availableFunding:int;
...
...
SetStyle("color", (PriceLabel.availableFunding >= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
}
In your parent document, set it when your text box gets updated
PriceLabel.availableFunding = textBox.text;
Obviously it'll be the same value for every ItemRenderer but it looks like that might be what you're doing anyway.
I like to override the set data function of the item renderer to change the renderer when the data provider changes as shown here
When you override the function you could cast the object to your object to make the availableFunding property available.
To access the text box you could try creating a public property and binding the property to the text box in the mxml file:
public var textVar:String;
<mx:itemRenderer>
<mx:Component>
<customrenderer textVar="{txtBox.text}" />
</mx:Component>
</mx:itemRenderer>
Nice ClassFactory Example here
See this example:
itemRenderer="{UIUtils.createRenderer(TextBox,{iconSrc:IconRepository.linechart,headerColor:0xB7D034,subHeaderColor:0xE3007F,textColor:0x75757D})}"

Problem with dispatching custom event in FLEX

Let me start off by saying that I'm pretty new to flex and action script. Anyways, I'm trying to create a custom event to take a selected employee and populate a form. The event is being dispatched (the dispatchEvent(event) is returning true) but the breakpoints set inside of the handler are never being hit.
Here is some snippets of my code (Logically Arranged for better understanding):
Employee Form Custom Component
<mx:Metadata>
[Event(name="selectEmployeeEvent", type="events.EmployeeEvent")]
<mx:Metadata>
<mx:Script>
[Bindable]
public var selectedEmployee:Employee;
</mx:Script>
...Form Fields
Application
<mx:DataGrid id="grdEmployees" width="160" height="268"
itemClick="EmployeeClicked(event);">
<custom:EmployeeForm
selectEmployeeEvent="SelectEmployeeEventHandler(event)"
selectedEmployee="{selectedEmployee}" />
<mx:Script>
[Bindable]
private var selectedEmployee:Employee;
private function EmployeeClicked(event:ListEvent):void
{
var employeeData:Employee;
employeeData = event.itemRenderer.data as Employee;
var employeeEventObject:EmployeeEvent =
new EmployeeEvent( "selectEmployeeEvent", employeeData);
var test:Boolean = dispatchEvent(employeeEventObject);
//test == true in debugger
}
private function SelectEmployeeEventHandler(event:EmployeeEvent):void
{
selectedEmployee = event.Employee; //This event never fires
}
</mx:Script>
There are a few things conspiring to cause trouble here. :)
First, this declaration in your custom component:
<mx:Metadata>
[Event(name="selectEmployeeEvent", type="events.EmployeeEvent")]
<mx:Metadata>
... announces to the compiler that your custom component dispatches this particular event, but you're actually dispatching it with your containing document, rather than your custom component -- so your event listener never gets called, because there's nothing set up to call it.
Another thing is that, in list-item-selection situations like this one, the more typical behavior is to extract the object from the selectedItem property of the currentTarget of the ListEvent (i.e., your grid -- and currentTarget rather than target, because of the way event bubbling works). In your case, the selectedItem property of the grid should contain an Employee object (assuming your grid's dataProvider is an ArrayCollection of Employee objects), so you'd reference it directly this way:
private function employeeClicked(event:ListEvent):void
{
var employee:Employee = event.currentTarget.selectedItem as Employee;
dispatchEvent(new EmployeeEvent("selectEmployeeEvent"), employee);
}
Using the itemRenderer as a source of data is sort of notoriously unreliable, since item renderers are visual elements that get reused by the framework in ways you can't always predict. It's much safer, instead, to pull the item from the event directly as I've demonstrated above -- in which case you wouldn't need an "employeeData" object at all, since you'd already have the Employee object.
Perhaps, though, it might be better to suggest an approach that's more in line with the way the framework operates -- one in which no event dispatching is necessary because of the data-binding features you get out-of-the-box with the framework. For example, in your containing document:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
[Bindable]
private var selectedEmployee:Employee;
private function dgEmployees_itemClick(event:ListEvent):void
{
selectedEmployee = event.currentTarget.selectedItem as Employee;
}
]]>
</mx:Script>
<mx:DataGrid id="dgEmployees" dataProvider="{someArrayCollectionOfEmployees}" itemClick="dgEmployees_itemClick(event)" />
<custom:EmployeeForm selectedEmployee="{selectedEmployee}" />
... you'd define a selectedEmployee member, marked as Bindable, which you set in the DataGrid's itemClick handler. That way, the binding expression you've specified on the EmployeeForm will make sure the form always has a reference to the selected employee. Then, in the form itself:
<mx:Script>
<![CDATA[
[Bindable]
[Inspectable]
public var selectedEmployee:Employee;
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="Name">
<mx:TextInput text="{selectedEmployee.name}" />
</mx:FormItem>
</mx:Form>
... you simply accept the selected employee (which is marked bindable again, to ensure the form fields always have the most recent information about that selected employee).
Does this make sense? I might be oversimplifying given that I've no familiarity with the requirements of your application, but it's such a common scenario that I figured I'd throw an alternative out there just for illustration, to give you a better understanding of the conventional way of handling it.
If for some reason you do need to dispatch a custom event, though, in addition to simply setting the selected item -- e.g., if there are other components listening for that event -- then the dispatchEvent call you've specified should be fine, so long as you understand it's the containing document that's dispatching the event, so anyone wishing to be notified of it would need to attach a specific listener to that document:
yourContainingDoc.addEventListener("selectEmployeeEvent", yourOtherComponentsSelectionHandler);
Hope it helps -- feel free to post comments and I'll keep an eye out.
You're dispatching the event from the class in the lower code sample (DataGrid, I guess), but the event is defined in the EmployeeForm class, and the event handler is likewise attached to the EmployeeForm instance. To the best of my knowledge, events propagate up in the hierarchy, not down, so when you fire the event from the form's parent, it will never trigger handlers in the form itself. Either fire the event from the form or attach the event handler to the parent component.

Resources