Flex Drop Down List Binding Issue (or Bug?) - actionscript-3

I'm using the MVC model in my flex project.
What I'd like is to bind a value object's class properties to a view mxml, then change that view by altering the value object.
What happens:
Set the selected value to 'c' - index 2
Add 'x,y,z,' before 'c'
Hit enter -> now index 5
Hit enter -> now index is -1
See 4.
Why does only the first update work ? I know I'm probably missing something obvious...
Edit: Running Example
(P.S. first post and im not sure how to turn on MXML highlighting)
<?xml version="1.0" encoding="utf-8"?>
<s:Application 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="created(event)"
width="160" height="220">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
import mx.events.FlexEvent;
import spark.events.IndexChangeEvent;
//===================================
// Pretend Value Object Class
[Bindable] private var list:ArrayList = null;
[Bindable] private var index:int = 0;
//===================================
protected function created(event:FlexEvent):void {
ddValues.addEventListener(FlexEvent.ENTER, update);
update();
}
private function update(... args):void {
//note selected item
trace("dropdown index: " + dd.selectedIndex);
var s:String = dd.selectedItem as String;
trace("selected item: " + s);
//build new list from csv
list = new ArrayList(ddValues.text.split(","));
trace("new list: " + ddValues.text);
trace("selected item: " + s);
//if exists in new list, set value object index
var newIndex:int = 0;
if(list)
list.toArray().forEach(function(ss:String, i:int, a:Array):void {
if(s == ss) newIndex = i;;
});
index = newIndex;
trace("new index: " + index + " (dropdown index: " + dd.selectedIndex + ")");
trace("===");
}
protected function ddChange(event:IndexChangeEvent):void
{
trace("selected item: " + (dd.selectedItem as String) + " (dropdown index: " + dd.selectedIndex + ")");
trace("===");
}
]]>
</fx:Script>
<s:Panel width="100%" height="100%" title="Drop Down Bug">
<s:layout>
<s:VerticalLayout gap="10" paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10"/>
</s:layout>
<s:DropDownList id="dd" dataProvider="{list}" selectedIndex="#{index}" change="ddChange(event)"></s:DropDownList>
<s:Label text="Label: {dd.selectedItem as String}" paddingTop="5" paddingBottom="5"/>
<s:Label text="Code Index: {index}" paddingTop="5" paddingBottom="5"/>
<s:Label text="DropDown Index: {dd.selectedIndex}" paddingTop="5" paddingBottom="5"/>
<s:TextInput id="ddValues" text="a,b,c,d,e"/>
</s:Panel>
</s:Application>
And heres the output
Edited code and added traces. Heres the output that shows my problem:
dropdown index: -1
selected item: null
new list: a,b,c,d,e
selected item: null
new index: 0 (dropdown index: 0)
===
selected item: c (dropdown index: 2)
===
dropdown index: 2
selected item: c
new list: a,b,x,y,z,c,d,e
selected item: c
new index: 5 (dropdown index: 5)
===
dropdown index: 5
selected item: c
new list: a,b,x,y,z,c,d,e
selected item: c
new index: 5 (dropdown index: 5)
===
dropdown index: -1
selected item: null
new list: a,b,x,y,z,c,d,e
selected item: null
new index: 0 (dropdown index: 0)
===

Oh, I see what's going on now. When you replace the whole list, the selected item will be null, because the item that was selected was in the old list. You'll need to store the selected item when it is selected, and then do the comparison against that stored item, not the current selected (null) item.
Note that what you're doing is in no way MVC, unless you've redefined MVC to mean "Model, View, and Controller are all the same thing." In MVC, the model has no idea of the view, and the view only has access to read as much of the model as it needs to to display the data. It does not have write access to the model. That is the function of the controller.

Related

List with different column count

