Creating a Frameworkless MXML File Part 2 (With Children)

6

Category : compiler, Flex, frameworkless mxml, MXML

In my last post, we saw how to use MXML (and Flex Builder) to instantiate one top-level object. However, to actually be useful, let’s learn how to add some children. There are two main ways to do this, through the IMXMLObject and through default properties. Both are pretty easy to understand.

Looking at the IMXMLObject interface, you’ll see one method:

package mx.core
{
 
/**
 *  The IMXMLObject interface defines the APIs that a non-visual component
 *  must implement in order to work properly with the MXML compiler.
 *  Currently, the only supported method is the <code>initialized()</code>
 *  method.
 */
public interface IMXMLObject
{
    //--------------------------------------------------------------------------
    //  Methods
    //--------------------------------------------------------------------------
 
    /**
     *  Called after the implementing object has been created and all
     *  component properties specified on the MXML tag have been initialized.
     *
     *  @param document The MXML document that created this object.
     *
     *  @param id The identifier used by <code>document</code> to refer
     *  to this object.
     *  If the object is a deep property on <code>document</code>,
     *  <code>id</code> is null.
     */
    function initialized(document:Object, id:String):void;
}
}

If your child object implements this interface, the MXML compiler will generate code to automatically call this method. It’s a pretty limited way to add children because it only has two properties, document and id. It actually doesn’t tell you who your parent is–just your document, which is your top-level root node object (usually Application).

Sticking with stupid-simple examples, I’m just going to draw some ellipses and rectangles. The code for drawing shapes has been ripped off of my co-workers, Chet Haase, and modified to make it as simple as possible and fit the example.

<?xml version="1.0" encoding="utf-8"?>
<components:MyContainer 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:components="components.*" 
    xmlns:shapes="components.shapes.*">
 
    <shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
    <shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
</components:MyContainer>

Rather than post all the code here, I’ll just post some snippets (full code is in two flex builder projects: IMXMLObject example and the Default property example). The interesting part is ArtShape, which is the base class for all shapes. That class is the one that implements IMXMLObject, and all it does is:

public function initialized(document:Object, id:String):void
{
    document.addChild(this);
    renderShape();
}

So basically this initialized method just adds ourselves to the top-level application and calls a method that Chet had called renderShape(). The ArtShape version of that method is empty, but here’s what Rectangle looks like:

/**
 * Draws a rectangle into this shape's display list
 */
override protected function renderShape():void
{
    if (filled) {
        graphics.beginFill(color);
    } else {
        graphics.lineStyle(strokeWidth, color);
    }
    graphics.drawRect(startX, startY, endX - startX, endY - startY);
}

Looking at the generated code, you’ll see what’s going on behind the scenes with our MXML.

package 
{
//  begin class def
public class FrameworklessChildren
    extends components.MyContainer
{
    //  instance variables
/**
 * @private
 **/
    public var _FrameworklessChildren_Ellipse1 : components.shapes.Ellipse;
 
/**
 * @private
 **/
    public var _FrameworklessChildren_Rect1 : components.shapes.Rect;
    //  type-import dummies
    //  constructor (non-Flex display object)
    /**
     * @private
     **/
    public function FrameworklessChildren()
    {
        super();
        //    our style settings
        //    properties
        _FrameworklessChildren_Ellipse1_i();
        _FrameworklessChildren_Rect1_i();
        //    events
    }
    //  scripts
    //  end scripts
    //    supporting function definitions for properties, events, styles, effects
private function _FrameworklessChildren_Ellipse1_i() : components.shapes.Ellipse
{
    var temp : components.shapes.Ellipse = new components.shapes.Ellipse();
    _FrameworklessChildren_Ellipse1 = temp;
    temp.color = 65280;
    temp.startX = 20;
    temp.startY = 120;
    temp.endX = 100;
    temp.endY = 130;
    temp.initialized(this, "_FrameworklessChildren_Ellipse1")
    return temp;
}
 
private function _FrameworklessChildren_Rect1_i() : components.shapes.Rect
{
    var temp : components.shapes.Rect = new components.shapes.Rect();
    _FrameworklessChildren_Rect1 = temp;
    temp.color = 16711680;
    temp.startX = 20;
    temp.startY = 20;
    temp.endX = 100;
    temp.endY = 30;
    temp.initialized(this, "_FrameworklessChildren_Rect1")
    return temp;
}
    //  embed carrier vars
    //  end embed carrier vars
//  end class def
}
//  end package def
}

