MCVC Model - Part 1

December 26th, 2007

The core part of my MCVC system is the BaseModel class. In this system it represents a core data class with no visual representation. All data classes are extended from this class and are responsible for loading xml data, parsing that data, managing the data/state of the supporting classes (such as Views and Components) and handling the receive/sending of events through the controller binding. Here is how it works. ** Warning but this post is long and is a continuation from this post on MCVC. **

First things first, here is the source code.

import com.jessefreeman.mcvc.v1.BaseController;
import com.jessefreeman.mcvc.v1.interfaces.Model;

/**
 * @author Jesse Freeman
 */

class com.jessefreeman.mcvc.v1.BaseModel implements Model {
       
        // Objects
        private var _oClass_path:BaseModel;// Defines the scope of the Model’s instance. Use _oClass_path instead of ‘this’ to refrence the Model.
       
        // Strings
        private var _sModel_name:String = “base_model”;
       
        // Booleans
        private var _bUsers_controller:Boolean = true;

        private var _aTimers : Array;
       
        /**
         * Returns the name of the model
         * @return name: String representing the name of the Model’s instance
         */

        public function get name():String{
                return _sModel_name;   
        }
       
        /**
         * Returns instance of the Model.
         * @return class_path: Refrence to the Model’s scope.
         */

        public function get class_path():Object{
                return  _oClass_path;
        }
       
        /**
         * Constructor for the BaseModel. It sets up the class_path reference, looks to see if a custom model name was supplied, then passes off all init data to the pre_init method.
         * @param init: Object with model’s startup data.
         */

        public function BaseModel(init:Object) {
                _oClass_path = this;
               
                // Handle custom model name
                if(init.model_name)
                        _sModel_name = init.model_name;
               
                // Pre Init to configure model
                pre_init(init);
        }
       
        /**
         * The pre_init method contains the bulk of the models configuration. Only override this method if you need the model to do specific things outside of its base startup.
         * @param
         */

        private function pre_init(init_data:Object):Void{
               
                _aTimers = new Array();
                // Controler
                create_controller();
               
                // Init Model
                init(init_data);
        }
       
        /**
         * Default init function for all Models, use this to add custom model activation code.
         * @param oInit: Object with ini data.
         */

        public function init(init_data : Object) : Void {
        }
       
       
        /**
         * Creates Controller that will be linked to this Model
         */

        public function create_controller() : Void {
                if(_bUsers_controller)
                        BaseController.model_to_control(_sModel_name,this);
        }

        /**
         * Use this to send data out to external flash applications or other webapps
         */

        public function submit_data(oData : Object) : Void {
        }
       
        /**
         * Used by the Controller to send events back to the Model
         * @param event: Object that contains event data.
         * @example
         *           // Inside instance sending event
         *           var oEvent:Object = {type:’trace’,message:’Put Any Data Here’}
         *           BaseController.new_event(oEvent);
         *           
         *           // This goes inside the model/component’s receive event method
         *           public function receive_event(event : Object) : Void {
         *                var sType:String = event.type;
         *                switch (sType) {
         *                     case "trace" :
         *                          trace(event.message);
         *                     break;
         *                }
         *           }
         */

        public function receive_event(event : Object) : Void {
                var sType:String = event.type;
                switch (sType) {
                        case “event” :
                                //do something
                        break;
                }
        }
       
        /**
         * Standard load_xml method. Use this for a quick way to load xml then send to a custom parser.
         * @param xml_url: Path to xml file.
         * @param parser_name: String preface of the parser method. It defaults to the ‘base_parse_xml’ method.
         */

        public function load_xml(xml_url:String,parser_name:String):Void{
                load_xml_broadcast(xml_url);
                var xmlData:XML = new XML();
                xmlData.ignoreWhite = true;
                var oClass:Object = this;
                // Check for parser_name
                if (!parser_name)
                        parser_name = “base”;
                // On load actions - send xml data to parser
                xmlData.onLoad = function(success:Boolean):Void{
                        if (success)
                                oClass[parser_name+‘_parse_xml’](xmlData);
                        else
                                trace(“ERROR: Could not load xml file at ‘”+xml_url+“‘”);
                       
                };
                // Load XML data
                xmlData.load(xml_url);
        }
       
