﻿//<using>
//  nui/nui.js
//  nui/prototype.js
//  nui/lang.js
//</using>

/**
 * NUI.Util 
 * Copyright 2007, NetRanking.cn
 *
 * @namespace NUI.Util
 */
NUI.Util = {
    ObjectService : {
        walkObject : function() {},
        searchObject : function() {},
        annex : function() {},
        clone : function() {},
        sameAs : function() {},
        remove : function() {},
        hasValue : function() {}
    },
    StringService : {
        StringBuilder : function(vInitValue) {},
        increaseChar : function(vChar, vIncreasement) {}
    },
    MathService : {
        toInt : function(vValue) {},
        toFloat : function(vValue) {}
    },
    WindowService : {
        Geometry : {},
        getNodeRect : function(vNode){},
        getFrameWindow : function(vFrame){},
        getSelectedText : function(vWindow){},
        Path : {
            imagePath : function(vSubPath){},
            jsPath : function(vSubPath){},
            flashPath : function(vSubPath){},
            urlPath : function(vSubPath){},
            pageName : function(){},
            getParam : function(vKey) {},
            setParam : function(vKey, vValue) {},
            getAnchor : function(vKey) {},
            setAnchor : function(vKey, vValue) {}
        }
    },
    EnvService : {
        browser : {}
    },
    DomService : {
        walkTheDom : function(vNode, vFunc){},
        searchTheDOM : function(vNode, vStopFunc){},
        
        createNode : function(vTag, vAttributes, vDocument){},
        getNode : function(vNode, vParent){},
        appendNode : function(vParent, vNode){},
        pushNode : function(vParent, vNode){},
        insertBefore : function(vTarget, vNode){},
        insertBehind : function(vTarget, vNode){},
        replaceNode : function(vTarget, vNode){},
        removeNode : function(vNode){},
        removeChildren : function(vNode){},
        destroyNode : function(vNode){},
        destroyChildren : function(vNode){},
        setChild : function(vParent, vChild){},
        getParent : function(vChild){},
        
        setAttributes : function(vNode, vAttributes){},
        setInnerHTML : function(vNode, vHtml){},
        
        getNodeByCssClass : function(vClassName) {},
        getCssClasses : function(vNode){},
        hasCssClass : function(vNode, vClassName){},
        addCssClass : function(vNode, vClassName){},
        removeCssClass : function(vNode, vClassName){},
        setCssClass : function(vNode, vClassNames){},
        replaceCssClass : function(vNode, vOldClassName, vNewClassName){},
        
        show : function(vNodes) {},
        hide : function(vNodes) {},
        
        isButton : function(vNode) {},
        isTextInput : function(vNode) {},
        isTextArea : function(vNode) {},
        isImage : function(vNode) {},
        isDiv : function(vNode) {},
        isCheckBox : function(vNode) {},
        isRadio : function(vNode) {},
        isLink : function(vNode) {},
        isSpan : function(vNode) {}
    },
    EventService : {
        purgeEventHandlers : function(vNode, vExcludeParent){},
        attachEventHandler : function(vNode, vEventType, vFunc){},
        appendTemporarily : function(vNode, vEventType, vFunc){},
        cancelBubble : function(){},
        
        attachEnterDownEvent : function(vNode, vFunc) {}
    },
    DataStructure : {
        HashTable : function() {},
        Sheet : function() {}
    }
};

/**
 * Global functions to deal with object
 */
NUI.Util.ObjectService = {
    /**
     * Walk throught the object's own property. execute an function
     * @method walkObject
     * @param vFunc {function} The function to execute.
     */
    walkObject : function(vObj, vFunc) {
        if(vObj == null) {
            return;
        }
        for (var name in vObj) {
            if (vObj.hasOwnProperty(name)) {
                vFunc(name);
            }
        }
    },
    
    /**
     * Search the object. Stop seaching if the stop function return true
     * @method searchObject
     * @param vStopFunc {function} The stop function.
     */
    searchObject : function(vObj, vStopFunc) {
        if(vObj == null) {
            return;
        }
        for (var name in vObj) {
            if (vObj.hasOwnProperty(name)) {
                if(vStopFunc(name)) {
                    break;
                }
            }
        }
    },
    
    /**
     * Annex another object. e.t. get all the methods and attributes from another object.
     * @method annex
     * @param vObjA {object} The object to annex other object.
     * @param vObjB {object} The object to be annexed.
     */
    annex : function (vObjA, vObjB) {                                  //!!!!!!! This method should be tested!
        if(vObjA == null || vObjB == null) {
            return;
        }
        NUI.Util.ObjectService.walkObject(vObjB, function(vName) {
            vObjA[vName] = vObjB[vName];
        });
        return vObjA;
    },
    
    /**
     * Clone the object     // 注意，该方法在克隆HashTable的时候失效，副本的getValue("key")和原始元素的getValue("key")指向同样的值
     * @method clone
     */
    clone : function(vObj) {
        function _clone(obj) {
	        if(obj == null || typeof(obj) != 'object') {
                return obj;
            }

            var retval = new obj.constructor();
            for(var key in obj) {
                if (obj.hasOwnProperty(key)) {        // 需要这个if？没有的话会把object.prototype.*都复制一遍
                    retval[key] = _clone(obj[key]);
                }
            }

            return retval;
        }
        
        var newObj = _clone(vObj);
        return newObj;
    },
    
    /**
     * Check whether the object containe the same values with vObj.
     * NOTE : "==" means two pointer point to the same object. 
     * This method means two pointers point to the different object
     * @method sameAs
     */
    sameAs : function(vObjA, vObjB) {
        function _same(objA, objB) {
            if(objA === null) {
                return (objB === null);
            } else if(objB === null) {
                return false;
            } else {
                if(typeof(objA) != "object") {
                    if(typeof(objB) != "object") {
                        return (objA === objB);
                    } else {
                        return false;
                    }
                } else if(typeof(objB) != "object"){
                    return false;
                } else {
                    for(var keyA in objA) {
                        if (objA.hasOwnProperty(keyA)) {
                            if(!_same(objA[keyA], objB[keyA])){
                                return false;
                            }
                        }
                    }
                    for(var keyB in objB) {
                        if (objB.hasOwnProperty(keyB)) {
                            if(!_same(objA[keyB], objB[keyB])){
                                return false;
                            }
                        }
                    }
                }
            }

            return true;
        }
        
        return _same(vObjA, vObjB);
    },
    
    /**
     * remove a property from the object
     * @method remove
     * @param vName {string}. The name of the attribute to be removed
     */
    remove : function(vObj, vName) {
        if(vObj == null) {
            return;
        }
        delete vObj[vName];
    },
    
    /**
     * Check if object has an attribute that is a certain value
     * @method hasValue
     * @param vValue {any type}. The value
     */
    hasValue : function(vObj, vValue) {
        if(vObj == null) {
            return false;
        }
        var find = false;
        NUI.Util.ObjectService.searchObject(vObj, function(vName) {
            find = (vObj[vName] === vValue);
            return find;
        });
        return find;
    }
};

