Flex: Dynamic, bindable value objects

10 10 2008

What a mouthful. Dynamic, bindable value objects.

Well, practically these puppies are pretty useful.

Lemme give you a scenario:

You are writing an Employee database that you wish to use for various Companies. Say that each Company wants custom fields for employees (on top of the essential fields). Sure, you could add another column to the Employee table “CustomFields” which is XML data and put the fields in there. However, my preferred method is to have two extra tables – one a list of available fields for the company, and the other an assignment of values for these custom fields to an employee record.

eg:

Example Database Schema

At this point I will clarify that I use .NET 3.5 and LINQ to pull out this data. That means that my Employee object will automatically be given the Company property and the EmployeeCustomValues[] property. Please see my other articles on Flex and .NET 3.5.

Now, to get this into Flex.

I’m using FluorineFx’s most recent revision (r33) posted on Sep 30 in their Google SVN (at time of writing, the most recent version for DL on the FluorineFx website is v1.0.0.13, which won’t handle dynamic objects for our purposes).

I also tried with WebORB 3.5 but was getting exceptions in sending this data back to .NET and still haven’t heard back from them regarding dynamic value objects.

Getting this data into the Employee.as file isn’t that hard. You write a normal value object, but you make it dynamic and you will need to extend Proxy to make it bindable. See this solution here. (I’ve also read this solution in the Flex 3 Cookbook).

Now the fun part, in Employee.as, I now need to also implement some more methods of Proxy, because I want to use these value objects inside Datagrids. First off I need to get all the properties within this object for enumeration. This means, in the Employee constructor, I’ll need to get all the strong properties and add them to my list. I do this using the describe type utility.

[Bindable(event=“propertyChange”)]

[RemoteClass(alias=“JustinJMoses.Examples.DynamicBindableVO.Employee”)]

dynamic public class Employee extends Proxy implements IEventDispatcher

