Pages

Search Site

Topics

Favorite Links

Archives

Entries Tagged as 'ActionScript'

Filtering a Flex Tree using an ArrayCollection

January 06 2009 by Kalen Gibbons

Flex Trees can be difficult to filter because of their hierarchical nature. One possible way to do it is to use an ArraryCollection as your dataProvider, with each node having a "children" property that is also an ArrayCollection.

The tricky part in filtering the Tree is making sure the children are filtered in addition to the root nodes.  So, in the example below, I have created a function that will loop through each node and its children recursively.

It is important to note that the filtering takes place from the bottom up, meaning that a node's children are always filtered before the node itself.  This is important, because normally a filter removes everything that does not match a certain criteria.  In our case, to remain in the collection, a node must match a certain criteria OR have children that do.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute">

<mx:Script>
<![CDATA[
import vo.Person;
import mx.collections.ArrayCollection;

[Bindable]
private var people:ArrayCollection = new ArrayCollection([
new Person("Grandma Susan", new ArrayCollection([
new Person("John", new ArrayCollection([
new Person("Timmy"),
new Person("Sammy"),
new Person("Alan")
])),
new Person("Tiffany", new ArrayCollection([
new Person("Billy"),
new Person("Adam"),
new Person("Graham"),
new Person("Vennesa")
])),
new Person("Michael", new ArrayCollection([
new Person("Jannette"),
new Person("Alan", new ArrayCollection([
new Person("Alice"),
new Person("Jane")
]))
])),
new Person("Peter"),
new Person("Cindy", new ArrayCollection([
new Person("Paul"),
new Person("David"),
new Person("Joseph"),
new Person("Cameron"),
new Person("Debra"),
new Person("Polly")
]))
]))
]);

private function refreshData():void{
//reset the root node to its original unfiltered data
people[0].children = new ArrayCollection(people[0].children.source);
//start the recursion at the root node
refreshRecursiveChildren(people.source[0]);
//update the Tree after the data has been filtered
personsTree.invalidateList();
}

private function refreshRecursiveChildren(person:Person):void{
if(person.children){
//loop through each child and filter its children
for each(var _person:Person in person.children.source){
refreshRecursiveChildren(_person);
}
//reset each "children" ArrayCollection to its original unfiltered data
person.children = new ArrayCollection(person.children.source);
//set the filterfunction for the newly updated node
person.children.filterFunction = filterData;
//run the fitlerFunction
person.children.refresh();
}
}

public function filterData(item:Object):Boolean{
//get the string to filter the nodes by
var searchString:String = iNameFilter.text;
//if string is found in node return true.
//since the recursive filtering takes place from bottom up, if
//a collection still has children after filtering, also return true

if(searchString.length == 0
|| item.name.toLowerCase().indexOf(searchString.toLowerCase()) >= 0)
return true;
else if(item.children != null && item.children.length > 0)
return true;

return false;
}
]]>
</mx:Script>

<mx:VBox width="100%" height="100%"
paddingTop="10"
paddingBottom="10"
paddingLeft="5"
paddingRight="5">
<mx:Tree id="personsTree"
dataProvider="{people}"
labelField="name"
width="100%"
height="100%" />
<mx:HBox>
<mx:Label text="Filter the Tree:" />
<mx:TextInput id="iNameFilter" change="refreshData()" />
</mx:HBox>
</mx:VBox>
</mx:Application>


You may right-click and select "View Source"
to view the full source code for the following example.

0 comments Posted in Flex | ActionScript | How-To

Displaying DataTips when using an itemRenderer

December 13 2008 by Kalen Gibbons

One of the bad things about using itemRenderers in a DataGridColumn is that you lose the dataTip functionality that it normally provides.  Well, here is a way to fake that functionality.

First, add the dataTipField or dataTipFunction to the DataGridColumn like you normally would.

<mx:DataGridColumn  headerText="DataTip"
dataField="name1"
showDataTips="true"
dataTipField="description1" />

