/**
 * common.js
 * 用于放置全局通用类及方法，全局事件等
 * IBOS
 * @author		inaki
 * @version		$Id: common.js 582 2013-06-13 09:50:56Z gzzcs $
 */
/**
 * Core
 * 命名空间注册机制
 * 继承机制
 * Event
 * 事件机制
 * Dom, Ui, Util
 
 * param type
 * Jquery  jquery查询得到的对象
 * Element 原生节点对象
 * Event   事件
 * Jevent  jq事件
 * String, Number, Object, Array, Function, RegExp...
 * 
 */

/**
 * 全局命名空间
 * @class Ibos
 * @static
 */
var Ibos = Ibos || {};
var L = L || {};

/**
 * 语言包入口
 * 读取语言包, 语言包定义于全局变量 L
 * 当data参数存在时，会调用模板
 * @method l
 * @for Ibos
 * @param {String} langNS 语言包命名空间
 * @param {Object} data  模板数据，当data === true 时，调用 L 对象
 */
Ibos.l = function(langNS, data) {
    var _langNs = langNS,
        lang;

    if (typeof L == "undefined") {
        return langNS;
    }

    lang = L;

    langNS = (langNS || "").split(".");

    for (var i = 0, ns; ns = langNS[i++];) {
        lang = lang[ns];
        if (!lang) {
            break;
        }
    }
    return data ? (data === true ? $.template(lang, L) : $.template(lang, data)) : lang || _langNs;
};

/**
 * 工具类，包括一些工具函数 
 * @class U
 * @static
 */