{

private var _evtDispatcher:EventDispatcher;

private var _properties:Array;

public function Employee():void

{

_evtDispatcher = new EventDispatcher(this);

_properties = new Array();

var classInfo:XML = flash.utils.describeType(this);

for each (var a:XML in classInfo..accessor)

{

addProperty(a.@name.toString());

}

}

….

Then, as I add dynamic properties in the getProperty() and setProperty() I will also append them to my _properties private array. This means that enumerating my object will now yield both my strong properties and my dynamic ones:

//because this class override Proxy, it must implement the iteration over this object’s properties

override flash_proxy function nextNameIndex (index:int):int {

if (index < _properties.length) {

return index + 1;

} else {

return 0;

}

}

//as above

override flash_proxy function nextName(index:int):String {

return _properties[index – 1];

}

override flash_proxy function nextValue(index:int):*

{

return this[_properties[index – 1]];

}

But there’s still a bit more. The callProperty() method is also needed for all these toString() calls that happen around the shop. My implmentation ins’t great I confess, but I found that without it, the rows in my Datagrids weren’t selectable as the UID created for each row is a toString() of the datarow’s item.

//this implementation of toString is required as a datagrid’s list items require it

override flash_proxy function callProperty(name:*, … rest):*

{

if (name.localName == “toString”)

{

var str:String = “”;

for each (var field:String in this)

{

if (field != null)

str += field.toString();

}

return str;

}

}

 

Now, as I mentioned, this dynamic value object – Employee – will only get sent back to .NET successfully in the most recent revision of FluorineFx, but this should be remedied soon.

Remember, in the implementation of get and setProperty() in Employee, you still have to manage the storage of your values into the array collections sent from .NET.

I’ve written out an abstract DynamicProperties class and my Employee solution below.

DynamicProperties.as

[Bindable(event="propertyChange")]
    dynamic public class DynamicProperties extends Proxy implements IEventDispatcher
    {

        private var _evtDispatcher:EventDispatcher;

        private var _properties:Array;

        public function DynamicProperties():void
        {
            _evtDispatcher = new EventDispatcher(this);

            _properties = new Array();

            var classInfo:XML = flash.utils.describeType(this);

             for each (var a:XML in classInfo..accessor)
             {
                addProperty(a.@name.toString());
            } 

            //if you want to listen to property changes
            //this.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onChanged,false,0,true);
        }

        override flash_proxy function getProperty(name:*):*
        {
            return getDynamicProperty(name);

        }

        public function addProperty(name:String):void
        {
            _properties.push(name);
        }

        protected function getDynamicProperty(name:*):*
        {
            //abstract
            throw new Error("Abstract");
        }

        //this override allows us to dispatch a binding event when change
        override flash_proxy function setProperty(name:*, value:*):void
        {
            var oldVal:* = setDynamicProperty(name, value);

            var evt:Event = PropertyChangeEvent.createUpdateEvent( this, name, oldVal, value );

            dispatchEvent( evt );
        }

        /**
        * Abstract.
        *
        * This function will set the property of a dynamic
        */
        protected function setDynamicProperty(name:*, value:*):*
        {
            throw new Error("Abstract");
        }

        //because this class override Proxy, it must implement the iteration over this object's properties
        override flash_proxy function nextNameIndex (index:int):int {

             if (index < _properties.length) {
                 return index + 1;
             } else {
                 return 0;
             }
         }

         //as above
         override flash_proxy function nextName(index:int):String {
             return _properties[index - 1];
         }

         override flash_proxy function nextValue(index:int):*
         {
             return this[_properties[index - 1]];
         }

         //this implementation of toString is required as a datagrid's list items require it
         override flash_proxy function callProperty(name:*, ... rest):*
         {
             if (name.localName == "toString")
             {
                 var str:String = "";

                 for each (var field:String in this)
                 {
                     if (field != null)
                         str += field.toString();
                 }

                 return str;
             }
         }

        // IEventDispatcher implementation.
        public function addEventListener( type:String,
                                        listener:Function,
                                        useCapture:Boolean = false,
                                        priority:int = 0,
                                        useWeakReference:Boolean = false):void
        {
            _evtDispatcher.addEventListener( type, listener, useCapture,
                                               priority, useWeakReference );
        }
        // IEventDispatcher implementation.
        public function removeEventListener( type:String,
                                            listener:Function,
                                            useCapture:Boolean = false ):void
        {

           _evtDispatcher.removeEventListener( type, listener, useCapture );
        }
        // IEventDispatcher implementation.
        public function dispatchEvent( evt:Event ):Boolean
        {
            return _evtDispatcher.dispatchEvent( evt );
        }
        // IEventDispatcher implementation.
        public function hasEventListener( type:String ):Boolean
        {
            return _evtDispatcher.hasEventListener( type );
        }
        // IEventDispatcher implementation.
        public function willTrigger( type:String ):Boolean
        {
            return _evtDispatcher.willTrigger( type );
        }

    }

And my final Employee.as file

[Bindable]
    [RemoteClass(alias="JustinJMoses.Examples.DynamicBindableVO.Employee")]
    dynamic public class Employee extends DynamicProperties implements IValueObject
    {

        public var EmployeeID:int = 0;

        public var CompanyID:int = 0;

        /**
         * These are the two implementations of the dynamic properties class.
         *
         * They allow dynamic properties to be getted/setted via some implementation.
         */

        override protected function getDynamicProperty(name:*):*
        {
            for each (var value:EmployeeCustomValue in this.EmployeeCustomValues)
            {
                if (value.EmployeeCustomField.FieldName == name.localName)
                {
                    return value.FieldValue;
                }
            }

            return null;
        }

        override protected function setDynamicProperty(name:*, value:*):*
        {
            var oldVal:*; 

            for each (var fieldValue:EmployeeCustomValue in this.EmployeeCustomValues)
            {
                if (fieldValue.EmployeeCustomField.FieldName == name.localName)
                {
                    oldVal = fieldValue.FieldValue;
                    fieldValue.FieldValue = value;

                    return oldVal;
                }
            }

            //can't find this value for the object, so find the field object and create a new associated value
            var allFields:ICollectionView = MyModelLocator.getInstance().dynamicEmployeeFields;

            if (allFields.length < 1)
            {
                throw new MyError(MyError.EMPLOYEE_CUSTOM_FIELDS_NOT_LOADED);
            }

            //search through available field names
            for each (var field:EmployeeCustomField in allFields)
            {
                if (field.FieldName == name.localName)
                {
                    var employeeCustomValue:EmployeeCustomValue = new  EmployeeCustomValue();
                    employeeCustomValue.Employee = this;
                    employeeCustomValue.EmployeeCustomField = field;
                    employeeCustomValue.FieldValue = value;
                    this.EmployeeCustomValues.addItem(employeeCustomValue);
                    return null;
                }

            }

            throw new MyError(MyError.NO_EMPLOYEE_FIELD);

        }

        public function Employee():void
        {
            EmployeeCustomValues = new ArrayCollection();
        }

        private var _employeeCustomValues:ArrayCollection;

        public function get EmployeeCustomValues():ArrayCollection
        {
            return _employeeCustomValues;
        }

        public function set EmployeeCustomValues(val:ArrayCollection):void
        {
            _employeeCustomValues = val;

            for each (var field:EmployeeCustomValue in EmployeeCustomValues)
            {
                //set the property (so it shows up)
                this[field.EmployeeCustomField.FieldName] = field.FieldValue;

                //add the property to the internal list
                this.addProperty(field.EmployeeCustomField.FieldName);
            }

        }

    }
Advertisements

Actions

Information

One response

4 03 2010
Laurence

One thing I’m not quite understanding yet… How do you refer to your dynamically-created fields programmatically? For example, in a normal VO, I simply assign a variable to be of that type (var employeeInfo:EmployeeVO) then I can access employeeInfo.GivenNames, employeeInfo.Surname, etc…

But how do you tell the data grid what fields to use in which columns, when you don’t know beforehand what those fields are going to be called? Or even how many fields there will be?

Further, is there a way to determine the type of each field as it is stored in the database? I don’t want to be writing strings to an int field, for example.

Thanks for any clarification you can provide.
Laurence MacNeill,
Mableton, Georgia, USA

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: