11.3 Enhancing a Standard Control

In the formal, object-oriented sense of the word, to extend an object means create a subclass that adds to the features of a given superclass. Here we use to term interchangeably with the more informal word enhance, by which we mean customizing an object's feature set either by adding methods to an existing class or by modifying a class to create a new one with the desired features.

It is often handy to enhance existing components that may lack the basic features for interacting with remote services. You can simply add the functionality to the prototype of the component, or overwrite the existing functionality. The basic syntax is:

FComponentNameClass.prototype.myMethod = function (args) {
  // Method code goes here
};

The advantage of defining the method on the prototype is that it will be available for all instances of the component.

The class names of most components follow the convention of using a capital F (Flash), the component name, and then the word Class. If you are in doubt about the name of the component class, you can check the component definition in the Library. For example, a CheckBox class definition is named FCheckBoxClass. Other components have similar names, as shown in Table 11-3.

Table 11-3. The class names for common Flash UI components

Component

Class definition name

Linkage symbol name

CheckBox

FCheckBoxClass

FCheckBoxSymbol

ComboBox

FComboBoxClass

FComboBoxSymbol

ListBox

FListBoxClass

FListBoxSymbol

PushButton

FPushButtonClass

FPushButtonSymbol

RadioButton

FRadioButtonClass

FRadioButtonSymbol

ScrollBar

FScrollBarClass

FScrollBarSymbol

ScrollPane

FScrollPaneClass

FScrollPaneSymbol

Tree

FTreeClass

FTreeSymbol

Ticker

FTickerClass

FTickerSymbol

Most Flash MX UI components are subclassed from the FUIComponentClass class. The FUIComponentClass class contains most of the everyday functionality that a component needs, such as initialization, setting colors and styles, setting callback methods, and setting focus. For more information on FUIComponentClass, see the third-party extension by Jesse Warden at http://www.jessewarden.com/downloads/FUIComponent.mxp.

Subsequent sections show examples of extending basic components.

11.3.1 Creating a ListBox with a Data-Driven Icon

An ActionScript programmer can visually and functionally extend the Flash MX components. There are numerous examples on the Web that demonstrate this, but I will show a way to add a custom icon to a ListBox, based on data from a remote service call, and, in doing so, create an enhanced version of the ListBox component.

You'll need two icons?a blank checkbox and a filled checkbox?to simulate a checkbox component for the data display. The files used in the procedure, blankbox.gif and check.gif, are also available at the online Code Depot, as are the completed .fla files.

As an exercise, you might want to experiment by adding a true CheckBox component rather than an icon to the ListBox. The icon is used for performance reasons, since we are simply displaying data and no user interaction is needed.

Flash 2004 and Flash Pro components are larger than their Flash MX predecessors. The updated component architecture uses a larger common base class to support accessibility, tabbing, and other popular features. The shared code affords some economies of scale in applications that use five or six components; however, using only one or two components in Flash 2004 and Flash Pro results in larger file sizes than might be justified. If using only one or two components, a custom component will likely be more efficient.

The ListBox, and other components like it, have three main parts: the data provider, the item (row), and the component itself. Components and their assets can be found in the Library after you drag an instance of the component from the Components panel to the Stage. The assets are organized in folders under the Flash UI Components folder in the Library, as shown in Figure 11-3.

Figure 11-3. The hierarchy of components in the Library panel
figs/frdg_1103.gif

