DataGrid Row Move

Another question on the Flex Forum about how to move a selected row on the DataGrid up or down, based on a button click.  The answer is straight forward, but with a catch.

When you click on the DataGrid, it will give you an index for the row you have clicked.  The first row in the DataGrid is 0.  If you wanted to simply move a row up and down the DataGrid, then you simply add or subtract 1 from the selected index.


// Move Item Up

SelectedIndex--;

// Move Item Down

SelectedIndex++;

Simple enough.  Unless you sort the DataGrid, which throws that out of wack and is pointless.  So, if you don’t plan on sorting the data, then adding a couple of buttons to move an item up and down within the DataGrid and the ArrayCollection provider, then here is the code.  It also includes checks to make sure you don’t move the item off ofthe grid.

Code for the Move Up Button


protected function moveUpBtn_clickHandler(event:MouseEvent):void
{
var upSelectedIndex:int = myDataGrid.selectedIndex;

// Move Item
if(upSelectedIndex > -1) {
if(upSelectedIndex > 0) {
// Get the selected Object
var moveObject:Object = myArrayCollection.getItemAt(upSelectedIndex);
// Get actual Object location if grid is sorted
var upActualIndex:int = myArrayCollection.getItemIndex(moveObject);
// Remove selected item at actualIndex
myArrayCollection.removeItemAt(upActualIndex);
myArrayCollection.refresh();
// Move both indexes
upActualIndex--;
upSelectedIndex--;
// insert item at actualIndex
myDataGrid.dataProvider.addItemAt(moveObject, upActualIndex);
myArrayCollection.refresh();
// select grid at selectedIndex
myDataGrid.selectedIndex = upSelectedIndex;
} else {
Alert.show("Item is currently at the top of the list.", "Move Alert",  Alert.OK, this);
}
} else {
Alert.show("No Item Selected.", "Move Alert",  Alert.OK, this);
}

}

Code for the Move Down Button


protected function moveDownBtn_clickHandler(event:MouseEvent):void
{
var downSelectedIndex:int = myDataGrid.selectedIndex;

if(downSelectedIndex > -1) {
// Move Item
if(downSelectedIndex < myArrayCollection.length -1) {
// Get the selected Object
var moveObject:Object = myArrayCollection.getItemAt(downSelectedIndex);
// Get actual Object location if grid is sorted
var downActualIndex:int = myArrayCollection.getItemIndex(moveObject);
// Remove selected item at actualIndex
myArrayCollection.removeItemAt(downActualIndex);
myArrayCollection.refresh();
// Move both indexes
downActualIndex++;
downSelectedIndex++;
// insert item at actualIndex
myDataGrid.dataProvider.addItemAt(moveObject, downActualIndex);
myArrayCollection.refresh();
// select grid at selectedIndex
myDataGrid.selectedIndex = downSelectedIndex;
} else {
Alert.show("Item is currently at the bottom of the list.", "Move Alert",  Alert.OK, this);
}
} else {
Alert.show("No Item Selected.", "Move Alert",  Alert.OK, this);
}

}

I did modify the DataGrid, so that the selectioMode is “singleRow”, which simplifies the selectedIndex information. I also set the selectedIndex for the row after the move, so the user can continue to move the row up and down and not have to select it again. Here is the example and source.

UPDATE: Had to set the Down Move to look for the Length of the ArrayCollection -1, because the DataGrid row starts at 0, it is one less than the total length of the ArrayCollection.

View Source

Date Sort in a DataGrid – the easy way

While trying to give suggestions to a post on the Flex Forum, I modified my Simple Data Grid to include a column for a Date. The poster had an issue with sorting the column with dates in it. My guess is, the Date value is actually a String object. It really isn’t very hard to create a Date from a string, just parse out the String and feed the appropriate date times into the Date object. Here is how I create the random date value.

// Create a Random Date
var randomMonth:Number = Math.floor(Math.random() * (11));
var randomDay:Number = Math.floor(Math.random() * (27));
var randomHour:Number = Math.floor(Math.random() * (23));
var randomMinute:Number = Math.floor(Math.random() * (59));
var thisDate:Date = new Date();
thisDate.month = randomMonth;
thisDate.date = randomDay;
thisDate.hours = randomHour;
thisDate.minutes = randomMinute;

Since I insert the value into the item Object as a Date object, the sort function knows how to sort for Descending/Ascending. It might take a few more steps to convert a set of String data into objects and insert them into an ArrayCollection, but I think it saves time in the long run, because you work with each value as what they are and not a String that you have to convert to a Number, Int or Date.