/**
 * @class NUI.Util.StringService.StringBuilder
 * @param vInitValue {string | boolean | number} The init value of stringbuilder
 */
NUI.Util.StringService.StringBuilder = function(vInitValue){
    var lang = NUI.Lang,
        m_value = [];
    
    /**
     * Check whether vValue is legal or not.
     * @method _isValueLegal
     * @private
     * @param vValue {string | number | boolean} Element's key
     * @return {boolean} Return ture if the type of vValue is string, number or boolean.
     */
    function _isValueLegal(vValue) {
        return lang.isString(vValue) || lang.isNumber(vValue) || lang.isBoolean(vValue);
    }
    
    if(_isValueLegal(vInitValue)){
        m_value[m_value.length] = vInitValue;
    }
        
    return {
        /**
         * Append the arguments behind the string.
         * @method append
         */
        append : function() {
            var len = arguments.length;
            for(var i=0; i<len; i++) {
                var v = arguments[i];
                if(_isValueLegal(v)){
                    m_value[m_value.length] = v;
                }
            }
        },
        
        /**
         * Get the result string of the stringbuilder.
         * @method toString
         * @return {String} The result string.
         */
        toString : function() {
            return m_value.join("");
        }
    };
};

/**
 * Increase an char. For example: a+1=b
 * @class NUI.Util.StringService
 * @method increaseChar
 * @param vChar {string} The target char. IMPORTANT: the length of the vChar is and should be 1.
 * @param vIncreasement {int}
 * @return The target window
 */
NUI.Util.StringService.increaseChar = function(vChar, vIncreasement) {
    var lang = NUI.Lang;
    if(!lang.isString(vChar) || !lang.isNumber(vIncreasement)) {
        return null;
    }
    if(vChar.length != 1) {
        return null;
    }
    return String.fromCharCode(vChar.charCodeAt(0) + vIncreasement);
};

/**
 * @class NUI.Util.MathService
 */
NUI.Util.MathService = (function(){
    return {
        toInt : function(vValue) {
            return parseInt(vValue, 10);
        },
        toFloat : function(vValue) {
            return parseFloat(vValue);
        }
    };
})();

/**
 * Get the window or document's size
 * @class NUI.Util.Geometry
 * @object Geometry
 * @methods : getWindowX, getWindowY, getViewportWidth, getViewportHeight, 
 *     getHorizontalScroll, getVerticalScroll, getDocumentWidth, getDocumentHeight
 */
NUI.Util.WindowService.Geometry = (function( ) {
    var that = {};
    
    if (window.screenLeft) { // IE and others
        that.getWindowX = function( ) { return window.screenLeft; };
        that.getWindowY = function( ) { return window.screenTop; };
    }
    else if (window.screenX) { // Firefox and others
        that.getWindowX = function( ) { return window.screenX; };
        that.getWindowY = function( ) { return window.screenY; };
    }

    if (window.innerWidth) { // All browsers but IE
        that.getViewportWidth = function( ) { return window.innerWidth; };
        that.getViewportHeight = function( ) { return window.innerHeight; };
        that.getHorizontalScroll = function( ) { return window.pageXOffset; };
        that.getVerticalScroll = function( ) { return window.pageYOffset; };
    }
    else if (document.documentElement && document.documentElement.clientWidth) {
        // These functions are for IE 6 when there is a DOCTYPE
        that.getViewportWidth = function( ) { return document.documentElement.clientWidth; };
        that.getViewportHeight = function( ) { return document.documentElement.clientHeight; };
        that.getHorizontalScroll = function( ) { return document.documentElement.scrollLeft; };
        that.getVerticalScroll = function( ) { return document.documentElement.scrollTop; };
    }
    else if (document.body.clientWidth) {
        // These are for IE4, IE5, and IE6 without a DOCTYPE
        that.getViewportWidth = function( ) { return document.body.clientWidth; };
        that.getViewportHeight = function( ) { return document.body.clientHeight; };
        that.getHorizontalScroll = function( ) { return document.body.scrollLeft; };
        that.getVerticalScroll = function( ) { return document.body.scrollTop; };
    }

    // These functions return the size of the document. They are not window
    // related, but they are useful to have here anyway.
    if (document.documentElement && document.documentElement.scrollWidth) {
        that.getDocumentWidth = function( ) { return document.documentElement.scrollWidth; };
        that.getDocumentHeight = function( ) { return document.documentElement.scrollHeight; };
    }
    else if (document.body.scrollWidth) {
        that.getDocumentWidth = function( ) { return document.body.scrollWidth; };
        that.getDocumentHeight = function( ) { return document.body.scrollHeight; };
    }
    
    NUI.Util.ObjectService.annex(that, {
        getAbsoluteRect : function(vNode) {
            var node = NUI.Util.DomService.getNode(vNode);
            if(!node) {
                return null;
            }
            var pos = { left: 0, top: 0, width: 0, height: 0 };
            pos.width = node.offsetWidth;
            pos.height = node.offsetHeight;
            while (node != null) {
                pos.left += node.offsetLeft;
                pos.top += node.offsetTop;
                node = node.offsetParent;
            }
            return pos;
        }
    });
    
    return that;
})();

