DekGenius.com
[ Team LiB ] Previous Section Next Section

27.2 Creating the Framework

The My Page framework is a single Flash movie, myPage.fla, which performs the following tasks:

  • Loads available module types from an XML file, which it uses to populate a menu

  • Creates a new visual container (a custom component instance) when a user chooses a module and loads the module into it

  • Remembers each module's position and state between sessions using a local shared object

27.2.1 Sharing Common Functions

The first step in creating the framework is to create the SupportFunctions.as file, which is shared among all the Flash movies, including myPage.fla. SupportFunctions.as should contain the following code:

MovieClip.prototype.getNewDepth = function (  ) {
  // If no currentDepth is defined, initialize it to 1.
  if (this.currentDepth == undefined) {
    this.currentDepth = 1;
  }
  // Return the new depth and increment it by 1 for next time.
  return this.currentDepth++;
};

This code adds a getNewDepth( ) method to the MovieClip class such that all MovieClip objects inherit it. The getNewDepth( ) method generates a unique depth each time it is called. This is important because this chapter creates all application elements at runtime via ActionScript. Each dynamically created element must reside on its own depth to prevent elements from overwriting each other.

27.2.2 Configuring the Available Modules

Later in this chapter we create three modules that are made available to the framework. However, the framework should be able to accommodate other modules as well. We achieve this flexibility using an XML document that contains information about the available modules the framework should load. For now, create services.xml as shown in the following example, which lists the three modules that we'll create shortly.

<choices>
  <choice title="Address Book" source="addressBook.swf" />
  <choice title="Notes" source="notes.swf" />
  <choice title="Search the Web" source="search.swf" />
</choices>

27.2.3 Adding a Component

The next step in the framework development is to create a custom component—the instances of which act as the containers into which each module is loaded. The main module container component actually uses another custom component, so there are two steps in this process. First, you should define the child component, UnitComponentBar, which is used as the title bar for the main module container component. Then, you should define UnitComponent.

27.2.3.1 Defining UnitComponentBar

Follow these steps to define UnitComponentBar:

  1. Within myPage.fla, insert a new movie clip library symbol named UnitComponentBar.

  2. Set the symbol to Export for ActionScript and Export in First Frame using the Linkage option under the Library panel's Options pop-up menu.

  3. Give it the linkage identifier UnitComponentBarSymbol.

In the following example, you can see the code that you should place on the first frame of the default layer of the UnitComponentBar symbol. We'll examine portions in more detail following the code listing.

The getNewDepth( ) method is used within the component class even though you cannot yet see how it has been made available. We will #include it in the framework document later. Because getNewDepth( ) is defined as a method for all MovieClip objects, it is accessible from any timeline once it has been loaded in a movie.

// #initclip with an option of 0 means that this code loads 
// before anything else, which is important, since it must be 
// defined before it can be used in UnitComponent.
#initclip 0

// The constructor is called when a new UnitComponentBar instance is created.
function UnitComponentBar (  ) {

  // Create the movie clip into which the bar is drawn.
  this.createEmptyMovieClip("barHolder", this.getNewDepth(  ));
  // Create the title text field.
  this.createAutoTextField("title", this.getNewDepth(  ));
  this.title.selectable = false;

  // Create a movie clip for the Close button that lets users close the module.
  this.closeSquare = this._parent.createEmptyMovieClip("closer", 
                      this._parent.getNewDepth(  ));
}

// UnitComponentBar extends the MovieClip class.
UnitComponentBar.prototype = new MovieClip(  );

// The draw(  ) method draws the title bar with the given width, height, and fill 
// color. It also aligns the title bar to the parent UnitComponent instance 
// according to the margin parameter, m, and sets the color of the text.
UnitComponentBar.prototype.draw = function (w, h, m, fillColor, titleColor) {

  // Draw the title bar.
  with (this.barHolder) {
    lineStyle(1, 0, 100);
    beginFill(fillColor, 100);
    drawRectangle(w + m, h, 0, 0, (w + m)/2, h/2);
    endFill(  );
  }

  // Set the text color
  this.title.textColor = titleColor;

  // Draw the Close button.
  with (this.closeSquare) {
    lineStyle(1, titleColor, 100);
    beginFill(0, 0);
    drawRectangle(10, 10, 0, 0, 5, 5);
    endFill(  );
    _x = w - m - 5;
    _y = 5;
  }

  // Invoke the close(  ) method to remove the module if the Close button is clicked.
  this.closeSquare.onRelease = this.close;
};

