Introduction to XML in Flash
by senocular
Example: Flash MX 2004 Classes Directory
This example will list out the contents of the
Classes folder in the Flash MX 2004 install
directory. Can you guess the format of this
information? Do I even need to ask? Yup, XML.
The XML contains the full contents of the Classes
folder, each file and folder and each file and
folder within those folders and so on until
there's no more files or folders left to file
or fold. A recursive function (which even includes
a loop on top of everything else) is then used
to list out the contents of each folder and
is re-called on if any item is itself a folder
thereby listing its contents. And this happens
until there is nothing left to file or fold!
As an added bonus to this example, we'll make
the folders expandable and collapsible. This
way you're not forced to look at all of the
files at once; you can be selective. And we
all know selection is a good thing. When all
done and through, you get the following end
result:.
[
flash mx 2004 classes directory ]
|
Note: Class Listing Lag |
Given
the sheer number of icons attached
for this example, there will
be a little bit of lag when
the XML is loaded and the directory
listing is created. As a precaution,
should you ever decide to create
something similar, you may wish
to start with all directories
closed loading in the icons
only when that directory is
opened. For simplicity's sake,
here, it was just easier to
add them all at once. |
|
XML Structure
There technically isn't much to the XML that
makes up this example. You're dealing with files
and directories, each with a name and type where
type determines icon. That's basically two different
kinds of elements with two different kinds of
data. The most important aspect of the XML is
the structure. Folders contain files and other
folders so folder elements will have file and
folder element children. Directories within
other directories can too have their own files
and directories and so on and so forth. So what
you get is a lot of nested directories. Here's
the XML:
mx04_classes.xml
It should look a little something like the
following (the entire file is not shown here):
- <?xml version="1.0"?>
- <directory
name="Classes"
type="directory">
- <file name="Accessibility.as"
type="actionscript"
/>
- <file name="Array.as"
type="actionscript"
/>
- <file name="AsBroadcaster.as"
type="actionscript"
/>
- .
- .
- .
Starting from the classes directory in a Flash
MX 2004 install, you get a listing of all the
files and directories that are within complete
with name and type.
To generate this list, I used this simple PHP
script:
- <?php
- function ExportDirectoryXML($base,
$indent){
- if ($dir
= @opendir($base)){
- while
($file
= readdir($dir)){
- if ($file{0}
!= "."){
- if
(is_dir($base
. "/"
. $file)){
- echo
"$indent<directory
name=\"$file\" type=\"directory\">\n";
- ExportDirectoryXML($base
. "/"
. $file,
$indent."\t");
- echo
"$indent</directory>\n";
- }else
echo
"$indent<file
name=\"$file\" type=\"actionscript\"
/>\n";
- }
- }
- closedir($dir);
- }
- }
-
- $directory
= "[DIRECTORY
PATH GOES HERE]";
-
- echo "<html><body><pre>\n";
- ExportDirectoryXML($directory,
"");
- echo "\n</pre></body></html>";
- ?>
This generated the directory structure in markup
which I then saved it to a XML file. It's that
file which is loaded by the Flash movie (the
movie loads a files separate from the script
though such a script could be used to load XML
like this dynamically from a server). If you'll
notice, it uses a recursive function as well.
Preparing the Flash File
This Flash file is really no more complicated
than the others before it, at least not in setting
up. Scripting is a little more involved. Otherwise,
in terms of screen elements, you're not dealing
with much more than about 3 different elements:
a movie clip for containing the full file listing,
an attached clip to represent a directory item
(a file or directory), and a scrollbar.
The other two parts are the directory items
and the movie clip to contain them. The movie
clip used to contain them is just an empty clip
called listing_mc. Nothing much in terms of
preparation goes into that. However, to keep
it masked, it is placed under a graphic that
hides content scrolling inside. listing_mc itself
isn't masked masked, it's just being
hidden by being beneath this graphic.
[
listing_mc beneath movie graphic interface ]
Next it's just a matter of making the movie
clip that is to be attached and representative
as a directory item. This will be used for both
files and directories when added to the listing_mc.
This movie clip has three parts: an icon, a
text field for a name, and an underlying button
used to select it. This example uses the button
to open and close directories only, but ideally
it would add interaction to the files as well.
[
item mc for each directory item ]
Take note that the registration point for this
movie clip is in the upper left. This will be
important later on when positioning each item
in the display. This will be linked as "directory_item"
in the library so that it can be attached dynamically
when needed.
Since this is used for all directory items,
files (of any kind) and directories, it will
need to facilitate the needs and differences
of each. The text field is no problem since,
as dynamic text, it can be changed easily at
any time. The icon, however, is a little more
difficult. The icon is actually another movie
clip with its own collection of frames inside.
These frames contain each of the icon graphics
needed for the file listing, the first frame
being a directory. For this example, there's
only one other file type so frame 2 is an ActionScript
file icon. Each frame also has a frame label
named after its icon type. This allows navigation
to any particular icon much more straight forward
using a name rather than a number. This name,
conveniently, should reflect the string used
to identify type in the XML document.
[
label frames for each icon based on type in
xml ]
Now, whenever one of these clips is attached
as a directory item, it can be given a name
and have its icon switched to whatever type
is necessary.
ActionScript
For the most part with this example,
you get another squirrel finder script-wise.
This too revolves around generating a list of
movie clips or buttons based on XML. However,
here, recursive functions are going to be used
to allow directory listings to be created for
each other directory within another directory
listing. So it's like taking the squirrel finder
and change some of its buttons to be other instances
of a squirrel finders.
The most difficult aspect of the file listing
is arranging the items in the view. Because
of the ability to open and close directories,
it means that whole groups of items will have
to move either up or down in response to more
items being displayed from the opening or closing
of directories. Instead of attaching items using
a variable to control spacing, a new technique
is used. This technique positions based on bounding
area moving lower items to the bottom of the
items above them. This way, should any items
be added or removed, basically, you just move
the item directly below that change where all
items below it can just reposition themselves
based on where they should be in respect to
the the position of item above them.
We'll get to that script in a second, but first
lets look at the definition of the XML instance.
- var directory_xml
= new
XML();
- directory_xml.ignoreWhite
= true;
- directory_xml.onLoad
= function(success){
- if (success){
- GenerateFileListing(this,
listing_mc);
- scrollbar.setTarget(listing_mc,
view_mc._y,
view_mc._height);
- }else
trace("Error
loading XML file");
- }
-
- directory_xml.load("mx04_classes.xml");
The directory_xml variable represents the XML
instance. It loads "mx04_classes.xml"
using load and has an onLoad which, upon success,
runs a function called GenerateFileListing passing
in itself and the listing_mc (and of course
there's that scrollbar action in there too).
- GenerateFileListing(this,
listing_mc);
As you might imagine, GenerateFileListing creates
a listing of files based on xml (this) in a
movie clip (listing_mc). Here is what GenerateFileListing
looks like:
- function GenerateFileListing(directory_node,
target_mc){
- var directory_mc
= target_mc.createEmptyMovieClip("directory_mc",
1);
- SetAtBottomOfParent(directory_mc);
- directory_mc._x
+= item_indent;
- directory_mc.depth
= 0;
-
- var contents
= directory_node.childNodes;
- var item_mc,
last_item_mc;
- for (var
i=0;
i<contents.length;
i++)
{
-
- item_mc
= directory_mc.attachMovie ("directory_item","item"+directory_mc.depth,
directory_mc.depth);
- directory_mc.depth++;
-
- item_mc.name_txt.text
= contents[i].attributes.name;
- item_mc.icon_mc.gotoAndStop(contents[i].attributes.type);
-
- if (last_item_mc
== undefined)
directory_mc.firstChild
= item_mc;
- else item_mc.previousSibling
= last_item_mc;
- last_item_mc.nextSibling
= item_mc;
- last_item_mc
= item_mc;
-
- if (contents[i].attributes.type
== "directory"){
- item_mc.select_btn.onPress
= Fold;
- GenerateFileListing(contents[i],
item_mc);
- }
-
- ArrangeContents(directory_mc);
- }
- }
The basic break down is as follows:
- create and position an empty movie
clip to hold the directory's contents
- loop through the children of the
node passed (representing the directory)
- attach an item movie clip for
each child in that node
- assign name and other properties
and change icon based on item type
- if type is a directory, call
the function again passing in the node representing
the item and the clip created for it
- position the item
Due to the fact that movie clips are being
used as directories (and files) and since movie
clips can contain other movie clips within them,
it seems perfect sense to maintain a directory's
own contents by keeping them within that directory's
movie clip as child movie clips. However, given
that the option of hiding those child movie
clips exists, it would be further beneficial
to keep all of those child movie clips within
one single movie clip (as a child of the directory).
That would allow us an easy way to get rid of
every single one of those movie clips at any
given time simply by removing hiding or that
single movie clip in which they all exist.
This is the initial command of GenerateFileListing,
creating that empty movie clip for directory
contents to exist.
- var directory_mc
= target_mc.createEmptyMovieClip("directory_mc",
1);
- SetAtBottomOfParent(directory_mc);
- directory_mc._x
+= item_indent;
- directory_mc.depth
= 0;
It does this within the passed target_mc which,
initially, is the listing_mc on the main timeline.
Other commands such as SetAtBottomOfParent and
the assignment of _x (based on item_indent,
a variable defined earlier on the main timeline)
manage its position while a depth variable is
set to manage the depths of clips being attached
within.
So right away, when the XML first loads, before
listing_mc gets any item movie clips attached
to it, it gets an empty clip which is to be
treated as a content holder for a collection
of directories and files within a directory.
This means listing_mc itself only gets one movie
clip added to it. All contents within listing_mc
end up going in that movie clip. Also, every
directory item thereafter will too get its own
empty movie clip when a recursive call to GenerateFileListing
is made to generate its file contents. What
you end up getting is something along the lines
of this in terms of movie clip structure:
[
movie clips in movie clips for directory structure
]
Now, if you'll imagine removing or hiding any
one of those movie clips holding one of those
directory's contents, you can see how the effects
would be similar to closing that directory in
a directory view which is exactly what we're
after.
All that remains is getting the contents in
each directory as needed, and that's what the
following loop lets us do.
- var contents
= directory_node.childNodes;
- var item_mc,
last_item_mc;
- for (var
i=0;
i<contents.length;
i++)
{
- // ...
- }
First the child nodes are put into a friendly
variable named contents. These are the nodes
within the current directory node initially
passed into GenerateFileListing. A few other
variables being used within the loop are also
declared here for safe keeping. Then the for
loop is used to cycle through each.
In the loop, each item, directory or file,
is immediately created within the directory_mc
movie clip (the empty one created to hold all
contents of a directory).
- item_mc =
directory_mc.attachMovie("directory_item", "item"+directory_mc.depth,
directory_mc.depth);
- directory_mc.depth++;
Then, the text and icon are set for that item.
- item_mc.name_txt.text
= contents[i].attributes.name;
- item_mc.icon_mc.gotoAndStop(contents[i].attributes.type);
This is pretty straight-forward. Simply take
the text from attributes the current node of
the loop. The text comes from the name attribute
and is placed into the name_txt text field while
the icon_mc is told to go to and stop on the
frame label specified by the type attribute.
Directly after this, however, are some assignments
that are less obvious.
- if (last_item_mc
== undefined)
directory_mc.firstChild
= item_mc;
- else item_mc.previousSibling
= last_item_mc;
- last_item_mc.nextSibling
= item_mc;
- last_item_mc
= item_mc;
What we have here is actually the borrowing
of naming conventions used by XML to help handle
the movie clip positioning of child movie clips
within a directory. Since item movie clips have
to be aware of their preceding item in order
to position themselves to it, a reference to
that item is added within the movie clip. This
is defined as previousSibling and handled through
the last_item_mc variable defined prior to the
loop which following this assignment becomes
the current item. Similarly, nextSibling is
used to be able to start at the top of a list
of directory items and work our way down in
order to perform the positioning of each in
the correct order. This will work much in the
same way the alternative approach to the squirrel
finder example did. The directory_mc too gets
a reference to a movie clip, the first in the
list (when last_item_mc is undefined, it means
the current item is the first) in order to be
able to start a chain of positioning beginning
with it's first item listed. Putting this into
motion is handled by four other functions that
are used for directory opening and closing.
Next in the loop is the recursive call, or
at least the check to see if a recursive call
to GenerateFileListing is needed. When it is
needed is when the type of the current item
is of type "directory."
- if (contents[i].attributes.type
== "directory"){
- item_mc.select_btn.onPress
= Fold;
- GenerateFileListing(contents[i],
item_mc);
- }
If so, a button action is added (using Fold,
covered shortly) and the call is made. This
particular call to GenerateFileListing uses
the current node in the loop and the current
item movie clip. Each exists within the previous
arguments passed in to GenerateFileListing during
the current call. So here, and in many cases,
the nesting of the function calls reflects the
nesting of the objects in which it affects.
[
generatefilelisting called for each directory
]
After each child is created, and if a directory
all its children or contents are created, then
it is positioned with a final
- ArrangeContents(directory_mc);
And that brings us to those functions used
to handle item position and directory opening
and closing. They are as follows:
- function SetAtBottomOfParent(below_mc){
- if (below_mc._parent._height)
{
- below_mc._y
= below_mc._parent.getBounds(below_mc._parent).yMax;
- }
- }
- function SetBelow(below_mc,
top_mc){
- if (top_mc
!= undefined)
{
- below_mc._y
= top_mc.getBounds(top_mc._parent).yMax;
- }
- }
- function ArrangeContents(directory_mc){
- var item
= directory_mc.firstChild;
- if (item
!= undefined){
- while
(item
= item.nextSibling){
- SetBelow(item,
item.previousSibling);
- }
- var parent_container
= directory_mc._parent._parent;
- if (parent_container
!= undefined)
ArrangeContents(parent_container);
- }
- }
- function Fold(){
- if (this._parent.directory_mc._visible){
- this._parent.directory_mc._visible
= false;
- this._parent.directory_mc._yscale
= 0;
- }else{
- this._parent.directory_mc._visible
= true;
- this._parent.directory_mc._yscale
= 100;
- }
- ArrangeContents(this._parent._parent);
- scrollbar.contentChanged();
- }
The first two functions, SetAtBottomOfParent
and SetBelow are used to position the individual
empty directory movie clips and attached item
clips (respectively).
- function SetAtBottomOfParent(below_mc){
- if (below_mc._parent._height)
{
- below_mc._y
= below_mc._parent.getBounds(below_mc._parent).yMax;
- }
- }
- function SetBelow(below_mc,
top_mc){
- if (top_mc
!= undefined)
{
- below_mc._y
= top_mc.getBounds(top_mc._parent).yMax;
- }
- }
Each function vertically positions a passed
clip, below_mc, to be either below its _parent
or another movie clip through the use of the
getBounds method. As you might be able to imagine,
SetBelow will be useful with a directory item
and its previousSibling property. And what do
you know, the next function, ArrangeContents,
does exactly that:
- function ArrangeContents(directory_mc){
- var item
= directory_mc.firstChild;
- if (item
!= undefined){
- while
(item
= item.nextSibling){
- SetBelow(item,
item.previousSibling);
- }
- var parent_container
= directory_mc._parent._parent;
- if (parent_container
!= undefined)
ArrangeContents(parent_container);
- }
- }
ArrangeContents gets passed to it a directory
container movie clip. If it has a firstChild,
it would mean there are movie clips within it.
This would have been assigned in the for loop
attaching the clips. Then positioning would
be performed, otherwise the function just ends.
To position items, a while loop is used to
cycle through each sibling of the movie clips
starting with the first in the directory_mc.
SetBelow is then used to position that sibling
below the sibling before it.
Now, the thing to remember is that if you reposition
the contents of a directory, other directories,
such as the one containing the directory in
question, will have to react and reposition
their content as to prevent gaps in the directory
layout. ArrangeContents handles this by calling
itself (recursively) on the directory_mc's _parent._parent
if it exists, ultimately correcting all movie
clip positions that the initial change could
have effected. (_parent._parent is used since
_parent reflects the attached item clip used
for the directory and we want the directory
clip in which it exists or its _parent clip).
That brings us to the remaining Fold function
which is assigned as an onPress event for directories.
- function Fold(){
- if (this._parent.directory_mc._visible){
- this._parent.directory_mc._visible
= false;
- this._parent.directory_mc._yscale
= 0;
- }else{
- this._parent.directory_mc._visible
= true;
- this._parent.directory_mc._yscale
= 100;
- }
- ArrangeContents(this._parent._parent);
- scrollbar.contentChanged();
- }
This shows or hides directory contents. Because
this is assigned to a button within an attached
item movie clip, _parent is used to access the
directory_mc within that item. Then the directory
is hidden and set to have a _yscale
of 0. The inclusion of the _yscale is necessary
because, despite the fact that this movie clip
may be invisible, its height would still register
as part of its parent's height. This means that
hiding the clip alone will not be enough for
ArrangeContents to recognize there being a gap.
The clip would actually have to be literally
folded up. When that happens, ArrangeContents
is called and all movie clips get to react to
the new change in position, whether it was movie
clips being shown or being hidden.
|