So basically to create the children it calls the methods in the constructor for the top-level root class. In those methods, the child object gets created, its properties get set, and the initialized method is called. The full code for the above example can be found here.

However, as I said before, this doesn’t really support nesting–these objects are essentially just direct children of the root object. So to support this, we need to use default properties. This is actually how the majority of objects will work in Flex 4 (Flex 3 essentially uses something else entirely which is built around UIComponents and UIComponentDescriptors).

Anyways, here’s our sample MXML file that we want to support:

<?xml version="1.0" encoding="utf-8"?>
<components:MyContainer 
    xmlns="http://ns.adobe.com/mxml/2009" 
    xmlns:components="components.*" 
    xmlns:shapes="components.shapes.*">
 
    <shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
    <shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
 
    <components:MyContainer x="100" y="100">
        <shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
        <shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
    </components:MyContainer>
</components:MyContainer>

You see I’ve added another container that has children. Another important thing to note is that I changed the compiler namespace to use the new 2009 namespace (so you’ll need the new compiler to take advantage of this). This isn’t actually needed, and I’ll show you what it looks like with just the 2006 namespace later on.

The basic idea of default properties is that every MXML object has a “default property.” In our case, MyContainer will have a default property of content. This is basically the property where the children go. Instead of the MXML code above, I could’ve used:

<?xml version="1.0" encoding="utf-8"?>
<components:MyContainer 
    xmlns="http://www.adobe.com/2006/mxml" 
    xmlns:components="components.*" 
    xmlns:shapes="components.shapes.*">
    <components:content>
        <shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
        <shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
 
        <components:MyContainer x="100" y="100">
            <components:content>
                <shapes:Rect color="0xFF0000" startX="20" startY="20" endX="100" endY="30" />
                <shapes:Ellipse color="0x00FF00" startX="20" startY="120" endX="100" endY="130" />
            </components:content>
        </components:MyContainer>
    </components:content>
</components:MyContainer>

The difference here is that when I instantiate MyContainer, I’m explicitly saying “fill the content property with these objects.” However, rather than having to explicitly say that the objects should be stuffed into the content property, I can tell MyContainer to have a default property of content. There was a bug in the MXML compiler in how it handled top-level default properties (only top-level ones), and that’s why I had to use the 2009 namespace to use it; however, if you explicitly specify the property, you can use the old compiler and the 2006 namespace.

So let’s see how we modified MyContainer to make this happen:

package components
{
    import flash.display.DisplayObjectContainer;
    import flash.display.Sprite;
 
    [DefaultProperty("content")]
    public class MyContainer extends Sprite
    {
        public function MyContainer()
        {
        }
 
        private var _content:*;
 
        public function get content():*
        {
            return _content;
        }
 
        public function set content(value:*):void
        {
            _content = value;
 
            if (_content is Array)
            {
                for (var i:int = 0; i < _content.length; i++)
                {
                    _content[i].initializeMe(this);
                }
            }
            else
            {
                _content.initializeMe(this);
            }
        }
 
        public function initializeMe(parent:DisplayObjectContainer):void
        {
            parent.addChild(this);
        }
 
    }
}

With the metadata, we defined MyContainer’s default property to be content. Then I just filled in some basic getters/setters for the property. Because content could be a single item or an Array of items, I special cased some logic in there. Basically all I did was loop through all the content and call initializeMe(this) on all the children. That’s just a method I made up, and I probably should create an interface for it. You can see I implemented initializeMe(parent:DisplayObjectContainer) in here as well, and all it does is call addChild. In the case of ArtBoard, it’s pretty similar:

public function initializeMe(parent:DisplayObjectContainer):void
{
    parent.addChild(this);
    renderShape();
}

So to complete the picture of how all this works, let’s take a look at the generated code for our MXML class (the first one which uses default properties):

package 
{
//  begin class def
public class FrameworklessChildren
    extends components.MyContainer
{
    //  instance variables
    //  type-import dummies
    //  constructor (non-Flex display object)
    /**
     * @private
     **/
    public function FrameworklessChildren()
    {
        super();
        //    our style settings
        //    properties
        this.content = [_FrameworklessChildren_Rect1_c(), _FrameworklessChildren_Ellipse1_c(), _FrameworklessChildren_MyContainer2_c()];
        //    events
    }
    //  scripts
    //  end scripts
    //    supporting function definitions for properties, events, styles, effects
private function _FrameworklessChildren_Rect1_c() : components.shapes.Rect
{
    var temp : components.shapes.Rect = new components.shapes.Rect();
    temp.color = 16711680;
    temp.startX = 20;
    temp.startY = 20;
    temp.endX = 100;
    temp.endY = 30;
    return temp;
}
 
private function _FrameworklessChildren_Ellipse1_c() : components.shapes.Ellipse
{
    var temp : components.shapes.Ellipse = new components.shapes.Ellipse();
    temp.color = 65280;
    temp.startX = 20;
    temp.startY = 120;
    temp.endX = 100;
    temp.endY = 130;
    return temp;
}
 
private function _FrameworklessChildren_MyContainer2_c() : components.MyContainer
{
    var temp : components.MyContainer = new components.MyContainer();
    temp.x = 100;
    temp.y = 100;
    temp.content = [_FrameworklessChildren_Rect2_c(), _FrameworklessChildren_Ellipse2_c()];
    return temp;
}
 
private function _FrameworklessChildren_Rect2_c() : components.shapes.Rect
{
    var temp : components.shapes.Rect = new components.shapes.Rect();
    temp.color = 16711680;
    temp.startX = 20;
    temp.startY = 20;
    temp.endX = 100;
    temp.endY = 30;
    return temp;
}
 
private function _FrameworklessChildren_Ellipse2_c() : components.shapes.Ellipse
{
    var temp : components.shapes.Ellipse = new components.shapes.Ellipse();
    temp.color = 65280;
    temp.startX = 20;
    temp.startY = 120;
    temp.endX = 100;
    temp.endY = 130;
    return temp;
}
    //  embed carrier vars
    //  end embed carrier vars
//  end class def
}
//  end package def
}

You can see it’s basically the same as the one using IMXMLObject; however, rather than calling the initialized() method for us, it just sets the content property. You can download the full code for this example here.

One thing that might be worth checking out is how this class gets generated in the first place. In the compiler, we have a few velocity templates that we use (for style sheets, MXML classes, binding, etc…). The one for the MXML->ActionScript is called ClassDef.vm (a lot of the methods used in that class exist in ClassDefLib.vm). If you’re interested in a lot of this stuff, definitely download the compiler code and take a look at these files. They’re pretty simple to follow and easy to modify if you want to start mucking around with it.

So that’s basically it for my short trip into compiler-land. If you’ve got any questions, let me know!

Creating a Frameworkless MXML File

3

Category : compiler, Flex, frameworkless mxml, MXML

Me saying it’s been a while since I’ve blogged here is an understatement. I’ve been heads down doing work on Flex 4, atleast that’s my excuse, and I’m sticking to it.

Along the way of deep-diving into the framework, I’ve learned how much of the nitty-gritty details actually work. Unfortunately, not all of it would be really valuable to you guys, but as I think of useful lessons I’ve learned, I’ll blog about them here.