// This function sets the text of the title bar.
UnitComponentBar.prototype.setTitle = function (titleText) {
  this.title.text = titleText;
};

// When a title bar is clicked, make its parent UnitComponent instance draggable.
UnitComponentBar.prototype.onPress = function (  ) {
  this._parent.startDrag(  );

  // Check for overlapping siblings.
  this._parent.onEnterFrame = this._parent.checkOverlaps;
};

// Stop the dragging when released.
UnitComponentBar.prototype.onRelease = function (  ) {

  // Stop dragging the item.
  this._parent.stopDrag(  );

  // Notify all listeners of the unit's new position
  var l = this._parent.listeners;
  for (var i = 0; i < l.length; i++) {
    l[i].onMoved(this._parent);
  }

  // Disable hit testing now that the drag event is over.
  delete this._parent.onEnterFrame;
};

// Close the parent UnitComponent instance.
UnitComponentBar.prototype.close = function (  ) {
  this._parent.onClose(  );
  this._parent.removeMovieClip(  );
};

// Register the class.
Object.registerClass("UnitComponentBarSymbol", UnitComponentBar);

#endinitclip

This constitutes a rather long listing with some important code that we should look at more closely.

To begin with, it is important that the #initclip directive include the load order parameter. In this case, use the value 0 because the code enclosed between #initclip and #endinitclip should load before any other code in the movie. This is because UnitComponentBar instances are created within UnitComponent objects (as you will see in the next section), and, therefore, the definition must be loaded first to be accessible.

#initclip 0

The UnitComponentBar constructor creates the close box movie clip with the createEmptyMovieClip( ) method. Notice, however, that the method is invoked from this._parent instead of just this. The reason for this is that the close box instance must be on top of the UnitComponentBar instance to function properly. For convenience, a reference to the close box movie clip created in the parent is set to a property named closeSquare:

this.closeSquare = this._parent.createEmptyMovieClip("closer", 
                    this._parent.getNewDepth(  ));

The onPress( ) and onRelease( ) event handler methods are defined for all UnitCompontBar instances by defining them on the prototype property of the class. Both of these definitions, as well as the close( ) method, invoke methods defined for the parent UnitCompont instance by using this._parent to reference it:

UnitComponentBar.prototype.onPress = function (  ) {
  this._parent.startDrag(  );
  this._parent.onEnterFrame = this._parent.checkOverlaps;
};

UnitComponentBar.prototype.onRelease = function (  ) {
  this._parent.stopDrag(  );
  var l = this._parent.listeners;
  for (var i = 0; i < l.length; i++) {
    l[i].onMoved(this._parent);
  }
  this._parent.onEnterFrame = null;
};

UnitComponentBar.prototype.close = function (  ) {
  this._parent.onClose(  );
  this._parent.removeMovieClip(  );
};

Finally, the class must be registered to be associated automatically with new instances of the movie clip library symbol in which it is defined. The linkage identifier UnitComponentBar is passed as the first parameter, and a reference to the class is passed as the second parameter to the Object.registerClass( ) method to accomplish this:

Object.registerClass("UnitComponentBarSymbol", UnitComponentBar);
27.2.3.2 Defining UnitComponent

Now that we've defined the UnitComponentBar, let's define the UnitComponent that contains it:

  1. Within myPage.fla, drag an instance of the ProgressBar component from the Components panel to the Stage in order to add it to the Library. After doing so, you can delete the instance from the Stage.

  2. Insert a new movie clip library symbol named UnitComponent.

  3. Set the library symbol to Export for ActionScript and Export in First Frame using the Linkage option under the Library panel's pop-up Options menu.

  4. Give it the linkage identifier UnitComponentSymbol.

Here is the code that goes on the first frame of the default layer of the new symbol. Look over the code briefly. We'll examine aspects of it in more detail shortly.

// Load this code second by using the load order parameter value of 1.
#initclip 1