/**
 * Get the target node's top, left, width , height
 * @class NUI.Util.WindowService
 * @method getNodeRect
 * @param vNode {string | object} The id or object of the target node
 */
NUI.Util.WindowService.getNodeRect = function(vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(node == null) {
        return null;
    }
    var pos = { 
        left: 0, 
        top: 0, 
        width: 0, 
        height: 0 
    };
    pos.width = node.offsetWidth;
    pos.height = node.offsetHeight;
    while (node != null) {
        pos.left += node.offsetLeft;
        pos.top += node.offsetTop;
        node = node.offsetParent;
    }
    return pos;
};

/**
 * Get the target frame's container window
 * @class NUI.Util.WindowService
 * @method getFrameWindow
 * @param vFrame {string | object} The id of the frame(string) or the frame(object)
 * @return The target window
 */
NUI.Util.WindowService.getFrameWindow = function(vFrame){
    var lang = NUI.Lang;
    
    var obj = null;
    if(lang.isObject(vFrame)) {
        obj = vFrame;
    } else if(lang.isString(vFrame)) {
        obj = NUI.Util.DomService.getNode(vFrame);
    }
        
    if(obj != null) {
        try {
            obj = obj.contentWindow;
        } catch(ex) {}
    }
    return obj;
};
$W = $W || NUI.Util.WindowService.getFrameWindow;

/**
 * Select the target window's selection
 * @class NUI.Util.WindowService
 * @method getSelectedText
 * @param vWindow {object} The window
 * @return The selected text
 */
NUI.Util.WindowService.getSelectedText = function(vWindow){
    if(vWindow == null) {
        vWindow = window;
    }
    var retval = null;
    if (vWindow.getSelection) {
        retval = vWindow.getSelection().toString();
    }
    else if (vWindow.document.getSelection) {
        retval = vWindow.document.getSelection();
    }
    else if (vWindow.document.selection) {
        var rge = vWindow.document.selection.createRange();
		rge.select();
        retval = rge.text;
    }
    return retval;
};

/**
 * Deal with the file/image/video/audio pathes of NUI
 * @class NUI.Util.WindowService.Path
 */
NUI.Util.WindowService.Path = (function () {
    var m_imagePre = "/Public/image/",
        m_jsPre = "/Public/javascript/",
        m_flashPre = "/Public/flash/",
        m_urlPre = "/Page/";
    
    return {
        imagePath : function(vSubPath) {
            return m_imagePre + vSubPath;
        },
        jsPath : function(vSubPath) {
            return m_jsPre + vSubPath;
        },
        flashPath : function(vSubPath) {
            return m_flashPre + vSubPath;
        },
        urlPath : function(vSubPath) {
            return m_urlPre + vSubPath;
        },
        pageName : function(){
            var p = location.href;
	        var retval = p.substr(p.lastIndexOf("/")+1);
	        retval = retval.substr(0, retval.indexOf("."));
	        return retval;
        },
        getParam : function(vKey){
            if(vKey == null) {
                return null;
            } 
            var searchstr = location.search;
            if(searchstr==null || searchstr.trim()=="") {
                return null;
            }
            var params = searchstr.substr(1, searchstr.length-1).split("&");
            for(var i=0; i<params.length; i++) {
                var keyandvalue = params[i].split("=");
                if(keyandvalue[0]==vKey) {
                    return unescape(keyandvalue[1]);
                }
            }
            return null;
        },
        setParam : function(vKey, vValue) {
            if(arguments == null) {
                return;
            }
            var ht = new NUI.Util.DataStructure.HashTable();
            // old params
            var searchstr = location.search;
            if(searchstr != null) {
                var params = searchstr.substr(1, searchstr.length-1).split("&");
                for(var i=0; i<params.length; i++) {
                    if(params[i] == null || params[i].trim() == "") {
                        continue;
                    }
                    var keyandvalue = params[i].split("=");
                    if(keyandvalue[0].trim() != ""){
                        ht.setValue(keyandvalue[0], unescape(keyandvalue[1]));
                    }
                }
            }
            // new params
            for (var i=0; i+1<arguments.length; i+=2) {
                var key = arguments[i], value = arguments[i+1];
                if(key==null || value==null || key.trim()=="") {
                    continue;
                }
                ht.setValue(key, value);
            }
            location.search = (function() {
                var retval = "";
                ht.walkTable(function(vName, vValue) {
                    retval += "&" + vName + "=" + escape(vValue);
                });
                if(retval!="") {
                    retval = retval.substr(1, retval.length-1);
                }
                return retval;
            })();
        },
        getAnchor : function(vKey){
            if(vKey == null) {
                return null;
            } 
            var anchorstr = location.hash;
            if(anchorstr==null || anchorstr.trim()=="") {
                return null;
            }
            var params = anchorstr.substr(1, anchorstr.length-1).split("&");
            for(var i=0; i<params.length; i++) {
                var keyandvalue = params[i].split("=");
                if(keyandvalue[0]==vKey) {
                    return unescape(keyandvalue[1]);
                }
            }
            return null;
        },
        setAnchor : function(vKey, vValue) {
            if(vKey == null || vValue == null) {
                return null;
            }
            var anchorstr = location.hash;
            if(anchorstr==null || anchorstr.trim()=="") {
                location.hash = vKey + "=" + escape(vValue);
                return;
            }
            var params = anchorstr.substr(1, anchorstr.length-1).split("&");
            var anchor_new_value = vKey + "=" + escape(vValue);
            for(var i=0; i<params.length; i++) {
                if(params[i] == null || params[i].trim() == "") {
                    continue;
                }
                var keyandvalue = params[i].split("=");
                if(keyandvalue[0] != vKey){
                    anchor_new_value += "&" + keyandvalue[0] + "=" + keyandvalue[1];
                }
            }
            location.hash = anchor_new_value;
        }
    };
})();

/**
 * Get the target window's selection
 * @class NUI.Util.EnvService
 * @variable browser
 * @return The object that contain information of the current browser.
 */
