Question on bindable variables in AS3 - actionscript-3

I'm probably misusing bindable variables here, so please bear with me here while I try and explain what I'm trying to to.
I have a simple spark list where I allow people to select a background by clicking on one of the items. When a background is selected, I then save it to the SharedObject in order to use it when the user loads the application again later.
This list is populated by an ArrayCollection (binded variable) created as follows:
[Bindable] private var arrBG:ArrayCollection = new ArrayCollection();
This then gets populated the following way:
var objImage:Object;
var strSharedObjImage:String = sharedObj.sharedBackground.data.backgroundIndex;
// Background
objImage = new Object();
objImage.icon = ICONS_PATH + objImage.label;
objImage.label = "Titanium";
objImage.selected = (strSharedObjImage == objImage.fileName) ? true : false;
arrBG.addItem(objImage);
objImage = new Object();
objImage.icon = ICONS_PATH + objImage.fileName;
objImage.label = "Iron";
objImage.selected = (strSharedObjImage == objImage.label) ? true : false;
arrBG.addItem(objImage);
I then use it as the dataProvider on my spark list.
If you notice above, on my object I have a property called selected, which will get set to true, if the value of my shared object is the same as the value on the "label" property.
On the item renderer for my spark list, I have the following:
<s:ItemRenderer name="HorizontalListSkin"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
autoDrawBackground="false"
creationComplete="initMenuSkin(event)"
>
<fx:Script>
<![CDATA[
protected function initMenuSkin(event:Event):void
{
iconImage.source = data.icon;
iconText.text = data.label;
// Check to see if the item we're displying is selected. If it is make it stand out
if(data.selected){
iconText.setStyle("color", "Green")
}
}
]]>
</fx:Script>
<s:VGroup x="10" y="10" width="50" height="50" buttonMode="true" horizontalAlign="center">
<s:Image id="iconImage" horizontalCenter="0"/>
<s:Label id="iconText" fontFamily="Verdana" fontSize="11" fontWeight="bold" horizontalCenter="0" showTruncationTip="false"/>
</s:VGroup>
</s:ItemRenderer>
So as you can see, I'm simply changing the colour of the font on my selected item.
When I load it up, I can see that the item I have previously selected is marked in green, and if I select a new item, I would like it to now be marked as green instead.
Obviously there's a big gap in here, since nowhere in my explanation above I mention updating my bindable variable so in theory it wold propagate to my spark list (being it a bindable variable I would of thought it would simultaneously update the item on my list(?)).
Well, I have tried doing it in a few different ways, and the debugger does "say" my array has been updated, however, my list isn't being updated at all, and will only bring another item marked in green if I close the screen and open again (when it all gets reloaded)
The whole logic described above to create a new background is contained within a function, so whenever I select an item from my list of backgrounds I was triggering my "loadBackgrounds" method again, which would apply all the logic to know which is the selected background, and because the variable is binded with my spark list I'd have hoped would update the list. Thing is, it doesn't.
What am I doing wrong here? Am I going totally bonkers and there's a much easier way of doing this but only I can't see it?
Any help here would be appreciated.
Thanks in advance

After you set the data ion the collection you need to refresh it.
arrBG.refresh();
[EDIT]
Ok I re-read your question.
I think I misunderstood what you were asking.
You want to know how to update the list so the item renderer will re-render the new list after you made changes to the data provider?
function newSelection( val:String ):void{
for each( var item:Object in arrBG ){
if( item.label == val ){
item.selected = true;
}else{
item.selected = false;
}
}
arrBG.refresh();
}
// use the commit properties on your renderer not the init
// commit properties will trigger whenever there is a dataprovider update/change
<s:ItemRenderer name="HorizontalListSkin"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
autoDrawBackground="false"
>
<fx:Script>
<![CDATA[
override protected function commitProperties():void{
super.commitProperties();
iconImage.source = data.icon;
iconText.text = data.label;
// Check to see if the item we're displying is selected. If it is make it stand out
if(data.selected){
iconText.setStyle("color", "Green")
}
}
]]>
</fx:Script>
<s:VGroup x="10" y="10" width="50" height="50" buttonMode="true" horizontalAlign="center">
<s:Image id="iconImage" horizontalCenter="0"/>
<s:Label id="iconText" fontFamily="Verdana" fontSize="11" fontWeight="bold" horizontalCenter="0" showTruncationTip="false"/>
</s:VGroup>
</s:ItemRenderer>

Related

Flash Builder. ArgumentError: Error #2006: The supplied index is out of bounds

I am having an issue adding multiple form inputs to a scrollable list in a mobile app.
I can add up to 110 entries and this will work but adding more than that produces the error "ArgumentError: Error #2006: The supplied index is out of bounds.". Stepping through the code I can see the error is produced by the ScrollableStageText class when it tries to run updateViewPort() which draws a rectangle and calculates the y value to more than 8192.
I have created this Flash builder code which will reproduce the error.
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="HomeView"
initialize="init()">
<fx:Script>
<![CDATA[
import spark.components.FormItem;
public function init():void
{
var fi:FormItem;
var ti:TextInput;
for (var i:int = 0; i < 150; i++)
{
fi = new FormItem;
fi.label = i.toString();
fi.percentWidth=100;
ti = new TextInput;
ti.id="item_"+i;
ti.percentWidth=100;
ti.text = "";
ti.styleName = "ti";
fi.addElement(ti);
listtest.addElement(fi);
}
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Scroller width="100%" height="100%" >
<s:VGroup id="listtest" width="100%" horizontalAlign="center">
<s:TextInput />
</s:VGroup>
</s:Scroller>
</s:View>
I can see the issue has been raised on the apache forums but I cannot see how to set the y value as suggested here within flash builder.
Any help on this issue would be apprecated.
This is an interesting error.
Until the Apache team provide a fix the only solution I can think of would be to override TextInput class and provide a fix right down to the methods that are wrongly trying to set the StageText instance y property beyond the accepted value.
Unfortunately StageText is a final class so it cannot be overrided which would have been easier but composition is possible and could be the right way to go.

Data Value lose color when Scrolling on Datagrid in Flex 4

I am having problem when I am scrolling my datagrid. My item renderer column is losing its value when I scroll.
In my code, I change the colour of the itemrenderer (a label) on MouseOver and MouseOut. When I load the datagrid, this works fine but when I scroll down my grid some values have their colours already changed as if the MouseOver event has been executed on them.
Any one can tell me what is the issue with this?
Please have a look at the code for my datagrid and the itemrenderer. Please note that I am using a flexicious datagrid in my case.
Thanks for your precious help.
<flxs:columnLevel>
<flxs:FlexDataGridColumnLevel>
<flxs:columns>
<flxs:FlexDataGridColumn dataField="testcol" width="118" id="coltest">
<flxs:itemRenderer>
<fx:Component>
<mx:VBox horizontalAlign="left" paddingLeft="10" verticalAlign="middle">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
protected function lbl1_clickHandler(event:MouseEvent):void
{
//Do Something
}
protected function lbl1_mouseOverHandler(event:MouseEvent):void
{
var dataColor:uint;
var dataUnderline:String = 'none';
if (data.payer == 'D'){
dataColor = 0x999999;
}
else{
dataColor = 0x0DACE0; //color
}
}
protected function lbl_mouseOutHandler(event:MouseEvent):void
{
var dataColor:uint;
if (data.payer == 'D'){
dataColor = 0x999999;
}
else{
dataColor = 0x000000;
}
}
]]>
</fx:Script>
<mx:Label id="lbl" paddingLeft="10" left="10" fontWeight="normal" mouseOut="lbl_mouseOutHandler(event)" mouseOver="lbl_mouseOverHandler(event)" text="{data.testcol}" click="lbl1_clickHandler(event)"/>
</mx:VBox>
</fx:Component>
</flxs:itemRenderer>
</flxs:FlexDataGridColumn>
</flxs:columns>
</flxs:FlexDataGridColumnLevel>
</flxs:columnLevel>
</flxs:FlexDataGrid>*
Short answer: You haven't overridden the "set data" on your itemrenderer, so it will keep the previous properties.
Itemrenderers in the Flex world are recycled, meaning that even though you may have 100 objects in your list, you only have 10-12 actual instances of your inline itemrenderer. The caveat is that the when the itemrenderer is populated with new data, the data-dependent properties aren't reset. That lack of resetting is why you have renderers that appear to have been mousedOver when in fact they havent'.
The standard solution is to override the "set data" function which does exactly as it sounds, it performs operations when the data is set. If I had a similar problem, my code would look like (best I can do with a 9:00AM memory).
override public function set data(value:Object):void
{
if( value != null )
{
super.data = value'
dataColor = 0xwhatever-color-non-moused-over-objects-should-have;
}
}
Here is a link with more information.

GridColumn labelFunction not bindable

Recently i noticed one problem when using labelFunction for gridColumn:
It does not refresh, when nested (displayed) property is changed.
// SomeClass and SomeSubClass has [Bindable] metadata
[Bindable]
private var someSubClass:SomeSubClass;
[Bindable]
private var someClass:SomeClass;
private function inits():void{
someSubClass = new SomeSubClass();
someSubClass.value = "SomeSubValue";
someClass = new SomeClass();
someClass.subclass = someSubClass;
var ac:ArrayCollection = new ArrayCollection();
ac.addItem(someClass);
dg.dataProvider = ac;
}
private function myLabelFunction(item:Object, column:GridColumn):String
{
return (item.subclass)? item.subclass.value : "";
}
mxml
<s:Button click="someSubClass.value = 'Hello World'"/>
<s:DataGrid dataProvider="{dataProvider}" width="100%" height="200">
<s:columns>
<s:ArrayList>
<s:GridColumn labelFunction="{myLabelFunction}" headerText="NoRenderer"/>
<s:GridColumn headerText="WithRenderer">
<s:itemRenderer>
<fx:Component>
<s:GridItemRenderer>
<s:Label text="{data.subclass.value}"/>
</s:GridItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:gridColumn>
</s:ArrayList>
</s:columns>
</s:DataGrid>
When button clicked first column which use labelFunction still displaing old value, but second column (with custom renderer) works fine. I am trying to use as less as possible custom item renderers for performance issue. Or something wrong with my label function?
I've never tried this, but I'm not surprised that it won't update. In the second example, you've explicitly set a binding chain on data.subclass.value. In the first case with myLabelFunction, there's no binding set up on item.subclass, so Flex won't have any "magic" way of knowing you'd like to watch that value for changes.
I would go with the custom renderer, I'm not sure what your particular performance concerns are but it should be manageable as long as you leave virtual layout / renderer recyling on.

Item Renderer not Working correctly in Spark List

I used flex sdk 4.5 for creating Adobe Air application. My application download files one by one. I show the currently downloading, pending and completed files in spark list. I used item Renderer that show the progress of the file and other states.
It works fine if selected files fit in current view. But as number of downloads increase, the Spark list shows vertical scroll. here I have the problem. Here file downloading functionality works fine, but if I try to scroll the list, File titles mix up or displayed out of order.
item renderer code:
<?xml version="1.0" encoding="utf-8"?>
<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"
creationComplete="onInit()"
>
<fx:Script><![CDATA[
import com.streamingnetworks.hdvrwin.vo.DownloadInfo;
import mx.events.CloseEvent;
[Bindable]private var downloadInfo:DownloadInfo;
private var url:String = "";
private var fileName:String = "";
private function onInit():void
{
downloadInfo = data as DownloadInfo;
downloadInfo.addEventListener("Progress", onProgress);
downloadInfo.addEventListener("DownloadComplete", onComplete);
prog.label = "Downloading %3%%";
lblPath.text = " - " + downloadInfo.getPathIp();
trace("Downloading: " + downloadInfo.name);
}
private function onProgress(e:Event):void
{
prog.setProgress(downloadInfo.downloadedBytes, downloadInfo.totalBytes);
lblState.text = downloadInfo.getDownState();
}
private function onComplete(e:Event):void
{
prog.label = "Download Complete";
}
protected function onClose(event:CloseEvent):void
{
downloadInfo.state = "deleted"
}
]]></fx:Script>
<s:TitleWindow top="0" left="0" right="0" bottom="0" dropShadowVisible="false"
title="{'Downloading ' + downloadInfo.name}" close="onClose(event)">
<mx:ProgressBar id="prog" label="" mode="manual"
trackHeight="20" labelPlacement="center" width="100%"/>
<s:HGroup top="25">
<s:Label id="lblState" text="Downloading 0 Bytes of 0 Bytes."/>
<s:Label id="lblPath"/>
</s:HGroup>
</s:TitleWindow>
</s:ItemRenderer>
here the list control where I am using that renderer
<s:List id="lstControls" dataProvider="{urlArr}"
itemRenderer="ItemRenderer"
left="5" right="5" bottom="5" top="40"/>
urlArr is an arrayList that contains only url in string form.
How can I solve this problem? Any Help will be appreciated. Thanks
Your renderers are not updating correctly because you are setting your data when the renderer is created - but you should be overriding the set data method, eg:
override public function set data(value:Object):void
{
super.data = value;
downloadInfo = data as DownloadInfo;
downloadInfo.addEventListener("Progress", onProgress);
downloadInfo.addEventListener("DownloadComplete", onComplete);
prog.label = "Downloading %3%%";
lblPath.text = " - " + downloadInfo.getPathIp();
trace("Downloading: " + downloadInfo.name);
}
And remove the creationComplete listener.
As a better practice. Have all the data your progress bars will need all ready in the data provider and have them react ONLY to changes in the data. Adding and removing listeners and binding internally will only cause you grief. Renderers do not live forever and are re-used inside of list components. Manage everything through overriding set data().
In this example I would create a model object with a name like FileDownloadInfo and properties such as .downloadProgress .fileName and .ipAddress then have the renderer update on property change events of .downloadProgress

Flex: preventDefault on spark:ListBase not working

The proposed UX I am trying to achieve is this:
user clicks menu item (via a listBase subclass: e.g. ButtonBar or TabBar)
prevent initial selection
validate if user needs to address issues (e.g. unsaved data on a form, etc.)
if valid, take selection and set the listBase to that selectedIndex, otherwise present warnings to user and cancel out the selection process altogether
This does not work as you'd expect. Utilizing the IndexChangeEvent.CHANGING type and the preventDefault works to kill the selection, but at step 4, when I am programmatically setting the selectedIndex of the listBase, it then tries to redispatch the CHANGING event (this despite what the API docs claim).
Here is a sample application src code if you'd like to try this for yourself. I look forward to your comments & solutions.
Thanks.
J
http://snipt.org/vUji3#expand
<?xml version="1.0" encoding="utf-8"?>
<s:Application minWidth="955" minHeight="600"
xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
import flash.utils.setTimeout;
import mx.core.mx_internal;
import spark.events.IndexChangeEvent;
use namespace mx_internal;
[Bindable]
private var doPreventDefault:Boolean;
[Bindable]
private var delayMS:uint = 500;
private function buttonbar_changingHandler( event:IndexChangeEvent ):void
{
// TODO Auto-generated method stub
if ( doPreventDefault )
{
event.preventDefault();
setTimeout( delayedLogic, delayMS, event.newIndex );
}
}
private function delayedLogic( index:int ):void
{
//disabling this results in an endless loop of trying to set the selected index
// doPreventDefault = false;
//this should NOT be hitting the changing handler since we're supposed to be dispatching a value commit event instead.
bb.setSelectedIndex( index, false );
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:layout>
<s:VerticalLayout horizontalAlign="center"/>
</s:layout>
<s:ButtonBar id="bb"
changing="buttonbar_changingHandler(event)">
<s:dataProvider>
<s:ArrayList>
<fx:String>btn 0</fx:String>
<fx:String>btn 1</fx:String>
<fx:String>btn 2</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ButtonBar>
<s:CheckBox label="preventDefault?"
selected="#{ doPreventDefault }"/>
<s:NumericStepper maximum="5000" minimum="500"
stepSize="500" value="#{ delayMS }"/>
</s:Application>
Looking at the SDK, the IndexChangeEvent.CHANGING event is actually preventable - despite the documentation here says that cancelable is false, so my bad on that (although ASDoc went a little sideways), however things get a little interesting from here.
In ListBase #1296 this is only ever dispatched from the commitSelection(dispatchEvents:Boolean = true) method. In ButtonBarBase:dataProvider_changeHandler() is the only place that specifically calls to not dispatch the event, but in ListBase, it's called in commitProperties #939 when there is a proposedSelectionIndex.
So from your code above, if you are trying to set the selection - this is going to call the commitSelection, which I believe is causing the call stack issue. The Timer delay is just going to exacerbate the issue, since at 500ms the UI will have gone through its invalidation cycle at least once, meaning the commitSelection will be executed again because of an invalidateProperties flag is being set from the proprosedSelectionIndex eventually stemming from setSelectedIndex #729
So how to fix this.
I would look at only doing the prevent if the validation fails, otherwise allow it to proceed as normal. If it does fail, call the prevent, set an errorString or equivalent, but don't attempt to change the selection.
[edit] Read RiaStar's comment, and I just concurred with the same 'solution'.