API Docs for: 2.1.2
Show:

File: src\model.js

/**
 * Model js - A simple javaScript library for creating the Model part of a MVC application.
 * https://github.com/dgeorges/modeljs.git
 *
 * Copyright 2012, Daniel Georges
 * modeljs is distributed freely under a MIT license
 *
 * @project modeljs
 * @author Daniel Georges
 * @version 2.1.2
 * @module Model
 */
(function (globalNS, undefined) { //globalNS === window in the browser or GLOBAL in nodejs
    "use strict";

    // copied from underscorejs
    function isFunction(fn) {
        return !!fn && Object.prototype.toString.call(fn) === '[object Function]';
    }

    function isObject(obj) {
        return !!obj && Object.prototype.toString.call(obj) === '[object Object]';
    }

    function arrays_equal(a,b) {
        return !(a<b || b<a);
    }

    function extend(destination, source) {
        for (var property in source) {
            destination[property] = source[property];
        }
        return destination;
    }

    function isEmptyObject(obj) {
        if (Object.getOwnPropertyNames) { // only exits on ECMAScript 5 compatible browsers
            return (Object.getOwnPropertyNames(obj).length === 0);
        } else {
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    return false;
                }
            }
            return true;
        }
    }

    function isValidDate(d) {
        if (Object.prototype.toString.call(d) !== "[object Date]") {
            return false;
        }
        return !isNaN(d.getTime());
    }

    function log(level, message) {
        //Only log when enabled and console log method is available
        if (Model.enableLogging && typeof globalNS.console === 'object' && globalNS.console[level]) {
            globalNS.console[level](message);
        }
    }

    var getXHRObject = (function () {
        if (globalNS.XMLHttpRequest) { // Mozilla, Safari, ...
            return function () {
                return new XMLHttpRequest();
            };
        } else if (globalNS.ActiveXObject) { // IE
            try {
                new ActiveXObject("Msxml2.XMLHTTP");
                return function () {
                    return new ActiveXObject("Msxml2.XMLHTTP");
                };
            } catch (e1) {
                try {
                    new ActiveXObject("Microsoft.XMLHTTP");
                    return function () {
                        return new ActiveXObject("Microsoft.XMLHTTP");
                    };
                } catch (e2) {
                    //do nothing
                }
            }
        }

        log('error', "Could not create an XMLHTTPRequestObject Remote Model requests will fail");
        return undefined;
    }());

    function makeJSONPRequest(url, id) {
        var scriptTag = document.createElement("SCRIPT");
        scriptTag.id = id;
        scriptTag.type = 'text/javascript';
        scriptTag.src = url;
        document.getElementsByTagName('head')[0].appendChild(scriptTag);
    }

    var callbackId = 0;
    function generateJSONPCallback(property) {
        var fnName = "modeljsJSONPCallback" + callbackId++;
        window[fnName] = function (property, json) { //create global callback
            property.setValue(json);
            var scriptElement = document.getElementById(fnName);
            document.getElementsByTagName('head')[0].removeChild(scriptElement); //remove callback script
            try {
                delete window[fnName]; // remove global callback method
            } catch (e) {
                // this seems to throw and exception in IE8 so we will release it get garbage collected.
                window[fnName] = undefined;
            }
        }.bind(null, property);
        return fnName;
    }

    function nodeHttpCallback(response) {
        var data = "",
            property = this; //This is the property making the request

        //another chunk of data has been received, so append it to `data`
        response.on('data', function (chunk) {
            data += chunk;
        });

        //the whole response has been received. Lets set it into the property.
        response.on('end', function () {
            property.setValue(JSON.parse(data));
        });
    }

    function makeRemoteRequest(property) {
        var url = property.getMetadata().url;
        if (property.getMetadata().isJSONPurl) { // JSONP request
            var uniqueCallbackId = generateJSONPCallback(property);
            url = url.replace("$jsonpCallback", uniqueCallbackId);
            makeJSONPRequest(url, uniqueCallbackId);
        } else {
            if (typeof window !== 'undefined') { // browser ajax request
                var httpRequest = getXHRObject();
                httpRequest.onreadystatechange = retrieveRemoteRequest.bind(null, httpRequest, property);
                httpRequest.open('GET', url);
                httpRequest.send();
            } else { // We are not in the browser so this must be node.
                try {
                    var parsedURL = require('url').parse(url);
                    var http = require('http');

                    http.request({host: parsedURL.host, path: parsedURL.path},
                        nodeHttpCallback.bind(property)).end();
                } catch (e) {
                    log('error', "attempt to make remote request using require('http') object failed");
                }
            }
        }
    }

    function retrieveRemoteRequest(xhr, property, xhrProgressEvent) {
        if (xhr.readyState === 4) {

            if (xhr.status !== 200) {
                log('warn', "Remote request for " + property.getName() + " failed due to return status of " + xhr.status);
                if (property.getMetadata().refreshRate === -1) {
                    log('warn', "Retrying remote request for " + property.getName() + " in 2 seconds");
                    setTimeout(makeRemoteRequest.bind(null, property), 2000);// try again in 2 sec
                }
                return;
            }

            if (xhr.responseType !== "json" && xhr.responseType !== "") {
                log('error', "Remote model (" + property.getName() + ") must return JSON. Not retrying.");
                return;
            }

            //look into ArrayBuffer
            var jsonResponse = {};
            try {
                jsonResponse = JSON.parse(xhr.response);
            } catch (e) {
                log('error', "Unable to parse remote Model request for " + property.getName());
                return; //should retry? makeRemoteRequest(property);
            }

            //use response header Last-Modified time stamp to determine if we should call setValue
            var responseLastModifiedDate = xhr.getResponseHeader("Last-Modified") && new Date(xhr.getResponseHeader("Last-Modified"));
            if (responseLastModifiedDate && isValidDate(responseLastModifiedDate)) {
                var metadata = property.getMetadata();
                var propertyLastModified = metadata.lastModified && new Date(metadata.lastModified);
                if (!propertyLastModified || !isValidDate(propertyLastModified) || // my last Modified date isn't valid
                        Date.parse(responseLastModifiedDate) > Date.parse(propertyLastModified)) { //  or it is and it's stale
                    property.setValue(jsonResponse);
                    metadata.lastModified = responseLastModifiedDate;
                } else {
                    // fetch data hasn't changed.
                }
            } else { // no last Modified date in response header, always setValue
                property.setValue(jsonResponse);
            }
        }
    }

    /*
     * Centralized place where all Model Events pass through.
     */
    var eventProxy = (function () {
        var eventQueue = [],
            state = {
                ACTIVE: "active",
                TRANSACTION: "transaction"
            },
            currentState = state.ACTIVE;

        var executedCallbacks = [];
        var callbackHashs = [];

        function _fireEvent(eventName, property, eventArgs) {

            // This weird executeCallback function is a bit more complicated than it needs to be but is
            // used to get around the JSLint warning of creating a function within the while loop below
            var executeCallbacksFunction = function (thisProperty, callbackArgs) {
                return function (callback) {

                    function notifyListener() {
                        if (Model.asyncEvents) {
                            setTimeout(function () {
                                callback.apply(thisProperty, callbackArgs);
                            }, 0);
                        } else {
                            callback.apply(thisProperty, callbackArgs);
                        }
                    }

                    if (Model.TRANSACTION_OPTIONS.flattenCallbacks || Model.TRANSACTION_OPTIONS.flattenCallbacksByHash) {
                        var callbackExecuted = false;
                        if (Model.TRANSACTION_OPTIONS.flattenCallbacks) {
                            if (executedCallbacks.indexOf(callback) === -1) { // Only call callback once
                                executedCallbacks.push(callback);
                                notifyListener();
                                callbackExecuted = true;
                            }
                        }
                        if (Model.TRANSACTION_OPTIONS.flattenCallbacksByHash) {
                            if (!callback.hash || callbackHashs.indexOf(callback.hash) === -1) { // Only call hash identified callback once
                                if (callback.hash) {
                                    callbackHashs.push(callback.hash);
                                }
                                if (!callbackExecuted) {
                                    notifyListener();
                                    callbackExecuted = true;
                                }
                            }
                        }
                    } else {
                        notifyListener();
                    }
                };
            };

            var callbackArgs = [property].concat(eventArgs);
            var eventListeners = property._eventListeners[eventName] ? property._eventListeners[eventName].slice(0) : [];
            if (eventName === Model.Event.PROPERTY_CHANGE) {
                Array.prototype.push.apply(eventListeners, property._eventListeners.change);
            } else if (eventName === Model.Event.MODEL_CHANGE) {
                Array.prototype.push.apply(eventListeners, property._eventListeners.change);
            } else if (eventName === Model.Event.CHANGE) {
                Array.prototype.push.apply(eventListeners, property._eventListeners.propertyChange);
            }

            // update listeners registered for the event
            eventListeners.forEach(
                executeCallbacksFunction(property, callbackArgs)
            );
            property._eventListeners.all.forEach(
                executeCallbacksFunction(property, callbackArgs.concat(eventName))
            );

            //MODEL_CHANGE only propagates from the following events
            if (eventName === Model.Event.CHANGE || eventName === Model.Event.MODEL_CHANGE || eventName === Model.Event.PROPERTY_CHANGE ||
                eventName === Model.Event.CHILD_CREATED || eventName === Model.Event.CHILD_DESTROYED) {
                // propagate change up the stack for the following events by notifying all parent ModelChange listers registered.
                var propertyParent = property._parent;
                while (propertyParent) {
                    var parentListeners = propertyParent._eventListeners.modelChange.concat(propertyParent._eventListeners.change);
                    parentListeners.forEach( // when we bubble the event we only notify modelListeners
                        executeCallbacksFunction(propertyParent, callbackArgs)
                    );
                    propertyParent = propertyParent._parent;
                }
            }
        }

        function fireEvent(eventName, property, customArgs) {
            if (currentState === state.ACTIVE) { // fire event now.
                _fireEvent(eventName, property, customArgs);
            } else { //place event on queue to be called at a later time.
                eventQueue.push({
                    eventName: eventName,
                    property: property,
                    customArg: customArgs
                });
            }
        }

        function flushEventQueue() {

            executedCallbacks = []; //reset state
            callbackHashs = [];
            if (Model.TRANSACTION_OPTIONS.suppressAllEvents) {
                //discard all events
                eventQueue = [];
            } else if (Model.TRANSACTION_OPTIONS.fireOnlyMostRecentPropertyEvent) {
                var optimizedQueue = [];
                var seenProperties = [];
                for (var i = eventQueue.length - 1; i >= 0; i -= 1) { // iterate backwards since last events are most recent.
                    var eventProperty = eventQueue[i].property;
                    if (seenProperties.indexOf(eventProperty) === -1) {
                        // Not seen yet add it.
                        seenProperties.push(eventProperty);
                        optimizedQueue.push(eventQueue[i]);
                    } else {
                        //eventQueue[i] = null; // null out event since it's propertyChange is on the Queue Already
                    }
                }
                eventQueue = optimizedQueue;
            }

            eventQueue.forEach(function (event) {
                _fireEvent(event.eventName, event.property, event.customArg);
            });
            eventQueue = []; //Queue has been flushed
        }

        function changeState(newState) {
            if (state[newState] !== currentState) {
                currentState = newState;
                if (newState === state.ACTIVE) {
                    flushEventQueue();
                }
            }
        }

        return {
            fireEvent: fireEvent,
            startTransaction: changeState.bind(null, state.TRANSACTION),
            endTransaction: changeState.bind(null, state.ACTIVE),
            inTransaction: function () {
                return currentState === state.TRANSACTION;
            }
        };
    }());

    /*
     * An Observable Array is a wrapper around the javaScript Array primitive which will
     * trigger the correct events when any of it mutator methods are called. It is not
     * exposed outside of this file.
     */
    function ObservableArray(myProperty, values) {
        for (var i =0; i < values.length; i++){
            this.push(values[i]);
        }
    }
    ObservableArray.prototype = Object.create(Array.prototype);

    ObservableArray.prototype.pop = function () {
        var args = Array.prototype.slice.call(arguments),
            element = Array.prototype.pop.apply(this, args);
        element.destroy();
        return element;
    };
    ObservableArray.prototype.push = function () {
        var args = Array.prototype.slice.call(arguments),
            length = this.length,
            pushedArgs = [],
            property,
            i = 0;
        for (i = 0; i < args.length; i++) {
            if ((args[i] instanceof Property) || Model.isArray(args[i])) {
                log('error', "Incorrect Syntax: use push([property].getValue()) instead");
                return;
            }
            property = _createProperty(length + i, args[i], this, {});
            pushedArgs.push(property);
        }
        var newLength = Array.prototype.push.apply(this, pushedArgs);
        for (i = 0; i < pushedArgs.length; i++) {
            this.trigger(Model.Event.CHILD_CREATED, pushedArgs[i]);
        }
        return newLength;
    };
    ObservableArray.prototype.reverse = function () {
        var args = Array.prototype.slice.call(arguments),
            newValue = Array.prototype.reverse.apply(this.getValue(), args);
        this.setValue(newValue);
        return this;
    };
    ObservableArray.prototype.shift = function () { // removes first element
        var arrayValue = this.getValue(),
            removed = arrayValue.shift(),
            removedProperty = this[this.length - 1];

        this.setValue(arrayValue); // set value will set all the values correctly and delete the last property
        removedProperty.setValue(removed);
        return removedProperty;
    };
    ObservableArray.prototype.sort = function () {
        var args = Array.prototype.slice.call(arguments),
            result = Array.prototype.sort.apply(this.getValue(), args);
        this.setValue(result);
        return this;
    };
    ObservableArray.prototype.splice = function () {
        var args = Array.prototype.slice.call(arguments),
            arrayValue = this.getValue(),
            returnValue = Array.prototype.splice.apply(arrayValue, args);

        this.setValue(arrayValue);
        return returnValue; // this is an array of primitives not Property Objects
    };
    ObservableArray.prototype.unshift = function () {
        var args = Array.prototype.slice.call(arguments),
            currentValue = this.getValue(),
            returnValue = Array.prototype.unshift.apply(currentValue, args);

        this.setValue(currentValue);
        return returnValue;
    };

    /**
     * A Property is a name value pair belonging to a Model.
     *
     * @class Property
     * @constructor
     * @private used internally by the Model.prototype.createProperty method.
     *
     * @param {[String]} name    The name of the property
     * @param {[String, Boolean, Number, null, Date, Function, Object]} value   The Property Value
     * @param {[Model]} parent  The parent property
     * @param {[Object]} metadata The metadata associated with the property. You can put any metadata you want. However the following keys have special meaning and are reserved for use by the framework.
     *                         validator - a function to validate if the new value is valid before it is assigned.
     *                         url - the resource this model should use to get it's value. Resource must return json. *Must be used with refreshRate*
     *                         refreshRate - the interval used to query the url for changes. must be > 0. minimal value used is 100. -1 indicates to only fetch value once. *Must be used with url*
     */
    function Property (name, value, parent, metadata) {

        var myName = "/" + name;
        if (parent) {
            myName = parent.getName() + myName;
        }

        Object.defineProperty(this, "_name", {
            value: myName,
            enumerable: false
        });

        Object.defineProperty(this, "_parent", {
            value: parent,
            enumerable: false,
            writable: false,
            configurable: true //set to configurable so we can delete it in destroy
        });

        Object.defineProperty(this, "_metadata", {
            value: metadata || {},
            enumerable: false
        });

        Object.defineProperty(this, "_eventListeners", {
            value: { //map of eventName to listener array. The following are modeljs Events
                propertyChange: [],
                modelChange: [],
                change: [],
                childCreated: [],
                childDestroyed: [],
                destroy: [],
                all: []
            },
            enumerable: false,
            writable: false,
            configurable: false
        });

        var myValue = value;
        if (isFunction(myValue)) {
            myValue = myValue.bind(parent);
        }
        //make sure value is valid
        if (!this.validateValue(myValue)) {
            myValue = undefined;
        }

        Object.defineProperty(this, "_myValue", {
            value: myValue,
            enumerable: false,
            writable: true
        });
    }

    //Override Default Object.prototype.valueOf so Property behave like underlying primitive when primitive value is expected.
    // eg if we had aProperty with value 1 and name numberOne. NumberOne++ would return 2.
    Property.prototype.valueOf = function () {
        return this.getValue();
    };

    /**
     * Gets the value of the property.
     *
     * @method  getValue
     *
     * @return {[String, Boolean, Number, null, Date, Function]} The value of the property
     */
    Property.prototype.getValue = function () {
        return this._myValue;
    };

    Property.prototype.toJSON = function () {
        if (this.getMetadata().doNotPersistValue || this.getMetadata().doNotPersist) {
            return undefined;
        }
        return this.getValue();
    };

    /**
     * Return the formatted value calculated by passing this.getValue() to the this.getMetadata().Formatter function
     * if it exists. If the metadata Formatter does not exist it will fall back to the global Formatter located at
     * Model.Formatter. If that does not exist it will return this.getValue();
     *
     * @method getFormattedValue
     *
     * @return {any} The formatted Value
     */
    Property.prototype.getFormattedValue = function () {
        if (isFunction(this.getMetadata().Formatter)) {
            return this.getMetadata().Formatter(this.getValue());
        } else if (isFunction(Model.Formatter)) {
            return Model.Formatter.call(this, this.getValue());
        }
        // default
        return this.getValue();
    };

    /**
     * The fully qualified name of this. The name is calculated by concatenating the name
     * of the parent, "/", and name of this AKA the shortName. To create a named root pass in the name option key
     * to the Model Constructor.
     *
     * @example
     *     defaultModel.getName();              // returns "/root"
     *     defaultModel.property1.getName();    // returns "/root/property1"
     *     namedRoot.property1.getName();       // returns "/customName/property1"
     * For more examples see:  <b>testGetNameMethod</b>
     *
     * @method  getName
     *
     * @return {String} The fully qualified name of this.
     */
    Property.prototype.getName = function (shortName) {
        if (shortName) {
            return this.getShortName();
        }
        return this._name;
    };

    /**
     * The given name of the property.
     *
     * @method  getShortName
     *
     * @return {[String]} The given name of the property.
     */
    Property.prototype.getShortName = function () {
        return this._name.substring(this._name.lastIndexOf("/") + 1);
    };


    /**
     * Called to set the value of a property. If the setValue is the same as the current value,
     * nothing will happen and no change events will be fired. If the value is different it must pass
     * the validator if there is one.  If it does pass the validator and the value is changed, all registered
     * listeners will be notified unless the suppressNotifications option indicates otherwise.
     *
     * @example
     * For more examples see:  <b>testPrimitiveSetGet</b>, <b>testComplexChangePropertyValue</b> and <b>testSuppressNotifications</b>
     *
     * @method setValue
     * @for Property
     *
     * @param  {[String, Boolean, Number, null, Date, Function, Object]} newValue The Value you want to assign to the Property.
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of change.
     *
     * @return {[this]} this for method chaining.
     */
    Property.prototype.setValue = function (value, suppressNotifications) {
        var newValue = value;
        // Note: this disallows setting a property to undefined. Only when it's first created can it be undefined.
        if (newValue !== undefined && newValue !== this._myValue) {

            if (this.validateValue(newValue)) {
                var oldValue = this._myValue;

                this._myValue = newValue;

                if (!suppressNotifications) {
                    this.trigger(Model.Event.PROPERTY_CHANGE, oldValue);
                }
            }
        }
        return this;
    };

    /**
     * Registers a callback function with the change event of this.  When the callback is executed it
     * will have it's 'this' context bound to this (ie. the property listening to the event). The first argument
     * will be the property that triggered the event. The final argument be the oldValue before it
     * was changed.
     *
     * @example
     *     model.onChange(callback, {listenToChildren: true}); //listens to change events on entire model
     *     model.property1.onChange(callback) //listen to change on property1 only
     *     model.subModel.onChange(callback) //listen to change on subModel only. (ie. via model.subModel.setValue(..))
     * For more examples see:  <b>testOnChangeCallbackWhenSettingToSameValue</b> and <b>testBubbleUpEvents</b>
     *
     * @method  onChange
     *
     * @param {Function} callback The function to be called if the value of this changes. The callback function will be passed the following arguments (oldValue, newValue, propertyName)
     * @param {Boolean}   listenToChildren? Registers the callback with sub property changes as well.
     */
    Property.prototype.onChange = function (callback, listenToChildren) {
        if (!isFunction(callback)) {
            log('warn', "Incorrect Syntax: callback must be a function");
            return;
        }
        if (listenToChildren) {
            this._eventListeners.change.push(callback);
        } else {
            this._eventListeners.propertyChange.push(callback);
        }
        return this;
    };

    /**
     * Removes the property and its children if any from the Model. This will fire the 'destroy' event on this and
     * the 'childDestroyed' event on the parent.
     *
     * @method  destroy
     *
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of destroy.
     * @return {Property}   The deleted Property.
     */
    Property.prototype.destroy = function (suppressNotifications) {
        delete this._parent[this.getShortName()]; // remove forward reference

        if (!suppressNotifications) {
            this.trigger(Model.Event.DESTROY); //equivalent since no event arg.
            this._parent.trigger(Model.Event.CHILD_DESTROYED, this);
        }
        // destroy _parent and eventListeners which makes the Property useless if someone is holding a reference.
        // By deleting _parent it also helps the gc because properties are no longer connected, just in case someone is holding to one.
        // We can only do this when were are not in a transaction since transaction will relay on the properties when it is ended
        if (!Model.inTransaction()) {
            //remove all listeners
            for (var eventListeners in this._eventListeners) {
                eventListeners = [];
            }
            delete this._parent;
        }
        return this;
    };

    Property.prototype.onDestroy = function (callback) {
        return this.on(Model.Event.DESTROY, callback);
    };

    Property.prototype.getRoot = function () {
        var ancestor = this._parent;
        while (ancestor._parent !== null) {
            ancestor = ancestor._parent;
        }
        return ancestor;
    };

    // is this really needed?
    Property.prototype.getParent = function () {
        return this._parent;
    };

    /**
     * Triggers the given event on this. Passing the optional argument.
     *
     * @method  trigger
     *
     * @param  {String} eventName The name of the event.
     * @param  {Any} ...eventArgs? Any number of additional parameters to pass to the registered event callback
     * @return {Property}           Returns this for Object chaining.
     */
    Property.prototype.trigger = function (eventName /*, ...eventArgs */) {
        //Should this restrict custom types
        var eventArgs = Array.prototype.slice.call(arguments, 1);
        eventProxy.fireEvent(eventName, this, eventArgs);
        return this;
    };

    /**
     * Registers the given callback with the given events on this. When the callback is executed it
     * will have it's 'this' context bound to this (ie. the property listening to the event). The first argument
     * will be the property that triggered the event. In most cases these are the same property,
     * unless the event is bubbling up the tree. The final argument is optional and varies depending on
     * event type.
     *
     * @method  on
     *
     * @param  {String} events     One or more space separated eventNames
     * @param  {Function} callback  The function to execute when the given event is triggered
     * @return {Property}          Returns this for Object chaining.
     */
    Property.prototype.on = function (events, callback) {
        if (!isFunction(callback)) {
            log('warn', "Incorrect Syntax: callback must be a function");
            return;
        }

        var eventNames = events.split(' ');
        eventNames.forEach(function (eventName) {
            if (!this._eventListeners[eventName]) {
                this._eventListeners[eventName] = [];
            }
            this._eventListeners[eventName].push(callback);
        }, this);

        return this;
    };

    /**
     * Removes all instances of the given callback on the given events on this.
     *
     * @method off
     *
     * @param  {String} events  One or more space separated eventNames
     * @param  {Function} callback? The function to remove. if not specified all callbacks are removed
     * @return {Property}         Returns this for Object chaining.
     */
    Property.prototype.off = function (events, callback) {
        if (typeof events !== 'string') {
            log('warn', "Incorrect Syntax: events must be a string of space separated eventNames");
            return;
        }
        var eventNames = events.split(' ');
        eventNames.forEach(function (eventName) {
            if (this._eventListeners[eventName]) {
                if (isFunction(callback)) {
                    this._eventListeners[eventName] = this._eventListeners[eventName].filter(function (element, index, array) {
                        return element !== callback;
                    });
                } else {
                   this._eventListeners[eventName] = [];
                }
            }
        }, this);

        return this;
    };

    /**
     * Retrieves the metadata associated with this. The metadata is persisted with the json when you
     * pass true to the toJSON method (eg. this.toJSON(true)). Likewise the metadata will be restored
     * when creating a model from the very same json. Note: the modeljs framework uses the metadata to
     * store attributes associated the properties that is uses. As a result the following keys have special
     * meaning. <b>[validator, Formatter, name, url, refreshRate, isJSONPurl, doNotPersist, doNotPersistValue, thin]</b>
     *
     * For example see: <b>testGetMetadataMethod</b>
     *
     * @method  getMetadata
     *
     * @return {Object} A map of metadata properties associated with this.
     */
    Property.prototype.getMetadata = function () {
        return this._metadata;
    };

    /**
     * Determine if this has a validation function associated with it.
     *
     * @example
     * For examples see:  <b>testPropertyValidationFunction</b>
     *
     * @method hasValidator
     *
     * @return {Boolean} True if this has a validator associated with it. False otherwise.
     */
    Property.prototype.hasValidator = function () {
        return isFunction(this._metadata.validator);
    };

    /**
     * Determines if the given value will pass the validation function of this.
     *
     * @example
     * For examples see:  <b>testPropertyValidationFunction</b>
     *
     * @method  validateValue
     *
     * @param  {[String, Boolean, Number, null, Date, Function]} value A value to test against the validation function if it exists.
     * @return {Boolean}      The result of passing value against the validation function if it exists. True otherwise.
     */
    Property.prototype.validateValue = function (value) {

        // disallow values that are Model Property or PropertyArray objects
        if (value instanceof Property || Model.isArray(value)) {
            // this is misleading syntax because other property attributes are not copied like _listener and _parent
            // so prevent it and provide alternate. Maybe we could clone, but the suggested is basically a clone and more transparent.
            log('error', "Incorrect Syntax: use setValue([property].getValue()) instead");
            return false;
        } else if (isObject(value) || Array.isArray(value)) { // value is a primitive (non-object && non-Array). this must be as well.
            log('error', "Not Supported: Can't set a Property value to a model or Array. Delete the property and use createProperty");
            return false;
        }

        if (this.hasValidator()) {
            return this._metadata.validator(value);
        }
        return true;
    };


    function createArrayPrototype(isA, inherits) {
        var proto = Object.create(isA);
        for (var i in inherits) {
            if (inherits.hasOwnProperty(i)) {
                proto[i] = inherits[i];
            }
        }

        return proto;
    }

    /**
     * The modeljs Property Object that extends a javaScript Array so that Array function like push,
     * pop, reverse, etc... can be used while at the same time inherited Property methods. Be
     * aware instances will return false to native Array.isArray() function. THe alternative is
     * to use Model.isArray() or Array.isArray(instance.getValue()). The former is recommended.
     *
     * @example
     * For examples see: <b>testPropertyArray</b>,  and <b>testPropertyArrayLoading</b>
     *
     * @class ArrayProperty
     * @constructor
     * @extends Property
     *
     */
    function ArrayProperty(name, value, parent, metadata) {
        Property.call(this, name, value, parent, metadata);
        ObservableArray.call(this, this, value);
    }
    ArrayProperty.prototype = createArrayPrototype(ObservableArray.prototype, Property.prototype);

    /**
     * Called to set the value of a ArrayProperty. If the setValue is the same as the current value,
     * nothing will happen and no change events will be fired. If the value is different it must pass
     * the validator if there is one.  If it does pass the validator and the value is changed, all registered
     * listeners will be notified unless the suppressNotifications option indicates otherwise.
     *
     * @method  setValue
     * @for  ArrayProperty
     *
     * @param  {[Array]} newValue The Value you want to assign to this.
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of change.
     *
     * @return {[this]} this for method chaining.
     */
    ArrayProperty.prototype.setValue = function (value, suppressNotifications) {
        var newValue = value,
            i = 0;
        // Note: this disallows setting a property to undefined. Only when it's first created can it be undefined.
        if (newValue !== undefined && !arrays_equal(newValue, this.getValue())) {
            if (this.validateValue(newValue)) {
                var oldValue = this._myValue;
                this._myValue = value;
                if (this.length > newValue.length) { //remove excess iterating backwards because modifying the end.
                    for (i = this.length - 1; i >= newValue.length; i-=1) {
                        this.pop(); // will fire CHILD_DESTROYED event
                    }
                }
                for (i = 0; i < newValue.length; i++) {
                    if (this[i]) {
                        this[i].setValue(newValue[i], suppressNotifications);
                    } else {
                        this.push(newValue[i]); // will fire CHILD_CREATED event
                    }
                }
                // fix suppressNotifications. Should be in transaction. what if transaction already there?
                if (!suppressNotifications) {
                    this.trigger(Model.Event.PROPERTY_CHANGE, oldValue);
                }
            }
        }
        return this;
    };

    ArrayProperty.prototype.getValue = function () {
        var value = [],
            i = 0;
        for (i = 0; i < this.length; i++) {
            value[i] = this[i].getValue();
        }
        return value;
    };
    ArrayProperty.prototype.toJSON = function (includeMetaData) {
        var json = [],
            i = 0,
            value;

        if (this.getMetadata().doNotPersist){
            return undefined;
        } else if (this.getMetadata().doNotPersistValue){
            return [];
        } else {
            for (i = 0; i < this.length; i++) {
                value = this[i].toJSON(includeMetaData);
                if (value !== undefined) {
                    json.push(value);
                }
            }
            return json;
        }
    };

    // setValueAt(i, value); suppose to be alternative to a[i] = .. which bypasses events
    ArrayProperty.prototype.setValueAt = function (index, value, metadata) {
        // Not sure if function required. So not documented or tested yet.
        var currentProperty = this[index];
        if (currentProperty === undefined){
            this[index] = _createProperty(index, value, this, metadata || {});
            this.trigger(Model.Event.CHILD_CREATED, this[index]);
        } else  {
            if (currentProperty.validateValue(value)) {
                currentProperty.setValue(value);
                extend(currentProperty.getMetadata(), metadata);
            } else {
                currentProperty.destroy();
                this[index] = _createProperty(index, value, this, metadata || {});
                this.trigger(Model.Event.CHILD_CREATED, this[index]);
            }
        }
    };

    /**
     * Determines if the given value will pass the validation function of this.
     *
     * @example
     * For examples see:  <b>testPropertyValidationFunction</b>
     *
     * @method  validateValue
     *
     * @param  {[Array]} value A value to test against the validation function if it exists.
     * @return {Boolean}      The result of passing value against the validation function if it exists. True otherwise.
     */
    ArrayProperty.prototype.validateValue = function (value) {

        // disallow values that are Model Property or PropertyArray objects
        if (value instanceof Property || Model.isArray(value)) {
            // this is misleading syntax because other property attributes are not copied like _listener and _parent
            // so prevent it and provide alternate. Maybe we could clone, but the suggested is basically a clone and more transparent.
            log('error', "Incorrect Syntax: use setValue([ArrayProperty].getValue()) instead");
            return false;
        } else if (!Array.isArray(value)) { // newValue is an Array
            log('error', "Not Supported: Can not set a non-Array Property to an Array. Delete the property and use createProperty passing it the array");
            return false;
        }

        if (this.hasValidator()) {
            return this._metadata.validator(value);
        }
        return true;
    };

    /**
     * Removes the property and its children if any from the Model. This will fire the 'destroy' event on this and
     * the 'childDestroyed' event on the parent for every Property in the Array.
     *
     * @method  destroy
     *
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of destroy.
     * @return {Property}   The deleted Property.
     */
    ArrayProperty.prototype.destroy = function (suppressNotifications) {
        var i = 0;
        Model.startTransaction();
        for (i = 0; i < this.length; i++) {
            this.pop(); // pop will call destroy on all elements.
        }
        Model.endTransaction({suppressAllEvents:suppressNotifications});

        Property.prototype.destroy.call(this, suppressNotifications);
    };

    /**
     * The model Object that wraps the JSON.
     *
     * @example
     * For examples see: <b>testPrimitiveSaveLoad</b>,  <b>testObjectsSaveLoad</b>, <b>testComplexSaveLoad</b>
     * <b>testGetNameMethod</b> and <b>testSaveLoadWithMetaData</b>
     *
     * @class Model
     * @constructor
     * @extends Property
     *
     * @param {Object} json?    The json object to be modeled.
     * @param {Object} metadata? May contain the following:
     *                         name - name of the Model, defaults to "root"
     *                         *plus any properties accepted by the createProperty method metadata argument or
     *                          additional data you want stored in the metadata.
     */
    function Model(json, metadata, parent) {
        var jsonModel = json || {},
            modelMetadata = metadata || {},
            modelName = modelMetadata.name !== undefined? modelMetadata.name : "root",
            modelParent = parent || null;

        if (modelMetadata.name) { // name is not part of the metadata.
            delete modelMetadata.name;
        }

        //A Model is in itself a Property so lets call our supers constructor
        Property.call(this, modelName, jsonModel, modelParent, modelMetadata);

        if (this.validateValue(jsonModel)) {

            for (var name in jsonModel) {
                if (name.match(Model.PROPERTY_METADATA_SERIALIZED_NAME_REGEX)) { // skip special meta data properties
                    continue;
                }

                var value = jsonModel[name];
                var propertyMetadata = jsonModel[name + Model.PROPERTY_METADATA_SERIALIZED_NAME_SUFFIX];

                if (!modelMetadata.thin) {
                    this.createProperty(name, value, propertyMetadata);
                }
            }
        }
    }
    Model.prototype = Object.create(Property.prototype);

    /**
     * Determines if the parameter passed in is an modeljs Array Property.
     *
     * @static
     * @method isArray
     *
     * @param  {Property}  property  Property to test whether or not it is an Array Property.
     * @return {Boolean}          true if the Property is an Array Property, false otherwise
     */
    Model.isArray = function (property) {
        return (property instanceof ArrayProperty);
    };

    Model.isProperty = function (property) {
        return Model.isArray(property) || ((property instanceof Property) && !(property instanceof Model));
    };

    Model.PROPERTY_METADATA_SERIALIZED_NAME_SUFFIX = "__modeljs__metadata";
    Model.PROPERTY_METADATA_SERIALIZED_NAME_REGEX = /__modeljs__metadata$/;

    /**
     * Gets the value associated with the Model. This will be a json Object.
     *
     * @method  getValue
     *
     * @return {Object} The json Object represented by the model
     */
    Model.prototype.getValue = function () {
        var property,
            value = {};

        if (this._metadata.thin) {
            return this._myValue;
        }

        for (var name in this) {
            if (this.hasOwnProperty(name) &&
                (this[name] instanceof Property || Model.isArray(this[name])) &&
                name !== '_parent') {
                // for ECMA backwards compatibility '_parent' must be filter since its non-enumerable. and would cause infinite recursion
                property = this[name];
                value[name] = property.getValue();
            }
        }
        return value;
    };

    /**
     * Called to set the value of a model. If the setValue is the same as the current value,
     * nothing will happen and no change events will be fired. If the value is different it must pass
     * the validator if there is one.  If it does pass the validator and the value is changed, all registered
     * listeners will be notified unless the suppressNotifications option indicates otherwise.
     *
     * @method  setValue
     * @for  Model
     *
     * @param  {[Object]} newValue The Value you want to assign to this.
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of change.
     *
     * @return {[this]} this for method chaining.
     */
    Model.prototype.setValue = function (value, suppressNotifications) {
        var newValue = value;
        // Note: this disallows setting a property to undefined. Only when it's first created can it be undefined.
        if (newValue !== undefined && JSON.stringify(newValue) !== JSON.stringify(this.getValue())) {  //TODO  figure this out

            if (this.validateValue(newValue)) {
                var oldValue = this._myValue;
                this._myValue = newValue; //set Value
                //This model need to be set to the newValue
                var mergeSuccessful = this.merge(newValue, false, suppressNotifications);
                if (!mergeSuccessful) {
                    this._myValue = oldValue; //set Value if successful
                }
                // fix suppressNotification should go around Merge!
                if (mergeSuccessful && !suppressNotifications) {
                    this.trigger(Model.Event.PROPERTY_CHANGE, oldValue);
                }
            }
        }
        return this;
    };

    /**
     * Determines if the given value will pass the validation function of this.
     *
     * @example
     * For examples see:  <b>testPropertyValidationFunction</b>
     *
     * @method  validateValue
     * @for  Model
     *
     * @param  {[Object]} value A value to test against the validation function if it exists.
     * @return {Boolean}      The result of passing value against the validation function if it exists. True otherwise.
     */
    Model.prototype.validateValue = function (value) {

        // disallow values that are Model Property or PropertyArray objects
        if (value instanceof Property || Model.isArray(value)) {
            // this is misleading syntax because other property attributes are not copied like _listener and _parent
            // so prevent it and provide alternate. Maybe we could clone, but the suggested is basically a clone and more transparent.
            log('error', "Incorrect Syntax: use setValue([model].getValue()) instead");
            return false;
        } else if (!isObject(value)) {
            log('error', "Not Supported: Can't set the Model value to a non Model value. Delete the model and use createProperty to change it's type");
            return false;
        }

        if (this.hasValidator()) {
            return this._metadata.validator(value);
        }
        return true;
    };


    var MIN_MODEL_REFRESH_RATE = 100;
    function _createProperty (name, value, parent, metadata) {
        var newProperty,
            propertyMetadata = metadata || {};
        if (value instanceof Property || Model.isArray(value)) {
            log('error', "Unsupported Operation: Try passing the Model/Properties value instead");
            return;
        } else if (Array.isArray(value)) {
            newProperty = new ArrayProperty(name, value, parent, metadata);
        } else if (isObject(value)) {
            propertyMetadata.name = name;
            newProperty = new Model(value, propertyMetadata, parent);
        } else {
            newProperty = new Property(name, value, parent, metadata);
        }

        if (propertyMetadata.url && propertyMetadata.refreshRate && newProperty) {
            if (propertyMetadata.refreshRate === -1){
                makeRemoteRequest(newProperty);
            } else {
                var interval = Math.max(MIN_MODEL_REFRESH_RATE, propertyMetadata.refreshRate);
                var intervalId = setInterval(makeRemoteRequest.bind(null, newProperty), interval);
                propertyMetadata.getMetadata().intervalId = intervalId;
            }
        }

        return newProperty;
    }

    /**
     * Creates the property with the given name on this. This will fire the childCreated event on the parent. The
     * metadata can contain custom keys or any of the special keys below.
     *
     * @example
     *     var model = new Model();
     *     model.createProperty("number", 1) // a simple property (model.number)
     *     .createProperty("subModel", { // a property that is a subModel (model.subModel and model.subModel.str)
     *         str: "stringProperty"
     *     })
     *     .createProperty("positiveNumber", 2, { // a property with a positiveNumber validator and a custom attribute
     *         validator: function (value) {
     *             return value > 0;
     *         },
     *         customMetadata: "this Property is special"
     *     }),
     *     .createProperty ("remoteModel", {prop1: "defaultValue"}, { // a remote model populated via the twitter rest api.
     *         url: "http://search.twitter.com/search.json?q=tennis&callback=$jsonpCallback",
     *         doNotPersist: true,
     *         refreshRate: -1, // -1 means fetch once.
     *         isJSONPurl: true
     *     }); // Note the method chaining.
     *
     * For examples see: <b>testModelCreationUsingCreatePropertyMethod, testThinModel</b>
     *
     * @method  createProperty
     *
     * @param {String} name    Name of the property
     * @param {[String, Boolean, Number, null, Date, Function, Object]} value   Property value
     * @param {[Object]} metadata? A hash of metadata associated with the property. You can put any metadata you want. However the following keys have special meaning and are reserved for use by the framework.
     *                         <ul><li>
     *                             <b>validator</b> {Function} - a function to validate if the new value is valid before it is assigned.
     *                         </li><li>
     *                             <b>Formatter</b> {Function} - a function that takes this.getValue() as input and output the value you want returned by the getFormattedValue function. See the documentation of getFormattedValue for more details.
     *                         </li><li>
     *                             <b>url</b> {String} - the resource this model should use to get it's value. Resource must return json. *Must be used with refreshRate*
     *                         </li><li>
     *                             <b>refreshRate</b> {Number} - the interval used to query the url for changes. must be > 100 or -1. -1 indicates to only fetch value once. *Must be used with url*
     *                         </li><li>
     *                             <b>isJSONPurl</b> {Boolean} - if true will use JSONP to fetch the data. The url provided must have the string "$jsonpCallback" where the jsonp callback function should be inserted.
     *                         </li><li>
     *                             <b>doNotPersist</b> {Boolean} - property will not exist in the json object returned by the toJSON method.
     *                         </li><li>
     *                             <b>doNotPersistValue</b> {Boolean} - will clear the value of the property when toJSON is called. For Object and Array types the value will be and empty object/array. Else it will be undefined. *Note metadata can still be persisted.
     *                         </li><li>
     *                             <b>thin</b> {Boolean} - will create a model property representing this but not model any of it's children properties
     *                         </li></ul>
     *
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of change.
     *
     * @return {Model}         Returns this for method chaining
     */
    Model.prototype.createProperty = function createProperty(name, value, metadata, suppressNotifications) {

        if (value instanceof Property || Model.isArray(value)) {
            log('error', "Unsupported Operation: Try passing the Model/Properties value instead");
            return this;
        } else if (this._metadata.thin) {
            log('error', "Unsupported Operation: Can not create a property on a Thin model.");
            return this;
        }
        this[name] = _createProperty(name, value, this, metadata);

        if (!suppressNotifications) {
            this.trigger(Model.Event.CHILD_CREATED, this[name]);
        }
        return this;
    };

    /**
     * Removes the property and its children if any from the Model. This will fire the 'destroy' event on this and
     * the 'childDestroyed' event on the parent for every Property in the Model.
     *
     * @method  destroy
     *
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of destroy.
     * @return {Property}   The deleted Property.
     */
    Model.prototype.destroy = function (suppressNotifications) {
        for (var propName in this) {
            if (this.hasOwnProperty(propName) &&
                (this[propName] instanceof Property || Model.isArray(this[propName])) &&
                propName !== '_parent') { // for ECMA backwards compatibility '_parent' must be filter since its non-enumerable
                this[propName].destroy(suppressNotifications);
            }
        }

        Property.prototype.destroy.call(this, suppressNotifications);
    };

    /**
     * Clones the Model rooted at this keeping all metadata that exist, but not keeping any event listeners.
     * The name of all properties are adjusted to reflect it's new root.
     *
     * @example
     *     var newModel = model.clone(); // clone root model
     *     var clonedSubModel = model.subModel.clone(); // clone subModel
     * For more examples: <b>testModelClone</b>
     *
     * @method  clone
     *
     * @return {Model}  Returns a new Model object rooted at this, keeping any metadata but no event listeners.
     */
    Model.prototype.clone = function () {
        var metadata = JSON.parse(JSON.stringify(this.getMetadata()));
        metadata.name = this.getShortName();
        return new Model(this.toJSON(true), metadata);
    };

    function mergeLoop(model, json, doModification, keepOldProperties, suppressNotifications) {

        for (var name in json) {
            if (!name.match(Model.PROPERTY_METADATA_SERIALIZED_NAME_REGEX)) {
                var value = json[name];
                if (model[name]) {
                    if (isObject(value)) { // right hand side is an object
                        if (model[name] instanceof Model) { // left is and Model. -> merging objects
                            if (!mergeLoop(model[name], value, doModification, keepOldProperties, suppressNotifications)) {
                                return false;
                            }
                        } else {
                            // Trying to assign a model to a property. This will fail.
                            return false;
                        }

                    } else { // right hand side is not an object.
                        if (Model.isProperty(model[name])) { // left is a Property -> merging properties
                            if (doModification) {
                                model[name].setValue(value, suppressNotifications);
                            }
                        } else {
                            // Trying to assign a property to a Model. This will fail.
                            return false;
                        }
                    }
                } else { //create new property
                    if (doModification) {
                        model.createProperty(name, value, {}, suppressNotifications);
                    }
                }
            }
        }

        // delete properties that are not found in json
        if (!keepOldProperties && doModification) {
            for (var modelProp in model) {
                if (!json[modelProp] && //property does exist in merge
                        model.hasOwnProperty(modelProp) &&
                        (model[modelProp] instanceof Property || Model.isArray(model[modelProp])) &&
                        modelProp !== '_parent') { // for ECMA backwards compatibility '_parent' must be filter since its non-enumerable
                    model[modelProp].destroy(suppressNotifications);
                }
            }
        }
        return true;
    }

    /**
     * Preforms the merge operation on this. The merge operation will add properties that exist in the merged object
     * but not in this, remove those that are not found in the merged object (unless keepOldProperties is set to true)
     * and will call setValue for those that exist in both. Note the operation will log an error to the console, return
     * false, and not modify the object if any of the setValue operation are not valid. Not valid set operations included
     * those that try to set a value from a property to a model and vise versa.
     *
     * @example
     * For an example see: <b>testModelMergeMethod</b>
     *
     * @method  merge
     *
     * @param  {[Object]} json              The json object to have merged.
     * @param  {[Boolean]} keepOldProperties? True if you want to keep properties that exist in this but not in the passed in json, Otherwise they will be deleted. Defaults to false.
     * @param  {[Boolean]} suppressNotifications? indicates if listeners should be notified of change.
     * @return {Boolean}                   Returns true if merge was successful, false otherwise.
     */
    Model.prototype.merge = function (json, keepOldProperties, suppressNotifications) {
        //will merge the properties in json with this. result will be the same as the Object extend.
        //if a property exists in the model but not in the json it will only be kept if keepOldProperties is true.
        if (this._metadata.thin) {
            this._myValue = json;
            return true;
        } else if (mergeLoop(this, json, false, keepOldProperties)) { // check if merge will be successful
            Model.startTransaction();
            mergeLoop(this, json, true, keepOldProperties, suppressNotifications);
            Model.endTransaction();
            return true;
        } else {
            log('error', "Merge operation Not Supported: An assignment was not valid. Model not modified");
            return false;
        }
    };


    /**
     * Retrieves the json representation of this. This json representation can be used in the Model Constructor
     * to recreate the same Model object. If you use includeMetaData validator metadata will be included.
     * Properties that have the doNotPersist flag in it's metadata will have it's value nullified. This means
     * properties will have the value set to 'undefined' while models will be set to an empty object ({}).
     *
     * @example
     * For an example see: <b>testSaveLoadWithMetaData</b> and <b>testDoNotPersist</b>
     *
     * @method  toJSON
     *
     * @param  {[Boolean]} includeMetaData? indicates if model meta data should be included in the returned JSON. Defaults to false.
     * @return {[Object]}                 The json representation of the Model.
     */
    Model.prototype.toJSON = function (includeMetaData) {
        var json = {};
        if (this.getMetadata().doNotPersist) {
            return undefined;
        } else if (this.getMetadata().doNotPersistValue){
            return {};
        } else if (this.getMetadata().thin) {
            return this.getValue();
        } else {
            for (var name in this) {
                if (this.hasOwnProperty(name) &&
                    (this[name] instanceof Property || Model.isArray(this[name])) &&
                    name !== '_parent') {
                    // for ECMA backwards compatibility '_parent' must be filter since its non-enumerable. and would cause infinite recursion
                    var property = this[name];
                    if (!property.getMetadata().doNotPersist) {
                        json[name] = property.toJSON(includeMetaData);
                        if (includeMetaData && !isEmptyObject(property.getMetadata())) {
                            json[name + Model.PROPERTY_METADATA_SERIALIZED_NAME_SUFFIX] = property.getMetadata();
                        }
                    }
                }
            }
            return json;
        }
    };

    /**
     * A global formatter used to calculate the formatted value of a Property. If defined the function
     * will be called when getFormattedValue gets called. The function should accept the value to be formatted
     * as the first argument and expect 'this' to be the Property. The formatter must be able to handle any
     * input type as a value.
     *
     * @example
     *     Model.Formatter = function (value) { //makes all strings uppercase
     *         if (typeof value === 'string') {
     *             return value.toUpperCase();
     *         }
     *         return value;
     *     }
     *
     * @for  Model
     * @property  Formatter
     * @static
     *
     * @type {Function} A format function whose first argument is the value to be formatted
     * @return {any}    The formatted result
     */
    Model.Formatter = undefined;

    /**
     * If logging is enabled any warning or incorrect uses of the api will result in output to the console
     * if it exists.
     *
     * @property isLoggingEnabled
     * @default false
     * @static
     * @type {Boolean} Indicates if Logging is enabled
     */
    Model.enableLogging = false;

    /**
     * Searches the given model for the property of the given name and returns it.
     *
     * @method find
     * @static
     *
     * @param  {[Property, Model, ArrayProperty]} model        The model to search.
     * @param  {[String]} propertyName The fully qualified name of the property. Equal to the getName() value.
     * @return {[Property, Model, ArrayProperty]}   The model object of the given name, null otherwise.
     */
    Model.find = function (model, propertyName) {
        if(!((model instanceof Property) || Model.isArray(model)) || typeof propertyName !== 'string') {
            return null;
        }

        var modelName = model.getName();
        var modelParts = modelName.substring(1).split('/');
        var propertyParts = propertyName.substring(1).split('/');
        var diff = "";

        if (modelParts[0] !== propertyParts[0]) {
            return null;  //not part of same model
        }

        var i = 0;
        while (modelParts[i] === propertyParts[i] && i < propertyParts.length) {
            i++;
        }

        var j = i;
        var commonDenominator = model;
        for ( j = i; j < modelParts.length; j++) { //traverses up to the common denominator
            commonDenominator = commonDenominator._parent;
        }

        var prop = commonDenominator;
        for (var k = i; k < propertyParts.length; k++) {
            prop = prop[propertyParts[k]];
            if (!prop) {
                return null;
            }
        }
        if (prop.getName() === propertyName){
            return prop;
        }
        return null;
    };

    /**
     * Connects two properties together. So that events fired on one are also fired on the other. The options
     * specifies the type of connection to create.
     *
     * @method connect
     * @static
     *
     * @param  {[Property]} src       The source Property
     * @param  {[Property]} dest       The destination Property
     * @param  {[Object]} options? A hash of options describing how to connect the two properties.
     *                             The following option are accepted:
     *                     <ul><li>
     *                         <b>eventBlackList</b> {Array} - A array of eventNames that <b>should not</b> be connected between the two Properties, all other event are connected.
     *                     </li><li>
     *                         <b>eventWhiteList</b> {Array} - A array of eventNames that <b>should</b> be connected between the two Properties, all other events not connected.
     *                     </li><li>
     *                         <b>includeChildred</b> {boolean} - Indicates if we should connect all the children of source Property as well. The default is false.
     *                     </li><li>
     *                         <b>mapFunction</b> {function} - A function that will map PropertyNames in 'a' to propertyNames in 'b' that should be connected.
     *                         If the function returns null, the property won't be connected to anything. *This option must be used in conjunction with includeChildren=true.
     *                         If not specified the default behavior is to map all properties via the getName function. Thus only properties with the same name are connected
     *                     </li>
     *                     </li><li>
     *                         <b>direction</b> {"oneWay"|"dual"} - Specifies how you would like to connect the two Properties. If not specified default is "dual".
     *                     </li>
     *
     * @return {[Function]}         The disconnect function. When executed will remove the connection between the properties.
     */
    Model.connect = function connect (src, dest, options) { //AKA join connect, disconnect.

        var propergateSrcToDest,
            propergateDestToSrc;
        options = options || {};

        /**
         * This function is registered on the ALL event to propagate its events to the
         * connected property. FYI 'this' is bound to the source linked property.
         *
         * @param  {Boolean} isAtoB   [description]
         * @param  {[type]}  property On any event the first argument is the property the event was triggered on.
         * @return {[type]}           [description]
         */
        function propergateEvent (isAtoB, property /*, ... other event callback arguments */) {
            var eventName = arguments[arguments.length-1]; //Since this is registered on the all event the last argument is the orginal Event name.
            if (eventName === Model.Event.CHILD_DESTROYED) { //we listen to he destroy action so no need to listen to CHILD_DESTROYED too
                return;
            }
            if(options && (options.eventBlackList || options.eventWhiteList)) {
                if (options.eventBlackList && options.eventBlackList.indexOf(eventName) !== -1) {
                    return;
                }
                if (options.eventWhiteList && options.eventWhiteList.indexOf(eventName) === -1) {
                    return;
                }
            }

            var linkedProperty = this;
            var newPropDisconnect;
            var reversePropergationFunction = isAtoB? propergateDestToSrc: propergateSrcToDest;

            // deregister our reverse propergation function and put it back later, so we don't have infinite loop.
            if (reversePropergationFunction) { // this will not exist if the connection direction was "one-way"
                linkedProperty.off(Model.Event.ALL, reversePropergationFunction);
            }

            if (eventName === Model.Event.PROPERTY_CHANGE) {
                linkedProperty.setValue(property.getValue());
            } else if (eventName === Model.Event.CHILD_CREATED) {
                var newProperty = arguments[2];
                if (Model.isArray(linkedProperty)) {
                    // newProperty.getShortName() == linkedProperty.length; <- do push when this is true
                    linkedProperty.push(newProperty.getValue());
                    newPropDisconnect = connect(newProperty, linkedProperty[newProperty.getShortName()]);
                } else {
                    linkedProperty.createProperty(newProperty.getShortName(), newProperty.getValue(), newProperty.getMetadata());
                    newPropDisconnect = connect(newProperty, linkedProperty[newProperty.getShortName()]);
                }
            } else if (eventName === Model.Event.DESTROY) {
                if (Model.isArray(linkedProperty._parent) &&
                    parseInt(linkedProperty.getShortName(), 10) === linkedProperty._parent.length - 1) {
                    linkedProperty._parent.pop();
                } else {
                    linkedProperty.destroy();
                }
            } else { //custom event
                // remove the first argument, 'isAtoB' which is bound to this function via bind
                // also remove the second which is the property event triggered on
                // extract the middle
                // remove the last argument which is the event name since this method is registared to the Model.Event.ALL event.
                var args = [eventName].concat(Array.prototype.slice.call(arguments, 2, arguments.length-1));
                Property.prototype.trigger.apply(linkedProperty, args );
            }

            // only restore the connection if property is not destroyed
            if (eventName !== Model.Event.DESTROY && reversePropergationFunction) {
                linkedProperty.on(Model.Event.ALL, reversePropergationFunction);
            }

            return newPropDisconnect; // how do we disconnect this.
        }

        function disconnect(src, dest, propergateSrcToDest, propergateDestToSrc) {
            if (propergateSrcToDest) {
                src.off(Model.Event.ALL, propergateSrcToDest);
            }
            if (propergateDestToSrc) {
                dest.off(Model.Event.ALL, propergateDestToSrc);
            }
        }

        // one direction
        propergateSrcToDest = propergateEvent.bind(dest, true);
        src.on(Model.Event.ALL, propergateSrcToDest);

        // other direction
        if (!options.direction || options.direction !== "oneWay") {
            propergateDestToSrc = propergateEvent.bind(src, false);
            dest.on(Model.Event.ALL, propergateDestToSrc);
        }

        if (options.includeChildren && (src instanceof Model || Model.isArray(src))) { // go through children
            var disconnectFunctions  = [];
            for (var propName in src) {
                if (src.hasOwnProperty(propName) &&
                    (src[propName] instanceof Property || Model.isArray(src[propName])) &&
                    propName !== "_parent") {

                    var childLinkedPropertyName = options.mapFunction ? options.mapFunction(src[propName].getName()) : src[propName].getName();
                    var childLinkedProperty = Model.find(dest, childLinkedPropertyName );
                    if (childLinkedProperty) {
                        disconnectFunctions.push(Model.connect(src[propName], childLinkedProperty, options));
                    }
                }
            }
            return function disconnectModel(functionsToDisconnect) {
                for (var i = 0; i < functionsToDisconnect.length; i++) {
                    functionsToDisconnect[i]();
                }
            }.bind(null, disconnectFunctions);
        }

        return disconnect.bind(null, src, dest, propergateSrcToDest, propergateDestToSrc);
    };


   /**
     * Begins a transaction. All events will be put into the queued. To be fired when endTransaction is called.
     *
     * @example
     * For an examples see <b>testModelTransactions</b>
     *
     * @for  Model
     * @method  startTransaction
     * @static
     *
     */
    Model.startTransaction = function () {
        eventProxy.startTransaction();
    };


    /**
     * Ends the current transaction causing all queued up events to be fired according to the global eventOptization settings or the settings passed in if they exist.
     *
     * @example
     *     model.endTransaction(); //uses settings found in Model.TRANSACTION_OPTIONS
     *     model.endTransaction({   // override the Model.TRANSACTION_OPTIONS settings for this transaction
     *         fireOnlyMostRecentPropertyEvent: false,
     *         flattenCallbacks: true,
     *         flattenCallbacksByHash: true
     *     })
     * For more examples see: <b>testFlattenCallbacks</b>, <b>testFlattenCallbacksByHash</b>,
     *      <b>testModelEndTransactionWithOptions</b>
     *
     * @for     Model
     * @method  endTransaction
     * @static
     *
     * @param  {Object} options? A map of Model.TRANSACTION_OPTIONS options that you want overridden when clearing this transaction queue.
     */
    Model.endTransaction = function (options) {
        var originalTransactionOptions;

        if (options) { // if option override global setting keeping them so they can be restored later
            originalTransactionOptions = JSON.parse(JSON.stringify(Model.TRANSACTION_OPTIONS));
            extend(Model.TRANSACTION_OPTIONS, options);
        }

        eventProxy.endTransaction();

        if (options) { //restore global settings
            extend(Model.TRANSACTION_OPTIONS, originalTransactionOptions);
        }

    };

    /**
     * Determines if you are currently in a start/end transaction block.
     *
     * @example
     * For an examples see <b>testModelTransactions</b>
     *
     * @for  Model
     * @method  inTransaction
     * @static
     *
     * @return {[Boolean]} True if your in a transaction block, false otherwise.
     */
    Model.inTransaction = function() {
        return eventProxy.inTransaction();
    };

    /**
     * Determines if Events notify listener asynchronously.
     *
     * @property asyncEvents
     * @default true
     * @static
     * @type {Boolean} Indicates if event notify listener asynchronously
     */
    Model.asyncEvents = true;

    // does property change bubble a model change?
    // does model change include this or just it's children?
    // what about change?
    Model.Event = {
        /**
         * The PROPERTY_CHANGE event is triggered only when the value of the property has changed
         * via setValue. When triggered it will bubble up a MODEL_CHANGED event. Listen to this event
         * if you only want to be notified of direct changes to the property and not changes to any of
         * it's children. This is what the onChange(callback, false) does.
         * PROPERTY_CHANGE event callbacks will be called with the following arguments:
         *      <ul>
         *      <li>this = the property listening to the event</li>
         *      <li>arg[0] = the property in it's current state</li>
         *      <li>arg[1] = the old value.</li>
         *      </ul>
         * @property Event.PROPERTY_CHANGE
         * @static
         * @type {String}
         */
        PROPERTY_CHANGE: "propertyChange",
        /**
         * The MODEL_CHANGE event is triggered when the value of any of the property's
         * children have changed. This includes child properties being created or destroyed.
         * When triggered on a property it will bubble the event up it's model tree.
         * The callback will have the following arguments:
         *      <ul>
         *      <li>this = the property listening to the event. (or is registered with the callback)</li>
         *      <li>arg[0] = the property that triggered the modelChange</li>
         *      <li>arg[1] = the old value.</li>
         *      </ul>
         * @property Event.MODEL_CHANGE
         * @static
         */
        MODEL_CHANGE: "modelChange",
        /**
         * The CHANGE event is pseudo event equivalent to a PROPERTY_CHANGE and MODEL_CHANGE event.
         * It is the event used when registering a listener using the onChange(callback, true) method.
         * The callback will have the following arguments:
         *      <ul>
         *      <li>this = the property listening to the event</li>
         *      <li>arg[0] = the property in it's current state</li>
         *      <li>arg[1] = the old value.</li>
         *      </ul>
         * @property Event.CHANGE
         * @static
         * @type {String}
         */
        CHANGE: "change",
        /**
         * The DESTROY event is triggered when destroy() method called on a property. It than triggers
         * a CHILD_DESTROYED event on it's parent.
         * The callback will have the following arguments:
         *      <ul>
         *      <li>this = the destroyed property</li>
         *      <li>arg[0] = the destroyed property</li>
         *      <li>arg[1] = the old value.</li>
         *      </ul>
         * @property Event.DESTROY
         * @static
         * @type {String}
         */
        DESTROY: "destroy",
        /**
         * The CHILD_CREATED event is triggered when a new property is created. It's triggered on the
         * parent and propagates a MODEL_CHANGE event up the model tree.
         * The callback will have the following arguments:
         *      <ul>
         *      <li>this = the parent property</li>
         *      <li>arg[0] = the created property</li>
         *      <li>arg[1] = undefined</li>
         *      </ul>
         * @property Event.CHILD_CREATED
         * @static
         * @type {String}
         */
        CHILD_CREATED: "childCreated",
        /**
         * The CHILD_Destroyed event is triggered when a child property is destroyed. It triggered on the
         * parent and propergates a MODEL_CHANGE event up the model tree.
         * The callback will have the following arguments:
         *      <ul>
         *      <li>this = the parent property</li>
         *      <li>arg[0] = the destroyed property</li>
         *      <li>arg[1] = undefined</li>
         *      </ul>
         * @property Event.CHILD_DESTROYED
         * @static
         * @type {String}
         */
        CHILD_DESTROYED: "childDestroyed",
        /**
         * A special pseudo event named "all" that equivalent to listening to all 'real' events.
         * All events are considered 'real' except for the MODEL_CHANGE event which is a special
         * propagation event and the CHANGE event which is a pseudo event.
         * Triggering the ALL event does not fire all event, but fires an event named all which will
         * call all callbacks registered to the ALL event. The callback will have the following arguments:
         *      <ul>
         *      <li>this = the property listening to the event</li>
         *      <li>arg[0-..n-1] = the same arguments passed to the original event</li>
         *      <li>arg[n-1] = the original event name</li>
         *      </ul>
         * @property Event.ALL
         * @static
         * @type {String}
         */
        ALL: "all"
    };


    Model.TRANSACTION_OPTIONS = {
        /**
            Only fires the last event of a property during a transaction.
            @Example For an example see <b>testFireOnlyMostRecentPropertyEvent</b>
            @property TRANSACTION_OPTIONS.fireOnlyMostRecentPropertyEvent
            @default false
            @static
            @type {boolean}
        */
        fireOnlyMostRecentPropertyEvent: false,
        /**
            Will make sure a callback only gets called only once during a transaction. Even if registered with several properties.
            @Example For an example see <b>testFlattenCallbacks</b>
            @property TRANSACTION_OPTIONS.flattenCallbacks
            @default false
            @static
            @type {boolean}
         **/
        flattenCallbacks: false,
        /**
            Will make sure callbacks identified by .hash only gets called only once during a transaction. Even if registered with several properties.
            @Example For an example see <b>testFlattenCallbacksByHash</b>
            @property TRANSACTION_OPTIONS.flattenCallbacksByHash
            @default false
            @static
            @type {boolean}
        */
        flattenCallbacksByHash: false,
        /**
            Will guarantee that no event are fired during a transaction
            @Example For an example see <b>testSuppressAllEvents</b>
            @property TRANSACTION_OPTIONS.suppressAllEvents
            @default false
            @static
            @type {boolean
        */
        suppressAllEvents: false

    };
    Object.seal(Model.TRANSACTION_OPTIONS);

    var oldModel = globalNS.Model;
    /**
     * Release control of the global window.Model variable restoring it to its previous value
     *
     * @Example
     *     // window.Model is restore to previous value and localModel now holds the window.Model reference
     *     var localModel = window.Model.noConflict();
     * For an example see <b>testModelNoConflict</b>
     *
     * @for  Model
     * @method  noConflict
     * @static
     *
     * @return {[Model]} The window Model variable that was just released.
     */
    Model.noConflict = function () {
        globalNS.Model = oldModel;
        return this;
    };


   if (typeof define === "function" && define.amd) {
        define([], function () {
            return Model;
        });
    } else if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) {
            exports = module.exports = Model;
        }
        exports.Model = Model;
    } else {
        /** @global */
        window["Model"] = Model;
    }

}(typeof window !== 'undefined' ? window : GLOBAL)); //window in the browser and GLOBAL in node