NUI.Util.EnvService.browser = (function(){
    var userAgent = userAgent || navigator.userAgent.toLowerCase();
    return {
        version : (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
        safari : /webkit/.test( userAgent ),
        opera : /opera/.test( userAgent ),
        ie : /msie/.test( userAgent ) && !/opera/.test( userAgent ),
        mozilla : /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
        
//        ie :     !!(window.attachEvent && !window.opera),
//        opera :  !!window.opera,
//        webkit : navigator.userAgent.indexOf('AppleWebKit/') > -1,
//        gecko :  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
    };
})();

/**
 * Apply a function to the target node and its offsprings.
 * @class NUI.Util.DomService
 * @method walkTheDOM
 * @param vNode {object | string} The root node to walk.
 * @param vFunc {function} The function to apply to the nodes.
 */
NUI.Util.DomService.walkTheDOM = function (vNode, vFunc) {
    if(!vFunc) {
        return;
    }
    var node = NUI.Util.DomService.getNode(vNode);
    function _walk(_node, _func) {
        if(!_node) {
            return;
        }
        _func(_node);
        _node = _node.firstChild; 
        while (_node) { 
            _walk(_node, _func); 
            _node = _node.nextSibling;
        } 
    }
    _walk(node, vFunc);
};

/**
 * Apply a function to the target node and its offsprings.
 * @class NUI.Util.DomService
 * @method walkTheDOM
 * @param vNode {object | string} The root node to walk.
 * @param vStopFunc {function} The function to search the nodes. If vStopFunc return true,stop the recursion.
 */
NUI.Util.DomService.searchTheDOM = function (vNode, vStopFunc) {
    if(!vStopFunc) {
        return;
    }
    var node = NUI.Util.DomService.getNode(vNode);
    function _search(_node, _stopfunc) {
        if(!_node) {
            return false;
        }
        var stop = _stopfunc(_node);
        if(stop) {
            return true;
        }
        _node = _node.firstChild; 
        while (!stop && _node) { 
            stop = _search(_node, _stopfunc); 
            _node = _node.nextSibling;
        }
        return stop;
    }
    return _search(node, vStopFunc);
};

/**
 * Apply a function to the target node and its offsprings.
 * @class NUI.Util.DomService
 * @method createNode
 * @param vTag {string} Tag name of new node.
 * @param vAttributes {object} The attributes of the created node. Including id, onclick etc.
 * @param vDocument {object} The container document of the created node. Default value is "document".
 */
NUI.Util.DomService.createNode = function (vTag, vAttributes, vDocument) {
    if(!vTag || !NUI.Lang.isString(vTag)) {
        return;
    }
    var doc = vDocument || document;
    var retval = doc.createElement(vTag);
    NUI.Util.DomService.setAttributes(retval, vAttributes);
    return retval;
};

/**
 * Get a dom node.
 * @class NUI.Util.DomService
 * @method getNode
 * @param vNode {object | string} The target node or its id
 * @param vParent {object} The target node's root node or the document that contain it.
 *      {
 *          document : document,
 *          root : root node
 *      }
 The container document of the target node. Default value is "document".
 * @return The target node.
 */
NUI.Util.DomService.getNode = function (vNode, vParent) {
    if(!vNode) {
        return null;
    }
    var doc = (vParent && vParent.document) || document;
    var root = vParent && vParent.root;
    switch(typeof vNode) {
        case "string":
            var retval = null;
            if(root) {
                NUI.Util.DomService.searchTheDOM(root, function(_node){
                    if(_node.id == vNode) {
                        retval = _node;
                        return true;
                    }
                    return false;
                });
            } else {
                retval = doc.getElementById(vNode);
            }
            return retval;
        case "object":
            if(NUI.Lang.isArray(vNode)) {
                return null;
            } else {
                return vNode;
            }
            break;
        default :
            return null;
    }
};
//$ = NUI.Util.DomService.getNode;

/**
 * Append a node to a parent node
 * @class NUI.Util.DomService
 * @method appendNode
 * @param vParent {object | string} The parent node for the new node to append to.
 * @param vNode {object} The node to insert.
 */
NUI.Util.DomService.appendNode = function (vParent, vNode) {
    var parent = NUI.Util.DomService.getNode(vParent);
    if(!parent || !vNode) {
        return;
    }
    parent.appendChild(vNode);
};

/**
 * Push a node into a parent node. The pushed node will be the first child of the parent node.
 * @class NUI.Util.DomService
 * @method pushNode
 * @param vParent {object | string} The parent node for the new node to append to.
 * @param vNode {object} The node to insert.
 */
NUI.Util.DomService.pushNode = function (vParent, vNode) {
    var parent = NUI.Util.DomService.getNode(vParent);
    if(!parent || !vNode) {
        return;
    }
    var first_child = vParent.firstChild;
    if(first_child == null) {
        parent.appendChild(vNode);
    } else {
        parent.insertBefore(vNode, vParent.firstChild);
    }
};

/**
 * Insert vNode behind vTarget
 * @class NUI.Util.DomService
 * @method insertBefore
 * @param vTarget {object | string} The existing node for the new node to insert before.
 * @param vNode {object} The node to insert.
 */
NUI.Util.DomService.insertBefore = function (vTarget, vNode) {
    var target = NUI.Util.DomService.getNode(vTarget);
    if(!target || !vNode) {
        return;
    }
    var p = target.parentNode;
    if(!p) {
        return;
    }
    p.insertBefore(vNode, target);
};

/**
 * Insert vNode behind vTarget
 * @class NUI.Util.DomService
 * @method insertBehind
 * @param vTarget {object | string} The existing node for the new node to insert behind.
 * @param vNode {object} The node to insert.
 */
NUI.Util.DomService.insertBehind = function (vTarget, vNode) {
    var target = NUI.Util.DomService.getNode(vTarget);
    if(!target || !vNode) {
        return;
    }
    var p = target.parentNode,
        s = target.nextSibling;
    if(!p) {
        return;
    }
    if(!s) {
        p.appendChild(vNode);
    } else {
        p.insertBefore(vNode, s);
    }
};

/**
 * Use vNode to replace vTarget
 * @class NUI.Util.DomService
 * @method replaceNode
 * @param vTarget {object | string} The existing node to be replaced.
 * @param vNode {object} The node to insert.
 */
NUI.Util.DomService.replaceNode = function (vTarget, vNode) {
    var target = NUI.Util.DomService.getNode(vTarget);
    if(!target || !vNode) {
        return;
    }
    var p = target.parentNode;
    if(!p) {
        return;
    }
    p.replaceChild(vNode, target);
};

/**
 * Remove the target node and its offsprings. 
 * IMPORTANT : Notice the difference between removeNode and destroyNode
 * @class NUI.Util.DomService
 * @method removeNode
 * @param vNode {object | string} The root node to remove.
 */
NUI.Util.DomService.removeNode = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
    var p = node.parentNode;
    if(p) {
        p.removeChild(node);
    }
};

