Flex List item renderer - apache-flex

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.

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).

How can i refer to an external function in an actionscript file - Flashbuilder

I hava a problem with flashbuilder:
I have a list with an itemrenderer that renders an image that (should be) draggable.
the rendered image refers to a function that is declared in an actionscript file: dragDrop.as in the folder AS.
the list:
<s:List id="imageList" width="139" height="438"
dataProvider="{xmlListColl}"
itemRenderer="itemRenderer.ImageRenderer"
dragEnabled="true">
</s:List>
the itemrenderer renders this image and refers to the function doDrag:
<mx:Image width="100" height="70" maintainAspectRatio="true"
MouseDownEffect="AS.dragDrop.doDrag(event)"
source="{data.#thumbnailImage}"/>
the function in dragDrop.as:
public function doDrag(event:MouseEvent):void
{
var img:mx.controls.Image = event.currentTarget as mx.controls.Image;
var dragImg:mx.controls.Image = new mx.controls.Image();
dragImg.source = img.source;
var dsource:DragSource = new DragSource();
dsource.addData(img, 'img');
DragManager.doDrag(img, dsource, event, dragImg);
}
but it seems the function is never called...
also parentdocument and outerdocument don't seem to work (if i put the function in the document where the itemrenderer is called)
Please Help!
There's a few issues here, but ultimately, you're not seeing a reference to that method, which means your dragDrop.as file is not including.
Here are a few suggestions:
Replace MouseDownEffect with mouseDown. Instead of causing an effect to occur, you're now listening for the "MouseEvent.MOUSE_DOWN" event to fire. Differences between effects and responding to events are described here and, to quote, "Behaviors let you add animation and motion to your application when some user or programmatic action occurs, where a behavior is a combination of a trigger paired with an effect. A trigger is an action, such as a mouse click on a component ... An effect is a visible or audible change to the target component that occurs over a period of time, measured in milliseconds."
Make sure you're including your dragDrop.as file. Flex 3 vs. Flex 4 handle script tags differently. If you're not including or importing your code, then of course it won't fire.
include vs import is a good question. You can "include" code that's a definition of methods, instances, constants, etc. But you would "import" defined classes for use. Since your method is a public function, does it live within a class? Or is it meant to just live in the script file, in which case you should remove the accessor "public"
If you're looking to implement Drag-and-Drop, I highly recommend NOT re-inventing the wheel and checking out what Adobe has already implemented for components, including dragEnabled='true' and dragMoveEnabled='true'. Check them out here: http://livedocs.adobe.com/flex/3/html/help.html?content=dragdrop_4.html
http://livedocs.adobe.com/flex/3/html/help.html?content=dragdrop_1.html
Here is an example of a Flex 3 script tag:
<mx:Script source="AS/dragDrop.as"/>
Here is an example of a Flex 4 script tag:
<fx:Script source="AS/dragDrop.as"/>
This is an link to the documentation on how to include directly into a <fx:Script> tag the code you'd like: http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf61c8a-7ff4.html

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.

Flex cancel a change event on a Tree

Brief details: I'm using Flex 3.5.
I have a Tree component that's used as a navigation menu between different 'pages'.
When the user clicks a certain option in the menu, I switch the 'page' by switching between State components in my application.
The thing is that when the user indeed clicks an option in the menu, I want to perform a validation of some of the information in a certain component. If the validation fails, I show an alert, and I'd like to prevent the navigation to the other page. One part of this is simply not changing the currentState of the document, but the tree component still goes on with the change event, and the result is page A still being shown on the screen, whereas the selected option in the tree is page B (to which the user wanted to navigate, but failed since some of the information wasn't valid).
I tried to figure out how I can cancel the change event on the tree component itself.
The thoughts I had didn't quite fit nicely:
I searched for a slightly different event (such as 'changing' or 'startChange') on which I can call the stopPropagation() method (since the regular 'change' event is not cancelable), but none exists for the Tree component.
I also thought about always saving the current option that's selected in the Tree component by myself, and when the validation fails, I will set the Tree's selectedItem to that saved option. That's also ugly because such an action will raise another change event on the Tree, thus another change to the States components, and another population of the page in which I'm already at. That's something I really don't want to do.
I also though about using a different component, such as Menu (and I also found an implementation of a vertical Menu), but that doesn't even seem to help. The same problem will exist there.
Is there a proper way to do this?
There must be a best-practice for preventing a change process to commit!
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.ListEvent;
private function tree_changeHandler(event:ListEvent):void
{
trace("Change, selectedItem.label is: " + tree.selectedItem.label);
}
protected function tree_itemClickHandler(event:ListEvent):void
{
var data:Object = event.itemRenderer.data;
if (!tree.isItemSelectable(data))
Alert.show("Item \"" + data.label + "\" is not selectable");
}
]]>
</mx:Script>
<local:MyTree id="tree" change="tree_changeHandler(event)" itemClick="tree_itemClickHandler(event)">
<local:dataProvider>
<mx:ArrayCollection>
<mx:Object label="Label 1"/>
<mx:Object label="Label 2"/>
<mx:Object label="Label 3 (non-selectable)"/>
<mx:Object label="Label 4"/>
</mx:ArrayCollection>
</local:dataProvider>
</local:MyTree>
</mx:Application>
Source for MyTree.as:
package
{
import mx.controls.Tree;
public class MyTree extends Tree
{
override public function isItemSelectable(data:Object):Boolean
{
if (!super.isItemSelectable(data))
return false;
var label:String = data.label;
if (label.indexOf("non-selectable") >= 0)
return false;
return true;
}
}
}
Eventually I found the place to put the code that determines each item's selectability: when the information that should be validated is changed, I perform the validation, and according to its result I set a property to all of the items in the Tree component, indicating whether they can be navigated to or not. If the validation was successful, the property is set to allow navigation, and if unsuccessful, it is set not to allow navigation.
Like Maxim, I extend the Tree component and overrode the isItemSelectable() method to check this property of the specified item, this way preventing the change process.
The access between the view that holds the information to-be-validated, and the view that holds the Tree component (they are not necessarily the same view) is done via a presentor class that holds both views (I use the MVP mechanism). This is not the most elegant design, but it is much better than anything else I could have thought of. The alleged problem with the design is the coupling between the views and the complexity of the presentor, that has to deal with more than one view and have methods that are related to the interaction between the views (instead of methods that represent actions of a specific view). The thing is that business-wise, the two views are coupled (since the information in one affects the navigation tree in the other), thus the presentor couples between them. The coupling is also done through the interface of the presentor, so that each view doesn't really "know" the other view.
I hope it might help other people.
Thanks,
Daniel

Resources