Source: hui/hui_basemodel.js

'::hui_basemodel::';
'use strict';
//   __    __           ______   ______  _____    __  __     
//  /\ \  /\ \ /'\_/`\ /\  _  \ /\__  _\/\  __`\ /\ \/\ \    
//  \ `\`\\/'//\      \\ \ \/\ \\/_/\ \/\ \ \/\ \\ \ \ \ \   
//   `\ `\ /' \ \ \__\ \\ \  __ \  \ \ \ \ \ \ \ \\ \ \ \ \  
//     `\ \ \  \ \ \_/\ \\ \ \/\ \  \ \ \ \ \ \_\ \\ \ \_\ \ 
//       \ \_\  \ \_\\ \_\\ \_\ \_\  \ \_\ \ \_____\\ \_____\
//        \/_/   \/_/ \/_/ \/_/\/_/   \/_/  \/_____/ \/_____/
//                                                                   

/**
 * @name @name 基础数据模型类
 * @public
 * @author wanghaiyang
 * @date 2014/05/05
 * @param {Object} options 控件初始化参数.
 */
hui.define('hui_basemodel', ['hui_eventdispatcher'], function () {
    
    hui.BaseModel = function (data) {
        hui.EventDispatcher.call(this);

        var _model = {};
        /**
         * @name 设置新的值,如果两个值不同,就会触发PropertyChangedEvent.
         * @param {String|Object} propertyName 需要设置的属性或数据对象.
         * @param {Any} value 属性的值.
         * @comment 接受`"key", value` 和 `{key: value}`两种的方式赋值.
         */
        this.set = function (propertyName, newValue) {
            var attr,
                attrs,
                changes = [],
                newValue,
                className = Object.prototype.toString.call(propertyName);

            if ((className !== '[object Object]' && className !== '[object String]') ||
                (className === '[object Object]' && newValue !== undefined)) {
                return this.trigger('SET_ERROR', propertyName, newValue);
            }

            if (className == '[object String]') {
                attrs = {};
                attrs[propertyName] = newValue;
            }
            else {
                attrs = propertyName;
            }

            for (attr in attrs) {
                if (!Object.prototype.hasOwnProperty.call(_model, attr)) {
                    changes.push([attr, undefined, hui.BaseModel.clone(attrs[attr])]);
                    _model[attr] = newValue;
                }
                else if (!hui.BaseModel.isEqual(_model[attr], attrs[attr])) {
                    changes.push([attr, hui.BaseModel.clone(_model[attr]), hui.BaseModel.clone(attrs[attr])]);
                    _model[attr] = attrs[attr];
                }
                // IE6,7 can not use JSON.stringify(), just use simple compare.
                else if (_model[attr] !== attrs[attr]) {
                    changes.push([attr, hui.BaseModel.clone(_model[attr]), hui.BaseModel.clone(attrs[attr])]);
                    _model[attr] = attrs[attr];
                }
            }

            // Trigger all relevant attribute changes.
            for (var i = 0, len = changes.length; i < len; i++) {
                this.trigger('change:' + changes[i][0], changes[i][1], changes[i][2]);
            }
            if (changes.length) {
                this.trigger('change');
            }
        };

        /**
         * @name 获取指定属性值
         * @param {String} propertyName 属性名.
         * @return {*} 属性的值.
         */
        this.get = function (propertyName) {
            return hui.BaseModel.clone(_model[propertyName]);
        };
        /**
         * @name 获取所有的属性值
         * @return {Map} 所有的属性值.
         */
        this.getData = function () {
            return hui.BaseModel.clone(_model);
        };
        /**
         * @name 移除指定属性值
         * @param {String} propertyName 属性名.
         * @return {*} 属性的值.
         */
        this.remove = function (propertyName) {
            var value = _model[propertyName];
            this.set(propertyName, undefined);
            delete _model[propertyName];
            return value;
        };
        /**
         * @name 销毁Model
         * @return {void}
         */
        this.dispose = function () {
            this._listeners = undefined;
            _model = undefined;
        };

        var child = _model,
            parent = data;
        for (var key in parent) {
            if (parent.hasOwnProperty(key)) {
                child[key] = parent[key];
            }
        }
    };

    hui.BaseModel.isEqual = function (a, b, aStack, bStack) {
        // Identical objects are equal. `0 === -0`, but they aren't identical.
        // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
        if (a === b) {
            return a !== 0 || 1 / a == 1 / b;
        }
        // A strict comparison is necessary because `null == undefined`.
        if (a === null || b === null || a === undefined || b === undefined) {
            return a === b;
        }
        if (aStack === undefined || bStack === undefined) {
            aStack = [];
            bStack = [];
        }
        // Compare `[[Class]]` names.
        var className = Object.prototype.toString.call(a);
        if (className != Object.prototype.toString.call(b)) {
            return false;
        }
        switch (className) {
            // Strings, numbers, dates, and booleans are compared by value.
        case '[object String]':
            // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
            // equivalent to `new String("5")`.
            return a == String(b);
        case '[object Number]':
            // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
            // other numeric values.
            return a != +a ? b != +b : (a === 0 ? 1 / a === 1 / b : a === +b);
        case '[object Date]':
        case '[object Boolean]':
            // Coerce dates and booleans to numeric primitive values. Dates are compared by their
            // millisecond representations. Note that invalid dates with millisecond representations
            // of `NaN` are not equivalent.
            return +a == +b;
            // RegExps are compared by their source patterns and flags.
        case '[object RegExp]':
            return a.source == b.source &&
                a.global == b.global &&
                a.multiline == b.multiline &&
                a.ignoreCase == b.ignoreCase;
        }
        if (typeof a != 'object' || typeof b != 'object') return false;
        // Assume equality for cyclic structures. The algorithm for detecting cyclic
        // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
        var length = aStack.length;
        while (length--) {
            // Linear search. Performance is inversely proportional to the number of
            // unique nested structures.
            if (aStack[length] == a) return bStack[length] == b;
        }
        // Add the first object to the stack of traversed objects.
        aStack.push(a);
        bStack.push(b);

        var size = 0,
            result = true;
        // Recursively compare objects and arrays.
        if (className == '[object Array]') {
            // Compare array lengths to determine if a deep comparison is necessary.
            size = a.length;
            result = size == b.length;
            if (result) {
                // Deep compare the contents, ignoring non-numeric properties.
                while (size--) {
                    if (!(result = hui.BaseModel.isEqual(a[size], b[size], aStack, bStack))) break;
                }
            }
        }
        else {
            // Objects with different constructors are not equivalent, but `Object`s
            // from different frames are.
            var aCtor = a.constructor,
                bCtor = b.constructor;
            if (aCtor !== bCtor && !(Object.prototype.toString.call(aCtor) == '[object Function]' && (aCtor instanceof aCtor) &&
                Object.prototype.toString.call(bCtor) == '[object Function]' && (bCtor instanceof bCtor))) {
                return false;
            }
            // Deep compare objects.
            for (var key in a) {
                if (Object.prototype.hasOwnProperty.call(a, key)) {
                    // Count the expected number of properties.
                    size++;
                    // Deep compare each member.
                    if (!(result = Object.prototype.hasOwnProperty.call(b, key) && hui.BaseModel.isEqual(a[key], b[key], aStack, bStack))) break;
                }
            }
            // Ensure that both objects contain the same number of properties.
            if (result) {
                for (key in b) {
                    if (Object.prototype.hasOwnProperty.call(b, key) && !(size--)) break;
                }
                result = !size;
            }
        }
        // Remove the first object from the stack of traversed objects.
        aStack.pop();
        bStack.pop();

        return result;
    };
    hui.inherits(hui.BaseModel, hui.EventDispatcher);

    /** 
     * @name对一个object进行深度拷贝
     * @param {Any} source 需要进行拷贝的对象.
     * @param {Array} oldArr 源对象树索引.
     * @param {Array} newArr 目标对象树索引.
     * @return {Any} 拷贝后的新对象.
     */
    hui.BaseModel.clone = function (source, oldArr, newArr) {
        if (typeof source === 'undefined') {
            return undefined;
        }
        if (typeof JSON !== 'undefined') {
            return JSON.parse(JSON.stringify(source));
        }

        var result = source,
            i,
            len,
            j,
            len2,
            exist = -1;
        oldArr = oldArr || [];
        newArr = newArr || [];

        if (source instanceof Date) {
            result = new Date(source.getTime());
        }
        else if ((source instanceof Array) || (Object.prototype.toString.call(source) == '[object Object]')) {
            for (j = 0, len2 = oldArr.length; j < len2; j++) {
                if (oldArr[j] == source) {
                    exist = j;
                    break;
                }
            }
            if (exist != -1) {
                result = newArr[exist];
                exist = -1;
            }
            else {
                if (source instanceof Array) {
                    result = [];
                    oldArr.push(source);
                    newArr.push(result);
                    var resultLen = 0;
                    for (i = 0, len = source.length; i < len; i++) {
                        result[resultLen++] = hui.BaseModel.clone(source[i], oldArr, newArr);
                    }
                }
                else if (!!source && Object.prototype.toString.call(source) == '[object Object]') {
                    result = {};
                    oldArr.push(source);
                    newArr.push(result);
                    for (i in source) {
                        if (source.hasOwnProperty(i)) {
                            result[i] = hui.BaseModel.clone(source[i], oldArr, newArr);
                        }
                    }
                }
            }
        }

        return result;
    };
});