/**
 * Remove the target node's offsprings. 
 * Purge the event handlers first, and then clear the innerHTML.
 * @class NUI.Util.DomService
 * @method removeChildren
 * @param vNode {object | string} The target node.
 */
NUI.Util.DomService.removeChildren = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
    var child = node.firstChild;
    while(child) {
        node.removeChild(child);
        child = node.firstChild;
    }
};

/**
 * Destroy the target node and its offsprings. 
 * Purge the event handlers first, and then clear the innerHTML.
 * @class NUI.Util.DomService
 * @method destroyNode
 * @param vNode {object | string} The root node to remove.
 */
NUI.Util.DomService.destroyNode = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
    NUI.Util.EventService.purgeEventHandlers(node);
    var p = node.parentNode;
    if(p) {
        p.removeChild(node);
    }
};

/**
 * Destroy the target node's offsprings. 
 * Purge the event handlers first, and then clear the innerHTML.
 * @class NUI.Util.DomService
 * @method destroyChildren
 * @param vNode {object | string} The target node.
 */
NUI.Util.DomService.destroyChildren = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
    NUI.Util.EventService.purgeEventHandlers(node, true);
    node.innerHTML = "";
};

/**
 * Set the target node's child. 
 * Purge the event handlers first, and then clear the innerHTML.
 * @class NUI.Util.DomService
 * @method setChild
 * @param vParent {object | string} The target node.
 * @param vChild {object | string}
 */
NUI.Util.DomService.setChild = function (vParent, vChild) {
    var parent = NUI.Util.DomService.getNode(vParent);
    if(!parent) {
        return;
    }
    if(vChild) {
        var m_lang = NUI.Lang,
            m_dom = NUI.Util.DomService;
        if(m_lang.isObject(vChild)) {
            m_dom.destroyChildren(parent);
            m_dom.appendNode(parent, vChild);
        } else if(m_lang.isString(vChild) || m_lang.isNumber(vChild) || m_lang.isBoolean(vChild)) {
            m_dom.setInnerHTML(parent, vChild);
        }
    }
};

/**
 * Get the target node's parent. 
 * @class NUI.Util.DomService
 * @method getParent
 * @param vChild {object | string}
 */
NUI.Util.DomService.getParent = function (vChild) {
    var child = NUI.Util.DomService.getNode(vChild);
    if(!child) {
        return;
    }
    return child.parentNode;
};

/**
 * Set the node's attributes from an attributes object. Only the {number|boolean|string|function} attribute
 * will be set.
 * @class NUI.Util.DomService
 * @method setAttributes
 * @param vNode {object | string} The target node.
 * @param vAttributes {object} The node's attributes. Format:
 *      vAttributes = {
 *          id          : "buttonId",
 *          tooltip     : "buttontooltip",
 *          checked     : false,
 *          onclick     : function() {alert("onclickevent");},
 *          onmouseover : function() {alert("mouseover");},
 *          style       : {
 *              color : "#FF0",
 *              fontSize : "12px",
 *              ...
 *          },
 *          ...
 *      }
 */
NUI.Util.DomService.setAttributes = function (vNode, vAttributes) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !vAttributes) {
        return;
    }
    NUI.Util.ObjectService.walkObject(vAttributes, function(name) {
        var v = vAttributes[name];
        switch(typeof v) {
            case "string":
            case "number":
            case "boolean":
                node[name] = v;
                break;
            case "function":
                NUI.Util.EventService.attachEventHandler(node, name, v);
                break;
            case "object":
                NUI.Util.DomService.setAttributes(node[name], v);
                break;
        }
    });
};

/**
 * Get a dom node.
 * @class NUI.Util.DomService
 * @method setInnerHTML
 * @param vNode {object | string} The target node to set innerHTML
 * @return The first child of the newly vNode.
 */
NUI.Util.DomService.setInnerHTML = function (vNode, vHtml) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !NUI.Lang.isString(vHtml)) {
        return;
    }
    NUI.Util.DomService.destroyChildren(node);
    node.innerHTML = vHtml;
    return node.firstChild;
};

/**
 * Get the nodes that has the target css classname.
 * @class NUI.Util.DomService
 * @method getNodeByCssClass
 * @param vNode {object | string} The target node
 * @return An array containing the css classes of the node.
 */
NUI.Util.DomService.getNodeByCssClass = function (vClassName) {
    if(!NUI.Lang.isString(vClassName)) {
        return [];
    }
    var cssclass = vClassName.trim();
    if(cssclass == "") {
        return [];
    }
    var results = [];
    NUI.Util.DomService.walkTheDom(document.body, function (node) {
        if(NUI.Util.DomService.hasCssClass(node, cssclass)) {
            results[results.length] = node;
        }
    }); 
    return results; 
};

/**
 * Get the target node's css classes.
 * @class NUI.Util.DomService
 * @method getCssClasses
 * @param vNode {object | string} The target node
 * @return An array containing the css classes of the node.
 */
NUI.Util.DomService.getCssClasses = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
    var classes = node.className,
        retval = [];
    if(classes){
        var splits = classes.split(" ");
        for(var i=0; i<splits.length; i++) {
            if(splits[i]!="") {
                retval[retval.length] = splits[i];
            }
        }
    }
    return retval;
};