// The UnitComponentGroup class keeps track of each UnitComponent instance that is
// created within the same timeline.
function UnitComponentGroup (  ) {}

// groupMembers is an associative array that holds references to each sibling
// instance (i.e., other members of the group).
UnitComponentGroup.prototype.groupMembers = new Object(  );

// The onNew(  ) method is called when a new UnitComponent instance is created.
UnitComponentGroup.prototype.onNew = function (newUnit) {

  // Add the new UnitComponent instance to the groupMembers array.
  this.groupMembers[newUnit._name] = newUnit;
  var sibs;

  // Loop through each sibling (each UnitComponent).
  for (var unit in this.groupMembers) {
    sibs = new Array(  );

    // Loop through all the groupMember elements to create an array of siblings (not
    // including itself) for each UnitComponent instance.
    for (var sib in this.groupMembers) {
      if (this.groupMembers[unit] != this.groupMembers[sib]) {
        sibs.push(this.groupMembers[sib]);
      }
    }

    // Call the UnitComponent instance's setSiblings(  ) method with a reference to the
    // newly created sibs array.
    this.groupMembers[unit].setSiblings(sibs);
  }
};

// The constructor creates the movie clip and text field objects for a module.
function UnitComponent (  ) {

  // Create the outline box and title bar movie clips.
  this.createEmptyMovieClip("outline", this.getNewDepth(  ));
  this.attachMovie("UnitComponentBarSymbol", "bar", this.getNewDepth(  ));

  // Add the placeholder for the loaded module.
  this.createEmptyMovieClip("loadPlaceHolder", this.getNewDepth(  ));

  // Obtain a unique depth for the new module.
  this.index = this.getDepth(  );

  // Create a unit grouping to contain this and any sibling modules (see the
  // discussion following this example).
  if (this._parent.unitGroup == undefined) {
    this._parent.unitGroup = new UnitComponentGroup(  );
  }
  this.group = this._parent.unitGroup;
  this.group.onNew(this);
}

// UnitComponent extends the MovieClip class.
UnitComponent.prototype = new MovieClip(  );

// Set the default attributes of all units.
UnitComponent.prototype.barColor = 0x000000;
UnitComponent.prototype.titleColor = 0xFFFFFF;
UnitComponent.prototype.listeners = new Array(  );

// Define the following properties and set them to null. They will be assigned a
// value by other methods.
UnitComponent.prototype.index = null;
UnitComponent.prototype.siblings = null;
UnitComponent.prototype.group = null;

// Load an external .swf file into the component.
UnitComponent.prototype.load = function (url) {

  // Load the module into loadPlaceHolder.
  this.loadPlaceHolder.loadMovie(url);

  // Create a text field to label the progress bar.
  this.createAutoTextField("loading", this.getNewDepth(  ));
  this.loading.text = "loading...";

  // Create a progress bar using the ProgressBar component.
  this.attachMovie("FProgressBarSymbol", "progress", this.getNewDepth(  ));
  this.progress._y = this.loading._height + 5;

  // Start an onEnterFrame(  ) loop to update the progress bar.
  this.onEnterFrame = this.onLoading;
};

// The onLoading(  ) method updates the progress while loading and then performs
// actions when the module has loaded.
UnitComponent.prototype.onLoading = function (  ) {

  // Update the loading module's progress bar with the loaded and total bytes.
  var lBytes = this.loadPlaceHolder.getBytesLoaded(  );
  var tBytes = this.loadPlaceHolder.getBytesTotal(  );
  this.progress.setProgress(lBytes, tBytes);

  // If the module has loaded completely, then do the following:
  if (this.progress.getPercentComplete(  ) == 100) {

    // Remove the progress bar and its label text field.
    this.progress.removeMovieClip(  );
    this.loading.removeTextField(  );

    // Call the drawOutline(  ) and the UnitComponentBar.draw(  ) methods with the
    // loaded module's dimensions. This creates a placeholder rectangle for the newly
    // loaded module.
    var margin = 5;
    var w = this.loadPlaceHolder._width + (margin * 2);
    var h = this.loadPlaceHolder._height + (margin * 2);
    this.drawOutline(w, h, margin);
    this.bar.draw(w, 20, margin, this.barColor, this.titleColor);

    // Create a table to position the title bar and the outline. Use a spacing of -1
    // so that there are no spaces between the two.
    var t = new Table(0, 0, 0, new TableRow(0, 
                      new TableColumn(-1, this.bar, this.outline)));
    this.loadPlaceHolder._x = this.outline._x + margin;
    this.loadPlaceHolder._y = this.outline._y + margin;

    // Stop the onEnterFrame(  ) loop by deleting it. 
    delete this.onEnterFrame;
  }
};