Anyways, on to the real post: using the Flex Builder IDE and the MXML compiler for generating framework-less code. By framework-less code, I mean no mx.core.*, no SystemManager, no Application, no UIComponents, etc… So why would you want to do this? Let’s say you want to create something really light-weight and don’t want to carry around some of the framework baggage, but you still want to use FlexBuilder IDE because it’s made for programmers, unlike the Flash Authoring IDE. Or, you want to create an ActionScript only project, but love the MXML syntax because it’s a lot easier for non-progammers to take a look at the MXML code and figure out what’s going on. Some awesome features won’t be available if you don’t use the framework (for instance, binding), but this technique might prove useful to some.

At first, I didn’t know you could actually do this, but it’s quite easy. If you just have one file that is your main ActionScript file, in fact you don’t have to do anything. So let’s say I wanted to create a clock (and not a sweet analog clock, but just a digital one because I’m lame).

Pop open FlexBuilder and create a simple project. Create a simple class called SimpleDigitalClock.as. You’ve got to make sure it extends Sprite because in Flash, the root object must always be a Sprite. My primitive understanding of the reason is that every SWF must have a root object that needs to be a Sprite. This object will be instantiated automatically by the Flash player.

Our clock is going to be super-simple (I’m super-lazy). Here’s my version, but feel free to do something fancier with yours:

package
{
  import flash.display.Sprite;
  import flash.events.TimerEvent;
  import flash.text.TextField;
  import flash.utils.Timer;
 
  public class SimpleDigitalClock extends Sprite
  {
      public function SimpleDigitalClock()
      {
          super();
 
          time = new TextField();
          addChild(time);
 
          updateTime();
 
          timer = new Timer(1000);
          timer.addEventListener(TimerEvent.TIMER, updateTime);
          timer.start();
      }
 
      private var time:TextField;
      private var timer:Timer;
 
      private function updateTime(event:TimerEvent = null):void
      {
          time.text = new Date().toString();
      }
 
  }
}

As mentioned before I extend Sprite. In the constructor, I create a new TextField and add it as a child. Then I call updateTime, which grabs the current time and puts it into our TextField. Lastly, I just create a Timer which runs every second to update the time so it stays current.

Now for the cool part. Let’s say I wanted to make this my Application. Well it’s actually really easy, just change your main MXML file to look like this:

<?xml version="1.0" encoding="utf-8"?>
<local:SimpleDigialClock xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
 
</local:SimpleDigitalClock>

When we run this file, and we get this beauty:

Digital Clock Screenshot

Some points to note:

  • We kept the mx namespace because the compiler needs this info, even though no components are instantiated from the mx namespace (hopefully I’ll talk about how this is done in another blog).
  • xmlns:local=”*” creates a new namespace, called local, which maps to actionscript files in my current directory.
  • All we need to do is tell the MXML compiler to instantiate our Sprite object, SimpleDigitalClock, as the root object for this SWF.

So that’s pretty damned simple. One of the things I often do is to delve into the compiled code. To do this, add “-keep” or “-keep-actionscript-generated” to the additional compiler arguments:

A new folder will popup called “generated.” This will be especially small because we’re using framework-less code. In fact, it’s only 2 files: FrameworkLessClock-generated.as and FrameworkLessClock-interface.as. Only the first one’s really interesting:

/**
*  Generated by mxmlc 4.0
*
*  Package: 
*  Class:      FrameworkLessClock
*  Source:     C:\Documents and Settings\rfrishbe\My Documents\Gumbo-FP10-MyBranch\FrameworkLessClock\src\FrameworkLessClock.mxml
*  Template:   flex2/compiler/mxml/gen/ClassDef.vm
*  Time:       2008.07.10 18:37:50 PDT
*/
 