We'll add a checkbox icon to the FListBoxItem to create a new element named FListCheckItem. Use the .fla created in Example 11-1 as a starting point. The final version is available as FListCheckItem.fla from the online Code Depot.

  1. Drag an instance of the ListBox component from the Components panel to the Stage. It appears as FListBoxItem in the Library panel under Core Assets - Developer Only FUIComponent Class Tree FUIComponent SubClasses FSelectableItem SubClasses, as shown in Figure 11-3.

  2. In the Library, right-click (Ctrl-click on Macintosh) FListBoxItem and choose Duplicate from the pop-up menu. If you've successfully duplicated the Library symbol and not an instance of it on the Stage, the Duplicate Symbol dialog box appears.

  3. In the Duplicate Symbol dialog box, name the new symbol FListCheckItem. Select the Export for ActionScript checkbox, and set the Linkage Identifier to FListCheckItemSymbol (click the Advanced button to expand the dialog box if you don't see the Linkage properties).

  4. Next, import the blankbox.gif and check.gif files to the Library using File Import to Library.

  5. Create a new symbol in the Library, name it Checkbox_icons, and open it for editing.

  6. Add blankbox.gif to the first frame of the Checkbox_ icons symbol, and add check.gif to a new second frame. You can position the images with their upper-left corners at the registration point of the movie clip.

  7. Open the FListCheckItem symbol for editing, and add a new layer. On that layer, drag an instance of the Checkbox_icons symbol from the Library to the symbol's canvas, and give it the instance name check_mc in the Property inspector.

  8. Next, select the first frame of the Actions layer of the FListCheckItem symbol, and open the Actions panel to show the existing code (which was duplicated from the original FListBoxItem symbol). Modify the code, as shown in Example 11-2 (changes are shown in bold):

    1. To define the FListCheckItemClass class, change FListItemClass to FListCheckItemClass throughout the code, as shown in bold. Also change FListItemSymbol to FListCheckItemSymbol.

    2. Our custom FListCheckItemClass class extends the FSelectableItemClass class, but we must add the layoutContent( ) and displayContent( ) methods to handle the attaching of the icons. The layoutContent( ) method overrides the FSelectableItemClass method of the same name. Copy the code for the layoutContent( ) method from the first frame of the Methods layer of the FSelectableItem symbol in the Library, and make the changes shown in bold.

    3. The displayContent( ) method overrides the superclass method of the same name, which it calls via super. Again, enter the text shown in bold.

  9. Finally, we'll add a few lines to the main .fla, as shown in Example 11-3, with the changes from Example 11-1 shown in bold.

Example 11-2. The FListCheckItem code
#initclip 3
/*
        FListCheckItemClass
        EXTENDS FSelectableItemClass
        This is mostly a code stub for extension purposes.
*/

function FListCheckItemClass( )
{
  this.init( );
}

FListCheckItemClass.prototype = new FSelectableItemClass( );

// EXTEND this method to change the content of an item and its layout
FListCheckItemClass.prototype.layoutContent = function (width) {
  this.attachMovie("FLabelSymbol", "fLabel_mc", 2,
       {hostComponent:this.controller});
  this.fLabel_mc._x = 2;
  this.fLabel_mc._y = 0;
  this.icon_mc._x = width - this.icon_mc._width - 10;
  this.fLabel_mc.setSize(width - 10 - this.icon_mc._width);
  this.fLabel_mc.labelField.selectable = false;
};

FListCheckItemClass.prototype.displayContent = function(itmObj, selected) {
  // Execute the superclass method first
  super.displayContent(itmObj, selected);
  // Show an icon dependent on the data.checked property
  this.check_mc.gotoAndStop(itmObj.data.checked ? 1 : 2);
}

Object.registerClass("FListCheckItemSymbol", FListCheckItemClass);

#endinitclip
Example 11-3. The main movie .fla code
#include "NetServices.as"
#include "DataGlue.as"

// Set up a responder object to handle recordsets for ListBoxes
function ListBoxResponder (lbName) {
  this.lbName = lbName;
}
ListBoxResponder.prototype.onResult = function (result_rs) {
  // Use a format function to bind the data to the individual rows
  DataGlue.bindFormatFunction(this.lbName, result_rs, formatTheData);
};
ListBoxResponder.prototype.onStatus = function (error) {
  trace(error.description);
};

// Create an object to pass to the data property of the ListBox
function formatTheData(record) {
  label = record.ProductName;
  temp = {};
  temp.ProductID = record.ProductID;
  temp.ProductName = record.ProductName;
  temp.QuantityPerUnit = record.QuantityPerUnit;
  temp.UnitPrice = record.UnitPrice;
  temp.UnitsInStock = record.UnitsInStock;
  temp.UnitsOnOrder = record.UnitsOnOrder;
  temp.checked = record.Discontinued;
  return {label:label, data:temp}
}

// Initialization code
if (connected == null) {
  connected = true;
  NetServices.setDefaultGatewayUrl("http://localhost/flashservices/gateway");
  var my_conn = NetServices.createGatewayConnection( );
  my_conn.onStatus = errorHandler;
  var myService = my_conn.getService("com.oreilly.frdg.SearchProducts");
  var Products_rs = null; // Main RecordSet object for product list
}

// Set up the FListCheckItemSymbol to be the item for the ListBox
products_lb.setItemSymbol("FListCheckItemSymbol");

// Call the service and populate the ListBox
myService.getSearchResult(new ListBoxResponder(products_lb));

products_lb.setChangeHandler("updateDisplay");

// Display properties of object contained in data property of the ListBox
function updateDisplay (lb) {
  var record = lb.getSelectedItem( ).data;
  ProductID_txt.text = record.ProductID;
  ProductName_txt.text = record.ProductName;
  QuantityPerUnit_txt.text = record.QuantityPerUnit;
  UnitPrice_txt.text = record.UnitPrice;
  UnitsInStock_txt.text = record.UnitsInStock;
  UnitsOnOrder_txt.text = record.UnitsOnOrder;
}

Save and test the movie. It should show the checkbox icons in our custom ListBox, depending on the Discontinued field in the Products table. (The Products table does not have a Discontinued field by default, but you can add it to your test database easily, as we did in Chapter 5.)

The following line sets up FListCheckItemSymbol as the list item for the ListBox instance on the Stage:

products_lb.setItemSymbol("FListCheckItemSymbol");

That's all there is to it. In this case, the original ListBox component was not touched, but a copy of it was enhanced to include an icon. That is, our custom FListCheckItemClass class extends the FSelectableItemClass class directly instead of extending FListItemClass, as could have been done. Regardless, a component like this can also be packaged and installed into the Components panel with very little effort.

11.3.2 Enhancing a ComboBox with Methods

In Chapter 4, we enhanced the ComboBox component to include a pickValue( ) method. This method allows you to pass a result from a Flash Remoting call to the ComboBox and choose that value in the UI. I'll take that a step further now by adding pickLabel( ), setDefault( ), and setDescriptor( ) methods. These new methods, and others like it, can make working with the ComboBox much easier, especially when working with Flash Remoting.

The following examples add methods to the prototype of the ComboBox component. This adds functionality to an existing component without creating a new one. You could instead create a new component that inherits from (i.e., subclasses or extends) the ComboBox, and add your new methods to the custom component. Full details on the intricacies of component building are beyond the scope of the book.

When enhancing ActionScript objects in this way, make sure that your method names do not conflict with method names from other programmers. For example, if another programmer on your team creates a pickValue( ) method for a ComboBox, the two namespaces will collide.

11.3.2.1 Using pickValue( ) and pickLabel( )

The custom pickValue( ) and pickLabel( ) methods can be used to set a value in a ComboBox. The methods are shown in Example 11-4.

Example 11-4. The pickValue( ) and pickLabel( ) methods for the ComboBox
// Set up the combo boxes to be able to pick a value
FComboBoxClass.prototype.pickValue = function (value) {
  var tempLength = this.getLength( );
  for (var i=0; i < tempLength; i++) {
    if (this.getItemAt(i).data == value) {
      this.setSelectedIndex(i);
      break;
    }
  }
};

// Set up the combo boxes to be able to pick a label
FComboBoxClass.prototype.pickLabel = function (text) {
  var tempLength = this.getLength( );
  for (var i=0; i < tempLength; i++) {
    if (this.getItemAt(i).label == value) {
      this.setSelectedIndex(i);
      break;
    }
  }
};

Typically, a ComboBox is populated from a remote database containing labels and values of a Categories table or some other related table. In a typical update of a database, you populate the user interface with a record from the database. In this situation, pickValue( ) or pickLabel( ) can be used to choose the correct value for the current record. You might use it like this, with a RecordSet object named myResults_rs:

myCombobox.pickValue(myResults_rs.getItemAt(0)["categoryid"]);
11.3.2.2 Using setDefault( )

Frequently, you may need to set a default value for a ComboBox or other UI component. The setDefault( ) method, shown in Example 11-5, handles these situations.

Example 11-5. Default value functionality for the ComboBox
// Set up a "default" property, which will be the value picked if
// the setDefault( ) method is called.
FComboBoxClass.prototype._default = null;

// setDefaultValue( ) sets up the default value when setDefault( ) is called
FComboBoxClass.prototype.setDefaultValue = function (value) {
  this._default = value;
};

// Getter method for the default value
FComboBoxClass.prototype.getDefaultValue = function ( ) {
  return this._default;
};

// Set up the combo boxes to keep a default value
FComboBoxClass.prototype.setDefault = function ( ) {
  this.pickValue(this.getDefaultValue( ));
};

The setDefault( ) method comes in handy for situations where you are inserting data into a database. The ComboBox can display the default item. If the user doesn't pick an item for the ComboBox, the default value to enter in the database can be pulled from the ComboBox. For example, when requesting the user's shipping address, you might specify an appropriate default country and shipping method, like this:

myCombobox.setDefaultValue(1);   // Initialize the default value

Now, whenever you want to display the default item in the ComboBox, simply call:

myCombobox.setDefault( );         // Displays the default item in the ComboBox

This technique is more flexible than simply choosing the value when you need to, because you can set the default value in one place in your movie and have the ability to set the ComboBox back to the default item at any time. If the default value changes at some point, your ComboBox code throughout your movie will still work. For example, if the user specifies his country as the United States, you might set the default shipping method to "UPS Ground." For other countries, you could set it to "Federal Express International".

11.3.2.3 Using setDescriptor( )

The last custom method in this section is setDescriptor( ). ComboBoxes frequently have a default label that states "?All options?" or "?Choose Shipping Method?". These types of items can be added easily to all of your ComboBoxes using the setDescriptor( ) method, shown in Example 11-6.

Example 11-6. Adding the setDescriptor( ) method to the ComboBox
// Add a descriptive row to the ComboBox
FComboBoxClass.prototype.setDescriptor = function (text, value) {
  // Create a blank record
  var temp = {};
  // Get the RecordSet object
  var rs = this.dataProvider.dataProvider;
  // Create a blank record
  rs.addItemAt(0, temp);
  // Get the recordset's field names in mTitles, and set the text and value
  rs.setField(0, rs.mTitles[1], text);
  rs.setField(0, rs.mTitles[0], value);
  this.pickValue(0);
};

The setDescriptor( ) method works with ComboBoxes that have been set up with DataGlue. In those cases, if you try to set the label directly, you'll find that it can't be done easily. You can create a new record in the data provider, however, which will propagate down to the ComboBox:

shipping_cb.setDescriptor("--Choose Shipping method-- ", 0);
country_cb.setDescriptor("--Country--", 0)

The ComboBox enhancements can be saved to the Flash MX\Configuration\Include\com\oreilly\frdg folder as DataFriendlyCombo.as. If you want to include the functionality in your Flash Remoting application, add the following #include directive to your code in the first frame:

#include "com/oreilly/frdg/DataFriendlyCombo.as"


    Part III: Advanced Flash Remoting
     
    ASPTreeView.com
     
    Evaluation has ЪЗБЦФДЖ·Й»Иexpired.
    Info...