As the pic show(This is drawn by PhotoShop, not implemented yet), I want to implemnt a List like this one. It has different column count, say the first row has only one item, and the others have two items . I tried to use itemRendererFunction to detect the different item(the first row treat as a rendererA, the others treat as another rendererB),but it didn't work.
The cleanest solution to this problem, is to create a custom layout (we've discussed in the comments how Romi's solution will eventually cause too many problems). However, this is usually not an easy thing to do.
I will give you a rough sketch of what this custom layout might look like, so you can use it as a starting point to create one that does exactly what you need.
To create a custom layout, you must subclass BaseLayout and override and implement its updateDisplayList and measure methods.
To make things easier (and in order not to dump 500 lines of code in here), I used some hardcoded variables for this example. It assumes there will always be two columns, the first item will always be 200x200 px, and the other items will always be 100x100 px. There is no horizontalGap or verticalGap.
The consequence is of course that you can use this custom layout (as it is now) only for this specific List and these specific ItemRenderers. If you want it to be more generic, you'll have to do a lot more calculations.
But now for the code:
public class MyCustomLayout extends LayoutBase {
//hardcoded variables
private var columnCount:int = 2;
private var bigTileWidth:Number = 200;
private var bigTileHeight:Number = 200;
private var smallTileWidth:Number = 100;
private var smallTileHeight:Number = 100;
override public function updateDisplayList(width:Number, height:Number):void {
var layoutTarget:GroupBase = target;
if (!layoutTarget) return;
var numElements:int = layoutTarget.numElements;
if (!numElements) return;
//position and size the first element
var el:ILayoutElement = useVirtualLayout ?
layoutTarget.getVirtualElementAt(0) : layoutTarget.getElementAt(0);
el.setLayoutBoundsSize(bigTileWidth, bigTileHeight);
el.setLayoutBoundsPosition(0, 0);
//position & size the other elements in 2 columns below the 1st element
for (var i:int=1; i<numElements; i++) {
var x:Number = smallTileWidth * ((i-1) % 2);
var y:Number = smallTileHeight * Math.floor((i-1) / 2) + bigTileHeight;
el = useVirtualLayout ?
layoutTarget.getVirtualElementAt(i) :
layoutTarget.getElementAt(i);
el.setLayoutBoundsSize(smallTileWidth, smallTileHeight);
el.setLayoutBoundsPosition(x, y);
}
//set the content size (necessary for scrolling)
layoutTarget.setContentSize(
layoutTarget.measuredWidth, layoutTarget.measuredHeight
);
}
override public function measure():void {
var layoutTarget:GroupBase = target;
if (!layoutTarget) return;
var rowCount:int = Math.ceil((layoutTarget.numElements - 1) / 2);
//measure the total width and height
layoutTarget.measuredWidth = layoutTarget.measuredMinWidth =
Math.max(smallTileWidth * columnCount, bigTileWidth);
layoutTarget.measuredHeight = layoutTarget.measuredMinHeight =
bigTileHeight + smallTileHeight * rowCount;
}
}
And you can use it like this:
<s:List dataProvider="{dp}" height="300">
<s:layout>
<l:MyCustomLayout />
</s:layout>
</s:List>
Whenever you want to change the defined behavior of an existing component, always check first if you can solve the problem with skinning. It is a really powerful feature i Flex, and can also provide a solution in this case.
So, let's begin, assuming you already have your List, you only need to create a custom skin which "splits" the data provider in two parts, the first item, and all the others. So, let's assume we have this initial setup:
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var c:ArrayCollection = new ArrayCollection([
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6",
"String 7",
"String 8",
"String 9",
"String 10",
"String 11",
"String 12",
"String 13",
"String 14",
"String 15"]);
]]>
</fx:Script>
<s:List skinClass="CustomSkinList" dataProvider="{c}" />
As you can see, we define a custom list skin, which is just a copy of spark.skins.spark.ListSkin, the default skin for spark.components.List element.
Before we handle the data provider logic, we need to take a look at how the list items are rendered. This is done by using a DataGroup element, added to the skin, like so:
<s:Scroller left="0" top="0" right="0" bottom="0" id="scroller" minViewportInset="1" hasFocusableChildren="false">
<!--- #copy spark.components.SkinnableDataContainer#dataGroup -->
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<!--- The default layout is vertical and measures at least for 5 rows.
When switching to a different layout, HorizontalLayout for example,
make sure to adjust the minWidth, minHeight sizes of the skin -->
<s:VerticalLayout gap="0" horizontalAlign="contentJustify" requestedMinRowCount="5" />
</s:layout>
</s:DataGroup>
</s:Scroller>
Here is the place where we will have to make the changes, in order to get the first element to render differently. What we need to do, is just add another DataGroup, for rendering the first element in a custom way (this of course means using a custom item renderer). Now, our scroller looks like this:
<s:Scroller left="0"
top="0"
right="0"
bottom="0"
id="scroller"
minViewportInset="1"
hasFocusableChildren="false">
<!--- #copy spark.components.SkinnableDataContainer#dataGroup -->
<s:VGroup width="100%" height="100%">
<s:DataGroup id="firstItemDataGroup"
width="100%"
itemRenderer="CustomItemRenderer"
height="20">
<s:layout>
<s:VerticalLayout />
</s:layout>
</s:DataGroup>
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<!--- The default layout is vertical and measures at least for 5 rows.
When switching to a different layout, HorizontalLayout for example,
make sure to adjust the minWidth, minHeight sizes of the skin -->
<s:TileLayout horizontalAlign="center" requestedColumnCount="2" />
</s:layout>
</s:DataGroup>
</s:VGroup>
</s:Scroller>
Notice the 'firstItemDataGroup' addition, also the fact that it uses a different item renderer, than the default dataGroup element. With this new container in place we can proceed to render the elements. The custom skin need to override the parent initializationComplete() method, like so:
override protected function initializationComplete():void
{
useChromeColor = true;
if (hostComponent.dataProvider && hostComponent.dataProvider.length > 0)
{
var allItems:Array = hostComponent.dataProvider.toArray().concat();
firstItemDataGroup.dataProvider = new ArrayCollection([hostComponent.dataProvider.getItemAt(0)]);
var remainingItems:Array = allItems.concat().reverse();
remainingItems.pop();
var reversed:Array = remainingItems.reverse();
dataGroupProvider = new ArrayCollection(reversed);
}
super.initializationComplete();
}
What was added was just the 'if' block, and a private variable, named dataGroupProvider. This is because we will set the new dataProvider, the one starting from the second element, to the dataGroup element, in the updateDisplayList() method. Here is what it looks like:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
if (getStyle("borderVisible") == true)
{
border.visible = true;
background.left = background.top = background.right = background.bottom = 1;
scroller.minViewportInset = 1;
}
else
{
border.visible = false;
background.left = background.top = background.right = background.bottom = 0;
scroller.minViewportInset = 0;
}
// Here we assign the new data provider to the dataGroup element
if (dataGroupProvider)
dataGroup.dataProvider = dataGroupProvider;
borderStroke.color = getStyle("borderColor");
borderStroke.alpha = getStyle("borderAlpha");
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
In conclusion, just by creating a custom skin for our List element, we can use two containers for rendering the first item in a different way, from the rest of the elements. You shouldn't underestimate the power of Flex Skinning :)
Hope this helps. Have a great day!