var U = Ibos.Util = (function() {

    var U = {
        /**
         * 判断值是否未定义， 未定义时返回true
         * @method isUnd
         * @param {Any} any  
         * @return {Boolean}
         */
        isUnd: function(prop) {
            return prop === void 0;
            //return typeof prop === "undefined";
        },

        /**
         * 内置正则集
         * @attribute reg
         * @type {Object}
         */
        reg: {
            'int': /^-?\d+$/,
            positiveInt: /^[1-9]\d*$/, // 正整数
            decimals: /^-?(([1-9]\d*(\.\d+))|(0\.\d+))$/, // 小数（不包含整数）
            positiveDecimals: /^(([1-9]\d*(\.\d+))|(0\.\d+))$/, // 正小数
            url: /^(http[s]?:\/\/)?([\w-]+\.)+[\w-]+([\w-.\/?%&=]*)?$/,
            email: /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/,
            zipcode: /^\d{6}$/,
            notempty: /\S+/,
            mobile: /^1\d{10}$/,
            tel: /^(([0\+]\d{2,3}-)?(0\d{2,3})-)?(\d{7,8})(-(\d{3,}))?$/, //电话号码的函数(包括验证国内区号,国际区号,分机号),
            currency: /^-?(\d{1,3})((\,\d{3})+)?(\.\d+)?$/ // 货币格式(包括千分号)
        },

        /**
         * 判断字符串是否符合某一类型正则
         * @method regex
         * @param  {String} str  要验证的字符串
         * @param  {String} type 正则类型，参考 U.reg
         * @see   U.reg
         * @return {[type]}      [description]
         */
        regex: function(str, type) {
            if (type && this.reg[type] && this.reg[type].test(str)) {
                return true;
            }
            return false;
        },

        /**
         * 判断值是否正整数
         * @method isPositiveInt
         * @param  {Number|String}  value 
         * @return {Boolean}
         */
        isPositiveInt: function(value) {
            return this.regex("" + value, 'positiveInt')
        },

        /**
         * 基于属性对比两个对象是否相等
         * @isEqualObject 
         * @param  {Object}  a 源对象
         * @param  {Object}  b 作为比较对象
         * @return {Boolean}   
         */
        isEqualObject: function(a, b) {
            var ret = true;
            if (typeof a !== "object" || typeof b !== "object") {
                return false;
            }
            for (var i in a) {
                if (a.hasOwnProperty(i)) {
                    if (typeof a[i] === "object") {
                        ret = this.isEqualObject(a[i], b[i]);
                        if (!ret) {
                            return false;
                        }
                    } else if (a[i] !== b[i]) {
                        return false;
                    }
                }
            }
            return ret;
        },

        /**
         * 获取url中参数段
         * @method getUrlParam
         * @param  {String} [url=window.location.href] url地址
         * @return {Object}                            参数集
         */
        getUrlParam: function(url) {
            var ret = {},
                index,
                lastIndex,
                str;

            url = url || window.location.href;
            index = url.indexOf("?") + 1;
            lastIndex = url.indexOf("#");
            lastIndex = (lastIndex == -1 ? url.length : lastIndex);

            str = url.substring(index, lastIndex);

            $.each(str.split("&"), function(index, s) {
                var kv = s.split("=");
                if (kv.length > 1) {
                    ret[kv[0]] = kv[1]
                }
            });

            return ret;
        },

        /**
         * 格式文件大小，将数字转为合理格式
         * formatFileSize(1024) => 1.00KB
         * @method formatFileSize
         * @param  {Number} size 		文件大小
         * @param  {Number} [dec=0]  	小数位，即默认保留多少位小数
         * @return {String}      		格式化的文件大小
         */
        formatFileSize: function(size, dec) {
            size = parseInt(size, 10);
            dec = dec || 0;
            if (isNaN(size)) {
                return '0B';
            } else {
                var unit = ["B", "KB", "MB", "GB"];
                var i = 0;
                while (size >= 1024) {
                    size = size / 1024;
                    if (++i == 3) break;
                }
                return size.toFixed(dec) + unit[i];
            }
        },
        /**
         * 为表单控件添加值(主要作用于text,hidden,textarea)，格式为 "a,b,c"
         * @method addValue 
         * @param {String|Element|Jquery} selector  选择器
         * @param {String} value    				目标值
         */
        addValue: function(selector, value) {
            var oldVal = $(selector).val();
            $(selector).val(oldVal ? oldVal + "," + value : value);
        },
        /**
         * 从表单控件中移除值(主要作用于text,hidden,textarea)，格式为 "a,b,c"
         * @method removeValue
         * @param {String|Element|Jquery} selector  选择器
         * @param {String} value    				目标值
         */
        removeValue: function(selector, value) {
            var oldVal = $(selector).val();
            if ($.trim(oldVal) !== "") {
                oldVal = ("," + oldVal + ",").replace(new RegExp("," + value + ",", "g"), ",");
                $(selector).val(oldVal.slice(1, -1));
            }
        },
        hasValue: function(selector, value) {
            var oldVal = $(selector).val();
            return ("," + oldVal + ",").indexOf("," + value + ",") !== -1;
        },
        /**
         * 获取对应文件类型icon地址
         * @DateTime 2017-10-10T15:35:19+0800
         * @author Clearlove
         * @param    {String}                 fileSuffix 文件类型后缀字符串
         * @return   {String}                            文件类型icon地址
         */
        getFileTypeIconUrl: function(fileSuffix){
            var ICON_PATH = 'static/image/filetype/', // 附件图片地址
                type;
            if($.inArray(fileSuffix, ['asf', 'avi', 'wm', 'wmp', 'wmv', 'ram', 'rm', 'rmvb', 'rp', 'rpm', 'rt', 'smil', 'scm', 
                'dat', 'm1v', 'm2v', 'm2p', 'm2ts', 'mp2v', 'mpe', 'mpeg', 'mpeg1', 'mpeg2', 'mpg', 'mpv2', 
                'pss', 'pva', 'tp', 'tpr', 'ts', 'm4b', 'm4p', 'mp4', 'mpeg4', '3g2', '3gp', '3gp2', '3gpp', 
                'mov', 'qt', 'mov', 'qt', 'flv', 'f4v', 'swf', 'hlv', 'ifo', 'vob', 'amv', 'csf', 'divx', 'evo', 
                'mkv', 'mod', 'pmp', 'vp6', 'bik', 'mts', 'xv', 'xlmv', 'ogm', 'ogv', 'ogx', 'dvd']) >= 0) {
                type = "video";
            } else if($.inArray(fileSuffix, ['aac', 'ac3', 'acc', 'aiff', 'amr', 'ape', 'au', 'cda', 'dts', 'flac', 'm1a', 'm2a', 
                'm4a', 'mka', 'mp2', 'mp3', 'mpa', 'mpc', 'ra', 'tta', 'wav', 'wma', 'wv', 'mid', 'midi', 'ogg', 
                'oga']) >= 0) {
                type = "music";
            } else if($.inArray(fileSuffix, ['xlsx', 'xlsm', 'xlsb', 'xltx', 'xltm', 'xlt', 'xls', 'xml', 'xlam', 'xla', 'xlw', 'csv']) >= 0) {
                type = "excel";
            } else if($.inArray(fileSuffix, ['pptx', 'pptm', 'ppt', 'potx', 'potm', 'pot', 'pps', 'ppsx', 'ppsm', 'ppam', 'ppa']) >= 0) {
                type = "ppt";
            } else if($.inArray(fileSuffix, ['doc', 'docm', 'docx', 'dot', 'dotm', 'dotx']) >= 0) {
                type = "word";
            } else if($.inArray(fileSuffix, ['php', 'js', 'pl', 'cgi', 'asp']) >= 0) {
                type = "code";
            } else if($.inArray(fileSuffix, ['jpg', 'gif', 'png', 'bmp']) >= 0) {
                type = "photo";
            } else if($.inArray(fileSuffix, ['txt', 'rtf', 'wri', 'chm']) >= 0) {
                type = "txt";
            } else if($.inArray(fileSuffix, ['fla', 'flv']) >= 0) {
                type = "fla";
            } else if($.inArray(fileSuffix, ['html', 'htm']) >= 0) {
                type = "html";
            } else if($.inArray(fileSuffix, ['swf', 'swi']) >= 0) {
                type = "swf";
            } else if('pdf' == fileSuffix) {
                type = "pdf";
            } else if('psd' == fileSuffix) {
                type = "psd";
            } else if('exe' == fileSuffix) {
                type = "exe";
            } else if('rar' == fileSuffix) {
                type = "rar";
            } else if('zip' == fileSuffix) {
                type = "zip";
            } else if('7z' == fileSuffix) {
                type = "7z";
            } else {
                type = "unknown";
            }

            return ICON_PATH + type + '_lt.png';
        }
    };

    /**
     * 设置cookie
     * @method setCookie
     * @param  {String} name    cookie标识符
     * @param  {String} value   cookie值，为空时删除该cookie
     * @param  {Number} [seconds] cookie有效周期，单位为秒，当传入值小于0时，会删除该cookie。
     * @return {[type]}         [description]
     */
    U.setCookie = function(name, value, seconds, path, domain, secure) {
            var SECOND_PER_MONTH = 2592000,
                expires = new Date();
            if (value === '' || seconds < 0) {
                seconds = -SECOND_PER_MONTH;
            }
            seconds = this.isUnd(seconds) ? SECOND_PER_MONTH : seconds;
            path = path || Ibos.app.g('cookiePath') || "";
            domain = domain || Ibos.app.g('cookieDomain') || "";
            expires.setTime(expires.getTime() + seconds * 1000);
            document.cookie = escape((Ibos.app.g('cookiePre') || "") + name) + '=' + escape(value) +
                (expires ? '; expires=' + expires.toGMTString() : '') +
                (path ? '; path=' + path : '') +
                (domain ? '; domain=' + domain : '') +
                (secure ? '; secure' : '');
        },

        /**
         * 获取cookie值
         * @method getCookie
         * @param  {String}  name                 cookie标识符
         * @param  {Boolean} [nounescape = false] 是否不使用unescape解码
         * @return {String}                       cookie值
         */
        U.getCookie = function(name, nounescape) {
            name = (Ibos.app.g('cookiePre') || "") + name;
            var cookieStart = document.cookie.indexOf(name),
                cookieEnd = document.cookie.indexOf(";", cookieStart);
            if (cookieStart == -1) {
                return '';
            } else {
                var v = document.cookie.substring(cookieStart + name.length + 1, (cookieEnd > cookieStart ? cookieEnd : document.cookie.length));
                return !nounescape ? unescape(v) : v;
            }
        },

        /**
         * 清除所有cookie值
         * @method clearCookie
         * @return {[type]} [description]
         */
        U.clearCookie = function(path) {
            var str = path ? 'path=' + path + ';' : '';
            var reg = /[^ =;]+(?=\=)/g,
                keys = document.cookie.match(reg);
            if (keys) {
                for (var i = keys.length; i--;) {
                    document.cookie = keys[i] + '=0;' + str + 'expires=' + new Date(0).toUTCString();
                }
            }
        }

    /**
     * 枚举出对象上所有可枚举的属性名
     * @method keys
     * @param  {Object} obj  要进行枚举的对象
     * @return {Array}       属性名组成的数组
     */
    U.keys = Object.keys || function(obj) {
        obj = Object(obj)
        var arr = []
        for (var a in obj) arr.push(a)
        return arr
    };

    /**
     * 对调对象的属性名与属性值
     * @method invertKeys
     * @param  {Object} obj  要进行key-value对调的对象
     * @return {Object}      对调完成后的新对象，不指向原对象
     */
    U.invertKeys = function(obj) {
        obj = Object(obj)
        var result = {}
        for (var a in obj) result[obj[a]] = a
        return result
    }

    /**
     * 获取字符串长度
     * 字母长度为0.5，中文长度为1，对url会做特殊处理
     * @method getCharLength
     * @param String str 要计算长度的字符串
     * @return {Number}  经过计算的长度值
     */
    U.getCharLength = (function() {
        var byteLength = function(str) {
            if (typeof str == "undefined") {
                return 0
            }

            var sChar = str.match(/[^\x00-\x80]/g);
            // 匹配出中文字符的数目并再次加上，这样子每个中文字符占位为2
            return (str.length + (!sChar ? 0 : sChar.length))
        };

        return function(str, opt) {
            opt = opt || {};
            opt.max = opt.max || 140;
            // opt.min = opt.min || 41;
            opt.surl = opt.surl || 20;
            var p = $.trim(str).length;
            if (p > 0) {
                // 下面这段正则用于匹配URL
                var result = str.match(/(http|https):\/\/[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)+([-A-Z0-9a-z\$\.\+\!\_\*\(\)\/\,\:;@&=\?~#%]*)*/gi) || [];
                var total = 0;
                for (var m = 0,
                        len = result.length; m < len; m++) {
                    var o = byteLength(result[m]);

                    total += o <= opt.max ? opt.surl : (o - opt.max + opt.surl)

                    str = str.replace(result[m], "")
                }
                return Math.ceil((total + byteLength(str)) / 2)
            } else {
                return 0
            }
        }
    })();

    /**
     * 对HTML进行实体转义处理的函数集
     */
    U.entity = (function() {
        var entityMap = {
            escape: {
                '&': '&amp;',
                '<': '&lt;',
                '>': '&gt;',
                '"': '&quot;',
                "'": "&apos;"
            }
        }
        entityMap.unescape = U.invertKeys(entityMap.escape);
        var entityReg = {
            escape: RegExp('[' + U.keys(entityMap.escape).join('') + ']', 'g'),
            unescape: RegExp('(' + U.keys(entityMap.unescape).join('|') + ')', 'g')
        }

        /**
         * 将HTML特殊字符转义为实体
         * @method entity.escape
         * @param  {String} html html文本
         * @return {String}      转义后的文本
         */
        function escape(html) {
            if (typeof html !== 'string') return ''
            return html.replace(entityReg.escape, function(match) {
                return entityMap.escape[match]
            })
        }
        /**
         * 将HTML实体转义为特殊字符
         * @method entity.unescape
         * @param  {String} html 转义后的html文本
         * @return {String}      html文本
         */
        function unescape(str) {
            if (typeof str !== 'string') return ''
            return str.replace(entityReg.unescape, function(match) {
                return entityMap.unescape[match]
            })
        }
        return {
            escape: escape,
            unescape: unescape
        }
    })();

    /**
     * 预读取图片
     * @method loadImage
     * @param  {String}   url      文件地址
     * @param  {Function} load     读取成功回调
     * @param  {Function} error    读取失败回调
     * @return {[type]}            [description]
     */
    U.loadImage = function(url, load, error) {
        var img = new Image(),
            loaded = false;
        img.onload = function() {
                img.onload = img.onerr = null;
                !loaded && load && load(img);
                loaded = true;
            }
            // 加载错误
        img.onerror = function() {
            img.onload = img.onerror = null;
            error && error(img);
        };
        img.src = url;
        if (img.complete) {
            loaded = true;
            load && load(img);
        }
    };

    /**
     * 用于动态加载JS、CSS文件
     * @method loadFile
     * @param {DocumentElement} 文档对象
     * @param {Object} 文件配置信息 {src|href, tag, id, *}
     * @param {Function} 加载成功回调
     * @return {[type]} [description]
     */
    U.loadFile = (function() {
        var tmpList = [];

        function getItem(doc, obj) {
            try {
                for (var i = 0, ci; ci = tmpList[i++];) {
                    if (ci.doc === doc && ci.url == (obj.src || obj.href)) {
                        return ci;
                    }
                }
            } catch (e) {
                return null;
            }

        }
        return function(doc, obj, fn) {
            var item = getItem(doc, obj);
            if (item) {
                if (item.ready) {
                    fn && fn();
                } else {
                    item.funs.push(fn)
                }
                return;
            }
            tmpList.push({
                doc: doc,
                url: obj.src || obj.href,
                funs: [fn]
            });
            if (!doc.body) {
                var html = [];
                for (var p in obj) {
                    if (p == 'tag') continue;
                    html.push(p + '="' + obj[p] + '"')
                }
                doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></' + obj.tag + '>');
                return;
            }
            if (obj.id && doc.getElementById(obj.id)) {
                return;
            }
            var element = doc.createElement(obj.tag);
            delete obj.tag;
            for (var p in obj) {
                element.setAttribute(p, obj[p]);
            }
            element.onload = element.onreadystatechange = function() {
                if (!this.readyState || /loaded|complete/.test(this.readyState)) {
                    item = getItem(doc, obj);
                    if (item.funs.length > 0) {
                        item.ready = 1;
                        for (var fi; fi = item.funs.pop();) {
                            fi();
                        }
                    }
                    element.onload = element.onreadystatechange = null;
                }
            };
            element.onerror = function() {
                throw Error('The load ' + (obj.href || obj.src) + ' fails')
            };
            doc.getElementsByTagName("head")[0].appendChild(element);
        }
    })();

    U.loadCss = function(href, callback) {
        if (document.getElementById(href)) {
            return false;
        }
        U.loadFile(document, {
            tag: "link",
            rel: "stylesheet",
            href: href,
            id: href
        }, callback)
    };

    /**
     * 用于将经过Jquery 系列化的表单数组转化为对象
     * @method serializedToObject
     * @deprecated            使用 $.fn.serializeObject 代替
     * @param  {Array} array  系列化后得到的数组
     * @return {Object}       解析后的对象
     */
    U.serializedToObject = function(serialized) {
        var data = {},
            name,
            val;

        if (serialized && serialized.length) {

            for (var i = 0; i < serialized.length; i++) {
                name = serialized[i].name;
                val = serialized[i].value;

                // 如果已经存在同名键，则将值存为数组
                if (data[name]) {
                    if (!$.isArray(data[name])) {
                        data[name] = [data[name]];
                    }
                    data[name].push(val);
                } else {
                    data[name] = val;
                }
            }

        }
        return data;
    };

    /**
     * 根据name属性获取选中的复选框
     * @method getChecked
     * @param  {String} name 表单控件的name
     * @param  {Jquery} [$ctx] 上下文
     * @return {Jquery}      复选框的jq对象数组
     */
    U.getChecked = function(name, $ctx) {
        return $("input[type='checkbox'][name='" + name + "']:checked", $ctx);
    };

    /**
     * 根据name属性获取选中的复选框值，返回以“,”分隔的字符串
     * @method getCheckedValue 
     * @param  {String} name 表单控件的name
     * @param  {Jquery} $ctx 上下文
     * @return {String}      复选框的值字符串
     */
    U.getCheckedValue = function(name, $context) {
        var $checkeds = this.getChecked(name, $context);

        return $checkeds.map(function() {
            return this.value;
        }).get().join(",");
    }

    U.lang = Ibos.l;

    /**
     * 获取唯一随机值
     * @method uniqid
     * @param  {String} [prefix]       值前缀
     * @param  {Boolean} [more_entropy] 当此参数为true时，会加长id以增强随机性
     * @return {String}                随机值
     */
    U.uniqid = function(prefix, more_entropy) {
        // + 	 original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +     revised by: Kankrelune (http://www.webfaktory.info/)
        // %     note 1: Uses an internal counter (in php_js global) to avoid collision
        if (typeof prefix === 'undefined') {
            prefix = "";
        }

        var retId;
        var formatSeed = function(seed, reqWidth) {
            seed = parseInt(seed, 10).toString(16); // to hex str
            if (reqWidth < seed.length) { // so long we split
                return seed.slice(seed.length - reqWidth);
            }
            if (reqWidth > seed.length) { // so short we pad
                return Array(1 + (reqWidth - seed.length)).join('0') + seed;
            }
            return seed;
        };

        // BEGIN REDUNDANT
        // END REDUNDANT
        if (!this.uniqidSeed) { // init seed with big random int
            this.uniqidSeed = Math.floor(Math.random() * 0x75bcd15);
        }
        this.uniqidSeed++;

        retId = prefix; // start with prefix, add current milliseconds hex string
        retId += formatSeed(parseInt(new Date().getTime() / 1000, 10), 8);
        retId += formatSeed(this.uniqidSeed, 5); // add seed hex string
        if (more_entropy) {
            // for more entropy we add a float lower to 10
            retId += (Math.random() * 10).toFixed(8).toString();
        }

        return retId;
    };

    return U;
})();

// Debug
Ibos.print = function() {
    console && console.log(arguments);
};
Ibos.time = function(name, time, func) {
    console.time(name || "untitle");
    for (var i = 0; i < (time || 1000); i++) {
        func();
    }
    console.timeEnd(name || "untitle");
};
Ibos.disableStyle = function() {
    $.each(document.styleSheets, function(i, stylesheet) {
        stylesheet.disabled = true;
    });
};

/**
 * 与应用相关的数据及操作
 * @class Ibos.app
 * @static
 */
Ibos.app = (function() {

    var app = {};
    var _pageParam = {};

    /**
     * 设置全局参数，等效于setPageParam函数
     * @method s
     * @param {String} key  参数名
     * @param {Any} value   参数值
     * @example
     * 		Ibos.app.s("name", "ink");
     * 		Ibos.app.s({ "name": "ink" });
     * @return {} 
     */
    app.s = app.setPageParam = function(key, value) {
        if (typeof key === "string") {
            _pageParam[key] = value;
        } else {
            $.extend(_pageParam, key);
        }
    };

    /**
     * 获取全局参数，等效于getPageParam函数
     * @method g
     * @param {String} key       参数名
     * @param {Any}    [defVal]  默认值，当key获取不到值时使用
     * @example
     * 		Ibos.app.g("name");
     * 		// ==> "ink"
     * 		
     * @return {Any}             全局参数中存储的值
     */
    app.g = app.getPageParam = function(key, defVal) {
        // G 为页面上定义的全局变量
        return typeof _pageParam[key] !== "undefined" ?
            _pageParam[key] :
            typeof G !== "undefined" && typeof G[key] !== "undefined" ?
            G[key] :
            defVal ? defVal : null;
    };

    /**
     * 获取路由
     * @method url
     * @param  {String} route   由三个子参数组成的字符： 模块/控制器/动作
     * @param {Object} [param]  作为url参数的对象，{a: 1, b: 1}将解析为 a=1&b=1的格式
     * @example 
     *  	Ibos.app.url('main/default/index');
     *  	// ==> localhost/?r=main/default/index
     *  	Ibos.app.url('main/default/index', { op: "add" });
     *  	// ==> localhost/?r=main/default/index&op=add
     *  	
     * @return {String}          Url地址
     */
    app.url = function(route, param) {
        route += "";
        if ((route).split("/").length !== 3) {
            // $.error("app.url: 参数route错误");
        } else {
            param = param ? '&' + $.param(param) : '';
            return this.g("SITE_URL") + "?r=" + route + param;
        }
    };

    /**
     * 获取静态文件夹地址，当无参数时，返回静态文件夹地址，传入相对地址时，返回最终文件地址
     * @method getStaticUrl
     * @param  {String} [url]  目标文件相对静态文件夹的地址
     * @return {String}
     */
    app.getStaticUrl = function(url) {
        return this.g("STATIC_URL") + (url || "");
    };

    app.getModulePath = function(mod) {
        return mod ? this.g("SITE_URL") + "system/modules/" + mod : ""
    };

    /**
     * 获取指定模块静态文件地址
     * @method getAssetUrl
     * @param  {String} mod     目标模块
     * @param  {String} [path]  具体文件地址
     * @example
     * 	Ibos.app.getAssetUrl("main", "/js/index.js");
     * @return
     */
    app.getAssetUrl = function(mod, path) {
        return (this.g("mods") && this.g("mods")[mod] ?
                this.g("mods")[mod].assetUrl :
                "") +
            (path || "");
    };

    /**
     * 获取事件参数，即节点上data-param属性解析后的对象
     * @method getEvtParams
     * @param  {HTMLElement} elem  元素节点
     * @example
     * 		**HTML**
     * 		<div id="a" data-param='{ "b": 1, "c": "HEHE"}'></div>
     * 		**Script**
     * 		app.getEvtParams(document.getElementById("a"));
     * 		// ==> { b: 1, c: "HEHE" }
     * 		
     * @return {Object}            解析后的数据
     */
    app.getEvtParams = function(elem) {
        var param = $.attr(elem, "data-param");
        return param ? $.parseJSON(param) : {};
    };

    app.assetUrl = (function() {
        var route = U.getUrlParam().r;
        if (route) {
            return app.getAssetUrl(route.substring(0, route.indexOf("/")));
        }
        return "";
    })();

    return app;
})();


/**
 * 全局设置，包括一些通用设置及通用插件默认配置
 */
Ibos.settings = {
    KEYCODE: {
        ENTER: 13,
        DELETE: 46,
        BACKSPACE: 8,
        UP: 38,
        DOWN: 40
    },

    imageTypes: "*.gif;*.jpg;*.jpeg;*.png;",

    dataTable: {
        // --- Data
        processing: true,
        serverSide: true,

        // --- Language
        language: $.extend({}, Ibos.l("DATATABLE"), {
            processing: '<img src="' + Ibos.app.getStaticUrl("/image/loading.gif") + '">' //"加载中..."
        }),

        // --- Pagination
        pageLength: 10,
        // pagingType: "full_numbers",
        pagingType: "input",
        lengthMenu: [5, 10, 20],

        dom: "rt<'page-list-footer'lp>",
    },

    zTree: {
        data: {
            simpleData: {
                enable: true
            }
        },
        view: {
            showLine: false,
            selectedMulti: false,
            showIcon: false
        }
    }
};


/**
 * 静态资源加载方法集
 * 以路径作为依据避免 js 重复执行
 */
Ibos.statics = {
    _fileList: {},

    defaults: {
        url: "",
        type: "js",
        callback: null,
        reload: false
    },

    // css 及 js 类型在读取时会自动执行
    // css 回调没有返回值
    load: function(options) { // { url, type[js], callback, reload[false] };
        var that = this;
        var def = $.Deferred();

        if (typeof options == "string") {
            options = $.extend(true, {}, this.defaults, {
                url: options
            });
        } else {
            options = $.extend(true, {}, this.defaults, options);
        }

        if (!options.url) {
            return false;
        }

        // 如果存在缓存且不强制要求重读
        if (this._fileList[options.url] && !options.reload) {
            def.resolve(this._fileList[options.url]);
            options.callback && options.callback(this._fileList[options.url]);
            // 否则请求对应文件
        } else {
            this._load(options, function(res) {
                that._fileList[options.url] = res;
                def.resolve(res);
                options.callback && options.callback(res);
            });
        }

        return def;
    },

    // 按顺序加载
    loads: function(arr) {
        var that = this;
        var ret = [];
        var def = $.Deferred();

        var loadStart = function(_arr) {
            this.load(_arr.shift()).done(function(res) {
                ret.push(res);

                if (_arr.length) {
                    loadStart.call(that, _arr);
                } else {
                    def.resolve.apply(def, ret);
                }
            });
        }
        if ($.isArray(arr)) {
            loadStart.call(that, arr);
        }

        return def;
    },

    _load: function(options, callback) {
        switch (options.type) {
            case "js":
                $.getScript(options.url, callback);
                break;
            case "css":
                U.loadCss(options.url, callback);
                break;
            case "html":
                $.get(options.url, callback, "html");
                break;
            default:
                $.get(options.url, callback, options.type);
                break;
        }
    }
};


// @deprecated 
Ibos.Event = function($ctx, type, flag) {
    var that = this;
    type = type || "click";
    flag = flag || type;

    $ctx = $ctx && $ctx.length ? $ctx : $(document);
    $ctx.on(type, "[data-" + flag + "]", function(evt) {
        var evtName = $.attr(this, "data-" + flag),
            params = Ibos.app.getEvtParams(this);
        that.fire(evtName, params, $(this), evt);
    })
    this._evts = {};
}

Ibos.Event.prototype = {
    constructor: Ibos.Event,
    // 查询是否存在指定标识的事件处理器，存在且为函数时返回true
    has: function(name) {
        return name in this._evts && $.isFunction(this._evts[name]);
    },
    add: function(name, evts) {
        // 若首选项为字符串，则作为模块标识符
        if (typeof name === "string") {
            this._evts[name] = evts;
        } else {
            evts = name;
            $.extend(this._evts, evts);
        }
    },
    remove: function(name) {
        delete this._evts[name];
    },

    fire: function(name, params, $elem, evt) {
        if (this.has(name)) {
            this._evts[name].call(this._evts.click, params, $elem, evt);
        }
    }
}

Ibos.events = new Ibos.Event();

/**
 * 全局事件委派
 * 依赖于节点触发，节点data-action属性对应事件名
 * @class Ibos.evt
 * @todo  只作为点击事件委派
 * @static
 */
Ibos.evt = (function() {
    var _events = {}
    var methods = {
        /**
         * 添加全局委派事件
         * 传入节点上data-param属性及节点本身作为参数
         * @method add
         * @param {String|Object} [evtType=click]  当为字符串时，作为事件类型，为对象时，作为事件集
         * @param {Object} handlers                事件集
         * @example
         * 	Ibos.evt.add({
         * 		"publish": function(){ console.log("publish") }
         * 	});
         */
        add: function(evtType, handlers) {
            if (typeof evtType !== "string") {
                handlers = evtType;
                evtType = "click";
            };
            // 如果事件类型已经存在，则加入新的事件
            if (!_events[evtType]) {
                _events[evtType] = {};
                $(document).on(evtType + ".ink", "[data-action], [data-act]", function(evt) {
                    evt.stopPropagation();
                    var act = $.attr(this, "data-action") || $.attr(this, "data-act"),
                        params = Ibos.app.getEvtParams(this);
                    _events[evtType][act] && _events[evtType][act].call(this, params, this, evt);
                })
            }

            $.extend(_events[evtType], handlers);
        },

        /**
         * 移除全局委派事件
         * @method remove
         * @param {String} handlerName     事件名
         * @param {String} [evtType=click] 事件类型
         * @return {} 
         */
        remove: function(handlerName, evtType) {
            evtType = evtType || "click";
            if (_events[evtType] && _events[evtType][handlerName]) {
                delete _events[evtType][handlerName];

                if ($.isEmptyObject(_events[evtType])) {
                    delete _events[evtType];
                    $(document).off(evtType + ".ink");
                }
            }
        },

        get: function(handlerName, evtType) {
            evtType = evtType || "click";
            if (_events[evtType] && _events[evtType][handlerName]) {
                return _events[evtType][handlerName]
            }
        },

        /**
         * 触发全局委派事件
         * @method fire
         * @param  {String} handlerName         事件名
         * @param  {String} [evtType="click"]   事件类型
         * @return {Any}                        事件执行后的结果
         */
        fire: function(handlerName, evtType) {
            var argu = Array.prototype.slice.call(arguments, 2);
            evtType = evtType || "click";
            if (_events[evtType] && _events[evtType][handlerName]) {
                return _events[evtType][handlerName].apply(null, argu)
            }
        }
    }
    return methods;
})();

/**
 * 全局核心函数集
 * @class Ibos.core
 * @static
 */
Ibos.core = {
    /**
     * 用于继承父类
     * @method inherits
     * @param  {Function} _subClass   父类
     * @param  {Function} _superClass 子类
     * @example
     * 		var A = function(){};
     * 		A.prototype.a = 1;
     * 		
     * 		var B = function(){};
     * 		B.prototype.b = 2;
     * 		
     * 		Ibos.core.inherits(B, A);
     * 		
     * 		var ins = new B
     * 		ins.a; // ==> 1
     * @return {Function}             子类
     */
    inherits: function(_subClass, _superClass) {
        var _F = function() {};
        _F.prototype = _superClass.prototype;
        _subClass.prototype = $.extend(new _F(), {
            _super: _superClass,
            constructor: _subClass
        }, _subClass.prototype);
        _F.prototype = null;
        return _subClass;
    }
};


(function(window, $) {
    var _slice = Array.prototype.slice,
        _call = function(func) {
            var args;
            if ($.isFunction(func)) {
                args = _slice.call(arguments, 1);
                return func.apply(this, args);
            }
        }

    /**
     * 节点处理函数集
     * @class Dom
     * @static
     */
    window.Dom = Ibos.Dom = (function() {
        var Dom = {
            /**
             * 通过 id 获取节点，等同于document.getElementById(id);
             * @method byId
             * @param  {String} id     节点id
             * @return {HTMLElment}    节点，没有时为null;
             */
            byId: function(id) {
                return document.getElementById(id) || null;
            },
            /**
             * 获取节点或节点对应的jq对象
             * @method getElem
             * @param  {String|HTMLElement|Jquery} id   节点id或节点本身或对应jquery对象
             * @param  {Boolean} toJq                   当此值为true时，返回jq对象，否则返回节点
             * @return {HTMLElement|Jquery}             
             */
            getElem: function(id, toJq) {
                var node, isJq = false;
                if (typeof id === "string") {
                    node = document.getElementById(id)
                } else {
                    node = id;
                    if (id && !id.nodeType) {
                        isJq = true;
                    }
                }
                return toJq ? (isJq ? node : $(node)) : (isJq ? node[0] : node);
            }
        }

        return Dom;
    })();

    //Plugins
    (function() {
        // 
        // data-selected
        var PseudoSelect = function($ctrl, $menu, options) {
            this.$ctrl = $ctrl;
            this.$menu = $menu;
            this.options = options || {};
            this._init();
        }
        PseudoSelect.prototype = {
            constructor: PseudoSelect,
            _init: function() {
                if (!this.$menu || !this.$menu.length) {
                    return false;
                }
                this._initSelected();
                this._bindSelectEvent();
            },
            _initSelected: function() {
                var value = this.$ctrl.attr("data-selected");
                if (typeof value !== "undefined") {
                    this.select(value, true);
                }
            },
            _bindSelectEvent: function() {
                var that = this;
                this.$menu.on("click", "li", function() {
                    var value = $.attr(this, "data-value");
                    that.select(value)
                })
            },
            _setSelectedValue: function(value, text) {
                var tpl = "";
                this.$ctrl.attr("data-selected", value);
                this.selectedValue = value;
                if (this.options.template) {
                    tpl = $.template(this.options.template, {
                        text: text
                    });
                } else {
                    tpl = text;
                }
                this.$ctrl.html(tpl);
            },
            _getItemText: function($item) {
                return $item.attr("data-text") || $item.text();
            },

            select: function(value, toRefresh) {
                var that = this;
                if (value === this.selectedValue) {
                    return false;
                }
                toRefresh = (toRefresh === false) ? false : true;
                this.$menu.find("li").each(function() {
                    var $item = $(this),
                        itemValue = $.attr(this, "data-value"), //; || $.trim($.text(this));
                        itemText;
                    $(this).removeClass("active");
                    if (itemValue && value === itemValue) {
                        $(this).addClass("active");
                        if (toRefresh) {
                            itemText = that._getItemText($item);
                            that._setSelectedValue(itemValue, itemText);
                            that.$ctrl.trigger({
                                type: "select",
                                selected: itemValue,
                                selectedItem: $item
                            })
                        }
                    }
                    if (value === "") {
                        that._setSelectedValue("", "");
                        that.$ctrl.trigger({
                            type: "select",
                            selected: "",
                            selectedItem: null
                        })
                    }
                })
            },

            reset: function() {
                this.select("");
            }
        }

        $(function() {
            $(document).find("[data-toggle='dropdown'][data-toggle-role='select']").each(function() {
                var $ctrl = $(this),
                    $menu = $ctrl.siblings(".dropdown-menu");
                $ctrl.data("select", new PseudoSelect($ctrl, $menu));
            })
        });

        // Simple Editor 
        /**
         * [SimpleEditor description]
         * @deprecated
         * @param {[type]} $el     [description]
         * @param {[type]} options [description]
         */
        var SimpleEditor = function($el, options) {
            this.$el = $el;
            this.options = $.extend({}, SimpleEditor.defaults, options);
            this._init();
        }
        SimpleEditor.defaults = {
            color: '',
            bold: false,
            italic: false,
            underline: false,
            onSetColor: null,
            onSetBold: null,
            onSetItalic: null,
            onSetUnderline: null
        };
        SimpleEditor.prototype = {
            _init: function() {
                this._initContainer();
                this._createBtns();
            },
            _initContainer: function() {
                var $el = this.$el,
                    cls = 'editor-btnbar';
                $el.addClass(cls);
            },
            _createBtns: function() {
                this.colorBtn = this._createColorBtn();
                this.boldBtn = this._createBoldBtn();
                this.italicBtn = this._createItalicBtn();
                this.underlineBtn = this._createUnderlineBtn();
                this.$el.append(this.colorBtn, this.boldBtn, this.italicBtn, this.underlineBtn);
            },
            _createBtn: function() {
                return $('<a href="javascript:;" class="editor-btn">')
            },
            // 通常按钮比较相似，共用一个创建的方法，包括bold, italic, underline
            _createNormalBtn: function(type, clickHandler) {
                var item = this._createBtn(),
                    that = this;
                this.options[type] && item.addClass('active');
                if (typeof clickHandler === 'function') {
                    item.on('click', clickHandler);
                }
                return item;
            },
            _createColorBtn: function() {
                var that = this,
                    item = this._createBtn();
                this.options.color && item.css('background-color', '#' + this.options.color);
                //跟$.fn.colorPicker的组合
                item.colorPicker({
                    mode: 'simple',
                    onPick: function(hex, text) {
                        that.setColor(hex, text);
                    }
                })
                return item;
            },
            _createBoldBtn: function() {
                var that = this,
                    item = this._createNormalBtn('bold', function() {
                        that.setBold(!that.options.bold)
                    });
                item.css('font-weight', 700).html('B').attr('title', '粗体');
                return item;
            },
            _createItalicBtn: function() {
                var that = this,
                    item = this._createNormalBtn('italic', function() {
                        that.setItalic(!that.options.italic)
                    })
                item.css('font-style', 'italic').html('I').attr('title', '斜体');
                return item;
            },
            _createUnderlineBtn: function() {
                var that = this,
                    item = this._createNormalBtn('underline', function() {
                        that.setUnderline(!that.options.underline)
                    });
                item.css('text-decoration', 'underline').html('U').attr('title', '下划线');
                return item;
            },
            // 设置bold，italic,underline状态的形式相似
            _setStatus: function($el, status, callback) {
                var method = status ? 'addClass' : 'removeClass',
                    currentStatus = $el.hasClass('active');
                if (status !== currentStatus) {
                    $el[method]('active');
                }
                _call.call(this, callback, status);
            },
            setColor: function(hex, text) {
                text = hex ? text : '';
                this.colorBtn.css('background-color', hex)
                    .attr('title', text);
                this.options.color = hex;
                _call.call(this, this.options.onSetColor, hex, text);
            },
            setBold: function(flag) {
                if (arguments.length === 0) {
                    flag = true
                }
                flag = !!flag;
                this._setStatus(this.boldBtn, flag, function(status) {
                    this.options.bold = status;
                    _call.call(this, this.options.onSetBold, status);
                })
            },
            setItalic: function(flag) {
                if (arguments.length === 0) {
                    flag = true
                }
                flag = !!flag;
                this._setStatus(this.italicBtn, flag, function(status) {
                    this.options.italic = status;
                    _call.call(this, this.options.onSetItalic, status);
                })
            },
            setUnderline: function(flag) {
                if (arguments.length === 0) {
                    flag = true
                }
                flag = !!flag;
                this._setStatus(this.underlineBtn, flag, function(status) {
                    this.options.underline = status;
                    _call.call(this, this.options.onSetUnderline, status);
                })
            }
        }

        var Tab = function($context, selector, callback) {
            var that = this;

            this.$context = ($context && $context.length) ? $context : $(document.body);
            this.selector = selector || "a";
            this.callback = callback;

            $context.on("click", selector, function() {
                that._tab($(this), callback);
            });
        }
        Tab.prototype = {
            constructor: Tab,
            _tab: function($elem, callback) {
                var target = $elem.attr("data-target"),
                    $target = $(target);
                $target.show().siblings().hide();
                _call.call(this, callback, $elem, $target);
            },
            on: function(selector) {
                var $elem = this.$context.find("[data-target='" + selector + "']");
                this._tab($elem, this.callback);
            }
        };

        var commonTab = function($context) {
            return new Tab($context, "a", function($ctrl) {
                $ctrl.parent().addClass("active").siblings().removeClass("active");
            });
        }

        window.P = Ibos.Plugins = {
            PseudoSelect: PseudoSelect,
            SimpleEditor: SimpleEditor,
            Tab: Tab,
            commonTab: commonTab
        };
    })();

    /**
     * UI层
     * 主要是一些弹出组件，交互组件及宽高计算函数
     * Dialog组件使用了插件artDialog
     * Tip组件使用了插件jGrowl
     * @class Ui
     * @static
     */
    (function() {
        var Ui = {};
        // Ui.themes = [ "3497DB", "A6C82F", "F4C73B", "EE8C0C", "E76F6F", "AD85CC", "98B2D1", "82939E"];

        (function(j) {
            if (!j) {
                //$.error("Ui.tip: 使用tip需要加载$.fn.jGrowl");
                return false;
            }
            /**
             * 全局提示，基于jGrowl
             * @method tip
             * @param  {String} msg   提示的文本
             * @param  {String} [theme=success] 主题(success|danger|warning|default|normal|info);
             * @return {}
             */
            Ui.tip = function(msg, theme) {
                msg = msg || "";
                if (msg.indexOf("@") == 0) {
                    msg = Ibos.l(msg.substr(1)) || msg;
                }
                return $.jGrowl(msg, {
                    theme: theme || 'success'
                });
            };
        })($.jGrowl || top.$.jGrowl);

        (function(d) {
            if (d) {
                $.extend(Ui, {
                    /**
                     * 全局对话框，基于artDialog
                     * @method dialog
                     * @param {Object} options 配置
                     * @return {Object} artDialog实例
                     */
                    dialog: d,
                    /**
                     * 全局警告框，基于artDialog，模态
                     * @method alert
                     * @param  {String}  msg 提示文本
                     * @param  {Function}  ok 确定后的回调
                     * @return {Object} artDialog实例
                     */
                    alert: d.alert,
                    /**
                     * 全局确定框，基于artDialog，模态
                     * @method confirm
                     * @param  {String} msg 提示文本
                     * @param  {Function}  ok 确定后的回调
                     * @param  {Function}  cancel 取消后的回调
                     * @return {Object} artDialog实例
                     */
                    confirm: d.confirm,
                    /**
                     * 全局信息接收框，基于artDialog，模态
                     * @method prompt
                     * @param  {String} msg 提示文本
                     * @param  {Function}  ok 确定后的回调， 输入的文本会作为首参数传入
                     * @param  {Function}  cancel 取消后的回调
                     * @return {Object} artDialog实例
                     */
                    prompt: d.prompt,
                    tips: d.tips,
                    /**
                     * ajax对话框，基于artDialog
                     * @method ajaxDialog
                     * @param  {String} url ajax地址
                     * @param  {Object}  options 配置，与Ui.dialog相同
                     * @return {Object} artDialog实例
                     */
                    ajaxDialog: d.load,
                    /**
                     * 框架的对话框，基于artDialog
                     * @method openFrame
                     * @param  {String} url 框架页地址
                     * @param  {Object}  options 配置，与Ui.dialog相同
                     * @return {Object} artDialog实例
                     */
                    openFrame: d.open,
                    /**
                     * 获取Dialog实例
                     * @method getDialog
                     * @param  {String} [id] dialog的自定义id, 为空时获取所有对话框实例
                     * @return {Object} artDialog实例
                     */
                    getDialog: d.get,
                    /**
                     * 关闭对话框
                     * @method closeDialog
                     * @param  {String} [id] dialog的自定义id, 为空时关闭所有对话框实例
                     * @return {Object} artDialog实例
                     */
                    closeDialog: function(id) {
                        // 没有传参时，关闭所有弹窗
                        if (typeof id === "undefined") {
                            for (var i in d.list) {
                                if (d.list.hasOwnProperty(i)) {
                                    d.list[i].close();
                                }
                            }
                        } else {
                            var dl = this.getDialog(id);
                            dl && dl.close();
                        }
                    }
                })
            }
        })($.artDialog || top.$.artDialog);


        /**
         * 通用菜单类
         * @class Menu
         * @todo 扩展出子类AjaxMenu
         * @todo 是否添加opiton.width跟option.height;
         * @constructor
         * @param {Jquery}  $menu                            作为菜单的节点
         * @param {Object}  [options]                        配置项
         *     @param {String}  [options.id]                 标识符，作为获取实例的依据
         *     @param {Object}  [options.position]           定位方位，使用jqui的position方法, 参数一致
         *     @param {String}  [options.content]            初始化时的内容
         *     @param {Number}  [options.autoHide]           自动隐藏的延迟时间数，默认不自动隐藏
         *     @param {Boolean} [options.clickToHide=false]  点击菜单以外的地方时隐藏
         *     @_param {Number} [options.zIndex = 1001]      菜单z-index值
         * @return {Menu}                                    菜单实例
         */
        Ui.Menu = (function() {
            var menuid = 0;
            var instance = {};

            var Menu = function(menu, options) {
                var me = this;
                this.$menu = Dom.getElem(menu, true);
                var opt = this.options = $.extend({}, Menu.defaults, options);

                // 添加新实例的引用
                if (opt.id) {
                    if (instance[opt.id]) {
                        return instance[opt.id];
                    } else {
                        instance[opt.id] = this;
                        this.id = opt.id;
                    }
                    // 没有设置 id 时，使用自动menuid
                } else {
                    this.id = "menu" + menuid++;
                    instance[this.id] = this;
                }

                this.$menu.css("position", "absolute").hide();
                this.showing = false;
                this.zIndex(opt.zIndex);

                // 悬停时中止自动隐藏
                if (opt.autoHide) {
                    this.$menu.on({
                        "mouseenter": function() {
                            me._stopDelay("hide");
                        },
                        "mouseleave": function() {
                            me.hide(me.options.autoHide);
                        }
                    })
                }

                // 有设置option.content属性时，初始化内容
                if (!U.isUnd(opt.content) && (typeof opt.content === "string" || opt.content.nodeType === 1)) {
                    this.$menu.html(opt.content);
                }

                if (opt.clickToHide) {
                    $(document).on("mousedown", function(evt) {
                        if (me.$menu[0] !== evt.target && !$.contains(me.$menu[0], evt.target)) {
                            me.hide();
                        }
                    })
                }

                opt.addClass && this.$menu.addClass(opt.addClass)
            }
            Menu.defaults = {
                animate: false,
                zIndex: 1000
            }
            Menu._access = function(id, callback) {
                    if (U.isUnd(id)) {
                        for (var i in instance) {
                            if (instance.hasOwnProperty(i)) {
                                callback.call(instance[i])
                            }
                        }
                        return instance;
                    } else {
                        if (id in instance) {
                            callback.call(instance[id]);
                            return instance[id];
                        }
                        return null;
                    }
                }
                /**
                 * 显示指定菜单
                 * @show
                 * @static
                 * @param  {String} id 要显示菜单的id，为空时显示所有菜单
                 * @return {}
                 */
            Menu.show = function(id) {
                    this._access(id, function() {
                        this.show();
                    })
                }
                /**
                 * 隐藏指定菜单
                 * @hide
                 * @static
                 * @param  {String} id 要隐藏菜单的id，为空时隐藏所有菜单
                 * @return {}    
                 */
            Menu.hide = function(id) {
                    this._access(id, function() {
                        this.hide();
                    })
                }
                /**
                 * 获取指定id的菜单实例
                 * @param  {String} id  菜单实例的id
                 * @return {Menu}       菜单实例
                 */
            Menu.getIns = function(id) {
                return id ? instance[id] : instance
            }
            Menu.prototype = {
                constructor: Menu,

                /**
                 * 定位菜单，直接使用了jqUi的position函数
                 * @method position
                 * @chainable
                 * @param  {Object} [opt] 定位配置项，详见jqUi position函数
                 * @return {Menu}       菜单实例
                 */
                position: function(opt) {
                    this.$menu.position($.extend({
                        of: document.body
                    }, opt));
                    return this;
                },

                /**
                 * 设置菜单z-index值
                 * @method zIndex
                 * @chainable
                 * @param  {Number|String} z z-index值
                 * @return {Menu}       菜单实例
                 */
                zIndex: function(z) {
                    this.$menu.css("z-index", z);
                    return this;
                },

                /**
                 * 显示菜单
                 * @method show
                 * @chainable
                 * @param  {Number} [delay] 延时显示
                 * @return {Menu}           菜单实例
                 */
                show: function(delay) {
                    var that = this,
                        opt = this.options;
                    if (this.showing) {
                        return this;
                    }

                    // 有设置option.content属性时，初始化内容
                    // 如果options.content是一个函数，则在beforeshow执行
                    if (!U.isUnd(opt.content) && typeof this.options.content === "function") {
                        this.$menu.html(this.options.content.call(this) || "");
                    }

                    this.showing = true;
                    // 设置了animate属性时，给予渐显过渡
                    if (opt.animate) {
                        this.$menu.fadeTo(0, 0).show().trigger("show")
                            .fadeTo(200, 1);
                        opt.show && opt.show.call(this);
                    } else {
                        this.$menu.show().trigger("show");
                        opt.show && opt.show.call(this);
                    }
                    // 定位需要在$menu出现在页面之后才执行，否则会有定位误差
                    this.position(opt.position);

                    // 自动隐藏
                    if (opt.autoHide) {
                        setTimeout(function() {
                            that.hide();
                        }, opt.autoHide)
                    }
                    return this;
                },

                /**
                 * 隐藏菜单
                 * @method hide
                 * @chainable
                 * @param  {Number} [delay] 延时隐藏
                 * @return {Menu}           菜单实例
                 */
                hide: function(delay) {
                    this.showing = false;
                    if (this.options.animate) {
                        this.$menu.fadeOut(200).trigger("hide");
                        this.options.hide && this.options.hide.call(this);
                    } else {
                        this.$menu.hide().trigger("hide");
                        this.options.hide && this.options.hide.call(this);
                    }
                    return this;
                },

                /**
                 * 设置菜单内容
                 * @method setContent
                 * @chainable
                 * @param  {String|HTMLElement|Jquery} [content] 内容
                 * @return {Menu}           菜单实例
                 */
                setContent: function(content) {
                    this.$menu.html(content);
                    this.position(this.options.position);
                    return this;
                }

                // destory: function(){
                // 	delete this.constructor.instance[this.id];
                // }
            }

            return Menu;
        })();

        /**
         * 通用弹出菜单类
         * @class PopMenu
         * @constructor
         * @extends {Menu}
         * @param {Jquery} $ctrl        作为触发器的节点
         * @param {Jquery} $menu        作为菜单的节点
         * @param {Object} [options]    配置项，详见Menu类
         *     @param {String} [options.trigger]  触发方式，目前支持hover、click
         * @return {Menu}               菜单实例
         */
        Ui.PopMenu = function($ctrl, $menu, options) {
            if (!$ctrl || !$ctrl.length) {
                return false
            }
            if (options.trigger === "click") {
                options.hideDelay = options.hideDelay ? options.hideDelay : 0;
                options.showDelay = options.showDelay ? options.showDelay : 0;
            }
            this._super.call(this, $menu, $.extend(true, {}, Ui.PopMenu.defaults, options));
            this.$ctrl = $ctrl;

            var that = this;
            var timer;

            if (!this.options.position.of) {
                this.options.position.of = this.$ctrl;
            }

            var _show = function() {
                clearTimeout(timer)
                timer = setTimeout(function() {
                    that.show();
                }, that.options.showDelay)
            };
            var _hide = function(e) {
                clearTimeout(timer);
                // 当离开的瞬间重新进入菜单或菜单开关时，不隐藏
                if (that.options.trigger === "hover") {
                    if (e.toElement === $ctrl[0] || e.toElement === $menu[0] || $.contains($ctrl[0], e.toElement) || $.contains($menu[0], e.toElement)) {
                        return false;
                    }
                }
                timer = setTimeout(function() {
                    that.hide();
                }, that.options.hideDelay);
            }

            if (this.options.trigger === "hover") {
                this.$ctrl.on({
                    "mouseenter.popmenu": _show,
                    "mouseleave.popmenu": _hide
                });
                this.$menu.on({
                    "mouseenter.popmenu": function() {
                        clearTimeout(timer);
                    },
                    "mouseleave.popmenu": _hide
                });
            } else if (this.options.trigger === "click") {
                this.$ctrl.on("click.popmenu", function() {
                    that.showing ? _hide() : _show();
                })
            }
        };
        Ibos.core.inherits(Ui.PopMenu, Ui.Menu);
        Ui.PopMenu.defaults = {
            position: {
                at: "left bottom",
                my: "left top"
            },
            trigger: "hover", // hover, click
            showDelay: 500,
            hideDelay: 500
        };

        $.extend(Ui, {
                modal: {
                    show: function(options) {
                        var opt = $.extend({
                            backgroundColor: '#FFF',
                            opacity: '.7',
                            zIndex: '2000'
                        }, options)
                        if (!this.$modal || !this.$modal.length) {
                            this.$modal = $("<div style='position:fixed; top:0; left:0; width:100%; height:100%; overflow:hidden'></div>").css("z-index", opt.zIndex).hide().appendTo(document.body);
                            $("<div style='height:100%;'></div>").css(opt).appendTo(this.$modal);

                            this.$modal.fadeIn(200)
                                .on("mousedown", function(evt) {
                                    evt.stopPropagation();
                                    evt.preventDefault();
                                });
                        }
                    },

                    hide: function() {
                        var $modal = this.$modal;
                        if ($modal) {
                            $modal.fadeOut(200, function() {
                                $modal.remove();
                                Ui.modal.$modal = null
                            });
                        }
                    },

                    toggle: function() {
                        if (this.$modal) {
                            this.hide();
                        } else {
                            this.show();
                        }
                    }
                },

                /**
                 * 聚集于指定上下文环境中的第一个指定控件上
                 * @method focusForm 
                 * @param  {Jquery}  [$context = $(document.body)] 			 上下文环境
                 * @param  {String}  [selector = 'input, textarea, select']  起作用的选择器
                 * @param  {Boolean} [cls = true]      						 是否为父节点添加has-focus类
                 * @return {[type]}          [description]
                 */
                focusForm: function($context, selector, cls) {
                    var $first;

                    selector = selector || "input, textarea, select";
                    cls = cls === false ? false : true;

                    $first = $(selector, $context).eq(0).focus();

                    cls && $first.parent().addClass("has-focus");
                },

                /**
                 * popover, 基于bootstrap $.fn.popover, 样式不同
                 */
                popover: function($elem, options) {
                    $elem.popover($.extend({
                        template: '<div class="popover popover-w"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
                        placement: "bottom"
                    }, options));
                },

                /**
                 * 滚动至指定元素的头部
                 * @method scrollYTo
                 * @for  Ui
                 * @param {HTMLElement|Jquery} elem 指定元素
                 * @param {Number} offset           位置偏移量调整
                 * @param {Function} [callback]     滚动完成后的回调函数
                 * @return {}
                 */
                scrollYTo: function(elem, offset, callback, time) {
                    var $body = (window.opera) ? (document.compatMode === "CSS1Compat" ? $('html') : $('body')) : $('html,body'),
                        $elem = Dom.getElem(elem, true),
                        top = $elem.offset().top + (offset || 0);
                    time = time || 500;
                    $body.stop().animate({
                        scrollTop: top
                    }, time, callback);
                },
                /**
                 * 页面回到顶部
                 * @method scrollToTop
                 * @return {} 
                 */
                scrollToTop: function(callback) {
                    this.scrollYTo($(document.body), 0, callback);
                },

                selectOne: function($elem, tag) {
                    tag = tag || $elem[0].nodeName;
                    $elem.addClass("active").siblings(tag).removeClass("active");
                },

                /**
                 * 动态计算节点高度，让其相对某祖先节点撑开高度
                 * @method fillHeight
                 * @example
                 * @todo 完善
                 * <div  id='a' style='height: 100px;'>
                 * 		<div id='b'></div>
                 * </div>
                 * fillHeight('b', 'a') ==> <div id='b' style='height: 100px;'></div>
                 * @param  {Jquery} $elem   元素
                 * @param  {Jquery} $target 相对元素
                 * @return {}         
                 */
                fillHeight: function($elem, $target) {
                    var height,
                        $child,
                        $parent;
                    $elem = $child = Dom.getElem($elem, true);
                    $target = Dom.getElem($target, true);
                    $parent = $elem.parent();
                    height = $target.height();

                    while ($parent && $parent.length && ($.isWindow($target[0]) ? $parent[0].nodeName.toLowerCase() !== "html" : $parent[0] !== $target[0])) {
                        height -= $parent.outerHeight() - $child.outerHeight();
                        $child = $parent;
                        $parent = $parent.parent();
                    }
                    // if(height > $elem.outerHeight()) {
                    // 这里要考虑padding 跟 border
                    $elem.css("min-height", height);
                    // }
                },

                /**
                 * 新消息标题闪动提醒
                 * @method blinkTitle 
                 * @param  {String} text     提醒内容
                 * @param  {Number} [interval=1000] 闪动的时间间隔
                 * @param  {Number} [timeout]  超时
                 * @return {}
                 */
                blinkTitle: (function() {
                    var timer,
                        oldTitle = document.title,
                        reset = function() {
                            clearInterval(timer);
                            document.title = oldTitle;
                        }

                    return function(text, interval, timeout) {
                        var _in = false;
                        interval = interval || 1000;
                        if (text === false) {
                            reset();
                            return false;
                        }

                        timer = setInterval(function() {
                            document.title = _in ? oldTitle : text;
                            _in = !_in
                        }, interval);

                        if (timeout) {
                            setTimeout(function() {
                                reset();
                            }, timeout);
                        }
                    }
                })(),

                /**
                 * 设置复选框联动
                 * @method linkCheckbox
                 * @param  {String|Element|Jquery} main 主复选框
                 * @param  {String|Element|Jquery} sub  从复选框
                 * @return 
                 */
                linkCheckbox: function(main, sub) {
                    var $mainCheckbox = $(main),
                        $subCheckboxs;

                    if ($mainCheckbox && $mainCheckbox.length) {
                        $subCheckboxs = $(sub);
                        // 当主复选框状态变化时，其它复选框跟随其状态变化
                        $mainCheckbox.on("change", function() {
                            var isChecked = this.checked;
                            $subCheckboxs.each(function() {
                                var labelIns;
                                this.checked = isChecked;
                                // 如果复选框是Label实例（即复选框），则调用实例刷新方法
                                labelIns = $(this).data("label");
                                labelIns && labelIns.refresh();
                            })
                        });

                        $subCheckboxs.on("change", function() {
                            if (!this.checked) {
                                var labelIns;
                                $mainCheckbox.prop("checked", false);
                                labelIns = $mainCheckbox.data("label");
                                labelIns && labelIns.refresh();
                            }
                        });
                    }
                }
            })
            /**
             * 弹出提示框
             * @method showPrompt
             * @param  {String} content 提示内容
             * @param  {Number} time    显示时间，秒数
             * @return {ArtDialog}      弹窗实例
             */
        Ui.showPrompt = function(content, time) {
                var dialog = Ui.dialog || $.artDialog;

                return dialog({
                        id: "prompt",
                        padding: 0,
                        title: false,
                        cancel: false,
                        zIndex: 9999,
                        skin: "prompt-dialog"
                    })
                    .content("<div class='prompt'>" + (content || "") + "</div>")
                    .time(time || 1.5);
            }
            /**
             * 弹出积分提示框
             * 由于依赖于cookie，在实际中基本不会手动调用
             * @method showCreditPrompt
             * @return {}
             */
        Ui.showCreditPrompt = function() {
            var remind = U.getCookie('creditremind').split('D'),
                base = U.getCookie('creditbase').split('D'),
                rule = decodeURI(U.getCookie('creditrule', true)).replace(String.fromCharCode(9), ' '),
                info = [],
                promptTpl = '',
                names,
                arr;
            var _reset = function() {
                U.setCookie('creditremind', '');
                U.setCookie('creditbase', '');
                U.setCookie('creditrule', '');
            }
            var _createTpl = function(rule, entrys, values) {
                var tpl = "";
                // 由于成长项最多只能定义5项
                for (var i = 1; i <= 5; i++) {
                    if (values[i] != '0' && entrys[i]) {
                        tpl += '<span>' + entrys[i] + ' <strong> ' + (values[i] >= 0 ? '+' : '') + values[i] + '</strong></span> ';
                    }
                }
                if (tpl) {
                    tpl = '<div class="point-tip"><div class="point-tip-title mbs">' + rule + '</div><div class="point-tip-body">' +
                        tpl + '</div></div>';
                }
                return tpl;
            }

            if (!Ibos.app.g('uid') || remind.length < 2 || remind[6] !== Ibos.app.g('uid')) {
                _reset();
                return;
            }

            names = Ibos.app.g('creditRemind').split(',');
            for (var i = 0; i < names.length; i++) {
                arr = names[i].split('|');
                info[arr[0]] = arr[1];
            }

            promptTpl = _createTpl(rule, info, remind);
            if (promptTpl) {
                Ui.showPrompt(promptTpl)
            }
            _reset();
        }

        Ui.submenu = function(menu) {
            var $menu = $(menu);

            $('[data-node="submenuToggle"]', $menu).each(function() {
                var $elem = $(this);
                $menu = $elem.find('[data-node="submenu"]');

                if ($menu.length) {
                    var menuIns = new Ui.PopMenu($elem, $menu, {
                        position: {
                            at: "right top",
                            my: "left top"
                        },
                        hideDelay: 0,
                        showDelay: 0
                    });
                    // 阻止点击冒泡导致父菜单被关闭
                    $menu.on("click", function(evt) {
                        evt.stopPropagation();
                    })
                }
            });
        }

        Ui.cookieTip = function(msg, theme) {
            U.setCookie('globalRemind', encodeURI(msg));
            U.setCookie('globalRemindType', theme);
        };

        // 窗口提示
        Ui.Notification = {
            NAME: 'noti',

            LOGO: '/image/logo_pic.png',

            // cookie: {
            // 	NAME: 'allow_desktop_notify',
            // 	EXPIRES: 7776000
            // },

            // isEnabled: function() {
            // 	return U.getCookie(this.cookie.NAME) == "1";
            // },

            // enable: function() {
            // 	U.setCookie(this.cookie.NAME, "1", this.cookie.EXPIRES);
            // },

            // disable: function() {
            // 	U.setCookie(this.cookie.NAME, "");
            // },

            show: function(title, content, icon) {
                var that = this;
                var _show;
                icon = icon || Ibos.app.getStaticUrl(this.LOGO);

                // 旧版本接口
                if (window.webkitNotifications) {
                    var wnf = window.webkitNotifications;
                    _show = function() {
                        var notification = wnf.createNotification(icon, title, content);
                        notification.replaceId = that.NAME;
                        notification.onclick = function() {
                            window.focus();
                            window.open(Ibos.app.url("message/notify/index"));
                        };
                        notification.ondisplay = function(evt) {
                            setTimeout(function() {
                                evt.currentTarget.cancel();
                            }, 1e4);
                        };
                        notification.show();
                        return notification;
                    };

                    // 查看接口权限，未允许时，申请权限
                    if (wnf.checkPermission() !== 0) {
                        wnf.requestPermission(_show);
                        // 已允许时直接通知
                    } else {
                        _show();
                    }
                    // 标准接口
                } else if ("Notification" in window) {
                    _show = function() {
                        var notification = new Notification(title, {
                            "icon": icon,
                            "body": content
                        });
                        notification.onclick = function() {
                            window.focus();
                            window.open(Ibos.app.url("message/notify/index"));
                        };
                        notification.onshow = function() {
                            setTimeout(function() {
                                notification.close();
                            }, 1e4);
                        };
                        return notification;
                    };

                    // 已允许时直接通知
                    if (Notification.permission === "granted") {
                        _show();
                    } else if (Notification.permission === "default") {
                        Notification.requestPermission(function(permission) {
                            // 判断是否有权限
                            if (Notification.permission === "granted") {
                                _show();
                            }
                        });
                    }
                }
            }
        };

        window.Ui = $.extend(window.Ui, Ui);
    })();

    $.extend(Ibos, {
        /**
         * 日期处理函数集
         * @class Ibos.date
         * @static
         */
        date: {
            /**
             * 日期格式化..
             * @method format
             * @param  {Date||number} [date]                   日期对象或时间戳，默认为当前时间
             * @param  {String} [format='yyyy-mm-dd'] 		   日期格式，有效值 yyyy, yy, m, mm, d, dd, h, hh, H, HH, i, ii, s, ss
             * @param  {Boolean} [utc]                         是否使用utc时间
             * @return {String}        						   格式化后的时间
             */
            format: function(date, format, utc) {
                date = date == null ? new Date() : typeof date === "number" ? new Date(date) : date;
                format = format || "yyyy-mm-dd",
                    getFullYear = utc ? "getUTCFullYear" : "getFullYear";
                getMonth = utc ? "getUTCMonth" : "getMonth";
                getDate = utc ? "getUTCDate" : "getDate";
                getHours = utc ? "getUTCHours" : "getHours";
                getMinutes = utc ? "getUTCMinutes" : "getMinutes";
                getSeconds = utc ? "getUTCSeconds" : "getSeconds";

                var val = {
                    yy: ("" + date[getFullYear]()).substring(2),
                    yyyy: date[getFullYear](),
                    m: date[getMonth]() + 1,
                    d: date[getDate](),
                    h: date[getHours](),
                    i: date[getMinutes](),
                    s: date[getSeconds]()
                };
                val.H = (val.h % 12 == 0 ? 12 : val.h % 12);
                val.HH = (val.H < 10 ? '0' : '') + val.H;
                val.hh = (val.h < 10 ? '0' : '') + val.h;
                val.ii = (val.i < 10 ? '0' : '') + val.i;
                val.ss = (val.s < 10 ? '0' : '') + val.s;
                val.dd = (val.d < 10 ? '0' : '') + val.d;
                val.mm = (val.m < 10 ? '0' : '') + val.m;

                for (var i in val) {
                    if (val.hasOwnProperty(i)) {
                        var reg = new RegExp('\\b' + i + '\\b', 'g');
                        format = format.replace(reg, val[i])
                    }
                }
                return format
            },
            /**
             * 日期计算
             * @method calc
             * @param  {Date} date   基本时间对象
             * @param  {Number} days 日期数
             * @example
             * 		var d = new Date;
             * 		Ibos.date.calc(d, 2); // 两天后的时间对象
             * @return {Date}
             */
            calc: function(date, days) {
                var DAY_MICROSECOND = 86400000;
                var date = date || new Date;
                days = days || 0;
                return new Date(date.setTime(date.getTime() + days * DAY_MICROSECOND))
            },
            /**
             * 数字转为时间格式
             * @method numberToTime
             * @param  {Number} num 数字
             * @example
             * 		Ibos.date.numberToTime(3.5);
             * 		// ==> 3:30
             * @return {String}     时间格式的字符串
             */
            numberToTime: function(num) {
                num = parseFloat(num, 10);
                // if(num < 0 || num > 24) {
                // 	return false;
                // }
                var intBit = parseInt(num, 10),
                    decBit = Math.abs(num - intBit);

                var hour = intBit > 9 || intBit < 0 ? intBit : "0" + intBit;
                var minute = parseInt(decBit * 60, 10);
                minute = minute > 9 || minute < 0 ? minute : "0" + minute;
                return hour + ":" + minute;
            }
        },

        /**
         * 字符串处理函数集
         * @class Ibos.string
         * @static
         */
        string: {
            /**
             * 移除字符串中的html标签
             * @method removeHtmlTag
             * @param  {String} str 目标字符串
             * @return {String}     结果字符串
             */
            removeHtmlTag: function(str) {
                return str.replace(/<\/?[^>]*>/g, '')
            },
            /**
             * 从utf16编码转化为utf8编码
             * @method utf16to8
             * @param  {String} str utf16编码格式的字符串
             * @return {String}     utf8编码的字符串
             */
            utf16to8: function(str) {
                var out, i, len, c;
                out = "";
                len = str.length;
                for (i = 0; i < len; i++) {
                    c = str.charCodeAt(i);
                    if ((c >= 0x0001) && (c <= 0x007F)) {
                        out += str.charAt(i);
                    } else if (c > 0x07FF) {
                        out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
                        out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
                        out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
                    } else {
                        out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
                        out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
                    }
                }
                return out;
            },

            /**
             * 当字符串过长时，使用省略号（或其它字符）替换部分文字
             * @method ellipsis
             * @param  {String} str           目标字符串
             * @param  {Number} [limit=20]    最大长度，字串超过此长度时执行替换
             * @param  {Number} [pos]         替换的起始位置，默认从字符串末替换
             * @param  {String} [ell="..."]   用于替换的字符串
             * @return {String}       
             */
            ellipsis: function(str, limit, pos, ell) {
                ell = ell || "...";
                str = "" + str;
                limit = limit || 20;

                if (str.length > limit) {
                    if (typeof pos === "undefined") {
                        str = str.substr(0, limit) + ell;
                    } else {
                        pos = +pos > limit ? limit : +pos;
                        str = str.substr(0, pos) + ell + str.substr(str.length - limit + pos);
                    }
                }

                return str;
            },

            /**
             * 将字符串转化为整数字符串
             * @method toPositiveInt 
             * @param  {String} string 源字符串
             * @return {String}
             */
            toPositiveInt: function(string) {
                string = "" + string;
                return string.replace(/\D+/g, "").replace(/^0+/g, "") || "0";
            },

            /**
             * 转化为小数字符串
             * @method toPositiveDecimals
             * @param  {String|Number}  value
             * @param  {Number}         [digits]   小数点后保留几位
             * @return {String}
             */
            toPositiveDecimals: function(value, digits) {
                var ret = 0;

                if (value) {
                    if (U.regex(value, "positiveDecimals")) {
                        ret = +value;
                    } else {
                        ret = Math.abs(parseFloat(value));
                        if (isNaN(ret)) {
                            ret = 0;
                        }
                    }
                }

                return digits == null ? ret : ret.toFixed(digits);
            },

            /**
             * 增加千分逗号
             * @method addCommas
             * @param {String|Number} value
             * @return {String}
             */
            addCommas: function(value) {
                value = value + "";

                if (U.regex(value, "currency")) {
                    return value;
                }

                if (U.regex(value, "int") || U.regex(value, "decimals")) {
                    value = value.replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, '$1,');
                    return value;
                }

                return value;
            }
        },

        number: {
            addCommas: function(num) {
                if (isNaN(num)) {
                    return '-';
                }
                num = (num + '').split('.');
                return num[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, '$1,') + (num.length > 1 ? ('.' + num[1]) : '');
            }
        },

        /**
         * 本地存储函数
         * 由于不考虑IE8 以下的浏览器，所以直接使用localStorage
         * 其实JSON也可以直接用原生的。。
         * @class Ibos.local
         * @static
         */
        local: {
            /**
             * 设置本地存储
             * @method set
             * @param {String} key   索引名
             * @param {Any} value    值
             */
            set: function(key, value) {
                return window.localStorage.setItem(key, $.toJSON(value));
            },
            /**
             * 获取本地存储
             * @method get
             * @param  {String} key   索引名
             * @return {Any} value    值
             */
            get: function(key) {
                var value = window.localStorage.getItem(key);
                return value === null ? null : $.parseJSON(value);
            },
            /**
             * 移除本地存储
             * @method remove
             * @param  {String} key   索引名
             * @return {}
             */
            remove: function(key) {
                return window.localStorage.removeItem(key);
            },
            /**
             * 清空本地存储
             * @method clear
             * @return {}
             */
            clear: function() {
                return window.localStorage.clear();
            }
        },

        /**
         * 拼音匹配汉字，正确时返回true，支持首字母
         * @method matchSpell
         * @for Ibos
         * @param  {String} term 要匹配的拼音
         * @param  {String} text 与之匹配的汉字
         * @example
         * matchSpell("bsxc", "博思协创") // => true;
         * matchSpell("bosi", "博思协创") // => true;
         * matchSpell("ink", "博思协创") // => false;
         * @return {Boolean}      true为匹配，false反之
         */
        matchSpell: function(term, text) {
            var textChars,
                termChars,
                match = false,
                matchStart = 0,
                pinyinText

            if (!term || !text) {
                return false;
            }

            /* 从字面上直接匹配 */
            if (text.toLowerCase().indexOf(term.toLowerCase()) !== -1) {
                return true;
            }

            /* 全拼匹配 */
            pinyinText = pinyinEngine.toPinyin(text, false, ",")
            pinyinText = typeof pinyinText === 'string' ? pinyinText : pinyinText[0];

            if (pinyinText.toLowerCase().indexOf(term.toLowerCase()) !== -1) {
                return true;
            }

            /* 首字母匹配 */

            // 匹配单文字首字符
            var matchFirstLetter = function(char, text) {
                var py = pinyinEngine.toPinyin(text);
                for (var n = 0; n < py.length; n++) {
                    if (py[n].charAt(0) === char) {
                        return true;
                    }
                }
                return false;
            }

            // 从指定索引开始逐字符匹配
            var matchFrom = function(index) {
                var usText = text.substr(index).split(""),
                    usTerms = term.substr(1).split("");

                for (var cr = 0; cr < usTerms.length; cr++) {
                    if (!usText[cr] || !matchFirstLetter(usTerms[cr], usText[cr])) {
                        return false;
                    }
                }

                return true;
            }


            var chars = text.split("");
            for (var i = 0; i < chars.length; i++) {
                // 在目标字符中找到开始位置后，进入逐字符匹配，成功则返回true
                // 失败则尝试找下一个开端，直到完全失败
                if (matchFirstLetter(term.charAt(0), chars[i])) {
                    if (matchFrom(i + 1)) {
                        return true;
                    };
                }
            }

            return false;
        },

        /**
         * 消息@功能
         * @method atwho
         * @for Ibos
         * @param  {[type]} $ctx    [description]
         * @param  {[type]} options [description]
         * @return {[type]}         [description]
         */
        atwho: function($ctx, options) {
            options = options || {};
            opts = $.extend({
                at: "@",
                data: Ibos.data && Ibos.data.get("user") || [],
                tpl: "<li data-value='${name}'><img src='${imgUrl}' height='20' width='20'/> ${name} </li>",
                callbacks: {
                    filter: function(query, data, search_key) {
                        var item, _i, _len, _results;
                        _results = [];
                        if (query === "") {
                            return false
                        }
                        for (_i = 0, _len = data.length; _i < _len; _i++) {
                            item = data[_i];
                            if (~item[search_key].toLowerCase().indexOf(query)) {
                                _results.push(item);
                            } else if (Ibos.matchSpell(query, item[search_key])) {
                                _results.push(item);
                            }
                        }
                        return _results
                    },
                    remote_filter: function(query, callback) {
                        var item, _i, _len, _results;
                        _results = [];
                        if (query === "" && options.url) {
                            $.ajax({
                                url: options.url,
                                type: "get",
                                dataType: "json",
                                success: function(res) {
                                    callback(res);
                                }
                            })
                        }
                    },
                    sorter: function(query, items, search_key) {
                        var item, _i, _len, _results;
                        if (!query) {
                            return items;
                        }
                        _results = [];
                        for (_i = 0, _len = items.length; _i < _len; _i++) {
                            item = items[_i];
                            item.atwho_order = item[search_key].toLowerCase().indexOf(query);
                            if (item.atwho_order > -1) {
                                _results.push(item);
                            } else if (Ibos.matchSpell(query, item[search_key])) {
                                item.atwho_order = 10000;
                                _results.push(item);
                            }
                        }
                        return _results.sort(function(a, b) {
                            return a.atwho_order - b.atwho_order;
                        });
                    }
                }
            }, options);
            try {
                $ctx.atwho(opts);
            } catch (e) {
                // 未引入 jquery.atwho.js
            }
        },

        /**
         * 功能引导，intro.js的入口
         * 直接退出引导当作完成引导处理
         * @method intro
         * @for Ibos
         * @param  {Array} steps       引导步骤
         * @param  {Function} complete 引导完成时的回调
         * @return {IntroJs}          引导类IntroJs实例
         */
        intro: function(steps, complete) {

            var ito;
            if (typeof introJs === "undefined") {
                $.error("Ibos.intro: 未引入intro.js");
            }
            ito = introJs();
            ito.setOptions({
                steps: steps,
                showStepNumbers: false,
                tooltipClass: 'ibosintro',
                prevLabel: "",
                nextLabel: "",
                skipLabel: "",
                doneLabel: ""
            });
            if (complete) {
                ito.oncomplete(complete)
                ito.onexit(complete);
            }
            ito.start();
            return ito;
        }
    });

    /**
     * 用于检查某个区域内的表单内容是否有修改
     * 当有修改未保存离开页面时给予提示
     * 当前思路是通过检查该区域内的表单控件是否有发生 change 事件
     * 但这种做法有不少局限性，期望有更好的解决方案
     * @method checkFormChange
     * @param  {String|Element|Jquery} context   修改的上下文	
     * @param  {String} tip                      离开页面的提示
     * @param  {String} selectors                选择器，用于规定作用的节点
     * @return
     */
    Ibos.checkFormChange = function(context, tip, selectors) {
        $context = context ? $(context) : $(document.body);
        tip = tip || "离开页面后，未保存的数据将丢失";
        selectors = selectors || "input, textarea, select";

        $context.on("change", selectors, function() {
                $context.data("formchange", true);
            })
            .on("formchange", function() {
                $context.data("formchange", true);
            })
            .on("submit ignoreformchange", function() {
                $context.data("formchange", false);
            });
        // Fix: IE 8 下点击 a 标签会先触发 beforeupload
        $(document).on("click", "a[href='javascript:;'], a[href='javascript:void(0);']", function(e) {
            e.preventDefault();
        });
        $(window).on("beforeunload", function(e) {
            if ($context.data("formchange")) {
                return tip;
            }
        })
    }
})(window, window.jQuery);


(function(window, $) {
    /**
     * 搜索功能配置
     * 为搜索框绑定事件
     * @method search
     * @for  jQuery 
     * @chainable
     * @param  {Function} onsearch 搜索时回调函数
     * @param  {Function} onconf   配置时回调函数
     * @return {Jquery}            
     */
    $.fn.search = function(onsearch, onconf) {
        return this.each(function() {
            var $input = $(this),
                $searchBtn = $input.next(),
                $cont = $input.closest(".search"),
                toconf = false;
            // 如果配置了高级设置事件，则出现设置按钮
            if (onconf) {
                toconf = true;
                $cont.addClass("search-config");
            }
            var search = function() {
                var val = $input.val();
                // if(val) {
                // 如果定义了搜索事件，则执行
                // 否则，获取到最近的一个表单并提交表单
                if (onsearch) {
                    onsearch(val, $input);
                } else {
                    $input[0].form.submit();
                }
                // }
            }
            var config = function() {
                var val = $input.val();
                onconf && onconf(val, $input);
            }

            // 点击事件
            $input.on({
                "keydown": function(evt) {
                    if (evt.which === 13) {
                        search();
                    }
                },
                "focus": function() {
                    toconf = false;
                    $cont.addClass("has-focus");
                },
                "blur": function() {
                    var val = $(this).val();
                    if ($.trim(val) === "") {
                        setTimeout(function() {
                            if (onconf) {
                                toconf = true;
                            }
                            $cont.removeClass("has-focus");
                        }, 200)
                    }
                }
            });

            // 按键事件
            $searchBtn.on("click", function(evt) {
                if (toconf) {
                    config();
                } else {
                    search();
                }
            })
        });
    }
})(window, window.jQuery);

// $.fn.blink 闪动提示
(function(window, $) {
    /**
     * 闪动提示
     * @method  blink
     * @for  jQuery
     * @param {Number} times 闪动次数
     * @param {Number} speed 闪动相隔毫秒数
     * @return {Jquery} jq对象
     */

    $.blink = function(el, times, speed) {
        times = times || 3;
        speed = speed || 100;
        var count = 0,
            isBlink = false,
            blinkBackground = '#FCC',
            blinkBorder = '#F99',
            timer;
        timer = setInterval(function() {
            if (isBlink) {
                $.style(el, 'background-color', '');
                $.style(el, 'border-color', '');
                isBlink = false;
            } else {
                $.style(el, 'background-color', blinkBackground);
                $.style(el, 'border-color', blinkBorder);
                isBlink = true;
            }
            count++;
            count >= times * 2 && clearInterval(timer);
        }, speed);
        el.focus();
    }
    $.fn.blink = function(times, speed) {
        return this.each(function() {
            $.blink(this, times, speed);
        })
    }

})(window, window.jQuery);

/**
 * 显示模态窗口
 * @method showModal
 * @for jQuery
 * @param  {Object} [options] 配置项，一组css属性
 * @return {Jquery}         
 */
$.fn.showModal = function(options) {
    var opt = $.extend($.fn.showModal.defaults, options);
    return this.each(function() {
        var $elem = $(this),
            $modal = $elem.data("modal"),
            posVal;
        if (!$modal) {
            posVal = $elem.css("position");
            if (posVal !== "fixed" && posVal !== "absolute" && posVal !== "relative") {
                $elem.css("position", "relative");
            }
            $modal = $('<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: #FFF; opacity: .7; filter: Alpha(opacity=70)"></div>').hide();;
            $modal.css(opt).appendTo($elem).fadeIn(200).on("mousedown", function(e) {
                e.stopPropagation();
                e.preventDefault();
            });
            $elem.data("modal", $modal);
        }
    });
};
$.fn.showModal.defaults = {
        backgroundColor: "#FFF",
        opacity: ".7",
        zIndex: 2000
    }
    /**
     * 隐藏模态窗口
     * @method hideModal
     * @for jQuery
     * @return {Jquery}         
     */
$.fn.hideModal = function() {
    return this.each(function() {
        var $elem = $(this),
            $modal = $elem.data("modal");
        if ($modal) {
            $modal.fadeOut(200, function() {
                $modal.remove();
            });
            $elem.removeData("modal");
        }
    });
};

/**
 * 显示或关闭等待中提示
 * @method waiting
 * @for  jQuery
 * @param  {String|False} [content]        提示文字或HTML，值为false时关闭等待提示
 * @param  {String}       [mode="normal"]  提示类型， normal、small、mini分别对应三种大小的图标
 * @param  {Boolean}      [lock=false] 	   是否锁定
 * @return {Jquery}         
 */
$.fn.waiting = function(content, mode, lock) {
    if (content === false) {
        return this.stopWaiting();
    }
    content = content || "";
    mode = mode || "normal";
    var tpl = '<div><img src="<%= Ibos.app.getStaticUrl(path) %>" width="<%= size %>" height="<%= size %>" />' +
        '<span style="margin-left: 10px; font-size: <%= fontSize %>px;"><%= content %></span>' +
        '</div>';

    var modeSet = {
        normal: $.template(tpl, {
            path: "/image/loading.gif",
            size: 60,
            fontSize: 16,
            content: content
        }),
        small: $.template(tpl, {
            path: "/image/loading_small.gif",
            size: 24,
            fontSize: 14,
            content: content
        }),
        mini: $.template(tpl, {
            path: "/image/loading_mini.gif",
            size: 16,
            fontSize: 12,
            content: content
        })
    }
    content = modeSet[mode] || content || "Loading...";

    return this.each(function() {
        var $ctx = $(this),
            $wait = $ctx.data("waiting");

        if (lock) {
            $ctx.showModal();
        }

        if ($wait) {
            $wait.html(content).position({
                of: $ctx
            });
        } else {
            $wait = $("<div class='waiting' style='position: absolute; z-index: 2001; width: 300px; text-align: center; overflow: hidden;'></div>")
                .html(content)
                .appendTo($ctx.parent())
                .position({
                    of: $ctx
                });

            $ctx.data("waiting", $wait);
        }
    });
}

// @deprecated 统一使用 $.fn.waiting 入口
$.fn.waitingC = function(content, lock) {
    return this.waiting(content, 'normal', lock);
}

// @deprecated 统一使用 $.fn.waiting 入口
$.fn.stopWaiting = function() {
    return this.each(function() {
        var $ctx = $(this),
            $wait = $ctx.data("waiting");

        $ctx.hideModal();

        if ($wait) {
            $wait.remove();
            $ctx.removeData("waiting");
        }
    });
}

/**
 * 阻止选择html文本
 * 此函数用于阻止浏览器默认的文本选择状态，调用的对象将不能划选文字
 * @method	noSelect
 * @for  jQuery
 * @param   {Boolean}	[g=true] true为阻止选择文本，false为允许选择文本
 * @return  {Jquery}
 */
$.fn.noSelect = function(g) { //阻止选择html文本
    return (g == null ? true : g) ? this.each(function() {
        //for IE及safari，支持onselectstart方法
        if ($.browser.msie || $.browser.safari) {
            $(this).bind("selectstart", function() {
                return false
            });
        } else if ($.browser.mozilla) { //for FF，支持user-select样式
            $(this).css("MozUserSelect", "none");
            $("body").trigger("focus")
        } else if ($.browser.chrome) { //for chrome
            $(this).css("WebkitUserSelect", "none");
            $("body").trigger("focus")
        } else {
            $.browser.opera ? $(this).bind("mousedown", function() {
                return false
            }) : $(this).attr("unselectable", "on");
        }
    }) : this.each(function() {
        if ($.browser.msie || $.browser.safari) {
            $(this).unbind("selectstart")
        } else if ($.browser.mozilla) {
            $(this).css("MozUserSelect", "inherit");
        } else if ($.browser.chrome) {
            $(this).css("WebkitUserSelect", "inherit");
        } else {
            $.browser.opera ? $(this).unbind("mousedown") : $(this).removeAttr("unselectable", "on");
        }
    })
};

// Ajax Popover
$.fn.ajaxPopover = function(url, reload, options) {
    return this.each(function() {
        var $current = $(this),
            defaultContent = "loading...",
            isLoad = false,
            // isShow = true,
            opts = $.extend({
                content: defaultContent,
                placement: "bottom",
                html: true
            }, options);

        $current.popover(opts)
            .on("show", function() {
                var popoverData = $current.data("popover");
                // isShow = true;
                if (!isLoad) {
                    $.when(
                        $.get(url, function(result) {
                            popoverData.options.content = result
                            isLoad = true;
                            // show函数执行时，会回调本函数体
                            // 当reload为true时，AJAX完成后会重置已读状态 isLoad = false;
                            // 使内容每次都从服务器上获取
                            popoverData.show();
                        })
                    ).then(function() {
                        if (reload) {
                            popoverData.options.content = defaultContent;
                            isLoad = false;
                        }
                    });
                }
            })
            // .on('hide', function(){
            // 	isShow = false;
            // })

        // $(document).on('mousedown.popoverhide', function(){
        // 	var popoverData = $current.data("popover");
        // 	if(popoverData && isShow){
        // 		popoverData.hide();
        // 		isShow = false;
        // 	}
        // });

        // $('[data-node-type]').on('click', function(evt){
        // 	evt.stopPropagation();
        // 	evt.preventDefault();
        // })

    })
};

$.fn.bindEvents = function(events, delegate) {
    events = events || {};
    delegate = delegate === false ? false : true;
    return this.each(function() {
        var evt,
            arr,
            evtType,
            selector;

        for (evt in events) {
            var spaceIndex = evt.indexOf(" ");
            evtType = evt.substr(0, spaceIndex);
            selector = $.trim(evt.substr(spaceIndex));

            if (delegate) {
                $(this).on(evtType, selector, events[evt]);
            } else {
                $(selector, this).on(evtType, events[evt]);
            }
        }
    });
}

// AffixTo
$.fn.affixTo = function(of) {
    var $win = $(window),
        $doc = $(document),
        $of = $(of);

    return this.each(function() {
        var $elem = $(this);
        $elem.affix({
            offset: {
                top: function() {
                    var winHeight = $win.height(),
                        ofHeight = $of.outerHeight(),
                        ofTop = $of.offset().top,
                        elemHeight = $elem.outerHeight();
                    // 如果元素的高大于相对元素的高或元素没有超出屏幕，则不定位
                    return (elemHeight > ofHeight || elemHeight < winHeight - ofTop) ? $doc.outerHeight() : (elemHeight - winHeight + ofTop);

                },
                bottom: function() {
                    var ofHeight = $of.outerHeight(),
                        elemHeight = $elem.outerHeight(),
                        docHeight = $doc.height(),
                        ofTop = $of.offset().top,
                        ofBottom = ofTop + ofHeight;

                    // 如果元素的高大于相对元素的高或元素没有超出屏幕，则不定位
                    return (elemHeight >= ofHeight || elemHeight < $win.height() - ofTop) ? null : docHeight - ofBottom;
                }
            }
        });
        $(window).on("scroll", function() {
            if ($elem.hasClass("affix")) {
                $elem.css("top", $win.height() - $elem.outerHeight());
            }
        });
    });
};

/**
 * 插入文本至 input 或 textarea 框光标处
 * @method insertText
 * @for  jQuery
 * @param {String} text
 * @return Jquery
 */
$.fn.insertText = function(text) {
    this.each(function() {
        if (this.tagName !== 'INPUT' && this.tagName !== 'TEXTAREA') {
            return;
        }
        if (document.selection) {
            this.focus();
            setTimeout(function() {
                var cr = document.selection.createRange();
                cr.text = text;
                cr.collapse();
                cr.select();
            }, 0)
        } else if (this.selectionStart || this.selectionStart == '0') {
            var start = this.selectionStart,
                end = this.selectionEnd;
            this.value = this.value.substring(0, start) + text + this.value.substring(end, this.value.length);
            this.selectionStart = this.selectionEnd = start + text.length;
        } else {
            this.value += text;
        }
    });
    return this;
};

// @deprecated 使用 Ibos.cmList 或 dataTable 替换
Ibos.List = (function() {
    var getItemId = (function() {
        var itemid = 0;
        return function() {
            return itemid++;
        }
    })();

    var List = function($elem, tpl, options) {
        var that = this;
        this.$elem = Dom.getElem($elem, true);
        this.tpl = tpl;
        this.options = $.extend({}, List.defaults, options);
        this._dataCache = [];
        this._itemCache = {};
    }
    List.defaults = {
        idField: "id" // 定义作为id的字段，默认为'id'
    }

    List.prototype = {
        constructor: List,
        // 判断某Id是否在cache中，返回在数组中的下标，不在时返回-1
        indexOf: function(id) {
            for (var i = 0; i < this._dataCache.length; i++) {
                if (this._dataCache[i][this.options.idField] == id) {
                    return i;
                }
            }
            return -1;
        },
        hasId: function(id) {
            return this.indexOf(id) !== -1;
        },
        // 用于补全数据和验证数据，给没有idField值的数据添加唯一符，对有idField的数据做验证
        _assignId: function(data) {
            var idField = this.options.idField,
                id;
            // 当设置了options.idField时， 该字段会作为数据的唯一标识符
            if (!data[idField]) {
                id = getItemId();
                if (this.hasId(id)) {
                    return this._assignId(data)
                }
                data[idField] = id;
            } else {
                if (this.hasId(data[idField])) {
                    $.error("Ibos.List._assignId: " + id + "重复");
                }
            }
            return data;
        },
        // 合并时，不能修改idField，所以清理掉对应idField属性
        _cleanData: function(data) {
            delete data[this.options.idField];
            return data;
        },
        //
        _createItem: function(data) {
            return $.tmpl(this.tpl, data);
        },
        // 传入一组数据以添加节点，prepend为true时，插入在容器的首个子节点
        addItem: function(data, prepend) {
            var ret = $(),
                handler = prepend ? "prependTo" : "appendTo",
                $item,
                d;
            data = $.isArray(data) ? data : [data];
            for (var i = 0; i < data.length; i++) {

                d = this._assignId(data[i]);
                $item = this._createItem(d);

                if ($item && $item.length) {
                    this._itemCache[d[this.options.idField]] = $item;
                    this._addItemData(d);
                    ret = ret.add($item[handler](this.$elem));
                    this.$elem.trigger("list.add", {
                        item: $item,
                        data: d
                    });
                }
            }
            return ret;
        },
        // 更新节点
        updateItem: function(id, data) {
            var ret,
                newData,
                $oldItem,
                $newItem;
            if (id == null) {
                return null;
            }
            if (typeof id === "string" || typeof id === "number") {
                // 更新数据，成功时返回新的数据，失败时返回false;
                newData = this._updateItemData(id, data);
                if (newData) {
                    $oldItem = this.getItem(id);
                    $newItem = this._createItem(newData);
                    $oldItem.replaceWith($newItem);
                    // 更新节点存储的指向
                    this._itemCache[id] = $newItem
                }
                this.$elem.trigger("list.update", {
                    item: $newItem,
                    data: newData
                })
                ret = $newItem;
            } else {
                data = $.isArray(id) ? id : [id];
                ret = $();
                for (var i = 0; i < data.length; i++) {
                    ret = ret.add(this.updateItem(data[i][this.options.idField], data[i]));
                }
            }
            return ret;
        },

        removeItem: function(id) {
            var ids = ("" + id).split(",");
            for (var i = 0; i < ids.length; i++) {
                // 成功删除数据时会返回true
                if (this._removeItemData(ids[i])) {
                    this._itemCache[ids[i]].remove();
                    delete this._itemCache[ids[i]];
                    this.$elem.trigger("list.remove");
                }
            }
        },
        // 获取所有列表节点, 参数为空时，返回所有节点组成的jq对象
        getItem: function(id) {
            var ret;
            if (U.isUnd(id)) {
                ret = $();
                for (var i in this._itemCache) {
                    if (this._itemCache.hasOwnProperty(i)) {
                        ret = ret.add(this._itemCache[i])
                    }
                }
            } else {
                ret = this._itemCache[id];
            }
            return ret;
        },

        _addItemData: function(data) {
            this._dataCache.push(data);
        },

        _updateItemData: function(id, newData) {
            // 获取到旧的数据
            var oldData = this.getItemData(id);
            if (oldData) {
                // 将新数据清理后，合并至旧数据
                return $.extend(oldData, this._cleanData(newData));
            }
            return false;
        },

        _removeItemData: function(id) {
            var index = this.indexOf(id);
            if (index !== -1) {
                this._dataCache.splice(index, 1);
                return true;
            }
            return false;
        },

        // 获取节点对应的数据，参数为空时返回全部数据
        getItemData: function(id) {
            var index;
            if (U.isUnd(id)) {
                return this._dataCache;
            } else {
                index = this.indexOf(id);
                if (index !== -1) {
                    return this._dataCache[index];
                }
                return null;
            }
        },

        clear: function() {
            for (var i = 0; i < this._dataCache.length; i++) {
                this.removeItem(this._dataCache[i--][this.options.idField]);
            }
        }
    }

    return List;
})();

// @deprecated 使用 Ibos.cmList 或 dataTable 替换
Ibos.OrderList = function($elem, tpl, options) {
    options = {} || options;
    options.reorder = options.reorder === false ? false : true;
    this._super.call(this, $elem, tpl, options);
    this.index = this.options.startIndex = (this.options.startIndex || 1);
}
Ibos.core.inherits(Ibos.OrderList, Ibos.List);

$.extend(Ibos.OrderList.prototype, {
    addItem: function(data, prepend) {
        data = $.extend({
            index: this.index
        }, data);
        this.index++;
        return this._super.prototype.addItem.call(this, data, prepend);
    },

    removeItem: function(ids) {
        var ret = this._super.prototype.removeItem.apply(this, arguments);
        if (this.options.reorder) {
            this.reorder();
        }
        return ret;
    },

    reorder: function() {
        var datas = this.getItemData();
        this.index = this.options.startIndex;
        for (var i = 0; i < datas.length; i++) {
            this.getItem(datas[i][this.options.idField]).find("[data-item-index]").attr("data-item-index", this.index).text(this.index++);
        }
    }
})


// 通用列表类，提供列表的增删除改功能
Ibos.CmList = function(container, opts) {
    this.$container = $(container);
    this.opts = $.extend({}, this.constructor.defaults, opts);
}
Ibos.CmList.prototype = {
    constructor: Ibos.CmList,

    getItems: function() {
        return this.$container.children();
    },

    getItem: function(id) {
        return this.getItems().filter("[" + this.opts.idAttr + "='" + id + "']");
    },

    getItemCount: function() {
        return this.getItems().length;
    },

    addItem: function(data, prependTo) {
        var _this = this;
        var $item = $.tmpl(this.opts.tpl, data);

        if (this.opts.animate) {
            $item.hide()[prependTo ? "prependTo" : "appendTo"](this.$container).fadeIn(200, function() {
                $(_this).trigger("itemadd", {
                    item: $item
                });
            });
        } else {
            $item[prependTo ? "prependTo" : "appendTo"](this.$container);
            $(_this).trigger("itemadd", {
                item: $item
            });
        }

        return $item;
    },

    updateItem: function(id, data) {
        var _this = this;
        var $item = $.tmpl(this.opts.tpl, data).replaceAll(this.getItem(id));

        if (this.opts.animate) {
            $item.fadeOut(0, function() {
                $item.fadeIn(200);
            });
        }

        $(_this).trigger("itemupdate", {
            item: $item
        });

        return $item;
    },

    removeItem: function(id) {
        var _this = this;
        var $item = this.getItem(id);
        var _remove = function() {
            $(_this).trigger("beforeitemremove", {
                item: $item
            });
            $item.remove();
            $(_this).trigger("itemremove", {
                item: $item
            });
        }
        if (this.opts.animate) {
            $item.fadeOut(200, function() {
                _remove();
            });
        } else {
            _remove();
        }

        return $item;
    }
}

Ibos.CmList.defaults = {
    tpl: "",
    idAttr: "data-id",
    animate: false
};


(function() {
    var KEYBOARD = {
            '8': 'BACKSPACE',
            '13': 'ENTER'
        },
        _fn = {};

    var _kill;

    $(document.body).on('keydown', function(evt) {
        //获取事件对象
        var evt, elem, code, _todo;

        evt = evt || window.event;
        elem = evt.relatedTarget || evt.srcElement || evt.target || evt.currentTarget;
        code = evt.keyCode;

        if (code in KEYBOARD) {
            _todo = '_' + KEYBOARD[code].toLowerCase();
            _fn[_todo].call(null, elem, evt);
        }
    });

    _kill = function(evt) {
        if (evt.returnValue) {
            evt.returnValue = false;
        }

        if (evt.preventDefault) {
            evt.preventDefault();
        }

        evt.stopPropagation();
        return false;
    };

    _fn._backspace = function(elem, evt) {
        var nodeName = elem.nodeName ? elem.nodeName.toLowerCase() : '',
            type = elem.type ? elem.type.toLowerCase() : '';

        if (nodeName != 'input' && nodeName != 'textarea') {
            return _kill(evt);
        }

        if (nodeName == 'input' && (type != 'text' && type != 'textarea' && type != 'password' && type != 'file')) {
            return _kill(evt);
        }

        if (nodeName == 'input' && (elem.readOnly == true || elem.disabled == true)) {
            return _kill(evt);
        }
    };

    _fn._enter = function(elem, evt) {
        var forms = document.forms,
            $form = $(forms[0]),
            nodeName = elem.nodeName ? elem.nodeName.toLowerCase() : '',
            len, val, submitAuto = true,
            $inputs = $form.find('input,textarea');

        // textarea默认处理
        if (nodeName == 'textarea') {
            return false;
        }

        // 页面非单表单时禁止回车提交
        if (forms.length != 1) {
            return _kill(evt);
        }

        // 非表单登录阻止表单回车提交
        if ($form.find('input[type="password"]').length === 0) {
            return _kill(evt);
        }

        // 表单字段数大于4时
        // 存在非空输入则不允许回车提交
        if ((len = $inputs.length) > 4) {
            for (len; len--;) {
                val = $inputs[len];
                val === '' && (submitAuto = false);
            }

            if (!submitAuto) {
                return _kill(evt);
            }
        }
    };
})();