        /**
         * Use this to update a preloader of data loading status or trigger an event in the Model.
         */

        private function load_xml_broadcast(xml_url:String):Void{
                trace(“ALERT: Now loading ‘”+xml_url+“‘”);     
        }
       
        /**
         * Default XML parser for model. Model parse_xml functions follow this naming convion [custom_name]_parse_xml(xml_data:XML)
         * When using the naming convention and the load_xml method it is possible to set up several parsers for any model.
         * @param xml_data: XML automatically supplied buy the load_xml method.
         */

        private function base_parse_xml(xml_data:XML):Void{
                trace(“Ready to parse xml data:”+newline+xml_data);
        }
       
        /**
         * Convert simple xml nodes into an object. This is not recursive and does not take into account any attributes a node may have, or its children.
         * Use this simply as a quick way to conver a set of nodes into and property/value pair.
         * @param xmlData: xml node with children nodes to be converted into an object.
         * @example:
         *
         * xml
         *      <data>
         *           <value_1>Data A</value_1>
         *           <value_2>Data B</value_2>
         *      </data>
         *      
         * Object Returned
         *      data
         *           .value_1 = ‘Data A’
         *           .value_2 = ‘Data B’;
         *      
         */

        public function clean_up_nodes(xmlData:XMLNode):Object{
                var oData:Object = new Object();
                var nTotal:Number = xmlData.childNodes.length;
                for (var i : Number = 0; i < nTotal; i++) {
                        var xmlNode:XMLNode = xmlData.childNodes[i];
                        oData[xmlNode.nodeName] = xmlNode.firstChild.nodeValue;
                }
                return oData;
        }
       
        /**
         * This will go through a nodes attributes and put them into an object.
         */

        public function clean_up_attributes(xmlNode:XMLNode):Object{
                var oData:Object = new Object();
               
                for (var attribute : String in xmlNode.attributes) {
                        oData[attribute] = xmlNode.attributes[attribute];
                }

                return oData;
        }
       
        /**
         * Convert simple xml nodes into an correctly typed objects.
         * @param xmlData: xml node with children nodes to be converted into an object.
         * @example:
         *
         * xml
         *      <data>
         *           <item type="number">5<data>
         *      </data>
         *      
         */

        public function clean_up_typed_nodes(xmlData:XMLNode):Object{
                var oData:Object = new Object();
                var nTotal:Number = xmlData.childNodes.length;
                for (var i : Number = 0; i < nTotal; i++) {
                        var xmlNode:XMLNode = xmlData.childNodes[i];
                        var sData:String = xmlNode.firstChild.nodeValue;
                        var sType:String = xmlNode.attributes[‘type’];
                        if(sType == “number”){
                                oData[xmlNode.nodeName] = Number(sData);
                        }else if (sType == “boolean”){
                                var bResults:Boolean;
                               
                                if (sData == “true”){
                                        bResults = true;
                                }else{
                                        bResults = false;
                                }
                               
                                oData[xmlNode.nodeName] = Boolean(bResults);
                               
                        }else{
                                oData[xmlNode.nodeName] = sData;
                        }
                }
                return oData;
        }
       
        /**
         * @Depricated
         */

        
        public function clean_up_typed_node(xmlData:XMLNode):Object{
                return clean_up_typed_nodes(xmlData);
        }
        
        
        private function start_timer(id:String,scope:Object,function_name:String,time:Number,data:Object):Number{
               
                if (time > 0){
                        var nTimer_id:Number = setInterval(scope,function_name,time,data);
                       
                        _aTimers[id] = nTimer_id;
                       
                        return nTimer_id;
                       
                }else{
                       
                        return null;   
               
                }
        }
       
        private function set_timeout(id:String,scope:Object,function_name:String,time:Number,data:Object):Boolean{
               
                if (time > 0){
               
                        _global[’setTimeout’](scope,function_name,time,data);
                       
                        return true;
               
                }else{
               
                        return false;   
               
                }
               
        }
       