package
{
 
import SimpleDigitalClock;
import flash.accessibility.*;
import flash.debugger.*;
import flash.display.*;
import flash.errors.*;
import flash.events.*;
import flash.external.*;
import flash.filters.*;
import flash.geom.*;
import flash.media.*;
import flash.net.*;
import flash.printing.*;
import flash.profiler.*;
import flash.system.*;
import flash.text.*;
import flash.ui.*;
import flash.utils.*;
import flash.xml.*;
import mx.binding.*;
import mx.core.ClassFactory;
import mx.core.DeferredInstanceFromClass;
import mx.core.DeferredInstanceFromFunction;
import mx.core.IDeferredInstance;
import mx.core.IFactory;
import mx.core.IPropertyChangeNotifier;
import mx.core.mx_internal;
import mx.styles.*;
 
 
 
//  begin class def
 
public class FrameworkLessClock
  extends SimpleDigitalClock
{
 
  //  instance variables
  //  type-import dummies
  //  constructor (non-Flex display object)
  /**
   * @private
   **/
  public function FrameworkLessClock()
  {
      super();
      //    our style settings
      //    properties
      //    events
  }
 
  //  scripts
  //  end scripts
  //    supporting function definitions for properties, events, styles, effects
  //  embed carrier vars
  //  end embed carrier vars
//  end class def
}
//  end package def
}

So you see not much happens. There’s a lot of comments in there for some framework stuff, but not much happens in our case because we don’t need that stuff. Let’s say you add event-listeners or properties to your SimpleDigitalClock. Those will get genned in here. So for a simple example, let’s create a property called timeZoneOffset, which will be the timezone offset (in minutes) that we want to change our clock to. I won’t go into the details of the math, but I think it works out… Also, let’s create a clockUpdated event, which dispatches anytime we change the text on the clock.

package
{
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.TimerEvent;
  import flash.text.TextField;
  import flash.utils.Timer;
 
  [Event("clockUpdated")]
 
  public class SimpleDigitalClock extends Sprite
  {
      public function SimpleDigitalClock()
      {
          super();
 
          time = new TextField();
          addChild(time);
 
          timer = new Timer(1000);
          timer.addEventListener(TimerEvent.TIMER, updateTime);
          timer.start();
      }
 
      private var time:TextField;
      private var timer:Timer;
 
      public var timeZoneOffset:int = new Date().getTimezoneOffset();
 
      private function updateTime(event:TimerEvent = null):void
      {
          var date:Date = new Date();
 
          // converts the Date to UTC by adding or subtracting the time zone offset
          var currentOffsetMilliseconds:Number = date.getTimezoneOffset() * 60 * 1000;
          var newOffsetMilliseconds:Number = timeZoneOffset * 60 * 1000;
 
          date.setTime(date.getTime() + currentOffsetMilliseconds);
          date.setTime(date.getTime() - newOffsetMilliseconds);
 
          time.text = date.toString();
          dispatchEvent(new Event("clockUpdated"));
      }
 
  }
}

The two things that really matter here are adding the public property and the event metadata. We also dispatch the event in updateTime, otherwise it’d never get fired.

So let’s change these variables in MXML:

<?xml version="1.0" encoding="utf-8"?>
<local:SimpleDigitalClock xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"
  timeZoneOffset="480" clockUpdated="trace('blah')">

</local:SimpleDigitalClock>

Now, let’s take a look at our genned code from this example:

    //  constructor (non-Flex display object)
  /**
   * @private
   **/
  public function FrameworkLessClock()
  {
      super();
      // our style settings
      // properties
 
      this.timeZoneOffset = 480;
      // events
      this.addEventListener("clockUpdated", ___FrameworkLessClock_SimpleDigitalClock1_clockUpdated);
  }
  //  scripts
  //  end scripts
  //  supporting function definitions for properties, events, styles, effects
 
  /**
   * @private
   **/
  public function ___FrameworkLessClock_SimpleDigitalClock1_clockUpdated(event:flash.events.Event):void
  {
      trace('blah')
  }

You can see from the genned code it added the property we set as well as the event and a function for the event handler.
So that’s it for now. I plan on blogging some more on this stuff, covering how to add children in MXML without the framework as well as some other cool topics.

Post a comment if you have any questions or have suggestions future blog posts that might be useful.