Flex ItemRenderer as a field of `data`? - apache-flex

I would like to let the data provided to a DataGrid decide how best it should be rendered (that is, let the data carry with it an object which will do the rendering).
For example, by creating a "Renderable" interface, which has a 'renderer:IFactory' property, then used as below:
<mx:DataGrid x="0" y="0" width="100%" dataProvider="{myDataProvider}">
<mx:columns>
<mx:DataGridColumn headerText="Task" width="100"
itemRenderer="{(data as Renderable).renderer}"/>
</mx:columns>
</mx:DataGrid>
But to do this, Renderable has to extend IEventDispatcher, which seems like a little much...
I've also tried using:
itemRenderer="{(data as Renderable).getRenderer()}"
Which does nothing (in fact, the getRenderer method never gets called).
Is there a better way to do this? Am I doing something fundamentally wrong?
Thanks!

I could be wrong but I think the "data" property you're referencing in the code sample above is the "data" for the top-level Container in your view and not for that particular row of the DataGrid. A few other approaches come to mind:
Implement a single item renderer class that examines the data being passed to it and utilizes the proper item renderer for the type of data supplied.
Implement a function in the view of your DataGrid that examines your dataProvider and returns the proper item renderer class; call this inside the DataGridColumn.itemRenderer property using a binding expression.
Implement an subclass of DataGridColumn that has the logic baked into it to set the correct itemRenderer.
I would actually recommend against mixing "renderer data" that is View-specific with data from your Model. Unless you wrap the core model data with an object that exposes it along with the renderer (what some people call a ViewModel).

Make getRenderer a [Bindable] property

Related

Trying to use the effectStart and effectEnd from a LIST object in Flex 4.6

