The Composite pattern is simple in terms of its implementation, but its usage might be a little unclear as of yet. We've already discussed the details of how it works, but an example usually helps illustrate the concepts. In this example, we'll build a simple application that uses the file-system metaphor to show how the Composite pattern works. This example uses the IFileSystemItem interface as well as the FileSystemItem, File, and Directory classes discussed earlier in this chapter. In addition, we'll create a FileSystemItemView class and a main class (CompositeExample). The application will load data from an XML file and use that data to allow the user to browse a graphical representation of a file system. The directories are represented by folder icons, and files are represented by white rectangles. The user can click a directory to browse the contents of the directory. For the purposes of this example, we'll read the data from an XML file called fileSystem.xml with the following content: <fileSystem> <fileSystemItem type="Directory" name="Program Files" bytes="1024"> <fileSystemItem type="Directory" name="Adobe Illustrator"> <fileSystemItem type="File" name="Illustrator.exe" /> </fileSystemItem> </fileSystemItem> <fileSystemItem type="Directory" name="My Documents"> <fileSystemItem type="File" name="Document.txt" /> <fileSystemItem type="File" name="Image.jpg" /> </fileSystemItem> </fileSystem> You can see that the root node is <fileSystem>, and contained within that are nested <fileSystemItem> tags. Each <fileSystemItem> tag is of type Directory or File. Directory nodes can contain nested elements whereas File nodes cannot. Each element has a name attribute as well. We'll load this XML file into the application and parse it into our composite structure. Next we'll need a class that is a view for the File and Directory classes. The FileSystemItemView constructor accepts a parameter of type IFileSystemItem and then draws the correct icon and adds a label. Note that FileSystemItemView extends Sprite because it needs to be a display object. package com.peachpit.aas3wdp.compositeexample.views { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.filters.BevelFilter; import com.peachpit.aas3wdp.compositeexample.data.Directory; import com.peachpit.aas3wdp.compositeexample.data.IFileSystemItem; public class FileSystemItemView extends Sprite { // The file or directory to display. private var _item:IFileSystemItem; // The icon for the item - either a white rectangle or // a folder icon private var _icon:Sprite; // The name of the item private var _label:TextField; // Return a reference to the file or directory public function get data():IFileSystemItem { return _item; } public function FileSystemItemView(item:IFileSystemItem) { _item = item; _icon = new Sprite(); // Test if the item is a Directory or File. Draw the // appropriate icon for the item type if(item is Directory) { _icon.graphics.lineStyle(); _icon.graphics.beginFill(0xFFFF00); _icon.graphics.drawRect(0, 10, 50, 30); _icon.graphics.endFill(); _icon.graphics.beginFill(0xFFFF00); _icon.graphics.drawRoundRect(0, 0, 25, 15, 5, 5); _icon.graphics.endFill(); _icon.filters = [new BevelFilter()]; } else { _icon.graphics.lineStyle(0, 0x000000, 1); _icon.graphics.beginFill(0xFFFFFF); _icon.graphics.drawRect(0, 0, 40, 50); _icon.graphics.endFill(); } addChild(_icon); // Add a label text field _label = new TextField(); _label.text = _item.getName(); _label.autoSize = TextFieldAutoSize.LEFT; _label.x = 50; addChild(_label); } // This method allows you to override the label text // value for special cases such as parent directories // where you want to display a specific label rather than // the name of the item public function overrideLabel(label:String):void { _label.text = label; } } } Next we need to define a main class. In this example, the main class is called CompositeExample. The main class loads the XML file, parses it into the composite structure, and displays the contents of the top-level directory. When the user clicks a directory, the main class dispatches an event that updates the view. package { import flash.display.Sprite; import flash.net.URLLoader; import flash.net.URLRequest; import flash.events.Event; import flash.events.MouseEvent; import com.peachpit.aas3wdp.compositeexample.data.Directory; import com.peachpit.aas3wdp.compositeexample.data.File; import com.peachpit.aas3wdp.compositeexample.data.FileSystemItem; import com.peachpit.aas3wdp.iterators.IIterator; import com.peachpit.aas3wdp.compositeexample.views.FileSystemItemView; import com.peachpit.aas3wdp.compositeexample.data.IFileSystemItem; public class CompositeExample extends Sprite { // The top-level directory which contains all the // child elements private var _fileSystem:Directory; // An array of all the views currently displayed private var _itemViews:Array; public function CompositeExample() { // Load the XML var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onLoadXML); loader.load(new URLRequest("fileSystem.xml")); // Construct the top-level directory. Set the name, // and set the parent to null. Setting the parent to // null will indicate that there are no parent // composite objects for this directory. _fileSystem = new Directory(); _fileSystem.setName("File System"); _fileSystem.setParent(null); _itemViews = new Array(); } // When the XML loads parse the XML into the composite // structure. The parseXmlToFileSystem() method accepts an // XMLList parameter and a Directory parameter. It parses // all the XMLList children into elements within the // directory. private function onLoadXML(event:Event):void { XML.ignoreWhitespace = true; var xml:XML = new XML(event.target.data); parseXmlToFileSystem(xml.children(), _fileSystem); // Display the contents of the top-level directory updateView(_fileSystem); } private function updateView(directory:Directory):void { var i:uint; // Loop through all the currently-displayed item // views, and remove them. for(i = 0; i < _itemViews.length; i++) { removeChild(_itemViews[i]); delete _itemViews[i]; } _itemViews = new Array(); // Retrieve the iterator for the current directory var iterator:IIterator = directory.iterator(); var itemY:Number = 0; var item:IFileSystemItem; var view:FileSystemItemView; // If the directory has a parent, then add a view for // the parent directory and override the label so // it simply says Parent Directory. Add a click // event listener so when the user clicks, it changes // to the parent directory if(directory.getParent() != null) { view = new FileSystemItemView(directory.getParent()); view.overrideLabel("Parent Directory"); view.addEventListener(MouseEvent.CLICK, onClick); addChild(view); _itemViews.push(view); itemY += view.height + 5; } // Loop through all the items in the directory. Add // a view for each item. If the item is a directory, // add a click event listener. while(iterator.hasNext()) { item = IFileSystemItem(iterator.next()); view = new FileSystemItemView(item); view.y = itemY; itemY += view.height + 5; if(item is Directory) { view.addEventListener(MouseEvent.CLICK, onClick); } addChild(view); _itemViews.push(view); } } private function parseXmlToFileSystem(xml:XMLList, directory:Directory):void { var i:uint; var item:FileSystemItem; // Loop through all the children of the XMLList. // If the item is a directory, then make a new // directory and call parseXmlToFileSystem() // recursively to populate the directory. Otherwise // construct a file. for(i = 0; i < xml.length(); i++) { if(xml[i].@type == "Directory") { item = new Directory(); parseXmlToFileSystem(xml[i].children(), Directory(item)); } else { item = new File(); } item.setParent(directory); item.setName(xml[i].@name); directory.addItem(item); } } // When the user clicks an item view, update the view to the // contents of the directory that the user clicked. private function onClick(event:MouseEvent):void { updateView(Directory(event.currentTarget.data)); } } } When you run the application, you ought to see two folders initially: ProgramFiles and MyDocuments. If you click one of the folders, the view updates to display the contents of the directory as well as a folder icon with a ParentDirectory label. An example of the application is shown in Figure 8.1. Figure 8.1. The sample application.
|