Then, in your itemRenderer add the following code to be able to tap into that information and display a tooltip instead.

private function getToolTip():String{
var dg:DataGrid = listData.owner as DataGrid;
var func:Function = dg.columns[listData.columnIndex].dataTipFunction;

if(func != null){
return func.call(this, this.data);
}else if(dg.columns[listData.columnIndex].dataTipField.length){
return data[dg.columns[listData.columnIndex].dataTipField];
}else{
return "";
}
}

override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void{
super.updateDisplayList(unscaledWidth, unscaledHeight);
this.toolTip = getToolTip();
}

This works with both dataTipFields and dataTipFunctions and lets you treat the dataTips in your columns the same way, regardless of whether you're using an itemRenderer or not.  The only minor difference is the positioning of the label, but that can be easily modified with styles. You can download the full source code here, for a functional example of how this works.

 

0 comments Posted in Flex | ActionScript | How-To

Update: Solution to disappearing data in your itemRenderer

December 08 2008 by Kalen Gibbons

In a previous post I explained how a rendering bug in Flex was causing items to disappear in an itemRenderer. In the example below, you can see that when you scroll, the items in the list disappear.  This is caused by the varying heights of the elements.  If you explicitly set the height for each element or set variableRowHeights to false, then the problem goes away.


What was causing the problem was apparent but the solution was not.  Thankfully, Olivier Lalonde was nice enough to shed some light on an easy fix.  The itemRenderer needs to be validated as the user scrolls.  So, as Olivier suggests, you simply need to listen for the dataChange event and execute the validateNow() function.

You can see in the example below that the list now has varying row heights... and the data no longer disappears. NICE!  The key point to focus on here is the following line: <mx:VBox dataChange="validateNow()" verticalGap="0">

<mx:List dataProvider="{myData.item}"
variableRowHeight="true"
alternatingItemColors="['#FFFFFF','#E6EEF3']"
width="100%"
height="100%">

<mx:itemRenderer>
<mx:Component>
<mx:VBox verticalGap="0" dataChange="validateNow()">
<mx:HBox>
<mx:Label text="{data.Label}" fontWeight="bold" />
<mx:Label text="{data.date}" color="#999999" />
</mx:HBox>
<mx:Text text="{data.description}" width="100%" />
</mx:VBox>
</mx:Component>
</mx:itemRenderer>

</mx:List>


5 comments Posted in Flex | ActionScript | Bugs | How-To

How to drag, drop, and parse a CSV file in AIR

November 26 2008 by Kalen Gibbons

Drag and Drop a CSV file in Adobe AIRI've been playing around with Adobe AIR lately and I've really been enjoying it.  So I thought I'd put out this quick tutorial on how to drag and drop a file into your AIR application; in this case we will be using a CSV file, and then we will parse that file so that its data can be used in a datagrid.  You can download the full source code here.

Dragging a file into AIR:

First, we need to set up handlers to listen for the drag events.  We are going to set up two listeners, one for when an item is dragged over the app and one for when that item is dropped.

this.addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, dragEnterHandler);
this.addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, dragDropHandler);

Then we'll set up the two handler function.  The dragEnterHandler function determines if the item is an acceptable file type.  You could use the different constants of the ClipboardFormats class to only accept specific file formats, but for now we will accept any valid file.

private function dragEnterHandler(evt:NativeDragEvent):void{
if(evt.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)){
NativeDragManager.acceptDragDrop(this);
}
}

Next, when the user drops the file, we will copy it from the clipboard, read its contents, and parse its values into a more usable data structure.

private function dragDropHandler(evt:NativeDragEvent):void{
NativeDragManager.dropAction = NativeDragActions.COPY;
//get an array of the files dropped in
var dropFiles:Array = evt.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
//get the content of the file
var fileContent:String = getFileContent(dropFiles[0]);
//parse it to get an array of Orders
orders = parseCSV(fileContent);
}

 

Reading the content of the dropped file:

For this we will use the FileStream class.  The getFileContent function takes a generic file as its only parameter; it opens a FileStream, reads the contents of the file and returns that value as a string.

private function getFileContent(_file:File):String{
//open a fileStream to read the content of the file
var fileStream:FileStream = new FileStream();
fileStream.open(_file, FileMode.READ);
var fileContent:String = fileStream.readUTFBytes(fileStream.bytesAvailable);
fileStream.close();
return fileContent;
}

 

Parsing the CSV file:

Finally, we are going to take the content of the CSV file, which is now in the form of a string, and parse out its data to populate an Array.  We start by using the split() function to create an Array of line items. We use character code 10, which represents carriage returns, and character code 13, which represents new lines. to separate each line item.  Then we loop through the newly created array of line items and pull out each comma separated value.  At this point we have the information we need to populate the Order object and add that to our Array.

private function parseCSV(_content:String):Array{
//create temporary array to store the Orders
var csvArray:Array = new Array();
//break the csv into individual lines
var csvLines:Array = _content.split(String.fromCharCode(13,10));
//remove title row
csvLines.splice(0,1);
//loop over each line
for each(var s:String in csvLines){
var lineItems:Array = s.split(",");
var transaction:Order = new Order( lineItems[0],
new Date(lineItems[1]),
lineItems[2],
lineItems[3],
lineItems[4]);
csvArray.push(transaction);
}
return csvArray;
}

The data pulled from the CSV file is now in a more manageable form and can be used as a dataProvider for components or to populate AIR's built in SQLite database.  Of course, in this tutorial, for simplicity's sake, we assume that the file dropped into the application is a CSV file.  In your application you should have some trapping to determine the file type and process it accordingly.

0 comments Posted in Flex | ActionScript | AIR | How-To

Disappearing data in your itemRenderer?

October 14 2008 by Kalen Gibbons

Note: An update to this post has been posted here. It provides a better solution than the one offered below, so I recommend you check it out.

Although I love Flex, it can still be a bit buggy at times. I was working with an itemRenderer the other day and came across some very odd behavior. I had a list using an itemRenderer and when the data originally loaded everything was fine, but whenever I scrolled some of the data started disappearing. You can see this demonstrated in the sample below.


<mx:List dataProvider="{myData.item}"
      variableRowHeight="true"
      alternatingItemColors="['#FFFFFF','#E6EEF3']"
      width="100%"
      height="100%">

      
   <mx:itemRenderer>
      <mx:Component>
         <mx:VBox verticalGap="0">
            <mx:HBox>
               <mx:Label text="{data.Label}" fontWeight="bold" />
               <mx:Label text="{data.date}" color="#999999" />
            </mx:HBox>
            <mx:Text text="{data.description}" width="100%" />
         </mx:VBox>
      </mx:Component>
   </mx:itemRenderer>
   
</mx:List>

Apparently, Flex likes to have all the items in a list the same height; so when an item such as the Text field in the example causes variation, Flex simply doesn't display it. I found that the easiest way to remedy the issue was to set explicit heights for the items in the renderer. Here is an example of how it works with the height explicitly set:


<mx:List dataProvider="{myData.item}"
      variableRowHeight="true"
      alternatingItemColors="['#FFFFFF','#E6EEF3']"
      width="100%"
      height="100%">

      
   <mx:itemRenderer>
      <mx:Component>
         <mx:VBox verticalGap="0">
            <mx:HBox>
               <mx:Label text="{data.Label}" fontWeight="bold" />
               <mx:Label text="{data.date}" color="#999999" />
            </mx:HBox>
            <mx:Text text="{data.description}" width="100%" height="30" />
         </mx:VBox>
      </mx:Component>
   </mx:itemRenderer>
   
</mx:List>

Now, this isn't a perfect solution. What we are doing here is effectively the same thing as setting variableRowHeight to false; thus making all the items in the List the same height. So if anybody has a better solution for this issue please let me know.

2 comments Posted in Flex | ActionScript | Bugs