/**
 * Check whether the target node has a css class.
 * @class NUI.Util.DomService
 * @method hasCssClass
 * @param vNode {object | string} The target node
 * @param vClassName {string} The css class
 */
NUI.Util.DomService.hasCssClass = function (vNode, vClassName) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !NUI.Lang.isString(vClassName)) {
        return;
    }
    var classes = node.className;
    if(classes) {
        var reg = new RegExp("(\\s|^)" + vClassName + "(\\s|$)", "g");
        return reg.test(classes);       //这里的正则表达式还不够完善，需要先整理vClassName
    }
};

/**
 * Add a css class to a node.
 * @class NUI.Util.DomService
 * @method addCssClass
 * @param vNode {object} The target node
 * @param vClassName {string} The css class to added
 */
NUI.Util.DomService.addCssClass = function (vNode, vClassName) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !NUI.Lang.isString(vClassName)) {
        return;
    }
    if(NUI.Util.DomService.hasCssClass(node, vClassName)) {
        return;
    }
    var classes = node.className;
    if(classes) {
        node.className = classes + " " + vClassName;
    } else {
        node.className = vClassName;
    }
};

/**
 * Remove a css class from a node.
 * @class NUI.Util.DomService
 * @method removeCssClass
 * @param vNode {object | string} The target node
 * @param vClassName {string} The css class to removed
 * @return {boolean} True if the target has this css class.
 */
NUI.Util.DomService.removeCssClass = function (vNode, vClassName) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !NUI.Lang.isString(vClassName)) {
        return false;
    }
    if(!NUI.Util.DomService.hasCssClass(node, vClassName)) {
        return false;
    }
    var classes = node.className;
    if(classes) {
        var regexp = new RegExp(vClassName, "g");
        node.className = classes.replace(regexp, "");
        return true;
    }
    return false;
};

/**
 * Set a css class to a node.
 * @class NUI.Util.DomService
 * @method setCssClass
 * @param vNode {object | string} The target node
 * @param vClassNames {string} The css class to set
 */
NUI.Util.DomService.setCssClass = function (vNode, vClassNames) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !NUI.Lang.isString(vClassNames)) {
        return;
    }
    node.className = vClassNames;
};

/**
 * Replace a css class of a node. If the node does not contain the old classname, add the new classname to it
 * @class NUI.Util.DomService
 * @method replaceCssClass
 * @param vNode {object | string} The target node
 * @param vOldClassName {string} The old css class to removed
 * @param vNewClassName {string} The new css class to add
 */
NUI.Util.DomService.replaceCssClass = function (vNode, vOldClassName, vNewClassName) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node || !NUI.Lang.isString(vOldClassName) || !NUI.Lang.isString(vNewClassName)) {
        return false;
    }
    if(!NUI.Util.DomService.hasCssClass(node, vOldClassName)) {
        return false;
    }
    var classes = node.className;
    if(classes) {
        var regexp = new RegExp(vOldClassName, "g");
        node.className = classes.replace(regexp, vNewClassName);
        return true;
    }
    return false;
};

/**
 * Show element.
 * @class NUI.Util.DomService
 * @method show
 * @param vNodes {object | string} The target node or its id
 */
NUI.Util.DomService.show = function (vNodes) {
    if(arguments == null) {
        return;
    }
    var len = arguments.length,
        dom = NUI.Util.DomService;
    for(var i=0; i<len; i++) {
        var node = dom.getNode(arguments[i]);
        if(node != null) {
            dom.removeCssClass(node, "hide");
        }
    }
};

/**
 * Hide elements.
 * @class NUI.Util.DomService
 * @method hide
 * @param vNodes {object | string} The target node or its id
 */
NUI.Util.DomService.hide = function (vNodes) {
    if(arguments == null) {
        return;
    }
    var len = arguments.length,
        dom = NUI.Util.DomService;
    for(var i=0; i<len; i++) {
        var node = dom.getNode(arguments[i]);
        if(node != null) {
            dom.addCssClass(node, "hide");
        }
    }
};

/**
 * @class NUI.Util.DomService
 * @method isButton
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isButton = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="input" && node.type.toLowerCase()=="button");
};

/**
 * @class NUI.Util.DomService
 * @method isTextInput
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isTextInput = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="input" && node.type.toLowerCase()=="text");
};

/**
 * @class NUI.Util.DomService
 * @method isTextArea
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isTextArea = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="textarea");
};

/**
 * @class NUI.Util.DomService
 * @method isImage
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isImage = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="img");
};

/**
 * @class NUI.Util.DomService
 * @method isDiv
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isDiv = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="div");
};

/**
 * @class NUI.Util.DomService
 * @method isCheckBox
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isCheckBox = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="input" && node.type.toLowerCase()=="checkbox");
};

/**
 * @class NUI.Util.DomService
 * @method isRadio
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isRadio = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="input" && node.type.toLowerCase()=="radio");
};

/**
 * @class NUI.Util.DomService
 * @method isLink
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isLink = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="a");
};

/**
 * @class NUI.Util.DomService
 * @method isSpan
 * @param vNode {object | string} The target node or its id
 */
NUI.Util.DomService.isSpan = function (vNode) {
    var node = NUI.Util.DomService.getNode(vNode);
    return (node && node.tagName.toLowerCase()=="span");
};

/**
 * ATTENTION : This method consumes much resource and is SLOW. Think it over before using it! 
 * Clear all the event handler asigned to the target node and its offsprings.
 * This method should be called before clearing one element's innerHTML, in order to avoid memory leak of IE6.
 * @class NUI.Util.EventService
 * @method purgeEventHandlers
 * @param vNode {object | string} The root node to walk.
 * @param vExcludeParent {boolean} Indicate whether to clear the root node or not.
 */