Here is the DataGrid example. Not only does sorting by the column header work for the dates, I added a button to sort by the dates. The button switches between Ascending and Descending each time it is clicked.
View Source

Simple DataGrid with Row Background Update

On the Adobe Flex forum, a question was asked about changing the background color for a row to a different color if the user selected it and hit the DELETE key. So, I updated the Simple Data Grid to include this feature. It doesn’t delete the row, but changes the background color to grey.

The first step is to capture the keyboard action. To do this, a listener has to be created and set to listen for a KeyBoard event. I set it for the stage, but doing so requires that the function is called when applicationComplete instead of earlier. An alternative would be to set it to FlexGlobals.topLevelApplication, either way works.

// Add keyboard listener
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);

After you point the listener to your handler, you will need to make the handler look for the key or key press. In my example, I am only looking for the DELETE key, but you can look for any Keyboard key. And you can also check to see if things like the “shift”, “alt” or the “capsLock” is activated when the key is pressed. After the function determines that the key pressed is DELETE, then it updates the ArrayCollection object to set the “isDeleted” value to true. I modified my original data object to include the “isDeleted” boolean value, which I default to false.

private function keyHandler(event:KeyboardEvent):void {
	// Handles the keyboard even
	// If the keyboard event is DELETE
	if(event.keyCode == Keyboard.DELETE) {
		debugTxt.text += " \n Key was DELETE";
		debugTxt.text += " \n keyCode = " +  event.keyCode + "/" + event.charCode;
		// If an item on the grid has been selected and isn't the header row
		if(myDataGrid.selectedIndex >= 0) {
			debugTxt.text += " \n Grid Index =  " +  myDataGrid.selectedIndex;
			// Set the object to isDeleted
			var tmpObject:Object = new Object();
			tmpObject = myDataGrid.selectedItem;
			tmpObject["isDeleted"] = true;

			var itemFoundAt:int = myArrayCollection.getItemIndex(myDataGrid.selectedItem);
			if(itemFoundAt != - 1) {
				myArrayCollection.setItemAt(tmpObject, itemFoundAt);
			}

		} else {
			debugTxt.text += " \n Grid not selected ";
		}

	} else {
		debugTxt.text += " \n " + event.keyCode + "/" + event.charCode;
	}
}

After the data is updated, then a physical change needs to take place to the DataGrid to show this change. As in the earlier example, I used an itemRenderer, but I added it to the DataGrid and not to the individual columns. This renderer simply checks the data value for “isDeleted” and sets the alpha and color for the column accordingly. Instead of setting the backgroundColor of the label, the renderer creates a rectangle as the background and the color fill and alpha values are set. I set the alpha to 0 for the false option, because this will allow the alternating rows to continue with the color displayed.

override public function prepare(hasBeenRecycled:Boolean):void {
	if(data != null) {
		if(data["isDeleted"]) {
			bgColor.color = 0xcccccc;
			bgColor.alpha = 1;
		} else {
			bgColor.alpha = 0.0;
		}
		lblData.text = data[column.dataField];
	} else {
		return;
	}
}

What I haven’t done is the final step to delete the rows highlighted for deletion. This would simply be a function to go through the ArrayCollection and find the isDeleted == true objects and then remove them. I also added the debug text area to show the key listener results.

Select a row and then hit the DELETE key on your keyboard.

View Source

Simple DataGrid with ArrayCollection Update Example

I was helping out on the Flex community forum and one of the posts needed a little more of example than I could give using the forum post tools. So, I decided to create a quick example of a DataGrid using an ArrayCollection for the dataprovider. The ArrayCollection is created dynamically using a randomization selection from two static arrays. Then there is an option for updating the list showing which of the items have “Red” as the color value. I also put a couple of other items in the example, including an ItemRenderer for the DataGrid and a button holder for the Panel.

The first part of this example is to show an Object being used in an ArrayCollection. The idea behind using an Object is so that when the data is inserted, it is done as a specific type, which eliminates the need for casting String values into other types for manipulation. In this example, the myObjectItem has 2 String, 1 Number and 1 Boolean objects. The class defines these objects and also has a function for creating the object from passed in values.

package controls
{
	public class myObjectItem
	{
		public static function create(itemValue1:Number, itemValue2:String = '', itemValue3:String = '', itemValue4:Boolean = false):myObjectItem {
			var tmpMyObject:myObjectItem = new myObjectItem();
			tmpMyObject.myItemValue1 = itemValue1;
			tmpMyObject.myItemValue2 = itemValue2;
			tmpMyObject.myItemValue3 = itemValue3;
			tmpMyObject.myItemValue4 = itemValue4;
			
			return tmpMyObject;
		}
		
		public function myObjectItem()
		{
		}
		
		[Bindable]
		public var myItemValue1:Number;
		[Bindable]
		public var myItemValue2:String;
		[Bindable]
		public var myItemValue3:String;
		[Bindable]
		public var myItemValue4:Boolean;
	}
}