// Set the siblings property to an array containing references to all other
// UnitComponent instances.
UnitComponent.prototype.setSiblings = function (s) {
  this.siblings = s;
};

// Draw the outline and background for the component.
UnitComponent.prototype.drawOutline = function (w, h, margin) {
  with (this.outline) {
    lineStyle(1, 0, 100);
    beginFill(0xFFFFFF, 100);
    drawRectangle(w + margin, h + margin, 0, 0, (w + margin)/2, (h + margin)/2);
    endFill(  );
  }
};

// Set the title of the component, as displayed in the title bar.
UnitComponent.prototype.setTitle = function (title) {
  // Call the UnitComponentBar instance's setTitle(  ) method.
  this.bar.setTitle(title);
};

// Check whether the instance is overlapped by another component instance.
UnitComponent.prototype.checkOverlaps = function (  ) {

  // Loop through all the siblings.
  for (var i = 0; i < this.siblings.length; i++) {

    // If the instance is overlapped by a sibling--hitTest(  ) is true and the sibling's
    // depth is greater than this instance's depth--swap the depths.
    if (this.hitTest(this.siblings[i]) && 
       this.siblings[i].getDepth() > this.getDepth(  )) {
      this.swapDepths(this.siblings[i]);
    }
  }
};

// Add a listener object (an object with an onMoved(  ) method defined), which is 
// called automatically when the component instance is moved (see the onMoved(  ) call
// made from this.bar.onRelease(  ) within drawBar(  )).
UnitComponent.prototype.addListener = function (listener) {
  this.listeners.push(listener);
};

// Register the class to a symbol linkage name.
Object.registerClass("UnitComponentSymbol", UnitComponent);

#endinitclip

Some of the code in preceding example requires illumination. The example code first defines a helper class, UnitComponentGroup. The UnitComponentGroup class is used to keep track of all the UnitComponent instances that are within the same timeline. Each time a new instance is created, it is added to the UnitComponentGroup instance. I refer to the UnitComponent instances within the same timeline as siblings. These siblings are stored within a groupMembers associative array property of the UnitComponentGroup object.

function UnitComponentGroup (  ) {}
UnitComponentGroup.prototype.groupMembers = new Object(  );

The UnitComponentGroup class defines only one method, onNew( ), which is invoked whenever a new UnitComponent is created. It is passed a reference to the new UnitComponent, and it adds that reference to it's groupMembers property. Then, it loops through all the siblings and updates their sibs properties so that each maintains a current list of all its siblings. This array is used by the UnitComponent instances whenever they are being dragged by the user to perform hit tests with its siblings (because siblings love to hit one another).

UnitComponentGroup.prototype.onNew = function (newUnit) {
  this.groupMembers[newUnit._name] = newUnit;
  var sibs;
  for (var unit in this.groupMembers) {
    sibs = new Array(  );
    for (var sib in this.groupMembers) {
      if (this.groupMembers[unit] != this.groupMembers[sib]) {
        sibs.push(this.groupMembers[sib]);
      }
    }
    this.groupMembers[unit].setSiblings(sibs);
  }
};

Next, the UnitComponent class is defined to extend the MovieClip class. This is important since UnitComponent instances have a visual representation.

UnitComponent.prototype = new MovieClip(  );

The constructor method for UnitComponent instances does a few interesting things. For one thing, it creates an instance of the UnitComponentBar component by calling attachMovie( ) with a symbol linkage identifier of "UnitComponentBarSymbol". Because the symbol linkage identifier "UnitComponentBarSymbol" is registered to the UnitComponentBar class, the UnitComponentBar constructor is called.

function UnitComponent (  ) {
   . . . 
  this.attachMovie("UnitComponentBarSymbol", "bar", this.getNewDepth(  ));
   . . . 
}

