Archive for November, 2006

Example of Flex 2 Coldfusion Connectivity

Many months ago I wrote this sample application to explore the new connectivity package for Flex 2 and Coldfusion 7.0.2. It was a lot of fun and a great learning experience. I’ve taken this long to post it because there isn’t any way to run it from here since it requires Coldfusion and a database. This blog doesn’t have access to that and my personal ISP does not have Coldfusion.

So I’ve decided to just put up some pictures, explain the application, and let you download it and run it for yourselves.

What You’ll Need

  • Flex 2 (Flex Builder 2 is needed to get the full flavor of the Connectivity package)
  • Coldfusion MX 7.0.2 (the 7.0.2 is critical)
  • MySQL (or some database that works with CF)

In addition to the source and assets, the download contains a readme.txt, instructions.rtf, and in the sql directory, the schema for creating the database.

Download Mystic Support

Mystic Support

Back when the CF Connectivity package was in development it was code-named Mystic and I’ve called my program Mystic Support. Since I’m a technical support engineer, I decided to put together my vision of what a support engineer needs to open new cases (tickets) and track their progress. We use a similar tool internally, but it isn’t written in Flex :-(

The application is pretty simple and assumes that every support engineer is assigned one or more customers that they work closely with (modeled on Adobe’s Gold and Platinum support plans). A customer can have zero or more tickets open at any given time. A ticket has some details (which product, version, description, etc.) and zero or more notes that track the progress of the case from start to finish.

At any given time the support engineer can see what tickets they have open and can easily open a new ticket for any of their customers.

The program also assumes that some administrator has populated the database with engineers and customers and made the relationship between them (assigning customers to engineers). This might be something you can do as an exercise.

When the program first starts you’ll see the inital screen to let you log in. There’s no security here – you just pick your name from the list. Should this be the first time the program is used, there may be a wait as Coldfusion and the database spin up.

Figure 1: Initializing
Figure 2: Select an Engineer

Once an engineer is selected, their customers and tickets are fetched and displayed in the panels on the left.

Figure 3: Open tickets and customers

To work on an open ticket, the engineer selects it from the list. This causes the ticket’s details to be fetched and a ticket form slides out (I used a Move effect).

Figure 4: Editing a ticket

Coldfusion Connectivity

The list of engineers that is presented is read from the database. Rather than covering all the details of the application in this blog entry, I’ll focus on this one detail, but it should give you an idea of how everything works.

I started with the database table, engineers. The table is pretty simple. Here is what it looks like using the RDS Viewer in Flex Builder 2.

Figure 5: The Flex Builder 2 RDS viewer

To make the application work I needed CFCs and ActionScript classes. Not being a wiz at CF (which proves anyone can use this stuff), I right-clicked the table in the RDS Viewer and selected the CF wizard.

Figure 6: The Coldfusion CFC Wizard

The wizard constructed the CFC for me. Notice that it actually created 2 CFCs: one represents an "engineer" as an "object". The other CFC provides an interface (gateway) with functions to get all of the engineers, get a specific engineer, update an engineer, or delete an engineer. How cool is that?

Once I had the CFC created I wanted a corresponding ActionScript object. So I right-clicked the CFC and picked the CF wizard that generates an ActionScript object. Here is the class it created:

Figure 7: The generated CFCs and ActionScript class

The engineers_gateway.cfc The engineers.cfc
The Engineers.as ActionScript class  
 

Notice the [RemoteClass] metadata – it names the CFC. This is key to the whole thing. When the program makes a RemoteObject call to the CFC (see figure 8), the CFC is set to return an Array of engineering CFC structures. The new 7.0.2 AMF gateway in Coldfusion will package them up into a binary format with clues as to what they are.

Figure 9: The Flex RemoteObject

When the data is received by the Flash Player, the clue for the CFC structure is matched with the RemoteClass metadata and rather than plain Objects, com.adobe.actors.Engineer objects are created with their properties populated from the data.

Figure 9: Flex Builder 2 debugging showing the contents of the returned result

Figure 9 shows what this looks like in the Flex Builder 2 debugger perspective. The program has a break-point in the result handler of the RemoteObject. You can see that when the event.result is expanded, it is in fact, an Array of Engineers.

Summary

You can whip up a Flex application fairly quickly using the Coldfusion Connectivity add-in for Flex Builder 2. Once you have your database tables in place, the CF wizards can create your CFCs and ActionScript classes.

In the code for Mystic Support you’ll see that I have condensed the ‘gateway’ CFCs (there would be one for each of the structure CFCs) into a single file. That’s another option you have: the ability to edit the files and use them as you please. After I worked with the code enough, I tossed out all of the functions I wasn’t going to use and repacked them into a more efficient bundle.

If you have Coldfusion and want to give your applications some FLEXappeal, give the Coldfusion Connectivity package a try.

Tree Drag and Drop Part 2

In Tree Drag and Drop Part 1 I covered how to drag items within the Tree control and from outside of the Tree control onto the control. In this article I cover how to drag items from the Tree.

Run Example

To drag items from a Tree you have to set the Tree’s dropEnabled property to false and its dragEnabled property to true:

<mx:Tree x="34" y="81" width="181" height="189"
    
dataProvider="{treeData.node}"
    
labelField="@label"
    
dropEnabled="false"
    
dragEnabled="true"

    
dragMoveEnabled="false"
/>

For this example, suppose the data in the tree represents US states, cities in those states, and restaurants within those cities. Further, suppose the target of the drop is a DataGrid which is defined like this:

<mx:DataGrid x="291" y="81" height="189"
    
dataProvider="{dataGridProvider}"
    
dragEnter="onDragEnter(event)"
    
dragOver="onDragOver(event)"
    
dragDrop="onGridDragDrop(event)"
    
dragExit="onDragExit(event)">
    <mx:columns>
        <mx:DataGridColumn headerText="Label" dataField="label"/>
        <mx:DataGridColumn headerText="Type" dataField="type"/>
    </mx:columns>
</mx:DataGrid>

As with any drag-and-drop operation, the event handlers are the key to making it work successfully.

The dragEnter event (on the DataGrid) determines whether or not the drop will be processed. In this example, only tree nodes which are restaurants will be accepted by the DataGrid. A typical tree node would look like this:

<node label="Lobster Pot" type="restaurant" />

where the @type attribute can be used to determine if the items being dropped are acceptable. The dragEnter event can be defined as:

private function onDragEnter( event:DragEvent ) : void
{
    
if( event.dragInitiator is Tree ) {

        
var ds:DragSource = event.dragSource;
        
if( !ds.hasFormat("treeItems") ) return; // no useful data
        
var items:Array = ds.dataForFormat("treeItems") as Array;
        
for(var i:Number=0; i < items.length; i++) {
            
var item:XML = XML(items[i]);
             if( item.@type != "restaurant" ) return; // not what we want
        
}   
    
}

    
// if the tree passes or the dragInitiator is not a tree, accept the drop
    
DragManager.acceptDragDrop(UIComponent(event.currentTarget));
}

If the control initiating the drag is a Tree control, then the dragSource is examined to see if holds any "treeItems" and if they are all of @type "restaurant". Should the test succeed, the DataGrid (the event.currentTarget) accepts the drop.

The dragOver event handles the feedback which the user is moving the dragProxy:

private function onDragOver( event:DragEvent ) : void
{
    
if( event.dragInitiator is Tree ) {
        
DragManager.showFeedback(DragManager.COPY);
     } else {
        
if (event.ctrlKey)
            
DragManager.showFeedback(DragManager.COPY);
        
else if (event.shiftKey)
            
DragManager.showFeedback(DragManager.LINK);
        
else {
            
DragManager.showFeedback(DragManager.MOVE);
        
}
    
}
}

When the dragInitiator is the Tree, the feedback is always set to COPY. You can of couse, set the feedback to anything you like, but for the sake of the example, the feedback is for a copy operation so the information does not have to be deleted from the Tree.

The dragDrop event on the DataGrid handles the release of the mouse over the DataGrid. The data is examined and then added to the DataGrid.

private function onGridDragDrop( event:DragEvent ) : void
{
    
var ds:DragSource = event.dragSource;
    
var dropTarget:DataGrid = DataGrid(event.currentTarget);
    
var arr:Array;

    
if( ds.hasFormat("items") ) {
        
arr = ds.dataForFormat("items") as Array;
    
} else if( ds.hasFormat("treeItems") ) {
        
arr = ds.dataForFormat("treeItems") as Array;
    
}

    
for(var i:Number=0; i < arr.length; i++) {
        
var node:XML = XML(arr[i]);
        
var item:Object = new Object();
         item.label = node.@label;
        
item.type = node.@type;
        
dataGridProvider.addItem(item);
    
}

    
onDragExit(event);
}

Since it is possible that another control has initiated the drag-drop, the dragSource is examined and the treeitems extracted. Then a loop is created to make new items to add to the DataGrid. Finally, the onDragExit method is called:

private function onDragExit( event:DragEvent ) : void
{
    
var dropTarget:ListBase=ListBase(event.currentTarget);
    
dropTarget.hideDropFeedback(event);
}

This function just makes sure the visual feedback is removed.

Dragging from the Tree is pretty easy – just examine the dragSource for "treeItems" and handle them according to what behavior your application should have. In this example, only"restaurant" type tree nodes are allowed to be dropped onto the DataGrid and, by being handled early in the DragEnter method, eliminates the need to test the value again in later steps.

 

Filtering Collections

Example

This article is a basic introduction to Collection filtering.

This example uses 2 ComboBoxes. You may have seen ComboBoxes like these on automotive web sites. One ComboBox contains a list of makes (Ford, Kia, Volkswagen, etc.) and the other ComboBox contains a list of models (Mustang, Passat, A4, etc.). The second ComboBox however, only displays models which come from the maker choosen in the first ComboBox. In other words, the models are filtered based on the selection of the make.

Figure 1: The Make ComboBox: Figure 2: The Model ComboBox:

Run the Example

Suppose you have a database that you query for all of the auto makers and all of the models. Normally you might get just the makers and fill the first ComboBox. When the user picks a make, another query is made for all of the models that match the maker. In this example, ALL of the models are returned, each keyed with their maker. Something like:

{make:"Ford", model:"Mustang"},
{make:"Ford", model:"Fusion"},

{make:"Volkswagen", model:"Beetle"},
{make:"Volkswagen", model:"Passat}

Suppose the data were assigned to ArrayCollections when retrieved from the server:

[Bindable] private var makeCollection:ArrayCollection;
[Bindable] private var modelCollection:ArrayCollection;

private var queryResultHandler( event:ResultEvent ) : void
{
     makeCollection = new ArrayCollection( event.result.makes );
     modelCollection = new ArrayCollection( event.result.models );
}

and these Collections are the dataProviders for the ComboBoxes:

<mx:ComboBox id="makeCombo" prompt="Select" dataProvider="{makeCollection}" labelField="make" />
<mx:ComboBox id="modelCombo" dataProvider="{modelCollection}" labelField="model" />

At this point the modelCombo shows ALL of the models – no matter what is selected in the makeCombo. This is not what you want. Initially, without anything selected in the makeCombo, the modelCombo should be empty.

filterFunction

To do this you need two things: filter criteria and a filter function.

The filter criteria for this example is simple: the selection of the makeCombo. The filter function is almost as simple:

private function filterByMake( item:Object ) : Boolean
{
     return( makeCombo.selectedItem.make == item.make );
}

Assuming that the item parameter in the filterByMake function is an element of the modelCollection, this function returns true whenever the item’s make property value matches the makeCombo’s selectedItem’s make property value.

The filterByMake function needs to be assigned to the modelCollection as its filterFunction:

modelCollection.filterFunction = filterByMake;

The final piece is triggering the filter by calling the Collection’s refresh() method:

modelCollection.refresh();

Putting it all together

Now that you have the parts, here is how it all fits together with some missing statements to make it work.

[Bindable] private var makeCollection:ArrayCollection;
[Bindable] private var modelCollection:ArrayCollection;

private function queryResultHandler( event:ResultEvent ) : void
{
     makeCollection = new ArrayCollection( event.result.makes );
     modelCollection = new ArrayCollection( event.result.models );

     // set the filterFunction and refresh the data so that the collection is initially empty
     // (there is no selection on the makeCombo so the test fails for every item)

     modelCollection.filterFunction = filterByMake;
     modelCollection.refresh();

}

private function filterByMake( item:Object ) : Boolean
{
     var makeItem:Object = makeCombo.selectedItem;
     if( makeItem == null ) return false;

     return( makeItem.make == item.make );
}

<mx:ComboBox id="makeCombo" prompt="Select"
         dataProvider="{makeCollection}" labelField="make"
         change="modelCollection.refresh(); modelCombo.selectedIndex=-1" />
<mx:ComboBox id="modelCombo" dataProvider="{modelCollection}" labelField="model" />

When a selection is made on the makeCombo, the change event handler calls the modelCollection’s refresh() function and clears the selection from the modelCombo. This runs all of the modelCollection items through the filterFunction, filterByMake(), screening out all those models that do not have the same maker.

The modelCombo dropdown list is automatically updated because it is data-bound to the modelCollection.

Tree Drag and Drop Part 1

This article is about dropping leaves – or rather dragging and dropping leaves.

Because this is a somewhat complex topic and blog entries shouldn’t be pages long, I’m breaking this topic into several entries. I’ll cover dragging and dropping within a Tree control, from a Tree control to something else, and from something else onto a Tree control.

If you are using a Tree control then you’ve already decided what type of data to use: XMLListCollection vs. ArrayCollection (see Tree Control DataProviders). Whichever you use the Tree uses hierarchical structures. This is ideal for XML; you’ll need ArrayCollections with ArrayCollections as children to provide the structure.

I mention this because if you have not used a Tree control before, get familiar with how to provide data to the Tree. Since I believe most applications use XML as the data for the Tree, these drag and drop examples will use XML.

Drag and Drop Within the Tree

It’s pretty easy to enable a Tree control to allow the user to drag nodes around to re-arrange the tree. Think of a file browser where you can drag a file from one folder to another.

Set up your Tree control like this:

<mx:Tree x="162" y="122" width="279" height="278"
      
dataProvider="{treeDataList}"
      
labelField="@label"
      
dragEnabled="true"
      
dragMoveEnabled="true"
      
dropEnabled="true"
       />

By setting dragMoveEnabled and dropEnabled to true, the Tree’s nodes can be moved around.

DragSource

Before discussing drag and drop, it is important to understand the DragSource class. An instance of this class is provided in the event (mx.events.DragEvent) argument of the drag event handlers. The DragSource contains, among other things, the control which initiated the drag operation, the formats of the data, and access to the data keyed by the format.

For example, if you drag an item from a catalog, the DragSource data could be the image of the item and the data about the item. So the DragSource would have two formats: images and items.

In these examples you will see "items" used as the name of the format to access the data in the DragSource. Flex controls such as Tree, DataGrid, and List, use "items" as the name of the format when they automatically create a DragSource.

Dragging onto the Tree

Use dropEnabled="true" to allow the Tree control to receive drag events and listen for the following events:

<mx:Tree x="162" y="122" width="279" height="278"
       dataProvider="{treeDataList}"
       labelField="@label"
       dropEnabled="true"
       dragEnter="onDragEnter(event)"
       dragOver="onDragOver(event)"
       dragDrop="onDragDrop(event)"

       />

The dragEnter event is dispatched when the mouse drags the drag-proxy onto the Tree. The Tree must determine if it will accept the drop or not. Just because dragEnabled has been set doesn’t mean every drop has to be honored. The dragEnter event can use information in the event – such as the control that initiated the drag (called the dragInitiator) or the data being dropped.

Here is a simple dragEnter event handler:

        private function onDragEnter( event:DragEvent ) : void
        {
              DragManager.acceptDragDrop(UIComponent(event.currentTarget));
         }

The dragOver event is dispatched while the mouse is dragging the drag-proxy within the Tree’s boundaries. You could ignore this event, but you can also use it as an opportunity to provide feedback to the user. For example, the node the mouse is over could be highlighted. You could even look at the data being dragged and at the node under the mouse and decide if the drop should be allowed. For example, if the Tree represents a car dealership and you are dragging a sports car proxy over the tree, you can change the drag cursor if the sports car proxy goes into the mini-van category telling the user the drop would be unacceptable there.

Here is a dragOver event handler which determines the node the mouse is over and

      private function onDragOver( event:DragEvent ) : void
      {

          // r is the visible index in the tree
           var dropTarget:Tree = Tree(event.currentTarget);
           var r:int = dropTarget.calculateDropIndex(event);
           tree.selectedIndex = r;

           // retrieving the newly selected node, you can examine it and decide to tell
           // the user the drop is invalid by changing the feedback.

           var node:XML = tree.selectedItem as XML;
           if( node.@type == "minivan" ) {
               DragManager.showFeedback(DragManager.NONE);
               return;
           }

           // the type of drop – copy, link, or move can be reflected in the feedback as well.
           // Here the control and shift keys determine that action.

           if (event.ctrlKey)
               DragManager.showFeedback(DragManager.COPY);
           else if (event.shiftKey)
               DragManager.showFeedback(DragManager.LINK);
           else {
              DragManager.showFeedback(DragManager.MOVE);
           }
      }

The dragDrop event is dispatched when the mouse is released. The Tree can still ignore the drop here (may be the mouse was released over the mini-van category by mistake), but it is this event that copies the data being dropped into the Tree. All, none or part of the data can be used. You can create nodes or replace nodes or even make new branches – whatever is called for by the application design.

private function onDragDrop( event:DragEvent ) : void
{
      
var ds:DragSource = event.dragSource;
      
var dropTarget:Tree = Tree(event.currentTarget);

       // retrieve the data associated with the "items" format. This will be the data that
       // the dragInitiator has copied into the DragSource.

      
var items:Array = ds.dataForFormat("items") as Array;

       // determine where in the tree the drop occurs and select that node by the index; followed by
       // retrieving the node itself.

       var r:int = tree.calculateDropIndex(event);
       tree.selectedIndex = r;
       var node:XML = tree.selectedItem as XML;
       var p:*;

      
// if the selected node has children (it is type==city),
       // then add the items at the beginning

       if( tree.dataDescriptor.hasChildren(node) ) {
             p = node;
             r = 0;
       } else {
             p = node.parent();
       }

       // taking all of the items in the DragSouce, insert them into the
       //
tree using parent p.

      
for(var i:Number=0; i < items.length; i++) {
            
var insert:XML = <node />;
             insert.@label = items[i];
            
insert.@type = "restaurant";
             tree.dataDescriptor.addChildAt(p, insert, r+i);
       }
}

The dragComplete event is registered on the drag initiator (a List, for example) which will be invoked once the drag operation has finished – either by the drop being accepted and processed, the drop being rejected outright, or by the user dropping the object over a non-drop zone.

In this example, the only thing that’s done is to clear the tree of the selection made in the dragOver and dragDrop operations.

private function onDragComplete( event:DragEvent ) : void
{
    
tree.selectedIndex = -1;
}

Summary

Dragging information onto a Tree control involves deciding if the drop should be accepted. This can be done at the outset of the operation in the dragEnter event or dynamically as the drag proxy is moved around the Tree control in the dragOver event.

Next time: dragging data from the Tree control.