NUI.Util.EventService.purgeEventHandlers = function (vNode, vExcludeParent) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
//    var lang = NUI.Lang;                              // 这个方法全面，但太慢了，下面的方法是折中的办法
//    function clear(vElmt) {
//        for (var n in vElmt) {
//            if (lang.isFunction(vElmt[n])) {
//                vElmt[n] = null;
//            }
//        }
//    }
    var clearElementProps = [
        'data',
        'onmouseover',
        'onmouseout',
        'onmousedown',
        'onmouseup',
        'ondblclick',
        'onclick',
        'onselectstart',
        'oncontextmenu'
    ];
    function clear(vElmt) {
        var len = clearElementProps.length;
        for(var i=0; i<len; i++) {
            if(vElmt[clearElementProps[i]]) {
                vElmt[clearElementProps[i]] = null;
            }
        }
    }
    if(!vExcludeParent) {
        NUI.Util.DomService.walkTheDOM(node, clear);
    } else {
        node = node.firstChild; 
        while(node) {
            NUI.Util.DomService.walkTheDOM(node, clear);
            node = node.nextSibling;
        }
    }
};

/**
 * Attach an event handler to a node
 * @class NUI.Util.EventService
 * @method attachEventHandler
 * @param vNode {object | string} The node to attach event handler to.
 * @param vEventType {string} The type of event. "onclick", "onmouseover"...
 * @param vFunc {function} The event handler function.
 */
NUI.Util.EventService.attachEventHandler = function (vNode, vEventType, vFunc) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
//    vNode[vEventType] = function(e) {
//        var evt = NUI.Util.EventService;
//        evt.eventInstance = e || event;
//        evt.eventSrcElement = evt.eventInstance.target || evt.eventInstance.srcElement;
//        vFunc.call(this);
//    };
    node[vEventType] = vFunc;
};

/**
 * Backup the old event hanlder first. And then appen a new handler before the old event handler.
 * After the event happend, restore the old hanlder back to the target node.
 * @class NUI.Util.EventService
 * @method switchTemporarily
 * @param vNode {object | string} The node to attach event handler to.
 * @param vEventType {string} The type of event. "onclick", "onmouseover"...
 * @param vFunc {function} The event handler function.
 */
NUI.Util.EventService.appendTemporarily = function (vNode, vEventType, vFunc) {
    var node = NUI.Util.DomService.getNode(vNode);
    if(!node) {
        return;
    }
    var backup = node[vEventType];
    node[vEventType] = function() {
        try {
            vFunc.call(node);
        } catch(ex1) {
        }
        node[vEventType] = backup;
        try {
            node[vEventType].call(node);
        } catch(ex2) {
        }
    };
};

/**
 * Cancel bubble
 * @method cancelBubble
 */
NUI.Util.EventService.cancelBubble = function () {
    if(event.stopPropagation) {
        event.stopPropagation();
    } else {
        event.cancelBubble=true;
    }
};

/**
 * Enter key down event of input element
 * @method attachEnterDownEvent
 * @param vNode {object | string}. The input element
 * @param vFunc(vText) {function}. The event
 */
NUI.Util.EventService.attachEnterDownEvent = function (vNode, vFunc) {
    var dom = NUI.Util.DomService,
        node = dom.getNode(vNode),
        func = vFunc ? vFunc : function() {};
    if(!dom.isTextInput(node) && !dom.isTextArea(node)) {
        return;
    }
    NUI.Util.EventService.attachEventHandler(node, "onkeydown", function() {
        var keyCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
        if (keyCode == 13) {
            func(node.value.trim());
        }
    });
};

/**
 * HashTable
 * @class NUI.Util.DataStructure.HashTable
 */