Also within the constructor, the instance is assigned a UnitComponentGroup object. If none exists, one is created on the parent timeline. Then, the group's onNew( ) method is invoked and passed a reference to the new UnitComponent.

function UnitComponent (  ) {
   . . . 
  if (this._parent.unitGroup == undefined) {
    this._parent.unitGroup = new UnitComponentGroup(  );
  }
  this.group = this._parent.unitGroup;
  this.group.onNew(this);
}

The purpose of the load( ) method—repeated in the following excerpt—is straightforward once you take a step back from the minutiae. It loads the external .swf module (located at the specified url) into the component. It also creates a progress bar and a text label that says "loading . . . ". The trickiest technique is the onEnterFrame( ) event handler used to detect whether the .swf file is loaded, which is assigned a reference to the onLoading( ) method. This means that the onLoading( ) method is called continuously as the playhead waits in the frame.

UnitComponent.prototype.load = function (url) {
  this.loadPlaceHolder.loadMovie(url);
  this.createTextField("loading", this.getNewDepth(  ), 0, 0, 0, 0);
  this.loading.text = "loading . . . ";
  this.loading.autoSize = true;
  this.attachMovie("FProgressBarSymbol", "progress", this.getNewDepth(  ));
  this.progress._y = this.loading._height + 5;
  this.onEnterFrame = this.onLoading;
};

The onLoading( ) method is called repeatedly until the module loads completely. The onLoading( ) method checks the status of the loading module using getBytesLoaded( ) and getBytesTotal( ) and updates the progress bar using setProgress( ). When the module is loaded, the progress bar and its label are removed, since they are no longer needed. At that time, the drawOutline( ) method and the title bar's draw( ) method are called once to draw the container to the proper dimensions for the loaded module. (Both methods are passed the values of the loaded module's dimensions.) Finally, the onEnterFrame( ) event handler method is deleted since it is no longer needed once the module finishes loading.

UnitComponent.prototype.onLoading = function (  ) {
  var lBytes = this.loadPlaceHolder.getBytesLoaded(  );
  var tBytes = this.loadPlaceHolder.getBytesTotal(  );
  this.progress.setProgress(lBytes, tBytes);
  if (this.progress.getPercentComplete(  ) == 100) {
    this.progress.removeMovieClip(  );
    this.loading.removeTextField(  );
    var margin = 5;
    var w = this.loadPlaceHolder._width + (margin * 2);
    var h = this.loadPlaceHolder._height + (margin * 2);
    this.drawOutline(w, h, margin);
    this.bar.draw(w, 20, margin, this.barColor, this.titleColor);
    var t = new Table(0, 0, 0, new TableRow(0, 
                      new TableColumn(-1, this.bar, this.outline)));
    this.loadPlaceHolder._x = this.outline._x + margin;
    this.loadPlaceHolder._y = this.outline._y + margin; 
    delete this.onEnterFrame;
  }
};

The checkOverlaps( ) method also benefits from explanation. This method is called continuously while a user drags the instance. It checks whether the dragged instance is overlapped by any of its siblings in the siblings array. It uses hitTest( ) to see if the current and sibling instances overlap in the coordinate space, and it compares the depths of the current and sibling instances to see if the sibling appears above the current instance. If these two conditions are met, it calls the swapDepths( ) method to put the current instance above the sibling.

UnitComponent.prototype.checkOverlaps = function (  ) {
  for (var i = 0; i < this.siblings.length; i++) {
    if (this.hitTest(this.siblings[i]) && 
       this.siblings[i].getDepth() > this.getDepth(  )) {
      this.swapDepths(this.siblings[i]);
    }
  }
};

27.2.4 Setting Up the Framework Form

Now that the component container has been defined, the only thing left to do for the framework is add the code for the movie's main routine on the first frame of the default layer of the main timeline.

Before adding the code to the main timeline, you need to make sure that the Flash document has all the required component symbols in the library. In addition to the custom components you created in the previous sections in this chapter, the framework also uses the ComboBox and PushButton components. So the first thing you should do is add the corresponding component symbols to the document's Library. You can do this by dragging instances of each of the components on the Stage, and then deleting those instances. The symbols are retained in the Library.