Keep specific Datagrid row always at the end - also with Sorting

I'm stuck on following matter:
I have a datagrid with 10 items... I also added a extra row (row number 11) where I show a total of prevouis fields... But I always want to keep this last row (the totals) on the last line of the Datagrid (so always on position 11).
This means that when the datagrid is sorted on a column, the last row also changes in position according to the value in the datafield. Is there any easy and straightforward way of preventing it to be sorted with the other columns so that the column with the totals is always placed last? Or what approach would I use best?
Thanks for any help!
Please find below code Hope this may help you, i tried some workaround to achieve what you are looking for i am not sure how feasible is this code:-
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
[Bindable]
private var dpHierarchy:ArrayCollection= new ArrayCollection([
{name:"A", region: "Arizona", number:1},
{name:"B", region: "Arizona", number:2},
{name:"C", region: "California", number:3},
{name:"D", region: "California", number:4}
]);
private function creationComp():void
{
var totalValue:int = 0;
for(var i:int=0; i<dpHierarchy.length; i++)
{
totalValue = totalValue + dpHierarchy[i].number;
}
var obj:Object = new Object();
obj.name = "Total";
obj.region = "==";
obj.number = totalValue;
dpHierarchy.addItem(obj)
myADG.dataProvider = dpHierarchy;
}
private function sortHandler(obj1:Object, obj2:Object):int
{
var lastObj:Object = dpHierarchy.getItemAt(dpHierarchy.length-1);
if(lastObj.number == obj1.number || lastObj.number == obj2.number)
return 0;
if(obj1.number < obj2.number) {
return -1;
} else if(obj1 == obj2) {
return 0;
}
return 1;
}
]]>
</fx:Script>
<mx:AdvancedDataGrid id="myADG" x="50" y="50"
width="400" height="300"
variableRowHeight="true" creationComplete="creationComp()">
<mx:columns>
<mx:AdvancedDataGridColumn dataField="name" headerText="Name" sortCompareFunction="sortHandler"/>
<mx:AdvancedDataGridColumn dataField="region" headerText="Region" sortCompareFunction="sortHandler"/>
<mx:AdvancedDataGridColumn dataField="number" headerText="Number" sortCompareFunction="sortHandler"/>
</mx:columns>
</mx:AdvancedDataGrid>
</s:Application>
I'd consider a few options in this order.
Put your summary data somewhere else. That's really not what a row in a datagrid was intended for.
Not let your users sort by clicking on the column name (sortableColumns='false') if you really don't want them to sort the data.
Create a custom sort for each column that ensures you always put your summary data last. Here's a decent tutorial: http://www.switchonthecode.com/tutorials/flex-datagrid-custom-sorting. I'd like to stress that this would be a high maintenance idea because you'd have to build out a custom sort for every column.