NUI.Util.DataStructure.HashTable = function () {
    var lang = NUI.Lang,
        m_collection = {},
        m_count = 0;
    
    /**
     * Check whether vKey is legal or not.
     * @method _isKeyLegal
     * @private
     * @param vKey {string | number | boolean} Element's key
     * @return {boolean} Return ture if the type of vKey is string, number or boolean.
     */
    function _isKeyLegal(vKey) {
        return lang.isString(vKey) || lang.isNumber(vKey) || lang.isBoolean(vKey);
    }
    
    function _key(vKey) {
        return vKey + "";
    }
    
    var that = {
        /**
         * Get the count of the collection's elements.
         * @method count
         * @return {number}.
         */
        count : function() {
            return m_count;
        },
        
        /**
         * Get the array representation of the collection's keys.
         * @method keys
         * @return {Array}.
         */
        keys : function() {
            var retval = [],
                id = 0;
            NUI.Util.ObjectService.walkObject(m_collection, function(m_key){
                var m_value = m_collection[m_key];
                if(!lang.isUndefined(m_value)) {
                    retval[id++] = m_key;
                }
            });
            return retval;
        },
        
        /**
         * Get the array representation of the collection's values.
         * @method values
         * @return {Array}.
         */
        values : function() {
            var retval = [],
                id = 0;
            NUI.Util.ObjectService.walkObject(m_collection, function(m_key){
                var m_value = m_collection[m_key];
                if(!lang.isUndefined(m_value)) {
                    retval[id++] = m_value;
                }
            });
            return retval;
        },
        
        /**
         * Get the array representation of the collection's elements.
         * @method elements
         * @param vKeyName {string}. The key's name
         * @param vValueName {string}. The value's name
         * @return {Array}.
         */
        elements : function(vKeyName, vValueName) {
            var keyName = vKeyName ? vKeyName : "key",
                valueName = vValueName? vValueName : "value",
                retval = [],
                id = 0;
            NUI.Util.ObjectService.walkObject(m_collection, function(m_key){
                var m_value = m_collection[m_key];
                if(!lang.isUndefined(m_value)) {
                    retval[id] = {};
                    retval[id][keyName] = m_key;
                    retval[id][valueName] = m_value;
                    id ++;
                }
            });
            return retval;
        },
        
        /**
         * Check whether vKey exists or not.
         * @method exist
         * @param vKey {string | number | boolean} Element's key
         * @return {boolean} Return ture if vKey is legal and vKey exists. else false.
         */
        exist : function(vKey) {
            if(!_isKeyLegal(vKey)) {
                return false;
            }
            return !lang.isUndefined( m_collection[_key(vKey)] );
        },
        
        /**
         * Get an element by its key.
         * @method getValue
         * @param vKey {string | number | boolean} Element's key
         * @return {any type} Return the target value is vKey exists, else null.
         */
        getValue : function(vKey) {
            if(_isKeyLegal(vKey)) {
                var retval = m_collection[_key(vKey)];
                if(!lang.isUndefined(retval)) {
                    return retval;
                }
            }
            return null;
        },
        
        /**
         * Add an element. increase m_count.
         * @method setValue
         * @param vKey {string | number | boolean} Element's key
         * @param vValue {any type} Element
         * @return {boolean} Return false if vKey is illegal. Else, add an the element into the collection.
         * ATTENTION : If vKey has already existed, overwrite it.
         */
        setValue : function(vKey, vValue){
            if(!_isKeyLegal(vKey)) {
                return false;
            }
            var key = _key(vKey);
            if(lang.isUndefined(m_collection[key])) {
                m_count ++;
            }
            m_collection[key] = vValue;
            return true;
        },
        
        /**
         * Add some elements.
         * @method setValues
         * @param vElems {array}. The elements to add. The structure of elements should be the same
         * @param vKeyName {string}. The name of the key in the element. ext. key = element.vKeyName
         * @param vValueName {string}. The name of the value in the element. ext. value = element.vValueName
         * @return {int}. The count of the elements added
         */
        setValues : function(vElems, vKeyName, vValueName) {
            if(vElems == null || vElems.length == 0) {
                return 0;
            }
            var keyName = (vKeyName!=null) ? vKeyName : "key",
                valueName = (vValueName!=null) ? vValueName : "value",
                len = vElems.length,
                retval = 0;
            for(var i=0; i<len; i++) {
                var elem = vElems[i];
                if(elem!=null) {
                    if(that.setValue(elem[keyName], elem[valueName])) {
                        retval ++;
                    }
                }
            }
            return retval;
        },
        
        /**
         * Remove an element.
         * @method remove
         * @param vKey {string | number | boolean} Element's key
         * @return {boolean} Return false if vKey is illegal. else true.
         * ATTENTION : If vKey doesn't exist, do nothing
         */
        remove : function(vKey) {
            if(!_isKeyLegal(vKey)) {
                return false;
            }
            var key = _key(vKey);
            if(!lang.isUndefined(m_collection[key])) {
                delete m_collection[key];   // 'delete' just set m_collection[key]-->undefined. 
                m_count --;                 // It has no affect to the original refrenced object of m_collection[key]
            }
            return true;
        },
        
        resetKey : function(vOldKey, vNewKey) {
            if(!_isKeyLegal(vOldKey) || !_isKeyLegal(vNewKey)) {
                return false;
            }
            var oldKey = _key(vOldKey),
                newKey = _key(vNewKey),
                ovalue = m_collection[oldKey],
                nvalue = m_collection[newKey];
            if(lang.isUndefined(ovalue) || !lang.isUndefined(nvalue)) {
                return false;
            }
            NUI.Util.ObjectService.remove(m_collection, oldKey);
            m_collection[newKey] = ovalue;
            return true;
        },
        
        /**
         * Walk through the hash table and execute an function
         * @param vFunc(key, value) {function}.
         */
        walkTable : function(vFunc) {
            NUI.Util.ObjectService.walkObject(m_collection, function(m_key){
                vFunc(m_key, m_collection[m_key]);
            });
        },
        
        /**
         * Search through the hash table and execute an function
         * @param vStopFunc(key, value) {function}.
         */
        searchTable : function(vStopFunc) {
            NUI.Util.ObjectService.searchObject(m_collection, function(m_key){
                return vStopFunc(m_key, m_collection[m_key]);
            });
        }
    };
    
    return that;
};

/**
 * Sheet
 * @class NUI.Util.DataStructure.Sheet
 */
NUI.Util.DataStructure.Sheet = (function () {
    var g_lang = NUI.Lang;
    
    /**
     * Constructor of Sheet
     * @param vData {array} 1 or 2 dimension array. The init data.
     */
    return function(vData) {
        /**
         * set init value
         */
        var m_data = [];
        if(g_lang.isArray(vData)) {
            for(var i=0; i<vData.length; i++) {
                if(g_lang.isArray(vData[i])) {
                    m_data[m_data.length] = NUI.Util.ObjectService.clone(vData[i]);
                    m_data[m_data.length-1].trimRight();
                } else {
                    m_data[m_data.length] = [];
                }
            }
            _trimY();
        }
        
        function _trimY() {
            var i = m_data.length-1;
            for(; i>=0; i--) {
                if(m_data[i] != []) {
                    break;
                }
            }
            m_data.splice(i+1, m_data.length-i-1);
        }
        
        var that = {
            /**
             * TODO : impliment these 4 functions
             */
            fill : function(vX, vY, vSheet, vIsRotated) {},
            clear : function(vX, vY, vXCount, vYCount) {},
            extend : function(vX, vY, vSheet, vIsRotated) {},
            shrink : function(vX, vY, vXCount, vYCount) {},
            
            /**
             * Get the x length of the sheet
             */
            getLengthX : function() {
                var retval = 0;
                for(var i=0; i<m_data.length; i++) {
                    if(m_data[i].length > retval) {
                        retval = m_data[i].length;
                    }
                }
                return retval;
            },
            
            /**
             * Get the y length of the sheet
             */
            getLengthY : function() {
                return m_data.length;
            },
            
            /**
             * Get and set a cell's value
             */
            getValue : function(vX, vY) {
                var retval = null;
                try {
                    retval = m_data[vY][vX];
                } catch(ex) {
                }
                return retval;
            },
            setValue : function(vX, vY, vValue) {
                if(!g_lang.isNumber(vX) || vX < 0 || !g_lang.isNumber(vY) || vY < 0) {
                    return;
                }
                while(m_data.length <= vY) {
                    m_data[m_data.length] = [];
                }
                var row = m_data[vY];
                while(row.length <= vX) {
                    row[row.length] = null;
                }
                row[vX] = vValue;
                row.trimRight();
                _trimY();
            }
        };
        return that;
    };
})();