The following code creates the form that allows the user to add modules to his page, and it remembers the user's page settings using a local shared object (and loads the form if it was created previously). This code constitutes the main routine in mypage.fla.

// Include the necessary libraries.
// Include MovieClip.as from Chapter 7.
#include "MovieClip.as"
//Include Table.as and Forms.as from Chapter 11
#include "Table.as"
#include "Forms.as"
// Include TextField.as from Chapter 8 and DrawingMethods.as from Chapter 4.
#include "TextField.as"
#include "DrawingMethods.as"

// Create a new local shared object (LSO) if it doesn't yet exist.
// Otherwise, reload the saved page state from the existing shared object.
function initLSO (  ) {
  lso = SharedObject.getLocal("myPage");
  // Create a new local shared object, if necessary.
  if (!lso.data.exists) {
    lso.data.exists = true;
    lso.data.opened = new Object(  );
  } else {
    // Otherwise, load the saved state from the existing local shared object.
    var o = lso.data.opened;
    var newUnit;
    for (var item in o) {
      newUnit = openUnit(o[item].title, o[item].source, item);
      newUnit._x = o[item].x;
      newUnit._y = o[item].y;
    }
  }
  // The LSO is notified whenever a unit moves, so it can resave the page state.
  lso.onMoved = saveUnitPosition;
}

// Save the current position of a unit to the shared object.
function saveUnitPosition (unit) {
  this.data.opened[unit.index].x = unit._x;
  this.data.opened[unit.index].y = unit._y;
  this.flush(  );
}

// Read the module information from the XML document into an array
// and pass the array to initForm(  ).
function loadXML (  ) {
  servicesXML = new XML(  );
  servicesXML.formFunction = initForm;
  servicesXML.ignoreWhite = true;
  servicesXML.load("services.xml");
  servicesXML.root = this;
  // Define an onLoad(  ) handler to parse the data into an array.
  servicesXML.onLoad = function (  ) {
    var services = new Array(  );
    var items = this.firstChild.childNodes;
    var attribs;
    for (var i = 0; i < items.length; i++) {
      attribs = items[i].attributes;
      services.push({label: attribs["title"], data: attribs["source"]});
    }
    this.root.initForm(services);
  };
}

// Create a combo box populated with the array of modules passed to the function.
// Include a button to add the currently selected module to the page.
function initForm (services) {
  this.attachMovie("FComboBoxSymbol", "choicesMenu", this.getNewDepth(  ));
  choicesMenu.setDataProvider(services);
  choicesMenu.adjustWidth(  );
  this.attachMovie("FPushButtonSymbol", "addBtn", this.getNewDepth(  ));
  addBtn.setLabel("add to my page");
  addBtn.setClickHandler("addUnit");
  var t = new Table(10, 10, 10, new TableRow(5, new TableColumn(5, choicesMenu), 
                                                new TableColumn(5, addBtn)));
}

// The addUnit(  ) function is the callback function for addBtn.
function addUnit (  ) {
  var si = choicesMenu.getSelectedItem(  );
  openUnit(si.label, si.data);
}

// Open a module unit based on the title and source parameters passed 
// to it. If index is null, the unit is new, so assign a new index. 
// Otherwise, restore the index, as loaded from the shared object.
function openUnit (title, source, index) {
  var u = this.attachMovie("UnitComponentSymbol", 
                           "unit" + this.getNewDepth(  ), 
                           this.getNewDepth(  ));
  u.setTitle(title);
  u.load(source);
  // Delete the unit if the user clicks the close box.
  u.onClose = function (  ) {
    delete this._parent.lso.data.opened[this.index];
  };
  if (index != null) {
    u.index = index
  } else {
    var openUnit = new Object(  );
    openUnit.title = title;
    openUnit.source = source;
    lso.data.opened[u.index] = openUnit;
    lso.flush(  );
  }
  u.addListener(lso);
  return u;
}

// Call initLSO(  ) and loadXML(  ) to start the movie.
initLSO(  );
loadXML(  );

The main routine of the framework application, as shown in the preceding example, consists of just a few major functions. To better understand some of the inner workings of these functions, let's examine them more closely.

