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;
}
Related
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";
I have a project that has 4 views where I'm using the tabBar with viewStack/NavigatorContent. I have a couple HTTPServices set up gathering the XML and converting to Bindable ArrayCollections. When and how is the best way to pass that data to such things as charts, dataGrids, etc. that aren't seen until the user clicks a tab? Right now I have each item set up with creationComplete functions that pass it then. Although it seems to be working, is this the best way, or is there something better and maybe even quicker?
Thanks,
Mark
The best way is using data binding. Say you have a main container which contains ViewStack witch components representing tabs content. So you should have [Bindable] properties for data in a main container like the following:
[Bindable]
private var chartData:ArrayCollection;
[Bindable]
private var dataGridData:ArrayCollection;
etc.
So for the component, containing chart, you should populate chart data with data binding:
<ViewStack>
…
<MyChartsTab chartData="{chartData}" />
…
</ViewStack>
And of course you should introduce the same chartData field (make sure it is public) in your MyChartsTab component. Your charts there can be populated with data binding too.
So after getting data you just fill your fields in main component and data binding performs the rest job without any care of initialization from your side.
When creating your views, make sure you allow a public variable (like 'dataProvider') where you can bind the data it needs. Like this:
<mx:ViewStack>
<s:NavigatorContent>
<SomeComponent dataProvider="{someData}" />
</s:NavigatorContent>
<s:NavigatorContent>
<SomeComponent2 dataProvider="{someData}" />
</s:NavigatorContent>
</mx:ViewStack>
And within the custom component you'd have:
private var _data:Object; // use strong typing if possible
public function set dataProvider(value:Object):void // use strong typing if possible
{
this._data = value;
}
public function get dataProvider():Object
{
return this._data;
}
I have a datagrid with itemRenderer in datagridcolumn as my custom component(c1). I am getting the dataprovider data in custom component by overriding the set data function, it is fine. But now my custom component(c1) is also having another custom component(c2).
Now the doubt is - how to get the dataprovider data in c2 component?
Thanks in advance.
Assuming c1 is the item renderer, and c2 is a child of the itemRenderer, all you need to do is something like, binding the child component's data property to the parents data property, or just passing in just the necessary data. You may not be able to do this in the set data function as the child component may not exsist yet, so you may have to add a check to make sure the child component exsists & also set the data when the child is created.
<mx:Canvas>
<mx:Script>
<![CDATA[
override public function set data(value:Object):void{
super.data = value;
//do whatever w/ the data
}
]]>
</mx:Script>
<components:MyCustomComponent id="c2" data="{data}" />
</mx:Canvas>
(note this is psuedo code and probably full of typos as well)
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
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})}"