I am trying to create a dynamic datagrid in Flex 3, I have a list of columns a list of objects which correspond to datapoints for those columns which I fetch from a url. While the grid works perfectly fine the problem is that sorting on the columns is done in lexical order.
I am aware that this can be fixed by adding a sortcomparefunction to a column, which is not easy for this case. I have tried doing
var dgc:DataGridColumn = new DataGridColumn(dtf);
f1[dtf] = function(obj1:Object, obj2:Object):int {
return Comparators.sortNumeric(obj1[dtf],obj2[dtf]);
};
dgc.sortCompareFunction = f1[dtf];`
But the problem is that the function object that I am creating here is being overwritten in every iteration (as I am adding columns) and eventually all the columns will have sorting done only on the last column added.
Suggestions please.
Lets assume you have dynamicColumns array of some objects that you want create datagrid columns from.
var columns:Array = new Array();
for (var i:int = 0; i < dynamicColumns.length; i++) {
var column:DataGridColumn = new DataGridColumn(dynamicColumns[i].label);
var dataGridColumnSortingHelper:DataGridColumnSortingHelper = new DataGridColumnSortingHelper(column);
column.sortCompareFunction = dataGridColumnSortingHelper.columnSortCompareFunction;
columns.push(column);
}
yourDataGrid.columns = columns;
Where DataGridColumnSortingHelper is like this:
public class DataGridColumnSortingHelper
{
private var column:DataGridColumn;
// Constructor
public function DataGridColumnSortingHelper(column:DataGridColumn)
{
this.column = column;
}
// Public Methods
public function columnSortCompareFunction(item1:Object, item2:Object):int
{
// define your custom function here and use column field to know what column you sorting by...
return 0;
}
}
Thanks Tom for comment.
Related
I have two questions regarding the Flex combo box.
The string representing the function name will be read from xml # run time.
var combo:ComboBox = new ComboBox();
combo.labelFunction = "functionName";
How can I achieve this?
So the first name, which is to be displayed in the combo box, can be only retrieved by accessing another DTO, called person and then its first name.
var combo:ComboBox = new ComboBox();
combo.labelField= "person.firstName";
My class looks like this,
public class Test
{
public var person:PersonDTO;
}
public class PersonDTO
{
public var firstName:String;
}
Is it possible to access any multi-level text using the combo box label field ?
You need to pass the function not the name.
Doing this
combo.labelFunction = "functionName";
Is passing a string.
The only work around I can think of is to make a switch statement with one case for each function you may have. Then call that with "case" from within your xml.
switch( xml.#labelfunction ){
case 'func1':
combo.labelFunction = this.func1;
break;
case 'func2':
combo.labelFunction = this.func2;
break;
}
Its hacky but should work.
ad 1) labelFunction
Calling functions when you know only the name as String is quite easy. The following snippets shows how you can execute a function that is a member of the same class. In case you need to call a function from another class replace this with the according variable name.
private function comboBox_labelFunction(item:Object):String
{
var functionName:String = myXml.#labelFunctionName;
return this[functionName](item);
}
ad 2) labelField
It's normally not possible to use "person.firstName" as labelField. However, you should be able use it within your labelFunction. Something like this should work...
private function comboBox_labelFunction(item:Object):String
{
var labelField:String = "person.firstName";
var attributeNames:Array = labelField.split(".");
for each (var attributeName:String in attributeNames)
{
if (item && item.hasOwnProperty(attributeName))
item = item[attributeName];
else
return null;
}
return item;
}
i have a problem using the itemRenderer functionality. When using an ArrayCollection the visible Data in the DataGrid using the itemRenderer will be rendered just fine. But if i start scrolling the entries are repeating in the cells using the renderer. The cells are not filled with date according to the id. What mistake i'm doing here.
I read a lot of the explainations like:
http://blogs.adobe.com/aharui/2007/03/thinking_about_item_renderers_1.html
here is the code for the set data function (itemRenderer is extending HBox):
override public function set data(value:Object):void {
_data = value;
if(data!=null)
{
var maxValue:Number = 0;
var maxFontHeight:int = 18;
for each(var term:ArrayCollection in _data.story)
{
if((term.getItemAt(1) as Number)>maxValue)
maxValue=term.getItemAt(1) as Number;
}
for each(var term:ArrayCollection in _data.story)
{
var FontHeight:int = Math.floor((term.getItemAt(1) as Number) * maxFontHeight / maxValue);
var l:Label = new Label();
l.text = term.getItemAt(0) as String;
l.setStyle("fontWeight","normal");
l.setStyle("fontFamily","Verdana");
l.setStyle("paddingRight",0);
l.setStyle("paddingLeft",0);
l.setStyle("fontSize", FontHeight);
l.setStyle("color", 0x000000);
this.addChild(l);
}
}
}
You aren't clearing down what's already there in the renderer before adding new stuff.
Try moving the construction of the label to createChildren, setting the text in set data (rather than constructing another label), and remembering to clear the text if the data is null.
There's a few more optimizations you could make such as checking the new data isn't the same as the current data before doing the work, for example.
Im trying to extend the flex ArrayCollection to be able to search for an object containing specific data and give it back.
Here is my function:
public function getItemContaining(value: String): Object {
//Loop through the collection
for each(var i: Object in this) {
//Loop through fields
for(var j: String in i) {
//If field value is equal to input value
if(i[j] == value) {
return i;
}
}
}
//If not found
return null;
}
Problem is j is always null so the second loop never works. So I read flex loop descriptions and actually it should work just fine. What can possibly be the problem?
Try it like this:
for (var name:String in myObject){
trace(name + ":" + myObject[name];
}
Okay that was actually the same you were doing. The error must be in this line:
for each(var i: Object in this) {
Try using this:
for each(var i: Object in this.source) {
My first instinct would be to have a look at data type. You're setting up a loop declaring j:String and the symptom is that j is always null. This suggests to me that Flex is failing to interpret the elements of i as strings. If Flex only recognizes the elements of i as Objects (because all Strings are Objects, and Objects are the lowest common denominator), it would return null for j:String.
Try this for your inner loop:
for(var j: Object in i) {
//If field value is equal to input value
if(i[j] is String && (i[j] as String) == value) {
return i;
}
}
if you are using ArrayCollection as your datasource, you should look at using the IViewCursor interface. You can supply a custom compare function, or supply the fields top compare to. This interface is well documented with examples in adobe/livedocs
var _cursor:IViewCursor;
var _idSortField:SortField;
var _idSort:Sort = new Sort();
_idSortField = new SortField();
_idSortField.compareFunction = this.myCompareFunction;
_idSort.fields = [_idSortField];
myArrayCollection.sort = _idSort;
myArrayCollection.refresh();
_cursor = myArrayCollection.createCursor();
if (_cursor.findAny(search))
return _cursor;
if you are search for a value in a specific property, then its even easier. Here's the link to adobe livedocs on this topic
I am searching for a method to intersect my array collections.
I have one collection: allItems and another subSet. I want to create another ArrayCollection where all items which do not exist in subSet will be stored. Is there a way to do this?
working answer provided by eemeli ... here is an alternative implementation optimized for speed (array access instead of calls) and scalability (approach provides O(m+n) instead of O(m*n))...
public static function difference(a:ArrayCollection, b:ArrayCollection):ArrayCollection {
var entry:*, map:Dictionary = new Dictionary(), intersection:Array = [];
for each (entry in a.source) map[entry] = entry;
for each (entry in b.source) delete map[entry];
for each (entry in map) intersection.push(entry);
return new ArrayCollection(intersection);
}
For getting a collection of items not in another you need a set difference algorithm (allItems minus subSet).
public function minus(a:ArrayCollection, b:ArrayCollection):ArrayCollection {
var result:ArrayCollection = new ArrayCollection()
for each (i in a) {
if (!b.contains(i)) {
result.addItem(i)
}
}
return result
}
var allLength:Number = allItems.length;
var intersection:ArrayCollection = new ArrayCollection();
for(var i:Number = 0; i < allLength; i++)
if(subSet.getItemIndex(allItems.getItemAt(i)) == -1)
intersection.addItem(allItems.getItemAt(i));
Note that this will work only if the subset contains the same objects as the super set. If the subset contains different objects with the same property values as of the super set object, you are gonna have to compare their properties separately.
OK, I am sorting an XMLListCollection in alphabetical order. I have one issue though. If the value is "ALL" I want it to be first in the list. In most cases this happens already but values that are numbers are being sorted before "ALL". I want "ALL" to always be the first selection in my dataProvider and then the rest alphabetical.
So I am trying to write my own sort function. Is there a way I can check if one of the values is all, and if not tell it to do the regular compare on the values?
Here is what I have:
function myCompare(a:Object, b:Object, fields:Array = null):int
{
if(String(a).toLowerCase() == 'all')
{
return -1;
}
else
if(String(b).toLowerCase() == 'all')
{
return 1;
}
// NEED to return default comparison results here?
}
//------------------------------
var sort:Sort = new Sort();
sort.compareFunction = myCompare;
Is there a solution for what I am trying to do?
The solution from John Isaacks is awesome, but he forgot about "fields" variable and his example doesn't work for more complicated objects (other than Strings)
Example:
// collection with custom objects. We want to sort them on "value" property
// var item:CustomObject = new CustomObject();
// item.name = 'Test';
// item.value = 'Simple Value';
var collection:ArrayCollection = new ArrayCollection();
var s:Sort = new Sort();
s.fields = [new SortField("value")];
s.compareFunction = myCompare;
collection.sort = s;
collection.refresh();
private function myCompare(a:Object, b:Object, fields:Array = null):int
{
if(String((a as CustomObject).value).toLowerCase() == 'all')
{
return -1;
}
else if(String((b as CustomObject).value).toLowerCase() == 'all')
{
return 1;
}
// NEED to return default comparison results here?
var s:Sort = new Sort();
s.fields = fields;
var f:Function = s.compareFunction;
return f.call(null,a,b,fields);
}
Well I tried something out, and I am really surprised it actually worked, but here is what I did.
The Sort class has a private function called internalCompare. Since it is private you cannot call it. BUT there is a getter function called compareFunction, and if no compare function is defined it returns a reference to the internalCompare function. So what I did was get this reference and then call it.
private function myCompare(a:Object, b:Object, fields:Array = null):int
{
if(String(a).toLowerCase() == 'all')
{
return -1;
}
else if(String(b).toLowerCase() == 'all')
{
return 1;
}
// NEED to return default comparison results here?
var s:Sort = new Sort();
var f:Function = s.compareFunction;
return f.call(null,a,b,fields);
}
Thanks guys, this helped a lot. In our case, we needed all empty rows (in a DataGrid) on the bottom. All non-empty rows should be sorted normally. Our row data is all dynamic Objects (converted from JSON) -- the call to ValidationHelper.hasData() simply checks if the row is empty. For some reason the fields sometimes contain the dataField String value instead of SortFields, hence the check before setting the 'fields' property:
private function compareEmptyAlwaysLast(a:Object, b:Object, fields:Array = null):int {
var result:int;
if (!ValidationHelper.hasData(a)) {
result = 1;
} else if (!ValidationHelper.hasData(b)) {
result = -1;
} else {
if (fields && fields.length > 0 && fields[0] is SortField) {
STATIC_SORT.fields = fields;
}
var f:Function = STATIC_SORT.compareFunction;
result = f.call(null,a,b,fields);
}
return result;
}
I didn't find these approaches to work for my situation, which was to alphabetize a list of Strings and then append a 'Create new...' item at the end of the list.
The way I handled things is a little inelegant, but reliable.
I sorted my ArrayCollection of Strings, called orgNameList, with an alpha sort, like so:
var alphaSort:Sort = new Sort();
alphaSort.fields = [new SortField(null, true)];
orgNameList.sort = alphaSort;
orgNameList.refresh();
Then I copied the elements of the sorted list into a new ArrayCollection, called customerDataList. The result being that the new ArrayCollection of elements are in alphabetical order, but are not under the influence of a Sort object. So, adding a new element will add it to the end of the ArrayCollection. Likewise, adding an item to a particular index in the ArrayCollection will also work as expected.
for each(var org:String in orgNameList)
{
customerDataList.addItem(org);
}
Then I just tacked on the 'Create new...' item, like this:
if(userIsAllowedToCreateNewCustomer)
{
customerDataList.addItem(CREATE_NEW);
customerDataList.refresh();
}