Creating the object is straight forward. Call the create function and add each value, in this case, by setting the Number to the loop value, the Strings to random vales from two arrays and the Boolean to false. The object is created and then added to the ArrayCollection. There are other ways of creating the ArrayCollection with data passed in from XML or data connections, but for this example, I am creating the data randomly.

// populate Arrary Collection with random values
var lengthOfMyArray:int = 26;
	for(var i:int = 0; i < lengthOfMyArray; i++) {
		myArrayCollection.addItem(myObjectItem.create(i, randomStringItem(colors), randomStringItem(fruits)));
	}

The second part of the example is to update a value in the ArrayCollection. The post on the Flex forum was doing a calculation and then setting a value to “true”. In my example, I check the String value for “Red”. If it is found, then I set the Boolean value to true as parts of a new object with old data from the original object and then using setItemAt to place the new object back in the ArrayCollection at the position of the old object.

protected function findRed_clickHandler(event:MouseEvent):void {
				// looks for the "red" in ItemValue2 and changes ItemValue4 
				for(var t:int = 0; t <  myArrayCollection.length; t++) {
					var tmpObject:Object = new Object();
					tmpObject = myArrayCollection.getItemAt(t);
					
					if(tmpObject["myItemValue2"] == "Red") {
						tmpObject.myItemValue4 = true;
					} else {
						tmpObject.myItemValue4 = false;
					}
					
					myArrayCollection.setItemAt(tmpObject, t);
				}
				// Refresh Data
				myArrayCollection.refresh();				
			}

This is a very straight forward example and I might add more items to it later.

View Source

ArrayCollection in an ArrayCollection

At work I am trying to display a bunch of information retrieved from a web services.  The data has a header table (top item) and then supporting item tables (sub items).  My current data only brings back one top item, but I have set it up to display multiple top items using a HorizontalList.  I created a custom component based on a Form, which allows me to easily display header and values.  In the future, I can probably use the form for inputs, but for now it is just displaying.  I am also using an Accordion container to display each of the header items that are wrapped in an overall item.  In each Accordion item, I dynamically create a box, to hold the form, which is dynamically created by passing it in as a new ClassFactory.  The Form acts as an item render in the HorizontalList.  Well, the problem I ran into is, that you can only pass in one DataProvider into the HorizontalList.  This was causing an issue with an item that would have an over all header information and then a DataGrid full up sub items.   After starting to fuss around with other options, it occurred to me to pass in the sub items as an ArrayCollection type.

For example, if I had a class called MyObject


package {

public class MyObject {

[Bindable]
 public var ID:Number:

[Bindable]
 public var Name:String:

[Bindable]
 public var itemList:ArrayCollection
}
}

And earlier I populated an ArrayCollection called itemListArrayCollection with data, then I wanted to add this to an ArrayCollection called myMainArrayCollection, it would look something like this.


var newObject:MyObject = new MyObject();

newObject.ID = 1;

newObject.Name = 'Test';

newObject.itemList = itemListArrayCollection;

myMainArrayCollection.addItem(newObject);

Then, in the Form I created to display the items, I would set the FormItems to show ID and Name


<mx:FormItem>

<mx:Label text="{data.ID}" />

</mx:FormItem>

<mx:FormItem>

<mx:Label text="{data.Name}" />

</mx:FormItem>

And the DataGrid within the form would get it’s DataProvider through the initial data value.  I can also point to the columns (DataFields) in the itemListArrayCollection in order to set up the DataGridColumns.


<mx:DataGrid dataprovider="{data.itemList}">

<mx:columns>

<mx:DataGridColumn DataField="listField1" />

<mx:DataGridColumn DataField="listField2" />

<mx:DataGridColumn DataField="listField2" />

</mx:columns>

</mx:DataGrid>

I’m sure I’m not the first person to pass an ArrayCollection inside of an ArrayCollection, but it was an “ah ha!” moment for me today.  I’m not sure if it is considered best practice to do this, but it works for what I need much simpler than trying to create a component to receive multiple multiples of DataProviders.  Once I get things straightened out with the current web services, I know I will have to go back and probably have an overall ArrayCollection with 3 – 5 ArrayCollections passed into it.