Flex: spark DataGrid displays value other than the current value in dataProvider

I have 2 flex datagrids on a screen. The user can select one or more rows from either datagrid and move them to the other datagrid. The lower table is empty when entering the application. For example:
Item Color Price
--------------------
item57 red $5.55
item62 blue $5.29
item808 green $2.21
Row Item Color Price
---------------------------
Note there is a column that numbers the rows on the bottom datagrid (only).
When I enter the application and move, for example, 3 rows from the top to bottom grid, the row numbers are fine (they show rows 1, 2, and 3). For example:
Item Color Price
--------------------
Row Item Color Price
---------------------------
1 item57 red $5.55
2 item62 blue $5.29
3 item808 green $2.21
If I then move, for example, row 1 in the bottom grid back to the top...
Item Color Price
--------------------
item57 red $5.55
Row Item Color Price
---------------------------
1 item62 blue $5.29
2 item808 green $2.21
and then back again to the bottom grid...
Item Color Price
--------------------
Row Item Color Price
---------------------------
1 item62 blue $5.29
2 item808 green $2.21
1 item57 red $5.55
the row number is supposed to display 3 because it inserts into the bottom grid at the end of the list, but when it does this, it displays the (old) row number value of 1.
When I debug and look at the dataprovider = _myData, I see the rowNumber value for the row in question (for item57 above) equals 3 (as it should). However, it is displayed in the lower datagrid as 1.
How can the dataprovider value be different than what is displayed in the DataGrid?
[I can also debug and look at gridLower column information, and it also shows the correct value of 3 for rowNumber for the data in question.]
The lower datagrid is similar to the following (although I'm using a custom itemRenderer, removed for simplicity here):
[Bindable]
private var _myData:ListCollectionView=new ListCollectionView(new ArrayList());
...
<s:DataGrid dataProvider="{_myData}">
<s:columns>
<fx:Array>
<s:GridColumn id="gridLower" headerText="myHeader" dataField="rowNumber"/>
...
The function that adds the upper table's row(s) to the lower table is:
private function addRow():void {
var selectedIndices:Object=gridUpper.grid.selectedIndices;
for (var i:int=selectedIndices.length-1;i>=0;i--) {
var item:Object=_upperTableData.removeItemAt(selectedIndices[i]);
item.rowNumber=_myData.length+1;
_myData.list.addItem(item);
}
// I tried adding "_myData.refresh();" here and it had no effect
// I tried adding "ListCollectionView(gridLower.dataProvider).refresh();" and it had no effect
// I tried adding "grid2.dataProvider.refresh();" here but it had no effect
}
UPDATE 1: If I re-sort any column in the lower table, the correct values appear. I seem to be observing what's reported in this link:
http://www.barneyb.com/barneyblog/2007/06/23/another-flex-weirdnessgotcha/
Haven't found a solution yet though. See attempts in my addRow() function above. Am I on the right track?
UPDATE 2: While re-sorting manually corrects the data in the lower grid, I haven't found a way to do this programmatically. I tried inserting:
_myData.sort=null;
var complete:Boolean=_myData.refresh();
just before the end of addRow() function above, but it didn't resolve my issue. When debugging, complete is true, but still the lower grid displays the stale data.
New answer :) I will delete the old one if this helps.
I haven't used Spark DataGrid yet, was expecting it to behave like a List.
Found this in this in some comments in the source for this method of DataGrid:
public function invalidateCell(rowIndex:int, columnIndex:int):void
You can invalidate an entire row/col by passing -1 for the other value. In the quote from the docs below, you can also use dataProvider.itemUpdated()
If the specified cell is visible, it is redisplayed. If
variableRowHeight=true, then doing so may cause the height of the
corresponding row to change. If columnIndex is -1, then the entire row
is invalidated. Similarly if rowIndex is -1, then the entire column is
invalidated.
This method should be called when there is a change to any aspect of
the data provider item at rowIndex that might have some impact on the
way the specified cell is displayed. Calling this method is similar to
calling the dataProvider.itemUpdated() method, which advises the Grid
that all rows displaying the specified item should be redisplayed.
Using this method can be relatively efficient, since it narrows the
scope of the change to a single cell.
Now I finally know where the itemUpdated() method on collections (ArrayCollection, ListCollectionView) can actually be used!
[edit]
Give your grid an id:
<s:DataGrid id="lowerDataGrid" dataProvider="{_myData}">
Then you should be able to do this in your addRow() method after updating the collection:
lowerDataGrid.invalidateCell(item.rowNmber -1, 1); // assuming rowNumbers are 0 based
In my case, in my main class that contains the grid as an element, whenever I want to force a refresh of all the cells:
gridsDataProvider.addItemAt(new Object(), 0);
gridsDataProvider.removeItemAt(0);
But my GridItemRenderer needs to cater for the change in the data. So I created an updateDisplay method:
private function updateDisplay() : void {
if (transWindow != null) {
if (data == transWindow.total) {
if (field != "vat" && field != "total") {
textInput.visible = false;
} else {
line.visible = true;
textInput.enabled = false;
textInput.setStyle("fontWeight", "bold");
textInput.setStyle("fontSize", 14);
}
} else {
line.visible = false;
textInput.visible = true;
textInput.enabled = true;
textInput.setStyle("fontWeight", "normal");
textInput.setStyle("fontSize", 12);
}
}
}
This method is called from both the creationComplete handler ...
protected function init(event:FlexEvent):void
{
getTransWindow(event.target.automationOwner);
updateDisplay();
}
... well as the data setter of the GridItemRenderer
override public function set data(v:Object):void {
super.data = v;
if (v == null) {
textInput.text = "";
return;
}
var value : * = v[field];
if (value == null || value == undefined) {
textInput.text = "";
return;
}
if (value is Number)
textInput.text = Number(value).toFixed(2);
else
textInput.text = ""+value;
updateDisplay();
}
The mxml body of my renderer is quite simple:
<s:Line id="line" width="100%" visible="false" yFrom="1" yTo="1">
<s:stroke>
<s:SolidColorStroke color="0" weight="0"/>
</s:stroke>
</s:Line>
<s:TextInput id="textInput" y="2" restrict="0123456789." width="100%" height="100%" minWidth="10" borderVisible="false" textAlign="right"/>
O, in case you wondered, the getTransWindow method is just a method to retrieve the main calling class that contains the grid:
private function getTransWindow(par : *) : void {
if (transWindow != null)
return;
transWindow = par;
while (!(transWindow is TransactionWindow)) {
transWindow = transWindow.parent;
}
}
Hope this saves someone some time
Regards
Christo

