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.
Related
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.
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.
I want to add an event listener to each component inside a repeater, but don't know how. Here's some code I have tried:
<mx:Repeater id="rp" dataProvider="{dataProvider}" width="100%">
<mx:Button id="attach" creationComplete="addListeners(attach[rp.currentIndex])"/>
</mx:Repeater>
This doesn't work. creationComplete is not called until the repeater has finished instead of (as I expected) when the creation of the button is complete. I'm not sure how to accomplish this.
BTW - I also tried placing the creationComplete on the parent component to the repeater, but it would only be called the first time the component was rendered (the data inside the repeater sometimes changes) so that didn't work.
If you're using an Event defined in metadata, it can be as simple as adding click="myClickHandler(event)". You can also do something like this:
<yourNS:YourComponent id="foo>
<creationComplete>
<fx:Script>
(foo[yourRepeater.currentIndex] as EventDispatcher).addEventListener('the event', yourEventHandler);
</fx:Script>
</creationComplete>
</yourNS:YourComponent >
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
Ok, so I have a custom render that I have created:
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
horizontalAlign="center"
verticalAlign="middle"
width="100"
height="100">
<mx:Script>
<![CDATA[
[Bindable]
private var fileLabel:String;
[Bindable]
private var fileIcon:Class;
override public function set data(value:Object):void{
fileLabel = value.label;
fileIcon = value.file.url;
}
]]>
</mx:Script>
<mx:Image source="{fileIcon}" />
<mx:Label text="{fileLabel}" />
</mx:VBox>
That I want to use for a photo gallery with images that are dragged and dropped onto a TileList. I have that part down, but I can't seem to get the icon thing to work.
Given: value is sort of wrapper for a File class. I want to set the mx:Image source to something that needs to be of type Class. Using nativePath or url gives me a cast error. I see tons of examples online using XML and something like "Embed(/url/to/img.jpg)". I promise you that if you give me one of those examples (using a static image) I will give you a negative vote. THAT IS NOT WHAT IM LOOKING FOR HERE!
The reason that this isn't working is because the type of the fileIcon property is Class. You would generally would only want an object of type Class if you plan to use it like a factory, creating instances of that class with it. When you use the [Embed] metadata, you are indicating to the compiler that it should embed the specified asset into the SWF and also generate a Class to act as a factory for vending object instances that can represent that asset. However, as you had already discovered before posting this question, the problem with [Embed] is that you need to hard-code the reference, it doesn't let you supply a value at runtime (because the compiler needs to literally embed the asset, at compile-time).
Fortunately, mx:Image.source is a very flexible property that also accepts Strings (despite the fact that most documentation demonstrates using it with embedded assets). As long as the Flex application is capable of loading the asset, you can just supply a String-typed URL as the source.
Change the type of fileIcon to a String, and also verify that value.file.url is actually a URL of an image that your application can load. (You can test this just by hardcoding this URL into the mx:Image's source attribute.)