The initLSO( ) function is responsible for opening the local shared object (LSO) and handling any stored data. First of all, it uses the SharedObject.getLocal( ) method to try to locate the shared object named "myPage". If such an object exists, it is opened; otherwise, it is created. If the object did not exist previously, the exists property would not be true. When working with shared objects, you can often benefit from setting such a Boolean property. In this case, the object should be initialized by defining the exists property so that, in future sessions, the framework knows that the object is no longer new.

Additionally, you should define the opened property as a new associative array to store the modules' states. If the object already exists from a previous session, the function uses a for . . . in loop to iterate through the opened associative array and calls openUnit( ) to open any modules that were open in the previous session. Finally, for each module, the openUnit( ) function sets the local shared object as a listener (see next listing). Because the listener objects for UnitComponent instances look for an onMoved( ) method for callbacks, you should create a method of this name for the shared object.

function initLSO (  ) {
  lso = SharedObject.getLocal("myPage");
  if (!lso.data.exists) {
    lso.data.exists = true;
    lso.data.opened = new Object(  );
  } else {
    var o = lso.data.opened;
    var newUnit;
    for (var item in o) {
      newUnit = openUnit(o[item].title, o[item].source, item);
      newUnit._x = o[item].x;
      newUnit._y = o[item].y;
    }
  }
  lso.onMoved = saveUnitPosition;
}

The openUnit( ) function opens a new module for a given source using the attachMovie( ) method. Notice that the attachMovie( ) method returns a reference to the movie clip it creates. Setting a local variable (u) to this reference is handy because it can save having to type out the full path to the movie clip within the function. Because the name "UnitComponent" is registered to the UnitComponent class, the newly created unit is actually a UnitComponent instance. Therefore, the methods of the UnitComponent class can be invoked from this object.

The setTitle( ) and load( ) methods are called to perform the necessary tasks, and the onClose( ) callback method is defined such that when a unit is closed, the reference to it in the local shared object is removed. The next part of the function checks to see if the function has been passed a value for an index. An index value is passed only when the unit is being reopened from a previous session. ActionScript functions do not validate the number of parameters passed to the function, so if index is not passed to the function, it is simply undefined when used within the function.

function openUnit (title, source, index) {
  var u = this.attachMovie("UnitComponentSymbol", 
                           "unit" + this.getNewDepth(  ), 
                           this.getNewDepth(  ));
  u.setTitle(title);
  u.load(source);
  u.onClose = function (  ) {
    delete this._parent.lso.data.opened[this.index];
  };
  if (index != undefined) {
    u.index = index
  } else {
    var openUnit = new Object(  );
    openUnit.title = title;
    openUnit.source = source;
    lso.data.opened[u.index] = openUnit;
    lso.flush(  );
  }
  u.addListener(lso);
  return u;
}

The framework also uses the loadXML( ) function to load the values for the available modules. The loadXML( ) function uses an XML object to load the values from services.xml. Generally, you should consider loading content into a Flash movie from XML documents or other sources. When you do this, you effectively separate content from functionality to make for cleaner code.

The loaded results are handled within the onLoad( ) method, as defined for this object. The onLoad( ) method loops through each of the modules of the loaded document and creates an array of objects with label and data properties. This array is in the format that can be used to populate a ComboBox component easily. After the array is assembled, the onLoad( ) method invokes the initForm( ) function on the main timeline and passes it the array.

The initForm( ) function call is a little bit tricky because it is invoked from the root property of the servicesXML object. The root property is a custom property (see bolded line in the following code) that is defined as a reference to the main timeline. When you set a reference to another object by way of a property, such as root in this example, you can maintain relative relationships. For example, because initForm( ) is called from within the XML object's onLoad( ) method, it is impossible to call it without making an absolute reference (_root.initForm( )) unless a reference property is set, as is done here.

function loadXML (  ) {
  servicesXML = new XML(  );
  servicesXML.ignoreWhite = true;
  servicesXML.load("services.xml");
  servicesXML.root = this;
  servicesXML.onLoad = function (  ) {
    var services = new Array(  );
    var items = this.firstChild.childNodes;
    var attribs;
    for (var i = 0; i < items.length; i++) {
      attribs = items[i].attributes;
      services.push({label: attribs["title"], data: attribs["source"]});
    }
    this.root.initForm(services);
  };
}
    [ Team LiB ] Previous Section Next Section