I have a mobile app that populates a list. This takes a couple of seconds so I am trying to display the busyindicator. I display the busy indicator when the view is activated and then when the list is complete I want to turn off the busy indicator.
My MXML for the busy indicator and the list declaration is like so:
<s:BusyIndicator id="BI" visible="true" />
<s:List id="lst" effectStart="lstStartHandler(event)" effectEnd="lstFinishHandler(event)" fontSize="20" horizontalCenter="0" textAlign="right" dataProvider="{dp}" useVirtualLayout="true" width="100%" height="100%" top="30" alternatingItemColors="[#66FFFF, #33CCCC]">
My event listeners are like so:
private function lstFinishHandler(event:EffectEvent):void {
BI.visible = false
}
private function lstStartHandler(event:EffectEvent):void {
BI.visible = true
}
My busy indicator always stays on and never goes invisible. It appears the event functions do not execute.
Obviously I am doing something wrong but cannot figure it out. Any ideas would be appreciated.
cheers,
The effectStart and effectEnd properties in MXML are to add event handlers when a Flex effect class is playing an effect on your component.
If you are not triggering any effects on the List, then those event handlers are not going to be executed.
You need to tell the busy indicator to go away by some other means:
dispatch your own event
use data binding
use view states and the currentState property
etc...
You are already using data binding to set the dataProvider for the list, you could simply add another variable and bind to the visible property of the BusyIndicator:
<s:BusyIndicator visible="{isServerResponseComplete}" />
It feels wrong to add a bindable variable (`isServerResponseComplete') just to do this, but it's the simplest answer. Dispatching an event is probably a better approach, but it's difficult to say exactly how you should do it w/out knowing how your app is structured.

How do I access a public function outside of the view it is in using Flex?

Hi, I have been working on a Flex Mobile application using Flash Builder 4.6.
I have 2 mxml 'views' in my project. In one mxml file, i have a function that grabs xml data.
In my other mxml file, I have a refresh button that when depressed is suppsosed to call the function in the first mxml file in order to once again grab the xml data.
I dont know how to call that function from outside the mxml file it is housed in.
I appreciate any help given. Thank you!
[UPDATE #2]*
I thought I should share some more details about my issue.
It is a reddit client mobile app. It fetches the feeds, etc.
In my main view called RedditReaderHomeView.mxml, I am using a splitViewNavigator spark component to house two other views like so:
RedditReaderHomeView.mxml
<s:SplitViewNavigator width="100%" height="100%" id="splitViewNavigator" autoHideFirstViewNavigator="true">
<s:ViewNavigator id="redditList" firstView="views.subredditList" width="300" height="100%"/>
<s:ViewNavigator id="redditFeed" firstView="views.redditFeed" width="100%" height="100%">
<s:actionContent.landscape>
<s:Button id="refreshButtonlLandscape" icon="#Embed('assets/refresh160.png')" click="refreshRSS()" />
</s:actionContent.landscape>
<s:actionContent.portrait>
<s:Button id="refreshButton" icon="#Embed('assets/refresh160.png')" />
<s:Button id="navigatorButton" label="Search" click="splitViewNavigator.showFirstViewNavigatorInPopUp(navigatorButton)" />
</s:actionContent.portrait>
</s:ViewNavigator>
</s:SplitViewNavigator>
As you can see in the code above, in my main view I have a button with the id "refreshButton." When I click this button, I want the reddit data to refresh. In other words I want to call a function to refresh the data, that is housed in the view, 'redditFeed'.
This is the function which is in a separate view named 'redditFeed.mxml', that I want to call using the refresh button in the main view shown above.
redditFeed.mxml
protected function myList_creationCompleteHandler(url:String):void
{
getRedditFeedResult.token = redditFeedGrabber.getRedditFeed(url);
getRedditFeedResult.addEventListener(ResultEvent.RESULT,busyOff);
}
I hope this helped clear out confusion as to what I was trying to do. Im assuming that the solution is quite simple, but alas, I am a novice programmer and new to Flex, so Im learning the ropes. Any help is appreciated. Thank you!
IF you have an instance of the view, then just do:
myViewInstance.myPublicFunction();
In MXML, the id element of the MXML tag is used to reference the view in ActionScript. Since you didnt' describe your architecture; it is unclear how one view can call the other.
If the view that needs to trigger the call is a parent of the view that has the function to make the call, then you could use the approach described above.
If the view that need to trigger the call is a child of the view that has the function to make the call, then you should dispatch an event from the "child" which the parent can listen to. In the event handler you would trigger the call.
If the view that needs to trigger and the view that has the function to make the call are both children of the same parent; then you should dispatch an event from the "Trigger" view, listen for it in the parent, and then use that event listener to make the call (Using similar code to what I explained above).
If you have a more complicated architecture of these two views; then you should look into some method to encapsulate the "remote call" functionality, such as into a service class. Many frameworks offer approaches to share that service class and/or results across multiple classes. ( MXML Files are classes).
There are two ways you can do this without getting into bad architecture by having the child view explicitly know about its parent:
Your child view can generate an event, which the parent is listening for. The parent will then call the function
The child view can have a public property of type Function. The parent view passes a reference to that function by setting the variable. The child view then calls the function (after checking to make sure it is not null).

Flex List item renderer

I have created an item renderer spark list in flex , but i want to call a function on addition of new row in list and not afterwards. I am getting a data object in rendered list in it i am getting the type of data to be displayed in list ie. either text or image. So on addition of new data in list i want a function to be called up in rendered list that checks the type of data received and then it will either create and add an image element or a text element. So the main problem is how i get a function called on addition of data. I have tried events like datachange and added but they keep on calling the function over and over again when we scroll the list but i want the function to be called once only on addition of data and not after wards. Below is the renderer list code , maybe you will get a better idea of what i am trying to do:
<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"
autoDrawBackground="true" dataChange="test_add()">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
public function test_add() : void {
Alert.show("type="+data.msg_type);
if(data.msg_type=="text"){
//code to create and add new text element to list_row//
}
if(data.msg_type=="image"){
//code to create and add new image element to list_row//
}
}
]]>
</fx:Script>
<s:Group id="list_row" width="100%" verticalAlign="middle" verticalCenter="0">
</s:Group>
</s:ItemRenderer>
Any help will be highly appreciated.
Thanks
As far as I can tell from the code you show, the easiest solution to your problem would be to work with two separate ItemRenderers: one that renders text and the other that renders images. You can do this using the SkinnableDataContainer#itemRendererFunction property instead of itemRenderer.
The List with the new property:
<s:List id="myList" dataProvider="{dp}"
itemRendererFunction="getItemRenderer" />
The function that returns a factory for the right ItemRenderer.
private function getItemRenderer(item:Object):IFactory {
if (item.msg_type == "text")
return new ClassFactory(MyTextItemRenderer);
if (item.msg_type == "image")
return new ClassFactory(MyImageItemRenderer);
}
In these two different ItemRenderers you can then display your data as you wish.
Edit: why it's OK that the dataChange event fires every time you scroll.
There is in fact nothing wrong with your approach as you describe it, although I would argue that the itemRendererFunction approach allows for better separation of concerns. I could tell you that you can turn the unwanted behavior off, simply by setting the List#useVirtualLayout property to false.
<s:List id="myList" dataProvider="{dp}"
itemRenderer="myItemRenderer" useVirtualLayout="false" />
Though this will do what you ask for (i.e. create the ItemRenderers only once), that would not be good advice. There is a good reason this property is set to true by default.
When virtual layout is used, item renderers are created only as they are needed, i.e. when they come into view and need to be displayed to the user. This allows you to load thousands of items without performance loss.
Let's say you load 1000 value objects: that doesn't take up much memory or CPU. But now you want to render them. If you don't use virtual layout an item renderer will be created for all of them up front, which means thousands of graphic elements and thousands of event listeners (how many exactly depends on your setup). Now that is going to hurt performance on a slow computer.
If you do use virtual layout only - say - 10 item renderers will be created at once. If the user scrolls down, the next 10 will be created and the ones that just disappeared from the view are removed and eventually garbage collected. So you see: what you may have perceived as something that was bad for performance at first, is actually a very good thing.
So I would advise you not to do what I just told you. Unless perhaps you would have a situation where you knew there would never be more than a very limited number of items in your List. Then you may consider not using virtual layout.

Problem getting tooltip to refresh properly on an itemrenderer in Flex

I'm having the following problem.
I have an ArrayCollection that's acting as the data provider for a tilelist (called favoriteLinksList)
I use an itemRenderer called FavoriteItem as the tilelist's itemRenderer. This FavoriteItem looks like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
width="280" height="163"
horizontalAlign="center"
paddingLeft="5" paddingRight="5" paddingTop="0" paddingBottom="0" xmlns:ns1="*">
<mx:Canvas width="100%" height="100%">
<mx:Image
id="thumbnail"
width="178" height="115"
source="{data.thumbnail}"
toolTip = "{data.tooltip}" x="46" y="10"/>
<mx:Text
id="title"
text="{data.tileListTitle}"
width="254"
toolTip="{data.tooltip}" x="10" y="133"/>
</mx:Canvas>
</mx:VBox>
As you can see, the tooltips for the two items in it are taken from data.tooltip
This works fine.
The problem is refreshing the tooltip when it has changed.
The objects (of type Object) in the ArrayCollection each have a property called tooltip (obviously since that's where the itemRenderer is getting its info from).
When I change this property to its new value, the tooltip of the itemRenderer doesn't change to reflect this.
I tried to set it manually by getting the itemRenderer from the event that is triggered upon clicking one of the items in the tilelist but without success.
Example:
event.itemRenderer.title.toolTip = event.currentTarget.selectedItem.tooltip;
after having updated the tooltip but this gives a compilation error:
Access of possibly undefined property title through a reference with static type mx.controls.listClasses:IListItemRenderer.
I also tried performing a refresh() on the favoriteLinksList array collection but this gave mixed results. The tooltip was updated correctly but one of the items (the first one) in the tilelist went missing! This seems to be a Flex bug. The data provider has the same number of elements before and after the refresh and this doesn't happen if I click on the first element in the tilelist.
All help is greatly appreciated.
Found a solution to my problem.
The favoriteLinksList is bindable and set as the dataProvider of the tileList. However, changes to the individual objects were not being propagated to the itemRenderer.
I thought that there must a change to the favoriteLinksList Array Collection itself.
As mentioned in my question, I already tried using favoriteLinksList.refresh() but this made the first element in the tileList vanish (though it still seemed to be in the Array Collection). A possible bug in Flex perhaps?
Anyway, discovered that a way around this was to perform the following:
favoriteLinksList.setItemAt(favoriteObject, favoriteLinksList.getItemIndex(favoriteObject));
Essentially, I'm setting the item at index X to itself so not actually doing anything but this is enough for the itemRenderer to refresh the data for the itemRenderer.
I would go about doing 2 things
that the object is actually bindable and the change is happening and getting to the item renderer
possible solution => override the setter for the data property in the item renderer, do not forget to call super.data = value
-
override public function set data(value:Object):void
{
super.data = value;
title.toolTip = data.tooltip;
}
stand with a breakpoint in this row, you should be getting to it when the data changes.

Binding mxml (as)

I have actionscript file with a binding {someBinding}...
The Main.mxml is where all the action happens. If I set {someBinding} in the "text" of a label component I will have a number.
I have another form.mxml file. Where I want that binding to be in, but it can't find such binding.
I need to have that {someBinding} in that other mxml, the same way as in the Main.mxml
Thanks, Yan
You can't a value in one component (or File) to a value in another component (or file) in the way you seem to be asking. You'll have to expose those related values as properties and set the values.
This type of approach should work:
First add a property to component 2 and make it Bindable. Do this in a Script block, like this:
[Bindable] public var hBoxWidth : int;
Then bind it to something in your MXML of the same component, like this:
<mx:HBox width="{this.hBoxWidth}" />
Now some component will contain this one:
<mx:HBox>
<myCustomComp:customHBox hBoxWidth={this.othervalue} />
</mx:Hbox>
So, when the othervalue changes it will change the hBoxWidth value on the customHBox component which will in turn change the width property on the HBox inside of customHBox.
Does that make sense?
You can create the binding but you have to use ActionScript and you need a reference to the form.mxml file in the main.mxml (or visa versa).
This should give you an ideal of how it might work. Take a look at the syntax for the BindingUtils. bindProperty method. The use of BindingUtils code would be within main.mxml.
BindingUtils.bindProperty(otherForm.someOtherTextComponent, "text", this.someTextComponent, "text");

Resources