        private function stop_timer(id:String):Void{
                clearInterval(_aTimers[id]);
                delete _aTimers[id];
        }
       
}

As you can see this class implements a model Interface. Here is what that looks like.

/**
 * @author Jesse Freeman
 */

interface com.jessefreeman.mcvc.v1.interfaces.Model {

        public function init(init_data:Object):Void;

        public function create_controller():Void;

        public function submit_data(data : Object) : Void;
       
        public function receive_event(event:Object):Void;
       
        public function load_xml(xml_url:String,parser_name:String):Void;
       
}

Right now you are probably like WTF? Well there is a lot to take in. Lets start with the model’s name:

private var _sModel_name:String = “base_model”;

This variable is key in how the Model binds itself to the controller. Each model is unique and should have it’s own name that you will need to remember later when you need to send the model’s instance an event.

Now you may be thinking, why not make the model a singleton since there can only be one of each type? Well I tried that but found that when you extend off of a singleton class, each of the extended classes share the same variables and things get messed up. In this system all of your models can still retain their uniqueness without directly sharing anything between the BaseModel class. I actually adopted this system from cakePHP and Ruby on Rails. Each model extends a BaseModel but is uniquely named class file.

Right, so each Model needs it’s own unique name. The constructor and pre_init methods begin the process of starting up the instance and making things happen. When you create a new BaseModel instance it looks for an init property of the type Object. This is where you can pass in configuration values.

/**
         * Constructor for the BaseModel. It sets up the class_path reference, looks to see if a custom model name was supplied, then passes off all init data to the pre_init method.
         * @param init: Object with model’s startup data.
         */

        public function BaseModel(init:Object) {
                _oClass_path = this;
               
                // Handle custom model name
                if(init.model_name)
                        _sModel_name = init.model_name;
               
                // Pre Init to configure model
                pre_init(init);
        }
       
        /**
         * The pre_init method contains the bulk of the models configuration. Only override this method if you need the model to do specific things outside of its base startup.
         * @param
         */

        private function pre_init(init_data:Object):Void{
               
                _aTimers = new Array();
                // Controler
                create_controller();
               
                // Init Model
                init(init_data);
        }

The constructor looks for a property “model_name” on the init Object. If it is found, it overrides the default _sModel_name’s value. After the constructor is done, it passes off the init object to the pre_init and then onto the init. Why are there two inits?

When you extend off of this class you should never override the pre_init method. This handles setting up any timers the model may need (Used for auto refreshing of data and general recursive procedures) as well as create the controller -> model binding. In hind site I would now put the pre_init inside of the init, and have the extended class’s init call super.init() before doing any custom actions. Just something to think about.

I am going to jump to the controller logic to help explain how the binding takes place. I am only going to go over the basics of how it works from the model’s stand point because I will get into more detail when I post about the controller.

/**
         * Creates Controller that will be linked to this Model
         */

        public function create_controller() : Void {
                if(_bUsers_controller)
                        BaseController.model_to_control(_sModel_name,this);
        }

The create_controller() method is really basic. It simply tells the Static BaseController class to control the model by its id. You see that it is passing in two values, one is the models’ name and the other is the model’s scope. This does two things on the controller’s side. First it adds the name and scope into an associate array for easy look up later. Next it allows any other model, view, or component to send the model instance an event by its name. That is why the name has to be unique or this system wouldn’t work.

What happens when you have more then one instance of the same Model class? Easy, you can pass in a unique name through the constructor and the init Object. So if you are creating model instances in a loop simply pass in the loop’s id along with the name and you can keep the models from interfering with each other inside of the controller.

Now lets jump back to the empty init function before getting into the data event system. This empty init is set up so the extended class can do some custom actions when it initializes. It really straight forward.

So that covers the basic beginning of this class and how everything gets started. Next up is the Event system, loading and parsing xml Data, and timer creation. Read part 2 here.

One Response to “MCVC Model - Part 1”

  1. The Flash Art of War » Blog Archive » Are Singletons Bad? Says:

    [...] a truly oop system, I just did what was needed to get the project out the door. In fact, my own MCVC framework made use of a Singleton like Controller class accessible from all Models and Views in the system. [...]

Leave a Reply