How to sort an ArrayCollection in Flex

I want to sort an Arraycollection by fieldName as ascending. Here's my code and I want to know whether it's right. Do you have any suggestions?
public static function arrayCollectionSort(ar:ArrayCollection, fieldName:String, isNumeric:Boolean):void
{var dataSortField:SortField = new SortField();
dataSortField.name = fieldName;
dataSortField.numeric = isNumeric;
var numericDataSort:Sort = new Sort();
numericDataSort.fields = [dataSortField];
arrCol.sort = numericDataSort;
arrCol.refresh();}
The code you have is correct, except for a type. arrCol should be ar. The code looks almost exactly like the code at the blog Flex Examples, which is also correct.
Just change is change arrCol to ar like below:
public static function arrayCollectionSort(ar:ArrayCollection, fieldName:String, isNumeric:Boolean):void
{
var dataSortField:SortField = new SortField();
dataSortField.name = fieldName;
dataSortField.numeric = isNumeric;
var numericDataSort:Sort = new Sort();
numericDataSort.fields = [dataSortField];
ar.sort = numericDataSort;
ar.refresh();
}
Not sure with numeric but otherwise everything else is correct.
Here is full example how to use sort in Array collection
http://blog.flexexamples.com/2007/08/05/sorting-an-arraycollection-using-the-sortfield-and-sort-classes/
Your code is fine, even so here are a couple of examples where a numeric and an alphabetical sort is applied on button clicks.
The alphabetical sort is a good example of sorting on 2 attributes. In this case, the primary sort is done on the 'firstname', the secondary sort is done on the 'lastname'.
The numerical sort is quite flexible, if you provide a boolean value of true for the numeric parameter of the sort field, the sort will cast the attribute to a number and sort by number. If you provide a boolean value of false, the built-in string compare function is used. Each of data items is cast to a String() function before the comparison. With the default value of null, the first data item is introspected to see if it is a number or string and the sort proceeds based on that introspection.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" minWidth="955" minHeight="600">
<mx:Button label="Sort by first then last name" click="sortItemsByName()"/>
<mx:Button label="Sort by number" click="sortItemsByNumber()"/>
<mx:DataGrid dataProvider="{items}"
width="300"
height="300">
<mx:columns>
<mx:DataGridColumn dataField="number"/>
<mx:DataGridColumn dataField="firstname"/>
<mx:DataGridColumn dataField="lastname"/>
</mx:columns>
</mx:DataGrid>
<mx:ArrayCollection id="items">
<mx:Object number="3" firstname="John" lastname="Brown"/>
<mx:Object number="1" firstname="Kate" lastname="Brown"/>
<mx:Object number="4" firstname="Jeremy" lastname="Ryan"/>
<mx:Object number="5" firstname="Joe" lastname="Wilson"/>
<mx:Object number="2" firstname="Greg" lastname="Walling"/>
</mx:ArrayCollection>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.collections.SortField;
/**
* Sort the arraycollection by the firstname and then the last name
* */
private function sortItemsByName():void{
var srt:Sort = new Sort();
srt.fields = [new SortField("firstname"), new SortField("lastname")];
items.sort = srt;
items.refresh();
}
/**
* Sort the arraycollection numerically
* */
private function sortItemsByNumber():void{
var srt:Sort = new Sort();
srt.fields = [new SortField("number", true, false, true)];
items.sort = srt;
items.refresh();
}
]]>
</mx:Script>
</mx:Application>
Also here is the language reference for the sortField...
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/collections/SortField.html
...and the Adobe livedocs reference for data providers and collections...
http://livedocs.adobe.com/flex/3/html/help.html?content=about_dataproviders_2.html
...and here is a good livedocs reference for sorting and filtering...
http://livedocs.adobe.com/flex/3/html/help.html?content=about_dataproviders_4.html

Scroll to selected item in Flex 4 Spark List component

I'm setting selected element in s:List component with Actionscript, it works, but List doesn't scroll to selected item -- need to scroll with scrollbar or mouse. Is it possible to auto-scroll to selected item ? Thanks !
Try the s:List method ensureIndexIsVisible(index:int):void.
For Spark:
list.ensureIndexIsVisible(index);
This function will scroll to the top of the list in Flex 4+. It takes in account the height of the item, so it will work for lists with different items with different height.
private function scrollToIndex(list:List,index:int):void
{
if (!list.layout)
return;
var dataGroup:DataGroup = list.dataGroup;
var spDelta:Point = dataGroup.layout.getScrollPositionDeltaToElement(index);
if (spDelta)
{
dataGroup.horizontalScrollPosition += spDelta.x;
//move it to the top if the list has enough items
if(spDelta.y > 0)
{
var maxVSP:Number = dataGroup.contentHeight - dataGroup.height;
var itemBounds:Rectangle = list.layout.getElementBounds(index);
var newHeight:Number = dataGroup.verticalScrollPosition + spDelta.y
+ dataGroup.height - itemBounds.height;
dataGroup.verticalScrollPosition = Math.min(maxVSP, newHeight);
}
else
{
dataGroup.verticalScrollPosition += spDelta.y;
}
}
}
//try this
this.callLater(updateIndex);//where you want to set the selectedIndex
private function updateIndex():void
{
list.selectedIndex = newIndex;
list.ensureIndexIsVisible(newIndex);
}
In flex-3 there is a scrollToIndex method and hence you can call
list.scrollToIndex(list.selectedIndex);
I believe this should work in flex-4 too.
This worked for me. had to use the callLater.
list.selectedItem = "MyTestItem"; //or list.selectedIndex = 10;
this.callLater(updateIndex); //dispatch an update to list
private function updateIndex():void {
list.ensureIndexIsVisible(list.selectedIndex);
}
I saw this basic idea here...
http://arthurnn.com/blog/2011/01/12/coverflow-layout-for-flex-4/
public function scrollGroup( n : int ) : void
{
var scrollPoint : Point = theList.layout.getScrollPositionDeltaToElement( n );
var duration : Number = ( Math.max( scrollPoint.x, theList.layout.target.horizontalScrollPosition ) - Math.min( scrollPoint.x, theList.layout.target.horizontalScrollPosition )) * .01;
Tweener.addTween(theList.layout,{ horizontalScrollPosition: scrollPoint.x , time:duration});
}
protected function theList_caretChangeHandler(event:IndexChangeEvent):void
{
scrollGroup( event.newIndex );
event.target.invalidateDisplayList();
}
You'll probably want to access the List's scroller directly and do something like:
list.scroller.scrollRect.y = list.itemRenderer.height * index;
You can multiply the height of an element by its index and pass this value to:
yourListID.scroller.viewport.verticalScrollPosition
It is a bug - you can see the demonstration and a workaround at the https://issues.apache.org/jira/browse/FLEX-33660
This custom List component extension worked for me:
<s:List
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
valueCommit="callLater(ensureIndexIsVisible, [selectedIndex])">
</s:List>
I recently accomplished this in one of my projects by having a defined size for my items in the group..
<s:Scroller x="940" y="0" maxHeight="465" maxWidth="940" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<s:HGroup id="tutPane" columnWidth="940" variableColumnWidth="false" gap="0" x="0" y="0">
</s:HGroup>
</s:Scroller>
Following this my button controls for manipulation worked by incrementing a private "targetindex" variable, then I called a checkAnimation function, which used the Animate class, in combo with a SimpleMotionPath and a comparison between tutpane.firstIndexInView and target index. This modified the "horizontalScrollPosition" of the group.
This allowed separate controls to essentially act as a scroll bar, but I had the requirement of sliding the control to view the selected item.. I believe this technique could work for automated selection of items as well