Check out "Do you speak JavaScript?" - my latest video course on advanced JavaScript.
Language APIs, Popular Concepts, Design Patterns, Advanced Techniques In the Browser

AS3: Using custom metadata in flex (part 2)

Because of the huge interest in AS3: Using Custom Metadata in Flex (part 1) I decided to dig a little bit deeper in this topic. You already know how to get information about your custom metadata, so in this article I'll show you how to use it.

The biggest problem in the usage of custom metadata is that you should add all your custom tags in the additional compiler arguments. If you have a lot of tags you should make a lot of changes in this direction. That's why I created a SWC component that will make your life easier. Just download the swc file from here and include it in your flex project. The idea is that the swc component is compiled with -keep-as3-metadata * argument, which means that if you include it in your project you can use tag with a name *. The swc file contains two classes:

  • com.krasimirtsonev.data.metadata.MetadataParserThe purpose of this class is to fetch the xml that contains information about the metadata and composes objects by type MetadataVO
  • com.krasimirtsonev.data.metadata.MetadataVOValue object that contains structured data about every metadata tag

So, let's see a simple example of using these classes. We have a basic application class:

package lib.document {
    import mx.containers.Canvas;
    public class App extends Canvas {
      public
      function App() {
        var m: MyCustomClass = new MyCustomClass();
        trace("application name = " + m.name);
      }
    }
  }

It is really simple. We created an object by type MyCustomClass and printed a property name of this object. Here is the interesting part:

package lib.document {
    import flash.utils.describeType;
    import com.krasimirtsonev.data.metadata.MetadataParser;
    public class MyCustomClass {
      [ * (doThisOperation = "get-from-storage", parameterToTheOperation = "name-of-the-app")] public
      var name: String = "";
      public
      function MyCustomClass() {
        MetadataParser.init(this, Analyser);
      }
    }
  }

As you can see the property name of MyCustomClass is empty and basically we didn't put any data into it. But if you run the application will see:

application name = CUSTOM_METADATA_EXAMPLE

The CUSTOM_METADATA_EXAMPLE string comes from an additional class called Storage:

package lib.document {
    public class Storage {
      public static
      function getData(key: String): String {
        if (key == "name-of-the-app") {
          return "CUSTOM_METADATA_EXAMPLE";
        } else {
          return "missing key '" + key + "'";
        }
      }
    }
  }

The Storage class has just one static method which returns that string if we pass a correct key (in our case this is name-of-the-app). The magic part is hidden in this line of code:

MetadataParser.init(this, Analyser);

The MetadataParser has only one method init which accepts two parametrs. The first one is your class (the class that contains the metadata) and the second one is class that will operate with your custom tags. Here is the content of my analyser:

package lib.document {
    import com.krasimirtsonev.data.metadata.MetadataVO;
    public class Analyser {
      public
      function Analyser(data: Array): void {
        var numOfVarData: int = data.length;
        for (var i: int = 0; i < numOfVarData; i++) {
          var vo: MetadataVO = data[i] as MetadataVO;
          if (vo.metadata.doThisOperation == "get-from-storage") {
            vo.classObj.name = Storage.getData(vo.metadata.parameterToTheOperation);
          }
        }
      }
    }
  }

What happens is that the MetadataParser parses the metadata of MyCustomClass and converts it to an array of MetadataVO, which is passed to the constructor of our analyser. We can set as many attributes as we want and they will be available. In this current example I checked if the doThisOperation attribute has value of get-from-storage and if yes then I called the getData method of the Storage class with a key equal to the value of parameterToTheOperation attribute. Here is the content of the MetadataVO class:

package com.krasimirtsonev.data.metadata {
    public class MetadataVO {
      public static
      const METADATA_FOR_CLASS: String = "MetadataForClass";
      public static
      const METADATA_FOR_VARIABLE: String = "MetadataForVariable";
      public static
      const METADATA_FOR_METHOD: String = "MetadataForMethod";
      public
      var xml: XML;
      public
      var classObj: * ;
      public
      var metadata: Object = {};
      public
      var metadataFor: String = "";
      public
      var name: String = "";
      public
      var type: String = "";
      public
      var params: Array;
      public
      var declaredBy: String = "";
      public
      var returnType: String = "";
    }
  }
  • xml - contains the xml representing the tag
  • classObj - reference to your class object. The one that calls MetadataParser.init. In most cases you will use it to access some public properties or methods.
  • metadata - an object that contains all the attributes of your metadata tag
  • metadataFor - string that shows where the tag is placed (i.e. before variable, method or class definition)
  • name - string that shows the name of the item after the tag (i.e. if you place the tag before variable with name myVar, this property will return myVar)
  • type - string that shows the type of the variable (if the tag is placed before variable)
  • params - if you place the tag before the method then this property returns information regarding the parameters of that method
  • declaredBy
  • returnType - string that shows the returned type of the method (if the tag is placed before the method)

And here is the code of the parser:

package com.krasimirtsonev.data.metadata {
    import flash.utils.getQualifiedClassName;
    import flash.utils.describeType;
    import flash.system.ApplicationDomain;
    public class MetadataParser {
      private static
      var _xml: XML;
      private static
      var _classObj: * ;
      public static
      function init(classObj: * , Analizer: Class): void {
        _xml = describeType(ApplicationDomain.currentDomain.getDefinition(getQualifiedClassName(classObj).replace("::", ".")));
        _classObj = classObj;
        var data: Array = [];
        data = data.concat(parse(_xml.factory.metadata.(@name == "*")));
        data = data.concat(parse(_xml.factory.variable.metadata.(@name == "*")));
        data = data.concat(parse(_xml.factory.method.metadata.(@name == "*")));
        new Analizer(data);
      }
      private static
      function parse(metadata: XMLList): Array {
        var data: Array = [];
        var vo: MetadataVO;
        var metadata: XMLList = metadata;
        var metadataLength: int = metadata.length();
        for (var i: int = 0; i < metadataLength; i++) {
          var args: XMLList = metadata[i].arg;
          var argsLength: int = args.length();
          switch (metadata[i].parent().name().toString()) {
            case "factory":
              vo = new MetadataVO();
              vo.metadataFor = MetadataVO.METADATA_FOR_CLASS;
              vo.xml = _xml;
              vo.classObj = _classObj;
              vo.metadata = {};
              for (var j: int = 0; j < argsLength; j++) {
                vo.metadata[metadata[i].arg[j].@key.toString()] = metadata[i].arg[j].@value.toString();
              }
              data.push(vo);
              break;
            case "variable":
              vo = new MetadataVO();
              vo.metadataFor = MetadataVO.METADATA_FOR_VARIABLE;
              vo.xml = _xml;
              vo.classObj = _classObj;
              vo.metadata = {};
              for (j = 0; j < argsLength; j++) {
                vo.metadata[metadata[i].arg[j].@key.toString()] = metadata[i].arg[j].@value.toString();
              }
              vo.name = metadata[i].parent().@name.toString();
              vo.type = metadata[i].parent().@type.toString();
              data.push(vo);
              break;
            case "method":
              vo = new MetadataVO();
              vo.metadataFor = MetadataVO.METADATA_FOR_VARIABLE;
              vo.xml = _xml;
              vo.classObj = _classObj;
              vo.metadata = {};
              vo.name = metadata[i].parent().@name.toString();
              vo.declaredBy = metadata[i].parent().@declaredBy.toString();
              vo.returnType = metadata[i].parent().@returnType.toString();
              for (j = 0; j < argsLength; j++) {
                vo.metadata[metadata[i].arg[j].@key.toString()] = metadata[i].arg[j].@value.toString();
              }
              var params: Array = [];
              var paramsList: XMLList = metadata[i].parent().parameter;
              var numOfParams: int = paramsList.length();
              for (j = 0; j < numOfParams; j++) {
                params.push({
                  index: metadata[i].parent().parameter[j].@index.toString(),
                  type: metadata[i].parent().parameter[j].@type.toString(),
                  optional: metadata[i].parent().parameter[j].@optional.toString()
                });
              }
              vo.params = params;
              data.push(vo);
              break;
          }
        }
        return data;
      }
    }
  }

As a conclustion I can say that by using the swc component you can parse your custom metadata in flex without adding anything to your compiler's path. The source code of the example is available here.

If you enjoy this post, share it on Twitter, Facebook or LinkedIn.