/** v2.9.7 | MIT Licensed */;/**
 * Layui
 * Classic modular front-end UI library
 * MIT Licensed
 */

;!function(win){
  "use strict";

  var doc = win.document;
  var config = {
    modules: {}, // 模块物理路径
    status: {}, // 模块加载状态
    timeout: 10, // 符合规范的模块请求最长等待秒数
    event: {} // 模块自定义事件
  };

  var Layui = function(){
    this.v = '2.9.7'; // Layui 版本号
  };

  // 识别预先可能定义的指定全局对象
  var GLOBAL = win.LAYUI_GLOBAL || {};

  // 获取 layui 所在目录
  var getPath = function(){
    var jsPath = doc.currentScript ? doc.currentScript.src : function(){
      var js = doc.scripts;
      var last = js.length - 1;
      var src;
      for(var i = last; i > 0; i--){
        if(js[i].readyState === 'interactive'){
          src = js[i].src;
          break;
        }
      }
      return src || js[last].src;
    }();

    return config.dir = GLOBAL.dir || jsPath.substring(0, jsPath.lastIndexOf('/') + 1);
  }();

  // 异常提示
  var error = function(msg, type){
    type = type || 'log';
    win.console && console[type] && console[type]('layui error hint: ' + msg);
  };

  var isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]';

  // 内置模块
  var modules = config.builtin = {
    lay: 'lay', // 基础 DOM 操作
    layer: 'layer', // 弹层
    laydate: 'laydate', // 日期
    laypage: 'laypage', // 分页
    laytpl: 'laytpl', // 模板引擎
    form: 'form', // 表单集
    upload: 'upload', // 上传
    dropdown: 'dropdown', // 下拉菜单
    transfer: 'transfer', // 穿梭框
    tree: 'tree', // 树结构
    table: 'table', // 表格
    treeTable: 'treeTable', // 树表
    element: 'element', // 常用元素操作
    rate: 'rate',  // 评分组件
    colorpicker: 'colorpicker', // 颜色选择器
    slider: 'slider', // 滑块
    carousel: 'carousel', // 轮播
    flow: 'flow', // 流加载
    util: 'util', // 工具块
    code: 'code', // 代码修饰器
    jquery: 'jquery', // DOM 库（第三方）

    all: 'all',
    'layui.all': 'layui.all' // 聚合标识（功能性的，非真实模块）
  };

  // 记录基础数据
  Layui.prototype.cache = config;

  // 定义模块
  Layui.prototype.define = function(deps, factory){
    var that = this;
    var type = typeof deps === 'function';
    var callback = function(){
      var setApp = function(app, exports){
        layui[app] = exports;
        config.status[app] = true;
      };
      typeof factory === 'function' && factory(function(app, exports){
        setApp(app, exports);
        config.callback[app] = function(){
          factory(setApp);
        }
      });
      return this;
    };

    type && (
      factory = deps,
      deps = []
    );

    that.use(deps, callback, null, 'define');
    return that;
  };

  // 使用特定模块
  Layui.prototype.use = function(apps, callback, exports, from){
    var that = this;
    var dir = config.dir = config.dir ? config.dir : getPath;
    var head = doc.getElementsByTagName('head')[0];

    apps = function(){
      if(typeof apps === 'string'){
        return [apps];
      }
      // 当第一个参数为 function 时，则自动加载所有内置模块，且执行的回调即为该 function 参数；
      else if(typeof apps === 'function'){
        callback = apps;
        return ['all'];
      }
      return apps;
    }();

    // 如果页面已经存在 jQuery 1.7+ 库且所定义的模块依赖 jQuery，则不加载内部 jquery 模块
    if(win.jQuery && jQuery.fn.on){
      that.each(apps, function(index, item){
        if(item === 'jquery'){
          apps.splice(index, 1);
        }
      });
      layui.jquery = layui.$ = jQuery;
    }

    var item = apps[0];
    var timeout = 0;

    exports = exports || [];

    // 静态资源host
    config.host = config.host || (dir.match(/\/\/([\s\S]+?)\//)||['//'+ location.host +'/'])[0];

    // 加载完毕
    function onScriptLoad(e, url){
      var readyRegExp = navigator.platform === 'PLaySTATION 3' ? /^complete$/ : /^(complete|loaded)$/
      if (e.type === 'load' || (readyRegExp.test((e.currentTarget || e.srcElement).readyState))) {
        config.modules[item] = url;
        head.removeChild(node);
        (function poll() {
          if(++timeout > config.timeout * 1000 / 4){
            return error(item + ' is not a valid module', 'error');
          }
          config.status[item] ? onCallback() : setTimeout(poll, 4);
        }());
      }
    }

    // 回调
    function onCallback(){
      exports.push(layui[item]);
      apps.length > 1 ?
        that.use(apps.slice(1), callback, exports, from)
      : ( typeof callback === 'function' && function(){
        // 保证文档加载完毕再执行回调
        if(layui.jquery && typeof layui.jquery === 'function' && from !== 'define'){
          return layui.jquery(function(){
            callback.apply(layui, exports);
          });
        }
        callback.apply(layui, exports);
      }() );
    }

    // 如果引入了聚合板，内置的模块则不必重复加载
    if( apps.length === 0 || (layui['layui.all'] && modules[item]) ){
      return onCallback(), that;
    }

    /*
     * 获取加载的模块 URL
     * 如果是内置模块，则按照 dir 参数拼接模块路径
     * 如果是扩展模块，则判断模块路径值是否为 {/} 开头，
     * 如果路径值是 {/} 开头，则模块路径即为后面紧跟的字符。
     * 否则，则按照 base 参数拼接模块路径
    */

    var url = ( modules[item] ? (dir + 'modules/')
      : (/^\{\/\}/.test(that.modules[item]) ? '' : (config.base || ''))
    ) + (that.modules[item] || item) + '.js';
    url = url.replace(/^\{\/\}/, '');

    // 如果扩展模块（即：非内置模块）对象已经存在，则不必再加载
    if(!config.modules[item] && layui[item]){
      config.modules[item] = url; // 并记录起该扩展模块的 url
    }

    // 首次加载模块
    if(!config.modules[item]){
      var node = doc.createElement('script');

      node.async = true;
      node.charset = 'utf-8';
      node.src = url + function(){
        var version = config.version === true
        ? (config.v || (new Date()).getTime())
        : (config.version||'');
        return version ? ('?v=' + version) : '';
      }();

      head.appendChild(node);

      if(node.attachEvent && !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && !isOpera){
        node.attachEvent('onreadystatechange', function(e){
          onScriptLoad(e, url);
        });
      } else {
        node.addEventListener('load', function(e){
          onScriptLoad(e, url);
        }, false);
      }

      config.modules[item] = url;
    } else { // 缓存
      (function poll() {
        if(++timeout > config.timeout * 1000 / 4){
          return error(item + ' is not a valid module', 'error');
        }
        (typeof config.modules[item] === 'string' && config.status[item])
        ? onCallback()
        : setTimeout(poll, 4);
      }());
    }

    return that;
  };

  // 弃用原有的指定模块，以便重新扩展新的同名模块
  Layui.prototype.disuse = function(apps){
    var that = this;
    apps = that.isArray(apps) ? apps : [apps];
    that.each(apps, function (index, item) {
      if (!config.status[item]) {
        // return error('module ' + item + ' is not exist');
      }
      delete that[item];
      delete modules[item];
      delete that.modules[item];
      delete config.status[item];
      delete config.modules[item];
    });
    return that;
  };

  // 获取节点的 style 属性值
  Layui.prototype.getStyle = function(node, name){
    var style = node.currentStyle ? node.currentStyle : win.getComputedStyle(node, null);
    return style[style.getPropertyValue ? 'getPropertyValue' : 'getAttribute'](name);
  };

  // css 外部加载器
  Layui.prototype.link = function(href, fn, cssname){
    var that = this;
    var head = doc.getElementsByTagName('head')[0];
    var link = doc.createElement('link');

    if(typeof fn === 'string') cssname = fn;

    var app = (cssname || href).replace(/\.|\//g, '');
    var id = 'layuicss-'+ app;
    var STAUTS_NAME = 'creating';
    var timeout = 0;

    link.href = href + (config.debug ? '?v='+new Date().getTime() : '');
    link.rel = 'stylesheet';
    link.id = id;
    link.media = 'all';

    if(!doc.getElementById(id)){
      head.appendChild(link);
    }

    if(typeof fn !== 'function') return that;

    // 轮询 css 是否加载完毕
    (function poll(status) {
      var delay = 100;
      var getLinkElem = doc.getElementById(id); // 获取动态插入的 link 元素

      // 如果轮询超过指定秒数，则视为请求文件失败或 css 文件不符合规范
      if(++timeout > config.timeout * 1000 / delay){
        return error(href + ' timeout');
      }

      // css 加载就绪
      if(parseInt(that.getStyle(getLinkElem, 'width')) === 1989){
        // 如果参数来自于初始轮询（即未加载就绪时的），则移除 link 标签状态
        if(status === STAUTS_NAME) getLinkElem.removeAttribute('lay-status');
        // 如果 link 标签的状态仍为「创建中」，则继续进入轮询，直到状态改变，则执行回调
        getLinkElem.getAttribute('lay-status') === STAUTS_NAME ? setTimeout(poll, delay) : fn();
      } else {
        getLinkElem.setAttribute('lay-status', STAUTS_NAME);
        setTimeout(function(){
          poll(STAUTS_NAME);
        }, delay);
      }
    }());

    // 轮询css是否加载完毕
    /*
    (function poll() {
      if(++timeout > config.timeout * 1000 / 100){
        return error(href + ' timeout');
      };
      parseInt(that.getStyle(doc.getElementById(id), 'width')) === 1989 ? function(){
        fn();
      }() : setTimeout(poll, 100);
    }());
    */

    return that;
  };

  // css 内部加载器
  Layui.prototype.addcss = function(firename, fn, cssname){
    return layui.link(config.dir + 'css/' + firename, fn, cssname);
  };

  // 存储模块的回调
  config.callback = {};

  // 重新执行模块的工厂函数
  Layui.prototype.factory = function(modName){
    if(layui[modName]){
      return typeof config.callback[modName] === 'function'
        ? config.callback[modName]
      : null;
    }
  };

  // 图片预加载
  Layui.prototype.img = function(url, callback, error) {
    var img = new Image();
    img.src = url;
    if(img.complete){
      return callback(img);
    }
    img.onload = function(){
      img.onload = null;
      typeof callback === 'function' && callback(img);
    };
    img.onerror = function(e){
      img.onerror = null;
      typeof error === 'function' && error(e);
    };
  };

  // 全局配置
  Layui.prototype.config = function(options){
    options = options || {};
    for(var key in options){
      config[key] = options[key];
    }
    return this;
  };

  // 记录全部模块
  Layui.prototype.modules = function(){
    var clone = {};
    for(var o in modules){
      clone[o] = modules[o];
    }
    return clone;
  }();

  // 拓展模块
  Layui.prototype.extend = function(options){
    var that = this;

    // 验证模块是否被占用
    options = options || {};
    for(var o in options){
      if(that[o] || that.modules[o]){
        error(o+ ' Module already exists', 'error');
      } else {
        that.modules[o] = options[o];
      }
    }

    return that;
  };

  // location.hash 路由解析
  Layui.prototype.router = Layui.prototype.hash = function(hash){
    var that = this;
    var hash = hash || location.hash;
    var data = {
      path: [],
      search: {},
      hash: (hash.match(/[^#](#.*$)/) || [])[1] || ''
    };

    if(!/^#\//.test(hash)) return data; // 禁止非路由规范

    hash = hash.replace(/^#\//, '');
    data.href = '/' + hash;
    hash = hash.replace(/([^#])(#.*$)/, '$1').split('/') || [];

    // 提取 Hash 结构
    that.each(hash, function(index, item){
      /^\w+=/.test(item) ? function(){
        item = item.split('=');
        data.search[item[0]] = item[1];
      }() : data.path.push(item);
    });

    return data;
  };

  // URL 解析
  Layui.prototype.url = function(href){
    var that = this;
    var data = {
      // 提取 url 路径
      pathname: function(){
        var pathname = href
          ? function(){
            var str = (href.match(/\.[^.]+?\/.+/) || [])[0] || '';
            return str.replace(/^[^\/]+/, '').replace(/\?.+/, '');
          }()
        : location.pathname;
        return pathname.replace(/^\//, '').split('/');
      }(),

      // 提取 url 参数
      search: function(){
        var obj = {};
        var search = (href
          ? function(){
            var str = (href.match(/\?.+/) || [])[0] || '';
            return str.replace(/\#.+/, '');
          }()
          : location.search
        ).replace(/^\?+/, '').split('&'); // 去除 ?，按 & 分割参数

        // 遍历分割后的参数
        that.each(search, function(index, item){
          var _index = item.indexOf('=')
          ,key = function(){ // 提取 key
            if(_index < 0){
              return item.substr(0, item.length);
            } else if(_index === 0){
              return false;
            } else {
              return item.substr(0, _index);
            }
          }();
          // 提取 value
          if(key){
            obj[key] = _index > 0 ? item.substr(_index + 1) : null;
          }
        });

        return obj;
      }(),

      // 提取 Hash
      hash: that.router(function(){
        return href
          ? ((href.match(/#.+/) || [])[0] || '/')
        : location.hash;
      }())
    };

    return data;
  };

  // 本地持久存储
  Layui.prototype.data = function(table, settings, storage){
    table = table || 'layui';
    storage = storage || localStorage;

    if(!win.JSON || !win.JSON.parse) return;

    // 如果 settings 为 null，则删除表
    if(settings === null){
      return delete storage[table];
    }

    settings = typeof settings === 'object'
      ? settings
    : {key: settings};

    try {
      var data = JSON.parse(storage[table]);
    } catch(e) {
      var data = {};
    }

    if('value' in settings) data[settings.key] = settings.value;
    if(settings.remove) delete data[settings.key];
    storage[table] = JSON.stringify(data);

    return settings.key ? data[settings.key] : data;
  };

  // 本地临时存储
  Layui.prototype.sessionData = function(table, settings){
    return this.data(table, settings, sessionStorage);
  }

  // 设备信息
  Layui.prototype.device = function(key){
    var agent = navigator.userAgent.toLowerCase();

    // 获取版本号
    var getVersion = function(label){
      var exp = new RegExp(label + '/([^\\s\\_\\-]+)');
      label = (agent.match(exp)||[])[1];
      return label || false;
    };

    // 返回结果集
    var result = {
      os: function(){ // 底层操作系统
        if(/windows/.test(agent)){
          return 'windows';
        } else if(/linux/.test(agent)){
          return 'linux';
        } else if(/iphone|ipod|ipad|ios/.test(agent)){
          return 'ios';
        } else if(/mac/.test(agent)){
          return 'mac';
        }
      }(),
      ie: function(){ // ie 版本
        return (!!win.ActiveXObject || "ActiveXObject" in win) ? (
          (agent.match(/msie\s(\d+)/) || [])[1] || '11' // 由于 ie11 并没有 msie 的标识
        ) : false;
      }(),
      weixin: getVersion('micromessenger')  // 是否微信
    };

    // 任意的 key
    if(key && !result[key]){
      result[key] = getVersion(key);
    }

    // 移动设备
    result.android = /android/.test(agent);
    result.ios = result.os === 'ios';
    result.mobile = (result.android || result.ios);

    return result;
  };

  // 提示
  Layui.prototype.hint = function(){
    return {
      error: error
    };
  };

  // typeof 类型细分 -> string/number/boolean/undefined/null、object/array/function/…
  Layui.prototype._typeof = Layui.prototype.type = function(operand){
    if(operand === null) return String(operand);

    // 细分引用类型
    return (typeof operand === 'object' || typeof operand === 'function') ? function(){
      var type = Object.prototype.toString.call(operand).match(/\s(.+)\]$/) || []; // 匹配类型字符
      var classType = 'Function|Array|Date|RegExp|Object|Error|Symbol'; // 常见类型字符

      type = type[1] || 'Object';

      // 除匹配到的类型外，其他对象均返回 object
      return new RegExp('\\b('+ classType + ')\\b').test(type)
        ? type.toLowerCase()
      : 'object';
    }() : typeof operand;
  };

  // 对象是否具备数组结构（此处为兼容 jQuery 对象）
  Layui.prototype._isArray = Layui.prototype.isArray = function(obj){
    var that = this;
    var len;
    var type = that.type(obj);

    if(!obj || (typeof obj !== 'object') || obj === win) return false;

    len = 'length' in obj && obj.length; // 兼容 ie
    return type === 'array' || len === 0 || (
      typeof len === 'number' && len > 0 && (len - 1) in obj // 兼容 jQuery 对象
    );
  };

  // 遍历
  Layui.prototype.each = function(obj, fn){
    var key;
    var that = this;
    var callFn = function(key, obj){ // 回调
      return fn.call(obj[key], key, obj[key])
    };

    if(typeof fn !== 'function') return that;
    obj = obj || [];

    // 优先处理数组结构
    if(that.isArray(obj)){
      for(key = 0; key < obj.length; key++){
        if(callFn(key, obj)) break;
      }
    } else {
      for(key in obj){
        if(callFn(key, obj)) break;
      }
    }

    return that;
  };

  // 将数组中的成员对象按照某个 key 的 value 值进行排序
  Layui.prototype.sort = function(arr, key, desc, notClone){
    var that = this;
    var clone = notClone ? (arr || []) : JSON.parse(
      JSON.stringify(arr || [])
    );

    // 若未传入 key，则直接返回原对象
    if(that.type(arr) === 'object' && !key){
      return clone;
    } else if(typeof arr !== 'object'){ // 若 arr 非对象
      return [clone];
    }

    // 开始排序
    clone.sort(function(o1, o2){
      var v1 = o1[key];
      var v2 = o2[key];

      /*
       * 特殊数据
       * 若比较的成员均非对象
       */

      // 若比较的成员均为数字
      if(!isNaN(o1) && !isNaN(o2)) return o1 - o2;
      // 若比较的成员只存在某一个非对象
      if(!isNaN(o1) && isNaN(o2)){
        if(key && typeof o2 === 'object'){
          v1 = o1;
        } else {
          return -1;
        }
      } else if (isNaN(o1) && !isNaN(o2)){
        if(key && typeof o1 === 'object'){
          v2 = o2;
        } else {
          return 1;
        }
      }

      /*
       * 正常数据
       * 即成员均为对象，也传入了对比依据： key
       * 若 value 为数字，按「大小」排序；若 value 非数字，则按「字典序」排序
       */

      // value 是否为数字
      var isNum = [!isNaN(v1), !isNaN(v2)];

      // 若为数字比较
      if(isNum[0] && isNum[1]){
        if(v1 && (!v2 && v2 !== 0)){ // 数字 vs 空
          return 1;
        } else if((!v1 && v1 !== 0) && v2){ // 空 vs 数字
          return -1;
        } else { // 数字 vs 数字
          return v1 - v2;
        }
      }

      /**
       * 字典序排序
       */

      // 若为非数字比较
      if(!isNum[0] && !isNum[1]){
        // 字典序比较
        if(v1 > v2){
          return 1;
        } else if (v1 < v2) {
          return -1;
        } else {
          return 0;
        }
      }

      // 若为混合比较
      if(isNum[0] || !isNum[1]){ // 数字 vs 非数字
        return -1;
      } else if(!isNum[0] || isNum[1]) { // 非数字 vs 数字
        return 1;
      }

    });

    desc && clone.reverse(); // 倒序
    return clone;
  };

  // 阻止事件冒泡
  Layui.prototype.stope = function(thisEvent){
    thisEvent = thisEvent || win.event;
    try { thisEvent.stopPropagation() } catch(e){
      thisEvent.cancelBubble = true;
    }
  };

  // 字符常理
  var EV_REMOVE = 'LAYUI-EVENT-REMOVE';

  // 自定义模块事件
  Layui.prototype.onevent = function(modName, events, callback){
    if(typeof modName !== 'string'
    || typeof callback !== 'function') return this;

    return Layui.event(modName, events, null, callback);
  };

  // 执行自定义模块事件
  Layui.prototype.event = Layui.event = function(modName, events, params, fn){
    var that = this;
    var result = null;
    var filter = (events || '').match(/\((.*)\)$/)||[]; // 提取事件过滤器字符结构，如：select(xxx)
    var eventName = (modName + '.'+ events).replace(filter[0], ''); // 获取事件名称，如：form.select
    var filterName = filter[1] || ''; // 获取过滤器名称,，如：xxx
    var callback = function(_, item){
      var res = item && item.call(that, params);
      res === false && result === null && (result = false);
    };

    // 如果参数传入特定字符，则执行移除事件
    if(params === EV_REMOVE){
      delete (that.cache.event[eventName] || {})[filterName];
      return that;
    }

    // 添加事件
    if(fn){
      config.event[eventName] = config.event[eventName] || {};

      if (filterName) {
        // 带filter不支持重复事件
        config.event[eventName][filterName] = [fn];
      } else {
        // 不带filter处理的是所有的同类事件，应该支持重复事件
        config.event[eventName][filterName] = config.event[eventName][filterName] || [];
        config.event[eventName][filterName].push(fn);
      }
      return this;
    }

    // 执行事件回调
    layui.each(config.event[eventName], function(key, item){
      // 执行当前模块的全部事件
      if(filterName === '{*}'){
        layui.each(item, callback);
        return;
      }

      // 执行指定事件
      key === '' && layui.each(item, callback);
      (filterName && key === filterName) && layui.each(item, callback);
    });

    return result;
  };

  // 新增模块事件
  Layui.prototype.on = function(events, modName, callback){
    var that = this;
    return that.onevent.call(that, modName, events, callback);
  }

  // 移除模块事件
  Layui.prototype.off = function(events, modName){
    var that = this;
    return that.event.call(that, modName, events, EV_REMOVE);
  };

  // 防抖
  Layui.prototype.debounce = function (func, wait) {
    var timeout;
    return function () {
      var context = this;
      var args = arguments;
      clearTimeout(timeout);
      timeout = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
  };

  // 节流
  Layui.prototype.throttle = function (func, wait) {
    var cooldown = false;
    return function () {
      var context = this;
      var args = arguments;
      if (!cooldown) {
        func.apply(context, args);
        cooldown = true;
        setTimeout(function () {
          cooldown = false;
        }, wait);
      }
    }
  };

  // exports layui
  win.layui = new Layui();

}(window); // gulp build: layui-footer

/**
 * 用于打包聚合版，该文件不会存在于构建后的目录 
 */
 
layui.define(function(exports){
  var cache = layui.cache;
  layui.config({
    dir: cache.dir.replace(/lay\/dest\/$/, '')
  });
  exports('layui.all', layui.v);
});
/** lay 基础模块 | MIT Licensed */

;!function(window){ // gulp build: lay-header
  "use strict";

  var MOD_NAME = 'lay'; // 模块名
  var document = window.document;

  /**
   * 元素查找
   * @param {string | HTMLElement | JQuery} selector
   */
  var lay = function(selector){
    return new Class(selector);
  };

  // 构造器
  var Class = function(selector){
    var that = this;
    var elem = typeof selector === 'object' ? function(){
      // 仅适配简单元素对象
      return layui.isArray(selector) ? selector : [selector];
    }() : (
      this.selector = selector,
      document.querySelectorAll(selector || null)
    );

    lay.each(elem, function(index, item){
      that.push(elem[index]);
    });
  };

  /*
   * API 兼容
   */
  Array.prototype.indexOf = Array.prototype.indexOf || function(searchElement, fromIndex) {
    var rst = -1;
    fromIndex = fromIndex || 0;
    layui.each(this, function(index, val){
      if (searchElement === val && index >= fromIndex) {
        rst = index;
        return !0;
      }
    });
    return rst;
  };

  /*
    lay 对象操作
  */

  Class.fn = Class.prototype = [];
  Class.fn.constructor = Class;

  /**
   * 将两个或多个对象的内容深度合并到第一个对象中
   * @callback ExtendFunc
   * @param {*} target - 一个对象
   * @param {...*} objectN - 包含额外的属性合并到第一个参数
   * @returns {*} 返回合并后的对象
   */
  /** @type ExtendFunc*/
  lay.extend = function(){
    var ai = 1;
    var length;
    var args = arguments;
    var clone = function(target, obj){
      target = target || (layui.type(obj) === 'array' ? [] : {}); // 目标对象
      for(var i in obj){
        // 若值为普通对象，则进入递归，继续深度合并
        target[i] = (obj[i] && obj[i].constructor === Object)
          ? clone(target[i], obj[i])
        : obj[i];
      }
      return target;
    };

    args[0] = typeof args[0] === 'object' ? args[0] : {};
    length = args.length

    for(; ai < length; ai++){
      if(typeof args[ai] === 'object'){
        clone(args[0], args[ai]);
      }
    }
    return args[0];
  };

  /**
   * IE 版本
   * @type {string | boolean} - 如果是 IE 返回版本字符串，否则返回 false
   */
  lay.ie = function(){
    var agent = navigator.userAgent.toLowerCase();
    return (!!window.ActiveXObject || "ActiveXObject" in window) ? (
      (agent.match(/msie\s(\d+)/) || [])[1] || '11' // 由于 ie11 并没有 msie 的标识
    ) : false;
  }();


  /**
   * 获取 layui 常见方法，以便用于组件单独版
   */

  lay.layui = layui || {};
  lay.getPath = layui.cache.dir; // 获取当前 JS 所在目录
  lay.stope = layui.stope; // 中止冒泡
  lay.each = function(){ // 遍历
    layui.each.apply(layui, arguments);
    return this;
  };


  /**
   * 数字前置补零
   * @param {number | string} num - 原始数字
   * @param {number} [length=2] - 数字长度，如果原始数字长度小于 length，则前面补零
   * @returns {string} 返回补 0 后的数字
   * @example
   * ```js
   * lay.digit(6, 2); // "06"
   * lay.digit('7', 3); // "007"
   * ```
   */
  lay.digit = function(num, length){
    if(!(typeof num === 'string' || typeof num === 'number')) return '';

    var str = '';
    num = String(num);
    length = length || 2;
    for(var i = num.length; i < length; i++){
      str += '0';
    }
    return num < Math.pow(10, length) ? str + num : num;
  };

  /**
   * 创建元素
   * @param {string} elemName - 元素的标签名
   * @param {Object.<string, string>} [attr] - 添加到元素上的属性
   * @returns {HTMLElement} 返回创建的 HTML 元素
   * @example
   * ```js
   * lay.elem('div', {id: 'test'}) // <div id="test"></div>
   * ```
   */
  lay.elem = function(elemName, attr){
    var elem = document.createElement(elemName);
    lay.each(attr || {}, function(key, value){
      elem.setAttribute(key, value);
    });
    return elem;
  };

  /**
   * 当前页面是否存在滚动条
   * @returns {boolean} 是否存在滚动条
   * @example
   * ```
   * lay.hasScrollbar() // true 或 false
   * ```
   */
  lay.hasScrollbar = function(){
    return document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight);
  };

  /**
   * 获取 style rules
   * @param {HTMLStyleElement} style - HTMLStyle 元素
   * @param {(ruleItem: CSSStyleRule, index: number) => boolean} [callback] - 用来返回 style 元素中的每个 `style rule` 的函数，返回 true 终止遍历
   * @returns {CSSRuleList } 返回 `style rules`
   * @example
   * ```
   * <style id="test">
   *   .lay-card{
   *     color: #000;
   *   }
   *   .lay-btn-success{
   *     color: green;
   *   }
   * </style>
   *
   * lay.getStyleRules($('#test')[0], function(rule, index){
   *   if(rule.selectorText === '.lay-card'){
   *     console.log(index, rule.cssText) // 0 '.lay-card{color: #000}'
   *     rule.style.color = '#EEE';
   *     return true; // 终止遍历
   *   }
   * }) // RuleList
   * ```
   */
  lay.getStyleRules = function(style, callback) {
    if (!style) return;

    var sheet = style.sheet || style.styleSheet || {};
    var rules = sheet.cssRules || sheet.rules;

    if (typeof callback === 'function') {
      layui.each(rules, function(i, item){
        if (callback(item, i)) return true;
      });
    }

    return rules;
  };

  /**
   * 创建 style 样式
   * @param {Object} options - 可配置的选项
   * @param {string | HTMLElement | JQuery} [options.target] - 目标容器，指定后会将样式追加到目标容器
   * @param {string} [options.id] - 样式元素的 id，默认自增
   * @param {string} options.text - 样式内容
   * @returns {HTMLStyleElement} 返回创建的样式元素
   * @example
   * ```html
   * <div id="targetEl">
   *   <!-- 样式追加到目标容器 -->
   *   <style id="LAY-STYLE-DF-0">.card{color: #000}</style>
   * </div>
   *
   * lay.style({
   *   target: '#targetEl',
   *   text: '.card{color: #000}'
   * }) // <style id="LAY-STYLE-DF-0">.card{color: #000}</style>
   * ```
   */
  lay.style = function(options){
    options = options || {};

    var style = lay.elem('style');
    var styleText = options.text || '';
    var target = options.target;

    if (!styleText) return;

    // 添加样式
    if ('styleSheet' in style) {
      style.setAttribute('type', 'text/css');
      style.styleSheet.cssText = styleText;
    } else {
      style.innerHTML = styleText;
    }

    // ID
    style.id = 'LAY-STYLE-'+ (options.id || function(index) {
      lay.style.index++;
      return 'DF-'+ index;
    }(lay.style.index || 0));

    // 是否向目标容器中追加 style 元素
    if (target) {
      var styleElem = lay(target).find('#'+ style.id);
      styleElem[0] && styleElem.remove();
      lay(target).append(style);
    }

    return style;
  };

  /**
   * 将元素定位到指定目标元素附近
   * @param {HTMLElement} target - 目标元素
   * @param {HTMLElement} elem - 定位元素
   * @param {Object} [opts] - 可配置的选项
   * @param {'absolute' | 'fixed'} [opts.position] - 元素的定位类型
   * @param {'left' | 'right'} [opts.clickType="left"] - 点击类型，默认为 'left'，如果 {@link target} 是 document 或 body 元素，则为 'right'
   * @param {'left' | 'right' | 'center'} [opts.align="left"] - 对齐方式
   * @param {boolean} [opts.allowBottomOut=false] - 顶部没有足够区域显示时，是否允许底部溢出
   * @param {string | number} [opts.margin=5] - 边距
   * @param {Event} [opts.e] - 事件对象，仅右键生效
   * @param {boolean} [opts.SYSTEM_RELOAD] - 是否重载，用于出现滚动条时重新计算位置
   * @example
   * ```js
   * <button id="targetEl">dropdown</button>
   * <ul id="contentEl" class="dropdown-menu">
   *   <li>菜单1</li>
   *   <li>菜单2</li>
   * </ul>
   *
   * // 下拉菜单将被定位到按钮附近
   * lay.position(
   *   $('#targetEl')[0],
   *   $('#contentEl')[0],
   *   {
   *     position: 'fixed',
   *     align: 'center'
   *   }
   * )
   * ```
   */
  lay.position = function(target, elem, opts){
    if(!elem) return;
    opts = opts || {};

    // 如果绑定的是 document 或 body 元素，则直接获取鼠标坐标
    if(target === document || target === lay('body')[0]){
      opts.clickType = 'right';
    }

    // 绑定绑定元素的坐标
    var rect = opts.clickType === 'right' ? function(){
      var e = opts.e || window.event || {};
      return {
        left: e.clientX,
        top: e.clientY,
        right: e.clientX,
        bottom: e.clientY
      }
    }() : target.getBoundingClientRect();
    var elemWidth = elem.offsetWidth; // 控件的宽度
    var elemHeight = elem.offsetHeight; // 控件的高度

    // 滚动条高度
    var scrollArea = function(type){
      type = type ? 'scrollLeft' : 'scrollTop';
      return document.body[type] | document.documentElement[type];
    };

    // 窗口宽高
    var winArea = function(type){
      return document.documentElement[type ? 'clientWidth' : 'clientHeight']
    };
    var margin = 'margin' in opts ? opts.margin : 5;
    var left = rect.left;
    var top = rect.bottom;

    // 相对元素居中
    if(opts.align === 'center'){
      left = left - (elemWidth - target.offsetWidth) / 2;
    } else if(opts.align === 'right'){
      left = left - elemWidth + target.offsetWidth;
    }

    // 判断右侧是否超出边界
    if(left + elemWidth + margin > winArea('width')){
      left = winArea('width') - elemWidth - margin; // 如果超出右侧，则将面板向右靠齐
    }
    // 左侧是否超出边界
    if(left < margin) left = margin;


    // 判断底部和顶部是否超出边界
    if(rect.bottom + elemHeight + margin > winArea()){ // 底部超出边界
      // 优先判断顶部是否有足够区域显示完全，且底部不能超出边界
      if(rect.top > elemHeight + margin && rect.top <= winArea() ){
        top = rect.top - elemHeight - margin*2; // 顶部有足够的区域显示
      } else if(!opts.allowBottomOut){ // 顶部没有足够区域显示时，是否允许底部溢出
        top = winArea() - elemHeight - margin*2; // 面板向底部靠齐
        if(top < 0) top = 0; // 如果面板底部靠齐时，又溢出窗口顶部，则只能将顶部靠齐
      }
    }
    /*
    if(top + elemHeight + margin > winArea()){
      // 优先顶部是否有足够区域显示完全
      if(rect.top > elemHeight + margin){
        top = rect.top - elemHeight - margin*2; // 顶部有足够的区域显示
      } else {
        // 如果面板是鼠标右键弹出，且顶部没有足够区域显示，则将面板向底部靠齐
        if(obj.clickType === 'right'){
          top = winArea() - elemHeight - margin*2;
          if(top < 0) top = 0; // 不能溢出窗口顶部
        } else {
          top = margin; // 位置计算逻辑完备性处理
        }
      }
    }
    */

    // 定位类型
    var position = opts.position;
    if(position) elem.style.position = position;

    // 设置坐标
    elem.style.left = left + (position === 'fixed' ? 0 : scrollArea(1)) + 'px';
    elem.style.top = top + (position === 'fixed' ? 0 : scrollArea()) + 'px';

    // 防止页面无滚动条时，又因为弹出面板而出现滚动条导致的坐标计算偏差
    if(!lay.hasScrollbar()){
      var rect1 = elem.getBoundingClientRect();
      // 如果弹出面板的溢出窗口底部，则表示将出现滚动条，此时需要重新计算坐标
      if(!opts.SYSTEM_RELOAD && (rect1.bottom + margin) > winArea()){
        opts.SYSTEM_RELOAD = true;
        setTimeout(function(){
          lay.position(target, elem, opts);
        }, 50);
      }
    }
  };

  /**
   * 获取元素上的属性配置项
   * @param {string | HTMLElement | JQuery} elem - HTML 元素
   * @param {{attr: string} | string} [opts="lay-options"] - 可配置的选项，string 类型指定属性名
   * @returns {Object.<string, any>} 返回元素上的属性配置项
   * @example
   * ```js
   * <div id="testEl" lay-options="{color:red}" lay-toc="{hot: true}"></div>
   *
   * var elem = $('#testEl')
   * lay.options(elem) // {color:red}
   * lay.options(elem[0]) // {color:red}
   * lay.options('#testEl') // {color:red}
   * lay.options('#testEl', {attr: 'lay-toc'}) // {hot: true}
   * lay.options('#testEl', 'lay-toc') // {hot: true}
   *
   * $('#testEl').attr('lay-toc') // '{hot: true}'
   * ```
   */
  lay.options = function(elem, opts){
    opts = typeof opts === 'object' ? opts : {attr: opts};

    if(elem === document) return {};

    var othis = lay(elem);
    var attrName = opts.attr || 'lay-options';
    var attrValue = othis.attr(attrName);

    try {
      /**
       * 请注意: 开发者在使用 lay-options="{}" 配置组件选项时，需确保属性值不来自于网页用户,
       * 即属性值必须在网页开发者自身的可控范围内，否则请勿在 HTML 标签属性中获取组件选项。
       */
      return new Function('return '+ (attrValue || '{}'))();
    } catch(ev) {
      layui.hint().error(opts.errorText || [
        attrName + '="'+ attrValue + '"',
        '\n parseerror: '+ ev
      ].join('\n'), 'error');
      return {};
    }
  };


  /**
   * 元素是否属于顶级元素（document 或 body）
   * @param {HTMLElement} elem - HTML 元素
   * @returns {boolean} 是否属于顶级元素
   * @example
   * ```js
   * lay.isTopElem(document) // true
   * ```
   */
  lay.isTopElem = function(elem){
    var topElems = [document, lay('body')[0]]
    ,matched = false;
    lay.each(topElems, function(index, item){
      if(item === elem){
        return matched = true
      }
    });
    return matched;
  };

  // 剪切板
  lay.clipboard = {
    /**
     * 写入文本
     * @param {Object} options - 可配置的选项
     * @param {string} options.text - 写入剪贴板的文本
     * @param {() => void} [options.done] - 写入成功/完成回调
     * @param {(err?: any) => void} [options.error] - 写入失败回调
     * @example
     * ```js
     * lay.clipboard.writeText({
     *   text: '测试文本',
     *   done: function(){ layer.msg('copied')},
     *   error: function(){ layer.msg('error')}
     * })
     * ```
     */
    writeText: function(options) {
      var text = String(options.text);

      if(navigator && 'clipboard' in navigator){
        navigator.clipboard.writeText(text)
          .then(options.done, function(){
            legacyCopy();
        });
      }else{
        legacyCopy();
      }

      function legacyCopy(){
        var elem = document.createElement('textarea');

        elem.value = text;
        elem.style.position = 'fixed';
        elem.style.opacity = '0';
        elem.style.top = '0px';
        elem.style.left = '0px';

        document.body.appendChild(elem);
        elem.select();

        try {
          document.execCommand('copy');
          typeof options.done === 'function' && options.done();
        } catch(err) {
          typeof options.error === 'function' && options.error(err);
        } finally {
          elem.remove ? elem.remove() : document.body.removeChild(elem);
        }
      }
    }
  };

  /**
   * 检测是否支持 Passive Event Listeners
   * 引用自 https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
   * @type {boolean}
   */
  lay.passiveSupported = function(){
    var passiveSupported = false;
    try {
      var opts = Object.defineProperty({}, 'passive', {
        get: function() {
          passiveSupported = true;
        }
      });
      window.addEventListener('test', null, opts);
      window.removeEventListener('test', null, opts);
    } catch (err) {}
    return passiveSupported;
  }();

  /**
   * 是否支持 touch 事件
   */
  lay.touchEventsSupported = function(){
    return 'ontouchstart' in window;
  };

  /**
   * @typedef touchSwipeState
   * @prop {{x: number,y: number}} pointerStart - 初始坐标
   * @prop {{x: number,y: number}} pointerEnd - 结束坐标
   * @prop {number} distanceX - X 轴移动距离
   * @prop {number} distanceY - Y 轴移动距离
   * @prop {'none'|'right'|'left'|'up'|'down'} direction - 滑动方向
   * @prop {Date} timeStart 开始时间
   */
  /**
   * @callback touchSwipeCallback
   * @param {TouchEvent} e 滑动事件
   * @param {touchSwipeState} state 滑动相关的状态
   */
  /**
   * 基于 touch 事件的触摸滑动
   * @param {string | HTMLElement | JQuery} elem - HTML 元素
   * @param {{onTouchStart?: touchSwipeCallback, onTouchMove?: touchSwipeCallback, onTouchEnd?: touchSwipeCallback}} opts - 配置项
   */
  lay.touchSwipe = function(elem, opts){
    var options = opts
    var targetElem = lay(elem)[0];

    if(!targetElem || !lay.touchEventsSupported()) return;

    var state = {
      pointerStart: {x:0, y:0},
      pointerEnd: {x:0, y:0},
      distanceX: 0,
      distanceY: 0,
      direction:'none', // 'up','down','left','right','none
      timeStart: null
    }

    var onStart = function(e){
      if(e.touches.length !== 1) return;
      bindEvents();
      // 重置状态
      state.timeStart = Date.now();
      state.pointerStart.x = state.pointerEnd.x = e.touches[0].clientX;
      state.pointerStart.y = state.pointerEnd.y = e.touches[0].clientY;
      state.distanceX = state.distanceY = 0;
      state.direction = 'none'

      options.onTouchStart && options.onTouchStart(e, state);
    }

    var onMove = function(e){
      e.preventDefault();
      state.pointerEnd.x = e.touches[0].clientX;
      state.pointerEnd.y = e.touches[0].clientY;
      state.distanceX = state.pointerStart.x - state.pointerEnd.x;
      state.distanceY = state.pointerStart.y - state.pointerEnd.y;
      if(Math.abs(state.distanceX) > Math.abs(state.distanceY)){
        state.direction = state.distanceX > 0 ? 'left' : 'right';
      }else{
        state.direction = state.distanceY > 0 ? 'up' : 'down';
      }
      options.onTouchMove && options.onTouchMove(e, state);
    }

    var onEnd = function(e){
      options.onTouchEnd && options.onTouchEnd(e, state);
      unbindEvents();
    }
    
    var bindEvents = function(){
      targetElem.addEventListener('touchmove', onMove, lay.passiveSupported ? { passive: false} : false);
      targetElem.addEventListener('touchend', onEnd);
      targetElem.addEventListener('touchcancel', onEnd);
    }

    var unbindEvents = function(){
      targetElem.removeEventListener('touchmove', onMove);
      targetElem.removeEventListener('touchend', onEnd, lay.passiveSupported ? { passive: false} : false);
      targetElem.removeEventListener('touchcancel', onEnd);
    }

    // 防止事件重复绑定
    if(targetElem.__lay_touchswipe_cb_){
      targetElem.removeEventListener('touchstart', targetElem.__lay_touchswipe_cb_);
    }
    targetElem.__lay_touchswipe_cb_ = onStart;
    targetElem.addEventListener('touchstart', onStart);
  }


  /*
   * lay 元素操作
   */


  // 追加字符
  Class.addStr = function(str, new_str){
    str = str.replace(/\s+/, ' ');
    new_str = new_str.replace(/\s+/, ' ').split(' ');
    lay.each(new_str, function(ii, item){
      if(!new RegExp('\\b'+ item + '\\b').test(str)){
        str = str + ' ' + item;
      }
    });
    return str.replace(/^\s|\s$/, '');
  };

  // 移除值
  Class.removeStr = function(str, new_str){
    str = str.replace(/\s+/, ' ');
    new_str = new_str.replace(/\s+/, ' ').split(' ');
    lay.each(new_str, function(ii, item){
      var exp = new RegExp('\\b'+ item + '\\b')
      if(exp.test(str)){
        str = str.replace(exp, '');
      }
    });
    return str.replace(/\s+/, ' ').replace(/^\s|\s$/, '');
  };

  // 查找子元素
  Class.fn.find = function(selector){
    var that = this;
    var elem = [];
    var isObject = typeof selector === 'object';

    this.each(function(i, item){
      var children = isObject && item.contains(selector)
        ? selector
      : item.querySelectorAll(selector || null);

      lay.each(children, function(index, child){
        elem.push(child);
      });
    });

    return lay(elem);
  };

  // 元素遍历
  Class.fn.each = function(fn){
    return lay.each.call(this, this, fn);
  };

  // 添加 className
  Class.fn.addClass = function(className, type){
    return this.each(function(index, item){
      item.className = Class[type ? 'removeStr' : 'addStr'](item.className, className)
    });
  };

  // 移除 className
  Class.fn.removeClass = function(className){
    return this.addClass(className, true);
  };

  // 是否包含 css 类
  Class.fn.hasClass = function(className){
    var has = false;
    this.each(function(index, item){
      if(new RegExp('\\b'+ className +'\\b').test(item.className)){
        has = true;
      }
    });
    return has;
  };

  // 添加或获取 css style
  Class.fn.css = function(key, value){
    var that = this;
    var parseValue = function(v){
      return isNaN(v) ? v : (v +'px');
    };
    return (typeof key === 'string' && value === undefined) ? function(){
      if(that.length > 0) return that[0].style[key];
    }() : that.each(function(index, item){
      typeof key === 'object' ? lay.each(key, function(thisKey, thisValue){
        item.style[thisKey] = parseValue(thisValue);
      }) : item.style[key] = parseValue(value);
    });
  };

  // 添加或获取宽度
  Class.fn.width = function(value){
    var that = this;
    return value === undefined ? function(){
      if(that.length > 0) return that[0].offsetWidth; // 此处还需做兼容
    }() : that.each(function(index, item){
      that.css('width', value);
    });
  };

  // 添加或获取高度
  Class.fn.height = function(value){
    var that = this;
    return value === undefined ? function(){
      if(that.length > 0) return that[0].offsetHeight; // 此处还需做兼容
    }() : that.each(function(index, item){
      that.css('height', value);
    });
  };

  // 添加或获取属性
  Class.fn.attr = function(key, value){
    var that = this;
    return value === undefined ? function(){
      if(that.length > 0) return that[0].getAttribute(key);
    }() : that.each(function(index, item){
      item.setAttribute(key, value);
    });
  };

  // 移除属性
  Class.fn.removeAttr = function(key){
    return this.each(function(index, item){
      item.removeAttribute(key);
    });
  };

  // 设置或获取 HTML 内容
  Class.fn.html = function(html){
    var that = this;
    return html === undefined ? function(){
      if(that.length > 0) return that[0].innerHTML;
    }() : this.each(function(index, item){
      item.innerHTML = html;
    });
  };

  // 设置或获取值
  Class.fn.val = function(value){
    var that = this;
    return value === undefined ? function(){
      if(that.length > 0) return that[0].value;
    }() : this.each(function(index, item){
        item.value = value;
    });
  };

  // 追加内容
  Class.fn.append = function(elem){
    return this.each(function(index, item){
      typeof elem === 'object'
        ? item.appendChild(elem)
      :  item.innerHTML = item.innerHTML + elem;
    });
  };

  // 移除内容
  Class.fn.remove = function(elem){
    return this.each(function(index, item){
      elem ? item.removeChild(elem) : item.parentNode.removeChild(item);
    });
  };

  // 事件绑定
  Class.fn.on = function(eventName, fn){
    return this.each(function(index, item){
      item.attachEvent ? item.attachEvent('on' + eventName, function(e){
        e.target = e.srcElement;
        fn.call(item, e);
      }) : item.addEventListener(eventName, fn, false);
    });
  };

  // 解除事件
  Class.fn.off = function(eventName, fn){
    return this.each(function(index, item){
      item.detachEvent
        ? item.detachEvent('on'+ eventName, fn)
      : item.removeEventListener(eventName, fn, false);
    });
  };

  // export
  window.lay = lay;

  // 输出为 layui 模块
  if(window.layui && layui.define){
    layui.define(function(exports){
      exports(MOD_NAME, lay);
    });
  }

}(window, window.document); // gulp build: lay-footer
/**
 * laytpl 轻量模板引擎
 */

layui.define(function(exports){
  "use strict";

  // 默认属性
  var config = {
    open: '{{', // 标签符前缀
    close: '}}' // 标签符后缀
  };

  // 模板工具
  var tool = {
    escape: function(html){
      var exp = /[<"'>]|&(?=#[a-zA-Z0-9]+)/g;
      if(html === undefined || html === null) return '';

      html += '';
      if(!exp.test(html)) return html;

      return html.replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
      .replace(/</g, '&lt;').replace(/>/g, '&gt;')
      .replace(/'/g, '&#39;').replace(/"/g, '&quot;');
    }
  };

  // 内部方法
  var inner = {
    exp: function(str){
      return new RegExp(str, 'g');
    },
    // 错误提示
    error: function(e, source){
      var error = 'Laytpl Error: ';
      typeof console === 'object' && console.error(error + e + '\n'+ (source || ''));
      return error + e;
    }
  };

  // constructor
  var Class = function(template, options){
    var that = this;
    that.config = that.config || {};
    that.template = template;

    // 简单属性合并
    var extend = function(obj){
      for(var i in obj){
        that.config[i] = obj[i];
      }
    };

    extend(config);
    extend(options);
  };

  // 标签正则
  Class.prototype.tagExp = function(type, _, __){
    var options = this.config;
    var types = [
      '#([\\s\\S])+?',   // js 语句
      '([^{#}])*?' // 普通字段
    ][type || 0];

    return inner.exp((_||'') + options.open + types + options.close + (__||''));
  };

  // 模版解析
  Class.prototype.parse = function(template, data){
    var that = this;
    var options = that.config;
    var source = template;
    var jss = inner.exp('^'+ options.open +'#', '');
    var jsse = inner.exp(options.close +'$', '');

    // 模板必须为 string 类型
    if(typeof template !== 'string') return template;

    // 正则解析
    template = template.replace(/\s+|\r|\t|\n/g, ' ')
    .replace(inner.exp(options.open +'#'), options.open +'# ')
    .replace(inner.exp(options.close +'}'), '} '+ options.close).replace(/\\/g, '\\\\')

    // 不匹配指定区域的内容
    .replace(inner.exp(options.open + '!(.+?)!' + options.close), function(str){
      str = str.replace(inner.exp('^'+ options.open + '!'), '')
      .replace(inner.exp('!'+ options.close), '')
      .replace(inner.exp(options.open + '|' + options.close), function(tag){
        return tag.replace(/(.)/g, '\\$1')
      });
      return str
    })

    // 匹配 JS 语法
    .replace(/(?="|')/g, '\\').replace(that.tagExp(), function(str){
      str = str.replace(jss, '').replace(jsse, '');
      return '";' + str.replace(/\\(.)/g, '$1') + ';view+="';
    })

    // 匹配普通输出语句
    .replace(that.tagExp(1), function(str){
      var start = '"+laytpl.escape(';
      if(str.replace(/\s/g, '') === options.open + options.close){
        return '';
      }
      str = str.replace(inner.exp(options.open + '|' + options.close), '');
      if(/^=/.test(str)){
        str = str.replace(/^=/, '');
      } else if(/^-/.test(str)){
        str = str.replace(/^-/, '');
        start = '"+(';
      }
      return start + str.replace(/\\(.)/g, '$1') + ')+"';
    });

    template = '"use strict";var view = "' + template + '";return view;';

    try {
      /**
       * 请注意: 开发者在使用模板语法时，需确保模板中的 JS 语句不来自于页面用户输入。
       * 即模板中的 JS 语句必须在页面开发者自身的可控范围内，否则请避免使用该模板解析。
       */
      that.cache = template = new Function('d, laytpl', template);
      return template(data, tool);
    } catch(e) {
      delete that.cache;
      return inner.error(e, source);
    }
  };

  // 数据渲染
  Class.prototype.render = function(data, callback){
    data = data || {};

    var that = this;
    var result = that.cache ? that.cache(data, tool) : that.parse(that.template, data);

    // 返回渲染结果
    typeof callback === 'function' && callback(result);
    return result;
  };

  // 创建实例
  var laytpl = function(template, options){
    return new Class(template, options);
  };

  // 配置全局属性
  laytpl.config = function(options){
    options = options || {};
    for(var i in options){
      config[i] = options[i];
    }
  };

  laytpl.v = '2.0.0';

  // export
  exports('laytpl', laytpl);
});
/**
 * laypage 分页组件
 */

layui.define(function(exports){
  "use strict";
  
  var doc = document;
  var id = 'getElementById';
  var tag = 'getElementsByTagName';
  
  // 字符常量
  var MOD_NAME = 'laypage';
  var DISABLED = 'layui-disabled';
  
  // 构造器
  var Class = function(options){
    var that = this;
    that.config = options || {};
    that.config.index = ++laypage.index;
    that.render(true);
  };

  // 判断传入的容器类型
  Class.prototype.type = function(){
    var config = this.config;
    if(typeof config.elem === 'object'){
      return config.elem.length === undefined ? 2 : 3;
    }
  };

  // 分页视图
  Class.prototype.view = function(){
    var that = this;
    var config = that.config;

    // 连续页码个数
    var groups = config.groups = 'groups' in config 
      ? (Number(config.groups) || 0)
    : 5; 
    
    // 排版
    config.layout = typeof config.layout === 'object' 
      ? config.layout 
    : ['prev', 'page', 'next'];
    
    config.count = Number(config.count) || 0; // 数据总数
    config.curr = Number(config.curr) || 1; // 当前页

    // 每页条数的选择项
    config.limits = typeof config.limits === 'object'
      ? config.limits
    : [10, 20, 30, 40, 50];

     // 默认条数
    config.limit = Number(config.limit) || 10;
    
    // 总页数
    config.pages = Math.ceil(config.count/config.limit) || 1;
    
    // 当前页不能超过总页数
    if(config.curr > config.pages){
      config.curr = config.pages;
    } else if(config.curr < 1) { // 当前分页不能小于 1
      config.curr = 1;
    }
    
    // 连续分页个数不能低于 0 且不能大于总页数
    if(groups < 0){
      groups = 1;
    } else if (groups > config.pages){
      groups = config.pages;
    }
    
    config.prev = 'prev' in config ? config.prev : '上一页'; // 上一页文本
    config.next = 'next' in config ? config.next : '下一页'; // 下一页文本
    
    // 计算当前组
    var index = config.pages > groups 
      ? Math.ceil( (config.curr + (groups > 1 ? 1 : 0)) / (groups > 0 ? groups : 1) )
    : 1;
    
    // 视图片段
    var views = {
      // 上一页
      prev: function(){
        return config.prev 
          ? '<a class="layui-laypage-prev'+ (config.curr == 1 ? (' ' + DISABLED) : '') +'" data-page="'+ (config.curr - 1) +'">'+ config.prev +'</a>'
        : '';
      }(),
      
      // 页码
      page: function(){
        var pager = [];
        
        // 数据量为0时，不输出页码
        if(config.count < 1){
          return '';
        }
        
        // 首页
        if(index > 1 && config.first !== false && groups !== 0){
          pager.push('<a class="layui-laypage-first" data-page="1"  title="首页">'+ (config.first || 1) +'</a>');
        }

        // 计算当前页码组的起始页
        var halve = Math.floor((groups-1)/2) // 页码数等分
        var start = index > 1 ? config.curr - halve : 1;
        var end = index > 1 ? (function(){
          var max = config.curr + (groups - halve - 1);
          return max > config.pages ? config.pages : max;
        }()) : groups;
        
        // 防止最后一组出现“不规定”的连续页码数
        if(end - start < groups - 1){
          start = end - groups + 1;
        }

        // 输出左分割符
        if(config.first !== false && start > 2){
          pager.push('<span class="layui-laypage-spr">...</span>')
        }
        
        // 输出连续页码
        for(; start <= end; start++){
          if(start === config.curr){
            // 当前页
            pager.push('<span class="layui-laypage-curr"><em class="layui-laypage-em" '+ (/^#/.test(config.theme) ? 'style="background-color:'+ config.theme +';"' : '') +'></em><em>'+ start +'</em></span>');
          } else {
            pager.push('<a data-page="'+ start +'">'+ start +'</a>');
          }
        }
        
        // 输出输出右分隔符 & 末页
        if(config.pages > groups && config.pages > end && config.last !== false){
          if(end + 1 < config.pages){
            pager.push('<span class="layui-laypage-spr">...</span>');
          }
          if(groups !== 0){
            pager.push('<a class="layui-laypage-last" title="尾页"  data-page="'+ config.pages +'">'+ (config.last || config.pages) +'</a>');
          }
        }

        return pager.join('');
      }(),
      
      // 下一页
      next: function(){
        return config.next 
          ? '<a class="layui-laypage-next'+ (config.curr == config.pages ? (' ' + DISABLED) : '') +'" data-page="'+ (config.curr + 1) +'">'+ config.next +'</a>'
        : '';
      }(),
      
      // 数据总数
      count: function(){
        var countText = typeof config.countText === 'object' ? config.countText : ['共 ', ' 条'];
        return '<span class="layui-laypage-count">'+ countText[0] + config.count + countText[1] +'</span>'
      }(),
      
      // 每页条数
      limit: function(){
        var elemArr = ['<span class="layui-laypage-limits"><select lay-ignore>'];
        var template = function(item) {
          var def = item +' 条/页';
          return typeof config.limitTemplet === 'function'
            ? (config.limitTemplet(item) || def)
          : def;
        };

        // 条目选项列表
        layui.each(config.limits, function(index, item){
          elemArr.push(
            '<option value="'+ item +'"'+ (item === config.limit ? ' selected' : '') +'>'
              + template(item)
            + '</option>'
          );
        });

        return elemArr.join('') +'</select></span>';
      }(),
      
      // 刷新当前页
      refresh: [
        '<a data-page="'+ config.curr +'" class="layui-laypage-refresh">',
          '<i class="layui-icon layui-icon-refresh"></i>',
        '</a>'
      ].join(''),

      // 跳页区域
      skip: function(){
        var skipText = typeof config.skipText === 'object' ? config.skipText : [
          '到第',
          '页',
          '确定'
        ];
        return [
          '<span class="layui-laypage-skip">'+ skipText[0],
            '<input type="text" min="1" value="'+ config.curr +'" class="layui-input">',
            skipText[1]+ '<button type="button" class="layui-laypage-btn">'+ skipText[2] +'</button>',
          '</span>'
        ].join('');
      }()
    };

    return ['<div class="layui-box layui-laypage layui-laypage-'+ (config.theme ? (
      /^#/.test(config.theme) ? 'molv' : config.theme
    ) : 'default') +'" id="layui-laypage-'+ config.index +'">',
      function(){
        var plate = [];
        layui.each(config.layout, function(index, item){
          if(views[item]){
            plate.push(views[item])
          }
        });
        return plate.join('');
      }(),
    '</div>'].join('');
  };

  // 跳页的回调
  Class.prototype.jump = function(elem, isskip){
    if(!elem) return;

    var that = this;
    var config = that.config;
    var childs = elem.children;
    var btn = elem[tag]('button')[0];
    var input = elem[tag]('input')[0];
    var select = elem[tag]('select')[0];
    var skip = function(){
      var curr = Number(input.value.replace(/\s|\D/g, ''));
      if(curr){
        config.curr = curr;
        that.render();
      }
    };
    
    if(isskip) return skip();
    
    // 页码
    for(var i = 0, len = childs.length; i < len; i++){
      if(childs[i].nodeName.toLowerCase() === 'a'){
        laypage.on(childs[i], 'click', function(){
          var curr = Number(this.getAttribute('data-page'));
          if(curr < 1 || curr > config.pages) return;
          config.curr = curr;
          that.render();
        });
      }
    }
    
    // 条数
    if(select){
      laypage.on(select, 'change', function(){
        var value = this.value;
        if(config.curr*value > config.count){
          config.curr = Math.ceil(config.count/value);
        }
        config.limit = value;
        that.render();
      });
    }
    
    // 确定
    if(btn){
      laypage.on(btn, 'click', function(){
        skip();
      });
    }
  };
  
  // 输入页数字控制
  Class.prototype.skip = function(elem){
    if(!elem) return;

    var that = this;
    var input = elem[tag]('input')[0];

    if(!input) return;

    // 键盘事件
    laypage.on(input, 'keyup', function(e){
      var value = this.value;
      var keyCode = e.keyCode;

      if(/^(37|38|39|40)$/.test(keyCode)) return;

      if(/\D/.test(value)){
        this.value = value.replace(/\D/, '');
      }
      if(keyCode === 13){
        that.jump(elem, true)
      }
    });
  };

  // 渲染分页
  Class.prototype.render = function(load){
    var that = this;
    var config = that.config;
    var type = that.type();
    var view = that.view();
    
    if(type === 2){
      config.elem && (config.elem.innerHTML = view);
    } else if(type === 3){
      config.elem.html(view);
    } else {
      if(doc[id](config.elem)){
        doc[id](config.elem).innerHTML = view;
      }
    }

    config.jump && config.jump(config, load);
    
    var elem = doc[id]('layui-laypage-' + config.index);
    that.jump(elem);
    
    if(config.hash && !load){
      location.hash = '!'+ config.hash +'='+ config.curr;
    }
    
    that.skip(elem);
  };
  
  // 外部接口
  var laypage = {
    // 分页渲染
    render: function(options){
      var o = new Class(options);
      return o.index;
    },
    index: layui.laypage ? (layui.laypage.index + 10000) : 0,
    on: function(elem, even, fn){
      elem.attachEvent ? elem.attachEvent('on'+ even, function(e){ // for ie
        e.target = e.srcElement;
        fn.call(elem, e);
      }) : elem.addEventListener(even, fn, false);
      return this;
    }
  }

  exports(MOD_NAME, laypage);
});
/** laydate 日期与时间控件 | MIT Licensed */

;!function(window, document){ // gulp build: laydate-header
  "use strict";

  var isLayui = window.layui && layui.define, ready = {
    getPath: (window.lay && lay.getPath) ? lay.getPath : ''

    // 载入 CSS 依赖
    ,link: function(href, fn, cssname){

      // 未设置路径，则不主动加载 css
      if(!laydate.path) return;

      // 加载 css
      if(window.lay && lay.layui){
        lay.layui.link(laydate.path + href, fn, cssname);
      }
    }
  };

  // 识别预先可能定义的指定全局对象
  var GLOBAL = window.LAYUI_GLOBAL || {};

  // 模块名
  var MOD_NAME = 'laydate';
  var MOD_ID = 'layui-'+ MOD_NAME +'-id' // 已渲染过的索引标记名

  // 外部调用
  var laydate = {
    v: '5.5.0' // layDate 版本号
    ,config: {
      weekStart: 0, // 默认周日一周的开始
    } // 全局配置项
    ,index: (window.laydate && window.laydate.v) ? 100000 : 0
    ,path: GLOBAL.laydate_dir || ready.getPath

    // 设置全局项
    ,set: function(options){
      var that = this;
      that.config = lay.extend({}, that.config, options);
      return that;
    }

    // 主体 CSS 等待事件
    ,ready: function(callback){
      var cssname = 'laydate';
      var ver = ''
      var path = (isLayui ? 'modules/' : '') + 'laydate.css?v='+ laydate.v + ver;

      isLayui ? (
        layui['layui.all']
          ? (typeof callback === 'function' && callback())
        : layui.addcss(path, callback, cssname)
      ) : ready.link(path, callback, cssname);

      return this;
    }
  };

  // 操作当前实例
  var thisModule = function(){
    var that = this;
    var options = that.config;
    var id = options.id;

    thisModule.that[id] = that; // 记录当前实例对象

    return that.inst = {
      // 提示框
      hint: function(content){
        that.hint.call(that, content);
      },
      // 重载实例
      reload: function(options){
        that.reload.call(that, options);
      },
      config: that.config
    };
  };

  // 字符常量
  var ELEM = '.layui-laydate';
  var THIS = 'layui-this';
  var SHOW = 'layui-show';
  var HIDE = 'layui-hide';
  var DISABLED = 'laydate-disabled';
  var LIMIT_YEAR = [100, 200000];

  var ELEM_STATIC = 'layui-laydate-static';
  var ELEM_LIST = 'layui-laydate-list';
  var ELEM_SELECTED = 'laydate-selected';
  var ELEM_HINT = 'layui-laydate-hint';
  var ELEM_DAY_NOW = 'laydate-day-now';
  var ELEM_PREV = 'laydate-day-prev';
  var ELEM_NEXT = 'laydate-day-next';
  var ELEM_FOOTER = 'layui-laydate-footer';
  var ELEM_SHORTCUT = 'layui-laydate-shortcut';
  var ELEM_NOW = '.laydate-btns-now'
  var ELEM_CONFIRM = '.laydate-btns-confirm';
  var ELEM_TIME_TEXT = 'laydate-time-text';
  var ELEM_TIME_BTN = 'laydate-btns-time';
  var ELEM_PREVIEW = 'layui-laydate-preview';
  var ELEM_MAIN = 'layui-laydate-main';
  var ELEM_SHADE = 'layui-laydate-shade';

  // 组件构造器
  var Class = function(options){
    var that = this;
    that.index = ++laydate.index;
    that.config = lay.extend({}, that.config, laydate.config, options);

    // 若 elem 非唯一，则拆分为多个实例
    var elem = lay(options.elem || that.config.elem);
    if(elem.length > 1){
      lay.each(elem, function(){
        laydate.render(lay.extend({}, that.config, {
          elem: this
        }));
      });
      return that;
    }

    // 初始化属性
    options = lay.extend(that.config, lay.options(elem[0])); // 继承节点上的属性

    // 若重复执行 render，则视为 reload 处理
    if(elem[0] && elem.attr(MOD_ID)){
      var newThat = thisModule.getThis(elem.attr(MOD_ID));
      if(!newThat) return;
      return newThat.reload(options);
    }

    // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
    options.id = 'id' in options ? options.id : (
      elem.attr('id') || that.index
    );

    // 自增索引
    options.index = that.index;

    // 初始化
    laydate.ready(function(){
      that.init();
    });
  };

  // 日期格式字符
  var dateType = 'yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s';

  // 将日期格式字符转换为数组
  thisModule.formatArr = function(format){
    return (format || '').match(new RegExp(dateType + '|.', 'g')) || []
  };

  /*
    组件操作
  */

  // 是否闰年
  Class.isLeapYear = function(year){
    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  };

  // 默认配置
  Class.prototype.config = {
    type: 'date' //控件类型，支持：year/month/date/time/datetime
    ,range: false //是否开启范围选择，即双控件
    ,format: 'yyyy-MM-dd' //默认日期格式
    ,value: null //默认日期，支持传入new Date()，或者符合format参数设定的日期格式字符
    ,isInitValue: true //用于控制是否自动向元素填充初始值（需配合 value 参数使用）
    ,min: '1900-1-1' //有效最小日期，年月日必须用“-”分割，时分秒必须用“:”分割。注意：它并不是遵循 format 设定的格式。
    ,max: '2099-12-31' //有效最大日期，同上
    ,trigger: 'click' //呼出控件的事件
    ,show: false //是否直接显示，如果设置 true，则默认直接显示控件
    ,showBottom: true //是否显示底部栏
    ,isPreview: true //是否显示值预览
    ,btns: ['clear', 'now', 'confirm'] //右下角显示的按钮，会按照数组顺序排列
    ,lang: 'cn' //语言，只支持cn/en，即中文和英文
    ,theme: 'default' //主题
    ,position: null //控件定位方式定位, 默认absolute，支持：fixed/absolute/static
    ,calendar: false //是否开启公历重要节日，仅支持中文版
    ,mark: {} //日期备注，如重要事件或活动标记
    ,holidays: null // 标注法定节假日或补假上班
    ,zIndex: null //控件层叠顺序
    ,done: null //控件选择完毕后的回调，点击清空/现在/确定也均会触发
    ,change: null //日期时间改变后的回调
    ,autoConfirm: true //是否自动确认（日期|年份|月份选择器非range下是否自动确认）
    ,shade: 0
  };

  //多语言
  Class.prototype.lang = function(){
    var that = this
    ,options = that.config
    ,text = {
      cn: {
        weeks: ['日', '一', '二', '三', '四', '五', '六']
        ,time: ['时', '分', '秒']
        ,timeTips: '选择时间'
        ,startTime: '开始时间'
        ,endTime: '结束时间'
        ,dateTips: '返回日期'
        ,month: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
        ,tools: {
          confirm: '确定'
          ,clear: '清空'
          ,now: '现在'
        }
        ,timeout: '结束时间不能早于开始时间<br>请重新选择'
        ,invalidDate: '不在有效日期或时间范围内'
        ,formatError: ['日期格式不合法<br>必须遵循下述格式：<br>', '<br>已为你重置']
        ,preview: '当前选中的结果'
      }
      ,en: {
        weeks: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
        ,time: ['Hours', 'Minutes', 'Seconds']
        ,timeTips: 'Select Time'
        ,startTime: 'Start Time'
        ,endTime: 'End Time'
        ,dateTips: 'Select Date'
        ,month: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        ,tools: {
          confirm: 'Confirm'
          ,clear: 'Clear'
          ,now: 'Now'
        }
        ,timeout: 'End time cannot be less than start Time<br>Please re-select'
        ,invalidDate: 'Invalid date'
        ,formatError: ['The date format error<br>Must be followed：<br>', '<br>It has been reset']
        ,preview: 'The selected result'
      }
    };
    return text[options.lang] || text['cn'];
  };

  // 重载实例
  Class.prototype.reload = function(options){
    var that = this;
    that.config = lay.extend({}, that.config, options);
    that.init();
  };

  //初始准备
  Class.prototype.init = function(){
    var that = this
    ,options = that.config
    ,isStatic = options.position === 'static'
    ,format = {
      year: 'yyyy'
      ,month: 'yyyy-MM'
      ,date: 'yyyy-MM-dd'
      ,time: 'HH:mm:ss'
      ,datetime: 'yyyy-MM-dd HH:mm:ss'
    };

    options.elem = lay(options.elem);
    options.eventElem = lay(options.eventElem);

    if(!options.elem[0]) return;

    layui.type(options.theme) !== 'array' && (options.theme = [options.theme]);
    // 设置了全面版模式
    if (options.fullPanel) {
      if (options.type !== 'datetime' || options.range) {
        // 目前只支持datetime的全面版
        delete options.fullPanel;
      }
    }

    //日期范围分隔符
    that.rangeStr =  options.range ? (
      typeof options.range === 'string' ? options.range : '-'
    ) : '';

    //日期范围的日历面板是否联动
    that.rangeLinked = !!(options.range && options.rangeLinked && (options.type === 'date' || options.type === 'datetime'))

    //切换日历联动方式
    that.autoCalendarModel = function () {
      var state = that.rangeLinked;
      that.rangeLinked = (options.range && (options.type === 'date' || options.type === 'datetime'))
        && ((!that.startDate || !that.endDate) || (that.startDate && that.endDate && that.startDate.year === that.endDate.year && that.startDate.month === that.endDate.month));
      lay(that.elem)[that.rangeLinked ? 'addClass' : 'removeClass']('layui-laydate-linkage');
      return that.rangeLinked != state; // 返回发生了变化
    };

    //是否自动切换
    that.autoCalendarModel.auto = that.rangeLinked && options.rangeLinked === 'auto';

    //若 range 参数为数组，则表示为开始日期和结束日期的 input 对象
    if(layui.type(options.range) === 'array'){
      that.rangeElem = [
        lay(options.range[0]),
        lay(options.range[1])
      ];
    }

    //若 type 设置非法，则初始化为 date 类型
    if(!format[options.type]){
      window.console && console.error && console.error('laydate type error:\''+ options.type + '\' is not supported')
      options.type = 'date';
    }

    //根据不同 type，初始化默认 format
    if(options.format === format.date){
      options.format = format[options.type] || format.date;
    }

    //将日期格式转化成数组
    that.format = thisModule.formatArr(options.format);

    // 设置了一周的开始是周几，此处做一个控制
    if (options.weekStart) {
      if (!/^[0-6]$/.test(options.weekStart)) {
        var lang = that.lang();
        options.weekStart = lang.weeks.indexOf(options.weekStart);
        if (options.weekStart === -1) options.weekStart = 0;
      }
    }

    //生成正则表达式
    that.EXP_IF = '';
    that.EXP_SPLIT = '';
    lay.each(that.format, function(i, item){
      var EXP =  new RegExp(dateType).test(item)
        ? '\\d{'+ function(){
          if(new RegExp(dateType).test(that.format[i === 0 ? i + 1 : i - 1]||'')){
            if(/^yyyy|y$/.test(item)) return 4;
            return item.length;
          }
          if(/^yyyy$/.test(item)) return '1,4';
          if(/^y$/.test(item)) return '1,308';
          return '1,2';
        }() +'}'
      : '\\' + item;
      that.EXP_IF = that.EXP_IF + EXP;
      that.EXP_SPLIT = that.EXP_SPLIT + '(' + EXP + ')';
    });
    //验证日期格式正则
    that.EXP_IF_ONE = new RegExp('^'+ that.EXP_IF +'$'); //验证单个日期格式
    that.EXP_IF = new RegExp('^'+ (
      options.range ?
        that.EXP_IF + '\\s\\'+ that.rangeStr + '\\s' + that.EXP_IF
      : that.EXP_IF
    ) +'$');
    that.EXP_SPLIT = new RegExp('^'+ that.EXP_SPLIT +'$', '');

    //如果不是 input|textarea 元素，则默认采用 click 事件
    if(!that.isInput(options.elem[0])){
      if(options.trigger === 'focus'){
        options.trigger = 'click';
      }
    }

    // 设置唯一 KEY
    options.elem.attr('lay-key', that.index);
    options.eventElem.attr('lay-key', that.index);
    options.elem.attr(MOD_ID, options.id); // 渲染过的标记

    //记录重要日期
    options.mark = lay.extend({}, (options.calendar && options.lang === 'cn') ? {
      '0-1-1': '元旦'
      ,'0-2-14': '情人'
      ,'0-3-8': '妇女'
      ,'0-3-12': '植树'
      ,'0-4-1': '愚人'
      ,'0-5-1': '劳动'
      ,'0-5-4': '青年'
      ,'0-6-1': '儿童'
      ,'0-9-10': '教师'
      ,'0-10-1': '国庆'
      ,'0-12-25': '圣诞'
    } : {}, options.mark);

    //获取限制内日期
    lay.each(['min', 'max'], function(i, item){
      var ymd = [];
      var hms = [];
      if(typeof options[item] === 'number'){ //如果为数字
        var day = options[item]
        ,tDate = new Date()
        ,time = that.newDate({ //今天的最大毫秒数
          year: tDate.getFullYear()
          ,month: tDate.getMonth()
          ,date: tDate.getDate()
          ,hours: i ? 23 : 0
          ,minutes: i ? 59 : 0
          ,seconds: i ? 59 : 0
        }).getTime()
        ,STAMP = 86400000 //代表一天的毫秒数
        ,thisDate = new Date(
          day ? (
            day < STAMP ? time + day*STAMP : day //如果数字小于一天的毫秒数，则数字为天数，否则为毫秒数
          ) : time
        );
        ymd = [thisDate.getFullYear(), thisDate.getMonth() + 1, thisDate.getDate()];
        hms = [thisDate.getHours(), thisDate.getMinutes(), thisDate.getSeconds()];
      } else if(typeof options[item] === 'string') {
        ymd = (options[item].match(/\d+-\d+-\d+/) || [''])[0].split('-');
        hms = (options[item].match(/\d+:\d+:\d+/) || [''])[0].split(':');
      } else if(typeof options[item] === 'object'){
        return options[item];
      }
      options[item] = {
        year: ymd[0] | 0 || new Date().getFullYear()
        ,month: ymd[1] ? (ymd[1] | 0) - 1 : new Date().getMonth()
        ,date: ymd[2] | 0 || new Date().getDate()
        ,hours: hms[0] | 0
        ,minutes: hms[1] | 0
        ,seconds: hms[2] | 0
      };
    });

    that.elemID = 'layui-laydate'+ options.elem.attr('lay-key');

    if(options.show || isStatic) that.render();
    isStatic || that.events();

    //默认赋值
    if(options.value && options.isInitValue){
      if(layui.type(options.value) === 'date'){
        that.setValue(that.parse(0, that.systemDate(options.value)));
      } else {
        that.setValue(options.value);
      }
    }
  };

  //控件主体渲染
  Class.prototype.render = function(){
    var that = this
    ,options = that.config
    ,lang = that.lang()
    ,isStatic = options.position === 'static'

    //主面板
    ,elem = that.elem = lay.elem('div', {
      id: that.elemID
      ,"class": [
        'layui-laydate'
        ,options.range ? ' layui-laydate-range' : ''
        ,that.rangeLinked ? ' layui-laydate-linkage' : ''
        ,isStatic ? (' '+ ELEM_STATIC) : ''
        ,options.fullPanel ? ' laydate-theme-fullpanel' : '' // 全面版
        // ,options.theme && options.theme !== 'default' && !/^#/.test(options.theme) ? (' laydate-theme-' + options.theme) : ''
        ,(function () {
          var themeStr = '';
          lay.each(options.theme, function (index, theme) {
            if (theme !== 'default' && !/^#/.test(theme)) {
              themeStr += ' laydate-theme-' + theme;
            }
          })
          return themeStr;
        })()
      ].join('')
    })

    //主区域
    ,elemMain = that.elemMain = []
    ,elemHeader = that.elemHeader = []
    ,elemCont = that.elemCont = []
    ,elemTable = that.table = []

    //底部区域
    ,divFooter = that.footer = lay.elem('div', {
      "class": ELEM_FOOTER
    })

    //快捷栏
    ,divShortcut = that.shortcut = lay.elem('ul', {
      "class": ELEM_SHORTCUT
    });

    if(options.zIndex) elem.style.zIndex = options.zIndex;

    //单双日历区域
    lay.each(new Array(2), function(i){
      if(!options.range && i > 0){
        return true;
      }

      //头部区域
      var divHeader = lay.elem('div', {
        "class": 'layui-laydate-header'
      })

      //左右切换
      ,headerChild = [function(){ //上一年
        var elem = lay.elem('i', {
          "class": 'layui-icon laydate-icon laydate-prev-y'
        });
        elem.innerHTML = '&#xe65a;';
        return elem;
      }(), function(){ //上一月
        var elem = lay.elem('i', {
          "class": 'layui-icon laydate-icon laydate-prev-m'
        });
        elem.innerHTML = '&#xe603;';
        return elem;
      }(), function(){ //年月选择
        var elem = lay.elem('div', {
          "class": 'laydate-set-ym'
        }), spanY = lay.elem('span'), spanM = lay.elem('span');
        elem.appendChild(spanY);
        elem.appendChild(spanM);
        return elem;
      }(), function(){ //下一月
        var elem = lay.elem('i', {
          "class": 'layui-icon laydate-icon laydate-next-m'
        });
        elem.innerHTML = '&#xe602;';
        return elem;
      }(), function(){ //下一年
        var elem = lay.elem('i', {
          "class": 'layui-icon laydate-icon laydate-next-y'
        });
        elem.innerHTML = '&#xe65b;';
        return elem;
      }()]

      //日历内容区域
      ,divContent = lay.elem('div', {
        "class": 'layui-laydate-content'
      })
      ,table = lay.elem('table')
      ,thead = lay.elem('thead'), theadTr = lay.elem('tr');

      //生成年月选择
      lay.each(headerChild, function(i, item){
        divHeader.appendChild(item);
      });

       //生成表格
      thead.appendChild(theadTr);
      lay.each(new Array(6), function(i){ //表体
        var tr = table.insertRow(0);
        lay.each(new Array(7), function(j){
          if(i === 0){
            var th = lay.elem('th');
            th.innerHTML = lang.weeks[(j + options.weekStart) % 7];
            theadTr.appendChild(th);
          }
          tr.insertCell(j);
        });
      });
      table.insertBefore(thead, table.children[0]); //表头
      divContent.appendChild(table);

      elemMain[i] = lay.elem('div', {
        "class": ELEM_MAIN + ' laydate-main-list-'+ i
      });

      elemMain[i].appendChild(divHeader);
      elemMain[i].appendChild(divContent);

      elemHeader.push(headerChild);
      elemCont.push(divContent);
      elemTable.push(table);
    });

    //生成底部栏
    lay(divFooter).html(function(){
      var html = [], btns = [];
      if(options.type === 'datetime'){
        html.push('<span lay-type="datetime" class="'+ ELEM_TIME_BTN +'">'+ lang.timeTips +'</span>');
      }
      if(!(!options.range && options.type === 'datetime') || options.fullPanel){
        html.push('<span class="'+ ELEM_PREVIEW +'" title="'+ lang.preview +'"></span>')
      }

      lay.each(options.btns, function(i, item){
        var title = lang.tools[item] || 'btn';
        if(options.range && item === 'now') return;
        if(isStatic && item === 'clear') title = options.lang === 'cn' ? '重置' : 'Reset';
        btns.push('<span lay-type="'+ item +'" class="laydate-btns-'+ item +'">'+ title +'</span>');
      });
      html.push('<div class="laydate-footer-btns">'+ btns.join('') +'</div>');
      return html.join('');
    }());

    // 生成快捷键栏
    if (options.shortcuts) {
      elem.appendChild(divShortcut);
      lay(divShortcut).html(function () {
        var shortcutBtns = [];
        lay.each(options.shortcuts, function (i, item) {
          shortcutBtns.push('<li data-index="' + i + '">'+item.text+'</li>')
        })
        return shortcutBtns.join('');
      }()).find('li').on('click', function (event) {
        var btnSetting = options.shortcuts[this.dataset['index']] || {};
        var value = (typeof btnSetting.value === 'function'
          ? btnSetting.value()
          : btnSetting.value) || [];
        if (!layui.isArray(value)) {
          value = [value];
        }
        var type = options.type;
        lay.each(value, function (i, item) {
          var dateTime = [options.dateTime, that.endDate][i];
          if (type === 'time' && layui.type(item) !== 'date') {
            if (that.EXP_IF.test(item)) {
              item = (item.match(that.EXP_SPLIT) || []).slice(1);
              lay.extend(dateTime, {hours: item[0] | 0, minutes: item[2] | 0, seconds: item[4] | 0})
            }
          } else {
            lay.extend(dateTime, that.systemDate(layui.type(item) === 'date' ? item : new Date(item)))
          }

          if (type === 'time' || type === 'datetime') {
            that[['startTime', 'endTime'][i]] = {
              hours: dateTime.hours,
              minutes: dateTime.minutes,
              seconds: dateTime.seconds,
            }
          }
          if (i === 0) { // 第一个值作为startDate
            that.startDate = lay.extend({}, dateTime);
          } else {
            that.endState = true;
          }
          if (type === 'year' || type === 'month' || type === 'time') {
            that.listYM[i] = [dateTime.year, dateTime.month + 1];
          } else if (i) {
            that.autoCalendarModel.auto && that.autoCalendarModel();
          }
        });
        that.checkDate('limit').calendar(null, null, 'init');

        var timeBtn = lay(that.footer).find('.'+ ELEM_TIME_BTN).removeClass(DISABLED);
        timeBtn && timeBtn.attr('lay-type') === 'date' && timeBtn[0].click();
        that.done(null, 'change');

        lay(this).addClass(THIS);

        // 自动确认
        if(options.position !== 'static'){
          that.setValue(that.parse()).done().remove();
        }
        /*
        if (options.position !== 'static' && !options.range && options.autoConfirm) {
          if (type === 'date') {
            that.choose(lay(elem).find('td.layui-this'))
          } else if (type === 'year' || type === 'month') {
            if(lay(elemMain[0]).find('.' + ELEM_MAIN + ' li.' + THIS + ':not(.laydate-disabled)')[0]) {
              that.setValue(that.parse()).done().remove();
            }
          }
        }
        */
      })
    }

    //插入到主区域
    lay.each(elemMain, function(i, main){
      elem.appendChild(main);
    });
    options.showBottom && elem.appendChild(divFooter);

    // 生成自定义主题
    var style = lay.elem('style');
    var styleText = [];
    var colorTheme;
    var isPrimaryColor = true;
    lay.each(options.theme, function (index, theme) {
      // 主色
      if(isPrimaryColor && /^#/.test(theme)){
        colorTheme = true;
        isPrimaryColor = false;
        styleText.push([
          '#{{id}} .layui-laydate-header{background-color:{{theme}};}',
          '#{{id}} li.layui-this,#{{id}} td.layui-this>div{background-color:{{theme}} !important;}',
          options.theme.indexOf('circle') !== -1 ? '' : '#{{id}} .layui-this{background-color:{{theme}} !important;}',
          '#{{id}} .laydate-day-now{color:{{theme}} !important;}',
          '#{{id}} .laydate-day-now:after{border-color:{{theme}} !important;}'
        ].join('').replace(/{{id}}/g, that.elemID).replace(/{{theme}}/g, theme));
        return;
      }
      // 第二个自定义颜色作为辅色
      if(!isPrimaryColor && /^#/.test(theme)){
        styleText.push([
          '#{{id}} .laydate-selected>div{background-color:{{theme}} !important;}',
          '#{{id}} .laydate-selected:hover>div{background-color:{{theme}} !important;}'
        ].join('').replace(/{{id}}/g, that.elemID).replace(/{{theme}}/g, theme));
      }
    });
    //快捷栏样式
    if (options.shortcuts && options.range) {
      styleText.push('#{{id}}.layui-laydate-range{width: 628px;}'.replace(/{{id}}/g, that.elemID))
    }
    if (styleText.length) {
      styleText = styleText.join('');
      if('styleSheet' in style){
        style.setAttribute('type', 'text/css');
        style.styleSheet.cssText = styleText;
      } else {
        style.innerHTML = styleText;
      }

      colorTheme && lay(elem).addClass('laydate-theme-molv');
      elem.appendChild(style);
    }

    //移除上一个控件
    that.remove(Class.thisElemDate);

    //记录当前执行的实例索引
    laydate.thisId = options.id;

    //如果是静态定位，则插入到指定的容器中，否则，插入到body
    isStatic ? options.elem.append(elem) : (
      document.body.appendChild(elem)
      ,that.position() //定位
    );

    var shade = options.shade ? ('<div class="'+ ELEM_SHADE +'" style="'+ ('z-index:'+ (parseInt(layui.getStyle(elem, 'z-index'))-1) +'; background-color: ' + (options.shade[1] || '#000') + '; opacity: ' + (options.shade[0] || options.shade)) +'"></div>') : '';
    elem.insertAdjacentHTML('beforebegin', shade);

    that.checkDate().calendar(null, 0, 'init'); //初始校验
    that.changeEvent(); //日期切换

    Class.thisElemDate = that.elemID;

    that.renderAdditional()
    typeof options.ready === 'function' && options.ready(lay.extend({}, options.dateTime, {
      month: options.dateTime.month + 1
    }));

    that.preview();
  };

  //控件移除
  Class.prototype.remove = function(prev){
    var that = this
    ,options = that.config
    ,elem = lay('#'+ (prev || that.elemID));
    if(!elem[0]) return that;

    if(!elem.hasClass(ELEM_STATIC)){
      that.checkDate(function(){
        elem.remove();
        //delete options.dateTime;
        delete that.startDate;
        delete that.endDate;
        delete that.endState;
        delete that.startTime;
        delete that.endTime;
        delete laydate.thisId;
        typeof options.close === 'function' && options.close(that);
      });
    }
    lay('.' + ELEM_SHADE).remove();
    return that;
  };

  //定位算法
  Class.prototype.position = function(){
    var that = this
    ,options = that.config;
    lay.position(options.elem[0], that.elem, {
      position: options.position
    });
    return that;
  };

  // 提示
  Class.prototype.hint = function(opts){
    var that = this;
    var options = that.config;
    var div = lay.elem('div', {
      "class": ELEM_HINT
    });

    if(!that.elem) return;

    // 兼容旧版参数
    if(typeof opts === 'object'){
      opts = opts || {};
    } else {
      opts = {
        content: opts
      }
    }

    div.innerHTML = opts.content || '';
    lay(that.elem).find('.'+ ELEM_HINT).remove();
    that.elem.appendChild(div);

    clearTimeout(that.hinTimer);
    that.hinTimer = setTimeout(function(){
      lay(that.elem).find('.'+ ELEM_HINT).remove();
    }, 'ms' in opts ? opts.ms : 3000);
  };

  //获取递增/减后的年月
  Class.prototype.getAsYM = function(Y, M, type){
    type ? M-- : M++;
    if(M < 0){
      M = 11;
      Y--;
    }
    if(M > 11){
      M = 0;
      Y++;
    }
    return [Y, M];
  };

  //系统日期
  Class.prototype.systemDate = function(newDate){
    var thisDate = newDate || new Date();
    return {
      year: thisDate.getFullYear() //年
      ,month: thisDate.getMonth() //月
      ,date: thisDate.getDate() //日
      ,hours: newDate ? newDate.getHours() : 0 //时
      ,minutes: newDate ? newDate.getMinutes() : 0 //分
      ,seconds: newDate ? newDate.getSeconds() : 0 //秒
    }
  };

  //日期校验
  Class.prototype.checkDate = function(fn){
    var that = this
    ,thisDate = new Date()
    ,options = that.config
    ,lang = that.lang()
    ,dateTime = options.dateTime = options.dateTime || that.systemDate()
    ,thisMaxDate, error

    ,elem = options.elem[0]
    ,valType = that.isInput(elem) ? 'val' : 'html'
    ,value = function(){
      //如果传入了开始和结束日期的 input 对象，则将其拼接为日期范围字符
      if(that.rangeElem){
        var vals = [that.rangeElem[0].val(), that.rangeElem[1].val()];

        if(vals[0] && vals[1]){
          return vals.join(' ' + that.rangeStr + ' ');
        }
      }
      return that.isInput(elem)
        ? elem.value
      : (options.position === 'static' ? '' : lay(elem).attr('lay-date'));
    }()

    //校验日期有效数字
    ,checkValid = function(dateTime){
      if (!dateTime) {
        return;
      }
      if(dateTime.year > LIMIT_YEAR[1]) dateTime.year = LIMIT_YEAR[1], error = true; //不能超过20万年
      if(dateTime.month > 11) dateTime.month = 11, error = true;
      if(dateTime.seconds > 59) dateTime.seconds = 0, dateTime.minutes++, error = true;
      if(dateTime.minutes > 59) dateTime.minutes = 0, dateTime.hours++, error = true;
      if(dateTime.hours > 23) dateTime.hours = 0, error = true;

      //计算当前月的最后一天
      thisMaxDate = laydate.getEndDate(dateTime.month + 1, dateTime.year);
      if(dateTime.date > thisMaxDate) dateTime.date = thisMaxDate, error = true;
    }

    //获得初始化日期值
    ,initDate = function(dateTime, value, index){
      var startEnd = ['startTime', 'endTime'];
      value = (value.match(that.EXP_SPLIT) || []).slice(1);
      index = index || 0;

      if(options.range){
        that[startEnd[index]] = that[startEnd[index]] || {};
      }
      lay.each(that.format, function(i, item){
        var thisv = parseFloat(value[i]);
        if(value[i].length < item.length) error = true;
        if(/yyyy|y/.test(item)){ //年
          if(thisv < LIMIT_YEAR[0]) thisv = LIMIT_YEAR[0], error = true; //年不能低于100年
          dateTime.year = thisv;
        } else if(/MM|M/.test(item)){ //月
          if(thisv < 1) thisv = 1, error = true;
          dateTime.month = thisv - 1;
        } else if(/dd|d/.test(item)){ //日
          if(thisv < 1) thisv = 1, error = true;
          dateTime.date = thisv;
        } else if(/HH|H/.test(item)){ //时
          if (thisv < 0) thisv = 0, error = true;
          if (thisv > 23) thisv = 23, error = true;
          dateTime.hours = thisv;
          options.range && (that[startEnd[index]].hours = thisv);
        } else if(/mm|m/.test(item)){ //分
          if (thisv < 0) thisv = 0, error = true;
          if (thisv > 59) thisv = 59, error = true;
          dateTime.minutes = thisv;
          options.range && (that[startEnd[index]].minutes = thisv);
        } else if(/ss|s/.test(item)){ //秒
          if (thisv < 0) thisv = 0, error = true;
          if (thisv > 59) thisv = 59, error = true;
          dateTime.seconds = thisv;
          options.range && (that[startEnd[index]].seconds = thisv);
        }
      });
      checkValid(dateTime);
    };

    if(fn === 'limit') {
      if (options.range) {
        checkValid(that.rangeLinked ? that.startDate : dateTime); // 校验开始时间
        that.endDate && checkValid(that.endDate); // 校验结束时间
      } else {
        checkValid(dateTime);
      }
      return that;
    }

    value = value || options.value;
    if(typeof value === 'string'){
      value = value.replace(/\s+/g, ' ').replace(/^\s|\s$/g, '');
    }

    //如果开启范围，则计算结束日期
    var getEndDate = function(){
      if(options.range){
        that.endDate = that.endDate || lay.extend({}, options.dateTime, function(){
          var obj = {}
          ,dateTime = options.dateTime
          ,EYM = that.getAsYM(dateTime.year, dateTime.month);

          //初始右侧面板的年月
          if(options.type === 'year'){
            obj.year = dateTime.year + 1;
          } else if(options.type !== 'time'){
            obj.year = EYM[0];
            obj.month = EYM[1];
          }

          //初始右侧面板的时间
          if(options.type === 'datetime' || options.type === 'time'){
            obj.hours = 23;
            obj.minutes = obj.seconds = 59;
          }

          return obj;
        }());
      }
    };
    getEndDate();

    if(typeof value === 'string' && value){
      if(that.EXP_IF.test(value)){ //校验日期格式
        if(options.range){
          value = value.split(' '+ that.rangeStr +' ');
          lay.each([options.dateTime, that.endDate], function(i, item){
            initDate(item, value[i], i);
          });
        } else {
          initDate(dateTime, value);
        }
      } else {
        //格式不合法
        that.hint(lang.formatError[0] + (
          options.range ? (options.format + ' '+ that.rangeStr +' ' + options.format) : options.format
        ) + lang.formatError[1]);
        error = true;
      }
    } else if(value && layui.type(value) === 'date'){ //若值为日期对象
      options.dateTime = that.systemDate(value);
    } else {
      //重置开始日期
      options.dateTime = that.systemDate();
      delete that.startTime;

      //重置结束日期
      delete that.endDate; //删除原有的结束日期
      getEndDate(); //并重新获得新的结束日期
      delete that.endTime;
    }

    //从日期范围表单中获取初始值
    (function(){
      if(that.rangeElem){
        var vals = [that.rangeElem[0].val(), that.rangeElem[1].val()]
        ,arrDate = [options.dateTime, that.endDate];
        lay.each(vals, function(_i, _v){
          if(that.EXP_IF_ONE.test(_v)){ //校验日期格式
            initDate(arrDate[_i], _v, _i);
          }
        });
      }
    })();

    // 校验日期有效数字
    checkValid(dateTime);
    if(options.range) checkValid(that.endDate);

    // 如果初始值格式错误，则纠正初始值
    if(error && value){
      that.setValue(
        options.range ? (that.endDate ? that.parse() : '') : that.parse()
      );
    }

    //如果当前日期不在设定的最大小日期区间，则自动纠正在可选区域
    //校验主面板是否在可选日期区间
    var minMaxError;
    if(that.getDateTime(dateTime) > that.getDateTime(options.max)){ //若超出最大日期
      dateTime = options.dateTime = lay.extend({}, options.max);
      minMaxError = true;
    } else if(that.getDateTime(dateTime) < that.getDateTime(options.min)){ //若少于最小日期
      dateTime = options.dateTime = lay.extend({}, options.min);
      minMaxError = true;
    }

    //校验右侧面板是否在可选日期区间
    if(options.range){
      if(that.getDateTime(that.endDate) < that.getDateTime(options.min) || that.getDateTime(that.endDate) > that.getDateTime(options.max)){
        that.endDate = lay.extend({}, options.max);
        minMaxError = true;
      }
      // 有时间范围的情况下初始化startTime和endTime
      that.startTime = {
        hours: options.dateTime.hours,
        minutes: options.dateTime.minutes,
        seconds: options.dateTime.seconds,
      }
      that.endTime = {
        hours: that.endDate.hours,
        minutes: that.endDate.minutes,
        seconds: that.endDate.seconds,
      }
      // 如果是年月范围，将对应的日期统一成当月的1日进行比较，避免出现同一个月但是开始日期大于结束日期的情况
      if (options.type === 'month') {
        options.dateTime.date = 1;
        that.endDate.date = 1;
      }
    }

    // 初始值不在最大最小范围内
    if(minMaxError && value){
      that.setValue(that.parse());
      that.hint('value ' + lang.invalidDate + lang.formatError[1]);
    }

    // 初始赋值 startDate,endState
    that.startDate = that.startDate || value && lay.extend({}, options.dateTime); // 有默认值才初始化startDate
    that.autoCalendarModel.auto && that.autoCalendarModel();
    that.endState = !options.range || !that.rangeLinked || !!(that.startDate && that.endDate); // 初始化选中范围状态

    fn && fn();
    return that;
  };

  // 公历重要日期与自定义备注
  Class.prototype.mark = function(td, YMD){
    var that = this
    ,mark, options = that.config;
    lay.each(options.mark, function(key, title){
      var keys = key.split('-');
      if((keys[0] == YMD[0] || keys[0] == 0) //每年的每月
      && (keys[1] == YMD[1] || keys[1] == 0) //每月的每日
      && keys[2] == YMD[2]){ //特定日
        mark = title || YMD[2];
      }
    });
    mark && td.find('div').html('<span class="laydate-day-mark">'+ mark +'</span>');

    return that;
  };

  // 标注法定节假日或补假上班
  Class.prototype.holidays = function(td, YMD) {
    var that = this;
    var options = that.config;
    var type = ['', 'work'];

    if(layui.type(options.holidays) !== 'array') return that;

    lay.each(options.holidays, function(idx, item) {
      lay.each(item, function(i, dayStr) {
        if(dayStr === td.attr('lay-ymd')){
          td.find('div').html('<span class="laydate-day-holidays"' + (
            type[idx] ? ('type="'+ type[idx] +'"') : ''
          ) + '>' + YMD[2] + '</span>');
        }
      });
    });

    return that;
  };

  // 无效日期范围的标记
  Class.prototype.limit = function(opts){
    opts = opts || {};

    var that = this;
    var options = that.config;
    var timestamp = {}
    var dateTime = opts.index > (opts.time ? 0 : 41) ? that.endDate : options.dateTime;
    var isOut;

    lay.each({
      now: lay.extend({}, dateTime, opts.date || {})
      ,min: options.min
      ,max: options.max
    }, function(key, item){
      timestamp[key] = that.newDate(lay.extend({
        year: item.year
        ,month: opts.type === 'year' ? 0 : item.month // 年份的时候只比较年
        ,date: (opts.type === 'year' || opts.type === 'month') ? 1 : item.date // 年月只比较年月不与最大最小比日期
      }, function(){
        var hms = {};
        lay.each(opts.time, function(i, keys){
          hms[keys] = item[keys];
        });
        return hms;
      }())).getTime();  //time：是否比较时分秒
    });

    isOut = timestamp.now < timestamp.min || timestamp.now > timestamp.max;
    opts.elem && opts.elem[isOut ? 'addClass' : 'removeClass'](DISABLED);

    return isOut;
  };

  //当前日期对象
  Class.prototype.thisDateTime = function(index){
    var that = this
    ,options = that.config;
    return index ? that.endDate: options.dateTime;
  };

  //日历表
  Class.prototype.calendar = function(value, index, type){
    index = index ? 1 : 0;
    var that = this
    ,options = that.config
    ,dateTime = value || that.thisDateTime(index)
    ,thisDate = new Date(), startWeek, prevMaxDate, thisMaxDate
    ,lang = that.lang()

    ,isAlone = options.type !== 'date' && options.type !== 'datetime'
    ,tds = lay(that.table[index]).find('td')
    ,elemYM = lay(that.elemHeader[index][2]).find('span');

    if(dateTime.year < LIMIT_YEAR[0]) dateTime.year = LIMIT_YEAR[0], that.hint(lang.invalidDate);
    if(dateTime.year > LIMIT_YEAR[1]) dateTime.year = LIMIT_YEAR[1], that.hint(lang.invalidDate);

    //记录初始值
    if(!that.firstDate){
      that.firstDate = lay.extend({}, dateTime);
    }

    //计算当前月第一天的星期
    thisDate.setFullYear(dateTime.year, dateTime.month, 1);
    startWeek = (thisDate.getDay() + (7 - options.weekStart)) % 7;

    prevMaxDate = laydate.getEndDate(dateTime.month || 12, dateTime.year); //计算上个月的最后一天
    thisMaxDate = laydate.getEndDate(dateTime.month + 1, dateTime.year); //计算当前月的最后一天

    //赋值日
    lay.each(tds, function(index_, item){
      var YMD = [dateTime.year, dateTime.month], st;
      item = lay(item);
      item.removeAttr("class");
      if(index_ < startWeek){
        st = prevMaxDate - startWeek + index_;
        item.addClass('laydate-day-prev');
        YMD = that.getAsYM(dateTime.year, dateTime.month, 'sub');
      } else if(index_ >= startWeek && index_ < thisMaxDate + startWeek){
        st = index_ - startWeek;
        if (!that.rangeLinked) {
          st + 1 === dateTime.date && item.addClass(THIS);
        }
      } else {
        st = index_ - thisMaxDate - startWeek;
        item.addClass('laydate-day-next');
        YMD = that.getAsYM(dateTime.year, dateTime.month);
      }
      YMD[1]++;
      YMD[2] = st + 1;
      item.attr('lay-ymd', YMD.join('-')).html('<div>' + YMD[2] + '</div>');
      that.mark(item, YMD).holidays(item, YMD).limit({
        elem: item,
        date: {
          year: YMD[0],
          month: YMD[1] - 1,
          date: YMD[2]
        },
        index: index_
      });
    });

    //同步头部年月
    lay(elemYM[0]).attr('lay-ym', dateTime.year + '-' + (dateTime.month + 1));
    lay(elemYM[1]).attr('lay-ym', dateTime.year + '-' + (dateTime.month + 1));

    if(options.lang === 'cn'){
      lay(elemYM[0]).attr('lay-type', 'year').html(dateTime.year + ' 年')
      lay(elemYM[1]).attr('lay-type', 'month').html((dateTime.month + 1) + ' 月');
    } else {
      lay(elemYM[0]).attr('lay-type', 'month').html(lang.month[dateTime.month]);
      lay(elemYM[1]).attr('lay-type', 'year').html(dateTime.year);
    }

    //初始默认选择器
    if(isAlone){ //年、月等独立选择器
      if(options.range){
        if(value || type !== 'init'){ // 判断是否需要显示年月时间列表
          that.listYM = [
            [(that.startDate || options.dateTime).year, (that.startDate || options.dateTime).month + 1]
            ,[that.endDate.year, that.endDate.month + 1]
          ];
          that.list(options.type, 0).list(options.type, 1);

          //同步按钮可点状态
          options.type === 'time' ? that.setBtnStatus('时间'
            ,lay.extend({}, that.systemDate(), that.startTime)
            ,lay.extend({}, that.systemDate(), that.endTime)
          ) : that.setBtnStatus(true);
        }
      } else {
        that.listYM = [[dateTime.year, dateTime.month + 1]];
        that.list(options.type, 0);
      }
    }

    //初始赋值双日历
    if(options.range && type === 'init'){
      //执行渲染第二个日历
      if (that.rangeLinked) {
        var EYM = that.getAsYM(dateTime.year, dateTime.month, index ? 'sub' : null)
        that.calendar(lay.extend({}, dateTime, {
          year: EYM[0]
          ,month: EYM[1]
        }), 1 - index); // 渲染另外一个
      } else {
        that.calendar(null, 1 - index);
      }
    }

    // 通过检测当前有效日期，来设定底部按钮状态
    if(!options.range){
      var timeParams = ['hours', 'minutes', 'seconds'];

      // 现在按钮
      that.limit({
        elem: lay(that.footer).find(ELEM_NOW),
        date: that.systemDate(/^(datetime|time)$/.test(options.type) ? new Date() : null),
        index: 0,
        time: timeParams
      });
      // 确认按钮
      that.limit({
        elem: lay(that.footer).find(ELEM_CONFIRM),
        index: 0,
        time: timeParams
      });
    }

    //同步按钮可点状态
    that.setBtnStatus();

    // 重置快捷栏选中状态
    lay(that.shortcut).find('li.' + THIS).removeClass(THIS);

    //标记选择范围
    if(options.range && !isAlone && type !== 'init') that.stampRange();

    return that;
  };

  //生成年月时分秒列表
  Class.prototype.list = function(type, index){
    var that = this
    ,options = that.config
    ,dateTime = that.rangeLinked ? options.dateTime : [options.dateTime, that.endDate][index]
    ,lang = that.lang()
    ,isAlone = options.range && options.type !== 'date' && options.type !== 'datetime' //独立范围选择器

    ,ul = lay.elem('ul', {
      "class": ELEM_LIST + ' ' + ({
        year: 'laydate-year-list'
        ,month: 'laydate-month-list'
        ,time: 'laydate-time-list'
      })[type]
    })
    ,elemHeader = that.elemHeader[index]
    ,elemYM = lay(elemHeader[2]).find('span')
    ,elemCont = that.elemCont[index || 0]
    ,haveList = lay(elemCont).find('.'+ ELEM_LIST)[0]
    ,isCN = options.lang === 'cn'
    ,text = isCN ? '年' : ''

    ,listYM = that.listYM[index] || {}
    ,hms = ['hours', 'minutes', 'seconds']
    ,startEnd = ['startTime', 'endTime'][index];

    if(listYM[0] < 1) listYM[0] = 1;

    //生成年列表
    if(type === 'year'){
      var yearNum, startY = yearNum = listYM[0] - 7;
      if(startY < 1) startY = yearNum = 1;
      lay.each(new Array(15), function(i){
        var li = lay.elem('li', {
          'lay-ym': yearNum
        })
        ,ymd = {
          year: yearNum
          ,month: 0
          ,date: 1
        };

        yearNum == listYM[0] && lay(li).addClass(THIS);
        li.innerHTML = yearNum + text;
        ul.appendChild(li);

        /*
        if(yearNum < that.firstDate.year){
          ymd.month = options.min.month;
          ymd.date = options.min.date;
        } else if(yearNum >= that.firstDate.year){
          ymd.month = options.max.month;
          ymd.date = options.max.date;
        }
        */

        that.limit({
          elem: lay(li),
          date: ymd,
          index: index,
          type: type
        });
        yearNum++;
      });

      lay(elemYM[isCN ? 0 : 1]).attr('lay-ym', (yearNum - 8) + '-' + listYM[1])
      .html((startY + text) + ' - ' + (yearNum - 1 + text));
    }

    //生成月列表
    else if(type === 'month'){
      lay.each(new Array(12), function(i){
        var li = lay.elem('li', {
          'lay-ym': i
        })
        ,ymd = {
          year: listYM[0]
          ,month: i
          ,date: 1
        };

        i + 1 == listYM[1] && lay(li).addClass(THIS);
        li.innerHTML = lang.month[i] + (isCN ? '月' : '');
        ul.appendChild(li);

        /*
        if(listYM[0] < that.firstDate.year){
          ymd.date = options.min.date;
        } else if(listYM[0] >= that.firstDate.year){
          ymd.date = options.max.date;
        }
        */

        that.limit({
          elem: lay(li),
          date: ymd,
          index: index,
          type: type
        });
      });

      lay(elemYM[isCN ? 0 : 1]).attr('lay-ym', listYM[0] + '-' + listYM[1])
      .html(listYM[0] + text);
    }

    //生成时间列表
    else if(type === 'time'){
      //检测时分秒状态是否在有效日期时间范围内
      var setTimeStatus = function(){
        lay(ul).find('ol').each(function(i, ol){
          lay(ol).find('li').each(function(ii, li){
            that.limit({
              elem: lay(li),
              date: [{
                hours: ii
              }, {
                hours: that[startEnd].hours
                ,minutes: ii
              }, {
                hours: that[startEnd].hours
                ,minutes: that[startEnd].minutes
                ,seconds: ii
              }][i],
              index: index,
              time: [
                ['hours'],
                ['hours', 'minutes'],
                ['hours', 'minutes', 'seconds']
              ][i]
            });
          });
        });
        if(!options.range){
          that.limit({
            elem: lay(that.footer).find(ELEM_CONFIRM),
            date: that[startEnd],
            index: 0,
            time: ['hours', 'minutes', 'seconds']
          });
        }
      };

      var setTimeListVisibility = function(){
        var showHour = options.format.indexOf('H') !== -1;
        var showMinute = options.format.indexOf('m') !== -1;
        var showSecond = options.format.indexOf('s') !== -1;
        var liElem = ul.children;
        var hideCount = 0;

        lay.each([showHour, showMinute, showSecond], function(i, isShow){
          if(!isShow){
            liElem[i].className += ' layui-hide';
            hideCount++;
          }
        })
        ul.className += (' laydate-time-list-hide-' + hideCount);
      }

      //初始化时间对象
      if(options.range){
        if(!that[startEnd]){
          that[startEnd] = startEnd === 'startTime' ? dateTime : that.endDate;
        }
      } else {
        that[startEnd] = dateTime;
      }

      //生成时分秒
      lay.each([24, 60, 60], function(i, item){
        var li = lay.elem('li'), childUL = ['<p>'+ lang.time[i] +'</p><ol>'];
        lay.each(new Array(item), function(ii){
          childUL.push('<li'+ (that[startEnd][hms[i]] === ii ? ' class="'+ THIS +'"' : '') +'>'+ lay.digit(ii, 2) +'</li>');
        });
        li.innerHTML = childUL.join('') + '</ol>';
        ul.appendChild(li);
      });
      setTimeStatus();
      setTimeListVisibility();
    }

    //插入容器
    if(haveList) elemCont.removeChild(haveList);
    elemCont.appendChild(ul);

    //年月面板 - 选择事件
    if(type === 'year' || type === 'month'){
      //显示切换箭头
      lay(that.elemMain[index]).addClass('laydate-ym-show');

      //选中
      lay(ul).find('li').on('click', function(){
        var ym = lay(this).attr('lay-ym') | 0;
        if(lay(this).hasClass(DISABLED)) return;
        if (that.rangeLinked) {
          lay.extend(dateTime, {
            year: type === 'year' ? ym : listYM[0]
            ,month: type === 'year' ? listYM[1] - 1 : ym
          });
        } else {
          dateTime[type] = ym;
        }

        //当为年选择器或者年月选择器
        var isYearOrMonth = options.type === 'year' || options.type === 'month';
        if(isYearOrMonth){
          lay(ul).find('.'+ THIS).removeClass(THIS);
          lay(this).addClass(THIS);

          //如果为年月选择器，点击了年列表，则切换到月选择器
          if(options.type === 'month' && type === 'year'){
            that.listYM[index][0] = ym;
            isAlone && ((index ? that.endDate : dateTime).year = ym);
            that.list('month', index);
          }
        } else {
          that.checkDate('limit').calendar(dateTime, index, 'init'); // 重新渲染一下两个面板
          that.closeList();
        }

        that.setBtnStatus(); //同步按钮可点状态

        //若为月选择器，只有当选择月份时才自动关闭；
        //若为年选择器，选择年份即自动关闭
        //且在范围未开启时
        if(!options.range && options.autoConfirm){
          if((options.type === 'month' && type === 'month') || (options.type === 'year' && type === 'year')){
            that.setValue(that.parse()).done().remove();
          }
        }

        (that.autoCalendarModel.auto && !that.rangeLinked) ? that.choose(lay(elemCont).find('td.layui-this'), index) : (that.endState && that.done(null, 'change'));
        lay(that.footer).find('.'+ ELEM_TIME_BTN).removeClass(DISABLED);
      });
    } else { //时间选择面板 - 选择事件
      var span = lay.elem('span', {
        "class": ELEM_TIME_TEXT
      })

      //滚动条定位
      ,scroll = function(){
        lay(ul).find('ol').each(function(i){
          var ol = this
          ,li = lay(ol).find('li')
          ol.scrollTop = 30*(that[startEnd][hms[i]] - 2);
          if(ol.scrollTop <= 0){
            li.each(function(ii, item){
              if(!lay(this).hasClass(DISABLED)){
                ol.scrollTop = 30*(ii - 2);
                return true;
              }
            });
          }
        });
      }
      ,haveSpan = lay(elemHeader[2]).find('.'+ ELEM_TIME_TEXT);

      scroll();
      span.innerHTML = options.range ? [lang.startTime,lang.endTime][index] : lang.timeTips;
      lay(that.elemMain[index]).addClass('laydate-time-show');

      if(haveSpan[0]) haveSpan.remove();
      elemHeader[2].appendChild(span);

      var olElem = lay(ul).find('ol');
      olElem.each(function(i){
        var ol = this;
        //选择时分秒
        lay(ol).find('li').on('click', function(){
          var value = this.innerHTML | 0;
          if(lay(this).hasClass(DISABLED)) return;

          if(options.range){
            that[startEnd][hms[i]]  = value;
          } else {
            dateTime[hms[i]] = value;
          }
          lay(ol).find('.'+ THIS).removeClass(THIS);
          lay(this).addClass(THIS);

          setTimeStatus();
          scroll();
          (that.endDate || options.type === 'time' || (options.type === 'datetime' && options.fullPanel)) && that.done(null, 'change');

          //同步按钮可点状态
          that.setBtnStatus();
        });
      });

      if(layui.device().mobile){
        olElem.css({
          overflowY: 'auto',
          touchAction: 'pan-y'
        })
      }
    }

    return that;
  };

  //记录列表切换后的年月
  Class.prototype.listYM = [];

  //关闭列表
  Class.prototype.closeList = function(){
    var that = this
    ,options = that.config;

    lay.each(that.elemCont, function(index, item){
      lay(this).find('.'+ ELEM_LIST).remove();
      lay(that.elemMain[index]).removeClass('laydate-ym-show laydate-time-show');
    });
    lay(that.elem).find('.'+ ELEM_TIME_TEXT).remove();
  };

  //检测结束日期是否超出开始日期
  Class.prototype.setBtnStatus = function(tips, start, end){
    var that = this
    ,options = that.config
    ,lang = that.lang()
    ,isOut, elemBtn = lay(that.footer).find(ELEM_CONFIRM);
    if(options.range && options.type !== 'time'){
      start = start || (that.rangeLinked ? that.startDate : options.dateTime);
      end = end || that.endDate;
      isOut = !that.endState || that.newDate(start).getTime() > that.newDate(end).getTime();

      //如果不在有效日期内，直接禁用按钮，否则比较开始和结束日期
      (that.limit({
        date: start
      }) || that.limit({
        date: end
      }))
        ? elemBtn.addClass(DISABLED)
      : elemBtn[isOut ? 'addClass' : 'removeClass'](DISABLED);

      //是否异常提示
      if(tips && isOut) that.hint(
        typeof tips === 'string' ? lang.timeout.replace(/日期/g, tips) : lang.timeout
      );
    }
  };

  // 转义为规定格式的日期字符
  Class.prototype.parse = function(state, date) {
    var that = this;
    var options = that.config;
    var startDate = (that.rangeLinked ? that.startDate : options.dateTime)
    var dateTime = date || (
      state == 'end' ? lay.extend({}, that.endDate, that.endTime) : (
        options.range
          ? lay.extend({}, startDate || options.dateTime, that.startTime)
        : options.dateTime
      )
    );
    var format = laydate.parse(dateTime, that.format, 1);

    // 返回日期范围字符
    if (options.range && state === undefined) {
      return format + ' '+ that.rangeStr +' ' + that.parse('end');
    }

    return format;
  };

  //创建指定日期时间对象
  Class.prototype.newDate = function(dateTime){
    dateTime = dateTime || {};
    return new Date(
      dateTime.year || 1
      ,dateTime.month || 0
      ,dateTime.date || 1
      ,dateTime.hours || 0
      ,dateTime.minutes || 0
      ,dateTime.seconds || 0
    );
  };

  // 获得指定日期时间对象的毫秒数
  Class.prototype.getDateTime = function(obj){
    return this.newDate(obj).getTime();
  }

  //赋值
  Class.prototype.setValue = function(value){
    var that = this
    ,options = that.config
    ,elem = options.elem[0];

    //静态展现则不作默认赋值
    if(options.position === 'static') return that;

    value = value || '';

    //绑定的元素是否为 input
    if(that.isInput(elem)){
      lay(elem).val(value);
    } else {
      //如果 range 传入了开始和结束的 input 对象，则分别对其赋值
      var rangeElem = that.rangeElem;
      if(rangeElem){
        if(layui.type(value) !== 'array'){
          value = value.split(' '+ that.rangeStr +' ');
        }
        rangeElem[0].val(value[0] || '');
        rangeElem[1].val(value[1] || '');
      } else {
        if(lay(elem).find('*').length === 0){
          lay(elem).html(value);
        }
        lay(elem).attr('lay-date', value);
      }
    }

    return that;
  };

  //预览
  Class.prototype.preview = function(){
    var that = this
    ,options = that.config;

    if(!options.isPreview) return;

    var elemPreview =  lay(that.elem).find('.'+ ELEM_PREVIEW)
    ,value = options.range ? ((that.rangeLinked ? that.endState : that.endDate) ? that.parse() : '') : that.parse();

    // 显示预览
    elemPreview.html(value);

    // 预览颜色渐变
    var oldValue = elemPreview.html();
    oldValue && (elemPreview.css({
      'color': '#16b777'
    }),
    setTimeout(function(){
      elemPreview.css({
        'color': '#777'
      });
    }, 300));
  };

  // 附加的渲染处理，在 ready 和 change 的时候调用
  Class.prototype.renderAdditional = function(){
    var that = this;
    var options = that.config;

    // 处理全面板
    if (options.fullPanel) {
      that.list('time', 0);
    }
  };

  // 标记范围内的日期
  Class.prototype.stampRange = function(){
    var that = this
      ,options = that.config
      ,startTime = that.rangeLinked ? that.startDate : options.dateTime, endTime
      ,tds = lay(that.elem).find('td');

    if(options.range && !that.endState) lay(that.footer).find(ELEM_CONFIRM).addClass(DISABLED);
    // if(!that.endState) return;

    startTime = startTime && that.newDate({
      year: startTime.year
      ,month: startTime.month
      ,date: startTime.date
    }).getTime();

    endTime = that.endState && that.endDate && that.newDate({
      year: that.endDate.year
      ,month: that.endDate.month
      ,date: that.endDate.date
    }).getTime();

    // if(startTime > endTime) return that.hint(TIPS_OUT);

    lay.each(tds, function(i, item){
      var ymd = lay(item).attr('lay-ymd').split('-');
      var thisTime = that.newDate({
        year: ymd[0]
        ,month: ymd[1] - 1
        ,date: ymd[2]
      }).getTime();

      // 标记当天
      if(options.rangeLinked && !that.startDate){
        if(thisTime === that.newDate(that.systemDate()).getTime()){
          lay(item).addClass(
            lay(item).hasClass(ELEM_PREV) || lay(item).hasClass(ELEM_NEXT)
              ? ''
            : ELEM_DAY_NOW
          );
        }
      }

      /*
       * 标注区间
       */

      lay(item).removeClass(ELEM_SELECTED + ' ' + THIS);

      if(thisTime === startTime || thisTime === endTime){
        (that.rangeLinked || (!that.rangeLinked && (i < 42 ? thisTime === startTime : thisTime === endTime))) &&
        lay(item).addClass(
          lay(item).hasClass(ELEM_PREV) || lay(item).hasClass(ELEM_NEXT)
            ? ELEM_SELECTED
            : THIS
        );
      }
      if(thisTime > startTime && thisTime < endTime){
        lay(item).addClass(ELEM_SELECTED);
      }
    });
  };

  // 执行 done/change 回调
  Class.prototype.done = function(param, type){
    var that = this;
    var options = that.config;
    var start = lay.extend({},
      lay.extend(that.rangeLinked ? that.startDate : options.dateTime, that.startTime)
    );
    var end = lay.extend({}, lay.extend(that.endDate, that.endTime));

    lay.each([start, end], function(i, item){
      if(!('month' in item)) return;
      lay.extend(item, {
        month: item.month + 1
      });
    });

    that.preview();

    param = param || [that.parse(), start, end];
    type === 'change' && that.renderAdditional();
    typeof options[type || 'done'] === 'function' && options[type || 'done'].apply(options, param);

    return that;
  };

  //选择日期
  Class.prototype.choose = function(td, index){
    if(td.hasClass(DISABLED)) return;

    var that = this
    ,options = that.config
    ,panelIndex = index; // 记录点击的是哪一个面板的

    if (that.rangeLinked) {
      if (that.endState || !that.startDate) {
        // 重新选择或者第一次选择
        index = 0;
        that.endState = false;
      } else {
        index = 1;
        that.endState = true;
      }
    }

    var dateTime = that.thisDateTime(index)

    ,tds = lay(that.elem).find('td')
    ,YMD = td.attr('lay-ymd').split('-');

    YMD = {
      year: YMD[0] | 0
      ,month: (YMD[1] | 0) - 1
      ,date: YMD[2] | 0
    };

    lay.extend(dateTime, YMD); //同步 dateTime

    //范围选择
    if(options.range){
      //补充时分秒
      lay.each(['startTime', 'endTime'], function(i, item){
        that[item] = that[item] || {
          hours: i ? 23: 0
          ,minutes: i ? 59: 0
          ,seconds: i ? 59: 0
        };
        if (index === i) {
          // 判断选择之后的是否在范围内，超出则需要调整时分秒
          if (that.getDateTime(lay.extend({}, dateTime, that[item])) < that.getDateTime(options.min)) {
            that[item] = {
              hours: options.min.hours
              ,minutes: options.min.minutes
              ,seconds: options.min.seconds
            };
            lay.extend(dateTime, that[item]);
          } else if (that.getDateTime(lay.extend({}, dateTime, that[item])) > that.getDateTime(options.max)) {
            that[item] = {
              hours: options.max.hours
              ,minutes: options.max.minutes
              ,seconds: options.max.seconds
            };
            lay.extend(dateTime, that[item]);
          }
        }
      });
      if (!index) {
        that.startDate = lay.extend({}, dateTime); // 同步startDate
      }
      // 校验另外一个日期是否在有效的范围内
      if (that.endState && !that.limit({date: that.thisDateTime(1 - index)})) {
        // 根据选择之后判断是否需要切换模式
        var isChange;
        if (that.endState && that.autoCalendarModel.auto) {
          isChange = that.autoCalendarModel();
        }
        // 判断是否反选
        var needSwapDate = (isChange || that.rangeLinked && that.endState) && that.newDate(that.startDate) > that.newDate(that.endDate);
        if (needSwapDate){
          var isSameDate = that.startDate.year === that.endDate.year && that.startDate.month === that.endDate.month && that.startDate.date === that.endDate.date;
          var startDate;
          // 如果是同一天并且出现了反选证明是时分秒出现开始时间大于结束时间的现象
          if(isSameDate){
            startDate = that.startTime;
            that.startTime = that.endTime;
            that.endTime = startDate;
          }
          // 当出现反向选择时（即“后点击”的日期比“先点击”的日期小），重新提取区间
          startDate = that.startDate;
          that.startDate = lay.extend({}, that.endDate, that.startTime);
          options.dateTime = lay.extend({}, that.startDate);
          that.endDate = lay.extend({}, startDate, that.endTime);
        }
        isChange && (options.dateTime = lay.extend({}, that.startDate));
      }
      if (that.rangeLinked) {
        var dateTimeTemp = lay.extend({}, dateTime);
        if (panelIndex && !index && !isChange) { // 处理可能出现的联动面板中点击右面板但是判定为开始日期这个时候点击头部的切换上下月第一次没有反应的问题
          // 选择了右面板但是判断之后作为开始时间
          var YM = that.getAsYM(dateTime.year, dateTime.month, 'sub');
          lay.extend(options.dateTime, {
            year: YM[0]
            ,month: YM[1]
          });
        }
        that.calendar(dateTimeTemp, panelIndex, isChange ? 'init' : null);
      } else {
        that.calendar(null, index, isChange ? 'init' : null);
      }
      that.endState && that.done(null, 'change');
    } else if(options.position === 'static'){ //直接嵌套的选中
      that.calendar().done().done(null, 'change'); //同时执行 done 和 change 回调
    } else if(options.type === 'date'){
      options.autoConfirm ? that.setValue(that.parse()).done().remove() : that.calendar().done(null, 'change');
    } else if(options.type === 'datetime'){
      that.calendar().done(null, 'change');
    }
  };

  //底部按钮
  Class.prototype.tool = function(btn, type){
    var that = this
    ,options = that.config
    ,lang = that.lang()
    ,dateTime = options.dateTime
    ,isStatic = options.position === 'static'
    ,active = {
      //选择时间
      datetime: function(){
        if(lay(btn).hasClass(DISABLED)) return;
        that.list('time', 0);
        options.range && that.list('time', 1);
        lay(btn).attr('lay-type', 'date').html(that.lang().dateTips);
      }

      //选择日期
      ,date: function(){
        that.closeList();
        lay(btn).attr('lay-type', 'datetime').html(that.lang().timeTips);
      }

      //清空、重置
      ,clear: function(){
        isStatic && (
          lay.extend(dateTime, that.firstDate)
          ,that.calendar()
        )
        options.range && (
          delete options.dateTime
          ,delete that.endDate
          ,delete that.startTime
          ,delete that.endTime
        );
        that.setValue('');
        that.done(null, 'onClear').done(['', {}, {}]).remove();
      }

      // 现在
      ,now: function(){
        var thisDate = new Date();

        // 当前系统时间未在 min/max 范围内，则不可点击
        if(lay(btn).hasClass(DISABLED)){
          return that.hint(lang.tools.now +', '+ lang.invalidDate);
        }

        lay.extend(dateTime, that.systemDate(), {
          hours: thisDate.getHours()
          ,minutes: thisDate.getMinutes()
          ,seconds: thisDate.getSeconds()
        });

        that.setValue(that.parse());
        isStatic && that.calendar();
        that.done(null, 'onNow').done().remove();
      }

      //确定
      ,confirm: function(){
        if(options.range){
          if(lay(btn).hasClass(DISABLED)) return that.hint(
            options.type === 'time' ? lang.timeout.replace(/日期/g, '时间') : lang.timeout
          );
        } else {
          if(lay(btn).hasClass(DISABLED)) return that.hint(lang.invalidDate);
        }

        that.setValue(that.parse());
        that.done(null, 'onConfirm').done().remove();
      }
    };
    active[type] && active[type]();
  };

  //统一切换处理
  Class.prototype.change = function(index){
    var that = this
    ,options = that.config
    ,dateTime = that.thisDateTime(index)
    ,isAlone = options.range && (options.type === 'year' || options.type === 'month')

    ,elemCont = that.elemCont[index || 0]
    ,listYM = that.listYM[index]
    ,addSubYear = function(type){
      var isYear = lay(elemCont).find('.laydate-year-list')[0]
      ,isMonth = lay(elemCont).find('.laydate-month-list')[0];

      //切换年列表
      if(isYear){
        listYM[0] = type ? listYM[0] - 15 : listYM[0] + 15;
        that.list('year', index);
      }

      if(isMonth){ //切换月面板中的年
        type ? listYM[0]-- : listYM[0]++;
        that.list('month', index);
      }

      if(isYear || isMonth){
        lay.extend(dateTime, {
          year: listYM[0]
        });
        if(isAlone) dateTime.year = listYM[0];
        options.range || that.done(null, 'change');
        options.range || that.limit({
          elem: lay(that.footer).find(ELEM_CONFIRM),
          date: {
            year: listYM[0]
          }
        });
      }

      that.setBtnStatus();
      return isYear || isMonth;
    };

    return {
      prevYear: function(){
        if(addSubYear('sub')) return;
        if (that.rangeLinked) {
          options.dateTime.year--;
          that.checkDate('limit').calendar(null, null, 'init');
        } else {
          dateTime.year--;
          that.checkDate('limit').calendar(null, index);
          // 面板自动切换的模式下重新判定是否发生模式转换等细节处理
          that.autoCalendarModel.auto ? that.choose(lay(elemCont).find('td.layui-this'), index) : that.done(null, 'change');
        }
      }
      ,prevMonth: function(){
        if (that.rangeLinked) {
          dateTime = options.dateTime;
        }
        var YM = that.getAsYM(dateTime.year, dateTime.month, 'sub');
        lay.extend(dateTime, {
          year: YM[0]
          ,month: YM[1]
        });

        that.checkDate('limit').calendar(null, null, 'init');
        if (!that.rangeLinked) {
          that.autoCalendarModel.auto ? that.choose(lay(elemCont).find('td.layui-this'), index) : that.done(null, 'change');
        }
      }
      ,nextMonth: function(){
        if (that.rangeLinked) {
          dateTime = options.dateTime;
        }
        var YM = that.getAsYM(dateTime.year, dateTime.month);
        lay.extend(dateTime, {
          year: YM[0]
          ,month: YM[1]
        });

        that.checkDate('limit').calendar(null, null, 'init');
        if (!that.rangeLinked) {
          that.autoCalendarModel.auto ? that.choose(lay(elemCont).find('td.layui-this'), index) : that.done(null, 'change');
        }
      }
      ,nextYear: function(){
        if(addSubYear()) return;
        if (that.rangeLinked) {
          options.dateTime.year++;
          that.checkDate('limit').calendar(null, 0, 'init');
        } else {
          dateTime.year++;
          that.checkDate('limit').calendar(null, index);
          that.autoCalendarModel.auto ? that.choose(lay(elemCont).find('td.layui-this'), index) : that.done(null, 'change');
        }
      }
    };
  };

  //日期切换事件
  Class.prototype.changeEvent = function(){
    var that = this
    ,options = that.config;

    //日期选择事件
    lay(that.elem).on('click', function(e){
      lay.stope(e);
    }).on('mousedown', function(e){
      lay.stope(e);
    });

    //年月切换
    lay.each(that.elemHeader, function(i, header){
      //上一年
      lay(header[0]).on('click', function(e){
        that.change(i).prevYear();
      });

      //上一月
      lay(header[1]).on('click', function(e){
        that.change(i).prevMonth();
      });

      //选择年月
      lay(header[2]).find('span').on('click', function(e){
        var othis = lay(this)
        ,layYM = othis.attr('lay-ym')
        ,layType = othis.attr('lay-type');

        if(!layYM) return;

        layYM = layYM.split('-');

        that.listYM[i] = [layYM[0] | 0, layYM[1] | 0];
        that.list(layType, i);
        lay(that.footer).find('.'+ ELEM_TIME_BTN).addClass(DISABLED);
      });

      //下一月
      lay(header[3]).on('click', function(e){
        that.change(i).nextMonth();
      });

      //下一年
      lay(header[4]).on('click', function(e){
        that.change(i).nextYear();
      });
    });

    //点击日期
    lay.each(that.table, function(i, table){
      var tds = lay(table).find('td');
      tds.on('click', function(){
        that.choose(lay(this), i);
      });
    });

    //点击底部按钮
    lay(that.footer).find('span').on('click', function(){
      var type = lay(this).attr('lay-type');
      that.tool(this, type);
    });
  };

  //是否输入框
  Class.prototype.isInput = function(elem){
    return /input|textarea/.test(elem.tagName.toLocaleLowerCase()) || /INPUT|TEXTAREA/.test(elem.tagName);
  };

  //绑定的元素事件处理
  Class.prototype.events = function(){
    var that = this
    var options = that.config

    if(!options.elem[0] || options.elem[0].eventHandler) return;

    var showEvent = function(){
      // 已经打开的面板避免重新渲染
      if(laydate.thisId === options.id) return;
      that.render();
    };

    //绑定呼出控件事件
    options.elem.on(options.trigger, showEvent);
    options.elem[0].eventHandler = true;
    options.eventElem.on(options.trigger, showEvent);

    // 元素解绑
    that.unbind = function () {
      that.remove();
      options.elem.off(options.trigger, showEvent);
      options.elem.removeAttr('lay-key');
      options.elem.removeAttr(MOD_ID);
      options.elem[0].eventHandler = false;
      options.eventElem.off(options.trigger, showEvent);
      options.eventElem.removeAttr('lay-key');
      delete thisModule.that[options.id];
    };
  };

  //记录所有实例
  thisModule.that = {}; //记录所有实例对象

  //获取当前实例对象
  thisModule.getThis = function(id){
    var that = thisModule.that[id];
    if(!that && isLayui) layui.hint().error(id ? (MOD_NAME +' instance with ID \''+ id +'\' not found') : 'ID argument required');
    return that;
  };

  // 初始执行
  ready.run = function(lay){
    // 绑定关闭控件事件
    lay(document).on('mousedown', function(e){
      if(!laydate.thisId) return;
      var that = thisModule.getThis(laydate.thisId);
      if(!that) return;

      var options = that.config;

      if(
        e.target === options.elem[0] ||
        e.target === options.eventElem[0] ||
        e.target === lay(options.closeStop)[0] ||
        (options.elem[0] && options.elem[0].contains(e.target))
      ) return;

      that.remove();

    }).on('keydown', function(e){
      if(!laydate.thisId) return;
      var that = thisModule.getThis(laydate.thisId);
      if(!that) return;

      // 回车触发确认
      if(that.config.position === 'static') return;
      if(e.keyCode === 13){
        if(lay('#'+ that.elemID)[0] && that.elemID === Class.thisElemDate){
          e.preventDefault();
          lay(that.footer).find(ELEM_CONFIRM)[0].click();
        }
      }
    });

    //自适应定位
    lay(window).on('resize', function(){
      if(!laydate.thisId) return;
      var that = thisModule.getThis(laydate.thisId);
      if(!that) return;

      if(!that.elem || !lay(ELEM)[0]){
        return false;
      }

      that.position();
    });
  };

  // 渲染 - 核心接口
  laydate.render = function(options){
    var inst = new Class(options);
    return thisModule.call(inst);
  };

  // 重载
  laydate.reload = function (id, options) {
    var that = thisModule.getThis(id);
    if(!that) return;
    return that.reload(options);
  };

  // 获取对应 ID 的实例
  laydate.getInst = function (id) {
    var that = thisModule.getThis(id);
    if(that){
      return that.inst;
    }
  };

  // 面板提示
  laydate.hint = function(id, opts){
    var that = thisModule.getThis(id);
    if(!that) return;
    return that.hint(opts);
  };

  // 解绑实例
  laydate.unbind = function(id){
    var that = thisModule.getThis(id);
    if(!that) return;
    return that.unbind();
  };

  // 关闭日期面板
  laydate.close = function(id){
    var that = thisModule.getThis(id || laydate.thisId);
    if(!that) return;
    return that.remove();
  };

  // 将指定对象转化为日期值
  laydate.parse = function(dateTime, format, one){
    dateTime = dateTime || {};

    //如果 format 是字符型，则转换为数组格式
    if(typeof format === 'string'){
      format = thisModule.formatArr(format);
    }

    format = (format || []).concat();

    //转义为规定格式
    lay.each(format, function(i, item){
      if(/yyyy|y/.test(item)){ //年
        format[i] = lay.digit(dateTime.year, item.length);
      } else if(/MM|M/.test(item)){ //月
        format[i] = lay.digit(dateTime.month + (one || 0), item.length);
      } else if(/dd|d/.test(item)){ //日
        format[i] = lay.digit(dateTime.date, item.length);
      } else if(/HH|H/.test(item)){ //时
        format[i] = lay.digit(dateTime.hours, item.length);
      } else if(/mm|m/.test(item)){ //分
        format[i] = lay.digit(dateTime.minutes, item.length);
      } else if(/ss|s/.test(item)){ //秒
        format[i] = lay.digit(dateTime.seconds, item.length);
      }
    });

    return format.join('');
  };

  // 得到某月的最后一天
  laydate.getEndDate = function(month, year){
    var thisDate = new Date();
    //设置日期为下个月的第一天
    thisDate.setFullYear(
      year || thisDate.getFullYear()
      ,month || (thisDate.getMonth() + 1)
    ,1);
    //减去一天，得到当前月最后一天
    return new Date(thisDate.getTime() - 1000*60*60*24).getDate();
  };

  //加载方式
  isLayui ? (
    laydate.ready()
    ,layui.define('lay', function(exports){ //layui 加载
      laydate.path = layui.cache.dir;
      ready.run(lay);
      exports(MOD_NAME, laydate);
    })
  ) : (
    (typeof define === 'function' && define.amd) ? define(function(){ //requirejs 加载
      ready.run(lay);
      return laydate;
    }) : function(){ //普通 script 标签加载
      laydate.ready();
      ready.run(window.lay);
      window.laydate = laydate;
    }()
  );

}(window, window.document);

/*!
 * jQuery JavaScript Library v1.12.4
 * http://jquery.com/
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2016-05-20T17:17Z
 */

(function( global, factory ) {

	if ( typeof module === "object" && typeof module.exports === "object" ) {
		// For CommonJS and CommonJS-like environments where a proper `window`
		// is present, execute the factory and get jQuery.
		// For environments that do not have a `window` with a `document`
		// (such as Node.js), expose a factory as module.exports.
		// This accentuates the need for the creation of a real `window`.
		// e.g. var jQuery = require("jquery")(window);
		// See ticket #14549 for more info.
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}

// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Support: Firefox 18+
// Can't be in strict mode, several libs including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
//"use strict";
var deletedIds = [];

var document = window.document;

var slice = deletedIds.slice;

var concat = deletedIds.concat;

var push = deletedIds.push;

var indexOf = deletedIds.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var support = {};



var
	version = "1.12.4",

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {

		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	},

	// Support: Android<4.1, IE<9
	// Make sure we trim BOM and NBSP
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

	// Matches dashed string for camelizing
	rmsPrefix = /^-ms-/,
	rdashAlpha = /-([\da-z])/gi,

	// Used by jQuery.camelCase as callback to replace()
	fcamelCase = function( all, letter ) {
		return letter.toUpperCase();
	};

jQuery.fn = jQuery.prototype = {

	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// Start with an empty selector
	selector: "",

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num != null ?

			// Return just the one element from the set
			( num < 0 ? this[ num + this.length ] : this[ num ] ) :

			// Return all the elements in a clean array
			slice.call( this );
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;
		ret.context = this.context;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	each: function( callback ) {
		return jQuery.each( this, callback );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map( this, function( elem, i ) {
			return callback.call( elem, i, elem );
		} ) );
	},

	slice: function() {
		return this.pushStack( slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
	},

	end: function() {
		return this.prevObject || this.constructor();
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: deletedIds.sort,
	splice: deletedIds.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var src, copyIsArray, copy, name, options, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {

		// Only deal with non-null/undefined values
		if ( ( options = arguments[ i ] ) != null ) {

			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
					( copyIsArray = jQuery.isArray( copy ) ) ) ) {

					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray( src ) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject( src ) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend( {

	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return jQuery.type( obj ) === "function";
	},

	isArray: Array.isArray || function( obj ) {
		return jQuery.type( obj ) === "array";
	},

	isWindow: function( obj ) {
		/* jshint eqeqeq: false */
		return obj != null && obj == obj.window;
	},

	isNumeric: function( obj ) {

		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
		// subtraction forces infinities to NaN
		// adding 1 corrects loss of precision from parseFloat (#15100)
		var realStringObj = obj && obj.toString();
		return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
	},

	isEmptyObject: function( obj ) {
		var name;
		for ( name in obj ) {
			return false;
		}
		return true;
	},

	isPlainObject: function( obj ) {
		var key;

		// Must be an Object.
		// Because of IE, we also have to check the presence of the constructor property.
		// Make sure that DOM nodes and window objects don't pass through, as well
		if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
			return false;
		}

		try {

			// Not own constructor property must be Object
			if ( obj.constructor &&
				!hasOwn.call( obj, "constructor" ) &&
				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
				return false;
			}
		} catch ( e ) {

			// IE8,9 Will throw exceptions on certain host objects #9897
			return false;
		}

		// Support: IE<9
		// Handle iteration over inherited properties before own properties.
		if ( !support.ownFirst ) {
			for ( key in obj ) {
				return hasOwn.call( obj, key );
			}
		}

		// Own properties are enumerated firstly, so to speed up,
		// if last one is own, then all properties are own.
		for ( key in obj ) {}

		return key === undefined || hasOwn.call( obj, key );
	},

	type: function( obj ) {
		if ( obj == null ) {
			return obj + "";
		}
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[ toString.call( obj ) ] || "object" :
			typeof obj;
	},

	// Workarounds based on findings by Jim Driscoll
	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
	globalEval: function( data ) {
		if ( data && jQuery.trim( data ) ) {

			// We use execScript on Internet Explorer
			// We use an anonymous function so that context is window
			// rather than jQuery in Firefox
			( window.execScript || function( data ) {
				window[ "eval" ].call( window, data ); // jscs:ignore requireDotNotation
			} )( data );
		}
	},

	// Convert dashed to camelCase; used by the css and data modules
	// Microsoft forgot to hump their vendor prefix (#9572)
	camelCase: function( string ) {
		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	},

	each: function( obj, callback ) {
		var length, i = 0;

		if ( isArrayLike( obj ) ) {
			length = obj.length;
			for ( ; i < length; i++ ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		} else {
			for ( i in obj ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		}

		return obj;
	},

	// Support: Android<4.1, IE<9
	trim: function( text ) {
		return text == null ?
			"" :
			( text + "" ).replace( rtrim, "" );
	},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArrayLike( Object( arr ) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
					[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		var len;

		if ( arr ) {
			if ( indexOf ) {
				return indexOf.call( arr, elem, i );
			}

			len = arr.length;
			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

			for ( ; i < len; i++ ) {

				// Skip accessing in sparse arrays
				if ( i in arr && arr[ i ] === elem ) {
					return i;
				}
			}
		}

		return -1;
	},

	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		while ( j < len ) {
			first[ i++ ] = second[ j++ ];
		}

		// Support: IE<9
		// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
		if ( len !== len ) {
			while ( second[ j ] !== undefined ) {
				first[ i++ ] = second[ j++ ];
			}
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, invert ) {
		var callbackInverse,
			matches = [],
			i = 0,
			length = elems.length,
			callbackExpect = !invert;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			callbackInverse = !callback( elems[ i ], i );
			if ( callbackInverse !== callbackExpect ) {
				matches.push( elems[ i ] );
			}
		}

		return matches;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var length, value,
			i = 0,
			ret = [];

		// Go through the array, translating each of the items to their new values
		if ( isArrayLike( elems ) ) {
			length = elems.length;
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}
		}

		// Flatten any nested arrays
		return concat.apply( [], ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// Bind a function to a context, optionally partially applying any
	// arguments.
	proxy: function( fn, context ) {
		var args, proxy, tmp;

		if ( typeof context === "string" ) {
			tmp = fn[ context ];
			context = fn;
			fn = tmp;
		}

		// Quick check to determine if target is callable, in the spec
		// this throws a TypeError, but we will just return undefined.
		if ( !jQuery.isFunction( fn ) ) {
			return undefined;
		}

		// Simulated bind
		args = slice.call( arguments, 2 );
		proxy = function() {
			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
		};

		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || jQuery.guid++;

		return proxy;
	},

	now: function() {
		return +( new Date() );
	},

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
} );

// JSHint would error on this code due to the Symbol not being defined in ES5.
// Defining this global in .jshintrc would create a danger of using the global
// unguarded in another place, it seems safer to just disable JSHint for these
// three lines.
/* jshint ignore: start */
if ( typeof Symbol === "function" ) {
	jQuery.fn[ Symbol.iterator ] = deletedIds[ Symbol.iterator ];
}
/* jshint ignore: end */

// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

function isArrayLike( obj ) {

	// Support: iOS 8.2 (not reproducible in simulator)
	// `in` check used to prevent JIT error (gh-2145)
	// hasOwn isn't used here due to false negatives
	// regarding Nodelist length in IE
	var length = !!obj && "length" in obj && obj.length,
		type = jQuery.type( obj );

	if ( type === "function" || jQuery.isWindow( obj ) ) {
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
 * Sizzle CSS Selector Engine v2.2.1
 * http://sizzlejs.com/
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2015-10-17
 */
(function( window ) {

var i,
	support,
	Expr,
	getText,
	isXML,
	tokenize,
	compile,
	select,
	outermostContext,
	sortInput,
	hasDuplicate,

	// Local document vars
	setDocument,
	document,
	docElem,
	documentIsHTML,
	rbuggyQSA,
	rbuggyMatches,
	matches,
	contains,

	// Instance-specific data
	expando = "sizzle" + 1 * new Date(),
	preferredDoc = window.document,
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
		}
		return 0;
	},

	// General-purpose constants
	MAX_NEGATIVE = 1 << 31,

	// Instance methods
	hasOwn = ({}).hasOwnProperty,
	arr = [],
	pop = arr.pop,
	push_native = arr.push,
	push = arr.push,
	slice = arr.slice,
	// Use a stripped-down indexOf as it's faster than native
	// http://jsperf.com/thor-indexof-vs-for/5
	indexOf = function( list, elem ) {
		var i = 0,
			len = list.length;
		for ( ; i < len; i++ ) {
			if ( list[i] === elem ) {
				return i;
			}
		}
		return -1;
	},

	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",

	// Regular expressions

	// http://www.w3.org/TR/css3-selectors/#whitespace
	whitespace = "[\\x20\\t\\r\\n\\f]",

	// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
	identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +
		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
		"*\\]",

	pseudos = ":(" + identifier + ")(?:\\((" +
		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
		// 1. quoted (capture 3; capture 4 or capture 5)
		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
		// 2. simple (capture 6)
		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
		// 3. anything else (capture 2)
		".*" +
		")\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rwhitespace = new RegExp( whitespace + "+", "g" ),
	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),

	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),

	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		"ID": new RegExp( "^#(" + identifier + ")" ),
		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
		"ATTR": new RegExp( "^" + attributes ),
		"PSEUDO": new RegExp( "^" + pseudos ),
		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	rnative = /^[^{]+\{\s*\[native \w/,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rsibling = /[+~]/,
	rescape = /'|\\/g,

	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
	funescape = function( _, escaped, escapedWhitespace ) {
		var high = "0x" + escaped - 0x10000;
		// NaN means non-codepoint
		// Support: Firefox<24
		// Workaround erroneous numeric interpretation of +"0x"
		return high !== high || escapedWhitespace ?
			escaped :
			high < 0 ?
				// BMP codepoint
				String.fromCharCode( high + 0x10000 ) :
				// Supplemental Plane codepoint (surrogate pair)
				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	},

	// Used for iframes
	// See setDocument()
	// Removing the function wrapper causes a "Permission Denied"
	// error in IE
	unloadHandler = function() {
		setDocument();
	};

// Optimize for push.apply( _, NodeList )
try {
	push.apply(
		(arr = slice.call( preferredDoc.childNodes )),
		preferredDoc.childNodes
	);
	// Support: Android<4.0
	// Detect silently failing push.apply
	arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
	push = { apply: arr.length ?

		// Leverage slice if possible
		function( target, els ) {
			push_native.apply( target, slice.call(els) );
		} :

		// Support: IE<9
		// Otherwise append directly
		function( target, els ) {
			var j = target.length,
				i = 0;
			// Can't trust NodeList.length
			while ( (target[j++] = els[i++]) ) {}
			target.length = j - 1;
		}
	};
}

function Sizzle( selector, context, results, seed ) {
	var m, i, elem, nid, nidselect, match, groups, newSelector,
		newContext = context && context.ownerDocument,

		// nodeType defaults to 9, since context defaults to document
		nodeType = context ? context.nodeType : 9;

	results = results || [];

	// Return early from calls with invalid selector or context
	if ( typeof selector !== "string" || !selector ||
		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {

		return results;
	}

	// Try to shortcut find operations (as opposed to filters) in HTML documents
	if ( !seed ) {

		if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
			setDocument( context );
		}
		context = context || document;

		if ( documentIsHTML ) {

			// If the selector is sufficiently simple, try using a "get*By*" DOM method
			// (excepting DocumentFragment context, where the methods don't exist)
			if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {

				// ID selector
				if ( (m = match[1]) ) {

					// Document context
					if ( nodeType === 9 ) {
						if ( (elem = context.getElementById( m )) ) {

							// Support: IE, Opera, Webkit
							// TODO: identify versions
							// getElementById can match elements by name instead of ID
							if ( elem.id === m ) {
								results.push( elem );
								return results;
							}
						} else {
							return results;
						}

					// Element context
					} else {

						// Support: IE, Opera, Webkit
						// TODO: identify versions
						// getElementById can match elements by name instead of ID
						if ( newContext && (elem = newContext.getElementById( m )) &&
							contains( context, elem ) &&
							elem.id === m ) {

							results.push( elem );
							return results;
						}
					}

				// Type selector
				} else if ( match[2] ) {
					push.apply( results, context.getElementsByTagName( selector ) );
					return results;

				// Class selector
				} else if ( (m = match[3]) && support.getElementsByClassName &&
					context.getElementsByClassName ) {

					push.apply( results, context.getElementsByClassName( m ) );
					return results;
				}
			}

			// Take advantage of querySelectorAll
			if ( support.qsa &&
				!compilerCache[ selector + " " ] &&
				(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {

				if ( nodeType !== 1 ) {
					newContext = context;
					newSelector = selector;

				// qSA looks outside Element context, which is not what we want
				// Thanks to Andrew Dupont for this workaround technique
				// Support: IE <=8
				// Exclude object elements
				} else if ( context.nodeName.toLowerCase() !== "object" ) {

					// Capture the context ID, setting it first if necessary
					if ( (nid = context.getAttribute( "id" )) ) {
						nid = nid.replace( rescape, "\\$&" );
					} else {
						context.setAttribute( "id", (nid = expando) );
					}

					// Prefix every selector in the list
					groups = tokenize( selector );
					i = groups.length;
					nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
					while ( i-- ) {
						groups[i] = nidselect + " " + toSelector( groups[i] );
					}
					newSelector = groups.join( "," );

					// Expand context for sibling selectors
					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
						context;
				}

				if ( newSelector ) {
					try {
						push.apply( results,
							newContext.querySelectorAll( newSelector )
						);
						return results;
					} catch ( qsaError ) {
					} finally {
						if ( nid === expando ) {
							context.removeAttribute( "id" );
						}
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {function(string, object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var keys = [];

	function cache( key, value ) {
		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
		if ( keys.push( key + " " ) > Expr.cacheLength ) {
			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return (cache[ key + " " ] = value);
	}
	return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created div and expects a boolean result
 */
function assert( fn ) {
	var div = document.createElement("div");

	try {
		return !!fn( div );
	} catch (e) {
		return false;
	} finally {
		// Remove from its parent by default
		if ( div.parentNode ) {
			div.parentNode.removeChild( div );
		}
		// release memory in IE
		div = null;
	}
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
	var arr = attrs.split("|"),
		i = arr.length;

	while ( i-- ) {
		Expr.attrHandle[ arr[i] ] = handler;
	}
}

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck( a, b ) {
	var cur = b && a,
		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
			( ~b.sourceIndex || MAX_NEGATIVE ) -
			( ~a.sourceIndex || MAX_NEGATIVE );

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( (cur = cur.nextSibling) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return (name === "input" || name === "button") && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction(function( argument ) {
		argument = +argument;
		return markFunction(function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ (j = matchIndexes[i]) ] ) {
					seed[j] = !(matches[j] = seed[j]);
				}
			}
		});
	});
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== "undefined" && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function( elem ) {
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833)
	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
	var hasCompare, parent,
		doc = node ? node.ownerDocument || node : preferredDoc;

	// Return early if doc is invalid or already selected
	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Update global variables
	document = doc;
	docElem = document.documentElement;
	documentIsHTML = !isXML( document );

	// Support: IE 9-11, Edge
	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
	if ( (parent = document.defaultView) && parent.top !== parent ) {
		// Support: IE 11
		if ( parent.addEventListener ) {
			parent.addEventListener( "unload", unloadHandler, false );

		// Support: IE 9 - 10 only
		} else if ( parent.attachEvent ) {
			parent.attachEvent( "onunload", unloadHandler );
		}
	}

	/* Attributes
	---------------------------------------------------------------------- */

	// Support: IE<8
	// Verify that getAttribute really returns attributes and not properties
	// (excepting IE8 booleans)
	support.attributes = assert(function( div ) {
		div.className = "i";
		return !div.getAttribute("className");
	});

	/* getElement(s)By*
	---------------------------------------------------------------------- */

	// Check if getElementsByTagName("*") returns only elements
	support.getElementsByTagName = assert(function( div ) {
		div.appendChild( document.createComment("") );
		return !div.getElementsByTagName("*").length;
	});

	// Support: IE<9
	support.getElementsByClassName = rnative.test( document.getElementsByClassName );

	// Support: IE<10
	// Check if getElementById returns elements by name
	// The broken getElementById methods don't pick up programatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert(function( div ) {
		docElem.appendChild( div ).id = expando;
		return !document.getElementsByName || !document.getElementsByName( expando ).length;
	});

	// ID find and filter
	if ( support.getById ) {
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var m = context.getElementById( id );
				return m ? [ m ] : [];
			}
		};
		Expr.filter["ID"] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute("id") === attrId;
			};
		};
	} else {
		// Support: IE6/7
		// getElementById is not reliable as a find shortcut
		delete Expr.find["ID"];

		Expr.filter["ID"] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== "undefined" &&
					elem.getAttributeNode("id");
				return node && node.value === attrId;
			};
		};
	}

	// Tag
	Expr.find["TAG"] = support.getElementsByTagName ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== "undefined" ) {
				return context.getElementsByTagName( tag );

			// DocumentFragment nodes don't have gEBTN
			} else if ( support.qsa ) {
				return context.querySelectorAll( tag );
			}
		} :

		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,
				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( (elem = results[i++]) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};

	// Class
	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
			return context.getElementsByClassName( className );
		}
	};

	/* QSA/matchesSelector
	---------------------------------------------------------------------- */

	// QSA and matchesSelector support

	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
	rbuggyMatches = [];

	// qSa(:focus) reports false when true (Chrome 21)
	// We allow this because of a bug in IE8/9 that throws an error
	// whenever `document.activeElement` is accessed on an iframe
	// So, we allow :focus to pass through QSA all the time to avoid the IE error
	// See http://bugs.jquery.com/ticket/13378
	rbuggyQSA = [];

	if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert(function( div ) {
			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explicitly
			// setting a boolean content attribute,
			// since its presence should be enough
			// http://bugs.jquery.com/ticket/12359
			docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
				"<option selected=''></option></select>";

			// Support: IE8, Opera 11-12.16
			// Nothing should be selected when empty strings follow ^= or $= or *=
			// The test attribute must be unknown in Opera but "safe" for WinRT
			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
			if ( div.querySelectorAll("[msallowcapture^='']").length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
			}

			// Support: IE8
			// Boolean attributes and "value" are not treated correctly
			if ( !div.querySelectorAll("[selected]").length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
			}

			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
			if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
				rbuggyQSA.push("~=");
			}

			// Webkit/Opera - :checked should return selected option elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":checked").length ) {
				rbuggyQSA.push(":checked");
			}

			// Support: Safari 8+, iOS 8+
			// https://bugs.webkit.org/show_bug.cgi?id=136851
			// In-page `selector#id sibing-combinator selector` fails
			if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
				rbuggyQSA.push(".#.+[+~]");
			}
		});

		assert(function( div ) {
			// Support: Windows 8 Native Apps
			// The type and name attributes are restricted during .innerHTML assignment
			var input = document.createElement("input");
			input.setAttribute( "type", "hidden" );
			div.appendChild( input ).setAttribute( "name", "D" );

			// Support: IE8
			// Enforce case-sensitivity of name attribute
			if ( div.querySelectorAll("[name=d]").length ) {
				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":enabled").length ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Opera 10-11 does not throw on post-comma invalid pseudos
			div.querySelectorAll("*,:x");
			rbuggyQSA.push(",.*:");
		});
	}

	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
		docElem.webkitMatchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector) )) ) {

		assert(function( div ) {
			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( div, "div" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( div, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		});
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );

	/* Contains
	---------------------------------------------------------------------- */
	hasCompare = rnative.test( docElem.compareDocumentPosition );

	// Element contains another
	// Purposefully self-exclusive
	// As in, an element does not contain itself
	contains = hasCompare || rnative.test( docElem.contains ) ?
		function( a, b ) {
			var adown = a.nodeType === 9 ? a.documentElement : a,
				bup = b && b.parentNode;
			return a === bup || !!( bup && bup.nodeType === 1 && (
				adown.contains ?
					adown.contains( bup ) :
					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
			));
		} :
		function( a, b ) {
			if ( b ) {
				while ( (b = b.parentNode) ) {
					if ( b === a ) {
						return true;
					}
				}
			}
			return false;
		};

	/* Sorting
	---------------------------------------------------------------------- */

	// Document order sorting
	sortOrder = hasCompare ?
	function( a, b ) {

		// Flag for duplicate removal
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		// Sort on method existence if only one input has compareDocumentPosition
		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
		if ( compare ) {
			return compare;
		}

		// Calculate position if both inputs belong to the same document
		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
			a.compareDocumentPosition( b ) :

			// Otherwise we know they are disconnected
			1;

		// Disconnected nodes
		if ( compare & 1 ||
			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {

			// Choose the first element that is related to our preferred document
			if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
				return -1;
			}
			if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
				return 1;
			}

			// Maintain original order
			return sortInput ?
				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
				0;
		}

		return compare & 4 ? -1 : 1;
	} :
	function( a, b ) {
		// Exit early if the nodes are identical
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		var cur,
			i = 0,
			aup = a.parentNode,
			bup = b.parentNode,
			ap = [ a ],
			bp = [ b ];

		// Parentless nodes are either documents or disconnected
		if ( !aup || !bup ) {
			return a === document ? -1 :
				b === document ? 1 :
				aup ? -1 :
				bup ? 1 :
				sortInput ?
				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
				0;

		// If the nodes are siblings, we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );
		}

		// Otherwise we need full lists of their ancestors for comparison
		cur = a;
		while ( (cur = cur.parentNode) ) {
			ap.unshift( cur );
		}
		cur = b;
		while ( (cur = cur.parentNode) ) {
			bp.unshift( cur );
		}

		// Walk down the tree looking for a discrepancy
		while ( ap[i] === bp[i] ) {
			i++;
		}

		return i ?
			// Do a sibling check if the nodes have a common ancestor
			siblingCheck( ap[i], bp[i] ) :

			// Otherwise nodes in our document sort first
			ap[i] === preferredDoc ? -1 :
			bp[i] === preferredDoc ? 1 :
			0;
	};

	return document;
};

Sizzle.matches = function( expr, elements ) {
	return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	// Make sure that attribute selectors are quoted
	expr = expr.replace( rattributeQuotes, "='$1']" );

	if ( support.matchesSelector && documentIsHTML &&
		!compilerCache[ expr + " " ] &&
		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {

		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||
					// As well, disconnected nodes are said to be in a document
					// fragment in IE 9
					elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch (e) {}
	}

	return Sizzle( expr, document, null, [ elem ] ).length > 0;
};

Sizzle.contains = function( context, elem ) {
	// Set document vars if needed
	if ( ( context.ownerDocument || context ) !== document ) {
		setDocument( context );
	}
	return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	var fn = Expr.attrHandle[ name.toLowerCase() ],
		// Don't get fooled by Object.prototype properties (jQuery #13807)
		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
			fn( elem, name, !documentIsHTML ) :
			undefined;

	return val !== undefined ?
		val :
		support.attributes || !documentIsHTML ?
			elem.getAttribute( name ) :
			(val = elem.getAttributeNode(name)) && val.specified ?
				val.value :
				null;
};

Sizzle.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		j = 0,
		i = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	hasDuplicate = !support.detectDuplicates;
	sortInput = !support.sortStable && results.slice( 0 );
	results.sort( sortOrder );

	if ( hasDuplicate ) {
		while ( (elem = results[i++]) ) {
			if ( elem === results[ i ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			results.splice( duplicates[ j ], 1 );
		}
	}

	// Clear input after sorting to release objects
	// See https://github.com/jquery/sizzle/pull/225
	sortInput = null;

	return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {
		// If no nodeType, this is expected to be an array
		while ( (node = elem[i++]) ) {
			// Do not traverse comment nodes
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
		// Use textContent for elements
		// innerText usage removed for consistency of new lines (jQuery #11153)
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {
			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}
	// Do not include comment or processing instruction nodes

	return ret;
};

Expr = Sizzle.selectors = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	attrHandle: {},

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		"ATTR": function( match ) {
			match[1] = match[1].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );

			if ( match[2] === "~=" ) {
				match[3] = " " + match[3] + " ";
			}

			return match.slice( 0, 4 );
		},

		"CHILD": function( match ) {
			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[1] = match[1].toLowerCase();

			if ( match[1].slice( 0, 3 ) === "nth" ) {
				// nth-* requires argument
				if ( !match[3] ) {
					Sizzle.error( match[0] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );

			// other types prohibit arguments
			} else if ( match[3] ) {
				Sizzle.error( match[0] );
			}

			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[6] && match[2];

			if ( matchExpr["CHILD"].test( match[0] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[3] ) {
				match[2] = match[4] || match[5] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&
				// Get excess from tokenize (recursively)
				(excess = tokenize( unquoted, true )) &&
				// advance to the next closing parenthesis
				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {

				// excess is a negative index
				match[0] = match[0].slice( 0, excess );
				match[2] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeNameSelector ) {
			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() { return true; } :
				function( elem ) {
					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
				};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
				classCache( className, function( elem ) {
					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
				});
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
			};
		},

		"CHILD": function( type, what, argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, context, xml ) {
					var cache, uniqueCache, outerCache, node, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType,
						diff = false;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( (node = node[ dir ]) ) {
									if ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) {

										return false;
									}
								}
								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {

							// Seek `elem` from a previously-cached index

							// ...in a gzip-friendly way
							node = parent;
							outerCache = node[ expando ] || (node[ expando ] = {});

							// Support: IE <9 only
							// Defend against cloned attroperties (jQuery gh-1709)
							uniqueCache = outerCache[ node.uniqueID ] ||
								(outerCache[ node.uniqueID ] = {});

							cache = uniqueCache[ type ] || [];
							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
							diff = nodeIndex && cache[ 2 ];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( (node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								(diff = nodeIndex = 0) || start.pop()) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						} else {
							// Use previously-cached element index if available
							if ( useCache ) {
								// ...in a gzip-friendly way
								node = elem;
								outerCache = node[ expando ] || (node[ expando ] = {});

								// Support: IE <9 only
								// Defend against cloned attroperties (jQuery gh-1709)
								uniqueCache = outerCache[ node.uniqueID ] ||
									(outerCache[ node.uniqueID ] = {});

								cache = uniqueCache[ type ] || [];
								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
								diff = nodeIndex;
							}

							// xml :nth-child(...)
							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
							if ( diff === false ) {
								// Use the same loop as above to seek `elem` from the start
								while ( (node = ++nodeIndex && node && node[ dir ] ||
									(diff = nodeIndex = 0) || start.pop()) ) {

									if ( ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) &&
										++diff ) {

										// Cache the index of each encountered element
										if ( useCache ) {
											outerCache = node[ expando ] || (node[ expando ] = {});

											// Support: IE <9 only
											// Defend against cloned attroperties (jQuery gh-1709)
											uniqueCache = outerCache[ node.uniqueID ] ||
												(outerCache[ node.uniqueID ] = {});

											uniqueCache[ type ] = [ dirruns, diff ];
										}

										if ( node === elem ) {
											break;
										}
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {
			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction(function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf( seed, matched[i] );
							seed[ idx ] = !( matches[ idx ] = matched[i] );
						}
					}) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {
		// Potentially complex pseudos
		"not": markFunction(function( selector ) {
			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction(function( seed, matches, context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( (elem = unmatched[i]) ) {
							seed[i] = !(matches[i] = elem);
						}
					}
				}) :
				function( elem, context, xml ) {
					input[0] = elem;
					matcher( input, null, xml, results );
					// Don't keep the element (issue #299)
					input[0] = null;
					return !results.pop();
				};
		}),

		"has": markFunction(function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		}),

		"contains": markFunction(function( text ) {
			text = text.replace( runescape, funescape );
			return function( elem ) {
				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
			};
		}),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {
			// lang value must be a valid identifier
			if ( !ridentifier.test(lang || "") ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( (elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
				return false;
			};
		}),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
		},

		// Boolean properties
		"enabled": function( elem ) {
			return elem.disabled === false;
		},

		"disabled": function( elem ) {
			return elem.disabled === true;
		},

		"checked": function( elem ) {
			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
		},

		"selected": function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {
			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos["empty"]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo(function() {
			return [ 0 ];
		}),

		"last": createPositionalPseudo(function( matchIndexes, length ) {
			return [ length - 1 ];
		}),

		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		}),

		"even": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"odd": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		})
	}
};

Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || (match = rcomma.exec( soFar )) ) {
			if ( match ) {
				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[0].length ) || soFar;
			}
			groups.push( (tokens = []) );
		}

		matched = false;

		// Combinators
		if ( (match = rcombinators.exec( soFar )) ) {
			matched = match.shift();
			tokens.push({
				value: matched,
				// Cast descendant combinators to space
				type: match[0].replace( rtrim, " " )
			});
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
				(match = preFilters[ type ]( match ))) ) {
				matched = match.shift();
				tokens.push({
					value: matched,
					type: type,
					matches: match
				});
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :
			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
};

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[i].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		checkNonElements = base && dir === "parentNode",
		doneName = done++;

	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, uniqueCache, outerCache,
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
			if ( xml ) {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || (elem[ expando ] = {});

						// Support: IE <9 only
						// Defend against cloned attroperties (jQuery gh-1709)
						uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});

						if ( (oldCache = uniqueCache[ dir ]) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return (newCache[ 2 ] = oldCache[ 2 ]);
						} else {
							// Reuse newcache so results back-propagate to previous elements
							uniqueCache[ dir ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
								return true;
							}
						}
					}
				}
			}
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[i]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[0];
}

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		Sizzle( selector, contexts[i], results );
	}
	return results;
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( (elem = unmatched[i]) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction(function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems,

			matcherOut = matcher ?
				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

					// ...intermediate processing is necessary
					[] :

					// ...otherwise use results directly
					results :
				matcherIn;

		// Find primary matches
		if ( matcher ) {
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( (elem = temp[i]) ) {
					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {
					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( (elem = matcherOut[i]) ) {
							// Restore matcherIn since elem is not yet a final match
							temp.push( (matcherIn[i] = elem) );
						}
					}
					postFinder( null, (matcherOut = []), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( (elem = matcherOut[i]) &&
						(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {

						seed[temp] = !(results[temp] = elem);
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	});
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[0].type ],
		implicitRelative = leadingRelative || Expr.relative[" "],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				(checkContext = context).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );
			// Avoid hanging onto element (issue #299)
			checkContext = null;
			return ret;
		} ];

	for ( ; i < len; i++ ) {
		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
		} else {
			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {
				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[j].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(
						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
					).replace( rtrim, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,
				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
				len = elems.length;

			if ( outermost ) {
				outermostContext = context === document || context || outermost;
			}

			// Add elements passing elementMatchers directly to results
			// Support: IE<9, Safari
			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;
					if ( !context && elem.ownerDocument !== document ) {
						setDocument( elem );
						xml = !documentIsHTML;
					}
					while ( (matcher = elementMatchers[j++]) ) {
						if ( matcher( elem, context || document, xml) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {
					// They will have gone through all possible matchers
					if ( (elem = !matcher && elem) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// `i` is now the count of elements visited above, and adding it to `matchedCount`
			// makes the latter nonnegative.
			matchedCount += i;

			// Apply set filters to unmatched elements
			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
			// no element matchers and no seed.
			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
			// numerically zero.
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( (matcher = setMatchers[j++]) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {
					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !(unmatched[i] || setMatched[i]) ) {
								setMatched[i] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					Sizzle.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {
		// Generate a function of recursive functions that can be used to check each element
		if ( !match ) {
			match = tokenize( selector );
		}
		i = match.length;
		while ( i-- ) {
			cached = matcherFromTokens( match[i] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );

		// Save selector and tokenization
		cached.selector = selector;
	}
	return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		compiled = typeof selector === "function" && selector,
		match = !seed && tokenize( (selector = compiled.selector || selector) );

	results = results || [];

	// Try to minimize operations if there is only one selector in the list and no seed
	// (the latter of which guarantees us context)
	if ( match.length === 1 ) {

		// Reduce context if the leading compound selector is an ID
		tokens = match[0] = match[0].slice( 0 );
		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
				support.getById && context.nodeType === 9 && documentIsHTML &&
				Expr.relative[ tokens[1].type ] ) {

			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
			if ( !context ) {
				return results;

			// Precompiled matchers will still verify ancestry, so step up a level
			} else if ( compiled ) {
				context = context.parentNode;
			}

			selector = selector.slice( tokens.shift().value.length );
		}

		// Fetch a seed set for right-to-left matching
		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
		while ( i-- ) {
			token = tokens[i];

			// Abort if we hit a combinator
			if ( Expr.relative[ (type = token.type) ] ) {
				break;
			}
			if ( (find = Expr.find[ type ]) ) {
				// Search, expanding context for leading sibling combinators
				if ( (seed = find(
					token.matches[0].replace( runescape, funescape ),
					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
				)) ) {

					// If seed is empty or no tokens remain, we can return early
					tokens.splice( i, 1 );
					selector = seed.length && toSelector( tokens );
					if ( !selector ) {
						push.apply( results, seed );
						return results;
					}

					break;
				}
			}
		}
	}

	// Compile and execute a filtering function if one is not provided
	// Provide `match` to avoid retokenization if we modified the selector above
	( compiled || compile( selector, match ) )(
		seed,
		context,
		!documentIsHTML,
		results,
		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
	return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;

// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( div1 ) {
	// Should return 1, but returns 4 (following)
	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});

// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
	div.innerHTML = "<a href='#'></a>";
	return div.firstChild.getAttribute("href") === "#" ;
}) ) {
	addHandle( "type|href|height|width", function( elem, name, isXML ) {
		if ( !isXML ) {
			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
		}
	});
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
	div.innerHTML = "<input/>";
	div.firstChild.setAttribute( "value", "" );
	return div.firstChild.getAttribute( "value" ) === "";
}) ) {
	addHandle( "value", function( elem, name, isXML ) {
		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
			return elem.defaultValue;
		}
	});
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
	return div.getAttribute("disabled") == null;
}) ) {
	addHandle( booleans, function( elem, name, isXML ) {
		var val;
		if ( !isXML ) {
			return elem[ name ] === true ? name.toLowerCase() :
					(val = elem.getAttributeNode( name )) && val.specified ?
					val.value :
				null;
		}
	});
}

return Sizzle;

})( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;



var dir = function( elem, dir, until ) {
	var matched = [],
		truncate = until !== undefined;

	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
		if ( elem.nodeType === 1 ) {
			if ( truncate && jQuery( elem ).is( until ) ) {
				break;
			}
			matched.push( elem );
		}
	}
	return matched;
};


var siblings = function( n, elem ) {
	var matched = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType === 1 && n !== elem ) {
			matched.push( n );
		}
	}

	return matched;
};


var rneedsContext = jQuery.expr.match.needsContext;

var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );



var risSimple = /^.[^:#\[\.,]*$/;

// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( jQuery.isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			/* jshint -W018 */
			return !!qualifier.call( elem, i, elem ) !== not;
		} );

	}

	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		} );

	}

	if ( typeof qualifier === "string" ) {
		if ( risSimple.test( qualifier ) ) {
			return jQuery.filter( qualifier, elements, not );
		}

		qualifier = jQuery.filter( qualifier, elements );
	}

	return jQuery.grep( elements, function( elem ) {
		return ( jQuery.inArray( elem, qualifier ) > -1 ) !== not;
	} );
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return elems.length === 1 && elem.nodeType === 1 ?
		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
			return elem.nodeType === 1;
		} ) );
};

jQuery.fn.extend( {
	find: function( selector ) {
		var i,
			ret = [],
			self = this,
			len = self.length;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter( function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			} ) );
		}

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		// Needed because $( selector, context ) becomes $( context ).find( selector )
		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
		ret.selector = this.selector ? this.selector + " " + selector : selector;
		return ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow( this, selector || [], false ) );
	},
	not: function( selector ) {
		return this.pushStack( winnow( this, selector || [], true ) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
} );


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

	init = jQuery.fn.init = function( selector, context, root ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// init accepts an alternate rootjQuery
		// so migrate can support jQuery.sub (gh-2101)
		root = root || rootjQuery;

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector.charAt( 0 ) === "<" &&
				selector.charAt( selector.length - 1 ) === ">" &&
				selector.length >= 3 ) {

				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && ( match[ 1 ] || !context ) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[ 1 ] ) {
					context = context instanceof jQuery ? context[ 0 ] : context;

					// scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[ 1 ],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {

							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[ 2 ] );

					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {

						// Handle the case where IE and Opera return items
						// by name instead of ID
						if ( elem.id !== match[ 2 ] ) {
							return rootjQuery.find( selector );
						}

						// Otherwise, we inject the element directly into the jQuery object
						this.length = 1;
						this[ 0 ] = elem;
					}

					this.context = document;
					this.selector = selector;
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || root ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this.context = this[ 0 ] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			return typeof root.ready !== "undefined" ?
				root.ready( selector ) :

				// Execute immediately if ready is not present
				selector( jQuery );
		}

		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,

	// methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.fn.extend( {
	has: function( target ) {
		var i,
			targets = jQuery( target, this ),
			len = targets.length;

		return this.filter( function() {
			for ( i = 0; i < len; i++ ) {
				if ( jQuery.contains( this, targets[ i ] ) ) {
					return true;
				}
			}
		} );
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
				jQuery( selectors, context || this.context ) :
				0;

		for ( ; i < l; i++ ) {
			for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {

				// Always skip document fragments
				if ( cur.nodeType < 11 && ( pos ?
					pos.index( cur ) > -1 :

					// Don't pass non-elements to Sizzle
					cur.nodeType === 1 &&
						jQuery.find.matchesSelector( cur, selectors ) ) ) {

					matched.push( cur );
					break;
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// index in selector
		if ( typeof elem === "string" ) {
			return jQuery.inArray( this[ 0 ], jQuery( elem ) );
		}

		// Locate the position of the desired element
		return jQuery.inArray(

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem, this );
	},

	add: function( selector, context ) {
		return this.pushStack(
			jQuery.uniqueSort(
				jQuery.merge( this.get(), jQuery( selector, context ) )
			)
		);
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	}
} );

function sibling( cur, dir ) {
	do {
		cur = cur[ dir ];
	} while ( cur && cur.nodeType !== 1 );

	return cur;
}

jQuery.each( {
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, i, until ) {
		return dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, i, until ) {
		return dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, i, until ) {
		return dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return siblings( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return siblings( elem.firstChild );
	},
	contents: function( elem ) {
		return jQuery.nodeName( elem, "iframe" ) ?
			elem.contentDocument || elem.contentWindow.document :
			jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var ret = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			ret = jQuery.filter( selector, ret );
		}

		if ( this.length > 1 ) {

			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				ret = jQuery.uniqueSort( ret );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				ret = ret.reverse();
			}
		}

		return this.pushStack( ret );
	};
} );
var rnotwhite = ( /\S+/g );



// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
	var object = {};
	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	} );
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		createOptions( options ) :
		jQuery.extend( {}, options );

	var // Flag to know if list is currently firing
		firing,

		// Last fire value for non-forgettable lists
		memory,

		// Flag to know if list was already fired
		fired,

		// Flag to prevent firing
		locked,

		// Actual callback list
		list = [],

		// Queue of execution data for repeatable lists
		queue = [],

		// Index of currently firing callback (modified by add/remove as needed)
		firingIndex = -1,

		// Fire callbacks
		fire = function() {

			// Enforce single-firing
			locked = options.once;

			// Execute callbacks for all pending executions,
			// respecting firingIndex overrides and runtime changes
			fired = firing = true;
			for ( ; queue.length; firingIndex = -1 ) {
				memory = queue.shift();
				while ( ++firingIndex < list.length ) {

					// Run callback and check for early termination
					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
						options.stopOnFalse ) {

						// Jump to end and forget the data so .add doesn't re-fire
						firingIndex = list.length;
						memory = false;
					}
				}
			}

			// Forget the data if we're done with it
			if ( !options.memory ) {
				memory = false;
			}

			firing = false;

			// Clean up if we're done firing for good
			if ( locked ) {

				// Keep an empty list if we have data for future add calls
				if ( memory ) {
					list = [];

				// Otherwise, this object is spent
				} else {
					list = "";
				}
			}
		},

		// Actual Callbacks object
		self = {

			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {

					// If we have memory from a past run, we should fire after adding
					if ( memory && !firing ) {
						firingIndex = list.length - 1;
						queue.push( memory );
					}

					( function add( args ) {
						jQuery.each( args, function( _, arg ) {
							if ( jQuery.isFunction( arg ) ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {

								// Inspect recursively
								add( arg );
							}
						} );
					} )( arguments );

					if ( memory && !firing ) {
						fire();
					}
				}
				return this;
			},

			// Remove a callback from the list
			remove: function() {
				jQuery.each( arguments, function( _, arg ) {
					var index;
					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
						list.splice( index, 1 );

						// Handle firing indexes
						if ( index <= firingIndex ) {
							firingIndex--;
						}
					}
				} );
				return this;
			},

			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ?
					jQuery.inArray( fn, list ) > -1 :
					list.length > 0;
			},

			// Remove all callbacks from the list
			empty: function() {
				if ( list ) {
					list = [];
				}
				return this;
			},

			// Disable .fire and .add
			// Abort any current/pending executions
			// Clear all callbacks and values
			disable: function() {
				locked = queue = [];
				list = memory = "";
				return this;
			},
			disabled: function() {
				return !list;
			},

			// Disable .fire
			// Also disable .add unless we have memory (since it would have no effect)
			// Abort any pending executions
			lock: function() {
				locked = true;
				if ( !memory ) {
					self.disable();
				}
				return this;
			},
			locked: function() {
				return !!locked;
			},

			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( !locked ) {
					args = args || [];
					args = [ context, args.slice ? args.slice() : args ];
					queue.push( args );
					if ( !firing ) {
						fire();
					}
				}
				return this;
			},

			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},

			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};


jQuery.extend( {

	Deferred: function( func ) {
		var tuples = [

				// action, add listener, listener list, final state
				[ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
				[ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
				[ "notify", "progress", jQuery.Callbacks( "memory" ) ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					return jQuery.Deferred( function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];

							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							deferred[ tuple[ 1 ] ]( function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.progress( newDefer.notify )
										.done( newDefer.resolve )
										.fail( newDefer.reject );
								} else {
									newDefer[ tuple[ 0 ] + "With" ](
										this === promise ? newDefer.promise() : this,
										fn ? [ returned ] : arguments
									);
								}
							} );
						} );
						fns = null;
					} ).promise();
				},

				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Keep pipe for back-compat
		promise.pipe = promise.then;

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];

			// promise[ done | fail | progress ] = list.add
			promise[ tuple[ 1 ] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add( function() {

					// state = [ resolved | rejected ]
					state = stateString;

				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}

			// deferred[ resolve | reject | notify ]
			deferred[ tuple[ 0 ] ] = function() {
				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
		} );

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( subordinate /* , ..., subordinateN */ ) {
		var i = 0,
			resolveValues = slice.call( arguments ),
			length = resolveValues.length,

			// the count of uncompleted subordinates
			remaining = length !== 1 ||
				( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

			// the master Deferred.
			// If resolveValues consist of only a single Deferred, just use that.
			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

			// Update function for both resolve and progress values
			updateFunc = function( i, contexts, values ) {
				return function( value ) {
					contexts[ i ] = this;
					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( values === progressValues ) {
						deferred.notifyWith( contexts, values );

					} else if ( !( --remaining ) ) {
						deferred.resolveWith( contexts, values );
					}
				};
			},

			progressValues, progressContexts, resolveContexts;

		// add listeners to Deferred subordinates; treat others as resolved
		if ( length > 1 ) {
			progressValues = new Array( length );
			progressContexts = new Array( length );
			resolveContexts = new Array( length );
			for ( ; i < length; i++ ) {
				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
					resolveValues[ i ].promise()
						.progress( updateFunc( i, progressContexts, progressValues ) )
						.done( updateFunc( i, resolveContexts, resolveValues ) )
						.fail( deferred.reject );
				} else {
					--remaining;
				}
			}
		}

		// if we're not waiting on anything, resolve the master
		if ( !remaining ) {
			deferred.resolveWith( resolveContexts, resolveValues );
		}

		return deferred.promise();
	}
} );


// The deferred used on DOM ready
var readyList;

jQuery.fn.ready = function( fn ) {

	// Add the callback
	jQuery.ready.promise().done( fn );

	return this;
};

jQuery.extend( {

	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );

		// Trigger any bound ready events
		if ( jQuery.fn.triggerHandler ) {
			jQuery( document ).triggerHandler( "ready" );
			jQuery( document ).off( "ready" );
		}
	}
} );

/**
 * Clean-up method for dom ready events
 */
function detach() {
	if ( document.addEventListener ) {
		document.removeEventListener( "DOMContentLoaded", completed );
		window.removeEventListener( "load", completed );

	} else {
		document.detachEvent( "onreadystatechange", completed );
		window.detachEvent( "onload", completed );
	}
}

/**
 * The ready event handler and self cleanup method
 */
function completed() {

	// readyState === "complete" is good enough for us to call the dom ready in oldIE
	if ( document.addEventListener ||
		window.event.type === "load" ||
		document.readyState === "complete" ) {

		detach();
		jQuery.ready();
	}
}

jQuery.ready.promise = function( obj ) {
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called
		// after the browser event has already occurred.
		// Support: IE6-10
		// Older IE sometimes signals "interactive" too soon
		if ( document.readyState === "complete" ||
			( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {

			// Handle it asynchronously to allow scripts the opportunity to delay ready
			window.setTimeout( jQuery.ready );

		// Standards-based browsers support DOMContentLoaded
		} else if ( document.addEventListener ) {

			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed );

		// If IE event model is used
		} else {

			// Ensure firing before onload, maybe late but safe also for iframes
			document.attachEvent( "onreadystatechange", completed );

			// A fallback to window.onload, that will always work
			window.attachEvent( "onload", completed );

			// If IE and not a frame
			// continually check to see if the document is ready
			var top = false;

			try {
				top = window.frameElement == null && document.documentElement;
			} catch ( e ) {}

			if ( top && top.doScroll ) {
				( function doScrollCheck() {
					if ( !jQuery.isReady ) {

						try {

							// Use the trick by Diego Perini
							// http://javascript.nwbox.com/IEContentLoaded/
							top.doScroll( "left" );
						} catch ( e ) {
							return window.setTimeout( doScrollCheck, 50 );
						}

						// detach all dom ready events
						detach();

						// and execute any waiting functions
						jQuery.ready();
					}
				} )();
			}
		}
	}
	return readyList.promise( obj );
};

// Kick off the DOM ready check even if the user does not
jQuery.ready.promise();




// Support: IE<9
// Iteration over object's inherited properties before its own
var i;
for ( i in jQuery( support ) ) {
	break;
}
support.ownFirst = i === "0";

// Note: most support tests are defined in their respective modules.
// false until the test is run
support.inlineBlockNeedsLayout = false;

// Execute ASAP in case we need to set body.style.zoom
jQuery( function() {

	// Minified: var a,b,c,d
	var val, div, body, container;

	body = document.getElementsByTagName( "body" )[ 0 ];
	if ( !body || !body.style ) {

		// Return for frameset docs that don't have a body
		return;
	}

	// Setup
	div = document.createElement( "div" );
	container = document.createElement( "div" );
	container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
	body.appendChild( container ).appendChild( div );

	if ( typeof div.style.zoom !== "undefined" ) {

		// Support: IE<8
		// Check if natively block-level elements act like inline-block
		// elements when setting their display to 'inline' and giving
		// them layout
		div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";

		support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
		if ( val ) {

			// Prevent IE 6 from affecting layout for positioned elements #11048
			// Prevent IE from shrinking the body in IE 7 mode #12869
			// Support: IE<8
			body.style.zoom = 1;
		}
	}

	body.removeChild( container );
} );


( function() {
	var div = document.createElement( "div" );

	// Support: IE<9
	support.deleteExpando = true;
	try {
		delete div.test;
	} catch ( e ) {
		support.deleteExpando = false;
	}

	// Null elements to avoid leaks in IE.
	div = null;
} )();
var acceptData = function( elem ) {
	var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
		nodeType = +elem.nodeType || 1;

	// Do not set data on non-element DOM nodes because it will not be cleared (#8335).
	return nodeType !== 1 && nodeType !== 9 ?
		false :

		// Nodes accept data unless otherwise specified; rejection can be conditional
		!noData || noData !== true && elem.getAttribute( "classid" ) === noData;
};




var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /([A-Z])/g;

function dataAttr( elem, key, data ) {

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {

		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();

		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
					data === "false" ? false :
					data === "null" ? null :

					// Only convert to a number if it doesn't change the string
					+data + "" === data ? +data :
					rbrace.test( data ) ? jQuery.parseJSON( data ) :
					data;
			} catch ( e ) {}

			// Make sure we set the data so it isn't changed later
			jQuery.data( elem, key, data );

		} else {
			data = undefined;
		}
	}

	return data;
}

// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
	var name;
	for ( name in obj ) {

		// if the public data object is empty, the private is still empty
		if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
			continue;
		}
		if ( name !== "toJSON" ) {
			return false;
		}
	}

	return true;
}

function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
	if ( !acceptData( elem ) ) {
		return;
	}

	var ret, thisCache,
		internalKey = jQuery.expando,

		// We have to handle DOM nodes and JS objects differently because IE6-7
		// can't GC object references properly across the DOM-JS boundary
		isNode = elem.nodeType,

		// Only DOM nodes need the global jQuery cache; JS object data is
		// attached directly to the object so GC can occur automatically
		cache = isNode ? jQuery.cache : elem,

		// Only defining an ID for JS objects if its cache already exists allows
		// the code to shortcut on the same path as a DOM node with no cache
		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

	// Avoid doing any more work than we need to when trying to get data on an
	// object that has no data at all
	if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
		data === undefined && typeof name === "string" ) {
		return;
	}

	if ( !id ) {

		// Only DOM nodes need a new unique ID for each element since their data
		// ends up in the global cache
		if ( isNode ) {
			id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
		} else {
			id = internalKey;
		}
	}

	if ( !cache[ id ] ) {

		// Avoid exposing jQuery metadata on plain JS objects when the object
		// is serialized using JSON.stringify
		cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
	}

	// An object can be passed to jQuery.data instead of a key/value pair; this gets
	// shallow copied over onto the existing cache
	if ( typeof name === "object" || typeof name === "function" ) {
		if ( pvt ) {
			cache[ id ] = jQuery.extend( cache[ id ], name );
		} else {
			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
		}
	}

	thisCache = cache[ id ];

	// jQuery data() is stored in a separate object inside the object's internal data
	// cache in order to avoid key collisions between internal data and user-defined
	// data.
	if ( !pvt ) {
		if ( !thisCache.data ) {
			thisCache.data = {};
		}

		thisCache = thisCache.data;
	}

	if ( data !== undefined ) {
		thisCache[ jQuery.camelCase( name ) ] = data;
	}

	// Check for both converted-to-camel and non-converted data property names
	// If a data property was specified
	if ( typeof name === "string" ) {

		// First Try to find as-is property data
		ret = thisCache[ name ];

		// Test for null|undefined property data
		if ( ret == null ) {

			// Try to find the camelCased property
			ret = thisCache[ jQuery.camelCase( name ) ];
		}
	} else {
		ret = thisCache;
	}

	return ret;
}

function internalRemoveData( elem, name, pvt ) {
	if ( !acceptData( elem ) ) {
		return;
	}

	var thisCache, i,
		isNode = elem.nodeType,

		// See jQuery.data for more information
		cache = isNode ? jQuery.cache : elem,
		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

	// If there is already no cache entry for this object, there is no
	// purpose in continuing
	if ( !cache[ id ] ) {
		return;
	}

	if ( name ) {

		thisCache = pvt ? cache[ id ] : cache[ id ].data;

		if ( thisCache ) {

			// Support array or space separated string names for data keys
			if ( !jQuery.isArray( name ) ) {

				// try the string as a key before any manipulation
				if ( name in thisCache ) {
					name = [ name ];
				} else {

					// split the camel cased version by spaces unless a key with the spaces exists
					name = jQuery.camelCase( name );
					if ( name in thisCache ) {
						name = [ name ];
					} else {
						name = name.split( " " );
					}
				}
			} else {

				// If "name" is an array of keys...
				// When data is initially created, via ("key", "val") signature,
				// keys will be converted to camelCase.
				// Since there is no way to tell _how_ a key was added, remove
				// both plain key and camelCase key. #12786
				// This will only penalize the array argument path.
				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
			}

			i = name.length;
			while ( i-- ) {
				delete thisCache[ name[ i ] ];
			}

			// If there is no data left in the cache, we want to continue
			// and let the cache object itself get destroyed
			if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
				return;
			}
		}
	}

	// See jQuery.data for more information
	if ( !pvt ) {
		delete cache[ id ].data;

		// Don't destroy the parent cache unless the internal data object
		// had been the only thing left in it
		if ( !isEmptyDataObject( cache[ id ] ) ) {
			return;
		}
	}

	// Destroy the cache
	if ( isNode ) {
		jQuery.cleanData( [ elem ], true );

	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
	/* jshint eqeqeq: false */
	} else if ( support.deleteExpando || cache != cache.window ) {
		/* jshint eqeqeq: true */
		delete cache[ id ];

	// When all else fails, undefined
	} else {
		cache[ id ] = undefined;
	}
}

jQuery.extend( {
	cache: {},

	// The following elements (space-suffixed to avoid Object.prototype collisions)
	// throw uncatchable exceptions if you attempt to set expando properties
	noData: {
		"applet ": true,
		"embed ": true,

		// ...but Flash objects (which have this classid) *can* handle expandos
		"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
	},

	hasData: function( elem ) {
		elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
		return !!elem && !isEmptyDataObject( elem );
	},

	data: function( elem, name, data ) {
		return internalData( elem, name, data );
	},

	removeData: function( elem, name ) {
		return internalRemoveData( elem, name );
	},

	// For internal use only.
	_data: function( elem, name, data ) {
		return internalData( elem, name, data, true );
	},

	_removeData: function( elem, name ) {
		return internalRemoveData( elem, name, true );
	}
} );

jQuery.fn.extend( {
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Special expections of .data basically thwart jQuery.access,
		// so implement the relevant behavior ourselves

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = jQuery.data( elem );

				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE11+
						// The attrs elements can be null (#14894)
						if ( attrs[ i ] ) {
							name = attrs[ i ].name;
							if ( name.indexOf( "data-" ) === 0 ) {
								name = jQuery.camelCase( name.slice( 5 ) );
								dataAttr( elem, name, data[ name ] );
							}
						}
					}
					jQuery._data( elem, "parsedAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each( function() {
				jQuery.data( this, key );
			} );
		}

		return arguments.length > 1 ?

			// Sets one value
			this.each( function() {
				jQuery.data( this, key, value );
			} ) :

			// Gets one value
			// Try to fetch any internally stored data first
			elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
	},

	removeData: function( key ) {
		return this.each( function() {
			jQuery.removeData( this, key );
		} );
	}
} );


jQuery.extend( {
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = jQuery._data( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray( data ) ) {
					queue = jQuery._data( elem, type, jQuery.makeArray( data ) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// not intended for public consumption - generates a queueHooks object,
	// or returns the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
			empty: jQuery.Callbacks( "once memory" ).add( function() {
				jQuery._removeData( elem, type + "queue" );
				jQuery._removeData( elem, key );
			} )
		} );
	}
} );

jQuery.fn.extend( {
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[ 0 ], type );
		}

		return data === undefined ?
			this :
			this.each( function() {
				var queue = jQuery.queue( this, type, data );

				// ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			} );
	},
	dequeue: function( type ) {
		return this.each( function() {
			jQuery.dequeue( this, type );
		} );
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},

	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while ( i-- ) {
			tmp = jQuery._data( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
} );


( function() {
	var shrinkWrapBlocksVal;

	support.shrinkWrapBlocks = function() {
		if ( shrinkWrapBlocksVal != null ) {
			return shrinkWrapBlocksVal;
		}

		// Will be changed later if needed.
		shrinkWrapBlocksVal = false;

		// Minified: var b,c,d
		var div, body, container;

		body = document.getElementsByTagName( "body" )[ 0 ];
		if ( !body || !body.style ) {

			// Test fired too early or in an unsupported environment, exit.
			return;
		}

		// Setup
		div = document.createElement( "div" );
		container = document.createElement( "div" );
		container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
		body.appendChild( container ).appendChild( div );

		// Support: IE6
		// Check if elements with layout shrink-wrap their children
		if ( typeof div.style.zoom !== "undefined" ) {

			// Reset CSS: box-sizing; display; margin; border
			div.style.cssText =

				// Support: Firefox<29, Android 2.3
				// Vendor-prefix box-sizing
				"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
				"box-sizing:content-box;display:block;margin:0;border:0;" +
				"padding:1px;width:1px;zoom:1";
			div.appendChild( document.createElement( "div" ) ).style.width = "5px";
			shrinkWrapBlocksVal = div.offsetWidth !== 3;
		}

		body.removeChild( container );

		return shrinkWrapBlocksVal;
	};

} )();
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;

var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );


var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var isHidden = function( elem, el ) {

		// isHidden might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;
		return jQuery.css( elem, "display" ) === "none" ||
			!jQuery.contains( elem.ownerDocument, elem );
	};



function adjustCSS( elem, prop, valueParts, tween ) {
	var adjusted,
		scale = 1,
		maxIterations = 20,
		currentValue = tween ?
			function() { return tween.cur(); } :
			function() { return jQuery.css( elem, prop, "" ); },
		initial = currentValue(),
		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

		// Starting value computation is required for potential unit mismatches
		initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
			rcssNum.exec( jQuery.css( elem, prop ) );

	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {

		// Trust units reported by jQuery.css
		unit = unit || initialInUnit[ 3 ];

		// Make sure we update the tween properties later on
		valueParts = valueParts || [];

		// Iteratively approximate from a nonzero starting point
		initialInUnit = +initial || 1;

		do {

			// If previous iteration zeroed out, double until we get *something*.
			// Use string for doubling so we don't accidentally see scale as unchanged below
			scale = scale || ".5";

			// Adjust and apply
			initialInUnit = initialInUnit / scale;
			jQuery.style( elem, prop, initialInUnit + unit );

		// Update scale, tolerating zero or NaN from tween.cur()
		// Break the loop if scale is unchanged or perfect, or if we've just had enough.
		} while (
			scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
		);
	}

	if ( valueParts ) {
		initialInUnit = +initialInUnit || +initial || 0;

		// Apply relative offset (+=/-=) if specified
		adjusted = valueParts[ 1 ] ?
			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
			+valueParts[ 2 ];
		if ( tween ) {
			tween.unit = unit;
			tween.start = initialInUnit;
			tween.end = adjusted;
		}
	}
	return adjusted;
}


// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		length = elems.length,
		bulk = key == null;

	// Sets many values
	if ( jQuery.type( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			access( elems, fn, i, key[ i ], true, emptyGet, raw );
		}

	// Sets one value
	} else if ( value !== undefined ) {
		chainable = true;

		if ( !jQuery.isFunction( value ) ) {
			raw = true;
		}

		if ( bulk ) {

			// Bulk operations run against the entire set
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				bulk = fn;
				fn = function( elem, key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}

		if ( fn ) {
			for ( ; i < length; i++ ) {
				fn(
					elems[ i ],
					key,
					raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
				);
			}
		}
	}

	return chainable ?
		elems :

		// Gets
		bulk ?
			fn.call( elems ) :
			length ? fn( elems[ 0 ], key ) : emptyGet;
};
var rcheckableType = ( /^(?:checkbox|radio)$/i );

var rtagName = ( /<([\w:-]+)/ );

var rscriptType = ( /^$|\/(?:java|ecma)script/i );

var rleadingWhitespace = ( /^\s+/ );

var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|" +
		"details|dialog|figcaption|figure|footer|header|hgroup|main|" +
		"mark|meter|nav|output|picture|progress|section|summary|template|time|video";



function createSafeFragment( document ) {
	var list = nodeNames.split( "|" ),
		safeFrag = document.createDocumentFragment();

	if ( safeFrag.createElement ) {
		while ( list.length ) {
			safeFrag.createElement(
				list.pop()
			);
		}
	}
	return safeFrag;
}


( function() {
	var div = document.createElement( "div" ),
		fragment = document.createDocumentFragment(),
		input = document.createElement( "input" );

	// Setup
	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";

	// IE strips leading whitespace when .innerHTML is used
	support.leadingWhitespace = div.firstChild.nodeType === 3;

	// Make sure that tbody elements aren't automatically inserted
	// IE will insert them into empty tables
	support.tbody = !div.getElementsByTagName( "tbody" ).length;

	// Make sure that link elements get serialized correctly by innerHTML
	// This requires a wrapper element in IE
	support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;

	// Makes sure cloning an html5 element does not cause problems
	// Where outerHTML is undefined, this still works
	support.html5Clone =
		document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav></:nav>";

	// Check if a disconnected checkbox will retain its checked
	// value of true after appended to the DOM (IE6/7)
	input.type = "checkbox";
	input.checked = true;
	fragment.appendChild( input );
	support.appendChecked = input.checked;

	// Make sure textarea (and checkbox) defaultValue is properly cloned
	// Support: IE6-IE11+
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;

	// #11217 - WebKit loses check when the name is after the checked attribute
	fragment.appendChild( div );

	// Support: Windows Web Apps (WWA)
	// `name` and `type` must use .setAttribute for WWA (#14901)
	input = document.createElement( "input" );
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
	// old WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Support: IE<9
	// Cloned elements keep attachEvent handlers, we use addEventListener on IE9+
	support.noCloneEvent = !!div.addEventListener;

	// Support: IE<9
	// Since attributes and properties are the same in IE,
	// cleanData must set properties to undefined rather than use removeAttribute
	div[ jQuery.expando ] = 1;
	support.attributes = !div.getAttribute( jQuery.expando );
} )();


// We have to close these tags to support XHTML (#13200)
var wrapMap = {
	option: [ 1, "<select multiple='multiple'>", "</select>" ],
	legend: [ 1, "<fieldset>", "</fieldset>" ],
	area: [ 1, "<map>", "</map>" ],

	// Support: IE8
	param: [ 1, "<object>", "</object>" ],
	thead: [ 1, "<table>", "</table>" ],
	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
	col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

	// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
	// unless wrapped in a div with non-breaking characters in front of it.
	_default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ]
};

// Support: IE8-IE9
wrapMap.optgroup = wrapMap.option;

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;


function getAll( context, tag ) {
	var elems, elem,
		i = 0,
		found = typeof context.getElementsByTagName !== "undefined" ?
			context.getElementsByTagName( tag || "*" ) :
			typeof context.querySelectorAll !== "undefined" ?
				context.querySelectorAll( tag || "*" ) :
				undefined;

	if ( !found ) {
		for ( found = [], elems = context.childNodes || context;
			( elem = elems[ i ] ) != null;
			i++
		) {
			if ( !tag || jQuery.nodeName( elem, tag ) ) {
				found.push( elem );
			} else {
				jQuery.merge( found, getAll( elem, tag ) );
			}
		}
	}

	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
		jQuery.merge( [ context ], found ) :
		found;
}


// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var elem,
		i = 0;
	for ( ; ( elem = elems[ i ] ) != null; i++ ) {
		jQuery._data(
			elem,
			"globalEval",
			!refElements || jQuery._data( refElements[ i ], "globalEval" )
		);
	}
}


var rhtml = /<|&#?\w+;/,
	rtbody = /<tbody/i;

function fixDefaultChecked( elem ) {
	if ( rcheckableType.test( elem.type ) ) {
		elem.defaultChecked = elem.checked;
	}
}

function buildFragment( elems, context, scripts, selection, ignored ) {
	var j, elem, contains,
		tmp, tag, tbody, wrap,
		l = elems.length,

		// Ensure a safe fragment
		safe = createSafeFragment( context ),

		nodes = [],
		i = 0;

	for ( ; i < l; i++ ) {
		elem = elems[ i ];

		if ( elem || elem === 0 ) {

			// Add nodes directly
			if ( jQuery.type( elem ) === "object" ) {
				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

			// Convert non-html into a text node
			} else if ( !rhtml.test( elem ) ) {
				nodes.push( context.createTextNode( elem ) );

			// Convert html into DOM nodes
			} else {
				tmp = tmp || safe.appendChild( context.createElement( "div" ) );

				// Deserialize a standard representation
				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
				wrap = wrapMap[ tag ] || wrapMap._default;

				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];

				// Descend through wrappers to the right content
				j = wrap[ 0 ];
				while ( j-- ) {
					tmp = tmp.lastChild;
				}

				// Manually add leading whitespace removed by IE
				if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
					nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[ 0 ] ) );
				}

				// Remove IE's autoinserted <tbody> from table fragments
				if ( !support.tbody ) {

					// String was a <table>, *may* have spurious <tbody>
					elem = tag === "table" && !rtbody.test( elem ) ?
						tmp.firstChild :

						// String was a bare <thead> or <tfoot>
						wrap[ 1 ] === "<table>" && !rtbody.test( elem ) ?
							tmp :
							0;

					j = elem && elem.childNodes.length;
					while ( j-- ) {
						if ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), "tbody" ) &&
							!tbody.childNodes.length ) {

							elem.removeChild( tbody );
						}
					}
				}

				jQuery.merge( nodes, tmp.childNodes );

				// Fix #12392 for WebKit and IE > 9
				tmp.textContent = "";

				// Fix #12392 for oldIE
				while ( tmp.firstChild ) {
					tmp.removeChild( tmp.firstChild );
				}

				// Remember the top-level container for proper cleanup
				tmp = safe.lastChild;
			}
		}
	}

	// Fix #11356: Clear elements from fragment
	if ( tmp ) {
		safe.removeChild( tmp );
	}

	// Reset defaultChecked for any radios and checkboxes
	// about to be appended to the DOM in IE 6/7 (#8060)
	if ( !support.appendChecked ) {
		jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
	}

	i = 0;
	while ( ( elem = nodes[ i++ ] ) ) {

		// Skip elements already in the context collection (trac-4087)
		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
			if ( ignored ) {
				ignored.push( elem );
			}

			continue;
		}

		contains = jQuery.contains( elem.ownerDocument, elem );

		// Append to fragment
		tmp = getAll( safe.appendChild( elem ), "script" );

		// Preserve script evaluation history
		if ( contains ) {
			setGlobalEval( tmp );
		}

		// Capture executables
		if ( scripts ) {
			j = 0;
			while ( ( elem = tmp[ j++ ] ) ) {
				if ( rscriptType.test( elem.type || "" ) ) {
					scripts.push( elem );
				}
			}
		}
	}

	tmp = null;

	return safe;
}


( function() {
	var i, eventName,
		div = document.createElement( "div" );

	// Support: IE<9 (lack submit/change bubble), Firefox (lack focus(in | out) events)
	for ( i in { submit: true, change: true, focusin: true } ) {
		eventName = "on" + i;

		if ( !( support[ i ] = eventName in window ) ) {

			// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
			div.setAttribute( eventName, "t" );
			support[ i ] = div.attributes[ eventName ].expando === false;
		}
	}

	// Null elements to avoid leaks in IE.
	div = null;
} )();


var rformElems = /^(?:input|select|textarea)$/i,
	rkeyEvent = /^key/,
	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

// Support: IE9
// See #13393 for more info
function safeActiveElement() {
	try {
		return document.activeElement;
	} catch ( err ) { }
}

function on( elem, types, selector, data, fn, one ) {
	var origFn, type;

	// Types can be a map of types/handlers
	if ( typeof types === "object" ) {

		// ( types-Object, selector, data )
		if ( typeof selector !== "string" ) {

			// ( types-Object, data )
			data = data || selector;
			selector = undefined;
		}
		for ( type in types ) {
			on( elem, type, selector, data, types[ type ], one );
		}
		return elem;
	}

	if ( data == null && fn == null ) {

		// ( types, fn )
		fn = selector;
		data = selector = undefined;
	} else if ( fn == null ) {
		if ( typeof selector === "string" ) {

			// ( types, selector, fn )
			fn = data;
			data = undefined;
		} else {

			// ( types, data, fn )
			fn = data;
			data = selector;
			selector = undefined;
		}
	}
	if ( fn === false ) {
		fn = returnFalse;
	} else if ( !fn ) {
		return elem;
	}

	if ( one === 1 ) {
		origFn = fn;
		fn = function( event ) {

			// Can use an empty set, since event contains the info
			jQuery().off( event );
			return origFn.apply( this, arguments );
		};

		// Use same guid so caller can remove using origFn
		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
	}
	return elem.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	} );
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {
		var tmp, events, t, handleObjIn,
			special, eventHandle, handleObj,
			handlers, type, namespaces, origType,
			elemData = jQuery._data( elem );

		// Don't attach events to noData or text/comment nodes (but allow plain objects)
		if ( !elemData ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !( events = elemData.events ) ) {
			events = elemData.events = {};
		}
		if ( !( eventHandle = elemData.handle ) ) {
			eventHandle = elemData.handle = function( e ) {

				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" &&
					( !e || jQuery.event.triggered !== e.type ) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};

			// Add elem as a property of the handle fn to prevent a memory leak
			// with IE non-native events
			eventHandle.elem = elem;
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend( {
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join( "." )
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !( handlers = events[ type ] ) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener/attachEvent if the special events handler returns false
				if ( !special.setup ||
					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

					// Bind the global event handler to the element
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );

					} else if ( elem.attachEvent ) {
						elem.attachEvent( "on" + type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {
		var j, handleObj, tmp,
			origCount, t, events,
			special, handlers, type,
			namespaces, origType,
			elemData = jQuery.hasData( elem ) && jQuery._data( elem );

		if ( !elemData || !( events = elemData.events ) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[ 2 ] &&
				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector ||
						selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown ||
					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {

					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			delete elemData.handle;

			// removeData also checks for emptiness and clears the expando if empty
			// so use it instead of delete
			jQuery._removeData( elem, "events" );
		}
	},

	trigger: function( event, data, elem, onlyHandlers ) {
		var handle, ontype, cur,
			bubbleType, special, tmp, i,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];

		cur = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf( "." ) > -1 ) {

			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split( "." );
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf( ":" ) < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join( "." );
		event.rnamespace = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === ( elem.ownerDocument || document ) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {

			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] &&
				jQuery._data( cur, "handle" );

			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if (
				( !special._default ||
				 special._default.apply( eventPath.pop(), data ) === false
				) && acceptData( elem )
			) {

				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					try {
						elem[ type ]();
					} catch ( e ) {

						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	dispatch: function( event ) {

		// Make a writable jQuery.Event from the native event object
		event = jQuery.event.fix( event );

		var i, j, ret, matched, handleObj,
			handlerQueue = [],
			args = slice.call( arguments ),
			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[ 0 ] = event;
		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( ( handleObj = matched.handlers[ j++ ] ) &&
				!event.isImmediatePropagationStopped() ) {

				// Triggered event must either 1) have no namespace, or 2) have namespace(s)
				// a subset or equal to those in the bound event (both can have no namespace).
				if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
						handleObj.handler ).apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( ( event.result = ret ) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var i, matches, sel, handleObj,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Support (at least): Chrome, IE9
		// Find delegate handlers
		// Black-hole SVG <use> instance trees (#13180)
		//
		// Support: Firefox<=42+
		// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
		if ( delegateCount && cur.nodeType &&
			( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {

			/* jshint eqeqeq: false */
			for ( ; cur != this; cur = cur.parentNode || this ) {
				/* jshint eqeqeq: true */

				// Don't check non-elements (#13208)
				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
					matches = [];
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matches[ sel ] === undefined ) {
							matches[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) > -1 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matches[ sel ] ) {
							matches.push( handleObj );
						}
					}
					if ( matches.length ) {
						handlerQueue.push( { elem: cur, handlers: matches } );
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		if ( delegateCount < handlers.length ) {
			handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
		}

		return handlerQueue;
	},

	fix: function( event ) {
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop, copy,
			type = event.type,
			originalEvent = event,
			fixHook = this.fixHooks[ type ];

		if ( !fixHook ) {
			this.fixHooks[ type ] = fixHook =
				rmouseEvent.test( type ) ? this.mouseHooks :
				rkeyEvent.test( type ) ? this.keyHooks :
				{};
		}
		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		event = new jQuery.Event( originalEvent );

		i = copy.length;
		while ( i-- ) {
			prop = copy[ i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Support: IE<9
		// Fix target property (#1925)
		if ( !event.target ) {
			event.target = originalEvent.srcElement || document;
		}

		// Support: Safari 6-8+
		// Target should not be a text node (#504, #13143)
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		// Support: IE<9
		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
		event.metaKey = !!event.metaKey;

		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
	},

	// Includes some event props shared by KeyEvent and MouseEvent
	props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
		"metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),

	fixHooks: {},

	keyHooks: {
		props: "char charCode key keyCode".split( " " ),
		filter: function( event, original ) {

			// Add which for key events
			if ( event.which == null ) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	mouseHooks: {
		props: ( "button buttons clientX clientY fromElement offsetX offsetY " +
			"pageX pageY screenX screenY toElement" ).split( " " ),
		filter: function( event, original ) {
			var body, eventDoc, doc,
				button = original.button,
				fromElement = original.fromElement;

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX +
					( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
					( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY +
					( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
					( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			// Add relatedTarget, if necessary
			if ( !event.relatedTarget && fromElement ) {
				event.relatedTarget = fromElement === event.target ?
					original.toElement :
					fromElement;
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

	special: {
		load: {

			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		focus: {

			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== safeActiveElement() && this.focus ) {
					try {
						this.focus();
						return false;
					} catch ( e ) {

						// Support: IE<9
						// If we error on focus to hidden element (#1486, #12518),
						// let .trigger() run the handlers
					}
				}
			},
			delegateType: "focusin"
		},
		blur: {
			trigger: function() {
				if ( this === safeActiveElement() && this.blur ) {
					this.blur();
					return false;
				}
			},
			delegateType: "focusout"
		},
		click: {

			// For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
					this.click();
					return false;
				}
			},

			// For cross-browser consistency, don't fire native .click() on links
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	},

	// Piggyback on a donor event to simulate a different one
	simulate: function( type, elem, event ) {
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true

				// Previously, `originalEvent: {}` was set here, so stopPropagation call
				// would not be triggered on donor event, since in our own
				// jQuery.event.stopPropagation function we had a check for existence of
				// originalEvent.stopPropagation method, so, consequently it would be a noop.
				//
				// Guard for simulated events was moved to jQuery.event.stopPropagation function
				// since `originalEvent` should point to the original event for the
				// constancy with other events and for more focused logic
			}
		);

		jQuery.event.trigger( e, null, elem );

		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

jQuery.removeEvent = document.removeEventListener ?
	function( elem, type, handle ) {

		// This "if" is needed for plain objects
		if ( elem.removeEventListener ) {
			elem.removeEventListener( type, handle );
		}
	} :
	function( elem, type, handle ) {
		var name = "on" + type;

		if ( elem.detachEvent ) {

			// #8545, #7054, preventing memory leaks for custom events in IE6-8
			// detachEvent needed property on element, by name of that event,
			// to properly expose it to GC
			if ( typeof elem[ name ] === "undefined" ) {
				elem[ name ] = null;
			}

			elem.detachEvent( name, handle );
		}
	};

jQuery.Event = function( src, props ) {

	// Allow instantiation without the 'new' keyword
	if ( !( this instanceof jQuery.Event ) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&

				// Support: IE < 9, Android < 4.0
				src.returnValue === false ?
			returnTrue :
			returnFalse;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	constructor: jQuery.Event,
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;
		if ( !e ) {
			return;
		}

		// If preventDefault exists, run it on the original event
		if ( e.preventDefault ) {
			e.preventDefault();

		// Support: IE
		// Otherwise set the returnValue property of the original event to false
		} else {
			e.returnValue = false;
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( !e || this.isSimulated ) {
			return;
		}

		// If stopPropagation exists, run it on the original event
		if ( e.stopPropagation ) {
			e.stopPropagation();
		}

		// Support: IE
		// Set the cancelBubble property of the original event to true
		e.cancelBubble = true;
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && e.stopImmediatePropagation ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// Create mouseenter/leave events using mouseover/out and event-time checks
// so that event delegation works in jQuery.
// Do the same for pointerenter/pointerleave and pointerover/pointerout
//
// Support: Safari 7 only
// Safari sends mouseenter too often; see:
// https://code.google.com/p/chromium/issues/detail?id=470258
// for the description of the bug (it existed in older Chrome versions as well).
jQuery.each( {
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mouseenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
} );

// IE submit delegation
if ( !support.submit ) {

	jQuery.event.special.submit = {
		setup: function() {

			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Lazy-add a submit handler when a descendant form may potentially be submitted
			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {

				// Node name check avoids a VML-related crash in IE (#9807)
				var elem = e.target,
					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ?

						// Support: IE <=8
						// We use jQuery.prop instead of elem.form
						// to allow fixing the IE8 delegated submit issue (gh-2332)
						// by 3rd party polyfills/workarounds.
						jQuery.prop( elem, "form" ) :
						undefined;

				if ( form && !jQuery._data( form, "submit" ) ) {
					jQuery.event.add( form, "submit._submit", function( event ) {
						event._submitBubble = true;
					} );
					jQuery._data( form, "submit", true );
				}
			} );

			// return undefined since we don't need an event listener
		},

		postDispatch: function( event ) {

			// If form was submitted by the user, bubble the event up the tree
			if ( event._submitBubble ) {
				delete event._submitBubble;
				if ( this.parentNode && !event.isTrigger ) {
					jQuery.event.simulate( "submit", this.parentNode, event );
				}
			}
		},

		teardown: function() {

			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
			jQuery.event.remove( this, "._submit" );
		}
	};
}

// IE change delegation and checkbox/radio fix
if ( !support.change ) {

	jQuery.event.special.change = {

		setup: function() {

			if ( rformElems.test( this.nodeName ) ) {

				// IE doesn't fire change on a check/radio until blur; trigger it on click
				// after a propertychange. Eat the blur-change in special.change.handle.
				// This still fires onchange a second time for check/radio after blur.
				if ( this.type === "checkbox" || this.type === "radio" ) {
					jQuery.event.add( this, "propertychange._change", function( event ) {
						if ( event.originalEvent.propertyName === "checked" ) {
							this._justChanged = true;
						}
					} );
					jQuery.event.add( this, "click._change", function( event ) {
						if ( this._justChanged && !event.isTrigger ) {
							this._justChanged = false;
						}

						// Allow triggered, simulated change events (#11500)
						jQuery.event.simulate( "change", this, event );
					} );
				}
				return false;
			}

			// Delegated event; lazy-add a change handler on descendant inputs
			jQuery.event.add( this, "beforeactivate._change", function( e ) {
				var elem = e.target;

				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "change" ) ) {
					jQuery.event.add( elem, "change._change", function( event ) {
						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
							jQuery.event.simulate( "change", this.parentNode, event );
						}
					} );
					jQuery._data( elem, "change", true );
				}
			} );
		},

		handle: function( event ) {
			var elem = event.target;

			// Swallow native change events from checkbox/radio, we already triggered them above
			if ( this !== elem || event.isSimulated || event.isTrigger ||
				( elem.type !== "radio" && elem.type !== "checkbox" ) ) {

				return event.handleObj.handler.apply( this, arguments );
			}
		},

		teardown: function() {
			jQuery.event.remove( this, "._change" );

			return !rformElems.test( this.nodeName );
		}
	};
}

// Support: Firefox
// Firefox doesn't have focus(in | out) events
// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
//
// Support: Chrome, Safari
// focus(in | out) events fire after focus & blur events,
// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857
if ( !support.focusin ) {
	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler on the document while someone wants focusin/focusout
		var handler = function( event ) {
			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
		};

		jQuery.event.special[ fix ] = {
			setup: function() {
				var doc = this.ownerDocument || this,
					attaches = jQuery._data( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this,
					attaches = jQuery._data( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					jQuery._removeData( doc, fix );
				} else {
					jQuery._data( doc, fix, attaches );
				}
			}
		};
	} );
}

jQuery.fn.extend( {

	on: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn );
	},
	one: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {

			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ?
					handleObj.origType + "." + handleObj.namespace :
					handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {

			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {

			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each( function() {
			jQuery.event.remove( this, types, fn, selector );
		} );
	},

	trigger: function( type, data ) {
		return this.each( function() {
			jQuery.event.trigger( type, data, this );
		} );
	},
	triggerHandler: function( type, data ) {
		var elem = this[ 0 ];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
} );


var rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
	rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ),
	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,

	// Support: IE 10-11, Edge 10240+
	// In IE/Edge using regex groups here causes severe slowdowns.
	// See https://connect.microsoft.com/IE/feedback/details/1736512/
	rnoInnerhtml = /<script|<style|<link/i,

	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
	rscriptTypeMasked = /^true\/(.*)/,
	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
	safeFragment = createSafeFragment( document ),
	fragmentDiv = safeFragment.appendChild( document.createElement( "div" ) );

// Support: IE<8
// Manipulating tables requires a tbody
function manipulationTarget( elem, content ) {
	return jQuery.nodeName( elem, "table" ) &&
		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?

		elem.getElementsByTagName( "tbody" )[ 0 ] ||
			elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) :
		elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = ( jQuery.find.attr( elem, "type" ) !== null ) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	var match = rscriptTypeMasked.exec( elem.type );
	if ( match ) {
		elem.type = match[ 1 ];
	} else {
		elem.removeAttribute( "type" );
	}
	return elem;
}

function cloneCopyEvent( src, dest ) {
	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
		return;
	}

	var type, i, l,
		oldData = jQuery._data( src ),
		curData = jQuery._data( dest, oldData ),
		events = oldData.events;

	if ( events ) {
		delete curData.handle;
		curData.events = {};

		for ( type in events ) {
			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
				jQuery.event.add( dest, type, events[ type ][ i ] );
			}
		}
	}

	// make the cloned public data object a copy from the original
	if ( curData.data ) {
		curData.data = jQuery.extend( {}, curData.data );
	}
}

function fixCloneNodeIssues( src, dest ) {
	var nodeName, e, data;

	// We do not need to do anything for non-Elements
	if ( dest.nodeType !== 1 ) {
		return;
	}

	nodeName = dest.nodeName.toLowerCase();

	// IE6-8 copies events bound via attachEvent when using cloneNode.
	if ( !support.noCloneEvent && dest[ jQuery.expando ] ) {
		data = jQuery._data( dest );

		for ( e in data.events ) {
			jQuery.removeEvent( dest, e, data.handle );
		}

		// Event data gets referenced instead of copied if the expando gets copied too
		dest.removeAttribute( jQuery.expando );
	}

	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
	if ( nodeName === "script" && dest.text !== src.text ) {
		disableScript( dest ).text = src.text;
		restoreScript( dest );

	// IE6-10 improperly clones children of object elements using classid.
	// IE10 throws NoModificationAllowedError if parent is null, #12132.
	} else if ( nodeName === "object" ) {
		if ( dest.parentNode ) {
			dest.outerHTML = src.outerHTML;
		}

		// This path appears unavoidable for IE9. When cloning an object
		// element in IE9, the outerHTML strategy above is not sufficient.
		// If the src has innerHTML and the destination does not,
		// copy the src.innerHTML into the dest.innerHTML. #10324
		if ( support.html5Clone && ( src.innerHTML && !jQuery.trim( dest.innerHTML ) ) ) {
			dest.innerHTML = src.innerHTML;
		}

	} else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {

		// IE6-8 fails to persist the checked state of a cloned checkbox
		// or radio button. Worse, IE6-7 fail to give the cloned element
		// a checked appearance if the defaultChecked value isn't also set

		dest.defaultChecked = dest.checked = src.checked;

		// IE6-7 get confused and end up setting the value of a cloned
		// checkbox/radio button to an empty string instead of "on"
		if ( dest.value !== src.value ) {
			dest.value = src.value;
		}

	// IE6-8 fails to return the selected option to the default selected
	// state when cloning options
	} else if ( nodeName === "option" ) {
		dest.defaultSelected = dest.selected = src.defaultSelected;

	// IE6-8 fails to set the defaultValue to the correct value when
	// cloning other types of input fields
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

function domManip( collection, args, callback, ignored ) {

	// Flatten any nested arrays
	args = concat.apply( [], args );

	var first, node, hasScripts,
		scripts, doc, fragment,
		i = 0,
		l = collection.length,
		iNoClone = l - 1,
		value = args[ 0 ],
		isFunction = jQuery.isFunction( value );

	// We can't cloneNode fragments that contain checked, in WebKit
	if ( isFunction ||
			( l > 1 && typeof value === "string" &&
				!support.checkClone && rchecked.test( value ) ) ) {
		return collection.each( function( index ) {
			var self = collection.eq( index );
			if ( isFunction ) {
				args[ 0 ] = value.call( this, index, self.html() );
			}
			domManip( self, args, callback, ignored );
		} );
	}

	if ( l ) {
		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
		first = fragment.firstChild;

		if ( fragment.childNodes.length === 1 ) {
			fragment = first;
		}

		// Require either new content or an interest in ignored elements to invoke the callback
		if ( first || ignored ) {
			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
			hasScripts = scripts.length;

			// Use the original fragment for the last item
			// instead of the first because it can end up
			// being emptied incorrectly in certain situations (#8070).
			for ( ; i < l; i++ ) {
				node = fragment;

				if ( i !== iNoClone ) {
					node = jQuery.clone( node, true, true );

					// Keep references to cloned scripts for later restoration
					if ( hasScripts ) {

						// Support: Android<4.1, PhantomJS<2
						// push.apply(_, arraylike) throws on ancient WebKit
						jQuery.merge( scripts, getAll( node, "script" ) );
					}
				}

				callback.call( collection[ i ], node, i );
			}

			if ( hasScripts ) {
				doc = scripts[ scripts.length - 1 ].ownerDocument;

				// Reenable scripts
				jQuery.map( scripts, restoreScript );

				// Evaluate executable scripts on first document insertion
				for ( i = 0; i < hasScripts; i++ ) {
					node = scripts[ i ];
					if ( rscriptType.test( node.type || "" ) &&
						!jQuery._data( node, "globalEval" ) &&
						jQuery.contains( doc, node ) ) {

						if ( node.src ) {

							// Optional AJAX dependency, but won't run scripts if not present
							if ( jQuery._evalUrl ) {
								jQuery._evalUrl( node.src );
							}
						} else {
							jQuery.globalEval(
								( node.text || node.textContent || node.innerHTML || "" )
									.replace( rcleanScript, "" )
							);
						}
					}
				}
			}

			// Fix #11809: Avoid leaking memory
			fragment = first = null;
		}
	}

	return collection;
}

function remove( elem, selector, keepData ) {
	var node,
		elems = selector ? jQuery.filter( selector, elem ) : elem,
		i = 0;

	for ( ; ( node = elems[ i ] ) != null; i++ ) {

		if ( !keepData && node.nodeType === 1 ) {
			jQuery.cleanData( getAll( node ) );
		}

		if ( node.parentNode ) {
			if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
				setGlobalEval( getAll( node, "script" ) );
			}
			node.parentNode.removeChild( node );
		}
	}

	return elem;
}

jQuery.extend( {
	htmlPrefilter: function( html ) {
		return html.replace( rxhtmlTag, "<$1></$2>" );
	},

	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var destElements, node, clone, i, srcElements,
			inPage = jQuery.contains( elem.ownerDocument, elem );

		if ( support.html5Clone || jQuery.isXMLDoc( elem ) ||
			!rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {

			clone = elem.cloneNode( true );

		// IE<=8 does not properly clone detached, unknown element nodes
		} else {
			fragmentDiv.innerHTML = elem.outerHTML;
			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
		}

		if ( ( !support.noCloneEvent || !support.noCloneChecked ) &&
				( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) {

			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			// Fix all IE cloning issues
			for ( i = 0; ( node = srcElements[ i ] ) != null; ++i ) {

				// Ensure that the destination node is not null; Fixes #9587
				if ( destElements[ i ] ) {
					fixCloneNodeIssues( node, destElements[ i ] );
				}
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0; ( node = srcElements[ i ] ) != null; i++ ) {
					cloneCopyEvent( node, destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		destElements = srcElements = node = null;

		// Return the cloned set
		return clone;
	},

	cleanData: function( elems, /* internal */ forceAcceptData ) {
		var elem, type, id, data,
			i = 0,
			internalKey = jQuery.expando,
			cache = jQuery.cache,
			attributes = support.attributes,
			special = jQuery.event.special;

		for ( ; ( elem = elems[ i ] ) != null; i++ ) {
			if ( forceAcceptData || acceptData( elem ) ) {

				id = elem[ internalKey ];
				data = id && cache[ id ];

				if ( data ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}

					// Remove cache only if it was not already removed by jQuery.event.remove
					if ( cache[ id ] ) {

						delete cache[ id ];

						// Support: IE<9
						// IE does not allow us to delete expando properties from nodes
						// IE creates expando attributes along with the property
						// IE does not have a removeAttribute function on Document nodes
						if ( !attributes && typeof elem.removeAttribute !== "undefined" ) {
							elem.removeAttribute( internalKey );

						// Webkit & Blink performance suffers when deleting properties
						// from DOM nodes, so set to undefined instead
						// https://code.google.com/p/chromium/issues/detail?id=378607
						} else {
							elem[ internalKey ] = undefined;
						}

						deletedIds.push( id );
					}
				}
			}
		}
	}
} );

jQuery.fn.extend( {

	// Keep domManip exposed until 3.0 (gh-2225)
	domManip: domManip,

	detach: function( selector ) {
		return remove( this, selector, true );
	},

	remove: function( selector ) {
		return remove( this, selector );
	},

	text: function( value ) {
		return access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().append(
					( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value )
				);
		}, null, value, arguments.length );
	},

	append: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.appendChild( elem );
			}
		} );
	},

	prepend: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.insertBefore( elem, target.firstChild );
			}
		} );
	},

	before: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		} );
	},

	after: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		} );
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; ( elem = this[ i ] ) != null; i++ ) {

			// Remove element nodes and prevent memory leaks
			if ( elem.nodeType === 1 ) {
				jQuery.cleanData( getAll( elem, false ) );
			}

			// Remove any remaining nodes
			while ( elem.firstChild ) {
				elem.removeChild( elem.firstChild );
			}

			// If this is a select, ensure that it displays empty (#12336)
			// Support: IE<9
			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
				elem.options.length = 0;
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map( function() {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		} );
	},

	html: function( value ) {
		return access( this, function( value ) {
			var elem = this[ 0 ] || {},
				i = 0,
				l = this.length;

			if ( value === undefined ) {
				return elem.nodeType === 1 ?
					elem.innerHTML.replace( rinlinejQuery, "" ) :
					undefined;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				( support.htmlSerialize || !rnoshimcache.test( value )  ) &&
				( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = jQuery.htmlPrefilter( value );

				try {
					for ( ; i < l; i++ ) {

						// Remove element nodes and prevent memory leaks
						elem = this[ i ] || {};
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch ( e ) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function() {
		var ignored = [];

		// Make the changes, replacing each non-ignored context element with the new content
		return domManip( this, arguments, function( elem ) {
			var parent = this.parentNode;

			if ( jQuery.inArray( this, ignored ) < 0 ) {
				jQuery.cleanData( getAll( this ) );
				if ( parent ) {
					parent.replaceChild( elem, this );
				}
			}

		// Force callback invocation
		}, ignored );
	}
} );

jQuery.each( {
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			i = 0,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
} );


var iframe,
	elemdisplay = {

		// Support: Firefox
		// We have to pre-define these values for FF (#10227)
		HTML: "block",
		BODY: "block"
	};

/**
 * Retrieve the actual display of a element
 * @param {String} name nodeName of the element
 * @param {Object} doc Document object
 */

// Called only from within defaultDisplay
function actualDisplay( name, doc ) {
	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),

		display = jQuery.css( elem[ 0 ], "display" );

	// We don't have any data stored on the element,
	// so use "detach" method as fast way to get rid of the element
	elem.detach();

	return display;
}

/**
 * Try to determine the default display value of an element
 * @param {String} nodeName
 */
function defaultDisplay( nodeName ) {
	var doc = document,
		display = elemdisplay[ nodeName ];

	if ( !display ) {
		display = actualDisplay( nodeName, doc );

		// If the simple way fails, read from inside an iframe
		if ( display === "none" || !display ) {

			// Use the already-created iframe if possible
			iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) )
				.appendTo( doc.documentElement );

			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
			doc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;

			// Support: IE
			doc.write();
			doc.close();

			display = actualDisplay( nodeName, doc );
			iframe.detach();
		}

		// Store the correct default display
		elemdisplay[ nodeName ] = display;
	}

	return display;
}
var rmargin = ( /^margin/ );

var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

var swap = function( elem, options, callback, args ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	for ( name in options ) {
		old[ name ] = elem.style[ name ];
		elem.style[ name ] = options[ name ];
	}

	ret = callback.apply( elem, args || [] );

	// Revert the old values
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
};


var documentElement = document.documentElement;



( function() {
	var pixelPositionVal, pixelMarginRightVal, boxSizingReliableVal,
		reliableHiddenOffsetsVal, reliableMarginRightVal, reliableMarginLeftVal,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	// Finish early in limited (non-browser) environments
	if ( !div.style ) {
		return;
	}

	div.style.cssText = "float:left;opacity:.5";

	// Support: IE<9
	// Make sure that element opacity exists (as opposed to filter)
	support.opacity = div.style.opacity === "0.5";

	// Verify style float existence
	// (IE uses styleFloat instead of cssFloat)
	support.cssFloat = !!div.style.cssFloat;

	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	container = document.createElement( "div" );
	container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
		"padding:0;margin-top:1px;position:absolute";
	div.innerHTML = "";
	container.appendChild( div );

	// Support: Firefox<29, Android 2.3
	// Vendor-prefix box-sizing
	support.boxSizing = div.style.boxSizing === "" || div.style.MozBoxSizing === "" ||
		div.style.WebkitBoxSizing === "";

	jQuery.extend( support, {
		reliableHiddenOffsets: function() {
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return reliableHiddenOffsetsVal;
		},

		boxSizingReliable: function() {

			// We're checking for pixelPositionVal here instead of boxSizingReliableVal
			// since that compresses better and they're computed together anyway.
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return boxSizingReliableVal;
		},

		pixelMarginRight: function() {

			// Support: Android 4.0-4.3
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return pixelMarginRightVal;
		},

		pixelPosition: function() {
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return pixelPositionVal;
		},

		reliableMarginRight: function() {

			// Support: Android 2.3
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return reliableMarginRightVal;
		},

		reliableMarginLeft: function() {

			// Support: IE <=8 only, Android 4.0 - 4.3 only, Firefox <=3 - 37
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return reliableMarginLeftVal;
		}
	} );

	function computeStyleTests() {
		var contents, divStyle,
			documentElement = document.documentElement;

		// Setup
		documentElement.appendChild( container );

		div.style.cssText =

			// Support: Android 2.3
			// Vendor-prefix box-sizing
			"-webkit-box-sizing:border-box;box-sizing:border-box;" +
			"position:relative;display:block;" +
			"margin:auto;border:1px;padding:1px;" +
			"top:1%;width:50%";

		// Support: IE<9
		// Assume reasonable values in the absence of getComputedStyle
		pixelPositionVal = boxSizingReliableVal = reliableMarginLeftVal = false;
		pixelMarginRightVal = reliableMarginRightVal = true;

		// Check for getComputedStyle so that this code is not run in IE<9.
		if ( window.getComputedStyle ) {
			divStyle = window.getComputedStyle( div );
			pixelPositionVal = ( divStyle || {} ).top !== "1%";
			reliableMarginLeftVal = ( divStyle || {} ).marginLeft === "2px";
			boxSizingReliableVal = ( divStyle || { width: "4px" } ).width === "4px";

			// Support: Android 4.0 - 4.3 only
			// Some styles come back with percentage values, even though they shouldn't
			div.style.marginRight = "50%";
			pixelMarginRightVal = ( divStyle || { marginRight: "4px" } ).marginRight === "4px";

			// Support: Android 2.3 only
			// Div with explicit width and no margin-right incorrectly
			// gets computed margin-right based on width of container (#3333)
			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
			contents = div.appendChild( document.createElement( "div" ) );

			// Reset CSS: box-sizing; display; margin; border; padding
			contents.style.cssText = div.style.cssText =

				// Support: Android 2.3
				// Vendor-prefix box-sizing
				"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
				"box-sizing:content-box;display:block;margin:0;border:0;padding:0";
			contents.style.marginRight = contents.style.width = "0";
			div.style.width = "1px";

			reliableMarginRightVal =
				!parseFloat( ( window.getComputedStyle( contents ) || {} ).marginRight );

			div.removeChild( contents );
		}

		// Support: IE6-8
		// First check that getClientRects works as expected
		// Check if table cells still have offsetWidth/Height when they are set
		// to display:none and there are still other visible table cells in a
		// table row; if so, offsetWidth/Height are not reliable for use when
		// determining if an element has been hidden directly using
		// display:none (it is still safe to use offsets if a parent element is
		// hidden; don safety goggles and see bug #4512 for more information).
		div.style.display = "none";
		reliableHiddenOffsetsVal = div.getClientRects().length === 0;
		if ( reliableHiddenOffsetsVal ) {
			div.style.display = "";
			div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
			div.childNodes[ 0 ].style.borderCollapse = "separate";
			contents = div.getElementsByTagName( "td" );
			contents[ 0 ].style.cssText = "margin:0;border:0;padding:0;display:none";
			reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
			if ( reliableHiddenOffsetsVal ) {
				contents[ 0 ].style.display = "";
				contents[ 1 ].style.display = "none";
				reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
			}
		}

		// Teardown
		documentElement.removeChild( container );
	}

} )();


var getStyles, curCSS,
	rposition = /^(top|right|bottom|left)$/;

if ( window.getComputedStyle ) {
	getStyles = function( elem ) {

		// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
		// IE throws on elements created in popups
		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
		var view = elem.ownerDocument.defaultView;

		if ( !view || !view.opener ) {
			view = window;
		}

		return view.getComputedStyle( elem );
	};

	curCSS = function( elem, name, computed ) {
		var width, minWidth, maxWidth, ret,
			style = elem.style;

		computed = computed || getStyles( elem );

		// getPropertyValue is only needed for .css('filter') in IE9, see #12537
		ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;

		// Support: Opera 12.1x only
		// Fall back to style even without computed
		// computed is undefined for elems on document fragments
		if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
			ret = jQuery.style( elem, name );
		}

		if ( computed ) {

			// A tribute to the "awesome hack by Dean Edwards"
			// Chrome < 17 and Safari 5.0 uses "computed value"
			// instead of "used value" for margin-right
			// Safari 5.1.7 (at least) returns percentage for a larger set of values,
			// but width seems to be reliably pixels
			// this is against the CSSOM draft spec:
			// http://dev.w3.org/csswg/cssom/#resolved-values
			if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {

				// Remember the original values
				width = style.width;
				minWidth = style.minWidth;
				maxWidth = style.maxWidth;

				// Put in the new values to get a computed value out
				style.minWidth = style.maxWidth = style.width = ret;
				ret = computed.width;

				// Revert the changed values
				style.width = width;
				style.minWidth = minWidth;
				style.maxWidth = maxWidth;
			}
		}

		// Support: IE
		// IE returns zIndex value as an integer.
		return ret === undefined ?
			ret :
			ret + "";
	};
} else if ( documentElement.currentStyle ) {
	getStyles = function( elem ) {
		return elem.currentStyle;
	};

	curCSS = function( elem, name, computed ) {
		var left, rs, rsLeft, ret,
			style = elem.style;

		computed = computed || getStyles( elem );
		ret = computed ? computed[ name ] : undefined;

		// Avoid setting ret to empty string here
		// so we don't default to auto
		if ( ret == null && style && style[ name ] ) {
			ret = style[ name ];
		}

		// From the awesome hack by Dean Edwards
		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

		// If we're not dealing with a regular pixel number
		// but a number that has a weird ending, we need to convert it to pixels
		// but not position css attributes, as those are
		// proportional to the parent element instead
		// and we can't measure the parent instead because it
		// might trigger a "stacking dolls" problem
		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {

			// Remember the original values
			left = style.left;
			rs = elem.runtimeStyle;
			rsLeft = rs && rs.left;

			// Put in the new values to get a computed value out
			if ( rsLeft ) {
				rs.left = elem.currentStyle.left;
			}
			style.left = name === "fontSize" ? "1em" : ret;
			ret = style.pixelLeft + "px";

			// Revert the changed values
			style.left = left;
			if ( rsLeft ) {
				rs.left = rsLeft;
			}
		}

		// Support: IE
		// IE returns zIndex value as an integer.
		return ret === undefined ?
			ret :
			ret + "" || "auto";
	};
}




function addGetHookIf( conditionFn, hookFn ) {

	// Define the hook, we'll check on the first run if it's really needed.
	return {
		get: function() {
			if ( conditionFn() ) {

				// Hook not needed (or it's not possible to use it due
				// to missing dependency), remove it.
				delete this.get;
				return;
			}

			// Hook needed; redefine it so that the support test is not executed again.
			return ( this.get = hookFn ).apply( this, arguments );
		}
	};
}


var

		ralpha = /alpha\([^)]*\)/i,
	ropacity = /opacity\s*=\s*([^)]*)/i,

	// swappable if display is none or starts with table except
	// "table", "table-cell", or "table-caption"
	// see here for display values:
	// https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),

	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	},

	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
	emptyStyle = document.createElement( "div" ).style;


// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( name ) {

	// shortcut for names that are not vendor prefixed
	if ( name in emptyStyle ) {
		return name;
	}

	// check for vendor prefixed names
	var capName = name.charAt( 0 ).toUpperCase() + name.slice( 1 ),
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in emptyStyle ) {
			return name;
		}
	}
}

function showHide( elements, show ) {
	var display, elem, hidden,
		values = [],
		index = 0,
		length = elements.length;

	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		values[ index ] = jQuery._data( elem, "olddisplay" );
		display = elem.style.display;
		if ( show ) {

			// Reset the inline display of this element to learn if it is
			// being hidden by cascaded rules or not
			if ( !values[ index ] && display === "none" ) {
				elem.style.display = "";
			}

			// Set elements which have been overridden with display: none
			// in a stylesheet to whatever the default browser style is
			// for such an element
			if ( elem.style.display === "" && isHidden( elem ) ) {
				values[ index ] =
					jQuery._data( elem, "olddisplay", defaultDisplay( elem.nodeName ) );
			}
		} else {
			hidden = isHidden( elem );

			if ( display && display !== "none" || !hidden ) {
				jQuery._data(
					elem,
					"olddisplay",
					hidden ? display : jQuery.css( elem, "display" )
				);
			}
		}
	}

	// Set the display of most of the elements in a second loop
	// to avoid the constant reflow
	for ( index = 0; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}
		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
			elem.style.display = show ? values[ index ] || "" : "none";
		}
	}

	return elements;
}

function setPositiveNumber( elem, value, subtract ) {
	var matches = rnumsplit.exec( value );
	return matches ?

		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
		value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
	var i = extra === ( isBorderBox ? "border" : "content" ) ?

		// If we already have the right measurement, avoid augmentation
		4 :

		// Otherwise initialize for horizontal or vertical properties
		name === "width" ? 1 : 0,

		val = 0;

	for ( ; i < 4; i += 2 ) {

		// both box models exclude margin, so add it if we want it
		if ( extra === "margin" ) {
			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
		}

		if ( isBorderBox ) {

			// border-box includes padding, so remove it if we want content
			if ( extra === "content" ) {
				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// at this point, extra isn't border nor margin, so remove border
			if ( extra !== "margin" ) {
				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		} else {

			// at this point, extra isn't content, so add padding
			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// at this point, extra isn't content nor padding, so add border
			if ( extra !== "padding" ) {
				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	return val;
}

function getWidthOrHeight( elem, name, extra ) {

	// Start with offset property, which is equivalent to the border-box value
	var valueIsBorderBox = true,
		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
		styles = getStyles( elem ),
		isBorderBox = support.boxSizing &&
			jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

	// some non-html elements return undefined for offsetWidth, so check for null/undefined
	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
	if ( val <= 0 || val == null ) {

		// Fall back to computed then uncomputed css if necessary
		val = curCSS( elem, name, styles );
		if ( val < 0 || val == null ) {
			val = elem.style[ name ];
		}

		// Computed unit is not pixels. Stop here and return.
		if ( rnumnonpx.test( val ) ) {
			return val;
		}

		// we need the check for style in case a browser which returns unreliable values
		// for getComputedStyle silently falls back to the reliable elem.style
		valueIsBorderBox = isBorderBox &&
			( support.boxSizingReliable() || val === elem.style[ name ] );

		// Normalize "", auto, and prepare for extra
		val = parseFloat( val ) || 0;
	}

	// use the active box-sizing model to add/subtract irrelevant styles
	return ( val +
		augmentWidthOrHeight(
			elem,
			name,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles
		)
	) + "px";
}

jQuery.extend( {

	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {

					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Don't automatically add "px" to these possibly-unitless properties
	cssNumber: {
		"animationIterationCount": true,
		"columnCount": true,
		"fillOpacity": true,
		"flexGrow": true,
		"flexShrink": true,
		"fontWeight": true,
		"lineHeight": true,
		"opacity": true,
		"order": true,
		"orphans": true,
		"widows": true,
		"zIndex": true,
		"zoom": true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {

		// normalize float css property
		"float": support.cssFloat ? "cssFloat" : "styleFloat"
	},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {

		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = jQuery.camelCase( name ),
			style = elem.style;

		name = jQuery.cssProps[ origName ] ||
			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// Convert "+=" or "-=" to relative numbers (#7345)
			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
				value = adjustCSS( elem, name, ret );

				// Fixes bug #9237
				type = "number";
			}

			// Make sure that null and NaN values aren't set. See: #7116
			if ( value == null || value !== value ) {
				return;
			}

			// If a number was passed in, add the unit (except for certain CSS properties)
			if ( type === "number" ) {
				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
			}

			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
			// but it would mean to define eight
			// (for every problematic property) identical functions
			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !( "set" in hooks ) ||
				( value = hooks.set( elem, value, extra ) ) !== undefined ) {

				// Support: IE
				// Swallow errors from 'invalid' CSS values (#5509)
				try {
					style[ name ] = value;
				} catch ( e ) {}
			}

		} else {

			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks &&
				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var num, val, hooks,
			origName = jQuery.camelCase( name );

		// Make sure that we're working with the right name
		name = jQuery.cssProps[ origName ] ||
			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		//convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Return, converting to number if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || isFinite( num ) ? num || 0 : val;
		}
		return val;
	}
} );

jQuery.each( [ "height", "width" ], function( i, name ) {
	jQuery.cssHooks[ name ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {

				// certain elements can have dimension info if we invisibly show them
				// however, it must have a current display style that would benefit from this
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
					elem.offsetWidth === 0 ?
						swap( elem, cssShow, function() {
							return getWidthOrHeight( elem, name, extra );
						} ) :
						getWidthOrHeight( elem, name, extra );
			}
		},

		set: function( elem, value, extra ) {
			var styles = extra && getStyles( elem );
			return setPositiveNumber( elem, value, extra ?
				augmentWidthOrHeight(
					elem,
					name,
					extra,
					support.boxSizing &&
						jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
					styles
				) : 0
			);
		}
	};
} );

if ( !support.opacity ) {
	jQuery.cssHooks.opacity = {
		get: function( elem, computed ) {

			// IE uses filters for opacity
			return ropacity.test( ( computed && elem.currentStyle ?
				elem.currentStyle.filter :
				elem.style.filter ) || "" ) ?
					( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
					computed ? "1" : "";
		},

		set: function( elem, value ) {
			var style = elem.style,
				currentStyle = elem.currentStyle,
				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
				filter = currentStyle && currentStyle.filter || style.filter || "";

			// IE has trouble with opacity if it does not have layout
			// Force it by setting the zoom level
			style.zoom = 1;

			// if setting opacity to 1, and no other filters exist -
			// attempt to remove filter attribute #6652
			// if value === "", then remove inline opacity #12685
			if ( ( value >= 1 || value === "" ) &&
					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
					style.removeAttribute ) {

				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
				// if "filter:" is present at all, clearType is disabled, we want to avoid this
				// style.removeAttribute is IE Only, but so apparently is this code path...
				style.removeAttribute( "filter" );

				// if there is no filter style applied in a css rule
				// or unset inline opacity, we are done
				if ( value === "" || currentStyle && !currentStyle.filter ) {
					return;
				}
			}

			// otherwise, set new filter values
			style.filter = ralpha.test( filter ) ?
				filter.replace( ralpha, opacity ) :
				filter + " " + opacity;
		}
	};
}

jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
	function( elem, computed ) {
		if ( computed ) {
			return swap( elem, { "display": "inline-block" },
				curCSS, [ elem, "marginRight" ] );
		}
	}
);

jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
	function( elem, computed ) {
		if ( computed ) {
			return (
				parseFloat( curCSS( elem, "marginLeft" ) ) ||

				// Support: IE<=11+
				// Running getBoundingClientRect on a disconnected node in IE throws an error
				// Support: IE8 only
				// getClientRects() errors on disconnected elems
				( jQuery.contains( elem.ownerDocument, elem ) ?
					elem.getBoundingClientRect().left -
						swap( elem, { marginLeft: 0 }, function() {
							return elem.getBoundingClientRect().left;
						} ) :
					0
				)
			) + "px";
		}
	}
);

// These hooks are used by animate to expand properties
jQuery.each( {
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// assumes a single number if not a string
				parts = typeof value === "string" ? value.split( " " ) : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( !rmargin.test( prefix ) ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
} );

jQuery.fn.extend( {
	css: function( name, value ) {
		return access( this, function( elem, name, value ) {
			var styles, len,
				map = {},
				i = 0;

			if ( jQuery.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	},
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		if ( typeof state === "boolean" ) {
			return state ? this.show() : this.hide();
		}

		return this.each( function() {
			if ( isHidden( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		} );
	}
} );


function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || jQuery.easing._default;
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			// Use a property on the element directly when it is not a DOM element,
			// or when there is no matching style property that exists.
			if ( tween.elem.nodeType !== 1 ||
				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
				return tween.elem[ tween.prop ];
			}

			// passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails
			// so, simple values such as "10px" are parsed to Float.
			// complex values such as "rotate(1rad)" are returned as is.
			result = jQuery.css( tween.elem, tween.prop, "" );

			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {

			// use step hook for back compat - use cssHook if its there - use .style if its
			// available and use plain properties where available
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.nodeType === 1 &&
				( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
					jQuery.cssHooks[ tween.prop ] ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Support: IE <=9
// Panic based approach to setting things on disconnected nodes

Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p * Math.PI ) / 2;
	},
	_default: "swing"
};

jQuery.fx = Tween.prototype.init;

// Back Compat <1.8 extension point
jQuery.fx.step = {};




var
	fxNow, timerId,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rrun = /queueHooks$/;

// Animations created synchronously will run synchronously
function createFxNow() {
	window.setTimeout( function() {
		fxNow = undefined;
	} );
	return ( fxNow = jQuery.now() );
}

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		attrs = { height: type },
		i = 0;

	// if we include width, step value is 1 to do all cssExpand values,
	// if we don't include width, step value is 2 to skip over Left and Right
	includeWidth = includeWidth ? 1 : 0;
	for ( ; i < 4 ; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

function createTween( value, prop, animation ) {
	var tween,
		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
		index = 0,
		length = collection.length;
	for ( ; index < length; index++ ) {
		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {

			// we're done with this property
			return tween;
		}
	}
}

function defaultPrefilter( elem, props, opts ) {
	/* jshint validthis: true */
	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHidden( elem ),
		dataShow = jQuery._data( elem, "fxshow" );

	// handle queue: false promises
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always( function() {

			// doing this makes sure that the complete handler will be called
			// before this completes
			anim.always( function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			} );
		} );
	}

	// height/width overflow pass
	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {

		// Make sure that nothing sneaks out
		// Record all 3 overflow attributes because IE does not
		// change the overflow attribute when overflowX and
		// overflowY are set to the same value
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Set display property to inline-block for height/width
		// animations on inline elements that are having width/height animated
		display = jQuery.css( elem, "display" );

		// Test default display if display is currently "none"
		checkDisplay = display === "none" ?
			jQuery._data( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;

		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {

			// inline-level elements accept inline-block;
			// block-level elements need to be inline with layout
			if ( !support.inlineBlockNeedsLayout || defaultDisplay( elem.nodeName ) === "inline" ) {
				style.display = "inline-block";
			} else {
				style.zoom = 1;
			}
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		if ( !support.shrinkWrapBlocks() ) {
			anim.always( function() {
				style.overflow = opts.overflow[ 0 ];
				style.overflowX = opts.overflow[ 1 ];
				style.overflowY = opts.overflow[ 2 ];
			} );
		}
	}

	// show/hide pass
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.exec( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// If there is dataShow left over from a stopped hide or show
				// and we are going to proceed with show, we should pretend to be hidden
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );

		// Any non-fx value stops us from restoring the original display value
		} else {
			display = undefined;
		}
	}

	if ( !jQuery.isEmptyObject( orig ) ) {
		if ( dataShow ) {
			if ( "hidden" in dataShow ) {
				hidden = dataShow.hidden;
			}
		} else {
			dataShow = jQuery._data( elem, "fxshow", {} );
		}

		// store state if its toggle - enables .stop().toggle() to "reverse"
		if ( toggle ) {
			dataShow.hidden = !hidden;
		}
		if ( hidden ) {
			jQuery( elem ).show();
		} else {
			anim.done( function() {
				jQuery( elem ).hide();
			} );
		}
		anim.done( function() {
			var prop;
			jQuery._removeData( elem, "fxshow" );
			for ( prop in orig ) {
				jQuery.style( elem, prop, orig[ prop ] );
			}
		} );
		for ( prop in orig ) {
			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );

			if ( !( prop in dataShow ) ) {
				dataShow[ prop ] = tween.start;
				if ( hidden ) {
					tween.end = tween.start;
					tween.start = prop === "width" || prop === "height" ? 1 : 0;
				}
			}
		}

	// If this is a noop like .hide().hide(), restore an overwritten display value
	} else if ( ( display === "none" ? defaultDisplay( elem.nodeName ) : display ) === "inline" ) {
		style.display = display;
	}
}

function propFilter( props, specialEasing ) {
	var index, name, easing, value, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = jQuery.camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( jQuery.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// not quite $.extend, this wont overwrite keys already present.
			// also - reusing 'index' from above because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = Animation.prefilters.length,
		deferred = jQuery.Deferred().always( function() {

			// don't match elem in the :animated selector
			delete tick.elem;
		} ),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),

				// Support: Android 2.3
				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length ; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ] );

			if ( percent < 1 && length ) {
				return remaining;
			} else {
				deferred.resolveWith( elem, [ animation ] );
				return false;
			}
		},
		animation = deferred.promise( {
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, {
				specialEasing: {},
				easing: jQuery.easing._default
			}, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,

					// if we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length ; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// resolve when we played the last frame
				// otherwise, reject
				if ( gotoEnd ) {
					deferred.notifyWith( elem, [ animation, 1, 0 ] );
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		} ),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length ; index++ ) {
		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			if ( jQuery.isFunction( result.stop ) ) {
				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
					jQuery.proxy( result.stop, result );
			}
			return result;
		}
	}

	jQuery.map( props, createTween, animation );

	if ( jQuery.isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		} )
	);

	// attach callbacks from options
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}

jQuery.Animation = jQuery.extend( Animation, {

	tweeners: {
		"*": [ function( prop, value ) {
			var tween = this.createTween( prop, value );
			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
			return tween;
		} ]
	},

	tweener: function( props, callback ) {
		if ( jQuery.isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.match( rnotwhite );
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length ; index++ ) {
			prop = props[ index ];
			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
			Animation.tweeners[ prop ].unshift( callback );
		}
	},

	prefilters: [ defaultPrefilter ],

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			Animation.prefilters.unshift( callback );
		} else {
			Animation.prefilters.push( callback );
		}
	}
} );

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			jQuery.isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
	};

	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
		opt.duration in jQuery.fx.speeds ?
			jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;

	// normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( jQuery.isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.fn.extend( {
	fadeTo: function( speed, to, easing, callback ) {

		// show any hidden elements after setting opacity to 0
		return this.filter( isHidden ).css( "opacity", 0 ).show()

			// animate to the value specified
			.end().animate( { opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {

				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );

				// Empty animations, or finishing resolves immediately
				if ( empty || jQuery._data( this, "finish" ) ) {
					anim.stop( true );
				}
			};
			doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue && type !== false ) {
			this.queue( type || "fx", [] );
		}

		return this.each( function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = jQuery._data( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this &&
					( type == null || timers[ index ].queue === type ) ) {

					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// start the next in the queue if the last step wasn't forced
			// timers currently will call their complete callbacks, which will dequeue
			// but only if they were gotoEnd
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		} );
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each( function() {
			var index,
				data = jQuery._data( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// enable finishing flag on private data
			data.finish = true;

			// empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.stop ) {
				hooks.stop.call( this, true );
			}

			// look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// turn off finishing flag
			delete data.finish;
		} );
	}
} );

jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
} );

// Generate shortcuts for custom animations
jQuery.each( {
	slideDown: genFx( "show" ),
	slideUp: genFx( "hide" ),
	slideToggle: genFx( "toggle" ),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
} );

jQuery.timers = [];
jQuery.fx.tick = function() {
	var timer,
		timers = jQuery.timers,
		i = 0;

	fxNow = jQuery.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];

		// Checks the timer has not already been removed
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	if ( timer() ) {
		jQuery.fx.start();
	} else {
		jQuery.timers.pop();
	}
};

jQuery.fx.interval = 13;

jQuery.fx.start = function() {
	if ( !timerId ) {
		timerId = window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
	}
};

jQuery.fx.stop = function() {
	window.clearInterval( timerId );
	timerId = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,

	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
// http://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
jQuery.fn.delay = function( time, type ) {
	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
	type = type || "fx";

	return this.queue( type, function( next, hooks ) {
		var timeout = window.setTimeout( next, time );
		hooks.stop = function() {
			window.clearTimeout( timeout );
		};
	} );
};


( function() {
	var a,
		input = document.createElement( "input" ),
		div = document.createElement( "div" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	// Setup
	div = document.createElement( "div" );
	div.setAttribute( "className", "t" );
	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
	a = div.getElementsByTagName( "a" )[ 0 ];

	// Support: Windows Web Apps (WWA)
	// `type` must use .setAttribute for WWA (#14901)
	input.setAttribute( "type", "checkbox" );
	div.appendChild( input );

	a = div.getElementsByTagName( "a" )[ 0 ];

	// First batch of tests.
	a.style.cssText = "top:1px";

	// Test setAttribute on camelCase class.
	// If it works, we need attrFixes when doing get/setAttribute (ie6/7)
	support.getSetAttribute = div.className !== "t";

	// Get the style information from getAttribute
	// (IE uses .cssText instead)
	support.style = /top/.test( a.getAttribute( "style" ) );

	// Make sure that URLs aren't manipulated
	// (IE normalizes it by default)
	support.hrefNormalized = a.getAttribute( "href" ) === "/a";

	// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
	support.checkOn = !!input.value;

	// Make sure that a selected-by-default option has a working selected property.
	// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
	support.optSelected = opt.selected;

	// Tests for enctype support on a form (#6743)
	support.enctype = !!document.createElement( "form" ).enctype;

	// Make sure that the options inside disabled selects aren't marked as disabled
	// (WebKit marks them as disabled)
	select.disabled = true;
	support.optDisabled = !opt.disabled;

	// Support: IE8 only
	// Check if we can trust getAttribute("value")
	input = document.createElement( "input" );
	input.setAttribute( "value", "" );
	support.input = input.getAttribute( "value" ) === "";

	// Check if an input maintains its value after becoming a radio
	input.value = "t";
	input.setAttribute( "type", "radio" );
	support.radioValue = input.value === "t";
} )();


var rreturn = /\r/g,
	rspaces = /[\x20\t\r\n\f]+/g;

jQuery.fn.extend( {
	val: function( value ) {
		var hooks, ret, isFunction,
			elem = this[ 0 ];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] ||
					jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if (
					hooks &&
					"get" in hooks &&
					( ret = hooks.get( elem, "value" ) ) !== undefined
				) {
					return ret;
				}

				ret = elem.value;

				return typeof ret === "string" ?

					// handle most common string cases
					ret.replace( rreturn, "" ) :

					// handle cases where value is null/undef or number
					ret == null ? "" : ret;
			}

			return;
		}

		isFunction = jQuery.isFunction( value );

		return this.each( function( i ) {
			var val;

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( isFunction ) {
				val = value.call( this, i, jQuery( this ).val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";
			} else if ( typeof val === "number" ) {
				val += "";
			} else if ( jQuery.isArray( val ) ) {
				val = jQuery.map( val, function( value ) {
					return value == null ? "" : value + "";
				} );
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		} );
	}
} );

jQuery.extend( {
	valHooks: {
		option: {
			get: function( elem ) {
				var val = jQuery.find.attr( elem, "value" );
				return val != null ?
					val :

					// Support: IE10-11+
					// option.text throws exceptions (#14686, #14858)
					// Strip and collapse whitespace
					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
					jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " );
			}
		},
		select: {
			get: function( elem ) {
				var value, option,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one" || index < 0,
					values = one ? null : [],
					max = one ? index + 1 : options.length,
					i = index < 0 ?
						max :
						one ? index : 0;

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// oldIE doesn't update selected after form reset (#2551)
					if ( ( option.selected || i === index ) &&

							// Don't return options that are disabled or in a disabled optgroup
							( support.optDisabled ?
								!option.disabled :
								option.getAttribute( "disabled" ) === null ) &&
							( !option.parentNode.disabled ||
								!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];

					if ( jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) {

						// Support: IE6
						// When new option element is added to select box we need to
						// force reflow of newly added node in order to workaround delay
						// of initialization properties
						try {
							option.selected = optionSet = true;

						} catch ( _ ) {

							// Will be executed only in IE6
							option.scrollHeight;
						}

					} else {
						option.selected = false;
					}
				}

				// Force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}

				return options;
			}
		}
	}
} );

// Radios and checkboxes getter/setter
jQuery.each( [ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( jQuery.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
			}
		}
	};
	if ( !support.checkOn ) {
		jQuery.valHooks[ this ].get = function( elem ) {
			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
		};
	}
} );




var nodeHook, boolHook,
	attrHandle = jQuery.expr.attrHandle,
	ruseDefault = /^(?:checked|selected)$/i,
	getSetAttribute = support.getSetAttribute,
	getSetInput = support.input;

jQuery.fn.extend( {
	attr: function( name, value ) {
		return access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each( function() {
			jQuery.removeAttr( this, name );
		} );
	}
} );

jQuery.extend( {
	attr: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set attributes on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === "undefined" ) {
			return jQuery.prop( elem, name, value );
		}

		// All attributes are lowercase
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			name = name.toLowerCase();
			hooks = jQuery.attrHooks[ name ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
		}

		if ( value !== undefined ) {
			if ( value === null ) {
				jQuery.removeAttr( elem, name );
				return;
			}

			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			elem.setAttribute( name, value + "" );
			return value;
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		ret = jQuery.find.attr( elem, name );

		// Non-existent attributes return null, we normalize to undefined
		return ret == null ? undefined : ret;
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !support.radioValue && value === "radio" &&
					jQuery.nodeName( elem, "input" ) ) {

					// Setting the type on a radio button after the value resets the value in IE8-9
					// Reset value to default in case type is set after value during creation
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	},

	removeAttr: function( elem, value ) {
		var name, propName,
			i = 0,
			attrNames = value && value.match( rnotwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( ( name = attrNames[ i++ ] ) ) {
				propName = jQuery.propFix[ name ] || name;

				// Boolean attributes get special treatment (#10870)
				if ( jQuery.expr.match.bool.test( name ) ) {

					// Set corresponding property to false
					if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
						elem[ propName ] = false;

					// Support: IE<9
					// Also clear defaultChecked/defaultSelected (if appropriate)
					} else {
						elem[ jQuery.camelCase( "default-" + name ) ] =
							elem[ propName ] = false;
					}

				// See #9699 for explanation of this approach (setting first, then removal)
				} else {
					jQuery.attr( elem, name, "" );
				}

				elem.removeAttribute( getSetAttribute ? name : propName );
			}
		}
	}
} );

// Hooks for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		if ( value === false ) {

			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {

			// IE<8 needs the *property* name
			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );

		} else {

			// Support: IE<9
			// Use defaultChecked and defaultSelected for oldIE
			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
		}
		return name;
	}
};

jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
		attrHandle[ name ] = function( elem, name, isXML ) {
			var ret, handle;
			if ( !isXML ) {

				// Avoid an infinite loop by temporarily removing this function from the getter
				handle = attrHandle[ name ];
				attrHandle[ name ] = ret;
				ret = getter( elem, name, isXML ) != null ?
					name.toLowerCase() :
					null;
				attrHandle[ name ] = handle;
			}
			return ret;
		};
	} else {
		attrHandle[ name ] = function( elem, name, isXML ) {
			if ( !isXML ) {
				return elem[ jQuery.camelCase( "default-" + name ) ] ?
					name.toLowerCase() :
					null;
			}
		};
	}
} );

// fix oldIE attroperties
if ( !getSetInput || !getSetAttribute ) {
	jQuery.attrHooks.value = {
		set: function( elem, value, name ) {
			if ( jQuery.nodeName( elem, "input" ) ) {

				// Does not return so that setAttribute is also used
				elem.defaultValue = value;
			} else {

				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
				return nodeHook && nodeHook.set( elem, value, name );
			}
		}
	};
}

// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !getSetAttribute ) {

	// Use this for any attribute in IE6/7
	// This fixes almost every IE6/7 issue
	nodeHook = {
		set: function( elem, value, name ) {

			// Set the existing or create a new attribute node
			var ret = elem.getAttributeNode( name );
			if ( !ret ) {
				elem.setAttributeNode(
					( ret = elem.ownerDocument.createAttribute( name ) )
				);
			}

			ret.value = value += "";

			// Break association with cloned elements by also using setAttribute (#9646)
			if ( name === "value" || value === elem.getAttribute( name ) ) {
				return value;
			}
		}
	};

	// Some attributes are constructed with empty-string values when not defined
	attrHandle.id = attrHandle.name = attrHandle.coords =
		function( elem, name, isXML ) {
			var ret;
			if ( !isXML ) {
				return ( ret = elem.getAttributeNode( name ) ) && ret.value !== "" ?
					ret.value :
					null;
			}
		};

	// Fixing value retrieval on a button requires this module
	jQuery.valHooks.button = {
		get: function( elem, name ) {
			var ret = elem.getAttributeNode( name );
			if ( ret && ret.specified ) {
				return ret.value;
			}
		},
		set: nodeHook.set
	};

	// Set contenteditable to false on removals(#10429)
	// Setting to empty string throws an error as an invalid value
	jQuery.attrHooks.contenteditable = {
		set: function( elem, value, name ) {
			nodeHook.set( elem, value === "" ? false : value, name );
		}
	};

	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
	// This is for removals
	jQuery.each( [ "width", "height" ], function( i, name ) {
		jQuery.attrHooks[ name ] = {
			set: function( elem, value ) {
				if ( value === "" ) {
					elem.setAttribute( name, "auto" );
					return value;
				}
			}
		};
	} );
}

if ( !support.style ) {
	jQuery.attrHooks.style = {
		get: function( elem ) {

			// Return undefined in the case of empty string
			// Note: IE uppercases css property names, but if we were to .toLowerCase()
			// .cssText, that would destroy case sensitivity in URL's, like in "background"
			return elem.style.cssText || undefined;
		},
		set: function( elem, value ) {
			return ( elem.style.cssText = value + "" );
		}
	};
}




var rfocusable = /^(?:input|select|textarea|button|object)$/i,
	rclickable = /^(?:a|area)$/i;

jQuery.fn.extend( {
	prop: function( name, value ) {
		return access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		name = jQuery.propFix[ name ] || name;
		return this.each( function() {

			// try/catch handles cases where IE balks (such as removing a property on window)
			try {
				this[ name ] = undefined;
				delete this[ name ];
			} catch ( e ) {}
		} );
	}
} );

jQuery.extend( {
	prop: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set properties on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {

			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			return ( elem[ name ] = value );
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		return elem[ name ];
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {

				// elem.tabIndex doesn't always return the
				// correct value when it hasn't been explicitly set
				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				// Use proper attribute retrieval(#12072)
				var tabindex = jQuery.find.attr( elem, "tabindex" );

				return tabindex ?
					parseInt( tabindex, 10 ) :
					rfocusable.test( elem.nodeName ) ||
						rclickable.test( elem.nodeName ) && elem.href ?
							0 :
							-1;
			}
		}
	},

	propFix: {
		"for": "htmlFor",
		"class": "className"
	}
} );

// Some attributes require a special call on IE
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !support.hrefNormalized ) {

	// href/src property should get the full normalized URL (#10299/#12915)
	jQuery.each( [ "href", "src" ], function( i, name ) {
		jQuery.propHooks[ name ] = {
			get: function( elem ) {
				return elem.getAttribute( name, 4 );
			}
		};
	} );
}

// Support: Safari, IE9+
// Accessing the selectedIndex property
// forces the browser to respect setting selected
// on the option
// The getter ensures a default option is selected
// when in an optgroup
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {
			var parent = elem.parentNode;

			if ( parent ) {
				parent.selectedIndex;

				// Make sure that it also works with optgroups, see #5701
				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
			return null;
		},
		set: function( elem ) {
			var parent = elem.parentNode;
			if ( parent ) {
				parent.selectedIndex;

				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
		}
	};
}

jQuery.each( [
	"tabIndex",
	"readOnly",
	"maxLength",
	"cellSpacing",
	"cellPadding",
	"rowSpan",
	"colSpan",
	"useMap",
	"frameBorder",
	"contentEditable"
], function() {
	jQuery.propFix[ this.toLowerCase() ] = this;
} );

// IE6/7 call enctype encoding
if ( !support.enctype ) {
	jQuery.propFix.enctype = "encoding";
}




var rclass = /[\t\r\n\f]/g;

function getClass( elem ) {
	return jQuery.attr( elem, "class" ) || "";
}

jQuery.fn.extend( {
	addClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( jQuery.isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		if ( typeof value === "string" && value ) {
			classes = value.match( rnotwhite ) || [];

			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );
				cur = elem.nodeType === 1 &&
					( " " + curValue + " " ).replace( rclass, " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = jQuery.trim( cur );
					if ( curValue !== finalValue ) {
						jQuery.attr( elem, "class", finalValue );
					}
				}
			}
		}

		return this;
	},

	removeClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( jQuery.isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		if ( !arguments.length ) {
			return this.attr( "class", "" );
		}

		if ( typeof value === "string" && value ) {
			classes = value.match( rnotwhite ) || [];

			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );

				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 &&
					( " " + curValue + " " ).replace( rclass, " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {

						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = jQuery.trim( cur );
					if ( curValue !== finalValue ) {
						jQuery.attr( elem, "class", finalValue );
					}
				}
			}
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var type = typeof value;

		if ( typeof stateVal === "boolean" && type === "string" ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}

		if ( jQuery.isFunction( value ) ) {
			return this.each( function( i ) {
				jQuery( this ).toggleClass(
					value.call( this, i, getClass( this ), stateVal ),
					stateVal
				);
			} );
		}

		return this.each( function() {
			var className, i, self, classNames;

			if ( type === "string" ) {

				// Toggle individual class names
				i = 0;
				self = jQuery( this );
				classNames = value.match( rnotwhite ) || [];

				while ( ( className = classNames[ i++ ] ) ) {

					// Check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}

			// Toggle whole class name
			} else if ( value === undefined || type === "boolean" ) {
				className = getClass( this );
				if ( className ) {

					// store className if set
					jQuery._data( this, "__className__", className );
				}

				// If the element has a class name or if we're passed "false",
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				jQuery.attr( this, "class",
					className || value === false ?
					"" :
					jQuery._data( this, "__className__" ) || ""
				);
			}
		} );
	},

	hasClass: function( selector ) {
		var className, elem,
			i = 0;

		className = " " + selector + " ";
		while ( ( elem = this[ i++ ] ) ) {
			if ( elem.nodeType === 1 &&
				( " " + getClass( elem ) + " " ).replace( rclass, " " )
					.indexOf( className ) > -1
			) {
				return true;
			}
		}

		return false;
	}
} );




// Return jQuery for attributes-only inclusion


jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu" ).split( " " ),
	function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
} );

jQuery.fn.extend( {
	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	}
} );


var location = window.location;

var nonce = jQuery.now();

var rquery = ( /\?/ );



var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;

jQuery.parseJSON = function( data ) {

	// Attempt to parse using the native JSON parser first
	if ( window.JSON && window.JSON.parse ) {

		// Support: Android 2.3
		// Workaround failure to string-cast null input
		return window.JSON.parse( data + "" );
	}

	var requireNonComma,
		depth = null,
		str = jQuery.trim( data + "" );

	// Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
	// after removing valid tokens
	return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {

		// Force termination if we see a misplaced comma
		if ( requireNonComma && comma ) {
			depth = 0;
		}

		// Perform no more replacements after returning to outermost depth
		if ( depth === 0 ) {
			return token;
		}

		// Commas must not follow "[", "{", or ","
		requireNonComma = open || comma;

		// Determine new depth
		// array/object open ("[" or "{"): depth += true - false (increment)
		// array/object close ("]" or "}"): depth += false - true (decrement)
		// other cases ("," or primitive): depth += true - true (numeric cast)
		depth += !close - !open;

		// Remove this token
		return "";
	} ) ) ?
		( Function( "return " + str ) )() :
		jQuery.error( "Invalid JSON: " + data );
};


// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml, tmp;
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	try {
		if ( window.DOMParser ) { // Standard
			tmp = new window.DOMParser();
			xml = tmp.parseFromString( data, "text/xml" );
		} else { // IE
			xml = new window.ActiveXObject( "Microsoft.XMLDOM" );
			xml.async = "false";
			xml.loadXML( data );
		}
	} catch ( e ) {
		xml = undefined;
	}
	if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
};


var
	rhash = /#.*$/,
	rts = /([?&])_=[^&]*/,

	// IE leaves an \r character at EOL
	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg,

	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,
	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
	allTypes = "*/".concat( "*" ),

	// Document location
	ajaxLocation = location.href,

	// Segment location into parts
	ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];

		if ( jQuery.isFunction( func ) ) {

			// For each dataType in the dataTypeExpression
			while ( ( dataType = dataTypes[ i++ ] ) ) {

				// Prepend if requested
				if ( dataType.charAt( 0 ) === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );

				// Otherwise append
				} else {
					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" &&
				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {

				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		} );
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
	var deep, key,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

/* Handles responses to an ajax request:
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {
	var firstDataType, ct, finalDataType, type,
		contents = s.contents,
		dataTypes = s.dataTypes;

	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {

		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}

		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

/* Chain conversions given the request and the original response
 * Also sets the responseXXX fields on the jqXHR instance
 */
function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},

		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	current = dataTypes.shift();

	// Convert to each sequential dataType
	while ( current ) {

		if ( s.responseFields[ current ] ) {
			jqXHR[ s.responseFields[ current ] ] = response;
		}

		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}

		prev = current;
		current = dataTypes.shift();

		if ( current ) {

			// There's only work to do if current dataType is non-auto
			if ( current === "*" ) {

				current = prev;

			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split( " " );
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {

								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s[ "throws" ] ) { // jscs:ignore requireDotNotation
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return {
								state: "parsererror",
								error: conv ? e : "No conversion from " + prev + " to " + current
							};
						}
					}
				}
			}
		}
	}

	return { state: "success", data: response };
}

jQuery.extend( {

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: ajaxLocation,
		type: "GET",
		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /\bxml\b/,
			html: /\bhtml/,
			json: /\bjson\b/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText",
			json: "responseJSON"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": jQuery.parseJSON,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var

			// Cross-domain detection vars
			parts,

			// Loop variable
			i,

			// URL without anti-cache param
			cacheURL,

			// Response headers as string
			responseHeadersString,

			// timeout handle
			timeoutTimer,

			// To know if global events are to be dispatched
			fireGlobals,

			transport,

			// Response headers
			responseHeaders,

			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),

			// Callbacks context
			callbackContext = s.context || s,

			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context &&
				( callbackContext.nodeType || callbackContext.jquery ) ?
					jQuery( callbackContext ) :
					jQuery.event,

			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks( "once memory" ),

			// Status-dependent callbacks
			statusCode = s.statusCode || {},

			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},

			// The jqXHR state
			state = 0,

			// Default abort message
			strAbort = "canceled",

			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( state === 2 ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
								responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
							}
						}
						match = responseHeaders[ key.toLowerCase() ];
					}
					return match == null ? null : match;
				},

				// Raw string
				getAllResponseHeaders: function() {
					return state === 2 ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					var lname = name.toLowerCase();
					if ( !state ) {
						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( !state ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( state < 2 ) {
							for ( code in map ) {

								// Lazy-add the new callback in a way that preserves old ones
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						} else {

							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR ).complete = completeDeferred.add;
		jqXHR.success = jqXHR.done;
		jqXHR.error = jqXHR.fail;

		// Remove hash character (#7531: and string promotion)
		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
		// Handle falsy url in the settings object (#10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || ajaxLocation ) + "" )
			.replace( rhash, "" )
			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];

		// A cross-domain request is in order when we have a protocol:host:port mismatch
		if ( s.crossDomain == null ) {
			parts = rurl.exec( s.url.toLowerCase() );
			s.crossDomain = !!( parts &&
				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
			);
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( state === 2 ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
		fireGlobals = jQuery.event && s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger( "ajaxStart" );
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		cacheURL = s.url;

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// If data is available, append data to url
			if ( s.data ) {
				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );

				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add anti-cache in url if needed
			if ( s.cache === false ) {
				s.url = rts.test( cacheURL ) ?

					// If there is already a '_' parameter, set its value
					cacheURL.replace( rts, "$1_=" + nonce++ ) :

					// Otherwise add one to the end
					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
			}
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
				s.accepts[ s.dataTypes[ 0 ] ] +
					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend &&
			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {

			// Abort if not done already and return
			return jqXHR.abort();
		}

		// aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		for ( i in { success: 1, error: 1, complete: 1 } ) {
			jqXHR[ i ]( s[ i ] );
		}

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}

			// If request was aborted inside ajaxSend, stop there
			if ( state === 2 ) {
				return jqXHR;
			}

			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = window.setTimeout( function() {
					jqXHR.abort( "timeout" );
				}, s.timeout );
			}

			try {
				state = 1;
				transport.send( requestHeaders, done );
			} catch ( e ) {

				// Propagate exception as error if not done
				if ( state < 2 ) {
					done( -1, e );

				// Simply rethrow otherwise
				} else {
					throw e;
				}
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Called once
			if ( state === 2 ) {
				return;
			}

			// State is "done" now
			state = 2;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				window.clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Determine if successful
			isSuccess = status >= 200 && status < 300 || status === 304;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// Convert no matter what (that way responseXXX fields are always set)
			response = ajaxConvert( s, response, jqXHR, isSuccess );

			// If successful, handle type chaining
			if ( isSuccess ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader( "Last-Modified" );
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader( "etag" );
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 || s.type === "HEAD" ) {
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					statusText = response.state;
					success = response.data;
					error = response.error;
					isSuccess = !error;
				}
			} else {

				// We extract error from statusText
				// then normalize statusText and status for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );

				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger( "ajaxStop" );
				}
			}
		}

		return jqXHR;
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	}
} );

jQuery.each( [ "get", "post" ], function( i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {

		// shift arguments if data argument was omitted
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		// The url can be an options object (which then must have .url)
		return jQuery.ajax( jQuery.extend( {
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		}, jQuery.isPlainObject( url ) && url ) );
	};
} );


jQuery._evalUrl = function( url ) {
	return jQuery.ajax( {
		url: url,

		// Make this explicit, since user can override this through ajaxSetup (#11264)
		type: "GET",
		dataType: "script",
		cache: true,
		async: false,
		global: false,
		"throws": true
	} );
};


jQuery.fn.extend( {
	wrapAll: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapAll( html.call( this, i ) );
			} );
		}

		if ( this[ 0 ] ) {

			// The elements to wrap the target around
			var wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map( function() {
				var elem = this;

				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
					elem = elem.firstChild;
				}

				return elem;
			} ).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapInner( html.call( this, i ) );
			} );
		}

		return this.each( function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		} );
	},

	wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );

		return this.each( function( i ) {
			jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
		} );
	},

	unwrap: function() {
		return this.parent().each( function() {
			if ( !jQuery.nodeName( this, "body" ) ) {
				jQuery( this ).replaceWith( this.childNodes );
			}
		} ).end();
	}
} );


function getDisplay( elem ) {
	return elem.style && elem.style.display || jQuery.css( elem, "display" );
}

function filterHidden( elem ) {

	// Disconnected elements are considered hidden
	if ( !jQuery.contains( elem.ownerDocument || document, elem ) ) {
		return true;
	}
	while ( elem && elem.nodeType === 1 ) {
		if ( getDisplay( elem ) === "none" || elem.type === "hidden" ) {
			return true;
		}
		elem = elem.parentNode;
	}
	return false;
}

jQuery.expr.filters.hidden = function( elem ) {

	// Support: Opera <= 12.12
	// Opera reports offsetWidths and offsetHeights less than zero on some elements
	return support.reliableHiddenOffsets() ?
		( elem.offsetWidth <= 0 && elem.offsetHeight <= 0 &&
			!elem.getClientRects().length ) :
			filterHidden( elem );
};

jQuery.expr.filters.visible = function( elem ) {
	return !jQuery.expr.filters.hidden( elem );
};




var r20 = /%20/g,
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( jQuery.isArray( obj ) ) {

		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {

				// Treat each array item as a scalar.
				add( prefix, v );

			} else {

				// Item is non-scalar (array or object), encode its numeric index.
				buildParams(
					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
					v,
					traditional,
					add
				);
			}
		} );

	} else if ( !traditional && jQuery.type( obj ) === "object" ) {

		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {

		// Serialize scalar item.
		add( prefix, obj );
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, value ) {

			// If value is a function, invoke it and return its value
			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
		};

	// Set traditional to true for jQuery <= 1.3.2 behavior.
	if ( traditional === undefined ) {
		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {

		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		} );

	} else {

		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" ).replace( r20, "+" );
};

jQuery.fn.extend( {
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map( function() {

			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		} )
		.filter( function() {
			var type = this.type;

			// Use .is(":disabled") so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !rcheckableType.test( type ) );
		} )
		.map( function( i, elem ) {
			var val = jQuery( this ).val();

			return val == null ?
				null :
				jQuery.isArray( val ) ?
					jQuery.map( val, function( val ) {
						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
					} ) :
					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		} ).get();
	}
} );


// Create the request object
// (This is still attached to ajaxSettings for backward compatibility)
jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ?

	// Support: IE6-IE8
	function() {

		// XHR cannot access local files, always use ActiveX for that case
		if ( this.isLocal ) {
			return createActiveXHR();
		}

		// Support: IE 9-11
		// IE seems to error on cross-domain PATCH requests when ActiveX XHR
		// is used. In IE 9+ always use the native XHR.
		// Note: this condition won't catch Edge as it doesn't define
		// document.documentMode but it also doesn't support ActiveX so it won't
		// reach this code.
		if ( document.documentMode > 8 ) {
			return createStandardXHR();
		}

		// Support: IE<9
		// oldIE XHR does not support non-RFC2616 methods (#13240)
		// See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx
		// and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
		// Although this check for six methods instead of eight
		// since IE also does not support "trace" and "connect"
		return /^(get|post|head|put|delete|options)$/i.test( this.type ) &&
			createStandardXHR() || createActiveXHR();
	} :

	// For all other browsers, use the standard XMLHttpRequest object
	createStandardXHR;

var xhrId = 0,
	xhrCallbacks = {},
	xhrSupported = jQuery.ajaxSettings.xhr();

// Support: IE<10
// Open requests must be manually aborted on unload (#5280)
// See https://support.microsoft.com/kb/2856746 for more info
if ( window.attachEvent ) {
	window.attachEvent( "onunload", function() {
		for ( var key in xhrCallbacks ) {
			xhrCallbacks[ key ]( undefined, true );
		}
	} );
}

// Determine support properties
support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
xhrSupported = support.ajax = !!xhrSupported;

// Create transport if the browser can provide an xhr
if ( xhrSupported ) {

	jQuery.ajaxTransport( function( options ) {

		// Cross domain only allowed if supported through XMLHttpRequest
		if ( !options.crossDomain || support.cors ) {

			var callback;

			return {
				send: function( headers, complete ) {
					var i,
						xhr = options.xhr(),
						id = ++xhrId;

					// Open the socket
					xhr.open(
						options.type,
						options.url,
						options.async,
						options.username,
						options.password
					);

					// Apply custom fields if provided
					if ( options.xhrFields ) {
						for ( i in options.xhrFields ) {
							xhr[ i ] = options.xhrFields[ i ];
						}
					}

					// Override mime type if needed
					if ( options.mimeType && xhr.overrideMimeType ) {
						xhr.overrideMimeType( options.mimeType );
					}

					// X-Requested-With header
					// For cross-domain requests, seeing as conditions for a preflight are
					// akin to a jigsaw puzzle, we simply never set it to be sure.
					// (it can always be set on a per-request basis or even using ajaxSetup)
					// For same-domain requests, won't change header if already provided.
					if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
						headers[ "X-Requested-With" ] = "XMLHttpRequest";
					}

					// Set headers
					for ( i in headers ) {

						// Support: IE<9
						// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
						// request header to a null-value.
						//
						// To keep consistent with other XHR implementations, cast the value
						// to string and ignore `undefined`.
						if ( headers[ i ] !== undefined ) {
							xhr.setRequestHeader( i, headers[ i ] + "" );
						}
					}

					// Do send the request
					// This may raise an exception which is actually
					// handled in jQuery.ajax (so no try/catch here)
					xhr.send( ( options.hasContent && options.data ) || null );

					// Listener
					callback = function( _, isAbort ) {
						var status, statusText, responses;

						// Was never called and is aborted or complete
						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {

							// Clean up
							delete xhrCallbacks[ id ];
							callback = undefined;
							xhr.onreadystatechange = jQuery.noop;

							// Abort manually if needed
							if ( isAbort ) {
								if ( xhr.readyState !== 4 ) {
									xhr.abort();
								}
							} else {
								responses = {};
								status = xhr.status;

								// Support: IE<10
								// Accessing binary-data responseText throws an exception
								// (#11426)
								if ( typeof xhr.responseText === "string" ) {
									responses.text = xhr.responseText;
								}

								// Firefox throws an exception when accessing
								// statusText for faulty cross-domain requests
								try {
									statusText = xhr.statusText;
								} catch ( e ) {

									// We normalize with Webkit giving an empty statusText
									statusText = "";
								}

								// Filter status for non standard behaviors

								// If the request is local and we have data: assume a success
								// (success with no data won't get notified, that's the best we
								// can do given current implementations)
								if ( !status && options.isLocal && !options.crossDomain ) {
									status = responses.text ? 200 : 404;

								// IE - #1450: sometimes returns 1223 when it should be 204
								} else if ( status === 1223 ) {
									status = 204;
								}
							}
						}

						// Call complete if needed
						if ( responses ) {
							complete( status, statusText, responses, xhr.getAllResponseHeaders() );
						}
					};

					// Do send the request
					// `xhr.send` may raise an exception, but it will be
					// handled in jQuery.ajax (so no try/catch here)
					if ( !options.async ) {

						// If we're in sync mode we fire the callback
						callback();
					} else if ( xhr.readyState === 4 ) {

						// (IE6 & IE7) if it's in cache and has been
						// retrieved directly we need to fire the callback
						window.setTimeout( callback );
					} else {

						// Register the callback, but delay it in case `xhr.send` throws
						// Add to the list of active xhr callbacks
						xhr.onreadystatechange = xhrCallbacks[ id ] = callback;
					}
				},

				abort: function() {
					if ( callback ) {
						callback( undefined, true );
					}
				}
			};
		}
	} );
}

// Functions to create xhrs
function createStandardXHR() {
	try {
		return new window.XMLHttpRequest();
	} catch ( e ) {}
}

function createActiveXHR() {
	try {
		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
	} catch ( e ) {}
}




// Install script dataType
jQuery.ajaxSetup( {
	accepts: {
		script: "text/javascript, application/javascript, " +
			"application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /\b(?:java|ecma)script\b/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
} );

// Handle cache's special case and global
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
		s.global = false;
	}
} );

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {

	// This transport only deals with cross domain requests
	if ( s.crossDomain ) {

		var script,
			head = document.head || jQuery( "head" )[ 0 ] || document.documentElement;

		return {

			send: function( _, callback ) {

				script = document.createElement( "script" );

				script.async = true;

				if ( s.scriptCharset ) {
					script.charset = s.scriptCharset;
				}

				script.src = s.url;

				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function( _, isAbort ) {

					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {

						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;

						// Remove the script
						if ( script.parentNode ) {
							script.parentNode.removeChild( script );
						}

						// Dereference the script
						script = null;

						// Callback if not abort
						if ( !isAbort ) {
							callback( 200, "success" );
						}
					}
				};

				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
				// Use native DOM manipulation to avoid our domManip AJAX trickery
				head.insertBefore( script, head.firstChild );
			},

			abort: function() {
				if ( script ) {
					script.onload( undefined, true );
				}
			}
		};
	}
} );




var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup( {
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
} );

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" &&
				( s.contentType || "" )
					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
				rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters[ "script json" ] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always( function() {

			// If previous value didn't exist - remove it
			if ( overwritten === undefined ) {
				jQuery( window ).removeProp( callbackName );

			// Otherwise restore preexisting value
			} else {
				window[ callbackName ] = overwritten;
			}

			// Save back as free
			if ( s[ callbackName ] ) {

				// make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		} );

		// Delegate to script
		return "script";
	}
} );




// data: string of html
// context (optional): If specified, the fragment will be created in this context,
// defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}
	context = context || document;

	var parsed = rsingleTag.exec( data ),
		scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[ 1 ] ) ];
	}

	parsed = buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};


// Keep a copy of the old load method
var _load = jQuery.fn.load;

/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	if ( typeof url !== "string" && _load ) {
		return _load.apply( this, arguments );
	}

	var selector, type, response,
		self = this,
		off = url.indexOf( " " );

	if ( off > -1 ) {
		selector = jQuery.trim( url.slice( off, url.length ) );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( jQuery.isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax( {
			url: url,

			// If "type" variable is undefined, then "GET" method will be used.
			// Make value of this field explicit since
			// user can override it through ajaxSetup method
			type: type || "GET",
			dataType: "html",
			data: params
		} ).done( function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		// If the request succeeds, this function gets "data", "status", "jqXHR"
		// but they are ignored because response was set above.
		// If it fails, this function gets "jqXHR", "status", "error"
		} ).always( callback && function( jqXHR, status ) {
			self.each( function() {
				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
			} );
		} );
	}

	return this;
};




// Attach a bunch of functions for handling common AJAX events
jQuery.each( [
	"ajaxStart",
	"ajaxStop",
	"ajaxComplete",
	"ajaxError",
	"ajaxSuccess",
	"ajaxSend"
], function( i, type ) {
	jQuery.fn[ type ] = function( fn ) {
		return this.on( type, fn );
	};
} );




jQuery.expr.filters.animated = function( elem ) {
	return jQuery.grep( jQuery.timers, function( fn ) {
		return elem === fn.elem;
	} ).length;
};





/**
 * Gets a window from an element
 */
function getWindow( elem ) {
	return jQuery.isWindow( elem ) ?
		elem :
		elem.nodeType === 9 ?
			elem.defaultView || elem.parentWindow :
			false;
}

jQuery.offset = {
	setOffset: function( elem, options, i ) {
		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
			position = jQuery.css( elem, "position" ),
			curElem = jQuery( elem ),
			props = {};

		// set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		curOffset = curElem.offset();
		curCSSTop = jQuery.css( elem, "top" );
		curCSSLeft = jQuery.css( elem, "left" );
		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
			jQuery.inArray( "auto", [ curCSSTop, curCSSLeft ] ) > -1;

		// need to be able to calculate position if either top or left
		// is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;
		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( jQuery.isFunction( options ) ) {

			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );
		} else {
			curElem.css( props );
		}
	}
};

jQuery.fn.extend( {
	offset: function( options ) {
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each( function( i ) {
					jQuery.offset.setOffset( this, options, i );
				} );
		}

		var docElem, win,
			box = { top: 0, left: 0 },
			elem = this[ 0 ],
			doc = elem && elem.ownerDocument;

		if ( !doc ) {
			return;
		}

		docElem = doc.documentElement;

		// Make sure it's not a disconnected DOM node
		if ( !jQuery.contains( docElem, elem ) ) {
			return box;
		}

		// If we don't have gBCR, just use 0,0 rather than error
		// BlackBerry 5, iOS 3 (original iPhone)
		if ( typeof elem.getBoundingClientRect !== "undefined" ) {
			box = elem.getBoundingClientRect();
		}
		win = getWindow( doc );
		return {
			top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
			left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
		};
	},

	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset,
			parentOffset = { top: 0, left: 0 },
			elem = this[ 0 ];

		// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
		// because it is its only offset parent
		if ( jQuery.css( elem, "position" ) === "fixed" ) {

			// we assume that getBoundingClientRect is available when computed position is fixed
			offset = elem.getBoundingClientRect();
		} else {

			// Get *real* offsetParent
			offsetParent = this.offsetParent();

			// Get correct offsets
			offset = this.offset();
			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
				parentOffset = offsetParent.offset();
			}

			// Add offsetParent borders
			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
		}

		// Subtract parent offsets and element margins
		// note: when an element has margin: auto the offsetLeft and marginLeft
		// are the same in Safari causing offset.left to incorrectly be 0
		return {
			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	offsetParent: function() {
		return this.map( function() {
			var offsetParent = this.offsetParent;

			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) &&
				jQuery.css( offsetParent, "position" ) === "static" ) ) {
				offsetParent = offsetParent.offsetParent;
			}
			return offsetParent || documentElement;
		} );
	}
} );

// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
	var top = /Y/.test( prop );

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {
			var win = getWindow( elem );

			if ( val === undefined ) {
				return win ? ( prop in win ) ? win[ prop ] :
					win.document.documentElement[ method ] :
					elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : jQuery( win ).scrollLeft(),
					top ? val : jQuery( win ).scrollTop()
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length, null );
	};
} );

// Support: Safari<7-8+, Chrome<37-44+
// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// getComputedStyle returns percent when specified for top/left/bottom/right
// rather than make the css module depend on the offset module, we just check for it here
jQuery.each( [ "top", "left" ], function( i, prop ) {
	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
		function( elem, computed ) {
			if ( computed ) {
				computed = curCSS( elem, prop );

				// if curCSS returns percentage, fallback to offset
				return rnumnonpx.test( computed ) ?
					jQuery( elem ).position()[ prop ] + "px" :
					computed;
			}
		}
	);
} );


// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
	function( defaultExtra, funcName ) {

		// margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return access( this, function( elem, type, value ) {
				var doc;

				if ( jQuery.isWindow( elem ) ) {

					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
					// isn't a whole lot we can do. See pull request at this URL for discussion:
					// https://github.com/jquery/jquery/pull/764
					return elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
					// whichever is greatest
					// unfortunately, this causes bug #3838 in IE6/8 only,
					// but there is currently no good, small way to fix it.
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?

					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable, null );
		};
	} );
} );


jQuery.fn.extend( {

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {

		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ?
			this.off( selector, "**" ) :
			this.off( types, selector || "**", fn );
	}
} );

// The number of elements contained in the matched element set
jQuery.fn.size = function() {
	return this.length;
};

jQuery.fn.andSelf = jQuery.fn.addBack;




// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon



//将jQuery对象局部暴露给layui
layui.define(function(exports){
  layui.$ = jQuery;
  exports('jquery', jQuery);
});

return jQuery;
}));/**
 * layer
 * 通用 Web 弹出层组件
 */

;!function(window, undefined){
"use strict";

var isLayui = window.layui && layui.define;
var $;
var win;
var ready = {
  getPath: function(){
    var jsPath = document.currentScript ? document.currentScript.src : function(){
      var js = document.scripts;
      var last = js.length - 1;
      var src;
      for(var i = last; i > 0; i--){
        if(js[i].readyState === 'interactive'){
          src = js[i].src;
          break;
        }
      }
      return src || js[last].src;
    }();
    var GLOBAL = window.LAYUI_GLOBAL || {};
    return GLOBAL.layer_dir || jsPath.substring(0, jsPath.lastIndexOf('/') + 1);
  }(),
  config: {
    removeFocus: true
  }, 
  end: {}, 
  events: {resize: {}}, 
  minStackIndex: 0,
  minStackArr: [],
  btn: ['确定', '取消'],

  // 五种原始层模式
  type: ['dialog', 'page', 'iframe', 'loading', 'tips'],
  
  // 获取节点的 style 属性值
  getStyle: function(node, name){
    var style = node.currentStyle ? node.currentStyle : window.getComputedStyle(node, null);
    return style[style.getPropertyValue ? 'getPropertyValue' : 'getAttribute'](name);
  },
  
  // 载入 CSS 依赖
  link: function(href, fn, cssname){
    // 未设置路径，则不主动加载 css
    if(!layer.path) return;
    
    var head = document.getElementsByTagName("head")[0];
    var link = document.createElement('link');
    
    if(typeof fn === 'string') cssname = fn;
    
    var app = (cssname || href).replace(/\.|\//g, '');
    var id = 'layuicss-'+ app;
    var STATUS_NAME = 'creating'
    var timeout = 0;
    
    link.rel = 'stylesheet';
    link.href = layer.path + href;
    link.id = id;
    
    if(!document.getElementById(id)){
      head.appendChild(link);
    }

    if(typeof fn !== 'function') return;

    // 轮询 css 是否加载完毕
    (function poll(status) {
      var delay = 100;
      var getLinkElem = document.getElementById(id); // 获取动态插入的 link 元素
      
      // 如果轮询超过指定秒数，则视为请求文件失败或 css 文件不符合规范
      if(++timeout > 10 * 1000 / delay){
        return window.console && console.error(app +'.css: Invalid');
      }
      
      // css 加载就绪
      if(parseInt(ready.getStyle(getLinkElem, 'width')) === 1989){
        // 如果参数来自于初始轮询（即未加载就绪时的），则移除 link 标签状态
        if(status === STATUS_NAME) getLinkElem.removeAttribute('lay-status');
        // 如果 link 标签的状态仍为「创建中」，则继续进入轮询，直到状态改变，则执行回调
        getLinkElem.getAttribute('lay-status') === STATUS_NAME ? setTimeout(poll, delay) : fn();
      } else {
        getLinkElem.setAttribute('lay-status', STATUS_NAME);
        setTimeout(function(){
          poll(STATUS_NAME);
        }, delay);
      }

      // parseInt(ready.getStyle(document.getElementById(id), 'width')) === 1989 ? fn() : setTimeout(poll, 1000);
    }());

  }
};

// 默认内置方法。
var layer = {
  v: '3.7.0',
  ie: function(){ // ie 版本
    var agent = navigator.userAgent.toLowerCase();
    return (!!window.ActiveXObject || "ActiveXObject" in window) ? (
      (agent.match(/msie\s(\d+)/) || [])[1] || '11' // 由于 ie11 并没有 msie 的标识
    ) : false;
  }(),
  index: (window.layer && window.layer.v) ? 100000 : 0,
  path: ready.getPath,
  config: function(options, fn){
    options = options || {};
    layer.cache = ready.config = $.extend({}, ready.config, options);
    layer.path = ready.config.path || layer.path;
    typeof options.extend === 'string' && (options.extend = [options.extend]);
    
    // 如果设置了路径，则加载样式
    if(ready.config.path) layer.ready();
    
    if(!options.extend) return this;
    
    // 加载 css
    isLayui 
      ? layui.addcss('modules/layer/' + options.extend)
    : ready.link('css/' + options.extend);
    
    return this;
  },

  // 主体 CSS 等待事件
  ready: function(callback){
    var cssname = 'layer';
    var ver = '';
    var path = (isLayui ? 'modules/' : 'css/') + 'layer.css?v='+ layer.v + ver;
    
    isLayui ? (
      layui['layui.all'] 
        ? (typeof callback === 'function' && callback()) 
      : layui.addcss(path, callback, cssname)
    ) : ready.link(path, callback, cssname);

    return this;
  },
  
  // 各种快捷引用
  alert: function(content, options, yes){
    var type = typeof options === 'function';
    if(type) yes = options;
    return layer.open($.extend({
      content: content,
      yes: yes
    }, type ? {} : options));
  }, 
  
  confirm: function(content, options, yes, cancel){ 
    var type = typeof options === 'function';
    if(type){
      cancel = yes;
      yes = options;
    }
    return layer.open($.extend({
      content: content,
      btn: ready.btn,
      yes: yes,
      btn2: cancel
    }, type ? {} : options));
  },
  
  msg: function(content, options, end){ // 最常用提示层
    var type = typeof options === 'function', rskin = ready.config.skin;
    var skin = (rskin ? rskin + ' ' + rskin + '-msg' : '')||'layui-layer-msg';
    var anim = doms.anim.length - 1;
    if(type) end = options;
    return layer.open($.extend({
      content: content,
      time: 3000,
      shade: false,
      skin: skin,
      title: false,
      closeBtn: false,
      btn: false,
      resize: false,
      end: end,
      removeFocus: false
    }, (type && !ready.config.skin) ? {
      skin: skin + ' layui-layer-hui',
      anim: anim
    } : function(){
       options = options || {};
       if(options.icon === -1 || options.icon === undefined && !ready.config.skin){
         options.skin = skin + ' ' + (options.skin||'layui-layer-hui');
       }
       return options;
    }()));  
  },
  
  load: function(icon, options){
    return layer.open($.extend({
      type: 3,
      icon: icon || 0,
      resize: false,
      shade: 0.01,
      removeFocus: false
    }, options));
  }, 
  
  tips: function(content, follow, options){
    return layer.open($.extend({
      type: 4,
      content: [content, follow],
      closeBtn: false,
      time: 3000,
      shade: false,
      resize: false,
      fixed: false,
      maxWidth: 260,
      removeFocus: false
    }, options));
  }
};

var Class = function(setings){  
  var that = this, creat = function(){
    that.creat();
  };
  that.index = ++layer.index;
  that.config.maxWidth = $(win).width() - 15*2; // 初始最大宽度：当前屏幕宽，左右留 15px 边距
  that.config = $.extend({}, that.config, ready.config, setings);
  document.body ? creat() : setTimeout(function(){
    creat();
  }, 30);
};

Class.pt = Class.prototype;

// 缓存常用字符
var doms = ['layui-layer', '.layui-layer-title', '.layui-layer-main', '.layui-layer-dialog', 'layui-layer-iframe', 'layui-layer-content', 'layui-layer-btn', 'layui-layer-close'];

// 内置动画类
doms.anim = {
  // 旧版动画
  0: 'layer-anim-00', 
  1: 'layer-anim-01', 
  2: 'layer-anim-02', 
  3: 'layer-anim-03', 
  4: 'layer-anim-04', 
  5: 'layer-anim-05', 
  6: 'layer-anim-06',

  // 滑出方向
  slideDown: 'layer-anim-slide-down',
  slideLeft: 'layer-anim-slide-left',
  slideUp: 'layer-anim-slide-up',
  slideRight: 'layer-anim-slide-right'
};

doms.SHADE = 'layui-layer-shade';
doms.MOVE = 'layui-layer-move';

var SHADE_KEY = 'LAYUI-LAYER-SHADE-KEY';
var RECORD_HEIGHT_KEY = 'LAYUI_LAYER_CONTENT_RECORD_HEIGHT';

// 默认配置
Class.pt.config = {
  type: 0,
  shade: 0.3,
  fixed: true,
  move: doms[1],
  title: '信息',
  offset: 'auto',
  area: 'auto',
  closeBtn: 1,
  icon: -1,
  time: 0, // 0 表示不自动关闭
  zIndex: 19891014, 
  maxWidth: 360,
  anim: 0,
  isOutAnim: true, // 退出动画
  minStack: true, // 最小化堆叠
  moveType: 1,
  resize: true,
  scrollbar: true, // 是否允许浏览器滚动条
  tips: 2
};

// 容器
Class.pt.vessel = function(conType, callback){
  var that = this, times = that.index, config = that.config;
  var zIndex = config.zIndex + times, titype = typeof config.title === 'object';
  var ismax = config.maxmin && (config.type === 1 || config.type === 2);
  var titleHTML = (config.title ? '<div class="layui-layer-title" style="'+ (titype ? config.title[1] : '') +'">' 
    + (titype ? config.title[0] : config.title) 
  + '</div>' : '');
  
  config.zIndex = zIndex;
  callback([
    // 遮罩
    config.shade ? ('<div class="'+ doms.SHADE +'" id="'+ doms.SHADE + times +'" times="'+ times +'" style="'+ ('z-index:'+ (zIndex-1) +'; ') +'"></div>') : '',
    
    // 主体
    '<div class="'+ doms[0] + (' layui-layer-'+ready.type[config.type]) + (((config.type == 0 || config.type == 2) && !config.shade) ? ' layui-layer-border' : '') + ' ' + (config.skin||'') +'" id="'+ doms[0] + times +'" type="'+ ready.type[config.type] +'" times="'+ times +'" showtime="'+ config.time +'" conType="'+ (conType ? 'object' : 'string') +'" style="z-index: '+ zIndex +'; width:'+ config.area[0] + ';height:' + config.area[1] + ';position:'+ (config.fixed ? 'fixed;' : 'absolute;') +'">'
      + (conType && config.type != 2 ? '' : titleHTML)

      // 内容区
      + '<div'+ (config.id ? ' id="'+ config.id +'"' : '') +' class="layui-layer-content'+ ((config.type == 0 && config.icon !== -1) ? ' layui-layer-padding' : '') + (config.type == 3 ? ' layui-layer-loading'+config.icon : '') +'">'
        // 表情或图标
        + function(){
          var face = [
            'layui-icon-tips',
            'layui-icon-success',
            'layui-icon-error',
            'layui-icon-question',
            'layui-icon-lock',
            'layui-icon-face-cry',
            'layui-icon-face-smile'
          ];

          var additFaceClass;

          // 动画类
          var animClass = 'layui-anim layui-anim-rotate layui-anim-loop';

          // 信息框表情
          if(config.type == 0 && config.icon !== -1){
            // 加载（加载图标）
            if(config.icon == 16){
              additFaceClass = 'layui-icon layui-icon-loading '+ animClass;
            }
            return '<i class="layui-layer-face layui-icon '+ (
              additFaceClass || face[config.icon] || face[0]
            ) +'"></i>';
          }

          // 加载层图标
          if(config.type == 3){
            var type = [
              'layui-icon-loading', 
              'layui-icon-loading-1'
            ];
            // 风格 2
            if(config.icon == 2){
              return '<div class="layui-layer-loading-2 '+ animClass +'"></div>';
            }
            return '<i class="layui-layer-loading-icon layui-icon '+ (
              type[config.icon] || type[0]
            )+' '+ animClass +'"></i>'
          }

          return '';
        }()
        + (config.type == 1 && conType ? '' : (config.content||''))
      + '</div>'

      // 右上角按钮
      + '<div class="layui-layer-setwin">'+ function(){
        var arr = [];

        // 最小化、最大化
        if(ismax){
          arr.push('<span class="layui-layer-min"></span>');
          arr.push('<span class="layui-layer-max"></span>');
        }

        // 关闭按钮
        if(config.closeBtn){
          arr.push('<span class="layui-icon layui-icon-close '+ [
            doms[7], 
            doms[7] + (config.title ? config.closeBtn : (config.type == 4 ? '1' : '2'))
          ].join(' ') +'"></span>')
        }

        return arr.join('');
      }() + '</div>'

      // 底部按钮
      + (config.btn ? function(){
        var button = '';
        typeof config.btn === 'string' && (config.btn = [config.btn]);
        for(var i = 0, len = config.btn.length; i < len; i++){
          button += '<a class="'+ doms[6] +''+ i +'">'+ config.btn[i] +'</a>'
        }
        return '<div class="'+ function(){
          var className = [doms[6]];
          if(config.btnAlign) className.push(doms[6] + '-' + config.btnAlign);
          return className.join(' ');
        }() +'">'+ button +'</div>'
      }() : '')
      + (config.resize ? '<span class="layui-layer-resize"></span>' : '')
    + '</div>'
  ], titleHTML, $('<div class="'+ doms.MOVE +'" id="'+ doms.MOVE +'"></div>'));
  return that;
};

// 创建骨架
Class.pt.creat = function(){
  var that = this;
  var config = that.config;
  var times = that.index, nodeIndex;
  var content = config.content;
  var conType = typeof content === 'object';
  var body = $('body');

  var setAnim = function(layero){
    // anim 兼容旧版 shift
    if(config.shift){
      config.anim = config.shift;
    }

    // 为兼容 jQuery3.0 的 css 动画影响元素尺寸计算
    if(doms.anim[config.anim]){
      var animClass = 'layer-anim '+ doms.anim[config.anim];
      layero.addClass(animClass).one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
        $(this).removeClass(animClass);
      });
    }
  }

  // 若 id 对应的弹层已经存在，则不重新创建
  if(config.id && $('.'+ doms[0]).find('#'+ config.id)[0]){
    return (function(){
      var layero = $('#'+ config.id).closest('.'+ doms[0]);
      var index = layero.attr('times');
      var options = layero.data('config');
      var elemShade = $('#'+ doms.SHADE + index);
      
      var maxminStatus = layero.data('maxminStatus') || {};
      // 若弹层为最小化状态，则点击目标元素时，自动还原
      if(maxminStatus === 'min'){
        layer.restore(index);
      } else if(options.hideOnClose){
        elemShade.show();
        layero.show();
        setAnim(layero);
        setTimeout(function(){
          elemShade.css({opacity: elemShade.data(SHADE_KEY)});
        }, 10);
      }
    })();
  }

  // 是否移除活动元素的焦点
  if(config.removeFocus) {
    document.activeElement.blur(); // 将原始的聚焦节点失焦
  }

  // 初始化 area 属性
  if(typeof config.area === 'string'){
    config.area = config.area === 'auto' ? ['', ''] : [config.area, ''];
  }
  
  if(layer.ie == 6){
    config.fixed = false;
  }
  
  switch(config.type){
    case 0:
      config.btn = ('btn' in config) ? config.btn : ready.btn[0];
      layer.closeAll('dialog');
    break;
    case 2:
      var content = config.content = conType ? config.content : [config.content||'', 'auto'];
      config.content = '<iframe scrolling="'+ (config.content[1]||'auto') +'" allowtransparency="true" id="'+ doms[4] +''+ times +'" name="'+ doms[4] +''+ times +'" onload="this.className=\'\';" class="layui-layer-load" frameborder="0" src="' + config.content[0] + '"></iframe>';
    break;
    case 3:
      delete config.title;
      delete config.closeBtn;
      config.icon === -1 && (config.icon === 0);
      layer.closeAll('loading');
    break;
    case 4:
      conType || (config.content = [config.content, 'body']);
      config.follow = config.content[1];
      config.content = config.content[0] + '<i class="layui-layer-TipsG"></i>';
      delete config.title;
      config.tips = typeof config.tips === 'object' ? config.tips : [config.tips, true];
      config.tipsMore || layer.closeAll('tips');
    break;
  }
  
  // 建立容器
  that.vessel(conType, function(html, titleHTML, moveElem){
    body.append(html[0]);
    conType ? function(){
      (config.type == 2 || config.type == 4) ? function(){
        $('body').append(html[1]);
      }() : function(){
        if(!content.parents('.'+doms[0])[0]){
          content.data('display', content.css('display')).show().addClass('layui-layer-wrap').wrap(html[1]);
          $('#'+ doms[0] + times).find('.'+doms[5]).before(titleHTML);
        }
      }();
    }() : body.append(html[1]);
    $('#'+ doms.MOVE)[0] || body.append(ready.moveElem = moveElem);
    
    that.layero = $('#'+ doms[0] + times);
    that.shadeo = $('#'+ doms.SHADE + times);
    
    config.scrollbar || ready.setScrollbar(times);
  }).auto(times);
  
  // 遮罩
  that.shadeo.css({
    'background-color': config.shade[1] || '#000'
    ,'opacity': config.shade[0] || config.shade
    ,'transition': config.shade[2] || ''
  });
  that.shadeo.data(SHADE_KEY, config.shade[0] || config.shade);

  config.type == 2 && layer.ie == 6 && that.layero.find('iframe').attr('src', content[0]);

  // 坐标自适应浏览器窗口尺寸
  config.type == 4 ? that.tips() : function(){
    that.offset()
    // 首次弹出时，若 css 尚未加载，则等待 css 加载完毕后，重新设定尺寸
    parseInt(ready.getStyle(document.getElementById(doms.MOVE), 'z-index')) ||  function(){
      that.layero.css('visibility', 'hidden');
      layer.ready(function(){
        that.offset();
        that.layero.css('visibility', 'visible');
      });
    }();
  }();
  
  // 若是固定定位，则跟随 resize 事件来自适应坐标
  if(config.fixed){
    if(!ready.events.resize[that.index]){
      ready.events.resize[that.index] = function(){
        that.resize();
      };
      // 此处 resize 事件不会一直叠加，当关闭弹层时会移除该事件
      win.on('resize', ready.events.resize[that.index]);
    }
  }
  
  config.time <= 0 || setTimeout(function(){
    layer.close(that.index);
  }, config.time);
  that.move().callback();
  setAnim(that.layero);
  
  // 记录配置信息
  that.layero.data('config', config);
};

// 当前实例的 resize 事件
Class.pt.resize = function(){
  var that = this;
  var config = that.config;
  
  that.offset();
  (/^\d+%$/.test(config.area[0]) || /^\d+%$/.test(config.area[1])) && that.auto(that.index);
  config.type == 4 && that.tips();
};

// 自适应
Class.pt.auto = function(index){
  var that = this, config = that.config, layero = $('#'+ doms[0] + index);
  
  if(config.area[0] === '' && config.maxWidth > 0){
    // 适配 ie7
    if(layer.ie && layer.ie < 8 && config.btn){
      layero.width(layero.innerWidth());
    }
    layero.outerWidth() > config.maxWidth && layero.width(config.maxWidth);
  }
  
  var area = [layero.innerWidth(), layero.innerHeight()];
  var titHeight = layero.find(doms[1]).outerHeight() || 0;
  var btnHeight = layero.find('.'+doms[6]).outerHeight() || 0;
  var setHeight = function(elem){
    elem = layero.find(elem);
    elem.height(area[1] - titHeight - btnHeight - 2*(parseFloat(elem.css('padding-top'))|0));
  };

  switch(config.type){
    case 2: 
      setHeight('iframe');
    break;
    default:
      if(config.area[1] === ''){
        if(config.maxHeight > 0 && layero.outerHeight() > config.maxHeight){
          area[1] = config.maxHeight;
          setHeight('.'+doms[5]);
        } else if(config.fixed && area[1] >= win.height()){
          area[1] = win.height();
          setHeight('.'+doms[5]);
        }
      } else {
        setHeight('.'+doms[5]);
      }
    break;
  }
  
  return that;
};

// 计算坐标
Class.pt.offset = function(){
  var that = this, config = that.config, layero = that.layero;
  var area = [layero.outerWidth(), layero.outerHeight()];
  var type = typeof config.offset === 'object';
  that.offsetTop = (win.height() - area[1])/2;
  that.offsetLeft = (win.width() - area[0])/2;
  
  if(type){
    that.offsetTop = config.offset[0];
    that.offsetLeft = config.offset[1]||that.offsetLeft;
  } else if(config.offset !== 'auto'){
    
    if(config.offset === 't'){ // 上
      that.offsetTop = 0;
    } else if(config.offset === 'r'){ // 右
      that.offsetLeft = win.width() - area[0];
    } else if(config.offset === 'b'){ // 下
      that.offsetTop = win.height() - area[1];
    } else if(config.offset === 'l'){ // 左
      that.offsetLeft = 0;
    } else if(config.offset === 'lt'){ // 左上
      that.offsetTop = 0;
      that.offsetLeft = 0;
    } else if(config.offset === 'lb'){ // 左下
      that.offsetTop = win.height() - area[1];
      that.offsetLeft = 0;
    } else if(config.offset === 'rt'){ // 右上
      that.offsetTop = 0;
      that.offsetLeft = win.width() - area[0];
    } else if(config.offset === 'rb'){ // 右下
      that.offsetTop = win.height() - area[1];
      that.offsetLeft = win.width() - area[0];
    } else {
      that.offsetTop = config.offset;
    }
    
  }
 
  if(!config.fixed){
    that.offsetTop = /%$/.test(that.offsetTop) ? 
      win.height()*parseFloat(that.offsetTop)/100
    : parseFloat(that.offsetTop);
    that.offsetLeft = /%$/.test(that.offsetLeft) ? 
      win.width()*parseFloat(that.offsetLeft)/100
    : parseFloat(that.offsetLeft);
    that.offsetTop += win.scrollTop();
    that.offsetLeft += win.scrollLeft();
  }
  
  // 最小化窗口时的自适应
  if(layero.data('maxminStatus') === 'min'){
    that.offsetTop = win.height() - (layero.find(doms[1]).outerHeight() || 0);
    that.offsetLeft = layero.css('left');
  }

  // 设置坐标
  layero.css({
    top: that.offsetTop, 
    left: that.offsetLeft
  });
};

// Tips
Class.pt.tips = function(){
  var that = this, config = that.config, layero = that.layero;
  var layArea = [layero.outerWidth(), layero.outerHeight()], follow = $(config.follow);
  if(!follow[0]) follow = $('body');
  var goal = {
    width: follow.outerWidth(),
    height: follow.outerHeight(),
    top: follow.offset().top,
    left: follow.offset().left
  }, tipsG = layero.find('.layui-layer-TipsG');
  
  var guide = config.tips[0];
  config.tips[1] || tipsG.remove();
  
  goal.autoLeft = function(){
    if(goal.left + layArea[0] - win.width() > 0){
      goal.tipLeft = goal.left + goal.width - layArea[0];
      tipsG.css({right: 12, left: 'auto'});
    } else {
      goal.tipLeft = goal.left;
    }
  };
  
  // 辨别 tips 的方位
  // 21 为箭头大小 8*2 + 箭头相对父元素的top偏移 5
  goal.where = [function(){ // 上        
    goal.autoLeft();
    goal.tipTop = goal.top - layArea[1] - 10;
    tipsG.removeClass('layui-layer-TipsB').addClass('layui-layer-TipsT').css('border-right-color', config.tips[1]);
  }, function(){ // 右
    goal.tipLeft = goal.left + goal.width + 10;
    goal.tipTop = goal.top - (goal.height * 0.75 < 21 ? 21 - goal.height * 0.5 : 0);
    goal.tipTop = Math.max(goal.tipTop, 0);
    tipsG.removeClass('layui-layer-TipsL').addClass('layui-layer-TipsR').css('border-bottom-color', config.tips[1]); 
  }, function(){ // 下
    goal.autoLeft();
    goal.tipTop = goal.top + goal.height + 10;
    tipsG.removeClass('layui-layer-TipsT').addClass('layui-layer-TipsB').css('border-right-color', config.tips[1]);
  }, function(){ // 左
    goal.tipLeft = goal.left - layArea[0] - 10;
    goal.tipTop = goal.top - (goal.height * 0.75 < 21 ? 21 - goal.height * 0.5 : 0);
    goal.tipTop = Math.max(goal.tipTop, 0);
    tipsG.removeClass('layui-layer-TipsR').addClass('layui-layer-TipsL').css('border-bottom-color', config.tips[1]);
  }];
  goal.where[guide-1]();
  
  /* 8*2为小三角形占据的空间 */
  if(guide === 1){
    goal.top - (win.scrollTop() + layArea[1] + 8*2) < 0 && goal.where[2]();
  } else if(guide === 2){
    win.width() - (goal.left + goal.width + layArea[0] + 8*2) > 0 || goal.where[3]()
  } else if(guide === 3){
    (goal.top - win.scrollTop() + goal.height + layArea[1] + 8*2) - win.height() > 0 && goal.where[0]();
  } else if(guide === 4){
     layArea[0] + 8*2 - goal.left > 0 && goal.where[1]()
  }

  layero.find('.'+doms[5]).css({
    'background-color': config.tips[1], 
    'padding-right': (config.closeBtn ? '30px' : '')
  });
  layero.css({
    left: goal.tipLeft - (config.fixed ? win.scrollLeft() : 0), 
    top: goal.tipTop  - (config.fixed ? win.scrollTop() : 0)
  });
}

// 拖拽层
Class.pt.move = function(){
  var that = this;
  var config = that.config;
  var _DOC = $(document);
  var layero = that.layero;
  var DATA_NAME = ['LAY_MOVE_DICT', 'LAY_RESIZE_DICT'];
  var moveElem = layero.find(config.move);
  var resizeElem = layero.find('.layui-layer-resize');
  
  // 给指定元素添加拖动光标
  if(config.move) moveElem.css('cursor', 'move');
  
  // 按下拖动元素
  moveElem.on('mousedown', function(e){
    if (e.button) {return;} // 不是左键不处理
    var othis = $(this);
    var dict = {};
    
    if(config.move){
      dict.layero = layero;
      dict.config = config;
      dict.offset = [
        e.clientX - parseFloat(layero.css('left')),
        e.clientY - parseFloat(layero.css('top'))
      ];
      
      othis.data(DATA_NAME[0], dict);
      ready.eventMoveElem = othis;
      ready.moveElem.css('cursor', 'move').show();
    }
    
    e.preventDefault();
  });
  
  // 按下右下角拉伸
  resizeElem.on('mousedown', function(e){
    var othis = $(this);
    var dict = {};
    
    if(config.resize){
      dict.layero = layero;
      dict.config = config;
      dict.offset = [e.clientX, e.clientY];
      dict.index = that.index;
      dict.area = [
        layero.outerWidth()
        ,layero.outerHeight()
      ];
      
      othis.data(DATA_NAME[1], dict);
      ready.eventResizeElem = othis;
      ready.moveElem.css('cursor', 'se-resize').show();
    }
    
    e.preventDefault();
  });
  
  // 拖动元素，避免多次调用实例造成事件叠加
  if(ready.docEvent) return that;
  _DOC.on('mousemove', function(e){
    // 拖拽移动
    if(ready.eventMoveElem){
      var dict = ready.eventMoveElem.data(DATA_NAME[0]) || {}
      ,layero = dict.layero
      ,config = dict.config;
      
      var X = e.clientX - dict.offset[0];
      var Y = e.clientY - dict.offset[1];
      var fixed = layero.css('position') === 'fixed';
      
      e.preventDefault();
      
      dict.stX = fixed ? 0 : win.scrollLeft();
      dict.stY = fixed ? 0 : win.scrollTop();

      // 控制元素不被拖出窗口外
      if(!config.moveOut){
        var setRig = win.width() - layero.outerWidth() + dict.stX;
        var setBot = win.height() - layero.outerHeight() + dict.stY;  
        X < dict.stX && (X = dict.stX);
        X > setRig && (X = setRig); 
        Y < dict.stY && (Y = dict.stY);
        Y > setBot && (Y = setBot);
      }
      
      // 拖动时跟随鼠标位置
      layero.css({
        left: X,
        top: Y
      });
    }
    
    // Resize
    if(ready.eventResizeElem){
      var dict = ready.eventResizeElem.data(DATA_NAME[1]) || {};
      var config = dict.config;
      
      var X = e.clientX - dict.offset[0];
      var Y = e.clientY - dict.offset[1];
      
      e.preventDefault();
      
      // 拉伸宽高
      layer.style(dict.index, {
        width: dict.area[0] + X
        ,height: dict.area[1] + Y
      });
      
      config.resizing && config.resizing(dict.layero);
    }
  }).on('mouseup', function(e){
    if(ready.eventMoveElem){
      var dict = ready.eventMoveElem.data(DATA_NAME[0]) || {};
      var config = dict.config;
      
      ready.eventMoveElem.removeData(DATA_NAME[0]);
      delete ready.eventMoveElem;
      ready.moveElem.hide();
      config.moveEnd && config.moveEnd(dict.layero);
    }
    if(ready.eventResizeElem){
      ready.eventResizeElem.removeData(DATA_NAME[1]);
      delete ready.eventResizeElem;
      ready.moveElem.hide();
    }
  });
  
  ready.docEvent = true; // 已给 document 执行全局事件
  return that;
};

Class.pt.callback = function(){
  var that = this, layero = that.layero, config = that.config;
  that.openLayer();
  if(config.success){
    if(config.type == 2){
      layero.find('iframe').on('load', function(){
        config.success(layero, that.index, that);
      });
    } else {
      config.success(layero, that.index, that);
    }
  }
  layer.ie == 6 && that.IE6(layero);
  
  // 按钮
  layero.find('.'+ doms[6]).children('a').on('click', function(){
    var index = $(this).index();
    if(index === 0){
      if(config.yes){
        config.yes(that.index, layero, that);
      } else if(config['btn1']){
        config['btn1'](that.index, layero, that);
      } else {
        layer.close(that.index);
      }
    } else {
      var close = config['btn'+(index+1)] && config['btn'+(index+1)](that.index, layero, that);
      close === false || layer.close(that.index);
    }
  });
  
  // 取消
  function cancel(){
    var close = config.cancel && config.cancel(that.index, layero, that);
    close === false || layer.close(that.index);
  }
  
  // 右上角关闭回调
  layero.find('.'+ doms[7]).on('click', cancel);
  
  // 点遮罩关闭
  if(config.shadeClose){
    that.shadeo.on('click', function(){
      layer.close(that.index);
    });
  } 
  
  // 最小化
  layero.find('.layui-layer-min').on('click', function(){
    var min = config.min && config.min(layero, that.index, that);
    min === false || layer.min(that.index, config);
  });
  
  // 全屏/还原
  layero.find('.layui-layer-max').on('click', function(){
    if($(this).hasClass('layui-layer-maxmin')){
      layer.restore(that.index);
      config.restore && config.restore(layero, that.index, that);
    } else {
      layer.full(that.index, config);
      setTimeout(function(){
        config.full && config.full(layero, that.index, that);
      }, 100);
    }
  });

  config.end && (ready.end[that.index] = config.end);
};

// for ie6 恢复 select
ready.reselect = function(){
  $.each($('select'), function(index , value){
    var sthis = $(this);
    if(!sthis.parents('.'+doms[0])[0]){
      (sthis.attr('layer') == 1 && $('.'+doms[0]).length < 1) && sthis.removeAttr('layer').show(); 
    }
    sthis = null;
  });
}; 

Class.pt.IE6 = function(layero){
  // 隐藏select
  $('select').each(function(index , value){
    var sthis = $(this);
    if(!sthis.parents('.'+doms[0])[0]){
      sthis.css('display') === 'none' || sthis.attr({'layer' : '1'}).hide();
    }
    sthis = null;
  });
};

// 需依赖原型的对外方法
Class.pt.openLayer = function(){
  var that = this;
  
  // 置顶当前窗口
  layer.zIndex = that.config.zIndex;
  layer.setTop = function(layero){
    var setZindex = function(){
      layer.zIndex++;
      layero.css('z-index', layer.zIndex + 1);
    };
    layer.zIndex = parseInt(layero[0].style.zIndex);
    layero.on('mousedown', setZindex);
    return layer.zIndex;
  };
};

// 记录宽高坐标，用于还原
ready.record = function(layero){
  if(!layero[0]) return window.console && console.error('index error');
  var type = layero.attr('type');
  var contentElem = layero.find('.layui-layer-content');
  var contentRecordHeightElem = type === ready.type[2] ? contentElem.children('iframe') : contentElem;
  var area = [
    layero[0].style.width || ready.getStyle(layero[0], 'width'),
    layero[0].style.height || ready.getStyle(layero[0], 'height'),
    layero.position().top, 
    layero.position().left + parseFloat(layero.css('margin-left'))
  ];
  layero.find('.layui-layer-max').addClass('layui-layer-maxmin');
  layero.attr({area: area});
  contentElem.data(RECORD_HEIGHT_KEY, ready.getStyle(contentRecordHeightElem[0], 'height'));
};

// 设置页面滚动条
ready.setScrollbar = function(index){
  doms.html.css('overflow', 'hidden').attr('layer-full', index);
};

// 恢复页面滚动条
ready.restScrollbar = function(index){
  if(doms.html.attr('layer-full') == index){
    doms.html[0].style[doms.html[0].style.removeProperty 
      ? 'removeProperty' 
    : 'removeAttribute']('overflow');
    doms.html.removeAttr('layer-full');
  }
};

/** 内置成员 */

window.layer = layer;

// 获取子 iframe 的 DOM
layer.getChildFrame = function(selector, index){
  index = index || $('.'+doms[4]).attr('times');
  return $('#'+ doms[0] + index).find('iframe').contents().find(selector);  
};

// 得到当前 iframe 层的索引，子 iframe 时使用
layer.getFrameIndex = function(name){
  return $('#'+ name).parents('.'+doms[4]).attr('times');
};

// iframe 层自适应宽高
layer.iframeAuto = function(index){
  if(!index) return;
  var heg = layer.getChildFrame('html', index).outerHeight();
  var layero = $('#'+ doms[0] + index);
  var titHeight = layero.find(doms[1]).outerHeight() || 0;
  var btnHeight = layero.find('.'+doms[6]).outerHeight() || 0;
  layero.css({height: heg + titHeight + btnHeight});
  layero.find('iframe').css({height: heg});
};

// 重置 iframe url
layer.iframeSrc = function(index, url){
  $('#'+ doms[0] + index).find('iframe').attr('src', url);
};

// 设定层的样式
layer.style = function(index, options, limit){
  var layero = $('#'+ doms[0] + index);
  var contentElem = layero.find('.layui-layer-content');
  var type = layero.attr('type');
  var titHeight = layero.find(doms[1]).outerHeight() || 0;
  var btnHeight = layero.find('.'+doms[6]).outerHeight() || 0;
  var minLeft = layero.attr('minLeft');
  
  // loading 和 tips 层不允许更改
  if(type === ready.type[3] || type === ready.type[4]){
    return;
  }
  
  if(!limit){
    if(parseFloat(options.width) <= 260){
      options.width = 260;
    }
    
    if(parseFloat(options.height) - titHeight - btnHeight <= 64){
      options.height = 64 + titHeight + btnHeight;
    }
  }
  layero.css(options);
  btnHeight = layero.find('.'+doms[6]).outerHeight() || 0;
  
  if(type === ready.type[2]){
    layero.find('iframe').css({
      height: (typeof options.height === 'number' ? options.height : layero.height()) - titHeight - btnHeight
    });
  } else {
    contentElem.css({
      height: (typeof options.height === 'number' ? options.height : layero.height()) - titHeight - btnHeight
      - parseFloat(contentElem.css('padding-top'))
      - parseFloat(contentElem.css('padding-bottom'))
    })
  }
};

// 最小化
layer.min = function(index, options){
  var layero = $('#'+ doms[0] + index);
  var maxminStatus = layero.data('maxminStatus');

  if(maxminStatus === 'min') return; // 当前的状态是否已经是最小化
  if(maxminStatus === 'max') layer.restore(index); // 若当前为最大化，则先还原后再最小化

  layero.data('maxminStatus', 'min');
  options = options || layero.data('config') || {};

  var shadeo = $('#'+ doms.SHADE + index);
  var elemMin = layero.find('.layui-layer-min');
  var titHeight = layero.find(doms[1]).outerHeight() || 0;
  var minLeft = layero.attr('minLeft'); // 最小化时的横坐标
  var hasMinLeft = typeof minLeft === 'string'; // 是否已经赋值过最小化坐标
  var left = hasMinLeft ? minLeft : (181*ready.minStackIndex)+'px';
  var position = layero.css('position');
  var minWidth = 180; // 最小化时的宽度
  var settings = {
    width: minWidth
    ,height: titHeight
    ,position: 'fixed'
    ,overflow: 'hidden'
  };

  ready.record(layero);  // 记录当前尺寸、坐标，用于还原

  // 简易最小化补位
  if(ready.minStackArr.length > 0){
    left = ready.minStackArr[0];
    ready.minStackArr.shift();
  }

  // left 是否超出边界
  if(parseFloat(left) + minWidth  > win.width()){
    left = win.width() - minWidth - function(){
      ready.minStackArr.edgeIndex = ready.minStackArr.edgeIndex || 0;
      return ready.minStackArr.edgeIndex += 3;
    }();
    if(left < 0) left = 0;
  }
  
  // 是否堆叠在左下角
  if(options.minStack){
    settings.left = left;
    settings.top = win.height() - titHeight;
    hasMinLeft || ready.minStackIndex++; // 若未赋值过最小化坐标，则最小化操作索引自增
    layero.attr('minLeft', left);
  }
  
  layero.attr('position', position);
  layer.style(index, settings, true);

  elemMin.hide();
  layero.attr('type') === 'page' && layero.find(doms[4]).hide();
  ready.restScrollbar(index);

  // 隐藏遮罩
  shadeo.hide();
};

// 还原
layer.restore = function(index){
  var layero = $('#'+ doms[0] + index);
  var shadeo = $('#'+ doms.SHADE + index);
  var contentElem = layero.find('.layui-layer-content');
  var area = layero.attr('area').split(',');
  var type = layero.attr('type');
  var options = layero.data('config') || {};
  var contentRecordHeight = contentElem.data(RECORD_HEIGHT_KEY);

  layero.removeData('maxminStatus'); // 移除最大最小状态
  
  // 恢复原来尺寸
  layer.style(index, {
    width: area[0], // 数值或百分比
    height: area[1],
    top: parseFloat(area[2]),
    left: parseFloat(area[3]),
    position: layero.attr('position'),
    overflow: 'visible'
  }, true);
  
  layero.find('.layui-layer-max').removeClass('layui-layer-maxmin');
  layero.find('.layui-layer-min').show();
  type === 'page' && layero.find(doms[4]).show();

  // 恢复页面滚动条弹层打开时的状态
  options.scrollbar ? ready.restScrollbar(index) : ready.setScrollbar(index);

  // #1604
  if(contentRecordHeight !== undefined){
    contentElem.removeData(RECORD_HEIGHT_KEY);
    var contentRecordHeightElem = type === ready.type[2] ? contentElem.children('iframe') : contentElem;
    contentRecordHeightElem.css({height: contentRecordHeight});
  }
  
  // 恢复遮罩
  shadeo.show();
  // ready.events.resize[index](); // ?
};

// 全屏（最大化）
layer.full = function(index){
  var layero = $('#'+ doms[0] + index);
  var maxminStatus = layero.data('maxminStatus');

  if(maxminStatus === 'max') return // 检查当前的状态是否已经是最大化
  if(maxminStatus === 'min') layer.restore(index); // 若当前为最小化，则先还原后再最大化

  layero.data('maxminStatus', 'max');
  ready.record(layero); // 记录当前尺寸、坐标

  if(!doms.html.attr('layer-full')){
    ready.setScrollbar(index);
  }

  setTimeout(function(){
    var isfix = layero.css('position') === 'fixed';
    layer.style(index, {
      top: isfix ? 0 : win.scrollTop(),
      left: isfix ? 0 : win.scrollLeft(),
      width: '100%',
      height: '100%'
    }, true);
    layero.find('.layui-layer-min').hide();
  }, 100);
};

// 改变 title
layer.title = function(name, index){
  var title = $('#'+ doms[0] + (index || layer.index)).find(doms[1]);
  title.html(name);
};

// 关闭 layer 总方法
layer.close = function(index, callback){
  var layero = function(){
    var closest = $('.'+ doms[0]).children('#'+ index).closest('.'+ doms[0]);
    return closest[0] ? (
      index = closest.attr('times'),
      closest
    ) : $('#'+ doms[0] + index)
  }();
  var type = layero.attr('type');
  var options = layero.data('config') || {};
  var hideOnClose = options.id && options.hideOnClose; // 是否关闭时移除弹层容器

  if(!layero[0]) return;

  // 关闭动画
  var closeAnim = ({
    slideDown: 'layer-anim-slide-down-out',
    slideLeft: 'layer-anim-slide-left-out',
    slideUp: 'layer-anim-slide-up-out',
    slideRight: 'layer-anim-slide-right-out'
  })[options.anim] || 'layer-anim-close';

  // 移除主容器
  var remove = function(){
    var WRAP = 'layui-layer-wrap';

    // 是否关闭时隐藏弹层容器
    if(hideOnClose){
      layero.removeClass('layer-anim '+ closeAnim);
      return layero.hide();
    }

    // 是否为页面捕获层
    if(type === ready.type[1] && layero.attr('conType') === 'object'){
      layero.children(':not(.'+ doms[5] +')').remove();
      var wrap = layero.find('.'+WRAP);
      for(var i = 0; i < 2; i++){
        wrap.unwrap();
      }
      wrap.css('display', wrap.data('display')).removeClass(WRAP);
    } else {
      // 低版本 IE 回收 iframe
      if(type === ready.type[2]){
        try {
          var iframe = $('#'+ doms[4] + index)[0];
          iframe.contentWindow.document.write('');
          iframe.contentWindow.close();
          layero.find('.'+doms[5])[0].removeChild(iframe);
        } catch(e){}
      }
      layero[0].innerHTML = '';
      layero.remove();
    }

    typeof ready.end[index] === 'function' && ready.end[index]();
    delete ready.end[index];
    typeof callback === 'function' && callback();

    // 移除 reisze 事件
    if(ready.events.resize[index]){
      win.off('resize', ready.events.resize[index]);
      delete ready.events.resize[index];
    }
  };
  // 移除遮罩
  var shadeo = $('#'+ doms.SHADE + index);
  if((layer.ie && layer.ie < 10) || !options.isOutAnim){
    shadeo[hideOnClose ? 'hide' : 'remove']();
  }else{
    shadeo.css({opacity: 0});
    setTimeout(function(){
      shadeo[hideOnClose ? 'hide' : 'remove']();
    }, 350);
  }
  
  // 是否允许关闭动画
  if(options.isOutAnim){
    layero.addClass('layer-anim '+ closeAnim);
  }
  
  layer.ie == 6 && ready.reselect();
  ready.restScrollbar(index); 
  
  // 记住被关闭层的最小化堆叠坐标
  if(typeof layero.attr('minLeft') === 'string'){
    ready.minStackIndex--;
    ready.minStackArr.push(layero.attr('minLeft'));
  }
  
  if((layer.ie && layer.ie < 10) || !options.isOutAnim){
    remove()
  } else {
    setTimeout(function(){
      remove();
    }, 200);
  }
};

// 关闭所有层
layer.closeAll = function(type, callback){
  if(typeof type === 'function'){
    callback = type;
    type = null;
  }
  var domsElem = $('.'+doms[0]);
  $.each(domsElem, function(_index){
    var othis = $(this);
    var is = type ? (othis.attr('type') === type) : 1;
    is && layer.close(othis.attr('times'), _index === domsElem.length - 1 ? callback : null);
    is = null;
  });
  if(domsElem.length === 0) typeof callback === 'function' && callback();
};

// 根据弹层类型关闭最近打开的层
layer.closeLast = function(type, callback){
  var layerIndexList = [];
  var isArrayType = $.isArray(type);
  $(typeof type === 'string' ? '.layui-layer-' + type : '.layui-layer').each(function(i, el){
    var layero = $(el);
    var shouldSkip = (isArrayType && type.indexOf(layero.attr('type')) === -1) || layero.css('display') === 'none';
    if(shouldSkip) return true;
    layerIndexList.push(Number(layero.attr('times')));
  });
  if(layerIndexList.length > 0){
    var layerIndexMax = Math.max.apply(null, layerIndexList);
    layer.close(layerIndexMax, callback);
  }
};


/*
 * 拓展模块，layui 开始合并在一起
 */


var cache = layer.cache || {};
var skin = function(type){
  return (cache.skin ? (' ' + cache.skin + ' ' + cache.skin + '-'+type) : '');
}; 
 
// 仿系统 prompt
layer.prompt = function(options, yes){
  var style = '', placeholder = '';
  options = options || {};
  
  if(typeof options === 'function') yes = options;
  
  if(options.area){
    var area = options.area;
    style = 'style="width: '+ area[0] +'; height: '+ area[1] + ';"';
    delete options.area;
  }
  if (options.placeholder) {
    placeholder = ' placeholder="' + options.placeholder + '"';
  }
  var prompt, content = options.formType == 2 ? '<textarea class="layui-layer-input"' + style + placeholder + '></textarea>' : function () {
    return '<input type="' + (options.formType == 1 ? 'password' : 'text') + '" class="layui-layer-input"' + placeholder + '>';
  }();
  
  var success = options.success;
  delete options.success;
  
  return layer.open($.extend({
    type: 1,
    btn: ['确定','取消'],
    content: content,
    skin: 'layui-layer-prompt' + skin('prompt'),
    maxWidth: win.width(),
    success: function(layero){
      prompt = layero.find('.layui-layer-input');
      prompt.val(options.value || '').focus();
      typeof success === 'function' && success(layero);
    },
    resize: false,
    yes: function(index){
      var value = prompt.val();
      if(value.length > (options.maxlength||500)) {
        layer.tips('最多输入'+ (options.maxlength || 500) +'个字符', prompt, {tips: 1});
      } else {
        yes && yes(value, index, prompt);
      }
    }
  }, options));
};

// tab 层
layer.tab = function(options){
  options = options || {};
  
  var tab = options.tab || {};
  var THIS = 'layui-this';
  var success = options.success;
  
  delete options.success;
  
  return layer.open($.extend({
    type: 1,
    skin: 'layui-layer-tab' + skin('tab'),
    resize: false,
    title: function(){
      var len = tab.length, ii = 1, str = '';
      if(len > 0){
        str = '<span class="'+ THIS +'">'+ tab[0].title +'</span>';
        for(; ii < len; ii++){
          str += '<span>'+ tab[ii].title +'</span>';
        }
      }
      return str;
    }(),
    content: '<ul class="layui-layer-tabmain">'+ function(){
      var len = tab.length, ii = 1, str = '';
      if(len > 0){
        str = '<li class="layui-layer-tabli '+ THIS +'">'+ (tab[0].content || 'no content') +'</li>';
        for(; ii < len; ii++){
          str += '<li class="layui-layer-tabli">'+ (tab[ii].content || 'no  content') +'</li>';
        }
      }
      return str;
    }() +'</ul>',
    success: function(layero){
      var btn = layero.find('.layui-layer-title').children();
      var main = layero.find('.layui-layer-tabmain').children();
      btn.on('mousedown', function(e){
        e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
        var othis = $(this), index = othis.index();
        othis.addClass(THIS).siblings().removeClass(THIS);
        main.eq(index).show().siblings().hide();
        typeof options.change === 'function' && options.change(index);
      });
      typeof success === 'function' && success(layero);
    }
  }, options));
};

// 图片层
layer.photos = function(options, loop, key){
  var dict = {};

  // 默认属性
  options = $.extend(true, {
    toolbar: true,
    footer: true
  }, options);

  if(!options.photos) return;
  
  // 若 photos 并非选择器或 jQuery 对象，则为普通 object
  var isObject = !(typeof options.photos === 'string' || options.photos instanceof $);
  var photos = isObject ? options.photos : {};
  var data = photos.data || [];
  var start = photos.start || 0;
  var success = options.success;
  
  dict.imgIndex = (start|0) + 1;
  options.img = options.img || 'img';
  delete options.success;
  
  // 若 options.photos 不是一个对象
  if(!isObject){ // 页面直接获取
    var parent = $(options.photos), pushData = function(){
      data = [];
      parent.find(options.img).each(function(index){
        var othis = $(this);
        othis.attr('layer-index', index);
        data.push({
          alt: othis.attr('alt'),
          pid: othis.attr('layer-pid'),
          src: othis.attr('lay-src') || othis.attr('layer-src') || othis.attr('src'),
          thumb: othis.attr('src')
        });
      });
    };
    
    pushData();
    
    if (data.length === 0) return;
    
    loop || parent.on('click', options.img, function(){
      pushData();
      var othis = $(this), index = othis.attr('layer-index'); 
      layer.photos($.extend(options, {
        photos: {
          start: index,
          data: data,
          tab: options.tab
        },
        full: options.full
      }), true);
    });
    
    // 不直接弹出
    if (!loop) return;
  } else if (data.length === 0){
    return layer.msg('没有图片');
  }
  
  // 上一张
  dict.imgprev = function(key){
    dict.imgIndex--;
    if(dict.imgIndex < 1){
      dict.imgIndex = data.length;
    }
    dict.tabimg(key);
  };
  
  // 下一张
  dict.imgnext = function(key,errorMsg){
    dict.imgIndex++;
    if(dict.imgIndex > data.length){
      dict.imgIndex = 1;
      if (errorMsg) {return}
    }
    dict.tabimg(key)
  };
  
  // 方向键
  dict.keyup = function(event){
    if(!dict.end){
      var code = event.keyCode;
      event.preventDefault();
      if(code === 37){
        dict.imgprev(true);
      } else if(code === 39) {
        dict.imgnext(true);
      } else if(code === 27) {
        layer.close(dict.index);
      }
    }
  }
  
  // 切换
  dict.tabimg = function(key){
    if(data.length <= 1) return;
    photos.start = dict.imgIndex - 1;
    layer.close(dict.index);
    return layer.photos(options, true, key);
  }

  dict.isNumber = function (n) {
    return typeof n === 'number' && !isNaN(n);
  }

  dict.image = {};

  dict.getTransform = function(opts){
    var transforms = [];
    var rotate = opts.rotate;
    var scaleX = opts.scaleX;
    var scale = opts.scale;

    if (dict.isNumber(rotate) && rotate !== 0) {
      transforms.push('rotate(' + rotate + 'deg)');
    }

    if (dict.isNumber(scaleX) && scaleX !== 1) {
      transforms.push('scaleX(' + scaleX + ')');
    }

    if (dict.isNumber(scale)) {
      transforms.push('scale(' + scale + ')');
    }

    return transforms.length ? transforms.join(' ') : 'none';
  }
  
  // 一些动作
  dict.event = function(layero, index, that){
    // 上一张
    dict.main.find('.layui-layer-photos-prev').on('click', function(event){
      event.preventDefault();
      dict.imgprev(true);
    });  
    
    // 下一张
    dict.main.find('.layui-layer-photos-next').on('click', function(event){
      event.preventDefault();
      dict.imgnext(true);
    });
    
    $(document).on('keyup', dict.keyup);

    // 头部工具栏事件
    layero.off('click').on('click','*[toolbar-event]', function () {
      var othis = $(this);
      var event = othis.attr('toolbar-event');
      switch (event) {
        case 'rotate':
          dict.image.rotate = ((dict.image.rotate || 0) + Number(othis.attr('data-option'))) % 360;
          dict.imgElem.css({
            transform: dict.getTransform(dict.image)
          });
          break;
        case 'scalex':
          dict.image.scaleX = dict.image.scaleX === -1 ? 1 : -1;
          dict.imgElem.css({
            transform: dict.getTransform(dict.image)
          });
          break;
        case 'zoom':
          var ratio = Number(othis.attr('data-option'));
          dict.image.scale = (dict.image.scale || 1) + ratio;
          // 缩小状态最小值
          if (ratio < 0 && dict.image.scale < 0 - ratio) {
            dict.image.scale = 0 - ratio;
          }
          dict.imgElem.css({
            transform: dict.getTransform(dict.image)
          });
          break;
        case 'reset':
          dict.image.scaleX = 1;
          dict.image.scale = 1;
          dict.image.rotate = 0;
          dict.imgElem.css({
            transform: 'none'
          });
          break;
        case 'close':
          layer.close(index);
          break;
      }
      that.offset();
      that.auto(index);
    });
    
    // 鼠标滚轮缩放图片事件
    dict.main.on('mousewheel DOMMouseScroll', function(e) {
      var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
      var zoomElem = dict.main.find('[toolbar-event="zoom"]');
      if (delta > 0) {
        zoomElem.eq(0).trigger('click');
      } else {
        zoomElem.eq(1).trigger('click');
      }
      e.preventDefault();
    });

    // 滑动切换图片事件，仅限 layui 中 
    if(window.layui || window.lay){
      var lay = window.layui.lay || window.lay;
      var touchEndCallback = function(e, state){
        var duration = Date.now() - state.timeStart;
        var speed = state.distanceX / duration;
        var threshold = win.width() / 3;
        var shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(state.distanceX) > threshold;
        if(!shouldSwipe) return;
        if(state.direction === 'left'){
          dict.imgnext(true);
        }else if(state.direction === 'right'){
          dict.imgprev(true);
        }
      }

      $.each([that.shadeo, dict.main], function(i, elem){
        lay.touchSwipe(elem, {
          onTouchEnd: touchEndCallback
        })
      })
    }
  };
  
  // 图片预加载
  function loadImage(url, callback, error) {   
    var img = new Image();
    img.src = url; 
    if(img.complete){
      return callback(img);
    }
    img.onload = function(){
      img.onload = null;
      callback(img);
    };
    img.onerror = function(e){
      img.onerror = null;
      error(e);
    };  
  }
  
  dict.loadi = layer.load(1, {
    shade: 'shade' in options ? false : [0.9, undefined, 'unset'],
    scrollbar: false
  });

  loadImage(data[start].src, function(img){
    layer.close(dict.loadi);
    
    var alt = data[start].alt || '';

    // 切换图片时不出现动画
    if(key) options.anim = -1;
    
    // 弹出图片层
    dict.index = layer.open($.extend({
      type: 1,
      id: 'layui-layer-photos',
      area: function(){
        var imgarea = [img.width, img.height];
        var winarea = [$(window).width() - 100, $(window).height() - 100];
        
        // 若实际图片的宽或者高比 屏幕大（那么进行缩放）
        if(!options.full && (imgarea[0]>winarea[0]||imgarea[1]>winarea[1])){
          var wh = [imgarea[0]/winarea[0],imgarea[1]/winarea[1]];// 取宽度缩放比例、高度缩放比例
          if(wh[0] > wh[1]){// 取缩放比例最大的进行缩放
            imgarea[0] = imgarea[0]/wh[0];
            imgarea[1] = imgarea[1]/wh[0];
          } else if(wh[0] < wh[1]){
            imgarea[0] = imgarea[0]/wh[1];
            imgarea[1] = imgarea[1]/wh[1];
          }
        }

        return [imgarea[0]+'px', imgarea[1]+'px']; 
      }(),
      title: false,
      shade: [0.9, undefined, 'unset'],
      shadeClose: true,
      closeBtn: false,
      move: '.layer-layer-photos-main img',
      moveType: 1,
      scrollbar: false,
      moveOut: true,
      anim: 5,
      isOutAnim: false,
      skin: 'layui-layer-photos' + skin('photos'),
      content: '<div class="layer-layer-photos-main">'
        + '<img src="'+ data[start].src +'" alt="'+ alt +'" layer-pid="'+ (data[start].pid || '') +'">'
        + function(){
          var arr = ['<div class="layui-layer-photos-pointer">'];

          // 左右箭头翻页
          if (data.length > 1) {
            arr.push(['<div class="layer-layer-photos-page">',
              '<span class="layui-icon layui-icon-left layui-layer-photos-prev"></span>',
              '<span class="layui-icon layui-icon-right layui-layer-photos-next"></span>',
            '</div>'].join(''));
          }

          // 头部工具栏
          if (options.toolbar) {
            arr.push([
              '<div class="layui-layer-photos-toolbar layui-layer-photos-header">',
                '<span toolbar-event="rotate" data-option="90" title="旋转"><i class="layui-icon layui-icon-refresh"></i></span>',
                '<span toolbar-event="scalex" title="变换"><i class="layui-icon layui-icon-slider"></i></span>',
                '<span toolbar-event="zoom" data-option="0.1" title="放大"><i class="layui-icon layui-icon-add-circle"></i></span>',
                '<span toolbar-event="zoom" data-option="-0.1" title="缩小"><i class="layui-icon layui-icon-reduce-circle"></i></span>',
                '<span toolbar-event="reset" title="还原"><i class="layui-icon layui-icon-refresh-1"></i></span>',
                '<span toolbar-event="close" title="关闭"><i class="layui-icon layui-icon-close"></i></span>',
              '</div>'
            ].join(''));
          }

          // 底部栏
          if (options.footer) {
            arr.push(['<div class="layui-layer-photos-toolbar layui-layer-photos-footer">',
              '<h3>'+ alt +'</h3>',
              '<em>'+ dict.imgIndex +' / '+ data.length +'</em>',
              '<a href="'+ data[start].src +'" target="_blank">查看原图</a>',
            '</div>'].join(''));
          }

          arr.push('</div>');
          return arr.join('');
        }()
      +'</div>',
      success: function(layero, index, that){
        dict.main = layero.find('.layer-layer-photos-main');
        dict.footer = layero.find('.layui-layer-photos-footer');
        dict.imgElem = dict.main.children('img');
        dict.event(layero, index, that);
        options.tab && options.tab(data[start], layero);
        typeof success === 'function' && success(layero);
      }, end: function(){
        dict.end = true;
        $(document).off('keyup', dict.keyup);
      }
    }, options));
  }, function(){
    layer.close(dict.loadi);
    layer.msg('当前图片地址异常，<br>是否继续查看下一张？', {
      time: 30000, 
      btn: ['下一张', '不看了'], 
      yes: function(){
        data.length > 1 && dict.imgnext(true,true);
      }
    });
  });
};

// 主入口
ready.run = function(_$){
  $ = _$;
  win = $(window);
  
  // 移动端兼容性处理
  // https://gitee.com/layui/layui/issues/I81WGC
  // https://github.com/jquery/jquery/issues/1729
  var agent = navigator.userAgent.toLowerCase();
  var isMobile = /android|iphone|ipod|ipad|ios/.test(agent)
  var _win = $(window);
  if(isMobile){
    $.each({Height: "height", Width: "width"}, function(propSuffix, funcName){
      var propName = 'inner' + propSuffix;
      win[funcName] = function(){
        return propName in window 
          ? window[propName]
          : _win[funcName]()
      }
    })
  }
  doms.html = $('html');
  layer.open = function(deliver){
    var o = new Class(deliver);
    return o.index;
  };
};

// 加载方式
window.layui && layui.define ? (
  layer.ready(),
  layui.define(['jquery','lay'], function(exports){ // layui
    layer.path = layui.cache.dir;
    ready.run(layui.$);

    // export api
    window.layer = layer;
    exports('layer', layer);
  })
) : (
  (typeof define === 'function' && define.amd) ? define(['jquery'], function(){ // requirejs
    ready.run(window.jQuery);
    return layer;
  }) : function(){ // 普通 script 标签引入
    layer.ready();
    ready.run(window.jQuery);
  }()
);

}(window);
/**
 * util 工具组件
 */

layui.define('jquery', function(exports){
  "use strict";

  var $ = layui.$;
  var hint = layui.hint();

  // 外部接口
  var util = {
    // 固定块
    fixbar: function(options){
      var ELEM = 'layui-fixbar';
      var $doc = $(document);

      // 默认可选项
      options = $.extend(true, {
        target: 'body', // fixbar 的插入目标选择器
        bars: [], //  bar 信息
        "default": true, // 是否显示默认 bar
        margin: 160, // 出现 top bar 的滚动条高度临界值
        duration: 320 // top bar 等动画时长（毫秒）
      }, options);

      // 目标元素对象
      var $target = $(options.target);

      // 滚动条所在元素对象
      var $scroll = options.scroll
        ? $(options.scroll)
      : $(options.target === 'body' ? $doc : $target)

      // 是否提供默认图标
      if(options['default']){
        // 兼容旧版本的一些属性
        if(options.bar1){
          options.bars.push({
            type: 'bar1',
            icon: 'layui-icon-chat'
          });
        }
        if(options.bar2){
          options.bars.push({
            type: 'bar2',
            icon: 'layui-icon-help'
          });
        }
        // 默认 top bar
        options.bars.push({
          type: 'top',
          icon: 'layui-icon-top'
        });
      }

      var elem = $('<ul>').addClass(ELEM);
      var elemTopBar;

      // 遍历生成 bars 节点
      layui.each(options.bars, function(i, item){
        var elemBar = $('<li class="layui-icon">');

        // 设置 bar 相关属性
        elemBar.addClass(item.icon).attr({
          'lay-type': item.type,
          'style': item.style || (options.bgcolor ? 'background-color: '+ options.bgcolor : '')
        }).html(item.content);

        // bar 点击事件
        elemBar.on('click', function(){
          var type = $(this).attr('lay-type');
          if(type === 'top'){
            (
              options.target === 'body'
                ? $('html,body')
              : $scroll
            ).animate({
              scrollTop : 0
            }, options.duration);
          }
          typeof options.click === 'function' && options.click.call(this, type);
        });

        // 自定义任意事件
        if(layui.type(options.on) === 'object'){
          layui.each(options.on, function(eventName, callback){
            elemBar.on(eventName, function(){
              var type = $(this).attr('lay-type');
              typeof callback === 'function' && callback.call(this, type);
            });
          })
        }

        // 获得 top bar 节点
        if(item.type === 'top'){
          elemBar.addClass('layui-fixbar-top');
          elemTopBar = elemBar;
        }

        elem.append(elemBar); // 插入 bar 节点
      });

      // 若目标元素已存在 fixbar，则移除旧的节点
      $target.find('.'+ ELEM).remove();

      // 向目标元素插入 fixbar 节点
      typeof options.css === 'object' && elem.css(options.css);
      $target.append(elem);

      // top bar 的显示隐藏
      if(elemTopBar){
        var lock;
        var setTopBar = (function setTopBar(){
          var top = $scroll.scrollTop();
          if(top >= options.margin){
            lock || (elemTopBar.show(), lock = 1);
          } else {
            lock && (elemTopBar.hide(), lock = 0);
          }
          return setTopBar;
        })();
      }

      // 根据 scrollbar 设置 fixbar 相关状态
      var timer;
      $scroll.on('scroll', function(){
        if(!setTopBar) return;
        clearTimeout(timer);
        timer = setTimeout(function(){
          setTopBar();
        }, 100);
      });
    },

    // 倒计时
    countdown: function(options){
      var that = this;

      // 默认可选项
      options = $.extend(true, {
        date: new Date(),
        now: new Date()
      }, options);

      // 兼容旧版参数
      var args = arguments;
      if(args.length > 1){
        options.date = new Date(args[0]);
        options.now = new Date(args[1]);
        options.clock = args[2];
      }

      // 实例对象
      var inst = {
        options: options,
        clear: function(){ // 清除计时器
          clearTimeout(inst.timer);
        },
        reload: function(opts){ // 重置倒计时
          this.clear();
          $.extend(true, this.options, {
            now: new Date()
          }, opts);
          count();
        }
      };

      typeof options.ready === 'function' && options.ready();

      // 计算倒计时
      var count = (function fn(){
        var date = new Date(options.date);
        var now = new Date(options.now);
        var countTime = function(time){
          return time > 0 ? time : 0;
        }(date.getTime() - now.getTime());
        var result = {
          d: Math.floor(countTime/(1000*60*60*24)), // 天
          h: Math.floor(countTime/(1000*60*60)) % 24, // 时
          m: Math.floor(countTime/(1000*60)) % 60, // 分
          s: Math.floor(countTime/1000) % 60 // 秒
        };
        var next = function(){
          now.setTime(now.getTime() + 1000);
          options.now = now;
          count();
        };

        // 兼容旧版返回值
        if(args.length > 1) result = [result.d,result.h,result.m,result.s]

        // 计时 - 以秒间隔
        inst.timer = setTimeout(next, 1000);
        typeof options.clock === 'function' && options.clock(result, inst);

        // 计时完成
        if(countTime <= 0){
          clearTimeout(inst.timer);
          typeof options.done === 'function' && options.done(result, inst);
        }

        return fn;
      })();

      return inst;
    },

    // 某个时间在当前时间的多久前
    timeAgo: function(time, onlyDate){
      var that = this;
      var arr = [[], []];
      var stamp = new Date().getTime() - new Date(time).getTime();

      // 返回具体日期
      if(stamp > 1000*60*60*24*31){
        stamp =  new Date(time);
        arr[0][0] = that.digit(stamp.getFullYear(), 4);
        arr[0][1] = that.digit(stamp.getMonth() + 1);
        arr[0][2] = that.digit(stamp.getDate());

        // 是否输出时间
        if(!onlyDate){
          arr[1][0] = that.digit(stamp.getHours());
          arr[1][1] = that.digit(stamp.getMinutes());
          arr[1][2] = that.digit(stamp.getSeconds());
        }
        return arr[0].join('-') + ' ' + arr[1].join(':');
      }

      // 30 天以内，返回「多久前」
      if(stamp >= 1000*60*60*24){
        return ((stamp/1000/60/60/24)|0) + ' 天前';
      } else if(stamp >= 1000*60*60){
        return ((stamp/1000/60/60)|0) + ' 小时前';
      } else if(stamp >= 1000*60*3){ // 3 分钟以内为：刚刚
        return ((stamp/1000/60)|0) + ' 分钟前';
      } else if(stamp < 0){
        return '未来';
      } else {
        return '刚刚';
      }
    },

    // 数字前置补零
    digit: function(num, length){
      var str = '';
      num = String(num);
      length = length || 2;
      for(var i = num.length; i < length; i++){
        str += '0';
      }
      return num < Math.pow(10, length) ? str + (num|0) : num;
    },

    // 转化为日期格式字符
    toDateString: function(time, format, options){
      // 若 null 或空字符，则返回空字符
      if(time === null || time === '') return '';

      // 引用自 dayjs
      // https://github.com/iamkun/dayjs/blob/v1.11.9/src/constant.js#L30
      var REGEX_FORMAT = /\[([^\]]+)]|y{1,4}|M{1,2}|d{1,2}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|SSS/g;
      var that = this;
      var date = new Date(function(){
        if(!time) return;
        return isNaN(time) ? time : (typeof time === 'string' ? parseInt(time) : time)
      }() || new Date())

      if(!date.getDate()) return hint.error('Invalid millisecond for "util.toDateString(millisecond)"'), '';

      var years = date.getFullYear();
      var month = date.getMonth();
      var days = date.getDate();
      var hours = date.getHours();
      var minutes = date.getMinutes();
      var seconds = date.getSeconds();
      var milliseconds = date.getMilliseconds();

      var defaultMeridiem = function(hours, minutes){
          var hm = hours * 100 + minutes;
          if (hm < 600) {
            return '凌晨';
          } else if (hm < 900) {
            return '早上';
          } else if (hm < 1100) {
            return '上午';
          } else if (hm < 1300) {
            return '中午';
          } else if (hm < 1800) {
            return '下午';
          }
          return '晚上';
      };

      var meridiem = (options && options.customMeridiem) || defaultMeridiem;

      var matches = {
        yy: function(){return String(years).slice(-2);},
        yyyy: function(){return that.digit(years, 4);},
        M: function(){return String(month + 1);},
        MM: function(){return that.digit(month + 1);},
        d: function(){return String(days);},
        dd: function(){return that.digit(days);},
        H: function(){return String(hours);},
        HH: function(){return that.digit(hours);},
        h: function(){return String(hours % 12 || 12);},
        hh: function(){return that.digit(hours % 12 || 12);},
        A: function(){return meridiem(hours, minutes);},
        m: function(){return String(minutes);},
        mm: function(){return that.digit(minutes);},
        s: function(){return String(seconds);},
        ss: function(){return that.digit(seconds);},
        SSS: function(){return that.digit(milliseconds, 3);}
      }

      format = format || 'yyyy-MM-dd HH:mm:ss';

      return format.replace(REGEX_FORMAT, function(match, $1) {
        return $1 || (matches[match] && matches[match]()) || match;
      });
    },

    // 转义 html
    escape: function(html){
      var exp = /[<"'>]|&(?=#[a-zA-Z0-9]+)/g;
      if(html === undefined || html === null) return '';

      html += '';
      if(!exp.test(html)) return html;

      return html.replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
      .replace(/</g, '&lt;').replace(/>/g, '&gt;')
      .replace(/'/g, '&#39;').replace(/"/g, '&quot;');
    },

    // 还原转义的 html
    unescape: function(html){
      if(html === undefined || html === null) html = '';
      html += '';

      return html.replace(/\&amp;/g, '&')
      .replace(/\&lt;/g, '<').replace(/\&gt;/g, '>')
      .replace(/\&#39;/g, '\'').replace(/\&quot;/g, '"');
    },

    // 打开新窗口
    openWin: function(options){
      var win;
      options = options || {};
      win = options.window || window.open((options.url || ''), options.target, options.specs);
      if(options.url) return;
      win.document.open('text/html', 'replace');
      win.document.write(options.content || '');
      win.document.close();
    },

    // 让指定的元素保持在可视区域
    toVisibleArea: function(options){
      options = $.extend({
        margin: 160, // 触发动作的边界值
        duration: 200, // 动画持续毫秒数
        type: 'y' // 触发方向，x 水平、y 垂直
      }, options);

      if(!options.scrollElem[0] || !options.thisElem[0]) return;

      var scrollElem = options.scrollElem // 滚动元素
      var thisElem = options.thisElem // 目标元素
      var vertical = options.type === 'y' // 是否垂直方向
      var SCROLL_NAME = vertical ? 'scrollTop' : 'scrollLeft' // 滚动方法
      var OFFSET_NAME = vertical ? 'top' : 'left' // 坐标方式
      var scrollValue = scrollElem[SCROLL_NAME]() // 当前滚动距离
      var size = scrollElem[vertical ? 'height' : 'width']() // 滚动元素的尺寸
      var scrollOffset = scrollElem.offset()[OFFSET_NAME] // 滚动元素所处位置
      var thisOffset = thisElem.offset()[OFFSET_NAME] - scrollOffset // 目标元素当前的所在位置
      var obj = {};

      // 边界满足条件
      if(thisOffset > size - options.margin || thisOffset < options.margin){
        obj[SCROLL_NAME] = thisOffset - size/2 + scrollValue
        scrollElem.animate(obj, options.duration);
      }
    },

    /**
     * 批量事件
     * @param {string} [attr="lay-on"] - 触发事件的元素属性名
     * @param {Object.<string, Function>} events - 事件集合
     * @param {Object} [options] - 参数的更多选项
     * @param {(string|HTMLElement|JQuery)} [options.elem="body"] - 触发事件的委托元素
     * @param {string} [options.trigger="click"] - 事件触发的方式
     * @returns {Object} 返回当前 events 参数设置的事件集合
     */
    on: function(attr, events, options) {
      // 若参数一为 object 类型，则为事件集，且省略 attr
      if (typeof attr === 'object') {
        options = events || {};
        events = attr;
        attr = options.attr || 'lay-on'; // 默认属性名
      }

      // 更多选项
      options = $.extend({
        elem: 'body',
        trigger: 'click'
      }, typeof options === 'object' ? options : {
        trigger: options // 兼容旧版
      });

      var elem = options.elem = $(options.elem);
      var attrSelector = '['+ attr +']';
      var DATANAME = 'UTIL_ON_DATA'; // 缓存在委托元素上的 data-* 属性名

      if (!elem[0]) return; // 若委托元素不存在

      // 初始化 data 默认值，以委托元素为存储单元
      if (!elem.data(DATANAME)) {
        elem.data(DATANAME, {
          events: {},
          callbacks: {}
        });
      }

      // 读取 data 缓存
      var dataCache = elem.data(DATANAME);
      var callbacks = dataCache.callbacks;

      // 根据 attr 记录事件集合
      events = dataCache.events[attr] = $.extend(true, dataCache.events[attr], events);

      // 清除事件委托，避免重复绑定
      elem.off(options.trigger, attrSelector, callbacks[attr]);

      // 绑定事件委托
      elem.on(
        options.trigger,
        attrSelector,
        callbacks[attr] = function(e) {
          var othis = $(this);
          var key = othis.attr(attr);
          typeof events[key] === 'function' && events[key].call(this, othis, e);
        }
      );

      return events;
    }
  };

  // 兼容旧版
  util.event = util.on;

  // 输出接口
  exports('util', util);
});
/**
 * dropdown 
 * 下拉菜单组件
 */

layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var laytpl = layui.laytpl;
  var util = layui.util;
  var hint = layui.hint();
  var device = layui.device();
  var clickOrMousedown = (device.mobile ? 'touchstart' : 'mousedown');
  
  // 模块名
  var MOD_NAME = 'dropdown';
  var MOD_INDEX = 'layui_'+ MOD_NAME +'_index'; // 模块索引名

  // 外部接口
  var dropdown = {
    config: {
      customName: { // 自定义 data 字段名
        id: 'id',
        title: 'title',
        children: 'child'
      }
    },
    index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0,

    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },
    
    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };

  // 操作当前实例
  var thisModule = function(){
    var that = this;
    var options = that.config;
    var id = options.id;

    thisModule.that[id] = that; // 记录当前实例对象

    return {
      config: options,
      // 重置实例
      reload: function(options){
        that.reload.call(that, options);
      },
      reloadData: function(options){
        dropdown.reloadData(id, options);
      },
      close: function () {
        that.remove()
      }
    }
  };

  // 字符常量
  var STR_ELEM = 'layui-dropdown';
  var STR_HIDE = 'layui-hide';
  var STR_DISABLED = 'layui-disabled';
  var STR_NONE = 'layui-none';
  var STR_ITEM_UP = 'layui-menu-item-up';
  var STR_ITEM_DOWN = 'layui-menu-item-down';
  var STR_MENU_TITLE = 'layui-menu-body-title';
  var STR_ITEM_GROUP = 'layui-menu-item-group';
  var STR_ITEM_PARENT = 'layui-menu-item-parent';
  var STR_ITEM_DIV = 'layui-menu-item-divider';
  var STR_ITEM_CHECKED = 'layui-menu-item-checked';
  var STR_ITEM_CHECKED2 = 'layui-menu-item-checked2';
  var STR_MENU_PANEL = 'layui-menu-body-panel';
  var STR_MENU_PANEL_L = 'layui-menu-body-panel-left';
  var STR_ELEM_SHADE = 'layui-dropdown-shade';
  
  var STR_GROUP_TITLE = '.'+ STR_ITEM_GROUP + '>.'+ STR_MENU_TITLE;

  // 构造器
  var Class = function(options){
    var that = this;
    that.index = ++dropdown.index;
    that.config = $.extend({}, that.config, dropdown.config, options);
    that.init();
  };

  // 默认配置
  Class.prototype.config = {
    trigger: 'click', // 事件类型
    content: '', // 自定义菜单内容
    className: '', // 自定义样式类名
    style: '', // 设置面板 style 属性
    show: false, // 是否初始即显示菜单面板
    isAllowSpread: true, // 是否允许菜单组展开收缩
    isSpreadItem: true, // 是否初始展开子菜单
    data: [], // 菜单数据结构
    delay: [200, 300], // 延时显示或隐藏的毫秒数，若为 number 类型，则表示显示和隐藏的延迟时间相同，trigger 为 hover 时才生效
    shade: 0, // 遮罩
    accordion: false // 手风琴效果，仅菜单组生效。基础菜单需要在容器上追加 'lay-accordion' 属性。
  };
  
  // 重载实例
  Class.prototype.reload = function(options, type){
    var that = this;
    that.config = $.extend({}, that.config, options);
    that.init(true, type);
  };

  // 初始化准备
  Class.prototype.init = function(rerender, type){
    var that = this;
    var options = that.config;
    
    // 若 elem 非唯一
    var elem = $(options.elem);
    if(elem.length > 1){
      layui.each(elem, function(){
        dropdown.render($.extend({}, options, {
          elem: this
        }));
      });
      return that;
    }

    // 合并 lay-options 属性上的配置信息
    $.extend(options, lay.options(elem[0]));

    // 若重复执行 render，则视为 reload 处理
    if(!rerender && elem[0] && elem.data(MOD_INDEX)){
      var newThat = thisModule.getThis(elem.data(MOD_INDEX));
      if(!newThat) return;

      return newThat.reload(options, type);
    }

    options.elem = $(options.elem);
    
    // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
    options.id = 'id' in options ? options.id : (
      elem.attr('id') || that.index
    );

    // 初始化自定义字段名
    options.customName = $.extend({}, dropdown.config.customName, options.customName);

    if(options.show || (type === 'reloadData' && that.elemView && $('body').find(that.elemView.get(0)).length)) that.render(rerender, type); //初始即显示或者面板弹出之后执行了刷新数据
    that.events(); // 事件
  };
  
  // 渲染
  Class.prototype.render = function(rerender, type){
    var that = this;
    var options = that.config;
    var customName = options.customName;
    var elemBody = $('body');
    
    // 默认菜单内容
    var getDefaultView = function(){
      var elemUl = $('<ul class="layui-menu layui-dropdown-menu"></ul>');
      if(options.data.length > 0 ){
        eachItemView(elemUl, options.data)
      } else {
        elemUl.html('<li class="layui-menu-item-none">暂无数据</li>');
      }
      return elemUl;
    };
    
    // 遍历菜单项
    var eachItemView = function(views, data){
      // var views = [];

      layui.each(data, function(index, item){
        // 是否存在子级
        var isChild = item[customName.children] && item[customName.children].length > 0;
        var isSpreadItem = ('isSpreadItem' in item) ? item.isSpreadItem : options.isSpreadItem
        var title = function(title){
          var templet = item.templet || options.templet;
          if(templet){
            title = typeof templet === 'function' 
              ? templet(item)
            : laytpl(templet).render(item);
          }
          return title;
        }(util.escape(item[customName.title]));
        
        // 初始类型
        var type = function(){
          if(isChild){
            item.type = item.type || 'parent';
          }
          if(item.type){
            return ({
              group: 'group'
              ,parent: 'parent'
              ,'-': '-'
            })[item.type] || 'parent';
          }
          return '';
        }();

        if(type !== '-' && (!item[customName.title] && !item[customName.id] && !isChild)) return;
        
        //列表元素
        var viewLi = $(['<li'+ function(){
          var className = {
            group: 'layui-menu-item-group'+ (
              options.isAllowSpread ? (
                isSpreadItem ? ' layui-menu-item-down' : ' layui-menu-item-up'
              ) : ''
            )
            ,parent: STR_ITEM_PARENT
            ,'-': 'layui-menu-item-divider'
          };
          if(isChild || type){
            return ' class="'+ className[type] +'"';
          }
          return item.disabled ? ' class="'+ STR_DISABLED +'"' : '';
        }() +'>'
        
          //标题区
          ,function(){
            //是否超文本
            var viewText = ('href' in item) ? (
              '<a href="'+ item.href +'" target="'+ (item.target || '_self') +'">'+ title +'</a>'
            ) : title;
            
            //是否存在子级
            if(isChild){
              return '<div class="'+ STR_MENU_TITLE +'">'+ viewText + function(){
                if(type === 'parent'){
                  return '<i class="layui-icon layui-icon-right"></i>';
                } else if(type === 'group' && options.isAllowSpread){
                  return '<i class="layui-icon layui-icon-'+ (isSpreadItem ? 'up' : 'down') +'"></i>';
                } else {
                  return '';
                }
              }() +'</div>'
              
            }
            return '<div class="'+ STR_MENU_TITLE +'">'+ viewText +'</div>';
          }()
        ,'</li>'].join(''));
        
        viewLi.data('item', item);
        
        //子级区
        if(isChild){
          var elemPanel = $('<div class="layui-panel layui-menu-body-panel"></div>');
          var elemUl = $('<ul></ul>');

          if(type === 'parent'){
            elemPanel.append(eachItemView(elemUl, item[customName.children]));
            viewLi.append(elemPanel);
          } else {
            viewLi.append(eachItemView(elemUl, item[customName.children]));
          }
        }

        views.append(viewLi);
      });
      return views;
    };
    
    // 主模板
    var TPL_MAIN = ['<div class="layui-dropdown layui-border-box layui-panel layui-anim layui-anim-downbit" lay-id="' + options.id + '">'
    ,'</div>'].join('');
    
    // 如果是右键事件，则每次触发事件时，将允许重新渲染
    if(options.trigger === 'contextmenu' || lay.isTopElem(options.elem[0])) rerender = true;
    
    // 判断是否已经打开了下拉菜单面板
    if(!rerender && options.elem.data(MOD_INDEX +'_opened')) return;

    // 记录模板对象
    that.elemView = $('.' + STR_ELEM + '[lay-id="' + options.id + '"]');
    if (type === 'reloadData' && that.elemView.length) {
      that.elemView.html(options.content || getDefaultView());
    } else {
      that.elemView = $(TPL_MAIN);
      that.elemView.append(options.content || getDefaultView());

      // 初始化某些属性
      if(options.className) that.elemView.addClass(options.className);
      if(options.style) that.elemView.attr('style', options.style);

      // 记录当前执行的实例索引
      dropdown.thisId = options.id;

      // 插入视图
      that.remove(); // 移除非当前绑定元素的面板
      elemBody.append(that.elemView);
      options.elem.data(MOD_INDEX +'_opened', true);

      // 遮罩
      var shade = options.shade ? ('<div class="'+ STR_ELEM_SHADE +'" style="'+ ('z-index:'+ (that.elemView.css('z-index')-1) +'; background-color: ' + (options.shade[1] || '#000') + '; opacity: ' + (options.shade[0] || options.shade)) +'"></div>') : '';
      that.elemView.before(shade);

      // 如果是鼠标移入事件，则鼠标移出时自动关闭
      if(options.trigger === 'mouseenter'){
        that.elemView.on('mouseenter', function(){
          clearTimeout(thisModule.timer);
        }).on('mouseleave', function(){
          that.delayRemove();
        });
      }
    }

    // 坐标定位
    that.position();
    thisModule.prevElem = that.elemView; // 记录当前打开的元素，以便在下次关闭
    thisModule.prevElem.data('prevElem', options.elem); // 将当前绑定的元素，记录在打开元素的 data 对象中
    
    // 阻止全局事件
    that.elemView.find('.layui-menu').on(clickOrMousedown, function(e){
      layui.stope(e);
    });

    // 触发菜单列表事件
    that.elemView.find('.layui-menu li').on('click', function(e){
      var othis = $(this);
      var data = othis.data('item') || {};
      var isChild = data[customName.children] && data[customName.children].length > 0;
      var isClickAllScope = options.clickScope === 'all'; // 是否所有父子菜单均触发点击事件

      if(data.disabled) return; // 菜单项禁用状态
      
      // 普通菜单项点击后的回调及关闭面板
      if((!isChild || isClickAllScope) && data.type !== '-'){
        var ret = typeof options.click === 'function' 
          ? options.click(data, othis) 
        : null;
        
        ret === false || (isChild || that.remove());
        layui.stope(e);
      }
    });
    
    // 触发菜单组展开收缩
    that.elemView.find(STR_GROUP_TITLE).on('click', function(e){
      var othis = $(this);
      var elemGroup = othis.parent();
      var data = elemGroup.data('item') || {};
      
      if(data.type === 'group' && options.isAllowSpread){
        thisModule.spread(elemGroup, options.accordion);
      }
    });

    // 组件打开完毕的事件
    typeof options.ready === 'function' && options.ready(
      that.elemView, 
      options.elem
    );
  };
  
  // 位置定位
  Class.prototype.position = function(obj){
    var that = this;
    var options = that.config;
    
    lay.position(options.elem[0], that.elemView[0], {
      position: options.position,
      e: that.e,
      clickType: options.trigger === 'contextmenu' ? 'right' : null,
      align: options.align || null
    });
  };
  
  // 删除视图
  Class.prototype.remove = function(){
    var that = this;
    var options = that.config;
    var prevContentElem = thisModule.prevElem;
    
    // 若存在已打开的面板元素，则移除
    if(prevContentElem){
      var prevId = prevContentElem.attr('lay-id');
      var prevTriggerElem = prevContentElem.data('prevElem');
      var prevInstance = thisModule.getThis(prevId);
      var prevOnClose = prevInstance.config.close;
      
      prevTriggerElem && prevTriggerElem.data(MOD_INDEX +'_opened', false);
      prevContentElem.remove();
      delete thisModule.prevElem;
      typeof prevOnClose === 'function' && prevOnClose.call(prevInstance.config, prevTriggerElem);
    }
    lay('.' + STR_ELEM_SHADE).remove();
  };

  Class.prototype.normalizedDelay = function(){
    var that = this;
    var options = that.config;
    var delay = [].concat(options.delay);
    
    return {
      show: delay[0],
      hide: delay[1] !== undefined ? delay[1] : delay[0]  
    }
  }
  
  // 延迟删除视图
  Class.prototype.delayRemove = function(){
    var that = this;
    var options = that.config;
    clearTimeout(thisModule.timer);

    thisModule.timer = setTimeout(function(){
      that.remove();
    }, that.normalizedDelay().hide);
  };
  
  // 事件
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;
    
    // 若传入 hover，则解析为 mouseenter
    if(options.trigger === 'hover') options.trigger = 'mouseenter';

    // 解除上一个事件
    if(that.prevElem) that.prevElem.off(options.trigger, that.prevElemCallback);

    // 是否鼠标移入时触发
    var isMouseEnter = options.trigger === 'mouseenter';
    
    // 记录被绑定的元素及回调
    that.prevElem = options.elem;
    that.prevElemCallback = function(e){
      clearTimeout(thisModule.timer);
      that.e = e;

      // 若为鼠标移入事件，则延迟触发
      isMouseEnter ? (
        thisModule.timer = setTimeout(function(){
          that.render();
        }, that.normalizedDelay().show)
      ) : that.render();
      
      e.preventDefault();
    };

    // 触发元素事件
    options.elem.on(options.trigger, that.prevElemCallback);
    
    // 如果是鼠标移入事件
    if (isMouseEnter) {
      // 执行鼠标移出事件
      options.elem.on('mouseleave', function(){
        that.delayRemove();
      });
    }
  };
  
  // 记录所有实例
  thisModule.that = {}; // 记录所有实例对象
  
  // 获取当前实例对象
  thisModule.getThis = function(id){
    var that = thisModule.that[id];
    if(!that) hint.error(id ? (MOD_NAME +' instance with ID \''+ id +'\' not found') : 'ID argument required');
    return that;
  };
  
  // 设置菜单组展开和收缩状态
  thisModule.spread = function(othis, isAccordion){
    var contentElem = othis.children('ul');
    var needSpread = othis.hasClass(STR_ITEM_UP);
    var ANIM_MS = 200;

    // 动画执行完成后的操作
    var complete = function() {
      $(this).css({'display': ''}); // 剔除临时 style，以适配外部样式的状态重置;
    };

    // 动画是否正在执行
    if (contentElem.is(':animated')) return;

    // 展开
    if (needSpread) {
      othis.removeClass(STR_ITEM_UP).addClass(STR_ITEM_DOWN);
      contentElem.hide().stop().slideDown(ANIM_MS, complete);
    } else { // 收缩
      contentElem.stop().slideUp(ANIM_MS, complete);
      othis.removeClass(STR_ITEM_DOWN).addClass(STR_ITEM_UP);
    }

    // 手风琴
    if (needSpread && isAccordion) {
      var groupSibs = othis.siblings('.' + STR_ITEM_DOWN);
      groupSibs.children('ul').stop().slideUp(ANIM_MS, complete);
      groupSibs.removeClass(STR_ITEM_DOWN).addClass(STR_ITEM_UP);
    }
  };
  
  // 全局事件
  (function(){
    var _WIN = $(window);
    var _DOC = $(document);
    
    // 自适应定位
    _WIN.on('resize', function(){
      if(!dropdown.thisId) return;
      var that = thisModule.getThis(dropdown.thisId);
      if(!that) return;
      
      if((that.elemView && !that.elemView[0]) || !$('.'+ STR_ELEM)[0]){
        return false;
      }
      
      var options = that.config;
      
      if(options.trigger === 'contextmenu'){
        that.remove();
      } else {
        that.position();
      }
    });
    
    
      
    // 点击任意处关闭
    _DOC.on(clickOrMousedown, function(e){
      if(!dropdown.thisId) return;
      var that = thisModule.getThis(dropdown.thisId)
      if(!that) return;
      
      var options = that.config;
      
      // 若触发的是绑定的元素，或者属于绑定元素的子元素，则不关闭
      // 满足条件：当前绑定的元素不是 body document，或者不是鼠标右键事件
      if(!(lay.isTopElem(options.elem[0]) || options.trigger === 'contextmenu')){
        if(
          e.target === options.elem[0] || 
          options.elem.find(e.target)[0] ||
          (that.elemView && e.target === that.elemView[0]) ||
          (that.elemView && that.elemView.find(e.target)[0])
        ) return;
      }
      
      that.remove();
    });
    
    // 基础菜单的静态元素事件
    var ELEM_LI = '.layui-menu:not(.layui-dropdown-menu) li';
    _DOC.on('click', ELEM_LI, function(e){
      var othis = $(this);
      var parent = othis.parents('.layui-menu').eq(0);
      var isChild = othis.hasClass(STR_ITEM_GROUP) || othis.hasClass(STR_ITEM_PARENT);
      var filter = parent.attr('lay-filter') || parent.attr('id');
      var options = lay.options(this);
      
      // 非触发元素
      if(othis.hasClass(STR_ITEM_DIV)) return;

      // 非菜单组
      if(!isChild){
        // 选中
        parent.find('.'+ STR_ITEM_CHECKED).removeClass(STR_ITEM_CHECKED); // 清除选中样式
        parent.find('.'+ STR_ITEM_CHECKED2).removeClass(STR_ITEM_CHECKED2); // 清除父级菜单选中样式
        othis.addClass(STR_ITEM_CHECKED); //添加选中样式
        othis.parents('.'+ STR_ITEM_PARENT).addClass(STR_ITEM_CHECKED2); // 添加父级菜单选中样式

        options.title = options.title || $.trim(othis.children('.'+ STR_MENU_TITLE).text());
        
        // 触发事件
        layui.event.call(this, MOD_NAME, 'click('+ filter +')', options);
      }
    });
    
    // 基础菜单的展开收缩事件
    _DOC.on('click', (ELEM_LI + STR_GROUP_TITLE), function(e){
      var othis = $(this);
      var elemGroup = othis.parents('.'+ STR_ITEM_GROUP +':eq(0)');
      var options = lay.options(elemGroup[0]);
      var isAccordion = typeof othis.parents('.layui-menu').eq(0).attr('lay-accordion') === 'string';

      if(('isAllowSpread' in options) ? options.isAllowSpread : true){
        thisModule.spread(elemGroup, isAccordion);
      }
    });
    
    // 判断子级菜单是否超出屏幕
    var ELEM_LI_PAR = '.layui-menu .'+ STR_ITEM_PARENT
    _DOC.on('mouseenter', ELEM_LI_PAR, function(e){
      var othis = $(this);
      var elemPanel = othis.find('.'+ STR_MENU_PANEL);

      if(!elemPanel[0]) return;
      var rect = elemPanel[0].getBoundingClientRect();
      
      // 是否超出右侧屏幕
      if(rect.right > _WIN.width()){
        elemPanel.addClass(STR_MENU_PANEL_L);
        // 不允许超出左侧屏幕
        rect = elemPanel[0].getBoundingClientRect();
        if(rect.left < 0){
          elemPanel.removeClass(STR_MENU_PANEL_L);
        }
      }
      
      // 是否超出底部屏幕
      if(rect.bottom > _WIN.height()){
        elemPanel.eq(0).css('margin-top', -(rect.bottom - _WIN.height() + 5));
      }
    }).on('mouseleave', ELEM_LI_PAR, function(e){
      var othis = $(this)
      var elemPanel = othis.children('.'+ STR_MENU_PANEL);
      
      elemPanel.removeClass(STR_MENU_PANEL_L);
      elemPanel.css('margin-top', 0);
    });
    
  })();

  // 关闭面板
  dropdown.close = function(id){
    var that = thisModule.getThis(id);
    if(!that) return this;
    
    that.remove();
    return thisModule.call(that);
  };
  
  // 重载实例
  dropdown.reload = function(id, options, type){
    var that = thisModule.getThis(id);
    if(!that) return this;

    that.reload(options, type);
    return thisModule.call(that);
  };

  // 仅重载数据
  dropdown.reloadData = function(){
    var args = $.extend([], arguments);
    args[2] = 'reloadData';

    // 重载时，与数据相关的参数
    var dataParams = new RegExp('^('+ [
      'data', 'templet', 'content'
    ].join('|') + ')$');

    // 过滤与数据无关的参数
    layui.each(args[1], function (key, value) {
      if(!dataParams.test(key)){
        delete args[1][key];
      }
    });

    return dropdown.reload.apply(null, args);
  };

  // 核心入口
  dropdown.render = function(options){
    var inst = new Class(options);
    return thisModule.call(inst);
  };

  exports(MOD_NAME, dropdown);
});
/**
 * slider 滑块组件
 */

layui.define(['jquery', 'lay'], function(exports){
  'use strict';

  var $ = layui.$;
  var lay = layui.lay;

  // 外部接口
  var slider = {
    config: {},
    index: layui.slider ? (layui.slider.index + 10000) : 0,

    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },

    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };

  // 操作当前实例
  var thisSlider = function(){
    var that = this;
    var options = that.config;

    return {
      setValue: function(value, index){ // 设置值
        value = value > options.max ? options.max : value;
        value = value < options.min ? options.min : value;
        options.value = value;
        return that.slide('set', value, index || 0);
      },
      config: options
    }
  };

  // 字符常量
  var MOD_NAME = 'slider';
  var DISABLED = 'layui-disabled';
  var ELEM_VIEW = 'layui-slider';
  var SLIDER_BAR = 'layui-slider-bar';
  var SLIDER_WRAP = 'layui-slider-wrap';
  var SLIDER_WRAP_BTN = 'layui-slider-wrap-btn';
  var SLIDER_TIPS = 'layui-slider-tips';
  var SLIDER_INPUT = 'layui-slider-input';
  var SLIDER_INPUT_TXT = 'layui-slider-input-txt';
  var SLIDER_INPUT_BTN = 'layui-slider-input-btn';
  var ELEM_HOVER = 'layui-slider-hover';

  // 构造器
  var Class = function(options){
    var that = this;
    that.index = ++slider.index;
    that.config = $.extend({}, that.config, slider.config, options);
    that.render();
  };

  // 默认配置
  Class.prototype.config = {
    type: 'default', //滑块类型，垂直：vertical
    min: 0, //最小值
    max: 100, //最大值，默认100
    value: 0, //初始值，默认为0
    step: 1, //间隔值
    showstep: false, //间隔点开启
    tips: true, //文字提示，开启
    tipsAlways: false, //文字提示，始终开启
    input: false, //输入框，关闭
    range: false, //范围选择，与输入框不能同时开启，默认关闭
    height: 200, //配合 type:"vertical" 使用，默认200px
    disabled: false, //滑块禁用，默认关闭
    theme: '#16baaa' //主题颜色
  };

  //滑块渲染
  Class.prototype.render = function(){
    var that = this;
    var options = that.config;

    // 若 elem 非唯一，则拆分为多个实例
    var elem = $(options.elem);
    if(elem.length > 1){
      layui.each(elem, function(){
        slider.render($.extend({}, options, {
          elem: this
        }));
      });
      return that;
    }

    // 合并 lay-options 属性上的配置信息
    $.extend(options, lay.options(elem[0]));

    //间隔值不能小于 1
    if(options.step < 1) options.step = 1;

    //最大值不能小于最小值
    if(options.max < options.min) options.max = options.min + options.step;



    //判断是否开启双滑块
    if(options.range){
      options.value = typeof(options.value) == 'object' ? options.value : [options.min, options.value];
      var minValue = Math.min(options.value[0], options.value[1])
      ,maxValue = Math.max(options.value[0], options.value[1]);
      options.value[0] = Math.max(minValue,options.min);
      options.value[1] = Math.max(maxValue,options.min);
      options.value[0] = Math.min(options.value[0],options.max);
      options.value[1] = Math.min(options.value[1],options.max);

      var scaleFir = Math.floor((options.value[0] - options.min) / (options.max - options.min) * 100);
      var scaleSec = Math.floor((options.value[1] - options.min) / (options.max - options.min) * 100);
      var scale = scaleSec - scaleFir + '%';
      scaleFir = scaleFir + '%';
      scaleSec = scaleSec + '%';
    } else {
      //如果初始值是一个数组，则获取数组的最小值
      if(typeof options.value == 'object'){
        options.value = Math.min.apply(null, options.value);
      }

      //初始值不能小于最小值且不能大于最大值
      if(options.value < options.min) options.value = options.min;
      if(options.value > options.max) options.value = options.max;

      var scale = Math.floor((options.value - options.min) / (options.max - options.min) * 100) + '%';
    }


    //如果禁用，颜色为统一的灰色
    var theme = options.disabled ? '#c2c2c2' : options.theme;

    //滑块
    var temp = '<div class="layui-slider '+ (options.type === 'vertical' ? 'layui-slider-vertical' : '') +'">'+ (options.tips ? '<div class="'+ SLIDER_TIPS +'" '+ (options.tipsAlways ? '' : 'style="display:none;"') +'></div>' : '') +
    '<div class="layui-slider-bar" style="background:'+ theme +'; '+ (options.type === 'vertical' ? 'height' : 'width') +':'+ scale +';'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ (scaleFir || 0) +';"></div><div class="layui-slider-wrap" style="'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ (scaleFir || scale) +';">' +
    '<div class="layui-slider-wrap-btn" style="border: 2px solid '+ theme +';"></div></div>'+ (options.range ? '<div class="layui-slider-wrap" style="'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ scaleSec +';"><div class="layui-slider-wrap-btn" style="border: 2px solid '+ theme +';"></div></div>' : '') +'</div>';

    var othis = $(options.elem);
    var hasRender = othis.next('.' + ELEM_VIEW);
    //生成替代元素
    hasRender[0] && hasRender.remove(); //如果已经渲染，则Rerender
    that.elemTemp = $(temp);

    //把数据缓存到滑块上
    if(options.range){
      that.elemTemp.find('.' + SLIDER_WRAP).eq(0).data('value', options.value[0]);
      that.elemTemp.find('.' + SLIDER_WRAP).eq(1).data('value', options.value[1]);
    }else{
      that.elemTemp.find('.' + SLIDER_WRAP).data('value', options.value);
    }

    //插入替代元素
    othis.html(that.elemTemp);

    //垂直滑块
    if(options.type === 'vertical'){
      that.elemTemp.height(options.height + 'px');
    }

    //显示间断点
    if(options.showstep){
      var number = (options.max - options.min) / options.step, item = '';
      for(var i = 1; i < number + 1; i++) {
        var step = i * 100 / number;
        if(step < 100){
          item += '<div class="layui-slider-step" style="'+ (options.type === 'vertical' ? 'bottom' : 'left') +':'+ step +'%"></div>'
        }
      }
      that.elemTemp.append(item);
    }

    //插入输入框
    if(options.input && !options.range){
      var elemInput = $('<div class="layui-slider-input"><div class="layui-slider-input-txt"><input type="text" class="layui-input"></div><div class="layui-slider-input-btn"><i class="layui-icon layui-icon-up"></i><i class="layui-icon layui-icon-down"></i></div></div>');
      othis.css("position","relative");
      othis.append(elemInput);
      othis.find('.' + SLIDER_INPUT_TXT).children('input').val(options.value);
      if(options.type === 'vertical'){
        elemInput.css({
          left: 0
          ,top: -48
        });
      } else {
        that.elemTemp.css("margin-right", elemInput.outerWidth() + 15);
      }
    }

    //给未禁止的滑块滑动事件
    if(!options.disabled){
      that.slide();
    }else{
      that.elemTemp.addClass(DISABLED);
      that.elemTemp.find('.' + SLIDER_WRAP_BTN).addClass(DISABLED);
    }

    /**
     * @description 设置提示文本内容
     * @param {Element} sliderWrapBtnElem 提示文本节点元素
     */
    function setSliderTipsTxt(sliderWrapBtnElem) {
      var value = sliderWrapBtnElem.parent().data('value');
      var tipsTxt = options.setTips ? options.setTips(value) : value;
      that.elemTemp.find('.' + SLIDER_TIPS).html(tipsTxt);
    }

    /**
     * @description 计算提示文本元素的 position left
     * @param {Element} sliderWrapBtnElem 提示文本节点元素
     */
    function calcSliderTipsLeft(sliderWrapBtnElem){
      var sliderWidth = options.type === 'vertical' ? options.height : that.elemTemp[0].offsetWidth;
      var sliderWrap = that.elemTemp.find('.' + SLIDER_WRAP);
      var tipsLeft = options.type === 'vertical' ? (sliderWidth - sliderWrapBtnElem.parent()[0].offsetTop - sliderWrap.height()) : sliderWrapBtnElem.parent()[0].offsetLeft;
      var left = tipsLeft / sliderWidth * 100;
      return left
    }

    /**
     * @description 设置提示文本元素的 position left
     * @param {number} left 要设置的 left 的大小
     */
    function setSliderTipsLeft(left) {
      if(options.type === 'vertical'){
        that.elemTemp.find('.' + SLIDER_TIPS).css({
          "bottom": left + '%',
          "margin-bottom": "20px",
          "display": "inline-block"
        });
      } else {
        that.elemTemp.find('.' + SLIDER_TIPS).css({
          "left": left + '%',
          "display": "inline-block"
        });
      }
    }

    //判断是否要始终显示提示文本
    if(options.tips){
      if(options.tipsAlways){
        var sliderWrapBtnElem = that.elemTemp.find('.' + SLIDER_WRAP_BTN);
        setSliderTipsTxt(sliderWrapBtnElem)
        var left = calcSliderTipsLeft(sliderWrapBtnElem);
        setSliderTipsLeft(left)
      }else{
        //划过滑块显示数值
        var timer;
        that.elemTemp.find('.' + SLIDER_WRAP_BTN).on('mouseover', function(){
          setSliderTipsTxt($(this))
          var left = calcSliderTipsLeft($(this));
          clearTimeout(timer);
          timer = setTimeout(function(){
            setSliderTipsLeft(left)
          }, 300);
        }).on('mouseout', function(){
          clearTimeout(timer);
          if(!options.tipsAlways){
            that.elemTemp.find('.' + SLIDER_TIPS).css("display", "none");
          }
        });
      }
    }
  };

  //滑块滑动
  Class.prototype.slide = function(setValue, value, i){
    var that = this;
    var options = that.config;
    var sliderAct = that.elemTemp;
    var sliderWidth = function(){
      return options.type === 'vertical' ? options.height : sliderAct[0].offsetWidth
    };
    var sliderWrap = sliderAct.find('.' + SLIDER_WRAP);
    var sliderTxt = sliderAct.next('.' + SLIDER_INPUT);
    var inputValue = sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').val();
    var step = 100 / ((options.max - options.min) / Math.ceil(options.step));
    var change = function(offsetValue, index, from){
      if(Math.ceil(offsetValue) * step > 100){
        offsetValue = Math.ceil(offsetValue) * step
      }else{
        offsetValue = Math.round(offsetValue) * step
      }
      offsetValue = offsetValue > 100 ? 100: offsetValue;
      offsetValue = offsetValue < 0 ? 0: offsetValue;
      sliderWrap.eq(index).css((options.type === 'vertical' ?'bottom':'left'), offsetValue + '%');
      var firLeft = valueTo(sliderWrap[0].offsetLeft);
      var secLeft = options.range ? valueTo(sliderWrap[1].offsetLeft) : 0;
      if(options.type === 'vertical'){
        sliderAct.find('.' + SLIDER_TIPS).css({"bottom":offsetValue + '%', "margin-bottom":"20px"});
        firLeft = valueTo(sliderWidth() - sliderWrap[0].offsetTop - sliderWrap.height());
        secLeft = options.range ? valueTo(sliderWidth() - sliderWrap[1].offsetTop - sliderWrap.height()) : 0;
      }else{
        sliderAct.find('.' + SLIDER_TIPS).css("left",offsetValue + '%');
      }
      firLeft = firLeft > 100 ? 100: firLeft;
      secLeft = secLeft > 100 ? 100: secLeft;
      var minLeft = Math.min(firLeft, secLeft)
      ,wrapWidth = Math.abs(firLeft - secLeft);
      if(options.type === 'vertical'){
        sliderAct.find('.' + SLIDER_BAR).css({"height":wrapWidth + '%', "bottom":minLeft + '%'});
      }else{
        sliderAct.find('.' + SLIDER_BAR).css({"width":wrapWidth + '%', "left":minLeft + '%'});
      }
      var selfValue = options.min + Math.round((options.max - options.min) * offsetValue / 100);
      inputValue = selfValue;
      sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').val(inputValue);
      sliderWrap.eq(index).data('value', selfValue);
      sliderAct.find('.' + SLIDER_TIPS).html(options.setTips ? options.setTips(selfValue) : selfValue);

      //如果开启范围选择，则返回数组值
      if(options.range){
        var arrValue = [
          sliderWrap.eq(0).data('value'),
          sliderWrap.eq(1).data('value')
        ];
        if(arrValue[0] > arrValue[1]) arrValue.reverse(); //如果前面的圆点超过了后面的圆点值，则调换顺序
      }

      that.value = options.range ? arrValue : selfValue; // 最新值
      options.change && options.change(that.value); // change 回调

      // 值完成选中的事件
      if(from === 'done') options.done && options.done(that.value);
    };
    var valueTo = function(value){
      var oldLeft = value / sliderWidth() * 100 / step;
      var left =  Math.round(oldLeft) * step;
      if(value == sliderWidth()){
        left =  Math.ceil(oldLeft) * step;
      }
      return left;
    };

    //拖拽元素
    var elemMove = $(['<div class="layui-auxiliar-moving" id="LAY-slider-moving"></div'].join(''));
    var createMoveElem = function(sliderBtnElem, move, up){
      var upCall = function(){
        // 移动端延时一秒关闭
        up && up(lay.touchEventsSupported() ? 1000 : 0);
        elemMove.remove();
        options.done && options.done(that.value);
        // 移动端
        if (lay.touchEventsSupported()) {
          sliderBtnElem[0].removeEventListener('touchmove', move, lay.passiveSupported ? { passive: false } : false);
          sliderBtnElem[0].removeEventListener('touchend', upCall);
          sliderBtnElem[0].removeEventListener('touchcancel', upCall);
        }
      };
      $('#LAY-slider-moving')[0] || $('body').append(elemMove);
      elemMove.on('mousemove', move);
      elemMove.on('mouseup', upCall).on('mouseleave', upCall);
      // 移动端
      if (lay.touchEventsSupported()) {
        sliderBtnElem[0].addEventListener('touchmove', move, lay.passiveSupported ? { passive: false } : false);
        sliderBtnElem[0].addEventListener('touchend', upCall);
        sliderBtnElem[0].addEventListener('touchcancel', upCall);
      }
    };

    //动态赋值
    if(setValue === 'set') return change(value - options.min, i, 'done');

    //滑块滑动
    sliderAct.find('.' + SLIDER_WRAP_BTN).each(function(index){
      var othis = $(this);
      othis.on('mousedown touchstart', function(e){
        e = e || window.event;
        if(e.type === 'touchstart'){
          e.clientX = e.originalEvent.touches[0].clientX;
          e.clientY = e.originalEvent.touches[0].clientY;
        }

        var oldleft = othis.parent()[0].offsetLeft;
        var oldx = e.clientX;
        if(options.type === 'vertical'){
          oldleft = sliderWidth() - othis.parent()[0].offsetTop - sliderWrap.height()
          oldx = e.clientY;
        }

        var move = function(e){
          e = e || window.event;
          if (e.type === 'touchmove') {
            e.clientX = e.touches[0].clientX;
            e.clientY = e.touches[0].clientY;
          }
          var left = oldleft + (options.type === 'vertical' ? (oldx - e.clientY) : (e.clientX - oldx));
          if(left < 0)left = 0;
          if(left > sliderWidth())left = sliderWidth();
          var reaLeft = left / sliderWidth() * 100 / step;
          change(reaLeft, index);
          othis.addClass(ELEM_HOVER);
          sliderAct.find('.' + SLIDER_TIPS).show();
          e.preventDefault();
        };

        var up = function(delay){
          othis.removeClass(ELEM_HOVER);
          if(!options.tipsAlways){
            setTimeout(function(){
              sliderAct.find('.' + SLIDER_TIPS).hide();
            }, delay);
          }
        };

        createMoveElem(othis, move, up)
      });
    });

    // 点击滑块
    sliderAct.on('click', function(e){
      var main = $('.' + SLIDER_WRAP_BTN);
      var othis = $(this);
      if(!main.is(event.target) && main.has(event.target).length === 0 && main.length){
        var index;
        var offset = options.type === 'vertical'
          ? (sliderWidth() - e.clientY + othis.offset().top - $(window).scrollTop())
        :(e.clientX - othis.offset().left - $(window).scrollLeft());

        if(offset < 0)offset = 0;
        if(offset > sliderWidth()) offset = sliderWidth();
        var reaLeft = offset / sliderWidth() * 100 / step;
        if(options.range){
          if(options.type === 'vertical'){
            index = Math.abs(offset - parseInt($(sliderWrap[0]).css('bottom'))) > Math.abs(offset -  parseInt($(sliderWrap[1]).css('bottom'))) ? 1 : 0;
          } else {
            index = Math.abs(offset - sliderWrap[0].offsetLeft) > Math.abs(offset - sliderWrap[1].offsetLeft) ? 1 : 0;
          }
        } else {
          index = 0;
        }
        change(reaLeft, index, 'done');
        e.preventDefault();
      }
    });

    //点击加减输入框
    sliderTxt.children('.' + SLIDER_INPUT_BTN).children('i').each(function(index){
      $(this).on('click', function(){
        inputValue = sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').val();
        if(index == 1){ //减
          inputValue = inputValue - options.step < options.min
            ? options.min
          : Number(inputValue) - options.step;
        }else{
          inputValue = Number(inputValue) + options.step > options.max
            ? options.max
          : Number(inputValue) + options.step;
        }
        var inputScale =  (inputValue - options.min) / (options.max - options.min) * 100 / step;
        change(inputScale, 0, 'done');
      });
    });

    //获取输入框值
    var getInputValue = function(){
      var realValue = this.value;
      realValue = isNaN(realValue) ? 0 : realValue;
      realValue = realValue < options.min ? options.min : realValue;
      realValue = realValue > options.max ? options.max : realValue;
      this.value = realValue;
      var inputScale =  (realValue - options.min) / (options.max - options.min) * 100 / step;
      change(inputScale, 0, 'done');
    };
    sliderTxt.children('.' + SLIDER_INPUT_TXT).children('input').on('keydown', function(e){
      if(e.keyCode === 13){
        e.preventDefault();
        getInputValue.call(this);
      }
    }).on('change', getInputValue);
  };

  //事件处理
  Class.prototype.events = function(){
     var that = this;
     var options = that.config;
  };

  //核心入口
  slider.render = function(options){
    var inst = new Class(options);
    return thisSlider.call(inst);
  };

  exports(MOD_NAME, slider);
})
/**
 * colorpicker 
 * 颜色选择组件
 */

layui.define(['jquery', 'lay'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var lay = layui.lay;
  var hint = layui.hint();
  var device = layui.device();
  var clickOrMousedown = (device.mobile ? 'click' : 'mousedown');

  //外部接口
  var colorpicker = {
    config: {}
    ,index: layui.colorpicker ? (layui.colorpicker.index + 10000) : 0

    //设置全局项
    ,set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    }
    
    //事件
    ,on: function(events, callback){
      return layui.onevent.call(this, 'colorpicker', events, callback);
    }
  };
  
  // 操作当前实例
  var thisModule = function(){
    var that = this;
    var options = that.config;
    var id = options.id;

    thisModule.that[id] = that; // 记录当前实例对象

    return {
      config: options
    };
  }

  //字符常量
  ,MOD_NAME = 'colorpicker', SHOW = 'layui-show', THIS = 'layui-this', ELEM = 'layui-colorpicker'
  
  ,ELEM_MAIN = '.layui-colorpicker-main', ICON_PICKER_DOWN = 'layui-icon-down', ICON_PICKER_CLOSE = 'layui-icon-close'
  ,PICKER_TRIG_SPAN = 'layui-colorpicker-trigger-span', PICKER_TRIG_I = 'layui-colorpicker-trigger-i', PICKER_SIDE = 'layui-colorpicker-side', PICKER_SIDE_SLIDER = 'layui-colorpicker-side-slider'
  ,PICKER_BASIS = 'layui-colorpicker-basis', PICKER_ALPHA_BG = 'layui-colorpicker-alpha-bgcolor', PICKER_ALPHA_SLIDER = 'layui-colorpicker-alpha-slider', PICKER_BASIS_CUR = 'layui-colorpicker-basis-cursor', PICKER_INPUT = 'layui-colorpicker-main-input'

  //RGB转HSB
  ,RGBToHSB = function(rgb){
    var hsb = {h:0, s:0, b:0};
    var min = Math.min(rgb.r, rgb.g, rgb.b);
    var max = Math.max(rgb.r, rgb.g, rgb.b);
    var delta = max - min;
    hsb.b = max;
    hsb.s = max !== 0 ? 255*delta/max : 0;
    if(hsb.s !== 0){
      if(rgb.r == max){ // 因 rgb 中返回的数字为 string 类型
        hsb.h = (rgb.g - rgb.b) / delta;
      }else if(rgb.g == max){
        hsb.h = 2 + (rgb.b - rgb.r) / delta;
      }else{
        hsb.h = 4 + (rgb.r - rgb.g) / delta;
      }
    }else{
      hsb.h = -1;
    }
    if(max === min){
      hsb.h = 0;
    }
    hsb.h *= 60;
    if(hsb.h < 0) {
      hsb.h += 360;
    }
    hsb.s *= 100/255;
    hsb.b *= 100/255;
    return hsb;  
  }

  //HEX转HSB
  ,HEXToHSB = function(hex){
    hex = hex.indexOf('#') > -1 ? hex.substring(1) : hex;
    if(hex.length === 3){
      var num = hex.split("");
      hex = num[0]+num[0]+num[1]+num[1]+num[2]+num[2]
    }
    hex = parseInt(hex, 16);
    var rgb = {r:hex >> 16, g:(hex & 0x00FF00) >> 8, b:(hex & 0x0000FF)};
    return RGBToHSB(rgb);
  }

  //HSB转RGB
  ,HSBToRGB = function(hsb){
    var rgb = {};
    var h = hsb.h;
    var s = hsb.s*255/100;
    var b = hsb.b*255/100;
    if(s === 0){
      rgb.r = rgb.g = rgb.b = b;
    }else{
      var t1 = b;
      var t2 = (255 - s) * b /255;
      var t3 = (t1 - t2) * (h % 60) /60;
      if(h === 360) h = 0;
      if(h < 60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
      else if(h < 120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
      else if(h < 180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
      else if(h < 240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
      else if(h < 300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
      else if(h < 360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
      else {rgb.r=0; rgb.g=0; rgb.b=0}
    }
    return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
  }

  //HSB转HEX
  ,HSBToHEX = function(hsb){
    var rgb = HSBToRGB(hsb);
    var hex = [
      rgb.r.toString(16)
      ,rgb.g.toString(16)
      ,rgb.b.toString(16)
    ];
    $.each(hex, function(nr, val){
      if(val.length === 1){
        hex[nr] = '0' + val;
      }
    });
    return hex.join('');
  }

  //转化成所需rgb格式
  ,RGBSTo = function(rgbs){
    var regexp = /[0-9]{1,3}/g;
    var re = rgbs.match(regexp) || [];
    return {r:re[0], g:re[1], b:re[2]};
  }
  
  ,$win = $(window)
  ,$doc = $(document)
  
  //构造器
  ,Class = function(options){
    var that = this;
    that.index = ++colorpicker.index;
    that.config = $.extend({}, that.config, colorpicker.config, options);
    that.render();
  };

  //默认配置
  Class.prototype.config = {
    color: ''  //默认颜色，默认没有
    ,size: null  //选择器大小
    ,alpha: false  //是否开启透明度
    ,format: 'hex'  //颜色显示/输入格式，可选 rgb,hex
    ,predefine: false //预定义颜色是否开启
    ,colors: [ //默认预定义颜色列表
      '#16baaa', '#16b777', '#1E9FFF', '#FF5722', '#FFB800', '#01AAED', '#999', '#c00', '#ff8c00','#ffd700'
      ,'#90ee90', '#00ced1', '#1e90ff', '#c71585', 'rgb(0, 186, 189)', 'rgb(255, 120, 0)', 'rgb(250, 212, 0)', '#393D49', 'rgba(0,0,0,.5)', 'rgba(255, 69, 0, 0.68)', 'rgba(144, 240, 144, 0.5)', 'rgba(31, 147, 255, 0.73)'
    ]
  };

  //初始颜色选择框
  Class.prototype.render = function(){
    var that = this;
    var options = that.config;

    // 若 elem 非唯一，则拆分为多个实例
    var elem = $(options.elem);
    if(elem.length > 1){
      layui.each(elem, function(){
        colorpicker.render($.extend({}, options, {
          elem: this
        }));
      });
      return that;
    }

    // 合并 lay-options 属性上的配置信息
    $.extend(options, lay.options(elem[0]));
    
    //颜色选择框对象
    var elemColorBox = $(['<div class="layui-unselect layui-colorpicker">'
      ,'<span '+ (options.format == 'rgb' && options.alpha
          ? 'class="layui-colorpicker-trigger-bgcolor"'
        : '') +'>'
        ,'<span class="layui-colorpicker-trigger-span" '
          ,'lay-type="'+ (options.format == 'rgb' ? (options.alpha ? 'rgba' : 'torgb') : '') +'" '
          ,'style="'+ function(){
            var bgstr = '';
            if(options.color){
              bgstr = options.color;
              
              if((options.color.match(/[0-9]{1,3}/g) || []).length > 3){ //需要优化
                if(!(options.alpha && options.format == 'rgb')){
                  bgstr = '#' + HSBToHEX(RGBToHSB(RGBSTo(options.color)))
                }
              }
              
              return 'background: '+ bgstr;
            }
            
            return bgstr;
          }() +'">'
          ,'<i class="layui-icon layui-colorpicker-trigger-i '+ (options.color 
            ? ICON_PICKER_DOWN 
          : ICON_PICKER_CLOSE) +'"></i>'
        ,'</span>'
      ,'</span>'
    ,'</div>'].join(''))

    //初始化颜色选择框
    elem = options.elem = $(options.elem);
    options.size && elemColorBox.addClass('layui-colorpicker-'+ options.size); //初始化颜色选择框尺寸
    
    // 插入颜色选择框
    elem.addClass('layui-inline').html(
      that.elemColorBox = elemColorBox
    );

    // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
    options.id = 'id' in options ? options.id : (
      elem.attr('id') || that.index
    );
    
    // 获取背景色值
    that.color = that.elemColorBox.find('.'+ PICKER_TRIG_SPAN)[0].style.background;
    
    // 相关事件
    that.events();
  };

  //渲染颜色选择器
  Class.prototype.renderPicker = function(){
    var that = this
    ,options = that.config
    ,elemColorBox = that.elemColorBox[0]
    
    //颜色选择器对象
    ,elemPicker = that.elemPicker = $(['<div id="layui-colorpicker'+ that.index +'" data-index="'+ that.index +'" class="layui-anim layui-anim-downbit layui-colorpicker-main">'
      //颜色面板
      ,'<div class="layui-colorpicker-main-wrapper">'
        ,'<div class="layui-colorpicker-basis">'
          ,'<div class="layui-colorpicker-basis-white"></div>'
          ,'<div class="layui-colorpicker-basis-black"></div>'
          ,'<div class="layui-colorpicker-basis-cursor"></div>'
        ,'</div>'
        ,'<div class="layui-colorpicker-side">'
          ,'<div class="layui-colorpicker-side-slider"></div>'
        ,'</div>'
      ,'</div>'
      
      //透明度条块
      ,'<div class="layui-colorpicker-main-alpha '+ (options.alpha ? SHOW : '') +'">'
        ,'<div class="layui-colorpicker-alpha-bgcolor">'
          ,'<div class="layui-colorpicker-alpha-slider"></div>'
        ,'</div>'
      ,'</div>'
      
      //预设颜色列表
      ,function(){
        if(options.predefine){
          var list = ['<div class="layui-colorpicker-main-pre">'];
          layui.each(options.colors, function(i, v){
            list.push(['<div class="layui-colorpicker-pre'+ ((v.match(/[0-9]{1,3}/g) || []).length > 3 
              ? ' layui-colorpicker-pre-isalpha' 
            : '') +'">'
              ,'<div style="background:'+ v +'"></div>'
            ,'</div>'].join(''));
          });
          list.push('</div>');
          return list.join('');
        } else {
          return '';
        }
      }()
      
      //底部表单元素区域
      ,'<div class="layui-colorpicker-main-input">'
        ,'<div class="layui-inline">'
          ,'<input type="text" class="layui-input">'
        ,'</div>'
        ,'<div class="layui-btn-container">'
          ,'<button class="layui-btn layui-btn-primary layui-btn-sm" colorpicker-events="clear">清空</button>'
          ,'<button class="layui-btn layui-btn-sm" colorpicker-events="confirm">确定</button>'
        ,'</div'
      ,'</div>'
    ,'</div>'].join(''))
    
    ,elemColorBoxSpan = that.elemColorBox.find('.' + PICKER_TRIG_SPAN)[0];
    
    //如果当前点击的颜色盒子已经存在选择器，则关闭
    if($(ELEM_MAIN)[0] && $(ELEM_MAIN).data('index') == that.index){
      that.removePicker(Class.thisElemInd);
    } else { //插入颜色选择器
      that.removePicker(Class.thisElemInd); 
      $('body').append(elemPicker);
    }

    // 记录当前执行的实例索引
    colorpicker.thisId = options.id;
    
    Class.thisElemInd = that.index; //记录最新打开的选择器索引
    Class.thisColor =  elemColorBox.style.background //记录最新打开的选择器颜色选中值
    
    that.position();
    that.pickerEvents();
  };

  //颜色选择器移除
  Class.prototype.removePicker = function(index){
    var that = this;
    var options = that.config;
    var elem = $('#layui-colorpicker'+ (index || that.index));

    if(elem[0]){
      elem.remove();
      delete colorpicker.thisId;

      // 面板关闭后的回调
      typeof options.close === 'function' && options.close(that.color);
    }

    return that;
  };
  
  //定位算法
  Class.prototype.position = function(){
    var that = this
    ,options = that.config;
    lay.position(that.bindElem || that.elemColorBox[0], that.elemPicker[0], {
      position: options.position
      ,align: 'center'
    });
    return that;
  };

  //颜色选择器赋值
  Class.prototype.val = function(){
    var that = this
    ,options = that.config
    
    ,elemColorBox = that.elemColorBox.find('.' + PICKER_TRIG_SPAN)
    ,elemPickerInput = that.elemPicker.find('.' + PICKER_INPUT)
    ,e = elemColorBox[0]
    ,bgcolor = e.style.backgroundColor;

    //判断是否有背景颜色
    if(bgcolor){
      
      //转化成hsb格式
      var hsb = RGBToHSB(RGBSTo(bgcolor))
      ,type = elemColorBox.attr('lay-type');
      
      //同步滑块的位置及颜色选择器的选择
      that.select(hsb.h, hsb.s, hsb.b);
      
      // 若格式要求为rgb
      if(type === 'torgb'){
        elemPickerInput.find('input').val(bgcolor);
      } else if(type === 'rgba'){ // 若格式要求为 rgba
        var rgb = RGBSTo(bgcolor);
        
        // 若开启透明度而没有设置，则给默认值
        if((bgcolor.match(/[0-9]{1,3}/g) || []).length === 3){
          elemPickerInput.find('input').val('rgba('+ rgb.r +', '+ rgb.g +', '+ rgb.b +', 1)');
          that.elemPicker.find('.'+ PICKER_ALPHA_SLIDER).css("left", 280);
        } else {
          elemPickerInput.find('input').val(bgcolor);
          var left = bgcolor.slice(bgcolor.lastIndexOf(",") + 1, bgcolor.length - 1) * 280;
          that.elemPicker.find('.'+ PICKER_ALPHA_SLIDER).css("left", left);
        }
        
        // 设置 span 背景色
        that.elemPicker.find('.'+ PICKER_ALPHA_BG)[0].style.background = 'linear-gradient(to right, rgba('+ rgb.r +', '+ rgb.g +', '+ rgb.b +', 0), rgb('+ rgb.r +', '+ rgb.g +', '+ rgb.b +'))';    
      } else {
        elemPickerInput.find('input').val('#'+ HSBToHEX(hsb));
      }
    } else {
      // 若没有背景颜色则默认到最初始的状态
      that.select(0,100,100);
      elemPickerInput.find('input').val("");
      that.elemPicker.find('.'+ PICKER_ALPHA_BG)[0].style.background = '';
      that.elemPicker.find('.'+ PICKER_ALPHA_SLIDER).css("left", 280);
    }
  };

  //颜色选择器滑动 / 点击
  Class.prototype.side = function(){
    var that = this
    ,options = that.config
    
    ,span = that.elemColorBox.find('.' + PICKER_TRIG_SPAN)
    ,type = span.attr('lay-type')

    ,side = that.elemPicker.find('.' + PICKER_SIDE)
    ,slider = that.elemPicker.find('.' + PICKER_SIDE_SLIDER)
    ,basis = that.elemPicker.find('.' + PICKER_BASIS)
    ,choose = that.elemPicker.find('.' + PICKER_BASIS_CUR)
    ,alphacolor = that.elemPicker.find('.' + PICKER_ALPHA_BG)
    ,alphaslider = that.elemPicker.find('.' + PICKER_ALPHA_SLIDER)
    
    ,_h = slider[0].offsetTop/180*360
    ,_b = 100 - (choose[0].offsetTop + 3)/180*100
    ,_s = (choose[0].offsetLeft + 3)/260*100
    ,_a = Math.round(alphaslider[0].offsetLeft/280*100)/100    
    
    ,i = that.elemColorBox.find('.' + PICKER_TRIG_I)
    ,pre = that.elemPicker.find('.layui-colorpicker-pre').children('div')

    ,change = function(x,y,z,a){
      that.select(x, y, z);
      var rgb = HSBToRGB({h:x, s:y, b:z});
      var color = HSBToHEX({h:x, s:y, b:z});
      var elemInput = that.elemPicker.find('.' + PICKER_INPUT).find('input');

      i.addClass(ICON_PICKER_DOWN).removeClass(ICON_PICKER_CLOSE);
      span[0].style.background = 'rgb('+ rgb.r +', '+ rgb.g +', '+ rgb.b +')';

      if(type === 'torgb'){
        elemInput.val('rgb('+ rgb.r +', '+ rgb.g +', '+ rgb.b +')');
      } else if(type  === 'rgba'){
        var left = a * 280;
        alphaslider.css("left", left);
        elemInput.val('rgba('+ rgb.r +', '+ rgb.g +', '+ rgb.b +', '+ a +')');
        span[0].style.background = 'rgba('+ rgb.r +', '+ rgb.g +', '+ rgb.b +', '+ a +')';
        alphacolor[0].style.background = 'linear-gradient(to right, rgba('+ rgb.r +', '+ rgb.g +', '+ rgb.b +', 0), rgb('+ rgb.r +', '+ rgb.g +', '+ rgb.b +'))'
      } else {
        elemInput.val('#'+ color);
      }
      
      //回调更改的颜色
      options.change && options.change($.trim(that.elemPicker.find('.' + PICKER_INPUT).find('input').val()));
    }

    //拖拽元素
    ,elemMove = $(['<div class="layui-auxiliar-moving" id="LAY-colorpicker-moving"></div>'].join(''))
    ,createMoveElem = function(call){
      $('#LAY-colorpicker-moving')[0] || $('body').append(elemMove);
      elemMove.on('mousemove', call);
      elemMove.on('mouseup', function(){
        elemMove.remove();
      }).on('mouseleave', function(){
        elemMove.remove();
      });
    };

    //右侧主色选择
    slider.on('mousedown', function(e){
      var oldtop = this.offsetTop
      ,oldy = e.clientY;
      var move = function(e){
        var top = oldtop + (e.clientY - oldy)
        ,maxh = side[0].offsetHeight;
        if(top < 0)top = 0;
        if(top > maxh)top = maxh;
        var h = top/180*360;
        _h = h;
        change(h, _s, _b, _a);
        e.preventDefault();
      };
      
      createMoveElem(move);
      //layui.stope(e);
      e.preventDefault();
    });
    
    side.on('click', function(e){
      var top = e.clientY - $(this).offset().top + $win.scrollTop();
      if(top < 0)top = 0;
      if(top > this.offsetHeight) top = this.offsetHeight;     
      var h = top/180*360;
      _h = h;
      change(h, _s, _b, _a); 
      e.preventDefault();
    });
    
    //中间小圆点颜色选择
    choose.on('mousedown', function(e){
      var oldtop = this.offsetTop
      ,oldleft = this.offsetLeft
      ,oldy = e.clientY
      ,oldx = e.clientX;
      var move = function(e){
        var top = oldtop + (e.clientY - oldy)
        ,left = oldleft + (e.clientX - oldx)
        ,maxh = basis[0].offsetHeight - 3
        ,maxw = basis[0].offsetWidth - 3;
        if(top < -3)top = -3;
        if(top > maxh)top = maxh;
        if(left < -3)left = -3;
        if(left > maxw)left = maxw;
        var s = (left + 3)/260*100
        ,b = 100 - (top + 3)/180*100;
        _b = b;
        _s = s;
        change(_h, s, b, _a); 
        e.preventDefault();
      };
      layui.stope(e);
      createMoveElem(move);
      e.preventDefault();
    });
    
    basis.on('mousedown', function(e){
      var top = e.clientY - $(this).offset().top - 3 + $win.scrollTop()
      ,left = e.clientX - $(this).offset().left - 3 + $win.scrollLeft()
      if(top < -3)top = -3;
      if(top > this.offsetHeight - 3)top = this.offsetHeight - 3;
      if(left < -3)left = -3;
      if(left > this.offsetWidth - 3)left = this.offsetWidth - 3;
      var s = (left + 3)/260*100
      ,b = 100 - (top + 3)/180*100;
      _b = b;
      _s = s;
      change(_h, s, b, _a); 
      layui.stope(e);
      e.preventDefault();
      choose.trigger(e, 'mousedown');
    });
    
    //底部透明度选择
    alphaslider.on('mousedown', function(e){
      var oldleft = this.offsetLeft
      ,oldx = e.clientX;
      var move = function(e){
        var left = oldleft + (e.clientX - oldx)
        ,maxw = alphacolor[0].offsetWidth;
        if(left < 0)left = 0;
        if(left > maxw)left = maxw;
        var a = Math.round(left /280*100) /100;
        _a = a;
        change(_h, _s, _b, a); 
        e.preventDefault();
      };
      
      createMoveElem(move);
      e.preventDefault();
    });
    alphacolor.on('click', function(e){
      var left = e.clientX - $(this).offset().left
      if(left < 0)left = 0;
      if(left > this.offsetWidth)left = this.offsetWidth;
      var a = Math.round(left /280*100) /100;
      _a = a;
      change(_h, _s, _b, a); 
      e.preventDefault();
    });
    
    //预定义颜色选择
    pre.each(function(){
      $(this).on('click', function(){
        $(this).parent('.layui-colorpicker-pre').addClass('selected').siblings().removeClass('selected');
        var color = this.style.backgroundColor
        ,hsb = RGBToHSB(RGBSTo(color))
        ,a = color.slice(color.lastIndexOf(",") + 1, color.length - 1),left;
        _h = hsb.h;
        _s = hsb.s;
        _b = hsb.b;
        if((color.match(/[0-9]{1,3}/g) || []).length === 3) a = 1;
        _a = a;
        left = a * 280;
        change(hsb.h, hsb.s, hsb.b, a);
      })
    });

    if(!lay.touchEventsSupported()) return;
    // 触摸事件模拟
    layui.each([
      {elem: side, eventType: 'click'},
      {elem: alphacolor, eventType: 'click'},
      {elem: basis, eventType: 'mousedown'}
    ], function(i, obj){
      lay.touchSwipe(obj.elem, {
        onTouchMove: function(e){
          touchHandler(e, obj.eventType)
        }
      })
    })

    function touchHandler(event, eventType) {
      var pointer = event.touches[0];
      var simulatedEvent = document.createEvent("MouseEvent");

      simulatedEvent.initMouseEvent(eventType, 
        true, true, window, 1, 
        pointer.screenX, pointer.screenY,pointer.clientX, pointer.clientY, 
        false, false, false, false, 0, null
      );
      pointer.target.dispatchEvent(simulatedEvent);
    }
  };

  //颜色选择器hsb转换
  Class.prototype.select = function(h, s, b, type){
    var that = this;
    var options = that.config;
    var hex = HSBToHEX({h:h, s:100, b:100});
    var color = HSBToHEX({h:h, s:s, b:b});
    var sidetop = h/360*180;
    var top = 180 - b/100*180 - 3;
    var left = s/100*260 - 3;
    
    that.elemPicker.find('.' + PICKER_SIDE_SLIDER).css("top", sidetop); //滑块的top
    that.elemPicker.find('.' + PICKER_BASIS)[0].style.background = '#' + hex; //颜色选择器的背景
    
    //选择器的top left
    that.elemPicker.find('.' + PICKER_BASIS_CUR).css({
      "top": top
      ,"left": left
    });
    
    // if(type === 'change') return;

    // 选中的颜色
    // that.elemPicker.find('.' + PICKER_INPUT).find('input').val('#'+ color);
  };
  
  Class.prototype.pickerEvents = function(){
    var that = this
    ,options = that.config
    
    ,elemColorBoxSpan = that.elemColorBox.find('.' + PICKER_TRIG_SPAN) //颜色盒子
    ,elemPickerInput = that.elemPicker.find('.' + PICKER_INPUT + ' input') //颜色选择器表单
    
    ,pickerEvents = {
      //清空
      clear: function(othis){
        elemColorBoxSpan[0].style.background ='';
        that.elemColorBox.find('.' + PICKER_TRIG_I).removeClass(ICON_PICKER_DOWN).addClass(ICON_PICKER_CLOSE);
        that.color = '';
        
        options.done && options.done('');
        that.removePicker();
      }
      
      //确认
      ,confirm: function(othis, change){
        var value =  $.trim(elemPickerInput.val())
        ,colorValue
        ,hsb;
        
        if(value.indexOf(',') > -1){
          hsb = RGBToHSB(RGBSTo(value));
          that.select(hsb.h, hsb.s, hsb.b);
          elemColorBoxSpan[0].style.background = (colorValue = '#' + HSBToHEX(hsb)); 
          
          if((value.match(/[0-9]{1,3}/g) || []).length > 3 && elemColorBoxSpan.attr('lay-type') === 'rgba'){
            var left = value.slice(value.lastIndexOf(",") + 1, value.length - 1) * 280;
            that.elemPicker.find('.' + PICKER_ALPHA_SLIDER).css("left", left);
            elemColorBoxSpan[0].style.background = value;
            colorValue = value;
          }
        } else {
          hsb = HEXToHSB(value);
          elemColorBoxSpan[0].style.background = (colorValue = '#' + HSBToHEX(hsb)); 
          that.elemColorBox.find('.' + PICKER_TRIG_I).removeClass(ICON_PICKER_CLOSE).addClass(ICON_PICKER_DOWN);
        }
        
        if(change === 'change'){
          that.select(hsb.h, hsb.s, hsb.b, change);
          options.change && options.change(colorValue);
          return;
        }
        that.color = value;
        
        options.done && options.done(value);
        that.removePicker(); 
      }
    };
    
    //选择器面板点击事件
    that.elemPicker.on('click', '*[colorpicker-events]', function(){
      var othis = $(this)
      ,attrEvent = othis.attr('colorpicker-events');
      pickerEvents[attrEvent] && pickerEvents[attrEvent].call(this, othis);
    });
    
    //输入框事件
    elemPickerInput.on('keyup', function(e){
      var othis = $(this);
      pickerEvents.confirm.call(this, othis, e.keyCode === 13 ?  null : 'change');
    });
  }

  // 颜色选择器输入
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;

    // 弹出颜色选择器
    that.elemColorBox.on('click' , function(){
      that.renderPicker();
      if($(ELEM_MAIN)[0]){
        that.val();
        that.side();
      }
    });
  };

  //全局事件
  (function(){
    //绑定关闭控件事件
    $doc.on(clickOrMousedown, function(e){
      if(!colorpicker.thisId) return;
      var that = thisModule.getThis(colorpicker.thisId);
      if(!that) return;

      var options = that.config;
      var elemColorBoxSpan = that.elemColorBox.find('.' + PICKER_TRIG_SPAN);

      //如果点击的元素是颜色框
      if($(e.target).hasClass(ELEM) 
        || $(e.target).parents('.'+ELEM)[0]
      ) return; 
      
      //如果点击的元素是选择器
      if($(e.target).hasClass(ELEM_MAIN.replace(/\./g, '')) 
        || $(e.target).parents(ELEM_MAIN)[0]
      ) return; 
      
      if(!that.elemPicker) return;
      
      if(that.color){
        var hsb = RGBToHSB(RGBSTo(that.color));
        that.select(hsb.h, hsb.s, hsb.b); 
      } else {
        that.elemColorBox.find('.' + PICKER_TRIG_I).removeClass(ICON_PICKER_DOWN).addClass(ICON_PICKER_CLOSE);
      }
      elemColorBoxSpan[0].style.background = that.color || '';
      
      // 取消选择的回调
      typeof options.cancel === 'function' && options.cancel(that.color);

      // 移除面板
      that.removePicker();
    });

    //自适应定位
    $win.on('resize', function(){
      if(!colorpicker.thisId) return;
      var that = thisModule.getThis(colorpicker.thisId);
      if(!that) return;

      if(!that.elemPicker ||  !$(ELEM_MAIN)[0]){
        return false;
      }
      that.position();
    });
  })();

  // 记录所有实例
  thisModule.that = {}; // 记录所有实例对象
  
  // 获取当前实例对象
  thisModule.getThis = function(id){
    var that = thisModule.that[id];
    if(!that) hint.error(id ? (MOD_NAME +' instance with ID \''+ id +'\' not found') : 'ID argument required');
    return that;
  };
  
  //核心入口
  colorpicker.render = function(options){
    var inst = new Class(options);
    return thisModule.call(inst);
  };
  
  exports(MOD_NAME, colorpicker);
});
/**
 * element
 * 常用元素操作组件
 */
 
layui.define('jquery', function(exports){
  'use strict';
  
  var $ = layui.$;
  var hint = layui.hint();
  var device = layui.device();
  
  var MOD_NAME = 'element';
  var THIS = 'layui-this';
  var SHOW = 'layui-show';
  var TITLE = '.layui-tab-title';
  
  var Element = function(){
    this.config = {};
  };
  
  // 全局设置
  Element.prototype.set = function(options){
    var that = this;
    $.extend(true, that.config, options);
    return that;
  };
  
  // 表单事件
  Element.prototype.on = function(events, callback){
    return layui.onevent.call(this, MOD_NAME, events, callback);
  };
  
  // 外部 Tab 新增
  Element.prototype.tabAdd = function(filter, options){
    var tabElem = $('.layui-tab[lay-filter='+ filter +']');
    var titElem = tabElem.children(TITLE);
    var barElem = titElem.children('.layui-tab-bar');
    var contElem = tabElem.children('.layui-tab-content');
    var li = '<li'+ function(){
      var layAttr = [];
      layui.each(options, function(key, value){
        if(/^(title|content)$/.test(key)) return;
        layAttr.push('lay-'+ key +'="'+ value +'"');
      });
      if(layAttr.length > 0) layAttr.unshift(''); //向前插，预留空格
      return layAttr.join(' ');
    }() +'>'+ (options.title || 'unnaming') +'</li>';
    
    barElem[0] ? barElem.before(li) : titElem.append(li);
    contElem.append('<div class="layui-tab-item">'+ (options.content || '') +'</div>');
    // call.hideTabMore(true);
    // 是否添加即切换
    options.change && this.tabChange(filter, options.id);
    titElem.data('LAY_TAB_CHANGE', options.change);
    call.tabAuto(options.change ? 'change' : null);
    return this;
  };
  
  // 外部 Tab 删除
  Element.prototype.tabDelete = function(filter, layid){
    var tabElem = $('.layui-tab[lay-filter='+ filter +']');
    var titElem = tabElem.children(TITLE);
    var liElem = titElem.find('>li[lay-id="'+ layid +'"]');
    call.tabDelete(null, liElem);
    return this;
  };
  
  // 外部 Tab 切换
  Element.prototype.tabChange = function(filter, layid){
    var tabElem = $('.layui-tab[lay-filter='+ filter +']');
    var titElem = tabElem.children(TITLE);
    var liElem = titElem.find('>li[lay-id="'+ layid +'"]');

    call.tabClick.call(liElem[0], {
      liElem: liElem
    });
    return this;
  };
  
  // 自定义 Tab 选项卡
  Element.prototype.tab = function(options){
    options = options || {};
    dom.on('click', options.headerElem, function(e){
      var index = $(this).index();
      call.tabClick.call(this, {
        index: index,
        options: options
      });
    });
  };
  
  
  // 动态改变进度条
  Element.prototype.progress = function(filter, percent){
    var ELEM = 'layui-progress';
    var elem = $('.'+ ELEM +'[lay-filter='+ filter +']');
    var elemBar = elem.find('.'+ ELEM +'-bar');
    var text = elemBar.find('.'+ ELEM +'-text');

    elemBar.css('width', function(){
      return /^.+\/.+$/.test(percent) 
        ? (new Function('return '+ percent)() * 100) + '%'
     : percent;
    }).attr('lay-percent', percent);
    text.text(percent);
    return this;
  };
  
  var NAV_ELEM = '.layui-nav';
  var NAV_ITEM = 'layui-nav-item';
  var NAV_BAR = 'layui-nav-bar';
  var NAV_TREE = 'layui-nav-tree';
  var NAV_CHILD = 'layui-nav-child';
  var NAV_CHILD_C = 'layui-nav-child-c';
  var NAV_MORE = 'layui-nav-more';
  var NAV_DOWN = 'layui-icon-down';
  var NAV_ANIM = 'layui-anim layui-anim-upbit';
  
  // 基础事件体
  var call = {
    // Tab 点击
    tabClick: function(obj){
      obj = obj || {};
      var options = obj.options || {};
      var othis = obj.liElem || $(this);
      var parents = options.headerElem 
        ? othis.parent() 
      : othis.parents('.layui-tab').eq(0);
      var item = options.bodyElem 
        ? $(options.bodyElem) 
      : parents.children('.layui-tab-content').children('.layui-tab-item');
      var elemA = othis.find('a');
      var isJump = elemA.attr('href') !== 'javascript:;' && elemA.attr('target') === '_blank'; // 是否存在跳转
      var unselect = typeof othis.attr('lay-unselect') === 'string'; // 是否禁用选中
      var filter = parents.attr('lay-filter');

      // 下标
      var index = 'index' in obj 
        ? obj.index 
      : othis.parent().children('li').index(othis);
      
      // 执行切换
      if(!(isJump || unselect)){
        othis.addClass(THIS).siblings().removeClass(THIS);
        item.eq(index).addClass(SHOW).siblings().removeClass(SHOW);
      }
      
      layui.event.call(this, MOD_NAME, 'tab('+ filter +')', {
        elem: parents,
        index: index
      });
    }
    
    // Tab 删除
    ,tabDelete: function(e, othis){
      var li = othis || $(this).parent();
      var index = li.parent().children('li').index(li);
      var tabElem = li.closest('.layui-tab');
      var item = tabElem.children('.layui-tab-content').children('.layui-tab-item');
      var filter = tabElem.attr('lay-filter');
      
      if(li.hasClass(THIS)){
        if (li.next()[0] && li.next().is('li')){
          call.tabClick.call(li.next()[0], {
            index: index + 1
          });
        } else if (li.prev()[0] && li.prev().is('li')){
          call.tabClick.call(li.prev()[0], null, index - 1);
        }
      }
      
      li.remove();
      item.eq(index).remove();
      setTimeout(function(){
        call.tabAuto();
      }, 50);
      
      layui.event.call(this, MOD_NAME, 'tabDelete('+ filter +')', {
        elem: tabElem,
        index: index
      });
    }
    
    // Tab 自适应
    ,tabAuto: function(spread){
      var SCROLL = 'layui-tab-scroll';
      var MORE = 'layui-tab-more';
      var BAR = 'layui-tab-bar';
      var CLOSE = 'layui-tab-close';
      var that = this;
      
      $('.layui-tab').each(function(){
        var othis = $(this);
        var title = othis.children('.layui-tab-title');
        var item = othis.children('.layui-tab-content').children('.layui-tab-item');
        var STOPE = 'lay-stope="tabmore"';
        var span = $('<span class="layui-unselect layui-tab-bar" '+ STOPE +'><i '+ STOPE +' class="layui-icon">&#xe61a;</i></span>');

        if(that === window && device.ie != 8){
          // call.hideTabMore(true)
        }
        
        // 开启关闭图标
        if(othis.attr('lay-allowclose')){
          title.find('li').each(function(){
            var li = $(this);
            if(!li.find('.'+CLOSE)[0]){
              var close = $('<i class="layui-icon layui-icon-close layui-unselect '+ CLOSE +'"></i>');
              close.on('click', call.tabDelete);
              li.append(close);
            }
          });
        }
        
        if(typeof othis.attr('lay-unauto') === 'string') return;
        
        // 响应式
        if(
          title.prop('scrollWidth') > title.outerWidth() + 1 || (
            title.find('li').length && title.height() > function(height){
              return height + height/2;
            }(title.find('li').eq(0).height())
          )
        ){
          // 若执行是来自于切换，则自动展开
          (
            spread === 'change' && title.data('LAY_TAB_CHANGE')
          ) && title.addClass(MORE);
          
          if(title.find('.'+BAR)[0]) return;
          title.append(span);
          othis.attr('overflow', '');

          // 展开图标事件
          span.on('click', function(e){
            var isSpread = title.hasClass(MORE);
            title[isSpread ? 'removeClass' : 'addClass'](MORE);
          });
        } else {
          title.find('.'+ BAR).remove();
          othis.removeAttr('overflow');
        }
      });
    }
    // 隐藏更多 Tab
    ,hideTabMore: function(e){
      var tsbTitle = $('.layui-tab-title');
      if(e === true || $(e.target).attr('lay-stope') !== 'tabmore'){
        tsbTitle.removeClass('layui-tab-more');
        tsbTitle.find('.layui-tab-bar').attr('title','');
      }
    }
    
    //点击一级菜单
    /*
    ,clickThis: function(){
      var othis = $(this), parents = othis.parents(NAV_ELEM)
      ,filter = parents.attr('lay-filter')
      ,elemA = othis.find('a')
      ,unselect = typeof othis.attr('lay-unselect') === 'string';

      if(othis.find('.'+NAV_CHILD)[0]) return;
      
      if(!(elemA.attr('href') !== 'javascript:;' && elemA.attr('target') === '_blank') && !unselect){
        parents.find('.'+THIS).removeClass(THIS);
        othis.addClass(THIS);
      }
      
      layui.event.call(this, MOD_NAME, 'nav('+ filter +')', othis);
    }
    )
    */
    
    // 点击菜单 - a 标签触发
    ,clickThis: function() {
      var othis = $(this);
      var parents = othis.closest(NAV_ELEM);
      var filter = parents.attr('lay-filter');
      var parent = othis.parent() ;
      var child = othis.siblings('.'+ NAV_CHILD);
      var unselect = typeof parent.attr('lay-unselect') === 'string'; // 是否禁用选中
      
      // 满足点击选中的条件
      if (!(othis.attr('href') !== 'javascript:;' && othis.attr('target') === '_blank') && !unselect) {
        if (!child[0]) {
          parents.find('.'+ THIS).removeClass(THIS);
          parent.addClass(THIS);
        }
      }
      
      // 若为垂直菜单
      if (parents.hasClass(NAV_TREE)) {
        var NAV_ITEMED = NAV_ITEM + 'ed'; // 用于标注展开状态
        var needExpand = !parent.hasClass(NAV_ITEMED); // 是否执行展开
        var ANIM_MS = 200; // 动画过渡毫秒数

        // 动画执行完成后的操作
        var complete = function() {
          $(this).css({
            "display": "" // 剔除动画生成的 style display，以适配外部样式的状态重置
          });
          // 避免导航滑块错位
          parents.children('.'+ NAV_BAR).css({
            opacity: 0
          })
        };

        // 是否正处于动画中的状态
        if (child.is(':animated')) return;

        // 剔除可能存在的 CSS3 动画类
        child.removeClass(NAV_ANIM);

        // 若有子菜单，则对其执行展开或收缩
        if (child[0]) {
          if (needExpand) {
            // 先执行 slideDown 动画，再标注展开状态样式，避免元素 `block` 状态导致动画无效
            child.slideDown(ANIM_MS, complete);
            parent.addClass(NAV_ITEMED);
          } else {
            // 先取消展开状态样式，再将元素临时显示，避免 `none` 状态导致 slideUp 动画无效
            parent.removeClass(NAV_ITEMED);
            child.show().slideUp(ANIM_MS, complete);
          }

          // 手风琴 --- 收缩兄弟展开项
          if (typeof parents.attr('lay-accordion') === 'string' || parents.attr('lay-shrink') === 'all') {
            var parentSibs = parent.siblings('.'+ NAV_ITEMED);
            parentSibs.removeClass(NAV_ITEMED);
            parentSibs.children('.'+ NAV_CHILD).show().stop().slideUp(ANIM_MS, complete);
          }
        }
      }
      
      layui.event.call(this, MOD_NAME, 'nav('+ filter +')', othis);
    }
    
    // 折叠面板
    ,collapse: function(){
      var othis = $(this);
      var icon = othis.find('.layui-colla-icon');
      var elemCont = othis.siblings('.layui-colla-content');
      var parents = othis.parents('.layui-collapse').eq(0);
      var filter = parents.attr('lay-filter');
      var isNone = elemCont.css('display') === 'none';
      
      // 是否手风琴
      if(typeof parents.attr('lay-accordion') === 'string'){
        var show = parents.children('.layui-colla-item').children('.'+SHOW);
        show.siblings('.layui-colla-title').children('.layui-colla-icon').html('&#xe602;');
        show.removeClass(SHOW);
      }
      
      elemCont[isNone ? 'addClass' : 'removeClass'](SHOW);
      icon.html(isNone ? '&#xe61a;' : '&#xe602;');
      
      layui.event.call(this, MOD_NAME, 'collapse('+ filter +')', {
        title: othis
        ,content: elemCont
        ,show: isNone
      });
    }
  };
  
  // 初始化元素操作
  Element.prototype.init = function(type, filter){
    var that = this, elemFilter = function(){
      return filter ? ('[lay-filter="' + filter +'"]') : '';
    }(), items = {
      
      // Tab 选项卡
      tab: function(){
        call.tabAuto.call({});
      }
      
      // 导航菜单
      ,nav: function(){
        var TIME = 200;
        var timer = {};
        var timerMore = {};
        var timeEnd = {};
        var NAV_TITLE = 'layui-nav-title';
        
        // 滑块跟随
        var follow = function(bar, nav, index) {
          var othis = $(this);
          var child = othis.find('.'+NAV_CHILD);

          // 是否垂直导航菜单
          if (nav.hasClass(NAV_TREE)) {
            // 无子菜单时跟随
            if (!child[0]) {
              var thisA = othis.children('.'+ NAV_TITLE);
              bar.css({
                top: othis.offset().top - nav.offset().top,
                height: (thisA[0] ? thisA : othis).outerHeight(),
                opacity: 1
              });
            }
          } else {
            child.addClass(NAV_ANIM);
            
            // 若居中对齐
            if (child.hasClass(NAV_CHILD_C)) {
              child.css({
                left: -(child.outerWidth() - othis.width()) / 2
              });
            }
            
            // 滑块定位
            if (child[0]) { // 若有子菜单，则滑块消失
              bar.css({
                left: bar.position().left + bar.width() / 2,
                width: 0,
                opacity: 0
              });
            } else { // bar 跟随
              bar.css({
                left: othis.position().left + parseFloat(othis.css('marginLeft')),
                top: othis.position().top + othis.height() - bar.height()
              });
            }
            
            // 渐显滑块并适配宽度
            timer[index] = setTimeout(function() {
              bar.css({
                width: child[0] ? 0 : othis.width(),
                opacity: child[0] ? 0 : 1
              });
            }, device.ie && device.ie < 10 ? 0 : TIME);
            
            // 显示子菜单
            clearTimeout(timeEnd[index]);
            if (child.css('display') === 'block') {
              clearTimeout(timerMore[index]);
            }
            timerMore[index] = setTimeout(function(){
              child.addClass(SHOW);
              othis.find('.'+NAV_MORE).addClass(NAV_MORE+'d');
            }, 300);
          }
        };
        
        // 遍历导航
        $(NAV_ELEM + elemFilter).each(function(index) {
          var othis = $(this);
          var bar = $('<span class="'+ NAV_BAR +'"></span>');
          var itemElem = othis.find('.'+NAV_ITEM);
          
          // hover 滑动效果
          if (!othis.find('.'+NAV_BAR)[0]) {
            othis.append(bar);
            ( othis.hasClass(NAV_TREE)
              ? itemElem.find('dd,>.'+ NAV_TITLE) 
              : itemElem
            ).on('mouseenter', function() {
              follow.call(this, bar, othis, index);
            }).on('mouseleave', function() { // 鼠标移出
              // 是否为垂直导航
              if (othis.hasClass(NAV_TREE)) {
                bar.css({
                  height: 0,
                  opacity: 0
                });
              } else {
                // 隐藏子菜单
                clearTimeout(timerMore[index]);
                timerMore[index] = setTimeout(function(){
                  othis.find('.'+ NAV_CHILD).removeClass(SHOW);
                  othis.find('.'+ NAV_MORE).removeClass(NAV_MORE +'d');
                }, 300);
              }
            });

            // 鼠标离开当前菜单时
            othis.on('mouseleave', function() {
              clearTimeout(timer[index])
              timeEnd[index] = setTimeout(function() {
                if (!othis.hasClass(NAV_TREE)) {
                  bar.css({
                    width: 0,
                    left: bar.position().left + bar.width() / 2,
                    opacity: 0
                  });
                }
              }, TIME);
            });
          }
          
          // 展开子菜单
          itemElem.find('a').each(function() {
            var thisA = $(this);
            var parent = thisA.parent();
            var child = thisA.siblings('.'+ NAV_CHILD);
            
            // 输出小箭头
            if (child[0] && !thisA.children('.'+ NAV_MORE)[0]) {
              thisA.append('<i class="layui-icon '+ NAV_DOWN +' '+ NAV_MORE +'"></i>');
            }
            
            thisA.off('click', call.clickThis).on('click', call.clickThis); // 点击菜单
          });
        });
      }
      
      //面包屑
      ,breadcrumb: function(){
        var ELEM = '.layui-breadcrumb';
        
        $(ELEM + elemFilter).each(function(){
          var othis = $(this)
          ,ATTE_SPR = 'lay-separator'
          ,separator = othis.attr(ATTE_SPR) || '/'
          ,aNode = othis.find('a');
          if(aNode.next('span['+ ATTE_SPR +']')[0]) return;
          aNode.each(function(index){
            if(index === aNode.length - 1) return;
            $(this).after('<span '+ ATTE_SPR +'>'+ separator +'</span>');
          });
          othis.css('visibility', 'visible');
        });
      }
      
      //进度条
      ,progress: function(){
        var ELEM = 'layui-progress';
        $('.' + ELEM + elemFilter).each(function(){
          var othis = $(this)
          ,elemBar = othis.find('.layui-progress-bar')
          ,percent = elemBar.attr('lay-percent');

          elemBar.css('width', function(){
            return /^.+\/.+$/.test(percent) 
              ? (new Function('return '+ percent)() * 100) + '%'
           : percent;
          });
          
          if(othis.attr('lay-showpercent')){
            setTimeout(function(){
              elemBar.html('<span class="'+ ELEM +'-text">'+ percent +'</span>');
            },350);
          }
        });
      }
      
      //折叠面板
      ,collapse: function(){
        var ELEM = 'layui-collapse';
        
        $('.' + ELEM + elemFilter).each(function(){
          var elemItem = $(this).find('.layui-colla-item')
          elemItem.each(function(){
            var othis = $(this)
            ,elemTitle = othis.find('.layui-colla-title')
            ,elemCont = othis.find('.layui-colla-content')
            ,isNone = elemCont.css('display') === 'none';
            
            //初始状态
            elemTitle.find('.layui-colla-icon').remove();
            elemTitle.append('<i class="layui-icon layui-colla-icon">'+ (isNone ? '&#xe602;' : '&#xe61a;') +'</i>');

            //点击标题
            elemTitle.off('click', call.collapse).on('click', call.collapse);
          });     
         
        });
      }
    };

    return items[type] ? items[type]() : layui.each(items, function(index, item){
      item();
    });
  };
  
  Element.prototype.render = Element.prototype.init;

  var element = new Element();
  var dom = $(document);
  
  $(function(){
    element.render();
  });

  dom.on('click', '.layui-tab-title li', call.tabClick); // Tab 切换
  // dom.on('click', call.hideTabMore); // 隐藏展开的 Tab
  $(window).on('resize', call.tabAuto); // 自适应
  
  exports(MOD_NAME, element);
});

/**
 * upload
 * 上传组件
 */
 
layui.define(['lay', 'layer'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var lay = layui.lay;
  var layer = layui.layer;
  var device = layui.device();

  // 模块名
  var MOD_NAME = 'upload';
  var MOD_INDEX = 'layui_'+ MOD_NAME +'_index'; // 模块索引名

  // 外部接口
  var upload = {
    config: {}, // 全局配置项
    index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0, // 索引
    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },
    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };
  
  // 操作当前实例
  var thisModule = function(){
    var that = this;
    var options = that.config;
    var id = options.id;

    thisModule.that[id] = that; // 记录当前实例对象

    return {
      upload: function(files){
        that.upload.call(that, files);
      },
      reload: function(options){
        that.reload.call(that, options);
      },
      config: that.config
    }
  };
  
  // 字符常量
  var ELEM = 'layui-upload';
  var THIS = 'layui-this';
  var SHOW = 'layui-show';
  var HIDE = 'layui-hide';
  var DISABLED = 'layui-disabled';
  
  var ELEM_FILE = 'layui-upload-file';
  var ELEM_FORM = 'layui-upload-form';
  var ELEM_IFRAME = 'layui-upload-iframe';
  var ELEM_CHOOSE = 'layui-upload-choose';
  var ELEM_DRAG = 'layui-upload-drag';
  var UPLOADING = 'UPLOADING';
  
  // 构造器
  var Class = function(options){
    var that = this;
    that.index = ++upload.index;
    that.config = $.extend({}, that.config, upload.config, options);
    that.render();
  };
  
  // 默认配置
  Class.prototype.config = {
    accept: 'images', // 允许上传的文件类型：images/file/video/audio
    exts: '', // 允许上传的文件后缀名
    auto: true, // 是否选完文件后自动上传
    bindAction: '', // 手动上传触发的元素
    url: '', // 上传地址
    force: '', // 强制规定返回的数据格式，目前只支持是否强制 json
    field: 'file', // 文件字段名
    acceptMime: '', // 筛选出的文件类型，默认为所有文件
    method: 'post', // 请求上传的 http 类型
    data: {}, // 请求上传的额外参数
    drag: true, // 是否允许拖拽上传
    size: 0, // 文件限制大小，默认不限制
    number: 0, // 允许同时上传的文件数，默认不限制
    multiple: false, // 是否允许多文件上传，不支持 ie8-9
    text: { // 自定义提示文本
      "cross-domain": "Cross-domain requests are not supported", // 跨域
      "data-format-error": "Please return JSON data format", // 数据格式错误
      "check-error": "", // 文件格式校验失败
      "error": "", // 上传失败
      "limit-number": null, // 限制 number 属性的提示 --- function
      "limit-size": null // 限制 size 属性的提示 --- function
    }
  };

  // 重载实例
  Class.prototype.reload = function(options){
    var that = this;
    that.config = $.extend({}, that.config, options);
    that.render(true);
  };
  
  // 初始渲染
  Class.prototype.render = function(rerender){
    var that = this;
    var options = that.config;

    // 若 elem 非唯一
    var elem = $(options.elem);
    if (elem.length > 1) {
      layui.each(elem, function() {
        upload.render($.extend({}, options, {
          elem: this
        }));
      });
      return that;
    }

    // 合并 lay-options 属性上的配置信息
    $.extend(options, lay.options(elem[0], {
      attr: elem.attr('lay-data') ? 'lay-data' : null // 兼容旧版的 lay-data 属性
    }));

    // 若重复执行 render，则视为 reload 处理
    if (!rerender && elem[0] && elem.data(MOD_INDEX)) {
      var newThat = thisModule.getThis(elem.data(MOD_INDEX));
      if(!newThat) return;

      return newThat.reload(options);
    }

    options.elem = $(options.elem);
    options.bindAction = $(options.bindAction);

    // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
    options.id = 'id' in options ? options.id : (
      elem.attr('id') || that.index
    );

    that.file();
    that.events();
  };
  
  //追加文件域
  Class.prototype.file = function(){
    var that = this;
    var options = that.config;
    var elemFile = that.elemFile = $([
      '<input class="'+ ELEM_FILE +'" type="file" accept="'+ options.acceptMime +'" name="'+ options.field +'"'
      ,(options.multiple ? ' multiple' : '') 
      ,'>'
    ].join(''));
    var next = options.elem.next();
    
    if(next.hasClass(ELEM_FILE) || next.hasClass(ELEM_FORM)){
      next.remove();
    }
    
    //包裹ie8/9容器
    if(device.ie && device.ie < 10){
      options.elem.wrap('<div class="layui-upload-wrap"></div>');
    }
    
    that.isFile() ? (
      that.elemFile = options.elem,
      options.field = options.elem[0].name
    ) : options.elem.after(elemFile);
    
    //初始化ie8/9的Form域
    if(device.ie && device.ie < 10){
      that.initIE();
    }
  };
  
  //ie8-9初始化
  Class.prototype.initIE = function(){
    var that = this;
    var options = that.config;
    var iframe = $('<iframe id="'+ ELEM_IFRAME +'" class="'+ ELEM_IFRAME +'" name="'+ ELEM_IFRAME +'" frameborder="0"></iframe>');
    var elemForm = $(['<form target="'+ ELEM_IFRAME +'" class="'+ ELEM_FORM +'" method="post" key="set-mine" enctype="multipart/form-data" action="'+ options.url +'">'
    ,'</form>'].join(''));
    
    //插入iframe    
    $('#'+ ELEM_IFRAME)[0] || $('body').append(iframe);

    //包裹文件域
    if(!options.elem.next().hasClass(ELEM_FORM)){
      that.elemFile.wrap(elemForm);      
      
      //追加额外的参数
      options.elem.next('.'+ ELEM_FORM).append(function(){
        var arr = [];
        layui.each(options.data, function(key, value){
          value = typeof value === 'function' ? value() : value;
          arr.push('<input type="hidden" name="'+ key +'" value="'+ value +'">')
        });
        return arr.join('');
      }());
    }
  };
  
  //异常提示
  Class.prototype.msg = function(content){
    return layer.msg(content, {
      icon: 2,
      shift: 6
    });
  };
  
  //判断绑定元素是否为文件域本身
  Class.prototype.isFile = function(){
    var elem = this.config.elem[0];
    if(!elem) return;
    return elem.tagName.toLocaleLowerCase() === 'input' && elem.type === 'file'
  }
  
  //预读图片信息
  Class.prototype.preview = function(callback){
    var that = this;
    if(window.FileReader){
      layui.each(that.chooseFiles, function(index, file){
        var reader = new FileReader();
        reader.readAsDataURL(file);  
        reader.onload = function(){
          callback && callback(index, file, this.result);
        }
      });
    }
  };
  
  // 执行上传
  Class.prototype.upload = function(files, type){
    var that = this;
    var options = that.config;
    var text = options.text || {};
    var elemFile = that.elemFile[0];

    // 获取文件队列
    var getFiles = function(){
      return files || that.files || that.chooseFiles || elemFile.files;
    };
    
    // 高级浏览器处理方式，支持跨域
    var ajaxSend = function(){
      var successful = 0;
      var failed = 0;
      var items = getFiles();

      // 多文件全部上传完毕的回调
      var allDone = function(){
        if(options.multiple && successful + failed === that.fileLength){
          typeof options.allDone === 'function' && options.allDone({
            total: that.fileLength,
            successful: successful,
            failed: failed
          });
        }
      };

      // 发送请求
      var request = function(sets){
        var formData = new FormData();

        // 恢复文件状态
        var resetFileState = function(file) {
          if (sets.unified) {
            layui.each(items, function(index, file){
              delete file[UPLOADING];
            });
          } else {
            delete file[UPLOADING];
          }
        };

        // 追加额外的参数
        layui.each(options.data, function(key, value){
          value = typeof value === 'function' 
            ? sets.unified ? value() : value(sets.index, sets.file)
            : value;
          formData.append(key, value);
        });

        /*
         * 添加 file 到表单域
         */

        // 是否统一上传
        if (sets.unified) {
          layui.each(items, function(index, file){
            if (file[UPLOADING]) return;
            file[UPLOADING] = true; // 上传中的标记
            formData.append(options.field, file);
          });
        } else { // 逐一上传
          if (sets.file[UPLOADING]) return;
          formData.append(options.field, sets.file);
          sets.file[UPLOADING] = true; // 上传中的标记
        }

        // ajax 参数
        var opts = {
          url: options.url,
          type: 'post', // 统一采用 post 上传
          data: formData,
          dataType: options.dataType || 'json',
          contentType: false,
          processData: false,
          headers: options.headers || {},
          success: function(res){ // 成功回调
            options.unified ? (successful += that.fileLength) : successful++;
            done(sets.index, res);
            allDone(sets.index);
            resetFileState(sets.file);
          },
          error: function(e){ // 异常回调
            options.unified ? (failed += that.fileLength) : failed++;
            that.msg(text['error'] || [
              'Upload failed, please try again.',
              'status: '+ (e.status || '') +' - '+ (e.statusText || 'error')
            ].join('<br>'));
            error(sets.index);
            allDone(sets.index);
            resetFileState(sets.file);
          }
        };

        // 进度条
        if(typeof options.progress === 'function'){
          opts.xhr = function(){
            var xhr = $.ajaxSettings.xhr();
            // 上传进度
            xhr.upload.addEventListener("progress", function (obj) {
              if(obj.lengthComputable){
                var percent = Math.floor((obj.loaded/obj.total)* 100); // 百分比
                options.progress(percent, (options.item ? options.item[0] : options.elem[0]) , obj, sets.index);
              }
            });
            return xhr;
          }
        }
        $.ajax(opts);
      };

      // 多文件是否一起上传
      if(options.unified){
        request({
          unified: true,
          index: 0
        });
      } else {
        layui.each(items, function(index, file){
          request({
            index: index,
            file: file
          });
        });
      }
    };
    
    // 低版本 IE 处理方式，不支持跨域
    var iframeSend = function(){
      var iframe = $('#'+ ELEM_IFRAME);
    
      that.elemFile.parent().submit();

      // 获取响应信息
      clearInterval(Class.timer);
      Class.timer = setInterval(function() {
        var res, iframeBody = iframe.contents().find('body');
        try {
          res = iframeBody.text();
        } catch(e) {
          that.msg(text['cross-domain']); 
          clearInterval(Class.timer);
          error();
        }
        if(res){
          clearInterval(Class.timer);
          iframeBody.html('');
          done(0, res);
        }
      }, 30); 
    };
    
    // 统一回调
    var done = function(index, res){
      that.elemFile.next('.'+ ELEM_CHOOSE).remove();
      elemFile.value = '';
      
      if(options.force === 'json'){
        if(typeof res !== 'object'){
          try {
            res = JSON.parse(res);
          } catch(e){
            res = {};
            return that.msg(text['data-format-error']);
          }
        }
      }
      
      typeof options.done === 'function' && options.done(res, index || 0, function(files){
        that.upload(files);
      });
    };
    
    // 统一网络异常回调
    var error = function(index){
      if(options.auto){
        elemFile.value = '';
      }
      typeof options.error === 'function' && options.error(index || 0, function(files){
        that.upload(files);
      });
    };
    
    var check;
    var exts = options.exts;
    var value = function(){
      var arr = [];
      layui.each(files || that.chooseFiles, function(i, item){
        arr.push(item.name);
      });
      return arr;
    }();
    
    // 回调函数返回的参数
    var args = {
      // 预览
      preview: function(callback){
        that.preview(callback);
      },
      // 上传
      upload: function(index, file){
        var thisFile = {};
        thisFile[index] = file;
        that.upload(thisFile);
      },
      // 追加文件到队列
      pushFile: function(){
        that.files = that.files || {};
        layui.each(that.chooseFiles, function(index, item){
          that.files[index] = item;
        });
        return that.files;
      },
      // 重置文件
      resetFile: function(index, file, filename){
        var newFile = new File([file], filename);
        that.files = that.files || {};
        that.files[index] = newFile;
      }
    };
    
    // 提交上传
    var send = function(){
      // 上传前的回调 - 如果回调函数明确返回 false，则停止上传
      if(options.before && (options.before(args) === false)) return;

      // IE 兼容处理
      if(device.ie){
        return device.ie > 9 ? ajaxSend() : iframeSend();
      }
      
      ajaxSend();
    };
    
    // 文件类型名称
    var typeName = ({
      file: '文件',
      images: '图片',
      video: '视频',
      audio: '音频'
    })[options.accept] || '文件';

    // 校验文件格式
    value = value.length === 0 
      ? ((elemFile.value.match(/[^\/\\]+\..+/g)||[]) || '')
    : value;
    
    // 若文件域值为空
    if (value.length === 0) return;
    
    // 根据文件类型校验
    switch(options.accept){
      case 'file': // 一般文件
        layui.each(value, function(i, item){
          if(exts && !RegExp('.\\.('+ exts +')$', 'i').test(escape(item))){
            return check = true;
          }
        });
      break;
      case 'video': // 视频文件
        layui.each(value, function(i, item){
          if(!RegExp('.\\.('+ (exts || 'avi|mp4|wma|rmvb|rm|flash|3gp|flv') +')$', 'i').test(escape(item))){
            return check = true;
          }
        });
      break;
      case 'audio': // 音频文件
        layui.each(value, function(i, item){
          if(!RegExp('.\\.('+ (exts || 'mp3|wav|mid') +')$', 'i').test(escape(item))){
            return check = true;
          }
        });
      break;
      default: // 图片文件
        layui.each(value, function(i, item){
          if(!RegExp('.\\.('+ (exts || 'jpg|png|gif|bmp|jpeg|svg') +')$', 'i').test(escape(item))){
            return check = true;
          }
        });
      break;
    }
    
    // 校验失败提示
    if(check){
      that.msg(text['check-error'] || ('选择的'+ typeName +'中包含不支持的格式'));
      return elemFile.value = '';
    }

    // 选择文件的回调      
    if(type === 'choose' || options.auto){
      options.choose && options.choose(args);
      if(type === 'choose'){
        return;
      }
    }
    
    // 检验文件数量
    that.fileLength = function(){
      var length = 0;
      var items = getFiles();
      layui.each(items, function(){
        length++;
      });
      return length;
    }();
    
    if(options.number && that.fileLength > options.number){
      return that.msg(typeof text['limit-number'] === 'function' 
        ? text['limit-number'](options, that.fileLength) 
      : (
        '同时最多只能上传: '+ options.number + ' 个文件'
        +'<br>您当前已经选择了: '+ that.fileLength +' 个文件'
      ));
    }
    
    // 检验文件大小
    if(options.size > 0 && !(device.ie && device.ie < 10)){
      var limitSize;
      
      layui.each(getFiles(), function(index, file){
        if(file.size > 1024*options.size){
          var size = options.size/1024;
          size = size >= 1 ? (size.toFixed(2) + 'MB') : options.size + 'KB'
          elemFile.value = '';
          limitSize = size;
        }
      });
      if(limitSize) return that.msg(typeof text['limit-size'] === 'function' 
        ? text['limit-size'](options, limitSize) 
      : '文件大小不能超过 '+ limitSize);
    }

    send();
  };
  
  //事件处理
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;
    
    // 设置当前选择的文件队列
    var setChooseFile = function(files){
      that.chooseFiles = {};
      layui.each(files, function(i, item){
        var time = new Date().getTime();
        that.chooseFiles[time + '-' + i] = item;
      });
    };
    
    // 设置选择的文本
    var setChooseText = function(files, filename){
      var elemFile = that.elemFile;
      var item = options.item ? options.item : options.elem;
      var value = files.length > 1 
        ? files.length + '个文件' 
      : ((files[0] || {}).name || (elemFile[0].value.match(/[^\/\\]+\..+/g)||[]) || '');
      
      if(elemFile.next().hasClass(ELEM_CHOOSE)){
        elemFile.next().remove();
      }
      that.upload(null, 'choose');
      if(that.isFile() || options.choose) return;
      elemFile.after('<span class="layui-inline '+ ELEM_CHOOSE +'">'+ value +'</span>');
    };

    // 点击上传容器
    options.elem.off('upload.start').on('upload.start', function(){
      var othis = $(this);

      that.config.item = othis;
      that.elemFile[0].click();
    });
    
    // 拖拽上传
    if(!(device.ie && device.ie < 10)){
      options.elem.off('upload.over').on('upload.over', function(){
        var othis = $(this)
        othis.attr('lay-over', '');
      })
      .off('upload.leave').on('upload.leave', function(){
        var othis = $(this)
        othis.removeAttr('lay-over');
      })
      .off('upload.drop').on('upload.drop', function(e, param){
        var othis = $(this);
        var files = param.originalEvent.dataTransfer.files || [];
        
        othis.removeAttr('lay-over');
        setChooseFile(files);

        options.auto ? that.upload() : setChooseText(files); // 是否自动触发上传
      });
    }
    
    // 文件选择
    that.elemFile.on('change', function(){
      var files = this.files || [];

      if(files.length === 0) return;

      setChooseFile(files);

      options.auto ? that.upload() : setChooseText(files); // 是否自动触发上传
    });
    
    // 手动触发上传
    options.bindAction.off('upload.action').on('upload.action', function(){
      that.upload();
    });


    // 防止事件重复绑定
    if(options.elem.data(MOD_INDEX)) return;


    // 目标元素 click 事件
    options.elem.on('click', function(){
      if(that.isFile()) return;
      $(this).trigger('upload.start');
    });
    
    // 目标元素 drop 事件
    if(options.drag){
      options.elem.on('dragover', function(e){
        e.preventDefault();
        $(this).trigger('upload.over');
      }).on('dragleave', function(e){
        $(this).trigger('upload.leave');
      }).on('drop', function(e){
        e.preventDefault();
        $(this).trigger('upload.drop', e);
      });
    }
    
    // 手动上传时触发上传的元素 click 事件
    options.bindAction.on('click', function(){
      $(this).trigger('upload.action');
    });
    
    // 绑定元素索引
    options.elem.data(MOD_INDEX, options.id);
  };

  // 记录所有实例
  thisModule.that = {}; // 记录所有实例对象

  // 获取当前实例对象
  thisModule.getThis = function(id){
    var that = thisModule.that[id];
    if(!that) hint.error(id ? (MOD_NAME +' instance with ID \''+ id +'\' not found') : 'ID argument required');
    return that;
  };
  
  // 核心入口  
  upload.render = function(options){
    var inst = new Class(options);
    return thisModule.call(inst);
  };
  
  exports(MOD_NAME, upload);
});

/**
 * form 表单组件
 */
 
layui.define(['lay', 'layer', 'util'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var layer = layui.layer;
  var util = layui.util;
  var hint = layui.hint();
  var device = layui.device();
  
  var MOD_NAME = 'form';
  var ELEM = '.layui-form';
  var THIS = 'layui-this';
  var SHOW = 'layui-show';
  var HIDE = 'layui-hide';
  var DISABLED = 'layui-disabled';
  var OUT_OF_RANGE = 'layui-input-number-out-of-range';
  
  var Form = function(){
    this.config = {
      // 内置的验证规则
      verify: {
        required: function(value) {
          if (!/[\S]+/.test(value)) {
            return '必填项不能为空';
          }
        },
        phone: function(value) {
          var EXP = /^1\d{10}$/;
          if (value && !EXP.test(value)) {
            return '手机号格式不正确';
          }
        },
        email: function(value) {
          var EXP = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
          if (value && !EXP.test(value)) {
            return '邮箱格式不正确';
          }
        },
        url: function(value) {
          var EXP = /^(#|(http(s?)):\/\/|\/\/)[^\s]+\.[^\s]+$/;
          if (value && !EXP.test(value)) {
            return '链接格式不正确';
          }
        },
        number: function(value){
          if (value && isNaN(value)) {
            return '只能填写数字';
          }
        },
        date: function(value){
          var EXP = /^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/;
          if (value && !EXP.test(value)) {
            return '日期格式不正确';
          }
        },
        identity: function(value) {
          var EXP = /(^\d{15}$)|(^\d{17}(x|X|\d)$)/;
          if (value && !EXP.test(value)) {
            return '身份证号格式不正确';
          }
        }
      },
      autocomplete: null // 全局 autocomplete 状态。 null 表示不干预
    };
  };
  
  // 全局设置
  Form.prototype.set = function(options){
    var that = this;
    $.extend(true, that.config, options);
    return that;
  };
  
  // 验证规则设定
  Form.prototype.verify = function(settings){
    var that = this;
    $.extend(true, that.config.verify, settings);
    return that;
  };

  // 获取指定表单对象
  Form.prototype.getFormElem = function(filter){
    return $(ELEM + function(){
      return filter ? ('[lay-filter="' + filter +'"]') : '';
    }());
  };
  
  // 表单事件
  Form.prototype.on = function(events, callback){
    return layui.onevent.call(this, MOD_NAME, events, callback);
  };
  
  // 赋值/取值
  Form.prototype.val = function(filter, object){
    var that = this
    ,formElem = that.getFormElem(filter);
    
    // 遍历
    formElem.each(function(index, item){
      var itemForm = $(this);
      
      // 赋值
      layui.each(object, function(key, value){
        var itemElem = itemForm.find('[name="'+ key +'"]')
        ,type;
        
        // 如果对应的表单不存在，则不执行
        if(!itemElem[0]) return;
        type = itemElem[0].type;
        
        // 如果为复选框
        if(type === 'checkbox'){
          itemElem[0].checked = value;
        } else if(type === 'radio') { // 如果为单选框
          itemElem.each(function(){
            this.checked = this.value == value;
          });
        } else { // 其它类型的表单
          itemElem.val(value);
        }
      });
    });
    
    form.render(null, filter);
    
    // 返回值
    return that.getValue(filter);
  };
  
  // 取值
  Form.prototype.getValue = function(filter, itemForm){
    itemForm = itemForm || this.getFormElem(filter);
        
    var nameIndex = {} // 数组 name 索引
    ,field = {}
    ,fieldElem = itemForm.find('input,select,textarea') // 获取所有表单域
    
    layui.each(fieldElem, function(_, item){ 
      var othis = $(this)
      ,init_name; // 初始 name
      
      item.name = (item.name || '').replace(/^\s*|\s*&/, '');
      if(!item.name) return;
      
      // 用于支持数组 name
      if(/^.*\[\]$/.test(item.name)){
        var key = item.name.match(/^(.*)\[\]$/g)[0];
        nameIndex[key] = nameIndex[key] | 0;
        init_name = item.name.replace(/^(.*)\[\]$/, '$1['+ (nameIndex[key]++) +']');
      }
      
      if(/^(checkbox|radio)$/.test(item.type) && !item.checked) return;  // 复选框和单选框未选中，不记录字段     
      field[init_name || item.name] = item.value;
    });
    
    return field;
  };
  
  // 表单控件渲染
  Form.prototype.render = function(type, filter){
    var that = this;
    var options = that.config;
    var elemForm = $(ELEM + function(){
      return filter ? ('[lay-filter="' + filter +'"]') : '';
    }());
    var items = {
      // 输入框
      input: function(elem){
        var inputs = elem || elemForm.find('input,textarea');

        // 初始化全局的 autocomplete
        options.autocomplete && inputs.attr('autocomplete', options.autocomplete);

        var handleInputNumber = function(elem, eventType){
          var that = this;
          var rawValue = elem.val();
          var value = Number(rawValue);
          var step = Number(elem.attr('step')) || 1; // 加减的数字间隔
          var min = Number(elem.attr('min'));
          var max = Number(elem.attr('max'));
          var precision = Number(elem.attr('lay-precision'));
          var noAction = eventType !== 'click' && rawValue === ''; // 初始渲染和失焦时空值不作处理
          var isInit = eventType === 'init';

          if(isNaN(value)) return; // 若非数字，则不作处理

          if(eventType === 'click'){
            var isDecrement = !!$(that).index() // 0: icon-up, 1: icon-down
            value = isDecrement ? value - step : value + step;
          }

          // 获取小数点后位数
          var decimals = function(step){
            var decimals = (step.toString().match(/\.(\d+$)/) || [])[1] || '';
            return decimals.length;
          };

          precision = precision >= 0 ? precision : Math.max(decimals(step), decimals(rawValue));

          if(!noAction){
            // 初始渲染时只处理数字精度
            if(!isInit){
              if(value <= min) value = min;
              if(value >= max) value = max;
            }
            if(precision) value = value.toFixed(precision);
            elem.val(value);
          }

          // 超出范围的样式
          var outOfRange = value < min || value > max;
          elem[outOfRange && !noAction ? 'addClass' : 'removeClass'](OUT_OF_RANGE);

          if(isInit) return;

          // 更新按钮状态
          var controlBtn = {
            increment: elem.next().find('.layui-icon-up'),
            decrement: elem.next().find('.layui-icon-down')
          }
          controlBtn.increment[(value >= max && !noAction) ? 'addClass' : 'removeClass'](DISABLED)
          controlBtn.decrement[(value <= min && !noAction) ? 'addClass' : 'removeClass'](DISABLED)
        }

        // 初始化输入框动态点缀
        elemForm.find('input[lay-affix],textarea[lay-affix]').each(function(){
          var othis = $(this);
          var affix = othis.attr('lay-affix');
          var CLASS_WRAP = 'layui-input-wrap';
          var CLASS_SUFFIX = 'layui-input-suffix';
          var CLASS_AFFIX = 'layui-input-affix';
          var disabled = othis.is('[disabled]') || othis.is('[readonly]');

          // 根据是否空值来显示或隐藏元素
          var showAffix = function(elem, value){
            elem = $(elem);
            if(!elem[0]) return;
            elem[$.trim(value) ? 'removeClass' : 'addClass'](HIDE);
          };

          // 渲染动态点缀内容
          var renderAffix = function(opts){
            opts = $.extend({}, (affixOptions[affix] || {
              value: affix
            }), opts, lay.options(othis[0]));
            var elemAffix = $('<div class="'+ CLASS_AFFIX +'">');
            var value = layui.isArray(opts.value) ? opts.value : [opts.value];
            var elemIcon = $(function(){
              var arr = [];
              layui.each(value, function(i, item){
                arr.push('<i class="layui-icon layui-icon-'+ item + (
                  opts.disabled ? (' '+ DISABLED) : ''
                ) +'"></i>');
              });
              return arr.join('');
            }());
            
            elemAffix.append(elemIcon); // 插入图标元素

            // 追加 className
            if(opts.split) elemAffix.addClass('layui-input-split');
            if(opts.className) elemAffix.addClass(opts.className);

            // 移除旧的元素
            var hasElemAffix = othis.next('.'+ CLASS_AFFIX);
            if(hasElemAffix[0]) hasElemAffix.remove();

            // 是否在规定的容器中
            if(!othis.parent().hasClass(CLASS_WRAP)){
              othis.wrap('<div class="'+ CLASS_WRAP +'"></div>');
            }

            // 是否已经存在后缀元素
            var hasElemSuffix = othis.next('.'+ CLASS_SUFFIX);
            if(hasElemSuffix[0]){
              hasElemAffix = hasElemSuffix.find('.'+ CLASS_AFFIX);
              if(hasElemAffix[0]) hasElemAffix.remove();

              hasElemSuffix.prepend(elemAffix);

              othis.css('padding-right', function(){
                var paddingRight = othis.closest('.layui-input-group')[0] 
                  ? 0 
                : hasElemSuffix.outerWidth();
                return paddingRight + elemAffix.outerWidth()
              });
            } else {
              elemAffix.addClass(CLASS_SUFFIX);
              othis.after(elemAffix);
            }

            opts.show === 'auto' && showAffix(elemAffix, othis.val());
            
            typeof opts.init === 'function' && opts.init.call(this, othis, opts);
            
            // 输入事件
            othis.on('input propertychange', function(){
              var value = this.value;
              opts.show === 'auto' && showAffix(elemAffix, value);
            });

            // 失去焦点事件
            othis.on('blur', function(){
              typeof opts.blur === 'function' && opts.blur.call(this, othis, opts);
            });
            
            // 点击动态后缀事件
            elemIcon.on('click', function(){
              var inputFilter = othis.attr('lay-filter');
              if($(this).hasClass(DISABLED)) return;
              
              typeof opts.click === 'function' && opts.click.call(this, othis, opts);
              
              // 对外事件
              layui.event.call(this, MOD_NAME, 'input-affix('+ inputFilter +')', {
                elem: othis[0],
                affix: affix,
                options: opts
              });
            });
          };
          
          // 动态点缀配置项
          var affixOptions = {
            eye: { // 密码显隐
              value: 'eye-invisible',
              click: function(elem, opts){ // 事件
                var SHOW_NAME = 'LAY_FORM_INPUT_AFFIX_SHOW';
                var isShow = elem.data(SHOW_NAME);
                
                elem.attr('type', isShow ? 'password' : 'text').data(SHOW_NAME, !isShow);

                renderAffix({
                  value: isShow ? 'eye-invisible' : 'eye'
                });
              }
            },
            clear: { // 内容清除
              value: 'clear',
              click: function(elem){
                elem.val('').focus();
                showAffix($(this).parent(), null);
              },
              show: 'auto', // 根据输入框值是否存在来显示或隐藏点缀图标
              disabled: disabled // 跟随输入框禁用状态
            },
            number: { // 数字输入框
              value: ['up', 'down'],
              split: true,
              className: 'layui-input-number',
              disabled: othis.is('[disabled]'), // 跟随输入框禁用状态
              init: function(elem){
                handleInputNumber.call(this, elem, 'init')
              },
              click: function(elem){
                handleInputNumber.call(this, elem, 'click')
              },
              blur: function(elem){
                handleInputNumber.call(this, elem, 'blur')
              },
            }
          };
          
          renderAffix();
        });
      }
      
      // 下拉选择框
      ,select: function(elem){
        var TIPS = '请选择';
        var CLASS = 'layui-form-select';
        var TITLE = 'layui-select-title';
        var NONE = 'layui-select-none';
        var CREATE_OPTION = 'layui-select-create-option';
        var initValue = '';
        var thatInput;
        var selects = elem || elemForm.find('select');

        // 隐藏 select
        var hide = function(e, clear){
          if(!$(e.target).parent().hasClass(TITLE) || clear){
            var elem = $('.' + CLASS);
            elem.removeClass(CLASS+'ed ' + CLASS+'up');
            if(elem.hasClass('layui-select-creatable')){
              elem.children('dl').children('.' + CREATE_OPTION).remove();
            }
            thatInput && initValue && thatInput.val(initValue);
          }
          thatInput = null;
        };
        
        // 各种事件
        var events = function(reElem, disabled, isSearch, isCreatable){
          var select = $(this);
          var title = reElem.find('.' + TITLE);
          var input = title.find('input');
          var dl = reElem.find('dl');
          var dds = dl.children('dd');
          var dts = dl.children('dt'); // select 分组dt元素
          var index =  this.selectedIndex; // 当前选中的索引
          var nearElem; // select 组件当前选中的附近元素，用于辅助快捷键功能
          
          if(disabled) return;

          // 搜索项
          var laySearch = select.attr('lay-search');
          
          // 展开下拉
          var showDown = function(){
            var top = reElem.offset().top + reElem.outerHeight() + 5 - $win.scrollTop();
            var dlHeight = dl.outerHeight();
            var dds = dl.children('dd');
            
            index = select[0].selectedIndex; // 获取最新的 selectedIndex
            reElem.addClass(CLASS+'ed');
            dds.removeClass(HIDE);
            dts.removeClass(HIDE);
            nearElem = null;

            // 初始选中样式
            dds.removeClass(THIS);
            index >= 0 && dds.eq(index).addClass(THIS);

            // 上下定位识别
            if(top + dlHeight > $win.height() && top >= dlHeight){
              reElem.addClass(CLASS + 'up');
            }

            followScroll();
          };
          
          // 隐藏下拉
          var hideDown = function(choose){
            reElem.removeClass(CLASS+'ed ' + CLASS+'up');
            input.blur();
            nearElem = null;
            isCreatable && dl.children('.' + CREATE_OPTION).remove();
            
            if(choose) return;
            
            notOption(input.val(), function(none){
              var selectedIndex = select[0].selectedIndex;
              
              // 未查询到相关值
              if(none){
                initValue = $(select[0].options[selectedIndex]).html(); // 重新获得初始选中值
                
                // 如果是第一项，且文本值等于 placeholder，则清空初始值
                if(selectedIndex === 0 && initValue === input.attr('placeholder')){
                  initValue = '';
                }

                // 如果有选中值，则将输入框纠正为该值。否则清空输入框
                input.val(initValue || '');
              }
            });
          };
          
          // 定位下拉滚动条
          var followScroll = function(){  
            var thisDd = dl.children('dd.'+ THIS);
            
            if(!thisDd[0]) return;
            
            var posTop = thisDd.position().top;
            var dlHeight = dl.height();
            var ddHeight = thisDd.height();
            
            // 若选中元素在滚动条不可见底部
            if(posTop > dlHeight){
              dl.scrollTop(posTop + dl.scrollTop() - dlHeight + ddHeight - 5);
            }
            
            // 若选择元素在滚动条不可见顶部
            if(posTop < 0){
              dl.scrollTop(posTop + dl.scrollTop() - 5);
            }
          };
          
          // 点击标题区域
          title.on('click', function(e){
            reElem.hasClass(CLASS+'ed') ? (
              hideDown()
            ) : (
              hide(e, true), 
              showDown()
            );
            dl.find('.'+NONE).remove();
          }); 
          
          // 点击箭头获取焦点
          title.find('.layui-edge').on('click', function(){
            input.focus();
          });
          
          // select 中 input 键盘事件
          input.on('keyup', function(e){ // 键盘松开
            var keyCode = e.keyCode;
            
            // Tab键展开
            if(keyCode === 9){
              showDown();
            }
          }).on('keydown', function(e){ // 键盘按下
            var keyCode = e.keyCode;

            // Tab键隐藏
            if(keyCode === 9){
              hideDown();
            }
            
            // 标注 dd 的选中状态
            var setThisDd = function(prevNext, thisElem1){
              var nearDd, cacheNearElem
              e.preventDefault();

              // 得到当前队列元素  
              var thisElem = function(){
                var thisDd = dl.children('dd.'+ THIS);
                
                // 如果是搜索状态，且按 Down 键，且当前可视 dd 元素在选中元素之前，
                // 则将当前可视 dd 元素的上一个元素作为虚拟的当前选中元素，以保证递归不中断
                if(dl.children('dd.'+  HIDE)[0] && prevNext === 'next'){
                  var showDd = dl.children('dd:not(.'+ HIDE +',.'+ DISABLED +')')
                  ,firstIndex = showDd.eq(0).index();
                  if(firstIndex >=0 && firstIndex < thisDd.index() && !showDd.hasClass(THIS)){
                    return showDd.eq(0).prev()[0] ? showDd.eq(0).prev() : dl.children(':last');
                  }
                }

                if(thisElem1 && thisElem1[0]){
                  return thisElem1;
                }
                if(nearElem && nearElem[0]){
                  return nearElem;
                }
       
                return thisDd;
                // return dds.eq(index);
              }();
              
              cacheNearElem = thisElem[prevNext](); // 当前元素的附近元素
              nearDd =  thisElem[prevNext]('dd:not(.'+ HIDE +')'); // 当前可视元素的 dd 元素

              // 如果附近的元素不存在，则停止执行，并清空 nearElem
              if(!cacheNearElem[0]) return nearElem = null;
              
              // 记录附近的元素，让其成为下一个当前元素
              nearElem = thisElem[prevNext]();

              // 如果附近不是 dd ，或者附近的 dd 元素是禁用状态，则进入递归查找
              if((!nearDd[0] || nearDd.hasClass(DISABLED)) && nearElem[0]){
                return setThisDd(prevNext, nearElem);
              }
              
              nearDd.addClass(THIS).siblings().removeClass(THIS); // 标注样式
              followScroll(); // 定位滚动条
            };
            
            if(keyCode === 38) setThisDd('prev'); // Up 键
            if(keyCode === 40) setThisDd('next'); // Down 键
            
            // Enter 键
            if(keyCode === 13){ 
              e.preventDefault();
              dl.children('dd.'+THIS).trigger('click');
            }
          }).on('paste', function(){
            showDown();
          });
          
          // 检测值是否不属于 select 项
          var notOption = function(value, callback, origin){
            var num = 0;
            var dds = dl.children('dd');
            var hasEquals = false;
            var rawValue = value;
            layui.each(dds, function(){
              var othis = $(this);
              var text = othis.text();

              // 需要区分大小写
              if(isCreatable && text === rawValue){
                hasEquals = true;
              }

              // 是否区分大小写
              if(laySearch !== 'cs'){
                text = text.toLowerCase();
                value = value.toLowerCase();
              }
              
              // 匹配
              var not = text.indexOf(value) === -1;
              
              if(value === '' || (origin === 'blur') ? value !== text : not) num++;
              origin === 'keyup' && othis[(not && (isCreatable ? !othis.hasClass(CREATE_OPTION) : true)) ? 'addClass' : 'removeClass'](HIDE);
            });
            // 处理 select 分组元素
            origin === 'keyup' && layui.each(dts, function(){
              var othis = $(this);
              var thisDds = othis.nextUntil('dt').filter('dd'); // 当前分组下的dd元素
              if(isCreatable) thisDds = thisDds.not('.' + CREATE_OPTION);
              var allHide = thisDds.length == thisDds.filter('.' + HIDE).length; // 当前分组下所有dd元素都隐藏了
              othis[allHide ? 'addClass' : 'removeClass'](HIDE);
            });
            var none = num === dds.length;
            return callback(none, hasEquals), none;
          };
          
          // 搜索匹配
          var search = function(e){
            var value = this.value, keyCode = e.keyCode;
            
            if(keyCode === 9 || keyCode === 13 
              || keyCode === 37 || keyCode === 38 
              || keyCode === 39 || keyCode === 40
            ){
              return false;
            }
            
            notOption(value, function(none, hasEquals){
              if(isCreatable){
                if(hasEquals){
                  dl.children('.' + CREATE_OPTION).remove();
                }else{
                  // 和初始渲染保持行为一致
                  var textVal = $('<div>' + value +'</div>').text();
                  var createOptionElem = dl.children('.' + CREATE_OPTION);
                  if(createOptionElem[0]){
                    createOptionElem.attr('lay-value', value);
                    createOptionElem.text(textVal);
                  }else{
                    dl.append('<dd class="' + CREATE_OPTION + '" lay-value="'+ value +'">' + textVal + '</dd>');
                  }
                }
              }else{
                if(none){
                  dl.find('.'+NONE)[0] || dl.append('<p class="'+ NONE +'">无匹配项</p>');
                } else {
                  dl.find('.'+NONE).remove();
                }
              }
            }, 'keyup');
            
            // 当搜索值清空时
            if(value === ''){
              // 取消选中项
              select.val('');
              dl.find('.'+ THIS).removeClass(THIS);
              (select[0].options[0] || {}).value || dl.children('dd:eq(0)').addClass(THIS);
              dl.find('.'+ NONE).remove();
              isCreatable && dl.children('.' + CREATE_OPTION).remove();
            }
            
            followScroll(); // 定位滚动条
          };
          
          if(isSearch){
            // #1449: IE10 和 11 中，带有占位符的 input 元素获得/失去焦点时，会触发 input 事件
            var eventsType = 'input propertychange';
            if(lay.ie && (lay.ie === '10' || lay.ie === '11') && input.attr('placeholder')){
              eventsType = 'keyup';
            }
            input.on(eventsType, search).on('blur', function(e){
              var selectedIndex = select[0].selectedIndex;
              
              thatInput = input; // 当前的 select 中的 input 元素
              initValue = $(select[0].options[selectedIndex]).text(); // 重新获得初始选中值
              
              // 如果是第一项，且文本值等于 placeholder，则清空初始值
              if(selectedIndex === 0 && initValue === input.attr('placeholder')){
                initValue = '';
              }
              
              setTimeout(function(){
                notOption(input.val(), function(none){
                  initValue || input.val(''); // none && !initValue
                }, 'blur');
              }, 200);
            });
          }

          // 选择
          dl.on('click', 'dd', function(){
            var othis = $(this), value = othis.attr('lay-value');
            var filter = select.attr('lay-filter'); // 获取过滤器
            
            if(othis.hasClass(DISABLED)) return false;
            
            if(othis.hasClass('layui-select-tips')){
              input.val('');
            } else {
              input.val(othis.text());
              othis.addClass(THIS);
            }

            if(isCreatable && othis.hasClass(CREATE_OPTION)){
              othis.removeClass(CREATE_OPTION);
              select.append('<option value="' + value + '">' + value + '</option>');
            }

            othis.siblings().removeClass(THIS);
            select.val(value).removeClass('layui-form-danger');

            layui.event.call(this, MOD_NAME, 'select('+ filter +')', {
              elem: select[0]
              ,value: value
              ,othis: reElem
            });

            hideDown(true);
            return false;
          });
          
          reElem.find('dl>dt').on('click', function(e){
            return false;
          });
          
          $(document).off('click', hide).on('click', hide); // 点击其它元素关闭 select
        }
        
        // 初始渲染 select 组件选项
        selects.each(function(index, select){
          var othis = $(this)
          ,hasRender = othis.next('.'+CLASS)
          ,disabled = this.disabled
          ,value = select.value
          ,selected = $(select.options[select.selectedIndex]) // 获取当前选中项
          ,optionsFirst = select.options[0];
          
          if(typeof othis.attr('lay-ignore') === 'string') return othis.show();
          
          var isSearch = typeof othis.attr('lay-search') === 'string'
          ,isCreatable = typeof othis.attr('lay-creatable') === 'string' && isSearch
          ,placeholder = optionsFirst ? (
            optionsFirst.value ? TIPS : (optionsFirst.innerHTML || TIPS)
          ) : TIPS;

          // 替代元素
          var reElem = $(['<div class="'+ (isSearch ? '' : 'layui-unselect ') + CLASS 
          ,(disabled ? ' layui-select-disabled' : '')
          ,(isCreatable ? ' layui-select-creatable' : '') + '">'
            ,'<div class="'+ TITLE +'">'
              ,('<input type="text" placeholder="'+ util.escape($.trim(placeholder)) +'" '
                +('value="'+ util.escape($.trim(value ? selected.html() : '')) +'"') // 默认值
                +((!disabled && isSearch) ? '' : ' readonly') // 是否开启搜索
                +' class="layui-input'
                +(isSearch ? '' : ' layui-unselect') 
              + (disabled ? (' ' + DISABLED) : '') +'">') // 禁用状态
            ,'<i class="layui-edge"></i></div>'
            ,'<dl class="layui-anim layui-anim-upbit'+ (othis.find('optgroup')[0] ? ' layui-select-group' : '') +'">'
            ,function(options){
              var arr = [];
              layui.each(options, function(index, item){
                var tagName = item.tagName.toLowerCase();

                if(index === 0 && !item.value && tagName !== 'optgroup'){
                  arr.push('<dd lay-value="" class="layui-select-tips">'+ $.trim(item.innerHTML || TIPS) +'</dd>');
                } else if(tagName === 'optgroup'){
                  arr.push('<dt>'+ item.label +'</dt>'); 
                } else {
                  arr.push('<dd lay-value="'+ util.escape(item.value) +'" class="'+ (value === item.value ?  THIS : '') + (item.disabled ? (' '+DISABLED) : '') +'">'+ $.trim(item.innerHTML) +'</dd>');
                }
              });
              arr.length === 0 && arr.push('<dd lay-value="" class="'+ DISABLED +'">没有选项</dd>');
              return arr.join('');
            }(othis.find('*')) +'</dl>'
          ,'</div>'].join(''));
          
          hasRender[0] && hasRender.remove(); // 如果已经渲染，则Rerender
          othis.after(reElem);          
          events.call(this, reElem, disabled, isSearch, isCreatable);
        });
      }
      
      // 复选框/开关
      ,checkbox: function(elem){
        var CLASS = {
          "checkbox": ['layui-form-checkbox', 'layui-form-checked', 'checkbox'],
          "switch": ['layui-form-switch', 'layui-form-onswitch', 'switch'],
          SUBTRA: 'layui-icon-indeterminate'
        };
        var checks = elem || elemForm.find('input[type=checkbox]');
        // 风格
        var skins = {
          "primary": true, // 默认风格
          "tag": true, // 标签风格
          "switch": true // 开关风格
        };
        // 事件
        var events = function(reElem, RE_CLASS){
          var check = $(this);
          
          // 勾选
          reElem.on('click', function(){
            var othis = $(this);
            var filter = check.attr('lay-filter') // 获取过滤器
            var title = (
              othis.next('*[lay-checkbox]')[0] 
                ? othis.next().html()
              : check.attr('title') || ''
            );
            var skin = check.attr('lay-skin') || 'primary';

            // 开关
            title = skin === 'switch' ? title.split('|') : [title];

            // 禁用
            if(check[0].disabled) return;
            
            // 半选
            if (check[0].indeterminate) {
              check[0].indeterminate = false;
              reElem.find('.'+ CLASS.SUBTRA).removeClass(CLASS.SUBTRA).addClass('layui-icon-ok');
            }

            // 开关
            check[0].checked ? (
              check[0].checked = false,
              reElem.removeClass(RE_CLASS[1]),
              skin === 'switch' && reElem.children('div').html(title[1])
            ) : (
              check[0].checked = true,
              reElem.addClass(RE_CLASS[1]),
              skin === 'switch' && reElem.children('div').html(title[0])
            );
            
            // 事件
            layui.event.call(check[0], MOD_NAME, RE_CLASS[2]+'('+ filter +')', {
              elem: check[0],
              value: check[0].value,
              othis: reElem
            });
          });
        };
        
        // 遍历复选框
        checks.each(function(index, check){
          var othis = $(this);
          var skin = othis.attr('lay-skin') || 'primary';
          var title = util.escape($.trim(check.title || function(){ // 向下兼容 lay-text 属性
            return check.title = othis.attr('lay-text') || '';
          }()));
          var disabled = this.disabled;

          // if(!skins[skin]) skin = 'primary'; // 若非内置风格，则强制为默认风格
          var RE_CLASS = CLASS[skin] || CLASS.checkbox;

          // 替代元素
          var hasRender = othis.next('.' + RE_CLASS[0]);
          hasRender[0] && hasRender.remove(); // 若已经渲染，则 Rerender
         
          // 若存在标题模板，则优先读取标题模板
          if(othis.next('[lay-checkbox]')[0]){
            title = othis.next().html() || '';
          }

          // 若为开关，则对 title 进行分隔解析
          title = skin === 'switch' ? title.split('|') : [title];
          
          if(typeof othis.attr('lay-ignore') === 'string') return othis.show();
          
          // 替代元素
          var reElem = $(['<div class="layui-unselect '+ RE_CLASS[0],
            (check.checked ? (' '+ RE_CLASS[1]) : ''), // 选中状态
            (disabled ? ' layui-checkbox-disabled '+ DISABLED : ''), // 禁用状态
            '"',
            (skin ? ' lay-skin="'+ skin +'"' : ''), // 风格
          '>',
          function(){ // 不同风格的内容
            var type = {
              // 复选框
              "checkbox": [
                (title[0] ? ('<div>'+ title[0] +'</div>') : (skin === 'primary' ? '' : '<div></div>')),
                '<i class="layui-icon '+(skin === 'primary' && !check.checked && othis.get(0).indeterminate ? CLASS.SUBTRA : 'layui-icon-ok')+'"></i>'
              ].join(''),
              // 开关
              "switch": '<div>'+ ((check.checked ? title[0] : title[1]) || '') +'</div><i></i>'
            };
            return type[skin] || type['checkbox'];
          }(),
          '</div>'].join(''));

          othis.after(reElem);
          events.call(this, reElem, RE_CLASS);
        });
      }
      
      // 单选框
      ,radio: function(elem){
        var CLASS = 'layui-form-radio';
        var ICON = ['layui-icon-radio', 'layui-icon-circle'];
        var radios = elem || elemForm.find('input[type=radio]');

        // 事件
        var events = function(reElem){
          var radio = $(this);
          var ANIM = 'layui-anim-scaleSpring';
          
          reElem.on('click', function(){
            var name = radio[0].name, forms = radio.parents(ELEM);
            var filter = radio.attr('lay-filter'); // 获取过滤器
            var sameRadio = forms.find('input[name='+ name.replace(/(\.|#|\[|\])/g, '\\$1') +']'); // 找到相同name的兄弟
            
            if(radio[0].disabled) return;
            
            layui.each(sameRadio, function(){
              var next = $(this).next('.' + CLASS);
              this.checked = false;
              next.removeClass(CLASS + 'ed');
              next.children('.layui-icon').removeClass(ANIM + ' ' + ICON[0]).addClass(ICON[1]);
            });
            
            radio[0].checked = true;
            reElem.addClass(CLASS + 'ed');
            reElem.children('.layui-icon').addClass(ANIM + ' ' + ICON[0]);
            
            layui.event.call(radio[0], MOD_NAME, 'radio('+ filter +')', {
              elem: radio[0],
              value: radio[0].value,
              othis: reElem
            });
          });
        };
        
        // 初始渲染
        radios.each(function(index, radio){
          var othis = $(this), hasRender = othis.next('.' + CLASS);
          var disabled = this.disabled;
          
          if(typeof othis.attr('lay-ignore') === 'string') return othis.show();
          hasRender[0] && hasRender.remove(); // 如果已经渲染，则Rerender
          
          // 替代元素
          var reElem = $(['<div class="layui-unselect '+ CLASS, 
            (radio.checked ? (' '+ CLASS +'ed') : ''), // 选中状态
          (disabled ? ' layui-radio-disabled '+DISABLED : '') +'">', // 禁用状态
          '<i class="layui-anim layui-icon '+ ICON[radio.checked ? 0 : 1] +'"></i>',
          '<div>'+ function(){
            var title = util.escape(radio.title || '');
            if(othis.next('[lay-radio]')[0]){
              title = othis.next().html();
            }
            return title;
          }() +'</div>',
          '</div>'].join(''));

          othis.after(reElem);
          events.call(this, reElem);
        });
      }
    };

    // 执行所有渲染项
    var renderItem = function(){
      layui.each(items, function(index, item){
        item();
      });
    };

    // jquery 对象
    if (layui.type(type) === 'object') {
      // 若对象为表单域容器
      if($(type).is(ELEM)){
        elemForm = $(type);
        renderItem();
      } else { // 对象为表单项
        type.each(function (index, item) {
          var elem = $(item);
          if (!elem.closest(ELEM).length) {
            return; // 若不在 layui-form 容器中直接跳过
          }
          if (item.tagName === 'SELECT') {
            items['select'](elem);
          } else if (item.tagName === 'INPUT') {
            var itemType = item.type;
            if (itemType === 'checkbox' || itemType === 'radio') {
              items[itemType](elem);
            } else {
              items['input'](elem);
            }
          }
        });
      }
    } else {
      type ? (
        items[type] ? items[type]() : hint.error('不支持的 "'+ type + '" 表单渲染')
      ) : renderItem();
    }
    return that;
  };

  /**
   * 主动触发验证
   * @param  {(string|HTMLElement|JQuery)} elem - 要验证的区域表单元素
   * @return {boolean} 返回结果。若验证通过，返回 `true`, 否则返回 `false`
   */
  Form.prototype.validate = function(elem) {
    var that = this;
    var intercept; // 拦截标识
    var options = that.config; // 获取全局配置项
    var verify = options.verify; // 验证规则
    var DANGER = 'layui-form-danger'; // 警示样式

    elem = $(elem);

    // 节点不存在可视为 true
    if (!elem[0]) return !0;

    // 若节点不存在特定属性，则查找容器内有待验证的子节点
    if (elem.attr('lay-verify') === undefined) {
      // 若校验的是一个不带验证规则的容器，校验内部的 lay-verify 节点
      if (that.validate(elem.find('*[lay-verify]')) === false) {
        return false;
      }
    }

    // 开始校验
    layui.each(elem, function(_, item) {
      var othis = $(this);
      var verifyStr = othis.attr('lay-verify') || '';
      var vers = verifyStr.split('|');
      var verType = othis.attr('lay-vertype'); // 提示方式
      var value = $.trim(othis.val());

      othis.removeClass(DANGER); // 移除警示样式
      
      // 遍历元素绑定的验证规则
      layui.each(vers, function(_, thisVer) {
        var verst; // 校验结果
        var errorText = ''; // 错误提示文本
        var rule = verify[thisVer]; // 获取校验规则
        
        // 匹配验证规则
        if (rule) {
          verst = typeof rule === 'function'
            ? errorText = rule(value, item) 
          : !rule[0].test(value); // 兼容早期数组中的正则写法
          
          // 是否属于美化替换后的表单元素
          var isForm2Elem = item.tagName.toLowerCase() === 'select' || (
            /^(checkbox|radio)$/.test(item.type)
          );
          
          errorText = errorText || rule[1];
          
          // 获取自定义必填项提示文本
          if (thisVer === 'required') {
            errorText = othis.attr('lay-reqtext') || errorText;
          }
          
          // 若命中校验规则
          if (verst) {
            // 提示层风格
            if (verType === 'tips') {
              layer.tips(errorText, function(){
                if(typeof othis.attr('lay-ignore') !== 'string'){
                  if(isForm2Elem){
                    return othis.next();
                  }
                }
                return othis;
              }(), {tips: 1});
            } else if(verType === 'alert') {
              layer.alert(errorText, {title: '提示', shadeClose: true});
            } 
            // 若返回的为字符或数字，则自动弹出默认提示框；否则由 verify 方法中处理提示
            else if(/\b(string|number)\b/.test(typeof errorText)) {
              layer.msg(errorText, {icon: 5, shift: 6});
            }

            setTimeout(function() {
              (isForm2Elem ? othis.next().find('input') : item).focus();
            }, 7);
            
            othis.addClass(DANGER);
            return intercept = true;
          }
        }
      });

      if (intercept) return intercept;
    });

    return !intercept;
  };

  // 提交表单并校验
  var submit = Form.prototype.submit = function(filter, callback){
    var field = {};  // 字段集合
    var button = $(this); // 当前触发的按钮

    // 表单域 lay-filter 属性值
    var layFilter = typeof filter === 'string' 
      ? filter 
    : button.attr('lay-filter');

    // 当前所在表单域
    var elem = this.getFormElem 
      ? this.getFormElem(layFilter) 
    : button.parents(ELEM).eq(0);

    // 获取需要校验的元素
    var verifyElem = elem.find('*[lay-verify]');

    // 开始校验
    if(!form.validate(verifyElem)) return false;

    // 获取当前表单值
    field = form.getValue(null, elem);

    // 返回的参数
    var params = {
      elem: this.getFormElem ? (window.event && window.event.target) : this // 触发事件的对象
      ,form: this.getFormElem ? elem[0] : button.parents('form')[0] // 当前所在的 form 元素，如果存在的话
      ,field: field // 当前表单数据
    };
    
    // 回调
    typeof callback === 'function' && callback(params);
 
    // 事件
    return layui.event.call(this, MOD_NAME, 'submit('+ layFilter +')', params);
  };
  
  var form = new Form();
  var $dom = $(document);
  var $win = $(window);
  
  // 初始自动完成渲染
  $(function(){
    form.render();
  });
  
  // 表单 reset 重置渲染
  $dom.on('reset', ELEM, function(){
    var filter = $(this).attr('lay-filter');
    setTimeout(function(){
      form.render(null, filter);
    }, 50);
  });
  
  // 表单提交事件
  $dom.on('submit', ELEM, submit)
  .on('click', '*[lay-submit]', submit);
  
  exports(MOD_NAME, form);
});

 
/**
 * layui.table
 * 表格组件
 */

layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
  "use strict";

  var $ = layui.$;
  var lay = layui.lay;
  var laytpl = layui.laytpl;
  var laypage = layui.laypage;
  var layer = layui.layer;
  var form = layui.form;
  var util = layui.util;
  var hint = layui.hint();
  var device = layui.device();

  // api
  var table = {
    config: { // 全局配置项
      checkName: 'LAY_CHECKED', // 是否选中状态的特定字段名
      indexName: 'LAY_INDEX', // 初始下标索引名，用于恢复当前页表格排序
      numbersName: 'LAY_NUM', // 序号
      disabledName: 'LAY_DISABLED' // 禁用状态的特定字段名
    },
    cache: {}, // 数据缓存
    index: layui.table ? (layui.table.index + 10000) : 0,

    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },

    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };

  // 操作当前实例
  var thisTable = function(){
    var that = this;
    var options = that.config;
    var id = options.id || options.index;

    return {
      config: options,
      reload: function(options, deep){
        that.reload.call(that, options, deep);
      },
      reloadData: function(options, deep){
        table.reloadData(id, options, deep);
      },
      setColsWidth: function(){
        that.setColsWidth.call(that);
      },
      resize: function(){ // 重置表格尺寸/结构
        that.resize.call(that);
      }
    }
  };

  // 获取当前实例
  var getThisTable = function(id){
    var that = thisTable.that[id];
    if(!that) hint.error(id ? ('The table instance with ID \''+ id +'\' not found') : 'ID argument required');
    return that || null;
  };

  // 获取当前实例配置项
  var getThisTableConfig = function(id){
    var config = thisTable.config[id];
    if(!config) hint.error(id ? ('The table instance with ID \''+ id +'\' not found') : 'ID argument required');
    return config || null;
  };

  // lay 函数可以处理 Selector，HTMLElement，JQuery 类型
  // 无效的 CSS 选择器字符串，会抛出 SyntaxError 异常，此时直接返回 laytpl 模板字符串
  var resolveTplStr = function(templet){
    try{ 
      return lay(templet).html();
    }catch(err){
      return templet;
    }
  }

  // 解析自定义模板数据
  var parseTempData = function(obj){
    obj = obj || {};

    var options = this.config || {};
    var item3 = obj.item3; // 表头数据
    var content = obj.content; // 原始内容
    if (item3.type === 'numbers') content = obj.tplData[table.config.numbersName];

    // 是否编码 HTML
    var escaped = 'escape' in item3 ? item3.escape : options.escape;
    if(escaped) content = util.escape(content);

    // 获取模板
    var templet = obj.text && item3.exportTemplet || (item3.templet || item3.toolbar);

    // 获取模板内容
    if(templet){
      content = typeof templet === 'function'
        ? templet.call(item3, obj.tplData, obj.obj)
        : laytpl(resolveTplStr(templet) || String(content)).render($.extend({
            LAY_COL: item3
          }, obj.tplData));
    }

    // 是否只返回文本
    return obj.text ? $('<div>'+ content +'</div>').text() : content;
  };

  // 字符
  var MOD_NAME = 'table';
  var ELEM = '.layui-table';
  var THIS = 'layui-this';
  var SHOW = 'layui-show';
  var HIDE = 'layui-hide';
  var HIDE_V = 'layui-hide-v';
  var DISABLED = 'layui-disabled';
  var NONE = 'layui-none';

  var ELEM_VIEW = 'layui-table-view';
  var ELEM_TOOL = '.layui-table-tool';
  var ELEM_BOX = '.layui-table-box';
  var ELEM_INIT = '.layui-table-init';
  var ELEM_HEADER = '.layui-table-header';
  var ELEM_BODY = '.layui-table-body';
  var ELEM_MAIN = '.layui-table-main';
  var ELEM_FIXED = '.layui-table-fixed';
  var ELEM_FIXL = '.layui-table-fixed-l';
  var ELEM_FIXR = '.layui-table-fixed-r';
  var ELEM_TOTAL = '.layui-table-total';
  var ELEM_PAGE = '.layui-table-page';
  var ELEM_PAGE_VIEW = '.layui-table-pageview';
  var ELEM_SORT = '.layui-table-sort';
  var ELEM_CHECKED = 'layui-table-checked';
  var ELEM_EDIT = 'layui-table-edit';
  var ELEM_HOVER = 'layui-table-hover';
  var ELEM_GROUP = 'laytable-cell-group';
  var ELEM_COL_SPECIAL = 'layui-table-col-special';
  var ELEM_TOOL_PANEL = 'layui-table-tool-panel';
  var ELEM_EXPAND = 'layui-table-expanded'

  var DATA_MOVE_NAME = 'LAY_TABLE_MOVE_DICT';

  // thead 区域模板
  var TPL_HEADER = function(options){
    var rowCols = '{{#var colspan = layui.type(item2.colspan2) === \'number\' ? item2.colspan2 : item2.colspan; if(colspan){}} colspan="{{=colspan}}"{{#} if(item2.rowspan){}} rowspan="{{=item2.rowspan}}"{{#}}}';

    options = options || {};
    return ['<table cellspacing="0" cellpadding="0" border="0" class="layui-table" '
      ,'{{# if(d.data.skin){ }}lay-skin="{{=d.data.skin}}"{{# } }} {{# if(d.data.size){ }}lay-size="{{=d.data.size}}"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>'
      ,'<thead>'
      ,'{{# layui.each(d.data.cols, function(i1, item1){ }}'
        ,'<tr>'
        ,'{{# layui.each(item1, function(i2, item2){ }}'
          ,'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}'
          ,'{{# if(item2.fixed === "right"){ right = true; } }}'
          ,function(){
            if(options.fixed && options.fixed !== 'right'){
              return '{{# if(item2.fixed && item2.fixed !== "right"){ }}';
            }
            if(options.fixed === 'right'){
              return '{{# if(item2.fixed === "right"){ }}';
            }
            return '';
          }()
          ,'{{# var isSort = !(item2.colGroup) && item2.sort; }}'
          ,'<th data-field="{{= item2.field||i2 }}" data-key="{{=d.index}}-{{=i1}}-{{=i2}}" {{# if( item2.parentKey){ }}data-parentkey="{{= item2.parentKey }}"{{# } }} {{# if(item2.minWidth){ }}data-minwidth="{{=item2.minWidth}}"{{# } }} {{# if(item2.maxWidth){ }}data-maxwidth="{{=item2.maxWidth}}"{{# } }} {{# if(item2.style){ }}style="{{=item2.style}}"{{# } }} '+ rowCols +' {{# if(item2.unresize || item2.colGroup){ }}data-unresize="true"{{# } }} class="{{# if(item2.hide){ }}layui-hide{{# } }}{{# if(isSort){ }} layui-unselect{{# } }}{{# if(!item2.field){ }} layui-table-col-special{{# } }}"{{# if(item2.title){ }} title="{{ layui.$(\'<div>\' + item2.title + \'</div>\').text() }}"{{# } }}>'
            ,'<div class="layui-table-cell laytable-cell-'
              ,'{{# if(item2.colGroup){ }}'
                ,'group'
              ,'{{# } else { }}'
                ,'{{=d.index}}-{{=i1}}-{{=i2}}'
                ,'{{# if(item2.type !== "normal"){ }}'
                  ,' laytable-cell-{{= item2.type }}'
                ,'{{# } }}'
              ,'{{# } }}'
            ,'" {{#if(item2.align){}}align="{{=item2.align}}"{{#}}}>'
              ,'{{# if(item2.type === "checkbox"){ }}' //复选框
                ,'<input type="checkbox" name="layTableCheckbox" lay-skin="primary" lay-filter="layTableAllChoose" {{# if(item2[d.data.checkName]){ }}checked{{# }; }}>'
              ,'{{# } else { }}'
                ,'<span>{{-item2.title||""}}</span>'
                ,'{{# if(isSort){ }}'
                  ,'<span class="layui-table-sort layui-inline"><i class="layui-edge layui-table-sort-asc" title="升序"></i><i class="layui-edge layui-table-sort-desc" title="降序"></i></span>'
                ,'{{# } }}'
              ,'{{# } }}'
            ,'</div>'
          ,'</th>'
          ,(options.fixed ? '{{# }; }}' : '')
        ,'{{# }); }}'
        ,'</tr>'
      ,'{{# }); }}'
      ,'</thead>'
    ,'</table>'].join('');
  };

  // tbody 区域模板
  var TPL_BODY = ['<table cellspacing="0" cellpadding="0" border="0" class="layui-table" '
    ,'{{# if(d.data.skin){ }}lay-skin="{{=d.data.skin}}"{{# } }} {{# if(d.data.size){ }}lay-size="{{=d.data.size}}"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>'
    ,'<tbody></tbody>'
  ,'</table>'].join('');

  // 主模板
  var TPL_MAIN = [
    ,'{{# if(d.data.toolbar){ }}'
    ,'<div class="layui-table-tool">'
      ,'<div class="layui-table-tool-temp"></div>'
      ,'<div class="layui-table-tool-self"></div>'
    ,'</div>'
    ,'{{# } }}'

    ,'<div class="layui-table-box">'
      ,'{{# if(d.data.loading){ }}'
      ,'<div class="layui-table-init" style="background-color: #fff;">'
        ,'<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>'
      ,'</div>'
      ,'{{# } }}'

      ,'{{# var left, right; }}'
      ,'<div class="layui-table-header">'
        ,TPL_HEADER()
      ,'</div>'
      ,'<div class="layui-table-body layui-table-main">'
        ,TPL_BODY
      ,'</div>'

      ,'{{# if(left){ }}'
      ,'<div class="layui-table-fixed layui-table-fixed-l">'
        ,'<div class="layui-table-header">'
          ,TPL_HEADER({fixed: true})
        ,'</div>'
        ,'<div class="layui-table-body">'
          ,TPL_BODY
        ,'</div>'
      ,'</div>'
      ,'{{# }; }}'

      ,'{{# if(right){ }}'
      ,'<div class="layui-table-fixed layui-table-fixed-r layui-hide">'
        ,'<div class="layui-table-header">'
          ,TPL_HEADER({fixed: 'right'})
          ,'<div class="layui-table-mend"></div>'
        ,'</div>'
        ,'<div class="layui-table-body">'
          ,TPL_BODY
        ,'</div>'
      ,'</div>'
      ,'{{# }; }}'
    ,'</div>'

    ,'{{# if(d.data.totalRow){ }}'
      ,'<div class="layui-table-total">'
        ,'<table cellspacing="0" cellpadding="0" border="0" class="layui-table" '
        ,'{{# if(d.data.skin){ }}lay-skin="{{=d.data.skin}}"{{# } }} {{# if(d.data.size){ }}lay-size="{{=d.data.size}}"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>'
          ,'<tbody><tr><td><div class="layui-table-cell" style="visibility: hidden;">Total</div></td></tr></tbody>'
      , '</table>'
      ,'</div>'
    ,'{{# } }}'

    ,'<div class="layui-table-column layui-table-page layui-hide">'
      ,'<div class="layui-inline layui-table-pageview" id="layui-table-page{{=d.index}}"></div>'
    ,'</div>'
  ].join('');

  var _WIN = $(window);
  var _DOC = $(document);

  // constructor
  var Class = function(options){
    var that = this;
    that.index = ++table.index;
    that.config = $.extend({}, that.config, table.config, options);
    that.render();
  };

  // 初始默认配置
  Class.prototype.config = {
    limit: 10, // 每页显示的数量
    loading: true, // 请求数据时，是否显示 loading
    escape: true, // 是否开启 HTML 编码功能，即转义 html 原文
    cellMinWidth: 60, // 所有单元格默认最小宽度
    cellMaxWidth: Number.MAX_VALUE, // 所有单元格默认最大宽度
    editTrigger: 'click', // 单元格编辑的事件触发方式
    defaultToolbar: ['filter', 'exports', 'print'], // 工具栏右侧图标
    defaultContextmenu: true, // 显示默认上下文菜单
    autoSort: true, // 是否前端自动排序。如果否，则需自主排序（通常为服务端处理好排序）
    text: {
      none: '无数据'
    },
    cols: []
  };

  // 表格渲染
  Class.prototype.render = function(type){
    var that = this;
    var options = that.config;

    options.elem = $(options.elem);
    options.where = options.where || {};

    // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
    var id = options.id = 'id' in options ? options.id : (
      options.elem.attr('id') || that.index
    );

    thisTable.that[id] = that; // 记录当前实例对象
    thisTable.config[id] = options; // 记录当前实例配置项

    //请求参数的自定义格式
    options.request = $.extend({
      pageName: 'page',
      limitName: 'limit'
    }, options.request)

    // 响应数据的自定义格式
    options.response = $.extend({
      statusName: 'code', //规定数据状态的字段名称
      statusCode: 0, //规定成功的状态码
      msgName: 'msg', //规定状态信息的字段名称
      dataName: 'data', //规定数据总数的字段名称
      totalRowName: 'totalRow', //规定数据统计的字段名称
      countName: 'count'
    }, options.response);

    //如果 page 传入 laypage 对象
    if(options.page !== null && typeof options.page === 'object'){
      options.limit = options.page.limit || options.limit;
      options.limits = options.page.limits || options.limits;
      that.page = options.page.curr = options.page.curr || 1;
      delete options.page.elem;
      delete options.page.jump;
    }

    if(!options.elem[0]) return that;

    // 若元素未设 lay-filter 属性，则取实例 id 值
    if(!options.elem.attr('lay-filter')){
      options.elem.attr('lay-filter', options.id);
    }

    // 仅重载数据
    if(type === 'reloadData'){
       // 请求数据
      return that.pullData(that.page, {
        type: 'reloadData'
      });
    }

    // 初始化索引
    options.index = that.index;
    that.key = options.id || options.index;

    // 初始化一些其他参数
    that.setInit();

    // 高度铺满：full-差距值
    if(options.height && /^full-.+$/.test(options.height)){
      that.fullHeightGap = options.height.split('-')[1];
      options.height = _WIN.height() - (parseFloat(that.fullHeightGap) || 0);
    } else if (options.height && /^#\w+\S*-.+$/.test(options.height)) {
      var parentDiv = options.height.split("-");
      that.parentHeightGap = parentDiv.pop();
      that.parentDiv = parentDiv.join("-");
      options.height = $(that.parentDiv).height() - (parseFloat(that.parentHeightGap) || 0);
    } else if (typeof options.height === "function"){
      that.customHeightFunc = options.height;
      options.height = that.customHeightFunc();
    }

    // 开始插入替代元素
    var othis = options.elem;
    var hasRender = othis.next('.' + ELEM_VIEW);

    // 主容器
    var reElem = that.elem = $('<div></div>');

    // 添加 className
    reElem.addClass(function(){
      var arr = [
        ELEM_VIEW,
        ELEM_VIEW +'-'+ that.index,
        'layui-form',
        'layui-border-box'
      ];
      if(options.className) arr.push(options.className);
      return arr.join(' ');
    }()).attr({
      'lay-filter': 'LAY-TABLE-FORM-DF-'+ that.index,
      'lay-id': options.id,
      'style': function(){
        var arr = [];
        if(options.width) arr.push('width:'+ options.width + 'px;');
        // if(options.height) arr.push('height:'+ options.height + 'px;');
        return arr.join('')
      }()
    }).html(laytpl(TPL_MAIN, {
      open: '{{', // 标签符前缀
      close: '}}' // 标签符后缀
    }).render({
      data: options,
      index: that.index //索引
    }));

    // 初始化样式
    that.renderStyle();

    // 生成替代元素
    hasRender[0] && hasRender.remove(); // 如果已经渲染，则 Rerender
    othis.after(reElem);

    // 各级容器
    that.layTool = reElem.find(ELEM_TOOL);
    that.layBox = reElem.find(ELEM_BOX);
    that.layHeader = reElem.find(ELEM_HEADER);
    that.layMain = reElem.find(ELEM_MAIN);
    that.layBody = reElem.find(ELEM_BODY);
    that.layFixed = reElem.find(ELEM_FIXED);
    that.layFixLeft = reElem.find(ELEM_FIXL);
    that.layFixRight = reElem.find(ELEM_FIXR);
    that.layTotal = reElem.find(ELEM_TOTAL);
    that.layPage = reElem.find(ELEM_PAGE);

    // 初始化头部工具栏
    that.renderToolbar();

    // 初始化底部分页栏
    that.renderPagebar();

    // 让表格平铺
    that.fullSize();

    that.pullData(that.page); // 请求数据
    that.events(); // 事件
  };

  // 根据列类型，定制化参数
  Class.prototype.initOpts = function(item){
    var that = this
    var options = that.config;
    var initWidth = {
      checkbox: 50,
      radio: 50,
      space: 30,
      numbers: 60
    };

    // 让 type 参数兼容旧版本
    if(item.checkbox) item.type = "checkbox";
    if(item.space) item.type = "space";
    if(!item.type) item.type = "normal";

    if(item.type !== "normal"){
      item.unresize = true;
      item.width = item.width || initWidth[item.type];
    }
  };

  //初始化一些参数
  Class.prototype.setInit = function(type){
    var that = this;
    var options = that.config;

    options.clientWidth = options.width || function(){ //获取容器宽度
      //如果父元素宽度为0（一般为隐藏元素），则继续查找上层元素，直到找到真实宽度为止
      var getWidth = function(parent){
        var width, isNone;
        parent = parent || options.elem.parent()
        width = parent.width();
        try {
          isNone = parent.css('display') === 'none';
        } catch(e){}
        if(parent[0] && (!width || isNone)) return getWidth(parent.parent());
        return width;
      };
      return getWidth();
    }();

    if(type === 'width') return options.clientWidth;
    // 初始化高度配置，如果设置了最高高度，以最高高度形式为准
    options.height = options.maxHeight || options.height;

    // 初始化 css 参数
    if(options.css && options.css.indexOf(ELEM_VIEW) === -1){
      var css = options.css.split('}');
      layui.each(css, function(index, value){
        if(value){
          css[index] = '.'+ ELEM_VIEW + '-'+ that.index + ' ' + value;
        }
      });
      options.css = css.join('}');
    }

    // 封装对 col 的配置处理
    var initChildCols = function (i1, item1, i2, item2) {
      //如果列参数为空，则移除
      if (!item2) {
        item1.splice(i2, 1);
        return;
      }

      item2.key = [options.index, i1, i2].join('-');
      item2.colspan = item2.colspan || 0;
      item2.rowspan = item2.rowspan || 0;

      //根据列类型，定制化参数
      that.initOpts(item2);

      //设置列的父列索引
      //如果是组合列，则捕获对应的子列
      var indexChild = i1 + (parseInt(item2.rowspan) || 1);
      if (indexChild < options.cols.length) { // 只要不是最后一层都会有子列
        item2.colGroup = true;
        var childIndex = 0;
        layui.each(options.cols[indexChild], function (i22, item22) {
          //如果子列已经被标注为{HAS_PARENT}，或者子列累计 colspan 数等于父列定义的 colspan，则跳出当前子列循环
          if (item22.HAS_PARENT || (childIndex >= 1 && childIndex == (item2.colspan || 1))) return;

          item22.HAS_PARENT = true;
          item22.parentKey = [options.index, i1, i2].join('-') // i1 + '-' + i2;
          childIndex = childIndex + parseInt(item22.colspan > 1 ? item22.colspan : 1);
          initChildCols(indexChild, options.cols[indexChild], i22, item22);
        });
      } else {
        item2.colGroup = false;
      }
      item2.hide = item2.hide && !item2.colGroup || false; // 初始化中中间节点的hide信息不做处理，否则会出错，如果需要必须将其子节点也都同步成hide
    };

    // 初始化列参数
    layui.each(options.cols, function(i1, item1){
      layui.each(item1, function(i2, item2){
        if (i1) {
          delete item2.HAS_PARENT; // 去掉临时的计数排除标识，避免有新字段插入的时候重新计算被跳过导致下标出错的问题
        } else {
          initChildCols(i1, item1, i2, item2); // 只解析顶层节点由递归完成解析
        }
      });
    });

  };

  // 初始化样式
  Class.prototype.renderStyle = function() {
    var that = this;
    var options = that.config;
    var index = that.index;
    var text = [];

    // 单元格宽度
    layui.each(options.cols, function(i1, item1) {
      layui.each(item1, function(i2, item2) {
        var key = [index, i1, i2].join('-');
        var val = ['width: ', (item2.width || options.cellMinWidth), 'px'].join('');
        text.push('.laytable-cell-'+ key +'{'+ val +'}');
      });
    });

    // 自定义行样式
    (function (lineStyle) {
      if (!lineStyle) return;
      var trClassName = '.layui-table-view-'+ index +' .layui-table-body .layui-table tr';
      var rules = lineStyle.split(';');
      var cellMaxHeight = 'none';

      // 计算单元格最大高度
      layui.each(rules, function(i, rule) {
        rule = rule.split(':');
        if (rule[0] === 'height') {
          var val = parseFloat(rule[1]);
          if (!isNaN(val)) cellMaxHeight = (val - 1) + 'px';
          return true;
        }
      });

      // 多行相关样式
      layui.each([
        '{'+ lineStyle +'}',
        '.layui-table-cell{height: auto; max-height: '+ cellMaxHeight +'; white-space: normal; text-overflow: clip;}',
        '> td:hover > .layui-table-cell{overflow: auto;}'
      ].concat(
        device.ie ? [
          '.layui-table-edit{height: '+ cellMaxHeight +';}',
          'td[data-edit]:hover:after{height: '+ cellMaxHeight +';}'
        ] : []
      ), function(i, val) {
        val && text.push(trClassName + ' ' + val);
      });
    })(options.lineStyle);

    // 自定义 css 属性
    if (options.css) text.push(options.css);

    // 生成 style
    lay.style({
      target: that.elem[0],
      text: text.join(''),
      id: 'DF-table-'+ index
    });
  };

  // 初始工具栏
  Class.prototype.renderToolbar = function(){
    var that = this
    var options = that.config

    // 添加工具栏左侧模板
    var leftDefaultTemp = [
      '<div class="layui-inline" lay-event="add"><i class="layui-icon layui-icon-add-1"></i></div>',
      '<div class="layui-inline" lay-event="update"><i class="layui-icon layui-icon-edit"></i></div>',
      '<div class="layui-inline" lay-event="delete"><i class="layui-icon layui-icon-delete"></i></div>'
    ].join('');
    var elemToolTemp = that.layTool.find('.layui-table-tool-temp');

    if(options.toolbar === 'default'){
      elemToolTemp.html(leftDefaultTemp);
    } else if(typeof options.toolbar === 'string'){
      var toolbarHtml = $(options.toolbar).html() || '';
      toolbarHtml && elemToolTemp.html(
        laytpl(toolbarHtml).render(options)
      );
    }

    // 添加工具栏右侧面板
    var layout = {
      filter: {
        title: '筛选列',
        layEvent: 'LAYTABLE_COLS',
        icon: 'layui-icon-cols'
      },
      exports: {
        title: '导出',
        layEvent: 'LAYTABLE_EXPORT',
        icon: 'layui-icon-export'
      },
      print: {
        title: '打印',
        layEvent: 'LAYTABLE_PRINT',
        icon: 'layui-icon-print'
      }
    }, iconElem = [];

    if(typeof options.defaultToolbar === 'object'){
      layui.each(options.defaultToolbar, function(i, item){
        var thisItem = typeof item === 'string' ? layout[item] : item;
        if(thisItem){
          iconElem.push('<div class="layui-inline" title="'+ thisItem.title +'" lay-event="'+ thisItem.layEvent +'">'
            +'<i class="layui-icon '+ thisItem.icon +'"></i>'
          +'</div>');
        }
      });
    }
    that.layTool.find('.layui-table-tool-self').html(iconElem.join(''));
  };

  // 分页栏
  Class.prototype.renderPagebar = function(){
    var that = this;
    var options = that.config;

    var layPagebar = that.layPagebar = $('<div class="layui-inline layui-table-pagebar"></div>');

    // 开启分页栏自定义模板
    if(options.pagebar){
      var pagebarHtml = $(options.pagebar).html() || '';
      pagebarHtml && layPagebar.append(laytpl(pagebarHtml).render(options));
      that.layPage.append(layPagebar);
    }
  };

  // 同步表头父列的相关值
  Class.prototype.setParentCol = function(hide, parentKey){
    var that = this;
    var options = that.config;

    var parentTh = that.layHeader.find('th[data-key="'+ parentKey +'"]'); // 获取父列元素
    var parentColspan = parseInt(parentTh.attr('colspan')) || 0;

    if(parentTh[0]){
      var arrParentKey = parentKey.split('-');
      var getThisCol = options.cols[arrParentKey[1]][arrParentKey[2]];

      hide ? parentColspan-- : parentColspan++;

      parentTh.attr('colspan', parentColspan);
      parentTh[parentColspan ? 'removeClass' : 'addClass'](HIDE); // 如果子列显示，父列必然需要显示

      getThisCol.colspan2 = parentColspan; // 更新实际的 colspan 数
      getThisCol.hide = parentColspan < 1; // 同步 hide 参数

      // 递归，继续往上查询是否有父列
      var nextParentKey = parentTh.data('parentkey');
      nextParentKey && that.setParentCol(hide, nextParentKey);
    }
  };

  // 多级表头补丁
  Class.prototype.setColsPatch = function(){
    var that = this;
    var options = that.config;

    // 同步表头父列的相关值
    layui.each(options.cols, function(i1, item1){
      layui.each(item1, function(i2, item2){
        if(item2.hide){
          that.setParentCol(item2.hide, item2.parentKey);
        }
      });
    });
  };

  // 设置组合表头的最大宽度
  Class.prototype.setGroupWidth = function(th){
    var that = this;
    var options = that.config;

    if(options.cols.length <= 1) return;

    // 获取表头组合
    var groups = that.layHeader.find((
      // 根据当前活动的表头 parentkey 属性查找其组合表头
      th ? ('th[data-key='+ th.data('parentkey') +']>') : ''
    ) + '.' + ELEM_GROUP); // 若无指向当前活动表头，则自下而上获取所有组合表头

    groups.css('width', 0);
    layui.each(groups.get().reverse(), function(){
      var othis = $(this);
      var key = othis.parent().data('key');
      var maxWidth = 0;

      that.layHeader.eq(0).find('th[data-parentkey='+ key +']').width(function(i, width){
        var oTh = $(this);
        if(oTh.hasClass(HIDE)) return;
        width > 0 && (maxWidth += width);
      });

      // 给组合表头赋值最大宽度
      if(maxWidth) othis.css('max-width', maxWidth - 1);

      // 若当前活动的组合表头仍存在上级，则继续向上设置
      if(th && othis.parent().data('parentkey')){
        that.setGroupWidth(othis.parent());
      }
    });
    groups.css('width', 'auto');
  };

  // 动态分配列宽
  Class.prototype.setColsWidth = function(){
    var that = this;
    var options = that.config;
    var colNums = 0; // 列个数
    var autoColNums = 0; // 自动列宽的列个数
    var autoWidth = 0; // 自动列分配的宽度
    var countWidth = 0; // 所有列总宽度和
    var cntrWidth = that.setInit('width');

    // 统计列个数
    that.eachCols(function(i, item){
      item.hide || colNums++;
    });

    // 减去边框差和滚动条宽
    cntrWidth = cntrWidth - function(){
      return (options.skin === 'line' || options.skin === 'nob') ? 2 : colNums + 1;
    }() - that.getScrollWidth(that.layMain[0]) - 1;

    // 计算自动分配的宽度
    var getAutoWidth = function(back){
      // 遍历所有列
      layui.each(options.cols, function(i1, item1){
        layui.each(item1, function(i2, item2){
          var width = 0;
          var minWidth = item2.minWidth || options.cellMinWidth; // 最小宽度
          var maxWidth = item2.maxWidth || options.cellMaxWidth; // 最大宽度

          if(!item2){
            item1.splice(i2, 1);
            return;
          }

          if(item2.colGroup || item2.hide) return;

          if(!back){
            width = item2.width || 0;
            if(/\d+%$/.test(width)){ // 列宽为百分比
              width = Math.floor((parseFloat(width) / 100) * cntrWidth);
              width < minWidth && (width = minWidth);
              width > maxWidth && (width = maxWidth);
            } else if(!width){ // 列宽未填写
              item2.width = width = 0;
              autoColNums++;
            } else if(item2.type === 'normal'){
              // 若 width 小于 minWidth， 则将 width 值自动设为 minWidth 的值
              width < minWidth && (item2.width = width = minWidth);
              // 若 width 大于 maxWidth， 则将 width 值自动设为 maxWidth 的值
              width > maxWidth && (item2.width = width = maxWidth);
            }
          } else if(autoWidth && autoWidth < minWidth){
            autoColNums--;
            width = minWidth;
          } else if(autoWidth && autoWidth > maxWidth){
            autoColNums--;
            width = maxWidth;
          }

          if(item2.hide) width = 0;
          countWidth = countWidth + width;
        });
      });

      // 如果未填充满，则将剩余宽度平分
      (cntrWidth > countWidth && autoColNums > 0) && (
        autoWidth = (cntrWidth - countWidth) / autoColNums
      );
    }

    getAutoWidth();
    getAutoWidth(true); // 重新检测分配的宽度是否低于最小列宽

    // 记录自动列数
    that.autoColNums = autoColNums = autoColNums > 0 ? autoColNums : 0;

    // 设置列宽
    that.eachCols(function(i3, item3){
      var minWidth = item3.minWidth || options.cellMinWidth;
      var maxWidth = item3.maxWidth || options.cellMaxWidth;

      if(item3.colGroup || item3.hide) return;

      // 给未分配宽的列平均分配宽
      if(item3.width === 0){
        that.cssRules(item3.key, function(item){
          item.style.width = Math.floor(function(){
            if(autoWidth < minWidth) return minWidth;
            if(autoWidth > maxWidth) return maxWidth;
            return autoWidth;
          }()) + 'px';
        });
      }

      // 给设定百分比的列分配列宽
      else if(/\d+%$/.test(item3.width)){
        that.cssRules(item3.key, function(item){
          var width = Math.floor((parseFloat(item3.width) / 100) * cntrWidth);
          width < minWidth && (width = minWidth);
          width > maxWidth && (width = maxWidth);
          item.style.width = width + 'px';
        });
      }

      // 给拥有普通 width 值的列分配最新列宽
      else {
        that.cssRules(item3.key, function(item){
          item.style.width = item3.width + 'px';
        });
      }
    });

    // 填补 Math.floor 造成的数差
    var patchNums = that.layMain.width() - that.getScrollWidth(that.layMain[0])
    - that.layMain.children('table').outerWidth();

    if(that.autoColNums > 0 && patchNums >= -colNums && patchNums <= colNums){
      var getEndTh = function(th){
        var field;
        th = th || that.layHeader.eq(0).find('thead > tr:first-child > th:last-child')
        field = th.data('field');
        if(!field && th.prev()[0]){
          return getEndTh(th.prev())
        }
        return th;
      };
      var th = getEndTh();
      var key = th.data('key');

      that.cssRules(key, function(item){
        var width = item.style.width || th.outerWidth();
        item.style.width = (parseFloat(width) + patchNums) + 'px';

        // 二次校验，如果仍然出现横向滚动条（通常是 1px 的误差导致）
        if(that.layMain.height() - that.layMain.prop('clientHeight') > 0){
          item.style.width = (parseFloat(item.style.width) - 1) + 'px';
        }
      });
    }

    that.setGroupWidth();

    // 如果表格内容为空（无数据 或 请求异常）
    if (that.layMain.find('tbody').is(":empty")) {
      // 将表格宽度设置为跟表头一样的宽度，使之可以出现底部滚动条，以便滚动查看所有字段
      var headerWidth = that.layHeader.first().children('table').width()
      that.layMain.find('table').width(headerWidth);
    } else {
      that.layMain.find('table').width('auto');
    }

    that.loading(!0);
  };

  // 重置表格尺寸/结构
  Class.prototype.resize = function(){
    var that = this;

    if (!that.layMain) return;

    that.fullSize(); // 让表格铺满
    that.setColsWidth(); // 自适应列宽
    that.scrollPatch(); // 滚动条补丁
  };

  // 表格重载
  Class.prototype.reload = function(options, deep, type){
    var that = this;

    options = options || {};
    delete that.haveInit;

    // 防止数组深度合并
    layui.each(options, function(key, item){
      if(layui.type(item) === 'array') delete that.config[key];
    });

    // 对参数进行深度或浅扩展
    that.config = $.extend(deep, {}, that.config, options);
    if (type !== 'reloadData') {
      layui.each(that.config.cols, function (i1, item1) {
        layui.each(item1, function (i2, item2) {
          delete item2.colspan2;
        })
      })
      delete that.config.HAS_SET_COLS_PATCH;
    }
    // 执行渲染
    that.render(type);
  };

  // 异常提示
  Class.prototype.errorView = function(html){
    var that = this
    ,elemNone = that.layMain.find('.'+ NONE)
    ,layNone = $('<div class="'+ NONE +'">'+ (html || 'Error') +'</div>');

    if(elemNone[0]){
      that.layNone.remove();
      elemNone.remove();
    }

    that.layFixed.addClass(HIDE);
    that.layMain.find('tbody').html('');

    that.layMain.append(that.layNone = layNone);

    // 异常情况下对 page 和 total 的内容处理
    that.layTotal.addClass(HIDE_V);
    that.layPage.find(ELEM_PAGE_VIEW).addClass(HIDE_V);

    table.cache[that.key] = []; //格式化缓存数据

    that.syncCheckAll();
    that.renderForm();
    that.setColsWidth();
  };

  // 初始页码
  Class.prototype.page = 1;

  // 获得数据
  Class.prototype.pullData = function(curr, opts){
    var that = this;
    var options = that.config;
    // 同步表头父列的相关值
    options.HAS_SET_COLS_PATCH || that.setColsPatch();
    options.HAS_SET_COLS_PATCH = true;
    var request = options.request;
    var response = options.response;
    var res;
    var sort = function(){
      if(typeof options.initSort === 'object'){
        that.sort({
          field: options.initSort.field,
          type: options.initSort.type,
          reloadType: opts.type
        });
      }
    };
    var done = function(res, origin){
      that.setColsWidth();
      typeof options.done === 'function' && options.done(
        res, curr, res[response.countName], origin
      );
    };

    opts = opts || {};

    // 数据拉取前的回调
    typeof options.before === 'function' && options.before(
      options
    );
    that.startTime = new Date().getTime(); // 渲染开始时间

    if (opts.renderData) { // 将 cache 信息重新渲染
      res = {};
      res[response.dataName] = table.cache[that.key];
      res[response.countName] = options.url ? (layui.type(options.page) === 'object' ? options.page.count : res[response.dataName].length) : options.data.length;

      // 记录合计行数据
      if(typeof options.totalRow === 'object'){
        res[response.totalRowName] = $.extend({}, that.totalRow);
      }

      that.renderData({
        res: res,
        curr: curr,
        count: res[response.countName],
        type: opts.type,
        sort: true
      }), done(res, 'renderData');
    } else if(options.url){ // Ajax请求
      var params = {};
      // 当 page 开启，默认自动传递 page、limit 参数
      if(options.page){
        params[request.pageName] = curr;
        params[request.limitName] = options.limit;
      }

      // 参数
      var data = $.extend(params, options.where);
      if(options.contentType && options.contentType.indexOf("application/json") == 0){ // 提交 json 格式
        data = JSON.stringify(data);
      }

      that.loading();

      $.ajax({
        type: options.method || 'get',
        url: options.url,
        contentType: options.contentType,
        data: data,
        dataType: options.dataType || 'json',
        jsonpCallback: options.jsonpCallback,
        headers: options.headers || {},
        complete: typeof options.complete === 'function' ? options.complete : undefined,
        success: function(res){
          // 若有数据解析的回调，则获得其返回的数据
          if(typeof options.parseData === 'function'){
            res = options.parseData(res) || res;
          }
          // 检查数据格式是否符合规范
          if(res[response.statusName] != response.statusCode){
            that.errorView(
              res[response.msgName] ||
              ('返回的数据不符合规范，正确的成功状态码应为："'+ response.statusName +'": '+ response.statusCode)
            );
          } else {
            that.totalRow = res[response.totalRowName];
            that.renderData({
              res: res,
              curr: curr,
              count: res[response.countName],
              type: opts.type
            }), sort();

            // 耗时（接口请求+视图渲染）
            options.time = (new Date().getTime() - that.startTime) + ' ms';
          }
          done(res);
        },
        error: function(e, msg){
          that.errorView('请求异常，错误提示：'+ msg);
          typeof options.error === 'function' && options.error(e, msg);
        }
      });
    } else if(layui.type(options.data) === 'array'){ //已知数据
      res = {};
      var startLimit = curr*options.limit - options.limit;
      var newData = options.data.concat();

      res[response.dataName] = options.page
        ? newData.splice(startLimit, options.limit)
      : newData;
      res[response.countName] = options.data.length;

      // 记录合计行数据
      if(typeof options.totalRow === 'object'){
        res[response.totalRowName] = $.extend({}, options.totalRow);
      }
      that.totalRow = res[response.totalRowName];

      that.renderData({
        res: res,
        curr: curr,
        count: res[response.countName],
        type: opts.type
      }), sort();

      done(res);
    }
  };

  // 遍历表头
  Class.prototype.eachCols = function(callback){
    var that = this;
    table.eachCols(null, callback, that.config.cols);
    return that;
  };

  // 获取表头参数项
  Class.prototype.col = function(key){
    try {
      key = key.split('-');
      return this.config.cols[key[1]][key[2]] || {};
    } catch(e){
      hint.error(e);
      return {};
    }
  };

  Class.prototype.getTrHtml = function(data, sort, curr, trsObj) {
    var that = this;
    var options = that.config;
    var trs = trsObj && trsObj.trs || [];
    var trs_fixed = trsObj && trsObj.trs_fixed || [];
    var trs_fixed_r = trsObj && trsObj.trs_fixed_r || [];
    curr = curr || 1

    layui.each(data, function(i1, item1){
      var tds = [];
      var tds_fixed = [];
      var tds_fixed_r = [];
      var numbers = i1 + options.limit*(curr - 1) + 1; // 序号

      // 数组值是否为 object，如果不是，则自动转为 object
      if(typeof item1 !== 'object'){
        data[i1] = item1 = {LAY_KEY: item1};
        try {
          table.cache[that.key][i1] = item1;
        } catch(e) {}
      }

      //若数据项为空数组，则不往下执行（因为删除数据时，会将原有数据设置为 []）
      if(layui.type(item1) === 'array' && item1.length === 0) return;

      // 加入序号保留字段
      item1[table.config.numbersName] = numbers;

      // 记录下标索引，用于恢复排序
      if(!sort) item1[table.config.indexName] = i1;

      // 遍历表头
      that.eachCols(function(i3, item3){
        var field = item3.field || i3;
        var key = item3.key;
        var content = item1[field];

        if(content === undefined || content === null) content = '';
        if(item3.colGroup) return;

        // td 内容
        var td = ['<td data-field="'+ field +'" data-key="'+ key +'" '+ function(){
          // 追加各种属性
          var attr = [];
          // 是否开启编辑。若 edit 传入函数，则根据函数的返回结果判断是否开启编辑
          (function(edit){
            if(edit) attr.push('data-edit="'+ edit +'"'); // 添加单元格编辑属性标识
          })(typeof item3.edit === 'function' ? item3.edit(item1) : item3.edit);
          if(item3.templet) attr.push('data-content="'+ util.escape(content) +'"'); // 自定义模板
          if(item3.toolbar) attr.push('data-off="true"'); // 行工具列关闭单元格事件
          if(item3.event) attr.push('lay-event="'+ item3.event +'"'); //自定义事件
          if(item3.minWidth) attr.push('data-minwidth="'+ item3.minWidth +'"'); // 单元格最小宽度
          if(item3.maxWidth) attr.push('data-maxwidth="'+ item3.maxWidth +'"'); // 单元格最大宽度
          if(item3.style) attr.push('style="'+ item3.style +'"'); // 自定义单元格样式
          return attr.join(' ');
        }() +' class="'+ function(){ // 追加样式
          var classNames = [];
          if(item3.hide) classNames.push(HIDE); // 插入隐藏列样式
          if(!item3.field) classNames.push(ELEM_COL_SPECIAL); // 插入特殊列样式
          return classNames.join(' ');
        }() +'">'
          ,'<div class="layui-table-cell laytable-cell-'+ function(){ // 返回对应的CSS类标识
            return item3.type === 'normal' ? key
              : (key + ' laytable-cell-' + item3.type);
          }() +'"'
          + (item3.align ? ' align="'+ item3.align +'"' : '')
          +'>'
          + function(){
            var tplData = $.extend(true, {
              LAY_COL: item3
            }, item1);
            var checkName = table.config.checkName;
            var disabledName = table.config.disabledName;

            // 渲染不同风格的列
            switch(item3.type){
              case 'checkbox': // 复选
                return '<input type="checkbox" name="layTableCheckbox" lay-skin="primary" '+ function(){
                  // 其他属性
                  var arr = [];

                  //如果是全选
                  if(item3[checkName]){
                    item1[checkName] = item3[checkName];
                    if(item3[checkName]) arr[0] = 'checked';
                  }
                  if(tplData[checkName]) arr[0] = 'checked';

                  // 禁选
                  if(tplData[disabledName]) arr.push('disabled');

                  return arr.join(' ');
                }() +' lay-type="layTableCheckbox">';
                //break;
              case 'radio': // 单选
                return '<input type="radio" name="layTableRadio_'+ options.index +'" '
                  + function(){
                    var arr = [];
                    if(tplData[checkName]) arr[0] = 'checked';
                    if(tplData[disabledName]) arr.push('disabled');
                    return arr.join(' ');
                  }() +' lay-type="layTableRadio">';
                //break;
              case 'numbers':
                return numbers;
                //break;
            }

            //解析工具列模板
            if(item3.toolbar){
              return laytpl($(item3.toolbar).html()||'').render(tplData);
            }
            return parseTempData.call(that, {
              item3: item3
              ,content: content
              ,tplData: tplData
            });
          }()
          ,'</div></td>'].join('');

        tds.push(td);
        if(item3.fixed && item3.fixed !== 'right') tds_fixed.push(td);
        if(item3.fixed === 'right') tds_fixed_r.push(td);
      });

      // 添加 tr 属性
      var trAttr = function(){
        var arr = ['data-index="'+ i1 +'"'];
        if(item1[table.config.checkName]) arr.push('class="'+ ELEM_CHECKED +'"');
        return arr.join(' ');
      }();

      trs.push('<tr '+ trAttr +'>'+ tds.join('') + '</tr>');
      trs_fixed.push('<tr '+ trAttr +'>'+ tds_fixed.join('') + '</tr>');
      trs_fixed_r.push('<tr '+ trAttr +'>'+ tds_fixed_r.join('') + '</tr>');
    });

    return {
      trs: trs,
      trs_fixed: trs_fixed,
      trs_fixed_r: trs_fixed_r
    }
  }

  // 返回行节点代码
  table.getTrHtml = function (id, data) {
    var that = getThisTable(id);
    return that.getTrHtml(data, null, that.page);
  }

  // 数据渲染
  Class.prototype.renderData = function(opts){
    var that = this;
    var options = that.config;

    var res = opts.res;
    var curr = opts.curr;
    var count = that.count = opts.count;
    var sort = opts.sort;

    var data = res[options.response.dataName] || []; //列表数据
    var totalRowData = res[options.response.totalRowName]; //合计行数据
    var trs = [];
    var trs_fixed = [];
    var trs_fixed_r = [];

    // 渲染视图
    var render = function(){ // 后续性能提升的重点
      if(!sort && that.sortKey){
        return that.sort({
          field: that.sortKey.field,
          type: that.sortKey.sort,
          pull: true,
          reloadType: opts.type
        });
      }
      that.getTrHtml(data, sort, curr, {
        trs: trs,
        trs_fixed: trs_fixed,
        trs_fixed_r: trs_fixed_r
      });

      // 容器的滚动条位置
      if(!(options.scrollPos === 'fixed' && opts.type === 'reloadData')){
        that.layBody.scrollTop(0);
      }
      if(options.scrollPos === 'reset'){
        that.layBody.scrollLeft(0);
      }

      that.layMain.find('.'+ NONE).remove();
      that.layMain.find('tbody').html(trs.join(''));
      that.layFixLeft.find('tbody').html(trs_fixed.join(''));
      that.layFixRight.find('tbody').html(trs_fixed_r.join(''));

      // 渲染表单
      that.syncCheckAll();
      that.renderForm();

      // 因为 page 参数有可能发生变化 先重新铺满
      that.fullSize();

      // 滚动条补丁
      that.haveInit ? that.scrollPatch() : setTimeout(function(){
        that.scrollPatch();
      }, 50);
      that.haveInit = true;

      layer.close(that.tipsIndex);
    };

    table.cache[that.key] = data; //记录数据

    //显示隐藏合计栏
    that.layTotal[data.length == 0 ? 'addClass' : 'removeClass'](HIDE_V);

    //显示隐藏分页栏
    that.layPage[(options.page || options.pagebar) ? 'removeClass' : 'addClass'](HIDE);
    that.layPage.find(ELEM_PAGE_VIEW)[
      (!options.page || count == 0 || (data.length === 0 && curr == 1))
        ? 'addClass'
      : 'removeClass'
    ](HIDE_V);

    //如果无数据
    if(data.length === 0){
      return that.errorView(options.text.none);
    } else {
      that.layFixLeft.removeClass(HIDE);
    }

    //如果执行初始排序
    if(sort){
      return render();
    }

    //正常初始化数据渲染
    render(); //渲染数据
    that.renderTotal(data, totalRowData); //数据合计
    that.layTotal && that.layTotal.removeClass(HIDE);

    //同步分页状态
    if(options.page){
      options.page = $.extend({
        elem: 'layui-table-page' + options.index,
        count: count,
        limit: options.limit,
        limits: options.limits || [10,20,30,40,50,60,70,80,90],
        groups: 3,
        layout: ['prev', 'page', 'next', 'skip', 'count', 'limit'],
        prev: '<i class="layui-icon">&#xe603;</i>',
        next: '<i class="layui-icon">&#xe602;</i>',
        jump: function(obj, first){
          if(!first){
            //分页本身并非需要做以下更新，下面参数的同步，主要是因为其它处理统一用到了它们
            //而并非用的是 options.page 中的参数（以确保分页未开启的情况仍能正常使用）
            that.page = obj.curr; //更新页码
            options.limit = obj.limit; //更新每页条数

            that.pullData(obj.curr);
          }
        }
      }, options.page);
      options.page.count = count; //更新总条数
      laypage.render(options.page);
    }
  };

  // 重新渲染数据
  table.renderData = function (id) {
    var that = getThisTable(id);
    if (!that) {
      return;
    }

    that.pullData(that.page, {
      renderData: true,
      type: 'reloadData'
    });
  }

  // 数据合计行
  Class.prototype.renderTotal = function(data, totalRowData){
    var that = this;
    var options = that.config;
    var totalNums = {};

    if(!options.totalRow) return;

    layui.each(data, function(i1, item1){
      // 若数据项为空数组，则不往下执行（因为删除数据时，会将原有数据设置为 []）
      if(layui.type(item1) === 'array' && item1.length === 0) return;

      that.eachCols(function(i3, item3){
        var field = item3.field || i3
        ,content = item1[field];

        if(item3.totalRow){
          totalNums[field] = (totalNums[field] || 0) + (parseFloat(content) || 0);
        }
      });
    });

    that.dataTotal = []; // 记录合计行结果

    var tds = [];
    that.eachCols(function(i3, item3){
      var field = item3.field || i3;

      // 合计数据的特定字段
      var TOTAL_NUMS = totalRowData && totalRowData[item3.field];

      // 合计数据的小数点位数处理
      var decimals = 'totalRowDecimals' in item3 ? item3.totalRowDecimals : 2;
      var thisTotalNum = totalNums[field]
        ? parseFloat(totalNums[field] || 0).toFixed(decimals)
      : '';

      // td 显示内容
      var content = function(){
        var text = item3.totalRowText || '';
        var tplData = {
          LAY_COL: item3
        };

        tplData[field] = thisTotalNum;

        // 获取自动计算的合并内容
        var getContent = item3.totalRow ? (parseTempData.call(that, {
          item3: item3,
          content: thisTotalNum,
          tplData: tplData
        }) || text) : text;

        // 如果直接传入了合计行数据，则不输出自动计算的结果
        return TOTAL_NUMS || getContent;
      }();

      // 合计原始结果
      var total = TOTAL_NUMS || thisTotalNum || '';
      item3.field && that.dataTotal.push({
        field: item3.field,
        total: $('<div>'+ content +'</div>').text()
      });

      // td 容器
      var td = ['<td data-field="'+ field +'" data-key="'+ item3.key +'" '+ function(){
        var attr = [];
        if(item3.minWidth) attr.push('data-minwidth="'+ item3.minWidth +'"'); // 单元格最小宽度
        if(item3.maxWidth) attr.push('data-maxwidth="'+ item3.maxWidth +'"'); // 单元格最小宽度
        if(item3.style) attr.push('style="'+ item3.style +'"'); // 自定义单元格样式
        return attr.join(' ');
      }() +' class="'+ function(){ // 追加样式
        var classNames = [];
        if(item3.hide) classNames.push(HIDE); // 插入隐藏列样式
        if(!item3.field) classNames.push(ELEM_COL_SPECIAL); // 插入特殊列样式
        return classNames.join(' ');
      }() +'">',
        '<div class="layui-table-cell laytable-cell-'+ function(){ // 返回对应的CSS类标识
          var key = item3.key;
          return item3.type === 'normal' ? key
          : (key + ' laytable-cell-' + item3.type);
        }() +'"'+ function(){
        var attr = [];
        if(item3.align) attr.push('align="'+ item3.align +'"'); // 对齐方式
        return attr.join(' ');
      }() +'>' + function(){
          var totalRow = item3.totalRow || options.totalRow;

          // 如果 totalRow 参数为字符类型，则解析为自定义模版
          if(typeof totalRow === 'string'){
            return laytpl(totalRow).render($.extend({
              TOTAL_NUMS: TOTAL_NUMS || totalNums[field],
              TOTAL_ROW: totalRowData || {},
              LAY_COL: item3
            }, item3));
          }
          return content;
        }(),
      '</div></td>'].join('');

      tds.push(td);
    });

    var patchElem = that.layTotal.find('.layui-table-patch'); // 可能存在滚动条补丁
    that.layTotal.find('tbody').html('<tr>' + tds.join('') + (patchElem.length ? patchElem.get(0).outerHTML : '') + '</tr>');
  };

  //找到对应的列元素
  Class.prototype.getColElem = function(parent, key){
    var that = this;
    //var options = that.config;
    return parent.eq(0).find('.laytable-cell-'+ key + ':eq(0)');
  };

  // 渲染表单
  Class.prototype.renderForm = function(type){
    var that = this;
    var options = that.config;
    var filter = that.elem.attr('lay-filter');
    form.render(type, filter);
  };

  // 定向渲染表单
  Class.prototype.renderFormByElem = function(elem){
    layui.each(['input', 'select'], function(i, formType){
      form.render(elem.find(formType));
    })
  };

  // 同步全选按钮状态
  Class.prototype.syncCheckAll = function(){
    var that = this;
    var options = that.config;
    var checkAllElem = that.layHeader.find('input[name="layTableCheckbox"]');
    var syncColsCheck = function(checked){
      that.eachCols(function(i, item){
        if(item.type === 'checkbox'){
          item[options.checkName] = checked;
        }
      });
      return checked;
    };
    var checkStatus = table.checkStatus(that.key);

    if(!checkAllElem[0]) return;

    // 选中状态
    syncColsCheck(checkStatus.isAll);
    checkAllElem.prop({
      checked: checkStatus.isAll,
      indeterminate: !checkStatus.isAll && checkStatus.data.length // 半选
    });
    form.render(checkAllElem);
  };

  // 标记当前活动行背景色
  Class.prototype.setRowActive = function(index, className, removeClass){
    var that = this;
    var options = that.config;
    var tr = that.layBody.find('tr[data-index="'+ index +'"]');
    className = className || 'layui-table-click';

    if(removeClass) return tr.removeClass(className);

    tr.addClass(className);
    tr.siblings('tr').removeClass(className);
  };

  // 设置行选中状态
  Class.prototype.setRowChecked = function(opts){
    var that = this;
    var options = that.config;
    var isCheckAll = opts.index === 'all'; // 是否操作全部
    var isCheckMult = layui.type(opts.index) === 'array'; // 是否操作多个

    // 匹配行元素
    var tr = function(tr) {
      return isCheckAll ? tr : tr.filter(isCheckMult ? function() {
        var dataIndex = $(this).data('index');
        return opts.index.indexOf(dataIndex) !== -1;
      } : '[data-index="'+ opts.index +'"]');
    }(that.layBody.find('tr'));

    // 默认属性
    opts = $.extend({
      type: 'checkbox' // 选中方式
    }, opts);

    // 同步数据选中属性值
    var thisData = table.cache[that.key];
    var existChecked = 'checked' in opts;

    // 若为单选框，则单向选中；若为复选框，则切换选中。
    var getChecked = function(value){
      return opts.type === 'radio' ? true : (existChecked ? opts.checked : !value)
    };

    // 设置选中状态
    layui.each(thisData, function(i, item){
      // 绕过空项和禁用项
      if(layui.type(item) === 'array' || item[options.disabledName]) return;

      // 匹配条件
      var matched = isCheckAll || (
        isCheckMult ? opts.index.indexOf(i) !== -1 : Number(opts.index) === i
      );

      // 设置匹配项的选中值
      if(matched){
        // 标记数据选中状态
        var checked = item[options.checkName] = getChecked(item[options.checkName]);

        // 标记当前行背景色
        var currTr = tr.filter('[data-index="'+ i +'"]');
        currTr[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED);

        // 若为 radio 类型，则取消其他行选中背景色
        if(opts.type === 'radio'){
          currTr.siblings().removeClass(ELEM_CHECKED);
        }
      } else if(opts.type === 'radio') {
        delete item[options.checkName];
      }
    });

    // 若存在复选框或单选框，则标注选中状态样式
    var checkedElem = tr.find('input[lay-type="'+ ({
      radio: 'layTableRadio',
      checkbox: 'layTableCheckbox'
    }[opts.type] || 'checkbox') +'"]:not(:disabled)');
    var checkedSameElem = checkedElem.last();
    var fixRElem = checkedSameElem.closest(ELEM_FIXR);

    ( opts.type === 'radio' && fixRElem.hasClass(HIDE)
      ?  checkedElem.first()
    : checkedElem ).prop('checked', getChecked(checkedSameElem.prop('checked')));

    that.syncCheckAll();
    that.renderForm(opts.type);
  };

  // 数据排序
  Class.prototype.sort = function(opts){ // field, type, pull, fromEvent
    var that = this;
    var field;
    var res = {};
    var options = that.config;
    var filter = options.elem.attr('lay-filter');
    var data = table.cache[that.key], thisData;

    opts = opts || {};

    // 字段匹配
    if(typeof opts.field === 'string'){
      field = opts.field;
      that.layHeader.find('th').each(function(i, item){
        var othis = $(this);
        var _field = othis.data('field');
        if(_field === opts.field){
          opts.field = othis;
          field = _field;
          return false;
        }
      });
    }

    try {
      field = field || opts.field.data('field');
      var key = opts.field.data('key');

      // 如果欲执行的排序已在状态中，则不执行渲染
      if(that.sortKey && !opts.pull){
        if(field === that.sortKey.field && opts.type === that.sortKey.sort){
          return;
        }
      }

      var elemSort = that.layHeader.find('th .laytable-cell-'+ key).find(ELEM_SORT);
      that.layHeader.find('th').find(ELEM_SORT).removeAttr('lay-sort'); // 清除其它标题排序状态
      elemSort.attr('lay-sort', opts.type || null);
      that.layFixed.find('th')
    } catch(e){
      hint.error('Table modules: sort field \''+ field +'\' not matched');
    }

    // 记录排序索引和类型
    that.sortKey = {
      field: field,
      sort: opts.type
    };

    // 默认为前端自动排序。如果否，则需自主排序（通常为服务端处理好排序）
    if(options.autoSort){
      if(opts.type === 'asc'){ //升序
        thisData = layui.sort(data, field, null, true);
      } else if(opts.type === 'desc'){ //降序
        thisData = layui.sort(data, field, true, true);
      } else { // 清除排序
        thisData = layui.sort(data, table.config.indexName, null, true);
        delete that.sortKey;
        delete options.initSort;
      }
    }

    res[options.response.dataName] = thisData || data;

    // 重载数据
    that.renderData({
      res: res,
      curr: that.page,
      count: that.count,
      sort: true,
      type: opts.reloadType
    });

    // 排序是否来自于点击表头事件触发
    if(opts.fromEvent){
      options.initSort = {
        field: field,
        type: opts.type
      };
      layui.event.call(opts.field, MOD_NAME, 'sort('+ filter +')', $.extend({
        config: options
      }, options.initSort));
    }
  };

  // 请求 loading
  Class.prototype.loading = function(hide){
    var that = this;
    var options = that.config;
    if(options.loading){
      if(hide){
        that.layInit && that.layInit.remove();
        delete that.layInit;
        that.layBox.find(ELEM_INIT).remove();
      } else {
        that.layInit = $(['<div class="layui-table-init">',
          '<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>',
          '</div>'].join(''));
        that.layBox.append(that.layInit);
      }
    }
  };

  // 获取对应单元格的 cssRules
  Class.prototype.cssRules = function(key, callback){
    var that = this;
    var style = that.elem.children('style')[0];

    lay.getStyleRules(style, function(item){
      if (item.selectorText === ('.laytable-cell-'+ key)) {
        callback(item);
        return true;
      }
    });
  };

  // 让表格铺满
  Class.prototype.fullSize = function(){
    var that = this;
    var options = that.config;
    var height = options.height;
    var bodyHeight;
    var MIN_HEIGHT = 135;

    if(that.fullHeightGap){
      height = _WIN.height() - that.fullHeightGap;
      if(height < MIN_HEIGHT) height = MIN_HEIGHT;
      // that.elem.css('height', height);
    } else if (that.parentDiv && that.parentHeightGap) {
      height = $(that.parentDiv).height() - that.parentHeightGap;
      if(height < MIN_HEIGHT) height = MIN_HEIGHT;
      // that.elem.css("height", height);
    } else if (that.customHeightFunc) {
      height = that.customHeightFunc();
      if(height < MIN_HEIGHT) height = MIN_HEIGHT;
    }

    // 如果多级表头，则填补表头高度
    if(options.cols.length > 1){
      // 补全高度
      var th = that.layFixed.find(ELEM_HEADER).find('th');
      // 固定列表头同步跟本体 th 一致高度
      var headerMain = that.layHeader.first();
      layui.each(th, function (thIndex, thElem) {
        thElem = $(thElem);
        thElem.height(headerMain.find('th[data-key="' + thElem.attr('data-key') + '"]').height() + 'px');
      })
    }

    if(!height) return;

    // 减去列头区域的高度 --- 此处的数字常量是为了防止容器处在隐藏区域无法获得高度的问题，只对默认尺寸表格做支持
    bodyHeight = parseFloat(height) - (that.layHeader.outerHeight() || 39)

    // 减去工具栏的高度
    if(options.toolbar){
      bodyHeight -= (that.layTool.outerHeight() || 51);
    }

    // 减去统计栏的高度
    if(options.totalRow){
      bodyHeight -= (that.layTotal.outerHeight() || 40);
    }

    // 减去分页栏的高度
    if(options.page || options.pagebar){
      bodyHeight -= (that.layPage.outerHeight() || 43);
    }

    if (options.maxHeight) {
      layui.each({elem: height, layMain: bodyHeight}, function (elemName, elemHeight) {
        that[elemName].css({
          height: 'auto',
          maxHeight: elemHeight + 'px'
        });
      });
    } else {
      that.layMain.outerHeight(bodyHeight);
    }
  };

  //获取滚动条宽度
  Class.prototype.getScrollWidth = function(elem){
    var width;
    if(elem){
      width = elem.offsetWidth - elem.clientWidth;
    } else {
      elem = document.createElement('div');
      elem.style.width = '100px';
      elem.style.height = '100px';
      elem.style.overflowY = 'scroll';

      document.body.appendChild(elem);
      width = elem.offsetWidth - elem.clientWidth;
      document.body.removeChild(elem);
    }
    return width;
  };

  // 滚动条补丁
  Class.prototype.scrollPatch = function(){
    var that = this;
    var layMainTable = that.layMain.children('table');
    var scrollWidth = that.layMain.width() - that.layMain.prop('clientWidth'); // 纵向滚动条宽度
    var scrollHeight = that.layMain.height() - that.layMain.prop('clientHeight'); // 横向滚动条高度
    var getScrollWidth = that.getScrollWidth(that.layMain[0]); // 获取主容器滚动条宽度，如果有的话
    var outWidth = layMainTable.outerWidth() - that.layMain.width(); // 表格内容器的超出宽度

    // 添加补丁
    var addPatch = function(elem){
      if(scrollWidth && scrollHeight){
        elem = elem.eq(0);
        if(!elem.find('.layui-table-patch')[0]){
          var patchElem = $('<th class="layui-table-patch"><div class="layui-table-cell"></div></th>'); // 补丁元素
          patchElem.find('div').css({
            width: scrollWidth
          });
          elem.find('tr').append(patchElem);
        }
      } else {
        elem.find('.layui-table-patch').remove();
      }
    };

    addPatch(that.layHeader);
    addPatch(that.layTotal);

    // 固定列区域高度
    var mainHeight = that.layMain.height();
    var fixHeight = mainHeight - scrollHeight;

    that.layFixed.find(ELEM_BODY).css(
      'height',
      layMainTable.height() >= fixHeight ? fixHeight : 'auto'
    ).scrollTop(that.layMain.scrollTop()); // 固定列滚动条高度

    // 表格宽度小于容器宽度时，隐藏固定列
    that.layFixRight[
      (table.cache[that.key] && table.cache[that.key].length) && outWidth > 0
        ? 'removeClass'
      : 'addClass'
    ](HIDE);

    // 操作栏
    that.layFixRight.css('right', scrollWidth - 1);
  };

  /**
   * @typedef updateRowOptions
   * @prop {number} index - 行索引
   * @prop {Object.<string, any>} data - 行数据
   * @prop {boolean | ((field, index) => boolean)} [related] - 更新其他包含自定义模板且可能有所关联的列视图
   */
  /**
   * 更新指定行
   * @param {updateRowOptions | updateRowOptions[]} opts 
   * @param {(field: string, value: any) => void} [callback] - 更新每个字段时的回调函数
   */
  Class.prototype.updateRow = function(opts, callback){
    var that = this;
    var ELEM_CELL = '.layui-table-cell';
    var opts = layui.type(opts) === 'array' ? opts : [opts];
    var dataCache = table.cache[that.key] || [];

    var update = function(opt){
      var index = opt.index;
      var row = opt.data;
      var related = opt.related;

      var data = dataCache[index] || {};
      var tr = that.layBody.find('tr[data-index="' + index + '"]');

      // 更新缓存中的数据
      layui.each(row, function (key, value) {
        data[key] = value;
        callback && callback(key, value);
      });

      // 更新单元格
      that.eachCols(function (i, item3) {
        var field = String(item3.field || i);
        var shouldUpdate = field in row || ((typeof related === 'function' ? related(field, i) : related) && (item3.templet || item3.toolbar));
        if(shouldUpdate){
          var td = tr.children('td[data-field="' + field + '"]');
          var cell = td.children(ELEM_CELL);
          var content = data[item3.field];
          cell.html(parseTempData.call(that, {
            item3: item3,
            content: content,
            tplData: $.extend({
              LAY_COL: item3,
            }, data)
          }));
          td.data("content", content);
          that.renderFormByElem(cell);
        }
      });
    }

    layui.each(opts, function(i, opt){
      update(opt);
    });
  };

  /**
   * 更新指定行
   * @param {string} id - table ID
   * @param {updateRowOptions | updateRowOptions[]} options 
   */
  table.updateRow = function (id, options){
    var that = getThisTable(id);
    return that.updateRow(options);
  }

  // 事件处理
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;

    var filter = options.elem.attr('lay-filter');
    var th = that.layHeader.find('th');
    var ELEM_CELL = '.layui-table-cell';

    var _BODY = $('body');
    var dict = {};

    // 头部工具栏操作事件
    that.layTool.on('click', '*[lay-event]', function(e){
      var othis = $(this);
      var events = othis.attr('lay-event');
      var data = table.cache[options.id];
      var openPanel = function(sets){
        var list = $(sets.list);
        var panel = $('<ul class="' + ELEM_TOOL_PANEL + '"></ul>');

        panel.html(list);

        // 限制最大高度
        if(options.height){
          panel.css('max-height', options.height - (that.layTool.outerHeight() || 50));
        }

        // 插入元素
        othis.find('.' + ELEM_TOOL_PANEL)[0] || othis.append(panel);
        that.renderForm();

        panel.on('click', function(e){
          layui.stope(e);
        });

        sets.done && sets.done(panel, list)
      };

      layui.stope(e);
      _DOC.trigger('table.tool.panel.remove');
      layer.close(that.tipsIndex);

      switch(events){
        case 'LAYTABLE_COLS': // 筛选列
          openPanel({
            list: function(){
              var lis = [];
              that.eachCols(function(i, item){
                if(item.field && item.type == 'normal'){
                  lis.push('<li><input type="checkbox" name="'+ item.field +'" data-key="'+ item.key +'" data-parentkey="'+ (item.parentKey||'') +'" lay-skin="primary" '+ (item.hide ? '' : 'checked') +' title="'+ util.escape($('<div>' + (item.fieldTitle || item.title || item.field) + '</div>').text()) +'" lay-filter="LAY_TABLE_TOOL_COLS"></li>');
                }
              });
              return lis.join('');
            }()
            ,done: function(){
              form.on('checkbox(LAY_TABLE_TOOL_COLS)', function(obj){
                var othis = $(obj.elem);
                var checked = this.checked;
                var key = othis.data('key');
                var col = that.col(key);
                var hide = col.hide;
                var parentKey = othis.data('parentkey');

                if(!col.key) return;

                // 同步勾选列的 hide 值和隐藏样式
                col.hide = !checked;
                that.elem.find('*[data-key="'+ key +'"]')[
                  checked ? 'removeClass' : 'addClass'
                ](HIDE);

                // 根据列的显示隐藏，同步多级表头的父级相关属性值
                if(hide != col.hide){
                  that.setParentCol(!checked, parentKey);
                }

                // 重新适配尺寸
                that.resize();

                // 列筛选（显示或隐藏）后的事件
                layui.event.call(this, MOD_NAME, 'colToggled('+ filter +')', {
                  col: col,
                  config: options
                });
              });
            }
          });
        break;
        case 'LAYTABLE_EXPORT': // 导出
          if (!data.length) return layer.tips('当前表格无数据', this, {tips: 3});
          if(device.ie){
            layer.tips('导出功能不支持 IE，请用 Chrome 等高级浏览器导出', this, {
              tips: 3
            });
          } else {
            openPanel({
              list: function(){
                return [
                  '<li data-type="csv">导出 csv 格式文件</li>',
                  '<li data-type="xls">导出 xls 格式文件</li>'
                ].join('')
              }(),
              done: function(panel, list){
                list.on('click', function(){
                  var type = $(this).data('type')
                  table.exportFile.call(that, options.id, null, type);
                });
              }
            });
          }
        break;
        case 'LAYTABLE_PRINT': // 打印
          if (!data.length) return layer.tips('当前表格无数据', this, {tips: 3});
          var printWin = window.open('about:blank', '_blank');
          var style = ['<style>',
            'body{font-size: 12px; color: #5F5F5F;}',
            'table{width: 100%; border-collapse: collapse; border-spacing: 0;}',
            'th,td{line-height: 20px; padding: 9px 15px; border: 1px solid #ccc; text-align: left; font-size: 12px; color: #5F5F5F;}',
            'a{color: #5F5F5F; text-decoration:none;}',
            'img{max-height: 100%;}',
            '*.layui-hide{display: none}',
          '</style>'].join('')
          var html = $(that.layHeader.html()); // 输出表头

          html.append(that.layMain.find('table').html()); // 输出表体
          html.append(that.layTotal.find('table').html()) // 输出合计行

          html.find('th.layui-table-patch').remove(); // 移除补丁
          // 移除表头特殊列
          html.find('thead>tr>th.'+ ELEM_COL_SPECIAL).filter(function(i, thElem){
            return !$(thElem).children('.'+ ELEM_GROUP).length; // 父级表头除外
          }).remove();
          html.find('tbody>tr>td.'+ ELEM_COL_SPECIAL).remove(); // 移除表体特殊列

          printWin.document.write(style + html.prop('outerHTML'));
          printWin.document.close();

          if(layui.device('edg').edg){
            printWin.onafterprint = printWin.close;
            printWin.print();
          }else{
            printWin.print();
            printWin.close();
          }
        break;
      }

      layui.event.call(this, MOD_NAME, 'toolbar('+ filter +')', $.extend({
        event: events,
        config: options
      },{}));
    });

    // 表头自定义元素事件
    that.layHeader.on('click', '*[lay-event]', function(e){
      var othis = $(this);
      var events = othis.attr('lay-event');
      var th = othis.closest('th');
      var key = th.data('key');
      var col = that.col(key);

      layui.event.call(this, MOD_NAME, 'colTool('+ filter +')', $.extend({
        event: events,
        config: options,
        col: col
      },{}));
    });

    // 分页栏操作事件
    that.layPagebar.on('click', '*[lay-event]', function(e){
      var othis = $(this);
      var events = othis.attr('lay-event');

      layui.event.call(this, MOD_NAME, 'pagebar('+ filter +')', $.extend({
        event: events,
        config: options
      },{}));
    });

    // 拖拽调整宽度
    th.on('mousemove', function(e){
      var othis = $(this);
      var oLeft = othis.offset().left;
      var pLeft = e.clientX - oLeft;
      if(othis.data('unresize') || thisTable.eventMoveElem){
        return;
      }
      dict.allowResize = othis.width() - pLeft <= 10; //是否处于拖拽允许区域
      _BODY.css('cursor', (dict.allowResize ? 'col-resize' : ''));
    }).on('mouseleave', function(){
      var othis = $(this);
      if(thisTable.eventMoveElem) return;
      dict.allowResize = false;
      _BODY.css('cursor', '');
    }).on('mousedown', function(e){
      var othis = $(this);
      if(dict.allowResize){
        var key = othis.data('key');
        e.preventDefault();
        dict.offset = [e.clientX, e.clientY]; //记录初始坐标

        that.cssRules(key, function(item){
          var width = item.style.width || othis.outerWidth();
          dict.rule = item;
          dict.ruleWidth = parseFloat(width);
          dict.minWidth = othis.data('minwidth') || options.cellMinWidth;
          dict.maxWidth = othis.data('maxwidth') || options.cellMaxWidth;
        });

        // 临时记录当前拖拽信息
        othis.data(DATA_MOVE_NAME, dict);
        thisTable.eventMoveElem = othis;
      }
    });

    // 拖拽中
    if(!thisTable.docEvent){
      _DOC.on('mousemove', function(e){
        if(thisTable.eventMoveElem){
          var dict = thisTable.eventMoveElem.data(DATA_MOVE_NAME) || {};

          thisTable.eventMoveElem.data('resizing', 1);
          e.preventDefault();

          if(dict.rule){
            var setWidth = dict.ruleWidth + e.clientX - dict.offset[0];
            var id = thisTable.eventMoveElem.closest('.' + ELEM_VIEW).attr('lay-id');
            var thatTable = getThisTable(id);

            if(!thatTable) return;

            if(setWidth < dict.minWidth) setWidth = dict.minWidth;
            if(setWidth > dict.maxWidth) setWidth = dict.maxWidth;

            dict.rule.style.width = setWidth + 'px';
            thatTable.setGroupWidth(thisTable.eventMoveElem);
            layer.close(that.tipsIndex);
          }
        }
      }).on('mouseup', function(e){
        if(thisTable.eventMoveElem){
          var th = thisTable.eventMoveElem; // 当前触发拖拽的 th 元素
          var id = th.closest('.' + ELEM_VIEW).attr('lay-id');
          var thatTable = getThisTable(id);

          if(!thatTable) return;

          var key = th.data('key');
          var col = thatTable.col(key);
          var filter = thatTable.config.elem.attr('lay-filter');

          // 重置过度信息
          dict = {};
          _BODY.css('cursor', '');
          thatTable.scrollPatch();

          // 清除当前拖拽信息
          th.removeData(DATA_MOVE_NAME);
          delete thisTable.eventMoveElem;

          // 列拖拽宽度后的事件
          thatTable.cssRules(key, function(item){
            col.width = parseFloat(item.style.width);
            layui.event.call(th[0], MOD_NAME, 'colResized('+ filter +')', {
              col: col,
              config: thatTable.config
            });
          });
        }
      });
    }

    // 已给 document 执行全局事件，避免重复绑定
    thisTable.docEvent = true;


    // 排序
    th.on('click', function(e){
      var othis = $(this);
      var elemSort = othis.find(ELEM_SORT);
      var nowType = elemSort.attr('lay-sort');
      var type;

      // 排序不触发的条件
      if(!elemSort[0] || othis.data('resizing') === 1){
        return othis.removeData('resizing');
      }

      if(nowType === 'asc'){
        type = 'desc';
      } else if(nowType === 'desc'){
        type = null;
      } else {
        type = 'asc';
      }
      that.sort({
        field: othis,
        type: type,
        fromEvent: true
      });
    }).find(ELEM_SORT+' .layui-edge ').on('click', function(e){
      var othis = $(this);
      var index = othis.index();
      var field = othis.parents('th').eq(0).data('field');
      layui.stope(e);
      if(index === 0){
        that.sort({
          field: field,
          type: 'asc',
          fromEvent: true
        });
      } else {
        that.sort({
          field: field,
          type: 'desc',
          fromEvent: true
        });
      }
    });

    //数据行中的事件返回的公共对象成员
    var commonMember = that.commonMember = function(sets){
      var othis = $(this);
      var index = othis.parents('tr').eq(0).data('index');
      var tr = that.layBody.find('tr[data-index="'+ index +'"]');
      var data = table.cache[that.key] || [];

      data = data[index] || {};

      // 事件返回的公共成员
      var obj = {
        tr: tr, // 行元素
        config: options,
        data: table.clearCacheKey(data), // 当前行数据
        dataCache: data, // 当前行缓存中的数据
        index: index,
        del: function(){ // 删除行数据
          table.cache[that.key][index] = [];
          tr.remove();
          that.scrollPatch();
        },
        update: function(fields, related){ // 修改行数据
          fields = fields || {};
          that.updateRow({
            index: index,
            data: fields,
            related: related
          }, function(key, value){
            obj.data[key] = value;
          });
        },
        // 设置行选中状态
        setRowChecked: function(opts){
          that.setRowChecked($.extend({
            index: index
          }, opts));
        }
        // 获取当前列
      };

      return $.extend(obj, sets);
    };

    // 复选框选择（替代元素的 click 事件）
    that.elem.on('click', 'input[name="layTableCheckbox"]+', function(e){
      var othis = $(this);
      var td = othis.closest('td');
      var checkbox = othis.prev();
      var children = that.layBody.find('input[name="layTableCheckbox"]');
      var index = checkbox.parents('tr').eq(0).data('index');
      var checked = checkbox[0].checked;
      var isAll = checkbox.attr('lay-filter') === 'layTableAllChoose';

      if(checkbox[0].disabled) return;

      // 全选
      if(isAll){
        that.setRowChecked({
          index: 'all',
          checked: checked
        });
      } else {
        that.setRowChecked({
          index: index,
          checked: checked
        });
        layui.stope(e);
      }

      // 事件
      layui.event.call(
        checkbox[0],
        MOD_NAME, 'checkbox('+ filter +')',
        commonMember.call(checkbox[0], {
          checked: checked,
          type: isAll ? 'all' : 'one',
          getCol: function(){ // 获取当前列的表头配置信息
            return that.col(td.data('key'));
          }
        })
      );
    });

    // 单选框选择
    that.elem.on('click', 'input[lay-type="layTableRadio"]+', function(e){
      var othis = $(this);
      var td = othis.closest('td');
      var radio = othis.prev();
      var checked = radio[0].checked;
      var index = radio.parents('tr').eq(0).data('index');

      layui.stope(e);
      if(radio[0].disabled) return false;

      // 标注选中样式
      that.setRowChecked({
        type: 'radio',
        index: index
      });

      // 事件
      layui.event.call(
        radio[0],
        MOD_NAME, 'radio('+ filter +')',
        commonMember.call(radio[0], {
          checked: checked,
          getCol: function(){ // 获取当前列的表头配置信息
            return that.col(td.data('key'));
          }
        })
      );
    });

    // 行事件
    that.layBody.on('mouseenter', 'tr', function(){ // 鼠标移入行
      var othis = $(this);
      var index = othis.index();
      if(othis.data('off')) return; // 不触发事件
      that.layBody.find('tr:eq('+ index +')').addClass(ELEM_HOVER)
    }).on('mouseleave', 'tr', function(){ // 鼠标移出行
      var othis = $(this);
      var index = othis.index();
      if(othis.data('off')) return; // 不触发事件
      that.layBody.find('tr:eq('+ index +')').removeClass(ELEM_HOVER)
    }).on('click', 'tr', function(e){ // 单击行
      // 不支持行单击事件的元素
      var UNROW = [
        '.layui-form-checkbox',
        '.layui-form-switch',
        '.layui-form-radio',
        '[lay-unrow]'
      ].join(',');
      if( $(e.target).is(UNROW) || $(e.target).closest(UNROW)[0]){
        return;
      }
      setRowEvent.call(this, 'row');
    }).on('dblclick', 'tr', function(){ // 双击行
      setRowEvent.call(this, 'rowDouble');
    }).on('contextmenu', 'tr', function(e){ // 菜单
      if (!options.defaultContextmenu) e.preventDefault();
      setRowEvent.call(this, 'rowContextmenu');
    });

    // 创建行单击、双击、菜单事件
    var setRowEvent = function(eventType){
      var othis = $(this);
      if(othis.data('off')) return; //不触发事件
      layui.event.call(this,
        MOD_NAME, eventType + '('+ filter +')',
        commonMember.call(othis.children('td')[0])
      );
    };

    // 渲染单元格编辑状态
    var renderGridEdit = function(othis, e){
      othis = $(othis);

      if(othis.data('off')) return; // 不触发事件

      var field = othis.data('field');
      var key = othis.data('key');
      var col = that.col(key);
      var index = othis.closest('tr').data('index');
      var data = table.cache[that.key][index];
      var elemCell = othis.children(ELEM_CELL);

      // 是否开启编辑
      // 若 edit 传入函数，则根据函数的返回结果判断是否开启编辑
      var editType = typeof col.edit === 'function'
        ? col.edit(data)
      : col.edit;

      // 显示编辑表单
      if(editType){
        var input = $(function(){
          var inputElem = '<input class="layui-input '+ ELEM_EDIT +'" lay-unrow>';
          if(editType === 'textarea') {
            inputElem = '<textarea class="layui-input ' + ELEM_EDIT + '" lay-unrow></textarea>';
          }
          return inputElem;
        }());
        input[0].value = function(val) {
          return (val === undefined || val === null) ? '' : val;
        }(othis.data('content') || data[field]);
        othis.find('.'+ELEM_EDIT)[0] || othis.append(input);
        input.focus();
        e && layui.stope(e);
      }
    };

    // 单元格编辑 - 输入框内容被改变的事件
    that.layBody.on('change', '.'+ ELEM_EDIT, function(){
      var othis = $(this);
      var td = othis.parent();
      var value = this.value;
      var field = othis.parent().data('field');
      var index = othis.closest('tr').data('index');
      var data = table.cache[that.key][index];

      //事件回调的参数对象
      var params = commonMember.call(td[0], {
        value: value,
        field: field,
        oldValue: data[field], // 编辑前的值
        td: td,
        reedit: function(){ // 重新编辑
          setTimeout(function(){
            // 重新渲染为编辑状态
            renderGridEdit(params.td);

            // 将字段缓存的值恢复到编辑之前的值
            var obj = {};
            obj[field] = params.oldValue;
            params.update(obj);
          });
        },
        getCol: function(){ // 获取当前列的表头配置信息
          return that.col(td.data('key'));
        }
      });

      // 更新缓存中的值
      var obj = {}; //变更的键值
      obj[field] = value;
      params.update(obj);

      // 执行 API 编辑事件
      layui.event.call(td[0], MOD_NAME, 'edit('+ filter +')', params);
    }).on('blur', '.'+ ELEM_EDIT, function(){ // 单元格编辑 - 恢复非编辑状态事件
      $(this).remove(); // 移除编辑状态
    });

    // 表格主体单元格触发编辑的事件
    that.layBody.on(options.editTrigger, 'td', function(e){
      renderGridEdit(this, e)
    }).on('mouseenter', 'td', function(){
      showGridExpandIcon.call(this)
    }).on('mouseleave', 'td', function(){
       showGridExpandIcon.call(this, 'hide');
    });

    // 表格合计栏单元格 hover 显示展开图标
    that.layTotal.on('mouseenter', 'td', function(){
      showGridExpandIcon.call(this)
    }).on('mouseleave', 'td', function(){
       showGridExpandIcon.call(this, 'hide');
    });

    // 显示单元格展开图标
    var ELEM_GRID = 'layui-table-grid';
    var ELEM_GRID_DOWN = 'layui-table-grid-down';
    var ELEM_GRID_PANEL = 'layui-table-grid-panel';
    var showGridExpandIcon = function(hide){
      var othis = $(this);
      var elemCell = othis.children(ELEM_CELL);

      if(othis.data('off')) return; // 不触发事件
      if(othis.parent().hasClass(ELEM_EXPAND)) return; // 是否已为展开状态

      if(hide){
        othis.find('.layui-table-grid-down').remove();
      } else if((
        elemCell.prop('scrollWidth') > elemCell.outerWidth() ||
        elemCell.find("br").length > 0
      ) && !options.lineStyle){
        if(elemCell.find('.'+ ELEM_GRID_DOWN)[0]) return;
        othis.append('<div class="'+ ELEM_GRID_DOWN +'"><i class="layui-icon layui-icon-down"></i></div>');
      }
    };
    // 展开单元格内容
    var gridExpand = function(e, expandedMode){
      var othis = $(this);
      var td = othis.parent();
      var key = td.data('key');
      var col = that.col(key);
      var index = td.parent().data('index');
      var elemCell = td.children(ELEM_CELL);
      var ELEM_CELL_C = 'layui-table-cell-c';
      var elemCellClose = $('<i class="layui-icon layui-icon-up '+ ELEM_CELL_C +'">');

      expandedMode = expandedMode || col.expandedMode || options.cellExpandedMode;

      // 展开风格
      if (expandedMode === 'tips') { // TIPS 展开风格
        that.tipsIndex = layer.tips([
          '<div class="layui-table-tips-main" style="margin-top: -'+ (elemCell.height() + 23) +'px;'+ function(){
            if(options.size === 'sm'){
              return 'padding: 4px 15px; font-size: 12px;';
            }
            if(options.size === 'lg'){
              return 'padding: 14px 15px;';
            }
            return '';
          }() +'">',
            elemCell.html(),
          '</div>',
          '<i class="layui-icon layui-table-tips-c layui-icon-close"></i>'
        ].join(''), elemCell[0], {
          tips: [3, ''],
          time: -1,
          anim: -1,
          maxWidth: (device.ios || device.android) ? 300 : that.elem.width()/2,
          isOutAnim: false,
          skin: 'layui-table-tips',
          success: function(layero, index){
            layero.find('.layui-table-tips-c').on('click', function(){
              layer.close(index);
            });
          }
        });
      } else { // 多行展开风格
        // 恢复其他已经展开的单元格
        that.elem.find('.'+ ELEM_CELL_C).trigger('click');

        // 设置当前单元格展开宽度
        that.cssRules(key, function(item){
          var width = item.style.width;
          var expandedWidth = col.expandedWidth || options.cellExpandedWidth;

          // 展开后的宽度不能小于当前宽度
          if(expandedWidth < parseFloat(width)) expandedWidth = parseFloat(width);

          elemCellClose.data('cell-width', width);
          item.style.width = expandedWidth + 'px';

          setTimeout(function(){
            that.scrollPatch(); // 滚动条补丁
          });
        });

        // 设置当前单元格展开样式
        that.setRowActive(index, ELEM_EXPAND);

        // 插入关闭按钮
        if(!elemCell.next('.'+ ELEM_CELL_C)[0]){
          elemCell.after(elemCellClose);
        }

        // 关闭展开状态
        elemCellClose.on('click', function(){
          var $this = $(this);
          that.setRowActive(index, [ELEM_EXPAND, ELEM_HOVER].join(' '), true); // 移除单元格展开样式
          that.cssRules(key, function(item){
            item.style.width =  $this.data('cell-width'); // 恢复单元格展开前的宽度
            setTimeout(function(){
              that.resize(); // 滚动条补丁
            });
          });
          $this.remove();
          // 重置单元格滚动条位置
          elemCell.scrollTop(0);
          elemCell.scrollLeft(0); 
        });
      }

      othis.remove();
      layui.stope(e);
    };

    // 表格主体单元格展开事件
    that.layBody.on('click', '.'+ ELEM_GRID_DOWN, function(e){
      gridExpand.call(this, e);
    });
    // 表格合计栏单元格展开事件
    that.layTotal.on('click', '.'+ ELEM_GRID_DOWN, function(e){
      gridExpand.call(this, e, 'tips'); // 强制采用 tips 风格
    });

    // 行工具条操作事件
    var toolFn = function(type){
      var othis = $(this);
      var td = othis.closest('td');
      var index = othis.parents('tr').eq(0).data('index');
      // 标记当前活动行
      that.setRowActive(index);

      // 执行事件
      layui.event.call(
        this,
        MOD_NAME,
        (type || 'tool') + '('+ filter +')',
        commonMember.call(this, {
          event: othis.attr('lay-event'),
          getCol: function(){ // 获取当前列的表头配置信息
            return that.col(td.data('key'));
          }
        })
      );
    };

     // 行工具条单击事件
    that.layBody.on('click', '*[lay-event]', function(e){
      toolFn.call(this);
      layui.stope(e);
    }).on('dblclick', '*[lay-event]', function(e){ //行工具条双击事件
      toolFn.call(this, 'toolDouble');
      layui.stope(e);
    });

    // 同步滚动条
    that.layMain.on('scroll', function(){
      var othis = $(this);
      var scrollLeft = othis.scrollLeft();
      var scrollTop = othis.scrollTop();

      that.layHeader.scrollLeft(scrollLeft);
      that.layTotal.scrollLeft(scrollLeft);
      that.layFixed.find(ELEM_BODY).scrollTop(scrollTop);

      layer.close(that.tipsIndex);
    });

    // 固定列滚轮事件 - 临时兼容方案
    that.layFixed.find(ELEM_BODY).on('mousewheel DOMMouseScroll', function(e) {
      var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
      var scrollTop = that.layMain.scrollTop();
      var step = 30;

      e.preventDefault();
      that.layMain.scrollTop(scrollTop + (delta > 0 ? -step : step));
    });
  };

  // 全局事件
  (function(){
    // 自适应尺寸
    _WIN.on('resize', function(){
      layui.each(thisTable.that, function(){
        this.resize();
      });
    });

    // 全局点击
    _DOC.on('click', function(){
      _DOC.trigger('table.remove.tool.panel');
    });

    // 工具面板移除事件
    _DOC.on('table.remove.tool.panel', function(){
      $('.' + ELEM_TOOL_PANEL).remove();
    });
  })();

  // 初始化
  table.init = function(filter, settings){
    settings = settings || {};
    var that = this;
    var inst = null;
    var elemTable = typeof filter === 'object' ? filter : (
      typeof filter === 'string'
        ? $('table[lay-filter="'+ filter +'"]')
      : $(ELEM + '[lay-data], '+ ELEM + '[lay-options]')
    );
    var errorTips = 'Table element property lay-data configuration item has a syntax error: ';

    //遍历数据表格
    elemTable.each(function(){
      var othis = $(this);
      var attrData = othis.attr('lay-data');
      var tableData = lay.options(this, {
        attr: attrData ? 'lay-data' : null,
        errorText: errorTips + (attrData || othis.attr('lay-options'))
      });

      var options = $.extend({
        elem: this
        ,cols: []
        ,data: []
        ,skin: othis.attr('lay-skin') //风格
        ,size: othis.attr('lay-size') //尺寸
        ,even: typeof othis.attr('lay-even') === 'string' //偶数行背景
      }, table.config, settings, tableData);

      filter && othis.hide();

      //获取表头数据
      othis.find('thead>tr').each(function(i){
        options.cols[i] = [];
        $(this).children().each(function(ii){
          var th = $(this);
          var attrData = th.attr('lay-data');
          var itemData = lay.options(this, {
            attr: attrData ? 'lay-data' : null,
            errorText: errorTips + (attrData || th.attr('lay-options'))
          });

          var row = $.extend({
            title: th.text()
            ,colspan: parseInt(th.attr('colspan')) || 1 //列单元格
            ,rowspan: parseInt(th.attr('rowspan')) || 1 //行单元格
          }, itemData);

          options.cols[i].push(row);
        });
      });

      //缓存静态表体数据
      var trElem = othis.find('tbody>tr');

      //执行渲染
      var tableIns = table.render(options);

      //获取表体数据
      if (trElem.length && !settings.data && !tableIns.config.url) {
        var tdIndex = 0;
        table.eachCols(tableIns.config.id, function (i3, item3) {
          trElem.each(function(i1){
            options.data[i1] = options.data[i1] || {};
            var tr = $(this);
            var field = item3.field;
            options.data[i1][field] = tr.children('td').eq(tdIndex).html();
          });
          tdIndex++;
        })

        tableIns.reloadData({
          data: options.data
        });
      }
    });

    return that;
  };

  //记录所有实例
  thisTable.that = {}; //记录所有实例对象
  thisTable.config = {}; //记录所有实例配置项

  var eachChildCols = function (index, cols, i1, item2) {
    //如果是组合列，则捕获对应的子列
    if (item2.colGroup) {
      var childIndex = 0;
      index++;
      item2.CHILD_COLS = [];
      // 找到它的子列所在cols的下标
      var i2 = i1 + (parseInt(item2.rowspan) || 1);
      layui.each(cols[i2], function (i22, item22) {
        if (item22.parentKey) { // 如果字段信息中包含了parentKey和key信息
          if (item22.parentKey === item2.key) {
            item22.PARENT_COL_INDEX = index;
            item2.CHILD_COLS.push(item22);
            eachChildCols(index, cols, i2, item22);
          }
        } else {
          // 没有key信息以colspan数量所谓判断标准
          //如果子列已经被标注为{PARENT_COL_INDEX}，或者子列累计 colspan 数等于父列定义的 colspan，则跳出当前子列循环
          if (item22.PARENT_COL_INDEX || (childIndex >= 1 && childIndex == (item2.colspan || 1))) return;
          item22.PARENT_COL_INDEX = index;
          item2.CHILD_COLS.push(item22);
          childIndex = childIndex + (parseInt(item22.colspan > 1 ? item22.colspan : 1));
          eachChildCols(index, cols, i2, item22);
        }
      });
    }
  };

  // 遍历表头
  table.eachCols = function(id, callback, cols){
    var config = thisTable.config[id] || {};
    var arrs = [], index = 0;

    cols = $.extend(true, [], cols || config.cols);

    //重新整理表头结构
    layui.each(cols, function(i1, item1){
      if (i1) return true; // 只需遍历第一层
      layui.each(item1, function(i2, item2){
        eachChildCols(index, cols, i1, item2);
        if(item2.PARENT_COL_INDEX) return; //如果是子列，则不进行追加，因为已经存储在父列中
        arrs.push(item2)
      });
    });

    //重新遍历列，如果有子列，则进入递归
    var eachArrs = function(obj){
      layui.each(obj || arrs, function(i, item){
        if(item.CHILD_COLS) return eachArrs(item.CHILD_COLS);
        typeof callback === 'function' && callback(i, item);
      });
    };

    eachArrs();
  };

  // 获取表格选中状态
  table.checkStatus = function(id){
    var nums = 0;
    var invalidNum = 0;
    var arr = [];
    var data = table.cache[id] || [];

    //计算全选个数
    layui.each(data, function(i, item){
      if(layui.type(item) === 'array' || item[table.config.disabledName]){
        invalidNum++; // 无效数据，或已删除的
        return;
      }
      if(item[table.config.checkName]){
        nums++;
        if(!item[table.config.disabledName]){
          arr.push(table.clearCacheKey(item));
        }
      }
    });
    return {
      data: arr, // 选中的数据
      isAll: data.length ? (nums === (data.length - invalidNum)) : false // 是否全选
    };
  };

  // 设置行选中状态
  table.setRowChecked = function(id, opts){
    var that = getThisTable(id);
    if(!that) return;
    that.setRowChecked(opts);
  };

  // 获取表格当前页的所有行数据
  table.getData = function(id){
    var arr = [];
    var data = table.cache[id] || [];
    layui.each(data, function(i, item){
      if(layui.type(item) === 'array'){
        return;
      }
      arr.push(table.clearCacheKey(item));
    });
    return arr;
  };

  // 重置表格尺寸结构
  table.resize = function(id){
    // 若指定表格唯一 id，则只执行该 id 对应的表格实例
    if(id){
      var config = getThisTableConfig(id); // 获取当前实例配置项
      if(!config) return;

      getThisTable(id).resize();

    } else { // 否则重置所有表格实例尺寸
      layui.each(thisTable.that, function(){
        this.resize();
      });
    }
  };

  // 表格导出
  table.exportFile = function(id, data, opts){
    data = data || table.clearCacheKey(table.cache[id]);
    opts = typeof opts === 'object' ? opts : function(){
      var obj = {};
      opts && (obj.type = opts);
      return obj;
    }();

    var type = opts.type || 'csv';
    var thatTable = thisTable.that[id];
    var config = thisTable.config[id] || {};
    var textType = ({
      csv: 'text/csv',
      xls: 'application/vnd.ms-excel'
    })[type];
    var alink = document.createElement("a");

    if(device.ie) return hint.error('IE_NOT_SUPPORT_EXPORTS');

    // 处理 treeTable 数据
    if (config.tree && config.tree.view) {
      try {
        data = $.extend(true, [], table.cache[id]);
        data = (function fn(data) {
          return data.reduce(function (acc, obj){
            var children = obj.children || [];
            delete obj.children;
            return acc.concat(obj, fn(children));
          }, []);
        })(Array.from(data));
      } catch (e) {}
    }

    alink.href = 'data:'+ textType +';charset=utf-8,\ufeff'+ encodeURIComponent(function(){
      var dataTitle = [];
      var dataMain = [];
      var dataTotal = [];
      var fieldsIsHide = {};

      // 表头和表体
      layui.each(data, function(i1, item1){
        var vals = [];
        if(typeof id === 'object'){ // 若 id 参数直接为表头数据
          layui.each(id, function(i, item){
            i1 == 0 && dataTitle.push(item || '');
          });
          layui.each(layui.isArray(item1) ? $.extend([], item1) : table.clearCacheKey(item1), function(i2, item2){
            vals.push('"'+ (item2 || '') +'"');
          });
        } else {
          table.eachCols(id, function(i3, item3){
            if(item3.ignoreExport === false || item3.field && item3.type == 'normal'){
              // 不导出隐藏列，除非设置 ignoreExport 强制导出
              if (
                (item3.hide && item3.ignoreExport !== false) ||
                item3.ignoreExport === true // 忽略导出
              ) {
                if(i1 == 0) fieldsIsHide[item3.field] = true; // 记录隐藏列
                return;
              }

              var content = item1[item3.field];
              if(content === undefined || content === null) content = '';

              i1 == 0 && dataTitle.push(item3.fieldTitle || item3.title || item3.field || '');

              // 解析内容
              content = parseTempData.call(thatTable, {
                item3: item3,
                content: content,
                tplData: item1,
                text: 'text',
                obj: {
                  td: function(field){
                    var td = thatTable.layBody.find('tr[data-index="'+ i1 +'"]>td');
                    return td.filter('[data-field="'+ field +'"]');
                  }
                }
              });

              // 异常处理
              content = content.replace(/"/g, '""'); // 避免内容存在「双引号」导致异常分隔
              // content += '\t'; // 加「水平制表符」 避免内容被转换格式
              content = '"'+ content +'"'; // 避免内容存在「逗号」导致异常分隔

              // 插入内容
              vals.push(content);
            }else if(item3.field && item3.type !== 'normal'){
              // https://gitee.com/layui/layui/issues/I8PHCR
              if(i1 == 0) fieldsIsHide[item3.field] = true;
            }
          });
        }
        dataMain.push(vals.join(','));
      });

      // 表合计
      thatTable && layui.each(thatTable.dataTotal, function(i, o){
        fieldsIsHide[o.field] || dataTotal.push('"' + (o.total || '') + '"');
      });

      return dataTitle.join(',') + '\r\n' + dataMain.join('\r\n') + '\r\n' + dataTotal.join(',');
    }());

    alink.download = (opts.title || config.title || 'table_'+ (config.index || '')) + '.' + type;
    document.body.appendChild(alink);
    alink.click();
    document.body.removeChild(alink);
  };

  // 获取表格配置信息
  table.getOptions = function (id) {
    return getThisTableConfig(id);
  }

  // 显示或隐藏列
  table.hideCol = function (id, cols) {
    var that = getThisTable(id);
    if (!that) {
      return;
    }

    if (layui.type(cols) === 'boolean') {
      // 显示全部或者隐藏全部
      that.eachCols(function (i2, item2) {
        var key = item2.key;
        var col = that.col(key);
        var parentKey = item2.parentKey;
        // 同步勾选列的 hide 值和隐藏样式
        if (col.hide != cols) {
          var hide = col.hide = cols;
          that.elem.find('*[data-key="'+ key +'"]')[
            hide ? 'addClass' : 'removeClass'
            ](HIDE);
          // 根据列的显示隐藏，同步多级表头的父级相关属性值
          that.setParentCol(hide, parentKey);
        }
      })
    } else {
      cols = layui.isArray(cols) ? cols : [cols];
      layui.each(cols, function (i1, item1) {
        that.eachCols(function (i2, item2) {
          if (item1.field === item2.field) {
            var key = item2.key;
            var col = that.col(key);
            var parentKey = item2.parentKey;
            // 同步勾选列的 hide 值和隐藏样式
            if ('hide' in item1 && col.hide != item1.hide) {
              var hide = col.hide = !!item1.hide;
              that.elem.find('*[data-key="'+ key +'"]')[
                hide ? 'addClass' : 'removeClass'
                ](HIDE);
              // 根据列的显示隐藏，同步多级表头的父级相关属性值
              that.setParentCol(hide, parentKey);
            }
          }
        })
      });
    }
    $('.' + ELEM_TOOL_PANEL).remove(); // 关闭字段筛选面板如果打开的话
    // 重新适配尺寸
    that.resize();
  }

  // 重载
  table.reload = function(id, options, deep, type){
    var config = getThisTableConfig(id); //获取当前实例配置项
    if(!config) return;

    var that = getThisTable(id);
    that.reload(options, deep, type);

    return thisTable.call(that);
  };

  // 仅重载数据
  table.reloadData = function(){
    var args = $.extend([], arguments);
    args[3] = 'reloadData';

    // 重载时，影响整个结构的参数，不适合更新的参数
    var dataParams = new RegExp('^('+ [
      'elem', 'id', 'cols', 'width', 'height', 'maxHeight',
      'toolbar', 'defaultToolbar',
      'className', 'css', 'pagebar'
    ].join('|') + ')$');

    // 过滤与数据无关的参数
    layui.each(args[1], function (key, value) {
      if(dataParams.test(key)){
        delete args[1][key];
      }
    });

    return table.reload.apply(null, args);
  };

  // 核心入口
  table.render = function(options){
    var inst = new Class(options);
    return thisTable.call(inst);
  };

  // 清除临时 Key
  table.clearCacheKey = function(data){
    data = $.extend({}, data);
    delete data[table.config.checkName];
    delete data[table.config.indexName];
    delete data[table.config.numbersName];
    delete data[table.config.disabledName];
    return data;
  };

  // 自动完成渲染
  $(function(){
    table.init();
  });

  exports(MOD_NAME, table);
});
/**
 * layui.treeTable
 * 树表组件
 */

layui.define(['table'], function (exports) {
  "use strict";

  var $ = layui.$;
  var form = layui.form;
  var table = layui.table;
  var hint = layui.hint();

  // api
  var treeTable = {
    config: {},
    // 事件
    on: table.on,
    // 遍历字段
    eachCols: table.eachCols,
    index: table.index,
    set: function (options) {
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },
    resize: table.resize,
    getOptions: table.getOptions,
    hideCol: table.hideCol,
    renderData: table.renderData
  };

  // 操作当前实例
  var thisTreeTable = function () {
    var that = this;
    var options = that.config
    var id = options.id || options.index;

    return {
      config: options,
      reload: function (options, deep) {
        that.reload.call(that, options, deep);
      },
      reloadData: function (options, deep) {
        treeTable.reloadData(id, options, deep);
      }
    }
  }

  // 获取当前实例
  var getThisTable = function (id) {
    var that = thisTreeTable.that[id];
    if (!that) hint.error(id ? ('The treeTable instance with ID \'' + id + '\' not found') : 'ID argument required');
    return that || null;
  }

  // 字符
  var MOD_NAME = 'treeTable';
  var HIDE = 'layui-hide';

  var ELEM_VIEW = '.layui-table-view';
  var ELEM_TREE = '.layui-table-tree';
  var ELEM_TOOL = '.layui-table-tool';
  var ELEM_BOX = '.layui-table-box';
  var ELEM_HEADER = '.layui-table-header';
  var ELEM_BODY = '.layui-table-body';
  var ELEM_MAIN = '.layui-table-main';
  var ELEM_FIXED = '.layui-table-fixed';
  var ELEM_FIXL = '.layui-table-fixed-l';
  var ELEM_FIXR = '.layui-table-fixed-r';
  var ELEM_CHECKED = 'layui-table-checked';

  var TABLE_TREE = 'layui-table-tree';
  var LAY_DATA_INDEX = 'LAY_DATA_INDEX';
  var LAY_DATA_INDEX_HISTORY = 'LAY_DATA_INDEX_HISTORY';
  var LAY_PARENT_INDEX = 'LAY_PARENT_INDEX';
  var LAY_CHECKBOX_HALF = 'LAY_CHECKBOX_HALF';
  var LAY_EXPAND = 'LAY_EXPAND';
  var LAY_HAS_EXPANDED = 'LAY_HAS_EXPANDED';
  var LAY_ASYNC_STATUS = 'LAY_ASYNC_STATUS';
  var LAY_CASCADE = ['all', 'parent', 'children', 'none'];

  // 构造器
  var Class = function (options) {
    var that = this;
    that.index = ++treeTable.index;
    that.config = $.extend(true, {}, that.config, treeTable.config, options);
    // 处理一些属性
    that.init();
    that.render();
  };

  var updateCache = function (id, childrenKey, data) {
    var tableCache = table.cache[id];
    layui.each(data || tableCache, function (index, item) {
      var itemDataIndex = item[LAY_DATA_INDEX] || '';
      if (itemDataIndex.indexOf('-') !== -1) {
        tableCache[itemDataIndex] = item
      }
      item[childrenKey] && updateCache(id, childrenKey, item[childrenKey]);
    })
  }

  var updateOptions = function (id, options, reload) {
    var that = getThisTable(id);
    reload === 'reloadData' || (that.status = { // 用于记录一些状态信息
      expand: {} // 折叠状态
    });
    var thatOptionsTemp = $.extend(true, {}, that.getOptions(), options);
    var treeOptions = thatOptionsTemp.tree;
    var childrenKey = treeOptions.customName.children;
    var idKey = treeOptions.customName.id;
    // 处理属性
    delete options.hasNumberCol;
    delete options.hasChecboxCol;
    delete options.hasRadioCol;
    table.eachCols(null, function (i1, item1) {
      if (item1.type === 'numbers') {
        options.hasNumberCol = true;
      } else if (item1.type === 'checkbox') {
        options.hasChecboxCol = true;
      } else if (item1.type === 'radio') {
        options.hasRadioCol = true;
      }
    }, thatOptionsTemp.cols)

    var parseData = options.parseData;
    var done = options.done;

    if (thatOptionsTemp.url) {
      // 异步加载的时候需要处理parseData进行转换
      if (!reload || (reload && parseData && !parseData.mod)) {
        options.parseData = function () {
          var parseDataThat = this;
          var args = arguments;
          var retData = args[0];
          if (layui.type(parseData) === 'function') {
            retData = parseData.apply(parseDataThat, args) || args[0];
          }
          var dataName = parseDataThat.response.dataName;
          // 处理 isSimpleData
          if (treeOptions.data.isSimpleData && !treeOptions.async.enable) { // 异步加载和 isSimpleData 不应该一起使用
            retData[dataName] = that.flatToTree(retData[dataName]);
          }
          // 处理节点状态
          updateStatus(retData[dataName], function (item) {
            item[LAY_EXPAND] = LAY_EXPAND in item ? item[LAY_EXPAND] : (item[idKey] !== undefined && that.status.expand[item[idKey]])
          }, childrenKey);

          if (parseDataThat.autoSort && parseDataThat.initSort && parseDataThat.initSort.type) {
            layui.sort(retData[dataName], parseDataThat.initSort.field, parseDataThat.initSort.type === 'desc', true)
          }

          that.initData(retData[dataName]);

          return retData;
        }
        options.parseData.mod = true
      }
    } else {
      options.data = options.data || [];
      // 处理 isSimpleData
      if (treeOptions.data.isSimpleData) {
        options.data = that.flatToTree(options.data);
      }
      that.initData(options.data);
    }

    if (!reload || (reload && done && !done.mod)) {
      options.done = function () {
        var args = arguments;
        var doneThat = this;
        var isRenderData = args[3]; // 是否是 renderData
        if (!isRenderData) {
          delete that.isExpandAll;
        }

        var tableView = this.elem.next();
        that.updateStatus(null, {
          LAY_HAS_EXPANDED: false // 去除已经打开过的状态
        });
        // 更新cache中的内容 将子节点也存到cache中
        updateCache(id, childrenKey);
        // 更新全选框的状态
        var layTableAllChooseElem = tableView.find('[name="layTableCheckbox"][lay-filter="layTableAllChoose"]');
        if (layTableAllChooseElem.length) {
          var checkStatus = treeTable.checkStatus(id);
          layTableAllChooseElem.prop({
            checked: checkStatus.isAll && checkStatus.data.length,
            indeterminate: !checkStatus.isAll && checkStatus.data.length
          })
        }
        if (!isRenderData && thatOptionsTemp.autoSort && thatOptionsTemp.initSort && thatOptionsTemp.initSort.type) {
          treeTable.sort(id);
        }

        that.renderTreeTable(tableView);

        if (layui.type(done) === 'function') {
          return done.apply(doneThat, args);
        }
      }
      options.done.mod = true;
    }
  }

  Class.prototype.init = function () {
    var that = this;
    var options = that.config;
    var cascade = options.tree.data.cascade;
    if (LAY_CASCADE.indexOf(cascade) === -1) {
      options.tree.data.cascade = 'all'; // 超出范围的都重置为全联动
    }

    // 先初始一个空的表格以便拿到对应的表格实例信息
    var tableIns = table.render($.extend({}, options, {
      data: [],
      url: '',
      done: null
    }))
    var id = tableIns.config.id;
    thisTreeTable.that[id] = that; // 记录当前实例对象
    that.tableIns = tableIns;

    updateOptions(id, options);
  }

  // 初始默认配置
  Class.prototype.config = {
    tree: {
      customName: {
        children: "children", // 节点数据中保存子节点数据的属性名称
        isParent: "isParent", // 节点数据保存节点是否为父节点的属性名称
        name: "name", // 节点数据保存节点名称的属性名称
        id: "id", // 唯一标识的属性名称
        pid: "parentId", // 父节点唯一标识的属性名称
        icon: "icon" // 图标的属性名称
      },
      view: {
        indent: 14, // 层级缩进量
        flexIconClose: '<i class="layui-icon layui-icon-triangle-r"></i>', // 关闭时候的折叠图标
        flexIconOpen: '<i class="layui-icon layui-icon-triangle-d"></i>', // 打开时候的折叠图标
        showIcon: true, // 是否显示图标(节点类型图标)
        icon: '', // 节点图标，如果设置了这个属性或者数据中有这个字段信息，不管打开还是关闭都以这个图标的值为准
        iconClose: '<i class="layui-icon layui-icon-folder"></i>', // 关闭时候的图标
        iconOpen: '<i class="layui-icon layui-icon-folder-open"></i>', // 打开时候的图标
        iconLeaf: '<i class="layui-icon layui-icon-leaf"></i>', // 叶子节点的图标
        showFlexIconIfNotParent: false, // 当节点不是父节点的时候是否显示折叠图标
        dblClickExpand: true, // 双击节点时，是否自动展开父节点的标识
        expandAllDefault: false // 默认展开所有节点
      },
      data: {
        isSimpleData: false, // 是否简单数据模式
        rootPid: null, // 根节点的父 ID 值
        cascade: 'all' // 级联方式 默认全部级联：all 可选 级联父 parent 级联子 children
      },
      async: {
        enable: false, // 是否开启异步加载模式，只有开启的时候其他参数才起作用
        url: '', // 异步加载的接口，可以根据需要设置与顶层接口不同的接口，如果相同可以不设置该参数
        type: null, // 请求的接口类型，设置可缺省同上
        contentType: null, // 提交参数的数据类型，设置可缺省同上
        headers: null, // 设置可缺省同上
        where: null, // 设置可缺省同上
        autoParam: [] // 自动参数
      },
      callback: {
        beforeExpand: null, // 展开前的回调 return false 可以阻止展开的动作
        onExpand: null // 展开之后的回调
      }
    },
  };

  Class.prototype.getOptions = function () {
    var that = this;
    if (that.tableIns) {
      return table.getOptions(that.tableIns.config.id); // 获取表格的实时配置信息
    } else {
      return that.config;
    }
  };

  function flatToTree(flatArr, idKey, pIdKey, childrenKey, rootPid) {
    idKey = idKey || 'id';
    pIdKey = pIdKey || 'parentId';
    childrenKey = childrenKey || 'children';
    // 创建一个空的 nodes 对象，用于保存所有的节点
    var nodes = {};
    // 遍历所有节点，将其加入 nodes 对象中
    var idTemp = '';
    layui.each(flatArr, function (index, item) {
      idTemp = idKey + item[idKey];
      nodes[idTemp] = $.extend({}, item);
      nodes[idTemp][childrenKey] = [];
    })
    // 遍历所有节点，将其父子关系加入 nodes 对象
    var pidTemp = '';
    layui.each(nodes, function (index, item) {
      pidTemp = idKey + item[pIdKey];
      if (pidTemp && nodes[pidTemp]) {
        nodes[pidTemp][childrenKey].push(item);
      }
    })
    // 返回顶层节点
    return Object.keys(nodes)
      .map(function(k) {
        return nodes[k];
      })
      .filter(function (item) {
        return rootPid ? item[pIdKey] === rootPid : !item[pIdKey];
      })
  }

  Class.prototype.flatToTree = function (tableData) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var customName = treeOptions.customName;
    var tableId = options.id;

    tableData = tableData || table.cache[tableId];

    return flatToTree(tableData, customName.id, customName.pid, customName.children, treeOptions.data.rootPid)
  }

  Class.prototype.treeToFlat = function (tableData, parentId, parentIndex) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var customName = treeOptions.customName;
    var childrenKey = customName.children;
    var pIdKey = customName.pid;

    var flat = [];
    layui.each(tableData, function (i1, item1) {
      var dataIndex = (parentIndex ? parentIndex + '-' : '') + i1;
      var dataNew = $.extend({}, item1);
      dataNew[pIdKey] = item1[pIdKey] || parentId;
      flat.push(dataNew);
      flat = flat.concat(that.treeToFlat(item1[childrenKey], item1[customName.id], dataIndex));
    });

    return flat;
  }

  // 通过当前行数据返回 treeNode 信息
  Class.prototype.getTreeNode = function (data) {
    var that = this;
    if (!data) {
      return hint.error('找不到节点数据');
    }
    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableId = options.id;
    var customName = treeOptions.customName;

    // 带上一些常用的方法
    return {
      data: data,
      dataIndex: data[LAY_DATA_INDEX],
      getParentNode: function () {
        return that.getNodeByIndex(data[LAY_PARENT_INDEX])
      },
    };
  }

  // 通过 index 返回节点信息
  Class.prototype.getNodeByIndex = function (index) {
    var that = this;
    var treeNodeData = that.getNodeDataByIndex(index);
    if (!treeNodeData) {
      return hint.error('找不到节点数据');
    }
    var options = that.getOptions();
    var treeOptions = options.tree;
    var customName = treeOptions.customName;
    var parentKey = customName.parent;
    var tableId = options.id;

    var treeNode = {
      data: treeNodeData,
      dataIndex: treeNodeData[LAY_DATA_INDEX],
      getParentNode: function () {
        return that.getNodeByIndex(treeNodeData[LAY_PARENT_INDEX])
      },
      update: function (data) {
        return treeTable.updateNode(tableId, index, data)
      },
      remove: function () {
        return treeTable.removeNode(tableId, index)
      },
      expand: function (opts) {
        return treeTable.expandNode(tableId, $.extend({}, opts, {
          index: index
        }))
      },
      setChecked: function (opts) {
        return treeTable.setRowChecked(tableId, $.extend({}, opts, {
          index: index
        }))
      }
    };

    treeNode.dataIndex = index;
    return treeNode;
  }

  // 通过 id 获取节点信息
  Class.prototype.getNodeById = function (id) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var customName = treeOptions.customName;
    var idKey = customName.id;

    // 通过 id 拿到数据的 dataIndex
    var dataIndex = '';
    var tableDataFlat = treeTable.getData(options.id, true);
    layui.each(tableDataFlat, function (i1, item1) {
      if (item1[idKey] === id) {
        dataIndex = item1[LAY_DATA_INDEX];
        return true;
      }
    })
    if (!dataIndex) {
      return;
    }

    // 用 index
    return that.getNodeByIndex(dataIndex);
  }

  // 通过 index 获取节点数据
  Class.prototype.getNodeDataByIndex = function (index, clone, newValue) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableId = options.id;
    var tableCache = table.cache[tableId];

    // 获取当前行中的数据
    var dataCache = tableCache[index];

    // 若非删除操作，则返回合并后的数据
    if (newValue !== 'delete' && dataCache) {
      $.extend(dataCache, newValue);
      return clone ? $.extend({}, dataCache) : dataCache;
    }

    // 删除操作
    var dataRet = tableCache;
    var indexArr = String(index).split('-');

    // if (options.url || indexArr.length > 1) tableCache = null // 只有在删除根节点的时候才需要处理

    // 根据 index 进行数据处理
    for (var i = 0, childrenKey = treeOptions.customName.children; i < indexArr.length; i++) {
      if (newValue && i === indexArr.length - 1) {
        if (newValue === 'delete') { // 删除并返回当前数据
          // 同步 cache --- 此段代码注释缘由：data 属性模式造成数据重复执行 splice (@Gitee: #I7Z0A/I82E2S)
          /*if (tableCache) {
            layui.each(tableCache, function (i1, item1) {
              if (item1[LAY_DATA_INDEX] === index) {
                tableCache.splice(i1, 1);
                return true;
              }
            })
          }*/
          return (i ? dataRet[childrenKey] : dataRet).splice(indexArr[i], 1)[0];
        } else { // 更新值
          $.extend((i ? dataRet[childrenKey] : dataRet)[indexArr[i]], newValue);
        }
      }
      dataRet = i ? dataRet[childrenKey][indexArr[i]] : dataRet[indexArr[i]];
    }
    return clone ? $.extend({}, dataRet) : dataRet;
  }

  treeTable.getNodeDataByIndex = function (id, index) {
    var that = getThisTable(id);
    if(!that) return;
    return that.getNodeDataByIndex(index, true);
  }

  // 判断是否是父节点
  var checkIsParent = function (data, isParentKey, childrenKey) {
    isParentKey = isParentKey || 'isParent';
    childrenKey = childrenKey || 'children';
    layui.each(data, function (i1, item1) {
      if (!(isParentKey in item1)) {
        item1[isParentKey] = !!(item1[childrenKey] && item1[childrenKey].length);
        checkIsParent(item1[childrenKey]);
      }
    })
  }

  Class.prototype.initData = function (data, parentIndex) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableId = options.id;

    data = data || that.getTableData();

    var customName = treeOptions.customName;
    var isParentKey = customName.isParent;
    var childrenKey = customName.children;

    layui.each(data, function (i1, item1) {
      if (!(isParentKey in item1)) {
        item1[isParentKey] = !!(item1[childrenKey] && item1[childrenKey].length);
      }
      item1[LAY_DATA_INDEX_HISTORY] = item1[LAY_DATA_INDEX];
      item1[LAY_PARENT_INDEX] = parentIndex = parentIndex || '';
      var dataIndex = item1[LAY_DATA_INDEX] = (parentIndex ? parentIndex + '-' : '') + i1;
      that.initData(item1[childrenKey] || [], dataIndex);
    });

    updateCache(tableId, childrenKey, data);

    return data;
  }

  // 与 tableId 有关带防抖的方法
  var debounceFn = (function () {
    var fn = {};
    return function (tableId, func, wait) {
      if (!fn[tableId]) {
        fn[tableId] = layui.debounce(func, wait);
      }
      return fn[tableId];
    }
  })()

  // 优化参数，添加一个 getNodeByIndex 方法 只传 表格id 和行 dataIndex 分几步优化 todo
  var expandNode = function (treeNode, expandFlag, sonSign, focus, callbackFlag) {
    // treeNode // 需要展开的节点
    var trElem = treeNode.trElem;
    var tableViewElem = treeNode.tableViewElem || trElem.closest(ELEM_VIEW);
    var tableId = treeNode.tableId || tableViewElem.attr('lay-id');
    var options = treeNode.options || table.getOptions(tableId);
    var dataIndex = treeNode.dataIndex || trElem.attr('lay-data-index'); // 可能出现多层
    var treeTableThat = getThisTable(tableId);

    var treeOptions = options.tree || {};
    var customName = treeOptions.customName || {};
    var isParentKey = customName.isParent;

    var trData = treeTableThat.getNodeDataByIndex(dataIndex);

    // 后续调优：对已经展开的节点进行展开和已经关闭的节点进行关闭应该做优化减少不必要的代码执行 todo
    var isToggle = layui.type(expandFlag) !== 'boolean';
    var trExpand = isToggle ? !trData[LAY_EXPAND] : expandFlag;
    var retValue = trData[isParentKey] ? trExpand : null;

    if (callbackFlag && trExpand != trData[LAY_EXPAND] && (!trData[LAY_ASYNC_STATUS] || trData[LAY_ASYNC_STATUS] === 'local')) {
      var beforeExpand = treeOptions.callback.beforeExpand;
      if (layui.type(beforeExpand) === 'function') {
        if (beforeExpand(tableId, trData, expandFlag) === false) {
          return retValue;
        }
      }
    }

    var trExpanded = trData[LAY_HAS_EXPANDED]; // 展开过，包括异步加载

    // 找到表格中的同类节点（需要找到lay-data-index一致的所有行）
    var trsElem = tableViewElem.find('tr[lay-data-index="' + dataIndex + '"]');
    // 处理折叠按钮图标
    var flexIconElem = trsElem.find('.layui-table-tree-flexIcon');
    flexIconElem.html(trExpand ? treeOptions.view.flexIconOpen : treeOptions.view.flexIconClose)
    trData[isParentKey] && flexIconElem.css('visibility', 'visible');
    // 处理节点图标
    treeOptions.view.showIcon && trsElem
      .find('.layui-table-tree-nodeIcon:not(.layui-table-tree-iconCustom,.layui-table-tree-iconLeaf)')
      .html(trExpand ? treeOptions.view.iconOpen : treeOptions.view.iconClose);
    trData[LAY_EXPAND] = trExpand;
    var trDataId = trData[customName.id];
    trDataId !== undefined && (treeTableThat.status.expand[trDataId] = trExpand);
    if (retValue === null) {
      return retValue;
    }

    var childNodes = trData[customName.children] || [];
    // 处理子节点展示与否
    if (trExpand) {
      // 展开
      if (trExpanded) { // 已经展开过
        if (!childNodes.length) return ;//异步如果子节点没有数据情况下双点行展开所有已展开的节点问题解决
        trsElem.nextAll(childNodes.map(function (value, index, array) {
          return 'tr[lay-data-index="' + value[LAY_DATA_INDEX] + '"]'
        }).join(',')).removeClass(HIDE);
        layui.each(childNodes, function (i1, item1) {
          if (!item1[isParentKey]) {
            return;
          }

          if (sonSign && !isToggle && !item1[LAY_EXPAND]) { // 非状态切换的情况下
            // 级联展开子节点
            expandNode({
              dataIndex: item1[LAY_DATA_INDEX],
              trElem: tableViewElem.find('tr[lay-data-index="' + item1[LAY_DATA_INDEX] + '"]').first(),
              tableViewElem: tableViewElem,
              tableId: tableId,
              options: options,
            }, expandFlag, sonSign, focus, callbackFlag);
          } else if (item1[LAY_EXPAND]) { // 初始化级联展开
            expandNode({
              dataIndex: item1[LAY_DATA_INDEX],
              trElem: tableViewElem.find('tr[lay-data-index="' + item1[LAY_DATA_INDEX] + '"]').first(),
              tableViewElem: tableViewElem,
              tableId: tableId,
              options: options,
            }, true);
          }
        });
      } else {
        var asyncSetting = treeOptions.async || {};
        var asyncUrl = asyncSetting.url || options.url;
        if (asyncSetting.enable && trData[isParentKey] && !trData[LAY_ASYNC_STATUS]) {
          trData[LAY_ASYNC_STATUS] = 'loading';
          flexIconElem.html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-loop layui-anim-rotate"></i>');

          // 异步获取子节点数据成功之后处理方法
          var asyncSuccessFn = function (data) {
            trData[LAY_ASYNC_STATUS] = 'success';
            trData[customName.children] = data;
            treeTableThat.initData(trData[customName.children], trData[LAY_DATA_INDEX])
            expandNode(treeNode, true, isToggle ? false : sonSign, focus, callbackFlag);
          }

          var format = asyncSetting.format; // 自定义数据返回方法
          if (layui.type(format) === 'function') {
            format(trData, options, asyncSuccessFn);
            return retValue;
          }

          var params = {};
          // 参数
          var data = $.extend(params, asyncSetting.where || options.where);
          var asyncAutoParam = asyncSetting.autoParam;
          layui.each(asyncAutoParam, function (index, item) {
            var itemStr = item;
            var itemArr = item.split('=');
            data[itemArr[0].trim()] = trData[(itemArr[1] || itemArr[0]).trim()]
          })

          var asyncContentType = asyncSetting.contentType || options.contentType;
          if (asyncContentType && asyncContentType.indexOf("application/json") == 0) { // 提交 json 格式
            data = JSON.stringify(data);
          }
          var asyncType = asyncSetting.method || options.method;
          var asyncDataType = asyncSetting.dataType || options.dataType;
          var asyncJsonpCallback = asyncSetting.jsonpCallback || options.jsonpCallback;
          var asyncHeaders = asyncSetting.headers || options.headers;
          var asyncParseData = asyncSetting.parseData || options.parseData;
          var asyncResponse = asyncSetting.response || options.response;

          $.ajax({
            type: asyncType || 'get',
            url: asyncUrl,
            contentType: asyncContentType,
            data: data,
            dataType: asyncDataType || 'json',
            jsonpCallback: asyncJsonpCallback,
            headers: asyncHeaders || {},
            success: function (res) {
              // 若有数据解析的回调，则获得其返回的数据
              if (typeof asyncParseData === 'function') {
                res = asyncParseData.call(options, res) || res;
              }
              // 检查数据格式是否符合规范
              if (res[asyncResponse.statusName] != asyncResponse.statusCode) {
                trData[LAY_ASYNC_STATUS] = 'error';
                // 异常处理 todo
                flexIconElem.html('<i class="layui-icon layui-icon-refresh"></i>');
                // 事件
              } else {
                // 正常返回
                asyncSuccessFn(res[asyncResponse.dataName]);
              }
            },
            error: function (e, msg) {
              trData[LAY_ASYNC_STATUS] = 'error';
              // 异常处理 todo
              typeof options.error === 'function' && options.error(e, msg);
            }
          });
          return retValue;
        }
        trExpanded = trData[LAY_HAS_EXPANDED] = true;
        if (childNodes.length) {
          // 判断是否需要排序
          if (options.initSort && (!options.url || options.autoSort)) {
            var initSort = options.initSort;
            if (initSort.type) {
              layui.sort(childNodes, initSort.field, initSort.type === 'desc', true);
            } else {
              // 恢复默认
              layui.sort(childNodes, table.config.indexName, null, true);
            }
          }
          treeTableThat.initData(trData[customName.children], trData[LAY_DATA_INDEX]);
          // 将数据通过模板得出节点的html代码
          var str2 = table.getTrHtml(tableId, childNodes, null, null, dataIndex);

          var str2Obj = {
            trs: $(str2.trs.join('')),
            trs_fixed: $(str2.trs_fixed.join('')),
            trs_fixed_r: $(str2.trs_fixed_r.join(''))
          }
          var dataLevel = dataIndex.split('-').length - 1;
          var dataLevelNew = (dataLevel || 0) + 1;
          layui.each(childNodes, function (childIndex, childItem) {
            str2Obj.trs.eq(childIndex).attr({
              'data-index': childItem[LAY_DATA_INDEX],
              'lay-data-index': childItem[LAY_DATA_INDEX],
              'data-level': dataLevelNew
            })
            str2Obj.trs_fixed.eq(childIndex).attr({
              'data-index': childItem[LAY_DATA_INDEX],
              'lay-data-index': childItem[LAY_DATA_INDEX],
              'data-level': dataLevelNew
            })
            str2Obj.trs_fixed_r.eq(childIndex).attr({
              'data-index': childItem[LAY_DATA_INDEX],
              'lay-data-index': childItem[LAY_DATA_INDEX],
              'data-level': dataLevelNew
            })
          })

          tableViewElem.find(ELEM_MAIN).find('tbody tr[lay-data-index="' + dataIndex + '"]').after(str2Obj.trs);
          tableViewElem.find(ELEM_FIXL).find('tbody tr[lay-data-index="' + dataIndex + '"]').after(str2Obj.trs_fixed);
          tableViewElem.find(ELEM_FIXR).find('tbody tr[lay-data-index="' + dataIndex + '"]').after(str2Obj.trs_fixed_r);

          // 初始化新增的节点中的内容
          treeTableThat.renderTreeTable(str2Obj.trs, dataLevelNew);

          if (sonSign && !isToggle) { // 非状态切换的情况下
            // 级联展开/关闭子节点
            layui.each(childNodes, function (i1, item1) {
              expandNode({
                dataIndex: item1[LAY_DATA_INDEX],
                trElem: tableViewElem.find('tr[lay-data-index="' + item1[LAY_DATA_INDEX] + '"]').first(),
                tableViewElem: tableViewElem,
                tableId: tableId,
                options: options,
              }, expandFlag, sonSign, focus, callbackFlag);
            })
          }
        }
      }
    } else {
      treeTableThat.isExpandAll = false;
      // 关闭
      if (sonSign && !isToggle) { // 非状态切换的情况下
        layui.each(childNodes, function (i1, item1) {
          expandNode({
            dataIndex: item1[LAY_DATA_INDEX],
            trElem: tableViewElem.find('tr[lay-data-index="' + item1[LAY_DATA_INDEX] + '"]').first(),
            tableViewElem: tableViewElem,
            tableId: tableId,
            options: options,
          }, expandFlag, sonSign, focus, callbackFlag);
        });
        tableViewElem.find(childNodes.map(function (value, index, array) { // 只隐藏直接子节点，其他由递归的处理
          return 'tr[lay-data-index="' + value[LAY_DATA_INDEX] + '"]'
        }).join(',')).addClass(HIDE);
      } else {
        var childNodesFlat = treeTableThat.treeToFlat(childNodes, trData[customName.id], dataIndex);
        tableViewElem.find(childNodesFlat.map(function (value, index, array) {
          return 'tr[lay-data-index="' + value[LAY_DATA_INDEX] + '"]'
        }).join(',')).addClass(HIDE);
      }
    }


    debounceFn('resize-' + tableId, function () {
      treeTable.resize(tableId);
    }, 0)();

    if (callbackFlag && trData[LAY_ASYNC_STATUS] !== 'loading') {
      var onExpand = treeOptions.callback.onExpand;
      layui.type(onExpand) === 'function' && onExpand(tableId, trData, trExpand);
    }

    return retValue;
  }

  /**
   * 展开或关闭一个节点
   * @param {String} id 树表id
   * @param {Object} opts
   * @param {Number|String} opts.index 展开行的数据下标
   * @param {Boolean} [opts.expandFlag] 展开、关闭、切换
   * @param {Boolean} [opts.inherit] 是否级联子节点
   * @param {Boolean} [opts.callbackFlag] 是否触发事件
   * @return [{Boolean}] 状态结果
   * */
  treeTable.expandNode = function (id, opts) {
    var that = getThisTable(id);
    if (!that) return;

    opts = opts || {};

    var index = opts.index;
    var expandFlag = opts.expandFlag;
    var sonSign = opts.inherit;
    var callbackFlag = opts.callbackFlag;

    var options = that.getOptions();
    var tableViewElem = options.elem.next();
    return expandNode({
      trElem: tableViewElem.find('tr[lay-data-index="' + index + '"]').first()
    }, expandFlag, sonSign, null, callbackFlag)
  };

  /**
   * 展开或关闭全部节点
   * @param {String} id 树表id
   * @param {Boolean} expandFlag 展开或关闭
   * */
  treeTable.expandAll = function (id, expandFlag) {
    if (layui.type(expandFlag) !== 'boolean') {
      return hint.error('expandAll 的展开状态参数只接收true/false')
    }

    var that = getThisTable(id);
    if (!that) return;

    that.isExpandAll = expandFlag;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableView = options.elem.next();
    var isParentKey = treeOptions.customName.isParent;
    var idKey = treeOptions.customName.id;
    var showFlexIconIfNotParent = treeOptions.view.showFlexIconIfNotParent;

    if (!expandFlag) {
      // 关闭所有
      // 将所有已经打开的节点的状态设置为关闭，
      that.updateStatus(null, function (d) {
        if (d[isParentKey] || showFlexIconIfNotParent) {
          d[LAY_EXPAND] = false;
          d[idKey] !== undefined && (that.status.expand[d[idKey]] = false);
        }
      }) // 只处理当前页，如果需要处理全部表格，需要用treeTable.updateStatus
      // 隐藏所有非顶层的节点
      tableView.find('.layui-table-box tbody tr[data-level!="0"]').addClass(HIDE);

      tableView.find('.layui-table-tree-flexIcon').html(treeOptions.view.flexIconClose);
      treeOptions.view.showIcon && tableView
        .find('.layui-table-tree-nodeIcon:not(.layui-table-tree-iconCustom,.layui-table-tree-iconLeaf)')
        .html(treeOptions.view.iconClose);
    } else {
      var tableDataFlat = treeTable.getData(id, true);
      // 展开所有
      // 存在异步加载
      if (treeOptions.async.enable) {
        // 判断是否有未加载过的节点
        var isAllAsyncDone = true;
        layui.each(tableDataFlat, function (i1, item1) {
          if (item1[isParentKey] && !item1[LAY_ASYNC_STATUS]) {
            isAllAsyncDone = false;
            return true;
          }
        })
        // 有未加载过的节点
        if (!isAllAsyncDone) {
          // 逐个展开
          layui.each(treeTable.getData(id), function (i1, item1) {
            treeTable.expandNode(id, {
              index: item1[LAY_DATA_INDEX],
              expandFlag: true,
              inherit: true
            })
          })
          return;
        }
      }

      // 先判断是否全部打开过了
      var isAllExpanded = true;
      layui.each(tableDataFlat, function (i1, item1) {
        if (item1[isParentKey] && !item1[LAY_HAS_EXPANDED]) {
            isAllExpanded = false;
            return true;
          }
      })
      // 如果全部节点已经都打开过，就可以简单处理跟隐藏所有节点反操作
      if (isAllExpanded) {
        that.updateStatus(null, function (d) {
          if (d[isParentKey] || showFlexIconIfNotParent) {
            d[LAY_EXPAND] = true;
            d[idKey] !== undefined && (that.status.expand[d[idKey]] = true);
          }
        });
        // 显示所有子节点
        tableView.find('tbody tr[data-level!="0"]').removeClass(HIDE);
        // 处理节点的图标
        tableView.find('.layui-table-tree-flexIcon').html(treeOptions.view.flexIconOpen);
        treeOptions.view.showIcon && tableView
          .find('.layui-table-tree-nodeIcon:not(.layui-table-tree-iconCustom,.layui-table-tree-iconLeaf)')
          .html(treeOptions.view.iconOpen);
      } else {
        // 如果有未打开过的父节点，将 tr 内容全部重新生成
        that.updateStatus(null, function (d) {
          if (d[isParentKey] || showFlexIconIfNotParent) {
            d[LAY_EXPAND] = true;
            d[LAY_HAS_EXPANDED] = true;
            d[idKey] !== undefined && (that.status.expand[d[idKey]] = true);
          }
        });
        if (options.initSort && options.initSort.type && options.autoSort) {
          return treeTable.sort(id);
        }
        var trAll = table.getTrHtml(id, tableDataFlat);

        var trAllObj = {
          trs: $(trAll.trs.join('')),
          trs_fixed: $(trAll.trs_fixed.join('')),
          trs_fixed_r: $(trAll.trs_fixed_r.join(''))
        }
        var props;
        layui.each(tableDataFlat, function (dataIndex, dataItem) {
          var dataLevel = dataItem[LAY_DATA_INDEX].split('-').length - 1;
          props = {
            'data-index': dataItem[LAY_DATA_INDEX],
            'lay-data-index': dataItem[LAY_DATA_INDEX],
            'data-level': dataLevel
          };
          trAllObj.trs.eq(dataIndex).attr(props)
          trAllObj.trs_fixed.eq(dataIndex).attr(props)
          trAllObj.trs_fixed_r.eq(dataIndex).attr(props)
        })
        layui.each(['main', 'fixed-l', 'fixed-r'], function (i, item) {
          tableView.find('.layui-table-' + item + ' tbody').html(trAllObj[['trs', 'trs_fixed', 'trs_fixed_r'][i]]);
        });
        that.renderTreeTable(tableView, 0, false);
      }
    }
    treeTable.resize(id);
  }

  Class.prototype.renderTreeTable = function (tableView, level, sonSign) {
    var that = this;
    var options = that.getOptions();
    var tableViewElem = options.elem.next();
    !tableViewElem.hasClass(TABLE_TREE) && tableViewElem.addClass(TABLE_TREE);
    var tableId = options.id;
    var treeOptions = options.tree || {};
    var treeOptionsData = treeOptions.data || {};
    var treeOptionsView = treeOptions.view || {};
    var customName = treeOptions.customName || {};
    var isParentKey = customName.isParent;
    var tableFilterId = tableViewElem.attr('lay-filter');
    var treeTableThat = that;
    // var tableData = treeTableThat.getTableData();

    level = level || 0;

    if (!level) {
      // 初始化的表格里面没有level信息，可以作为顶层节点的判断
      tableViewElem.find('.layui-table-body tr:not([data-level])').attr('data-level', level);
      layui.each(table.cache[tableId], function (dataIndex, dataItem) {
        tableViewElem.find('.layui-table-main tbody tr[data-level="0"]:eq(' + dataIndex + ')').attr('lay-data-index', dataItem[LAY_DATA_INDEX]);
        tableViewElem.find('.layui-table-fixed-l tbody tr[data-level="0"]:eq(' + dataIndex + ')').attr('lay-data-index', dataItem[LAY_DATA_INDEX]);
        tableViewElem.find('.layui-table-fixed-r tbody tr[data-level="0"]:eq(' + dataIndex + ')').attr('lay-data-index', dataItem[LAY_DATA_INDEX]);
      })
    }

    var dataExpand = null; // 记录需要展开的数据
    var nameKey = customName.name;
    var indent = treeOptionsView.indent || 14;
    layui.each(tableView.find('td[data-field="' + nameKey + '"]'), function (index, item) {
      item = $(item);
      var trElem = item.closest('tr');
      var itemCell = item.children('.layui-table-cell');
      if (itemCell.hasClass('layui-table-tree-item')) {
        return;
      }
      var trIndex = trElem.attr('lay-data-index');
      if (!trIndex) { // 排除在统计行中的节点
        return;
      }
      trElem = tableViewElem.find('tr[lay-data-index="' + trIndex + '"]');
      var trData = treeTableThat.getNodeDataByIndex(trIndex);

      if (trData[LAY_EXPAND] && trData[isParentKey]) {
        // 需要展开
        dataExpand = dataExpand || {};
        dataExpand[trIndex] = true;
      }
      if (trData[LAY_CHECKBOX_HALF]) {
        trElem.find('input[type="checkbox"][name="layTableCheckbox"]').prop('indeterminate', true);
      }

      var htmlTemp = itemCell.html();
      itemCell = trElem.find('td[data-field="' + nameKey + '"]>div.layui-table-cell');
      itemCell.addClass('layui-table-tree-item');
      var flexIconElem = itemCell
        .html(['<div class="layui-inline layui-table-tree-flexIcon" ',
          'style="',
          'margin-left: ' + (indent * trElem.attr('data-level')) + 'px;',
          (trData[isParentKey] || treeOptionsView.showFlexIconIfNotParent) ? '' : ' visibility: hidden;',
          '">',
          trData[LAY_EXPAND] ? treeOptionsView.flexIconOpen : treeOptionsView.flexIconClose, // 折叠图标
          '</div>',
          treeOptionsView.showIcon ? '<div class="layui-inline layui-table-tree-nodeIcon' +
            ((trData[customName.icon] || treeOptionsView.icon) ? ' layui-table-tree-iconCustom' : '') +
            (trData[isParentKey] ? '' : ' layui-table-tree-iconLeaf') +
            '">' +
            (trData[customName.icon] || treeOptionsView.icon ||
              (trData[isParentKey] ?
                (trData[LAY_EXPAND] ? treeOptionsView.iconOpen : treeOptionsView.iconClose) :
                treeOptionsView.iconLeaf) ||
              '') + '</div>' : '', // 区分父子节点
          htmlTemp].join('')) // 图标要可定制
        .find('.layui-table-tree-flexIcon');

      // 添加展开按钮的事件
      flexIconElem.on('click', function (event) {
        layui.stope(event);
        // 处理数据
        // var trElem = item.closest('tr');
        expandNode({trElem: trElem}, null, null, null, true);
      });
    });

    if (!level && treeOptions.view.expandAllDefault && that.isExpandAll === undefined) {
      return treeTable.expandAll(tableId, true); // 默认展开全部
    }

    // 当前层的数据看看是否需要展开
    if (sonSign !== false && dataExpand) {
      layui.each(dataExpand, function (index, item) {
        var trDefaultExpand = tableViewElem.find('tr[lay-data-index="' + index + '"]');
        trDefaultExpand.find('.layui-table-tree-flexIcon').html(treeOptionsView.flexIconOpen);
        expandNode({trElem: trDefaultExpand.first()}, true);
      });
      // #1463 expandNode 中已经展开过的节点不会重新渲染
      debounceFn('renderTreeTable2-' + tableId, function () {
        form.render($('.layui-table-tree[lay-id="' + tableId + '"]'));
      }, 0)();
    } else {
      debounceFn('renderTreeTable-' + tableId, function () {
        options.hasNumberCol && formatNumber(that);
        form.render($('.layui-table-tree[lay-id="' + tableId + '"]'));
      }, 0)();
    }
  }

  var formatNumber = function (that) {
    var options = that.getOptions();
    var tableViewElem = options.elem.next();

    var num = 0;
    var trMain = tableViewElem.find('.layui-table-main tbody tr');
    var trFixedL = tableViewElem.find('.layui-table-fixed-l tbody tr');
    var trFixedR = tableViewElem.find('.layui-table-fixed-r tbody tr');
    layui.each(that.treeToFlat(table.cache[options.id]), function (i1, item1) {
      if (item1['LAY_HIDE']) return;
      var itemData = that.getNodeDataByIndex(item1[LAY_DATA_INDEX]);
      itemData['LAY_NUM'] = ++num;
      trMain.eq(i1).find('.laytable-cell-numbers').html(num);
      trFixedL.eq(i1).find('.laytable-cell-numbers').html(num);
      trFixedR.eq(i1).find('.laytable-cell-numbers').html(num);
    })
  }

  // 树表渲染
  Class.prototype.render = function (type) {
    var that = this;
    that.tableIns = table[type === 'reloadData' ? 'reloadData' : 'reload'](that.tableIns.config.id, $.extend(true, {}, that.config));
    that.config = that.tableIns.config;
  };

  // 表格重载
  Class.prototype.reload = function (options, deep, type) {
    var that = this;

    options = options || {};
    delete that.haveInit;

    // 防止数组深度合并
    layui.each(options, function (key, item) {
      if (layui.type(item) === 'array') delete that.config[key];
    });

    // 根据需要处理options中的一些参数
    updateOptions(that.getOptions().id, options, type || true);

    // 对参数进行深度或浅扩展
    that.config = $.extend(deep, {}, that.config, options);

    // 执行渲染
    that.render(type);
  };

  // 仅重载数据
  treeTable.reloadData = function () {
    var args = $.extend(true, [], arguments);
    args[3] = 'reloadData';

    return treeTable.reload.apply(null, args);
  };

  var updateStatus = function (data, statusObj, childrenKey, notCascade) {
    var dataUpdated = [];
    layui.each(data, function (i1, item1) {
      if (layui.type(statusObj) === 'function') {
        statusObj(item1);
      } else {
        $.extend(item1, statusObj);
      }
      dataUpdated.push($.extend({}, item1));
      notCascade || (dataUpdated = dataUpdated.concat(updateStatus(item1[childrenKey], statusObj, childrenKey, notCascade)));
    });
    return dataUpdated;
  }

  Class.prototype.updateStatus = function (data, statusObj, notCascade) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    data = data || table.cache[options.id];

    return updateStatus(data, statusObj, treeOptions.customName.children, notCascade);
  }

  Class.prototype.getTableData = function () {
    var that = this;
    var options = that.getOptions();
    // return options.url ? table.cache[options.id] : options.data;
    return table.cache[options.id];
  }

  treeTable.updateStatus = function (id, statusObj, data) {
    var that = getThisTable(id);
    var options = that.getOptions();
    if (!data) {
      if (options.url) {
        data = table.cache[options.id];
      } else {
        data = options.data;
      }
    }
    return that.updateStatus(data, statusObj);
  }

  treeTable.sort = function (id) {
    var that = getThisTable(id);
    if(!that) return;

    var options = that.getOptions();
    if (options.autoSort) {
      that.initData();
      treeTable.renderData(id);
    }
  }

  // 处理事件
  var updateObjParams = function (obj) {
    var tableId = obj.config.id;
    var tableThat = getThisTable(tableId);
    var trData = obj.data = treeTable.getNodeDataByIndex(tableId, obj.index); // 克隆的
    var trIndex = trData[LAY_DATA_INDEX];
    obj.dataIndex = trIndex;

    // 处理update方法
    var updateFn = obj.update;
    obj.update = function () {
      var updateThat = this;
      var args = arguments;
      $.extend(tableThat.getNodeDataByIndex(trIndex), args[0]);
      var ret = updateFn.apply(updateThat, args); // 主要负责更新节点内容
      var nameKey = obj.config.tree.customName.name;
      nameKey in args[0] && obj.tr.find('td[data-field="' + nameKey + '"]').children('div.layui-table-cell').removeClass('layui-table-tree-item');
      tableThat.renderTreeTable(obj.tr, obj.tr.attr('data-level'), false);
      return ret;
    }

    // 处理del方法
    obj.del = function () {
      treeTable.removeNode(tableId, trData);
    }

    // 处理setRowChecked
    obj.setRowChecked = function (checked) {
      treeTable.setRowChecked(tableId, {
        index: trData,
        checked: checked
      });
    }
  }

  // 更新数据
  treeTable.updateNode = function (id, index, newNode) {
    var that = getThisTable(id);
    if(!that) return;

    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableView = options.elem.next();
    var trElem = tableView.find('tr[lay-data-index="' + index + '"]');
    var trIndex = trElem.attr('data-index');
    var trLevel = trElem.attr('data-level')

    if (!newNode) {
      return;
    }
    // 更新值
    var newNodeTemp = that.getNodeDataByIndex(index, false, newNode);
    // 获取新的tr替换
    var trNew = table.getTrHtml(id, [newNodeTemp]);
    // 重新渲染tr
    layui.each(['main', 'fixed-l', 'fixed-r'], function (i, item) {
      tableView.find('.layui-table-' + item + ' tbody tr[lay-data-index="' + index + '"]').replaceWith($(trNew[['trs', 'trs_fixed', 'trs_fixed_r'][i]].join('')).attr({
        'data-index': trIndex,
        'lay-data-index': index,
        'data-level': trLevel
      }));
    });
    that.renderTreeTable(tableView.find('tr[lay-data-index="' + index + '"]'), trLevel);
  }

  // 删除数据
  treeTable.removeNode = function (id, node) {
    var that = getThisTable(id);
    if(!that) return;

    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableView = options.elem.next();
    var delNode;
    var indexArr = [];
    delNode = that.getNodeDataByIndex(layui.type(node) === 'string' ? node : node[LAY_DATA_INDEX], false, 'delete');
    var nodeP = that.getNodeDataByIndex(delNode[LAY_PARENT_INDEX]);
    that.updateCheckStatus(nodeP);
    var delNodesFlat = that.treeToFlat([delNode], delNode[treeOptions.customName.pid], delNode[LAY_PARENT_INDEX]);
    layui.each(delNodesFlat, function (i2, item2) {
      indexArr.push('tr[lay-data-index="' + item2[LAY_DATA_INDEX] + '"]');
    })

    tableView.find(indexArr.join(',')).remove(); // 删除行
    // 重新整理数据
    var tableData = that.initData();
    // index发生变化需要更新页面tr中对应的lay-data-index 新增和删除都要注意数据结构变动之后的index问题
    layui.each(that.treeToFlat(tableData), function (i3, item3) {
      if (item3[LAY_DATA_INDEX_HISTORY] && item3[LAY_DATA_INDEX_HISTORY] !== item3[LAY_DATA_INDEX]) {
        tableView.find('tr[lay-data-index="' + item3[LAY_DATA_INDEX_HISTORY] + '"]').attr({
          'data-index': item3[LAY_DATA_INDEX],
          'lay-data-index': item3[LAY_DATA_INDEX],
        });
        // item3[LAY_DATA_INDEX_HISTORY] = item3[LAY_DATA_INDEX]
      }
    });
    // 重新更新顶层节点的data-index;
    layui.each(table.cache[id], function (i4, item4) {
      tableView.find('tr[data-level="0"][lay-data-index="' + item4[LAY_DATA_INDEX] + '"]').attr('data-index', i4);
    })
    options.hasNumberCol && formatNumber(that);

    // 重新适配尺寸
    treeTable.resize(id);
  }

  /**
   * 新增数据节点
   * @param {String} id 树表id
   * @param {Object} opts
   * @param {String|Number} opts.parentIndex 指定的父节点，如果增加根节点，请设置 parentIndex 为 null 即可
   * @param {Number} opts.index 新节点插入的位置（从 0 开始）index = -1(默认) 时，插入到最后
   * @param {Object|Array} opts.data 新增的节点，单个或者多个
   * @param {Boolean} opts.focus 新增的节点，单个或者多个
   * @return {Array} 新增的节点
   * */
  treeTable.addNodes = function (id, opts) {
    var that = getThisTable(id);
    if(!that) return;

    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableViewElem = options.elem.next();
    var checkName = table.config.checkName;

    opts = opts || {};

    var parentIndex = opts.parentIndex;
    var index = opts.index;
    var newNodes = opts.data;
    var focus = opts.focus;

    parentIndex = layui.type(parentIndex) === 'number' ? parentIndex.toString() : parentIndex;
    var parentNode = parentIndex ? that.getNodeDataByIndex(parentIndex) : null;
    index = layui.type(index) === 'number' ? index : -1;

    // 添加数据
    newNodes = $.extend(true, [], (layui.isArray(newNodes) ? newNodes : [newNodes]));

    // 若未传入 LAY_CHECKED 属性，则继承父节点的 checked 状态
    layui.each(newNodes, function(i, item){
      if(!(checkName in item) && parentNode){
        item[checkName] = parentNode[checkName];
      }
    })

    var tableData = that.getTableData(), dataAfter;
    if (!parentNode) {
      // 添加到根节点
      dataAfter = table.cache[id].splice(index === -1 ? table.cache[id].length : index);
      table.cache[id] = table.cache[id].concat(newNodes, dataAfter);
      if (!options.url) {
        // 静态data模式
        if (!options.page) {
          options.data = table.cache[id];
        } else {
          var pageOptions = options.page;
          options.data.splice.apply(options.data, [pageOptions.limit * (pageOptions.curr - 1), pageOptions.limit].concat(table.cache[id]))
        }
      }
      // 将新节点添加到页面
      tableData = that.initData();

      if (tableViewElem.find('.layui-none').length) {
        table.renderData(id);
        return newNodes;
      }

      var newNodesHtml = table.getTrHtml(id, newNodes);
      var newNodesHtmlObj = {
        trs: $(newNodesHtml.trs.join('')),
        trs_fixed: $(newNodesHtml.trs_fixed.join('')),
        trs_fixed_r: $(newNodesHtml.trs_fixed_r.join(''))
      }

      var attrs = {};
      layui.each(newNodes, function (newNodeIndex, newNodeItem) {
        attrs = {
          'data-index': newNodeItem[LAY_DATA_INDEX],
          'lay-data-index': newNodeItem[LAY_DATA_INDEX],
          'data-level': '0'
        };
        newNodesHtmlObj.trs.eq(newNodeIndex).attr(attrs)
        newNodesHtmlObj.trs_fixed.eq(newNodeIndex).attr(attrs)
        newNodesHtmlObj.trs_fixed_r.eq(newNodeIndex).attr(attrs)
      })
      var trIndexPrev = parseInt(newNodes[0][LAY_DATA_INDEX]) - 1;
      var tableViewElemMAIN = tableViewElem.find(ELEM_MAIN);
      var tableViewElemFIXL = tableViewElem.find(ELEM_FIXL);
      var tableViewElemFIXR = tableViewElem.find(ELEM_FIXR);
      if (trIndexPrev === -1) {
        // 插入到开头
        tableViewElemMAIN.find('tr[data-level="0"][data-index="0"]').before(newNodesHtmlObj.trs);
        tableViewElemFIXL.find('tr[data-level="0"][data-index="0"]').before(newNodesHtmlObj.trs_fixed);
        tableViewElemFIXR.find('tr[data-level="0"][data-index="0"]').before(newNodesHtmlObj.trs_fixed_r);
      } else {
        if (index === -1) {
          // 追加到最后
          tableViewElemMAIN.find('tbody').append(newNodesHtmlObj.trs);
          tableViewElemFIXL.find('tbody').append(newNodesHtmlObj.trs_fixed);
          tableViewElemFIXR.find('tbody').append(newNodesHtmlObj.trs_fixed_r);
        } else {
          var trIndexNext = dataAfter[0][LAY_DATA_INDEX_HISTORY];
          tableViewElemMAIN.find('tr[data-level="0"][data-index="' + trIndexNext + '"]').before(newNodesHtmlObj.trs);
          tableViewElemFIXL.find('tr[data-level="0"][data-index="' + trIndexNext + '"]').before(newNodesHtmlObj.trs_fixed);
          tableViewElemFIXR.find('tr[data-level="0"][data-index="' + trIndexNext + '"]').before(newNodesHtmlObj.trs_fixed_r);
        }

      }

      // 重新更新顶层节点的data-index;
      layui.each(table.cache[id], function (i4, item4) {
        tableViewElem.find('tr[data-level="0"][lay-data-index="' + item4[LAY_DATA_INDEX] + '"]').attr('data-index', i4);
      })

      that.renderTreeTable(tableViewElem.find(newNodes.map(function (value, index, array) {
        return 'tr[lay-data-index="' + value[LAY_DATA_INDEX] + '"]'
      }).join(',')));
    } else {
      var isParentKey = treeOptions.customName.isParent;
      var childKey = treeOptions.customName.children;

      parentNode[isParentKey] = true;
      var childrenNodes = parentNode[childKey];
      if (!childrenNodes) {
        childrenNodes = parentNode[childKey] = newNodes;
      } else {
        dataAfter = childrenNodes.splice(index === -1 ? childrenNodes.length : index);
        childrenNodes = parentNode[childKey] = childrenNodes.concat(newNodes, dataAfter);
      }
      // 删除已经存在的同级节点以及他们的子节点，并且把中间节点的已展开过的状态设置为false
      that.updateStatus(childrenNodes, function (d) {
        if (d[isParentKey] || treeOptions.view.showFlexIconIfNotParent) {
          d[LAY_HAS_EXPANDED] = false;
        }
      });
      var childrenNodesFlat = that.treeToFlat(childrenNodes);
      tableViewElem.find(childrenNodesFlat.map(function (value) {
        return 'tr[lay-data-index="' + value[LAY_DATA_INDEX] + '"]'
      }).join(',')).remove();

      tableData = that.initData();
      // 去掉父节点的已经展开过的状态，重新执行一次展开的方法
      parentNode[LAY_HAS_EXPANDED] = false;
      parentNode[LAY_ASYNC_STATUS] = 'local'; // 转为本地数据，应该规定异步加载子节点的时候addNodes的规则
      expandNode({trElem: tableViewElem.find('tr[lay-data-index="' + parentIndex + '"]')}, true)
    }
    that.updateCheckStatus(parentNode);
    treeTable.resize(id);
    if (focus) {
      // 滚动到第一个新增的节点
      tableViewElem.find(ELEM_MAIN).find('tr[lay-data-index="' + newNodes[0][LAY_DATA_INDEX] + '"]').get(0).scrollIntoViewIfNeeded();
    }

    return newNodes;
  }

  // 获取表格选中状态
  treeTable.checkStatus = function (id, includeHalfCheck) {
    var that = getThisTable(id);
    if (!that) return;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var checkName = table.config.checkName;

    // 需要区分单双选
    var tableData = treeTable.getData(id, true);
    var checkedData = tableData.filter(function (value, index, array) {
      return value[checkName] || (includeHalfCheck && value[LAY_CHECKBOX_HALF]);
    });

    var isAll = true;
    layui.each(treeOptions.data.cascade === 'all' ? table.cache[id] : treeTable.getData(id, true), function (i1, item1) {
      if (!item1[checkName]) {
        isAll = false;
        return true;
      }
    })

    return {
      data: checkedData,
      isAll: isAll
    }
  }

  // 排序之后重新渲染成树表
  treeTable.on('sort', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      treeTable.sort(tableId);
    }
  });

  // 行点击
  treeTable.on('row', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();

    if (tableView.hasClass(TABLE_TREE)) {
      updateObjParams(obj);
    }
  })

  // 行双击
  treeTable.on('rowDouble', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      updateObjParams(obj);

      var treeOptions = options.tree || {};
      if (treeOptions.view.dblClickExpand) {
        expandNode({trElem: obj.tr.first()}, null, null, null, true);
      }
    }
  })

  // 菜单
  treeTable.on('rowContextmenu', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      updateObjParams(obj);
    }
  })

  // tr中带lay-event节点点击
  treeTable.on('tool', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      updateObjParams(obj);
    }
  })

  // 行内编辑
  treeTable.on('edit', function (obj) {
    // 如果编辑涉及到关键的name字段需要重新更新一下tr节点
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      updateObjParams(obj);
      if (obj.field === options.tree.customName.name) {
        var updateData = {};
        updateData[obj.field] = obj.value;
        obj.update(updateData); // 通过update调用执行tr节点的更新
      }
    }
  });

  // 单选
  treeTable.on('radio', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      var that = getThisTable(tableId);
      updateObjParams(obj);
      checkNode.call(that, obj.tr, obj.checked)
    }
  })

  // 设置或取消行选中样式
  Class.prototype.setRowCheckedClass = function(tr, checked){
    var that = this;
    var options = that.getOptions();

    var index = tr.data('index');
    var tableViewElem = options.elem.next();
    
    tr[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED); // 主体行

    // 右侧固定行
    tr.each(function(){
      var index = $(this).data('index');
      var trFixedR = tableViewElem.find('.layui-table-fixed-r tbody tr[data-index="'+ index +'"]');
      trFixedR[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED);
    });
  };

  // 更新表格的复选框状态
  Class.prototype.updateCheckStatus = function (dataP, checked) {
    var that = this;
    var options = that.getOptions();
    if (!options.hasChecboxCol) {
      return false; // 如果没有复选列则不需要更新状态
    }
    var treeOptions = options.tree;
    var tableId = options.id;
    var tableView = options.elem.next();

    var checkName = table.config.checkName;

    var cascade = treeOptions.data.cascade;
    var isCascadeParent = cascade === 'all' || cascade === 'parent';

    // 如有必要更新父节点们的状态
    if (isCascadeParent && dataP) {
      var trsP = that.updateParentCheckStatus(dataP, layui.type(checked) === 'boolean' ? checked : null);
      layui.each(trsP, function (indexP, itemP) {
        var checkboxElem = tableView.find('tr[lay-data-index="' + itemP[LAY_DATA_INDEX] + '"]  input[name="layTableCheckbox"]:not(:disabled)');
        var checked = itemP[checkName];

        // 标记父节点行背景色
        that.setRowCheckedClass(checkboxElem.closest('tr'), checked);
        
        // 设置原始复选框 checked 属性值并渲染
        form.render(checkboxElem.prop({
          checked: checked,
          indeterminate: itemP[LAY_CHECKBOX_HALF]
        }))
      })
    }

    // 更新全选的状态
    var isAll = true;
    var isIndeterminate = false;
    var data = treeOptions.data.cascade === 'all' ? table.cache[tableId] : treeTable.getData(tableId, true);
    data = data.filter(function (item) {
        return !item[options.disabledName];
    });
    layui.each(data, function (i1, item1) {
      if (item1[checkName] || item1[LAY_CHECKBOX_HALF]) {
        isIndeterminate = true;
      }
      if (!item1[checkName]) {
        isAll = false;
      }
      if (isIndeterminate && !isAll) {
        return true;
      }
    })
    isIndeterminate = isIndeterminate && !isAll;
    form.render(tableView.find('input[name="layTableCheckbox"][lay-filter="layTableAllChoose"]').prop({
      'checked': isAll,
      indeterminate: isIndeterminate
    }));

    return isAll
  }

  // 更新父节点的选中状态
  Class.prototype.updateParentCheckStatus = function (dataP, checked) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableId = options.id;
    var checkName = table.config.checkName;
    var childrenKey = treeOptions.customName.children;

    var dataRet = [];
    dataP[LAY_CHECKBOX_HALF] = false; // 先设置为非半选，是否为半选又下面逻辑判断
    if (checked === true) {
      // 为真需要判断子节点的情况
      if (!dataP[childrenKey].length) {
        checked = false;
      } else {
        layui.each(dataP[childrenKey], function (index, item) {
          if (!item[checkName]) { // 只要有一个子节点为false
            checked = false;
            dataP[LAY_CHECKBOX_HALF] = true;
            return true; // 跳出循环
          }
        });
      }
    } else if (checked === false) {
      // 判断是否为半选
      layui.each(dataP[childrenKey], function (index, item) {
        if (item[checkName] || item[LAY_CHECKBOX_HALF]) { // 只要有一个子节点为选中或者半选状态
          dataP[LAY_CHECKBOX_HALF] = true;
          return true;
        }
      });
    } else {
      // 状态不确定的情况下根据子节点的信息
      checked = false;
      var checkedNum = 0;
      layui.each(dataP[childrenKey], function (index, item) {
        if (item[checkName]) {
          checkedNum++;
        }
      });
      checked = dataP[childrenKey].length ? dataP[childrenKey].length === checkedNum : dataP[checkName]; // 如果没有子节点保留原来的状态;
      dataP[LAY_CHECKBOX_HALF] = checked ? false : checkedNum > 0;
    }
    dataP[checkName] = checked;
    dataRet.push($.extend({}, dataP));
    if (dataP[LAY_PARENT_INDEX]) {
      dataRet = dataRet.concat(that.updateParentCheckStatus(table.cache[tableId][dataP[LAY_PARENT_INDEX]], checked));
    }
    return dataRet
  }

  var checkNode = function (trElem, checked, callbackFlag) {
    var that = this;
    var options = that.getOptions();
    var treeOptions = options.tree;
    var tableId = options.id;
    var tableView = options.elem.next();
    var inputElem = (trElem.length ? trElem : tableView).find('.laytable-cell-radio, .laytable-cell-checkbox').children('input').last();
    // 判断是单选还是多选 不应该同时存在radio列和checkbox列
    var isRadio = inputElem.attr('type') === 'radio';

    if (callbackFlag) {
      var triggerEvent = function () {
        var fn = function (event) {
          layui.stope(event);
        }
        inputElem.parent().on('click', fn); // 添加临时的阻止冒泡事件
        inputElem.next().click();
        inputElem.parent().off('click', fn);
      }
      // 如果需要触发事件可以简单的触发对应节点的click事件
      if (isRadio) {
        // 单选只能选中或者切换其他的不能取消选中 后续看是否有支持的必要 todo
        if (checked && !inputElem.prop('checked')) {
          triggerEvent()
        }
      } else {
        if (layui.type(checked) === 'boolean') {
          if (inputElem.prop('checked') !== checked) {
            // 如果当前已经是想要修改的状态则不做处理
            triggerEvent()
          }
        } else {
          // 切换
          triggerEvent()
        }
      }
    } else {
      var trData = that.getNodeDataByIndex(trElem.attr('data-index'));
      var checkName = table.config.checkName;
      // 如果不触发事件应该有一个方法可以更新数据以及页面的节点
      if (isRadio) {
        if (!trData) {
          // 单选必须是一个存在的行
          return;
        }
        var statusChecked = {};
        statusChecked[checkName] = false;
        // that.updateStatus(null, statusChecked); // 取消其他的选中状态
        that.updateStatus(null, function (d) {
          if (d[checkName]) {
            var radioElem = tableView.find('tr[lay-data-index="' + d[LAY_DATA_INDEX] + '"] input[type="radio"][lay-type="layTableRadio"]');
            d[checkName] = false;

            // 取消当前选中行背景色
            that.setRowCheckedClass(radioElem.closest('tr'), false);
            form.render(radioElem.prop('checked', false));
          }
        }); // 取消其他的选中状态
        trData[checkName] = checked;

        that.setRowCheckedClass(trElem, checked);  // 标记当前选中行背景色
        that.setRowCheckedClass(trElem.siblings(), false); // 取消其他行背景色

        form.render(trElem.find('input[type="radio"][lay-type="layTableRadio"]').prop('checked', checked));
      } else {
        // 切换只能用到单条，全选到这一步的时候应该是一个确定的状态
        checked = layui.type(checked) === 'boolean' ? checked : !trData[checkName]; // 状态切换，如果遇到不可操作的节点待处理 todo
        // 全选或者是一个父节点，将子节点的状态同步为当前节点的状态
        // 处理不可操作的信息
        var checkedStatusFn = function (d) {
          if (!d[table.config.disabledName]) { // 节点不可操作的不处理
            d[checkName] = checked;
            d[LAY_CHECKBOX_HALF] = false;
          }
        }

        var trs = that.updateStatus(trData ? [trData] : table.cache[tableId], checkedStatusFn, trData && ['parent', 'none'].indexOf(treeOptions.data.cascade) !== -1);
        var checkboxElem = tableView.find(trs.map(function (value) {
          return 'tr[lay-data-index="' + value[LAY_DATA_INDEX] + '"] input[name="layTableCheckbox"]:not(:disabled)';
        }).join(','));

        that.setRowCheckedClass(checkboxElem.closest('tr'), checked);  // 标记当前选中行背景色
        form.render(checkboxElem.prop({checked: checked, indeterminate: false}));

        var trDataP;

        // 更新父节点以及更上层节点的状态
        if (trData && trData[LAY_PARENT_INDEX]) {
          // 找到父节点，然后判断父节点的子节点是否全部选中
          trDataP = that.getNodeDataByIndex(trData[LAY_PARENT_INDEX]);
        }

        return that.updateCheckStatus(trDataP, checked);
      }
    }
  }

  // 多选
  treeTable.on('checkbox', function (obj) {
    var options = obj.config;
    var tableView = options.elem.next();
    var tableId = options.id;

    if (tableView.hasClass(TABLE_TREE)) {
      var that = getThisTable(tableId);
      var checked = obj.checked;
      updateObjParams(obj)
      obj.isAll = checkNode.call(that, obj.tr, checked);
    }
  })

  /**
   * 设置行选中状态
   * @param {String} id 树表id
   * @param {Object} opts
   * @param {Object|String} opts.index 节点下标
   * @param {Boolean} opts.checked 选中或取消
   * @param {Boolean} [opts.callbackFlag] 是否触发事件回调
   * */
  treeTable.setRowChecked = function (id, opts) {
    var that = getThisTable(id);
    if(!that) return;

    var options = that.getOptions();
    var tableView = options.elem.next();

    opts = opts || {};

    var node = opts.index;
    var checked = opts.checked;
    var callbackFlag = opts.callbackFlag;

    var dataIndex = layui.type(node) === 'string' ? node : node[LAY_DATA_INDEX];
    // 判断是否在当前页面中
    var nodeData = that.getNodeDataByIndex(dataIndex);
    if (!nodeData) {
      // 目前只能处理当前页的数据
      return;
    }

    var collectNeedExpandNodeIndex = function(index){
      needExpandIndex.push(index);
      var trElem = tableView.find('tr[lay-data-index="' + index + '"]');
      if (!trElem.length) {
        var nodeData = that.getNodeDataByIndex(index);
        var parentIndex = nodeData[LAY_PARENT_INDEX];
        parentIndex && collectNeedExpandNodeIndex(parentIndex);
      }
    }

    // 判断是否展开过
    var trElem = tableView.find('tr[lay-data-index="' + dataIndex + '"]');
    if (!trElem.length) {
      var parentIndex = nodeData[LAY_PARENT_INDEX];
      var needExpandIndex = [];
      collectNeedExpandNodeIndex(parentIndex);
      // 如果还没有展开没有渲染的要先渲染出来
      layui.each(needExpandIndex.reverse(),function(index, nodeIndex){
        treeTable.expandNode(id, {
          index: nodeIndex,
          expandFlag: true
        });
      })
      trElem = tableView.find('tr[lay-data-index="' + dataIndex + '"]');
    }
    checkNode.call(that, trElem, checked, callbackFlag);
  }

  treeTable.checkAllNodes = function (id, checked) {
    var that = getThisTable(id);
    if(!that) return;

    var options = that.getOptions();
    var tableView = options.elem.next();

    checkNode.call(that, tableView.find('tr[data-index="NONE"]'), !!checked)
  }

  /**
   * 获得数据
   * @param {String} id 表格id
   * @param {Boolean} [isSimpleData] 是否返回平铺结构的数据
   * @return {Array} 表格数据
   * */
  treeTable.getData = function (id, isSimpleData) {
    var that = getThisTable(id);
    if (!that) return;

    var tableData = [];
    layui.each($.extend(true, [], table.cache[id] || []), function (index, item) {
      // 遍历排除掉临时的数据
      tableData.push(item);
    })
    return isSimpleData ? that.treeToFlat(tableData) : tableData;
  }

  /**
   * 重新加载子节点
   * @param {String} id 表格id
   * @param {String} dataIndex 父节点的dataIndex
   * */
  treeTable.reloadAsyncNode = function (id, dataIndex) {
    var that = getThisTable(id);
    if (!that) {
      return;
    }

    var options = that.getOptions();
    var treeOptions = options.tree;
    if (!treeOptions.async || !treeOptions.async.enable) {
      return;
    }
    var dataP = that.getNodeDataByIndex(dataIndex);
    if (!dataP) {
      return;
    }
    dataP[LAY_HAS_EXPANDED] = false;
    dataP[LAY_EXPAND] = false;
    dataP[LAY_ASYNC_STATUS] = false;
    layui.each(that.treeToFlat(dataP[treeOptions.customName.children]).reverse(), function (i1, item1) {
      treeTable.removeNode(id, item1[LAY_DATA_INDEX]);
    })
    // 重新展开
    treeTable.expandNode(id, {
      index: dataIndex,
      expandFlag: true,
      callbackFlag: true,
    })
  }

  /**
   * 通过数据id获取节点对象
   * */
  treeTable.getNodeById = function (id, dataId) {
    var that = getThisTable(id);
    if (!that) return;

    return that.getNodeById(dataId);
  }

  /**
   * 根据自定义规则搜索节点数据
   * @param {String} id 树表id
   * @param {Function} filter 自定义过滤器函数
   * @param {Object} [opts]
   * @param {Boolean} [opts.isSingle] 是否只找到第一个
   * @param {Object} [opts.parentNode] 在指定在某个父节点下的子节点中搜索
   * @return {Object} 节点对象
   * */
  treeTable.getNodesByFilter = function (id, filter, opts) {
    var that = getThisTable(id);
    if (!that) return;
    var options = that.getOptions();

    opts = opts || {};
    var isSingle = opts.isSingle;
    var parentNode = opts.parentNode;
    var dataP = parentNode && parentNode.data;
    // dataP = dataP || table.cache[id];
    var nodes = that.treeToFlat(dataP ? (dataP[options.tree.customName.children] || []) : table.cache[id]).filter(filter);
    var nodesResult = [];
    layui.each(nodes, function (i1, item1) {
      nodesResult.push(that.getNodeByIndex(item1[LAY_DATA_INDEX]));
      if (isSingle) {
        return true;
      }
    });

    return nodesResult;
  }


  // 记录所有实例
  thisTreeTable.that = {}; // 记录所有实例对象
  // thisTreeTable.config = {}; // 记录所有实例配置项

  // 重载
  treeTable.reload = function (id, options, deep, type) {
    // deep = deep !== false; // 默认采用深拷贝
    var that = getThisTable(id);
    if (!that) return;
    that.reload(options, deep, type);
    return thisTreeTable.call(that);
  };

  // 核心入口
  treeTable.render = function (options) {
    var inst = new Class(options);
    return thisTreeTable.call(inst);
  };

  exports(MOD_NAME, treeTable);
});
/**
 * tree 树组件
 */

layui.define(['form','util'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var form = layui.form;
  var layer = layui.layer;
  var util = layui.util;
  
  // 模块名
  var MOD_NAME = 'tree';

  // 外部接口
  var tree = {
    config: {
      customName: { // 自定义 data 字段名
        id: 'id',
        title: 'title',
        children: 'children'
      }
    },
    index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0,

    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },
    
    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };

  // 操作当前实例
  var thisModule = function(){
    var that = this;
    var options = that.config;
    var id = options.id || that.index;
    
    thisModule.that[id] = that; // 记录当前实例对象
    thisModule.config[id] = options; // 记录当前实例配置项
    
    return {
      config: options,
      // 重置实例
      reload: function(options){
        that.reload.call(that, options);
      },
      getChecked: function(){
        return that.getChecked.call(that);
      },
      setChecked: function(id){// 设置值
        return that.setChecked.call(that, id);
      }
    }
  };
  
  // 获取当前实例配置项
  var getThisModuleConfig = function(id){
    var config = thisModule.config[id];
    if(!config) hint.error('The ID option was not found in the '+ MOD_NAME +' instance');
    return config || null;
  }

  // 字符常量
  var SHOW = 'layui-show';
  var HIDE = 'layui-hide';
  var NONE = 'layui-none';
  var DISABLED = 'layui-disabled';
  
  var ELEM_VIEW = 'layui-tree';
  var ELEM_SET = 'layui-tree-set';
  var ICON_CLICK = 'layui-tree-iconClick';
  var ICON_ADD = 'layui-icon-addition';
  var ICON_SUB = 'layui-icon-subtraction';
  var ELEM_ENTRY = 'layui-tree-entry';
  var ELEM_MAIN = 'layui-tree-main';
  var ELEM_TEXT = 'layui-tree-txt';
  var ELEM_PACK = 'layui-tree-pack';
  var ELEM_SPREAD = 'layui-tree-spread';
  var ELEM_LINE_SHORT = 'layui-tree-setLineShort';
  var ELEM_SHOW = 'layui-tree-showLine';
  var ELEM_EXTEND = 'layui-tree-lineExtend';
 
  // 构造器
  var Class = function(options){
    var that = this;
    that.index = ++tree.index;
    that.config = $.extend({}, that.config, tree.config, options);
    that.render();
  };

  // 默认配置
  Class.prototype.config = {
    data: [],  // 数据
    
    showCheckbox: false,  // 是否显示复选框
    showLine: true,  // 是否开启连接线
    accordion: false,  // 是否开启手风琴模式
    onlyIconControl: false,  // 是否仅允许节点左侧图标控制展开收缩
    isJump: false,  // 是否允许点击节点时弹出新窗口跳转
    edit: false,  // 是否开启节点的操作图标
    
    text: {
      defaultNodeName: '未命名', // 节点默认名称
      none: '无数据'  // 数据为空时的文本提示
    }
  };
  
  // 重载实例
  Class.prototype.reload = function(options){
    var that = this;
    
    layui.each(options, function(key, item){
      if(layui.type(item) === 'array') delete that.config[key];
    });
    
    that.config = $.extend(true, {}, that.config, options);
    that.render();
  };

  // 主体渲染
  Class.prototype.render = function(){
    var that = this;
    var options = that.config;

    // 初始化自定义字段名
    options.customName = $.extend({}, tree.config.customName, options.customName);
    
    that.checkids = [];

    var temp = $('<div class="layui-tree layui-border-box'+ (options.showCheckbox ? " layui-form" : "") + (options.showLine ? " layui-tree-line" : "") +'" lay-filter="LAY-tree-'+ that.index +'"></div>');
    that.tree(temp);

    var othis = options.elem = $(options.elem);
    if(!othis[0]) return;

    // 索引
    that.key = options.id || that.index;
    
    // 插入组件结构
    that.elem = temp;
    that.elemNone = $('<div class="layui-tree-emptyText">'+ options.text.none +'</div>');
    othis.html(that.elem);

    if(that.elem.find('.layui-tree-set').length == 0){
      return that.elem.append(that.elemNone);
    };
    
    // 复选框渲染
    if(options.showCheckbox){
      that.renderForm('checkbox');
    };

    that.elem.find('.layui-tree-set').each(function(){
      var othis = $(this);
      // 最外层
      if(!othis.parent('.layui-tree-pack')[0]){
        othis.addClass('layui-tree-setHide');
      };

      // 没有下一个节点 上一层父级有延伸线
      if(!othis.next()[0] && othis.parents('.layui-tree-pack').eq(1).hasClass('layui-tree-lineExtend')){
        othis.addClass(ELEM_LINE_SHORT);
      };
      
      // 没有下一个节点 外层最后一个
      if(!othis.next()[0] && !othis.parents('.layui-tree-set').eq(0).next()[0]){
        othis.addClass(ELEM_LINE_SHORT);
      };
    });

    that.events();
  };
  
  // 渲染表单
  Class.prototype.renderForm = function(type){
    form.render(type, 'LAY-tree-'+ this.index);
  };

  // 节点解析
  Class.prototype.tree = function(elem, children){
    var that = this;
    var options = that.config;
    var customName = options.customName;
    var data = children || options.data;

    // 遍历数据
    layui.each(data, function(index, item){
      var hasChild = item[customName.children] && item[customName.children].length > 0;
      var packDiv = $('<div class="layui-tree-pack" '+ (item.spread ? 'style="display: block;"' : '') +'></div>');
      var entryDiv = $(['<div data-id="'+ item[customName.id] +'" class="layui-tree-set'+ (item.spread ? " layui-tree-spread" : "") + (item.checked ? " layui-tree-checkedFirst" : "") +'">'
        ,'<div class="layui-tree-entry">'
          ,'<div class="layui-tree-main">'
            // 箭头
            ,function(){
              if(options.showLine){
                if(hasChild){
                  return '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon '+ (item.spread ? "layui-icon-subtraction" : "layui-icon-addition") +'"></i></span>';
                }else{
                  return '<span class="layui-tree-iconClick"><i class="layui-icon layui-icon-file"></i></span>';
                };
              }else{
                return '<span class="layui-tree-iconClick"><i class="layui-tree-iconArrow '+ (hasChild ? "": HIDE) +'"></i></span>';
              };
            }()
            
            // 复选框
            ,function(){
              return options.showCheckbox ? '<input type="checkbox" name="'+ (item.field || ('layuiTreeCheck_'+ item[customName.id])) +'" same="layuiTreeCheck" lay-skin="primary" '+ (item.disabled ? "disabled" : "") +' value="'+ item[customName.id] +'">' : '';
            }()
            
            // 节点
            ,function(){
              if(options.isJump && item.href){
                return '<a href="'+ item.href +'" target="_blank" class="'+ ELEM_TEXT +'">'+ (item[customName.title] || item.label || options.text.defaultNodeName) +'</a>';
              }else{
                return '<span class="'+ ELEM_TEXT + (item.disabled ? ' '+ DISABLED : '') +'">'+ (item[customName.title] || item.label || options.text.defaultNodeName) +'</span>';
              }
            }()
      ,'</div>'
      
      // 节点操作图标
      ,function(){
        if(!options.edit) return '';
        
        var editIcon = {
          add: '<i class="layui-icon layui-icon-add-1"  data-type="add"></i>'
          ,update: '<i class="layui-icon layui-icon-edit" data-type="update"></i>'
          ,del: '<i class="layui-icon layui-icon-delete" data-type="del"></i>'
        }, arr = ['<div class="layui-btn-group layui-tree-btnGroup">'];
        
        if(options.edit === true){
          options.edit = ['update', 'del']
        }
        
        if(typeof options.edit === 'object'){
          layui.each(options.edit, function(i, val){
            arr.push(editIcon[val] || '')
          });
          return arr.join('') + '</div>';
        }
      }()
      ,'</div></div>'].join(''));

      // 如果有子节点，则递归继续生成树
      if(hasChild){
        entryDiv.append(packDiv);
        that.tree(packDiv, item[customName.children]);
      };

      elem.append(entryDiv);
      
      // 若有前置节点，前置节点加连接线
      if(entryDiv.prev('.'+ELEM_SET)[0]){
        entryDiv.prev().children('.layui-tree-pack').addClass('layui-tree-showLine');
      };
      
      // 若无子节点，则父节点加延伸线
      if(!hasChild){
        entryDiv.parent('.layui-tree-pack').addClass('layui-tree-lineExtend');
      };

      // 展开节点操作
      that.spread(entryDiv, item);
      
      // 选择框
      if(options.showCheckbox){
        item.checked && that.checkids.push(item[customName.id]);
        that.checkClick(entryDiv, item);
      }
      
      // 操作节点
      options.edit && that.operate(entryDiv, item);
      
    });
  };

  // 展开节点
  Class.prototype.spread = function(elem, item){
    var that = this;
    var options = that.config;
    var entry = elem.children('.'+ELEM_ENTRY);
    var elemMain = entry.children('.'+ ELEM_MAIN);
    var elemCheckbox = elemMain.find('input[same="layuiTreeCheck"]');
    var elemIcon = entry.find('.'+ ICON_CLICK);
    var elemText = entry.find('.'+ ELEM_TEXT);
    var touchOpen = options.onlyIconControl ? elemIcon : elemMain; // 判断展开通过节点还是箭头图标
    var state = '';
    
    // 展开收缩
    touchOpen.on('click', function(e){
      var packCont = elem.children('.'+ELEM_PACK)
      ,iconClick = touchOpen.children('.layui-icon')[0] ? touchOpen.children('.layui-icon') : touchOpen.find('.layui-tree-icon').children('.layui-icon');

      // 若没有子节点
      if(!packCont[0]){
        state = 'normal';
      }else{
        if(elem.hasClass(ELEM_SPREAD)){
          elem.removeClass(ELEM_SPREAD);
          packCont.slideUp(200);
          iconClick.removeClass(ICON_SUB).addClass(ICON_ADD); 
          that.updateFieldValue(item, 'spread', false);
        }else{
          elem.addClass(ELEM_SPREAD);
          packCont.slideDown(200);
          iconClick.addClass(ICON_SUB).removeClass(ICON_ADD);
          that.updateFieldValue(item, 'spread', true);

          // 是否手风琴
          if(options.accordion){
            var sibls = elem.siblings('.'+ELEM_SET);
            sibls.removeClass(ELEM_SPREAD);
            sibls.children('.'+ELEM_PACK).slideUp(200);
            sibls.find('.layui-tree-icon').children('.layui-icon').removeClass(ICON_SUB).addClass(ICON_ADD);
          };
        };
      };
    });
    
    // 点击回调
    elemText.on('click', function(){
      var othis = $(this);
      
      // 判断是否禁用状态
      if(othis.hasClass(DISABLED)) return;
      
      // 判断展开收缩状态
      if(elem.hasClass(ELEM_SPREAD)){
        state = options.onlyIconControl ? 'open' : 'close';
      } else {
        state = options.onlyIconControl ? 'close' : 'open';
      }

      // 获取选中状态
      if(elemCheckbox[0]){
        that.updateFieldValue(item, 'checked', elemCheckbox.prop('checked'));
      }
      
      // 点击产生的回调
      options.click && options.click({
        elem: elem,
        state: state,
        data: item
      });
    });
  };

  // 更新数据源 checked,spread 字段值
  Class.prototype.updateFieldValue = function(obj, field, value){
    if(field in obj) obj[field] = value;
  };
  
  // 计算复选框选中状态
  Class.prototype.setCheckbox = function(elem, item, elemCheckbox){
    var that = this;
    var options = that.config;
    var customName = options.customName;
    var checked = elemCheckbox.prop('checked');
    
    if(elemCheckbox.prop('disabled')) return;

    // 同步子节点选中状态
    if(typeof item[customName.children] === 'object' || elem.find('.'+ELEM_PACK)[0]){
      var elemCheckboxs = elem.find('.'+ ELEM_PACK).find('input[same="layuiTreeCheck"]');
      elemCheckboxs.each(function(index){
        if(this.disabled) return; // 不可点击则跳过
        var children = item[customName.children][index];
        if(children) that.updateFieldValue(children, 'checked', checked);
        that.updateFieldValue(this, 'checked', checked);
      });
    };

    // 同步父节点选中状态
    var setParentsChecked = function(thisNodeElem){
      // 若无父节点，则终止递归
      if(!thisNodeElem.parents('.'+ ELEM_SET)[0]) return;

      var state;
      var parentPack = thisNodeElem.parent('.'+ ELEM_PACK);
      var parentNodeElem = parentPack.parent();
      var parentCheckbox =  parentPack.prev().find('input[same="layuiTreeCheck"]');

      // 如果子节点有任意一条选中，则父节点为选中状态
      if(checked){
        parentCheckbox.prop('checked', checked);
      } else { // 如果当前节点取消选中，则根据计算“兄弟和子孙”节点选中状态，来同步父节点选中状态
        parentPack.find('input[same="layuiTreeCheck"]').each(function(){
          if(this.checked){
            state = true;
          }
        });
        
        // 如果兄弟子孙节点全部未选中，则父节点也应为非选中状态
        state || parentCheckbox.prop('checked', false);
      }
      
      // 向父节点递归
      setParentsChecked(parentNodeElem);
    };
    
    setParentsChecked(elem);

    that.renderForm('checkbox');
  };
  
  // 复选框选择
  Class.prototype.checkClick = function(elem, item){
    var that = this;
    var options = that.config;
    var entry = elem.children('.'+ ELEM_ENTRY);
    var elemMain = entry.children('.'+ ELEM_MAIN);
    
    
    
    // 点击复选框
    elemMain.on('click', 'input[same="layuiTreeCheck"]+', function(e){
      layui.stope(e); // 阻止点击节点事件

      var elemCheckbox = $(this).prev();
      var checked = elemCheckbox.prop('checked');
      
      if(elemCheckbox.prop('disabled')) return;
      
      that.setCheckbox(elem, item, elemCheckbox);
      that.updateFieldValue(item, 'checked', checked);

      // 复选框点击产生的回调
      options.oncheck && options.oncheck({
        elem: elem,
        checked: checked,
        data: item
      });
    });
  };

  // 节点操作
  Class.prototype.operate = function(elem, item){
    var that = this;
    var options = that.config;
    var customName = options.customName;
    var entry = elem.children('.'+ ELEM_ENTRY);
    var elemMain = entry.children('.'+ ELEM_MAIN);

    entry.children('.layui-tree-btnGroup').on('click', '.layui-icon', function(e){
      layui.stope(e);  // 阻止节点操作

      var type = $(this).data("type");
      var packCont = elem.children('.'+ELEM_PACK);
      var returnObj = {
        data: item,
        type: type,
        elem:elem
      };
      // 增加
      if(type == 'add'){
        // 若节点本身无子节点
        if(!packCont[0]){
          // 若开启连接线，更改图标样式
          if(options.showLine){
            elemMain.find('.'+ICON_CLICK).addClass('layui-tree-icon');
            elemMain.find('.'+ICON_CLICK).children('.layui-icon').addClass(ICON_ADD).removeClass('layui-icon-file');
          // 若未开启连接线，显示箭头
          } else {
            elemMain.find('.layui-tree-iconArrow').removeClass(HIDE);
          };
          // 节点添加子节点容器
          elem.append('<div class="layui-tree-pack"></div>');
        };

        // 新增节点
        var key = options.operate && options.operate(returnObj);
        var obj = {};

        obj[customName.title] = options.text.defaultNodeName;
        obj[customName.id] = key;
        that.tree(elem.children('.'+ELEM_PACK), [obj]);
        
        // 放在新增后面，因为要对元素进行操作
        if(options.showLine){
          // 节点本身无子节点
          if(!packCont[0]){
            // 遍历兄弟节点，判断兄弟节点是否有子节点
            var siblings = elem.siblings('.'+ELEM_SET)
            var num = 1;
            var parentPack = elem.parent('.'+ELEM_PACK);

            layui.each(siblings, function(index, i){
              if(!$(i).children('.'+ELEM_PACK)[0]){
                num = 0;
              };
            });

            // 若兄弟节点都有子节点
            if(num == 1){
              // 兄弟节点添加连接线
              siblings.children('.'+ELEM_PACK).addClass(ELEM_SHOW);
              siblings.children('.'+ELEM_PACK).children('.'+ELEM_SET).removeClass(ELEM_LINE_SHORT);
              elem.children('.'+ELEM_PACK).addClass(ELEM_SHOW);
              // 父级移除延伸线
              parentPack.removeClass(ELEM_EXTEND);
              // 同层节点最后一个更改线的状态
              parentPack.children('.'+ELEM_SET).last().children('.'+ELEM_PACK).children('.'+ELEM_SET).last().addClass(ELEM_LINE_SHORT);
            } else {
              elem.children('.'+ELEM_PACK).children('.'+ELEM_SET).addClass(ELEM_LINE_SHORT);
            };
          } else {
            // 添加延伸线
            if(!packCont.hasClass(ELEM_EXTEND)){
              packCont.addClass(ELEM_EXTEND);
            };
            // 子节点添加延伸线
            elem.find('.'+ELEM_PACK).each(function(){
              $(this).children('.'+ELEM_SET).last().addClass(ELEM_LINE_SHORT);
            });
            // 如果前一个节点有延伸线
            if(packCont.children('.'+ELEM_SET).last().prev().hasClass(ELEM_LINE_SHORT)){
              packCont.children('.'+ELEM_SET).last().prev().removeClass(ELEM_LINE_SHORT);
            }else{
              // 若之前的没有，说明处于连接状态
              packCont.children('.'+ELEM_SET).last().removeClass(ELEM_LINE_SHORT);
            };
            // 若是最外层，要始终保持相连的状态
            if(!elem.parent('.'+ELEM_PACK)[0] && elem.next()[0]){
              packCont.children('.'+ELEM_SET).last().removeClass(ELEM_LINE_SHORT);
            };
          };
        };
        if(!options.showCheckbox) return;
        // 若开启复选框，同步新增节点状态
        if(elemMain.find('input[same="layuiTreeCheck"]')[0].checked){
          var packLast = elem.children('.'+ELEM_PACK).children('.'+ELEM_SET).last();
          packLast.find('input[same="layuiTreeCheck"]')[0].checked = true;
        };
        that.renderForm('checkbox');
      
      // 修改
      } else if(type == 'update') {
        var text = elemMain.children('.'+ ELEM_TEXT).html();
        elemMain.children('.'+ ELEM_TEXT).html('');
        // 添加输入框，覆盖在文字上方
        elemMain.append('<input type="text" class="layui-tree-editInput">');
        // 获取焦点
        elemMain.children('.layui-tree-editInput').val(util.unescape(text)).focus();
        // 嵌入文字移除输入框
        var getVal = function(input){
          var textNew = input.val().trim();
          textNew = textNew ? textNew : options.text.defaultNodeName;
          input.remove();
          elemMain.children('.'+ ELEM_TEXT).html(textNew);
          
          // 同步数据
          returnObj.data[customName.title] = textNew;
          
          // 节点修改的回调
          options.operate && options.operate(returnObj);
        };
        // 失去焦点
        elemMain.children('.layui-tree-editInput').blur(function(){
          getVal($(this));
        });
        // 回车
        elemMain.children('.layui-tree-editInput').on('keydown', function(e){
          if(e.keyCode === 13){
            e.preventDefault();
            getVal($(this));
          };
        });

      // 删除
      } else {
        layer.confirm('确认删除该节点 "<span style="color: #999;">'+ (item[customName.title] || '') +'</span>" 吗？', function(index){
          options.operate && options.operate(returnObj); // 节点删除的回调
          returnObj.status = 'remove'; // 标注节点删除
          
          layer.close(index);
          
          // 若删除最后一个，显示空数据提示
          if(!elem.prev('.'+ELEM_SET)[0] && !elem.next('.'+ELEM_SET)[0] && !elem.parent('.'+ELEM_PACK)[0]){
            elem.remove();
            that.elem.append(that.elemNone);
            return;
          };
          // 若有兄弟节点
          if(elem.siblings('.'+ELEM_SET).children('.'+ELEM_ENTRY)[0]){
            // 若开启复选框
            if(options.showCheckbox){
              // 若开启复选框，进行下步操作
              var elemDel = function(elem){
                // 若无父结点，则不执行
                if(!elem.parents('.'+ELEM_SET)[0]) return;
                var siblingTree = elem.siblings('.'+ELEM_SET).children('.'+ELEM_ENTRY);
                var parentTree = elem.parent('.'+ELEM_PACK).prev();
                var checkState = parentTree.find('input[same="layuiTreeCheck"]')[0];
                var state = 1;
                var num = 0;

                // 若父节点未勾选
                if(checkState.checked == false){
                  // 遍历兄弟节点
                  siblingTree.each(function(i, item1){
                    var input = $(item1).find('input[same="layuiTreeCheck"]')[0]
                    if(input.checked == false && !input.disabled){
                      state = 0;
                    };
                    // 判断是否全为不可勾选框
                    if(!input.disabled){
                      num = 1;
                    };
                  });
                  // 若有可勾选选择框并且已勾选
                  if(state == 1 && num == 1){
                    // 勾选父节点
                    checkState.checked = true;
                    that.renderForm('checkbox');
                    // 向上遍历祖先节点
                    elemDel(parentTree.parent('.'+ELEM_SET));
                  };
                };
              };
              elemDel(elem);
            };
            // 若开启连接线
            if(options.showLine){
              // 遍历兄弟节点，判断兄弟节点是否有子节点
              var siblings = elem.siblings('.'+ELEM_SET);
              var num = 1;
              var parentPack = elem.parent('.'+ELEM_PACK);

              layui.each(siblings, function(index, i){
                if(!$(i).children('.'+ELEM_PACK)[0]){
                  num = 0;
                };
              });
              // 若兄弟节点都有子节点
              if(num == 1){
                // 若节点本身无子节点
                if(!packCont[0]){
                  // 父级去除延伸线，因为此时子节点里没有空节点
                  parentPack.removeClass(ELEM_EXTEND);
                  siblings.children('.'+ELEM_PACK).addClass(ELEM_SHOW);
                  siblings.children('.'+ELEM_PACK).children('.'+ELEM_SET).removeClass(ELEM_LINE_SHORT);
                };
                // 若为最后一个节点
                if(!elem.next()[0]){
                  elem.prev().children('.'+ELEM_PACK).children('.'+ELEM_SET).last().addClass(ELEM_LINE_SHORT);
                }else{
                  parentPack.children('.'+ELEM_SET).last().children('.'+ELEM_PACK).children('.'+ELEM_SET).last().addClass(ELEM_LINE_SHORT);
                };
                // 若为最外层最后一个节点，去除前一个结点的连接线
                if(!elem.next()[0] && !elem.parents('.'+ELEM_SET)[1] && !elem.parents('.'+ELEM_SET).eq(0).next()[0]){
                  elem.prev('.'+ELEM_SET).addClass(ELEM_LINE_SHORT);
                };
              }else{
                // 若为最后一个节点且有延伸线
                if(!elem.next()[0] && elem.hasClass(ELEM_LINE_SHORT)){
                  elem.prev().addClass(ELEM_LINE_SHORT);
                };
              };
            };
          
          } else {
            // 若无兄弟节点
            var prevDiv = elem.parent('.'+ELEM_PACK).prev();
            // 若开启了连接线
            if(options.showLine){
              prevDiv.find('.'+ICON_CLICK).removeClass('layui-tree-icon');
              prevDiv.find('.'+ICON_CLICK).children('.layui-icon').removeClass(ICON_SUB).addClass('layui-icon-file');
              // 父节点所在层添加延伸线
              var pare = prevDiv.parents('.'+ELEM_PACK).eq(0);
              pare.addClass(ELEM_EXTEND);

              // 兄弟节点最后子节点添加延伸线
              pare.children('.'+ELEM_SET).each(function(){
                $(this).children('.'+ELEM_PACK).children('.'+ELEM_SET).last().addClass(ELEM_LINE_SHORT);
              });
            }else{
            // 父节点隐藏箭头
              prevDiv.find('.layui-tree-iconArrow').addClass(HIDE);
            };
            // 移除展开属性
            elem.parents('.'+ELEM_SET).eq(0).removeClass(ELEM_SPREAD);
            // 移除节点容器
            elem.parent('.'+ELEM_PACK).remove();
          };

          elem.remove();
        });
        
      };
    });
  };

  // 部分事件
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;
    var checkWarp = that.elem.find('.layui-tree-checkedFirst');
    
    // 初始选中
    that.setChecked(that.checkids);
    
    // 搜索
    that.elem.find('.layui-tree-search').on('keyup', function(){
      var input = $(this);
      var val = input.val();
      var pack = input.nextAll();
      var arr = [];

      // 遍历所有的值
      pack.find('.'+ ELEM_TEXT).each(function(){
        var entry = $(this).parents('.'+ELEM_ENTRY);
        // 若值匹配，加一个类以作标识
        if($(this).html().indexOf(val) != -1){
          arr.push($(this).parent());
          
          var select = function(div){
            div.addClass('layui-tree-searchShow');
            // 向上父节点渲染
            if(div.parent('.'+ELEM_PACK)[0]){
              select(div.parent('.'+ELEM_PACK).parent('.'+ELEM_SET));
            };
          };
          select(entry.parent('.'+ELEM_SET));
        };
      });

      // 根据标志剔除
      pack.find('.'+ELEM_ENTRY).each(function(){
        var parent = $(this).parent('.'+ELEM_SET);
        if(!parent.hasClass('layui-tree-searchShow')){
          parent.addClass(HIDE);
        };
      });
      if(pack.find('.layui-tree-searchShow').length == 0){
        that.elem.append(that.elemNone);
      };

      // 节点过滤的回调
      options.onsearch && options.onsearch({
        elem: arr
      });
    });

    // 还原搜索初始状态
    that.elem.find('.layui-tree-search').on('keydown', function(){
      $(this).nextAll().find('.'+ELEM_ENTRY).each(function(){
        var parent = $(this).parent('.'+ELEM_SET);
        parent.removeClass('layui-tree-searchShow '+ HIDE);
      });
      if($('.layui-tree-emptyText')[0]) $('.layui-tree-emptyText').remove();
    });
  };

  // 得到选中节点
  Class.prototype.getChecked = function(){
    var that = this;
    var options = that.config;
    var customName = options.customName;
    var checkId = [];
    var checkData = [];
    
    // 遍历节点找到选中索引
    that.elem.find('.layui-form-checked').each(function(){
      checkId.push($(this).prev()[0].value);
    });
    
    // 遍历节点
    var eachNodes = function(data, checkNode){
      layui.each(data, function(index, item){
        layui.each(checkId, function(index2, item2){
          if(item[customName.id] == item2){
            that.updateFieldValue(item, 'checked', true);

            var cloneItem = $.extend({}, item);
            delete cloneItem[customName.children];

            checkNode.push(cloneItem);
            
            if(item[customName.children]){
              cloneItem[customName.children] = [];
              eachNodes(item[customName.children], cloneItem[customName.children]);
            }
            return true
          }
        });
      });
    };

    eachNodes($.extend({}, options.data), checkData);
    
    return checkData;
  };

  // 设置选中节点
  Class.prototype.setChecked = function(checkedId){
    var that = this;
    var options = that.config;

    // 初始选中
    that.elem.find('.'+ELEM_SET).each(function(i, item){
      var thisId = $(this).data('id');
      var input = $(item).children('.'+ELEM_ENTRY).find('input[same="layuiTreeCheck"]');
      var reInput = input.next();
      
      // 若返回数字
      if(typeof checkedId === 'number'){
        if(thisId.toString() == checkedId.toString()){
          if(!input[0].checked){
            reInput.click();
          };
          return false;
        };
      } 
      // 若返回数组
      else if(typeof checkedId === 'object'){
        layui.each(checkedId, function(index, value){
          if(value.toString() == thisId.toString() && !input[0].checked){
            reInput.click();
            return true;
          }
        });
      };
    });
  };

  // 记录所有实例
  thisModule.that = {}; // 记录所有实例对象
  thisModule.config = {}; // 记录所有实例配置项
  
  // 重载实例
  tree.reload = function(id, options){
    var that = thisModule.that[id];
    that.reload(options);
    
    return thisModule.call(that);
  };
  
  // 获得选中的节点数据
  tree.getChecked = function(id){
    var that = thisModule.that[id];
    return that.getChecked();
  };
  
  // 设置选中节点
  tree.setChecked = function(id, checkedId){
    var that = thisModule.that[id];
    return that.setChecked(checkedId);
  };
    
  // 核心入口
  tree.render = function(options){
    var inst = new Class(options);
    return thisModule.call(inst);
  };

  exports(MOD_NAME, tree);
})/**
 * transfer 穿梭框组件
 */

layui.define(['laytpl', 'form'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var laytpl = layui.laytpl;
  var form = layui.form;
  
  // 模块名
  var MOD_NAME = 'transfer';

  // 外部接口
  var transfer = {
    config: {},
    index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0,

    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },
    
    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };

  // 操作当前实例
  var thisModule = function(){
    var that = this;
    var options = that.config;
    var id = options.id || that.index;
    
    thisModule.that[id] = that; // 记录当前实例对象
    thisModule.config[id] = options; // 记录当前实例配置项
    
    return {
      config: options,
      // 重置实例
      reload: function(options){
        that.reload.call(that, options);
      },
      // 获取右侧数据
      getData: function(){
        return that.getData.call(that);
      }
    }
  };
  
  // 获取当前实例配置项
  var getThisModuleConfig = function(id){
    var config = thisModule.config[id];
    if(!config) hint.error('The ID option was not found in the '+ MOD_NAME +' instance');
    return config || null;
  };

  // 字符常量
  var ELEM = 'layui-transfer';
  var HIDE = 'layui-hide'; 
  var DISABLED = 'layui-btn-disabled';
  var NONE = 'layui-none';
  var ELEM_BOX = 'layui-transfer-box';
  var ELEM_HEADER = 'layui-transfer-header';
  var ELEM_SEARCH = 'layui-transfer-search';
  var ELEM_ACTIVE = 'layui-transfer-active';
  var ELEM_DATA = 'layui-transfer-data';
  
  // 穿梭框模板
  var TPL_BOX = function(obj){
    obj = obj || {};
    return ['<div class="layui-transfer-box" data-index="'+ obj.index +'">',
      '<div class="layui-transfer-header">',
        '<input type="checkbox" name="'+ obj.checkAllName +'" lay-filter="layTransferCheckbox" lay-type="all" lay-skin="primary" title="{{= d.data.title['+ obj.index +'] || \'list'+ (obj.index + 1) +'\' }}">',
      '</div>',
      '{{# if(d.data.showSearch){ }}',
      '<div class="layui-transfer-search">',
        '<i class="layui-icon layui-icon-search"></i>',
        '<input type="text" class="layui-input" placeholder="关键词搜索">',
      '</div>',
      '{{# } }}',
      '<ul class="layui-transfer-data"></ul>',
    '</div>'].join('');
  };
  
  // 主模板
  var TPL_MAIN = ['<div class="layui-transfer layui-form layui-border-box" lay-filter="LAY-transfer-{{= d.index }}">',
    TPL_BOX({
      index: 0,
      checkAllName: 'layTransferLeftCheckAll'
    }),
    '<div class="layui-transfer-active">',
      '<button type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-btn-disabled" data-index="0">',
        '<i class="layui-icon layui-icon-next"></i>',
      '</button>',
      '<button type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-btn-disabled" data-index="1">',
        '<i class="layui-icon layui-icon-prev"></i>',
      '</button>',
    '</div>',
    TPL_BOX({
      index: 1,
      checkAllName: 'layTransferRightCheckAll'
    }),
  '</div>'].join('');

  // 构造器
  var Class = function(options){
    var that = this;
    that.index = ++transfer.index;
    that.config = $.extend({}, that.config, transfer.config, options);
    that.render();
  };

  // 默认配置
  Class.prototype.config = {
    title: ['列表一', '列表二'],
    width: 200,
    height: 360,
    data: [], // 数据源
    value: [], // 选中的数据
    showSearch: false, // 是否开启搜索
    id: '', // 唯一索引，默认自增 index
    text: {
      none: '无数据',
      searchNone: '无匹配数据'
    }
  };
  
  // 重载实例
  Class.prototype.reload = function(options){
    var that = this;
    that.config = $.extend({}, that.config, options);
    that.render();
  };

  // 渲染
  Class.prototype.render = function(){
    var that = this;
    var options = that.config;
    
    // 解析模板
    var thisElem = that.elem = $(laytpl(TPL_MAIN, {
      open: '{{', // 标签符前缀
      close: '}}' // 标签符后缀
    }).render({
      data: options,
      index: that.index // 索引
    }));
    
    var othis = options.elem = $(options.elem);
    if(!othis[0]) return;
    
    // 初始化属性
    options.data = options.data || [];
    options.value = options.value || [];
    
    // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
    options.id = 'id' in options ? options.id : (
      elem.attr('id') || that.index
    );
    that.key = options.id;
    
    // 插入组件结构
    othis.html(that.elem);
    
    // 各级容器
    that.layBox = that.elem.find('.'+ ELEM_BOX)
    that.layHeader = that.elem.find('.'+ ELEM_HEADER)
    that.laySearch = that.elem.find('.'+ ELEM_SEARCH)
    that.layData = thisElem.find('.'+ ELEM_DATA);
    that.layBtn = thisElem.find('.'+ ELEM_ACTIVE + ' .layui-btn');
    
    // 初始化尺寸
    that.layBox.css({
      width: options.width,
      height: options.height
    });
    that.layData.css({
      height: function(){
        var height = options.height - that.layHeader.outerHeight();
        if(options.showSearch){
          height -= that.laySearch.outerHeight();
        }
        return height - 2;
      }()
    });
    
    that.renderData(); // 渲染数据
    that.events(); // 事件
  };
  
  // 渲染数据
  Class.prototype.renderData = function(){
    var that = this;
    var options = that.config;
    
    // 左右穿梭框差异数据
    var arr = [{
      checkName: 'layTransferLeftCheck',
      views: []
    }, {
      checkName: 'layTransferRightCheck',
      views: []
    }];
    
    // 解析格式
    that.parseData(function(item){      
      // 标注为 selected 的为右边的数据
      var _index = item.selected ? 1 : 0
      var listElem = ['<li>',
        '<input type="checkbox" name="'+ arr[_index].checkName +'" lay-skin="primary" lay-filter="layTransferCheckbox" title="'+ item.title +'"'+ (item.disabled ? ' disabled' : '') + (item.checked ? ' checked' : '') +' value="'+ item.value +'">',
      '</li>'].join('');
      // 按照 options.value 顺序排列右侧数据
      if(_index){
        layui.each(options.value, function(i, v){
          if(v == item.value && item.selected){
            arr[_index].views[i] = listElem;
          }
        });
      } else {
        arr[_index].views.push(listElem);
      }
      delete item.selected;
    });
    
    that.layData.eq(0).html(arr[0].views.join(''));
    that.layData.eq(1).html(arr[1].views.join(''));
    
    that.renderCheckBtn();
  };
  
  // 渲染表单
  Class.prototype.renderForm = function(type){
    form.render(type, 'LAY-transfer-'+ this.index);
  };
  
  // 同步复选框和按钮状态
  Class.prototype.renderCheckBtn = function(obj){
    var that = this;
    var options = that.config;
    
    obj = obj || {};
    
    that.layBox.each(function(_index){
      var othis = $(this);
      var thisDataElem = othis.find('.'+ ELEM_DATA);
      var allElemCheckbox = othis.find('.'+ ELEM_HEADER).find('input[type="checkbox"]');
      var listElemCheckbox =  thisDataElem.find('input[type="checkbox"]');
      
      // 同步复选框和按钮状态
      var nums = 0;
      var haveChecked = false;

      listElemCheckbox.each(function(){
        var isHide = $(this).data('hide');
        if(this.checked || this.disabled || isHide){
          nums++;
        }
        if(this.checked && !isHide){
          haveChecked = true;
        }
      });
      
      allElemCheckbox.prop('checked', haveChecked && nums === listElemCheckbox.length); // 全选复选框状态
      that.layBtn.eq(_index)[haveChecked ? 'removeClass' : 'addClass'](DISABLED); // 对应的按钮状态
      
      // 无数据视图
      if(!obj.stopNone){
        var isNone = thisDataElem.children('li:not(.'+ HIDE +')').length
        that.noneView(thisDataElem, isNone ? '' : options.text.none);
      }
    });
    
    that.renderForm('checkbox');
  };
  
  // 无数据视图
  Class.prototype.noneView = function(thisDataElem, text){
    var createNoneElem = $('<p class="layui-none">'+ (text || '') +'</p>');
    if(thisDataElem.find('.'+ NONE)[0]){
      thisDataElem.find('.'+ NONE).remove();
    }
    text.replace(/\s/g, '') && thisDataElem.append(createNoneElem);
  };
  
  // 同步 value 属性值
  Class.prototype.setValue = function(){
    var that = this;
    var options = that.config;
    var arr = [];

    that.layBox.eq(1).find('.'+ ELEM_DATA +' input[type="checkbox"]').each(function(){
      var isHide = $(this).data('hide');
      isHide || arr.push(this.value);
    });
    options.value = arr;
    
    return that;
  };

  // 解析数据
  Class.prototype.parseData = function(callback){
    var that = this;
    var options = that.config;
    var newData = [];
    
    layui.each(options.data, function(index, item){
      // 解析格式
      item = (typeof options.parseData === 'function' 
        ? options.parseData(item) 
      : item) || item;
      
      newData.push(item = $.extend({}, item))
      
      layui.each(options.value, function(index2, item2){
        if(item2 == item.value){
          item.selected = true;
        }
      });
      callback && callback(item);
    });
   
    options.data = newData;
    return that;
  };
  
  // 获得右侧面板数据
  Class.prototype.getData = function(value){
    var that = this;
    var options = that.config;
    var selectedData = [];
    
    that.setValue();
    
    layui.each(value || options.value, function(index, item){
      layui.each(options.data, function(index2, item2){
        delete item2.selected;
        if(item == item2.value){
          selectedData.push(item2);
        };
      });
    });
    return selectedData;
  };

  // 执行穿梭
  Class.prototype.transfer = function (_index, elem) {
    var that = this;
    var options = that.config;
    var thisBoxElem = that.layBox.eq(_index);
    var arr = [];

    if (!elem) {
      // 通过按钮触发找到选中的进行移动
      thisBoxElem.each(function(_index){
        var othis = $(this);
        var thisDataElem = othis.find('.'+ ELEM_DATA);

        thisDataElem.children('li').each(function(){
          var thisList = $(this);
          var thisElemCheckbox = thisList.find('input[type="checkbox"]');
          var isHide = thisElemCheckbox.data('hide');

          if(thisElemCheckbox[0].checked && !isHide){
            thisElemCheckbox[0].checked = false;
            thisBoxElem.siblings('.'+ ELEM_BOX).find('.'+ ELEM_DATA).append(thisList.clone());
            thisList.remove();

            // 记录当前穿梭的数据
            arr.push(thisElemCheckbox[0].value);
          }

          that.setValue();
        });
      });
    } else {
      // 双击单条记录移动
      var thisList = elem;
      var thisElemCheckbox = thisList.find('input[type="checkbox"]');

      thisElemCheckbox[0].checked = false;
      thisBoxElem.siblings('.'+ ELEM_BOX).find('.'+ ELEM_DATA).append(thisList.clone());
      thisList.remove();

      // 记录当前穿梭的数据
      arr.push(thisElemCheckbox[0].value);

      that.setValue();
    }

    that.renderCheckBtn();

    // 穿梭时，如果另外一个框正在搜索，则触发匹配
    var siblingInput = thisBoxElem.siblings('.'+ ELEM_BOX).find('.'+ ELEM_SEARCH +' input')
    siblingInput.val() === '' ||  siblingInput.trigger('keyup');

    // 穿梭时的回调
    options.onchange && options.onchange(that.getData(arr), _index);
  }

  // 事件
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;
    
    // 左右复选框
    that.elem.on('click', 'input[lay-filter="layTransferCheckbox"]+', function(){ 
      var thisElemCheckbox = $(this).prev();
      var checked = thisElemCheckbox[0].checked;
      var thisDataElem = thisElemCheckbox.parents('.'+ ELEM_BOX).eq(0).find('.'+ ELEM_DATA);
      
      if(thisElemCheckbox[0].disabled) return;
      
      // 判断是否全选
      if(thisElemCheckbox.attr('lay-type') === 'all'){
        thisDataElem.find('input[type="checkbox"]').each(function(){
          if(this.disabled) return;
          this.checked = checked;
        });
      }

      setTimeout(function () {
        that.renderCheckBtn({stopNone: true});
      }, 0)
    });

    // 双击穿梭
    that.elem.on('dblclick', '.' + ELEM_DATA + '>li', function(event){
      var elemThis = $(this);
      var thisElemCheckbox = elemThis.children('input[type="checkbox"]');
      var thisDataElem = elemThis.parent();
      var thisBoxElem = thisDataElem.parent();
      var index = thisBoxElem.data('index');

      if(thisElemCheckbox[0].disabled) return;

      // 根据 dblclick 回调函数返回值决定是否执行穿梭 --- 2.9.3+
      var ret = typeof options.dblclick === 'function' ? options.dblclick({
        elem: elemThis,
        data: that.getData([thisElemCheckbox[0].value])[0],
        index: index
      }) : null;

      if(ret === false) return;

      that.transfer(index, elemThis);
    })

    // 穿梭按钮事件
    that.layBtn.on('click', function(){
      var othis = $(this);
      var _index = othis.data('index');

      if(othis.hasClass(DISABLED)) return;
      that.transfer(_index);
    });
    
    // 搜索
    that.laySearch.find('input').on('keyup', function(){
      var value = this.value;
      var thisDataElem = $(this).parents('.'+ ELEM_SEARCH).eq(0).siblings('.'+ ELEM_DATA);
      var thisListElem = thisDataElem.children('li');

      thisListElem.each(function(){
        var thisList = $(this);
        var thisElemCheckbox = thisList.find('input[type="checkbox"]');
        var title = thisElemCheckbox[0].title;

        // 是否区分大小写
        if(options.showSearch !== 'cs'){
          title = title.toLowerCase();
          value = value.toLowerCase();
        }

        var isMatch = title.indexOf(value) !== -1;

        thisList[isMatch ? 'removeClass': 'addClass'](HIDE);
        thisElemCheckbox.data('hide', isMatch ? false : true);
      });

      that.renderCheckBtn();
      
      // 无匹配数据视图
      var isNone = thisListElem.length === thisDataElem.children('li.'+ HIDE).length;
      that.noneView(thisDataElem, isNone ? options.text.searchNone : '');
    });
  };
  
  // 记录所有实例
  thisModule.that = {}; // 记录所有实例对象
  thisModule.config = {}; // 记录所有实例配置项
  
  // 重载实例
  transfer.reload = function(id, options){
    var that = thisModule.that[id];
    that.reload(options);
    
    return thisModule.call(that);
  };
  
  // 获得选中的数据（右侧面板）
  transfer.getData = function(id){
    var that = thisModule.that[id];
    return that.getData();
  };

  // 核心入口
  transfer.render = function(options){
    var inst = new Class(options);
    return thisModule.call(inst);
  };

  exports(MOD_NAME, transfer);
});
/**
 * carousel 轮播模块
 * MIT Licensed  
 */
 
layui.define(['jquery', 'lay'], function(exports){
  "use strict";
  
  var $ = layui.$;
  var lay = layui.lay;

  var hint = layui.hint();
  var device = layui.device();

  // 外部接口
  var carousel = {
    config: {}, // 全局配置项

    // 设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },
    
    // 事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  };
  
  // 字符常量
  var MOD_NAME = 'carousel';
  var ELEM = '.layui-carousel';
  var THIS = 'layui-this';
  var SHOW = 'layui-show';
  var HIDE = 'layui-hide';
  var DISABLED = 'layui-disabled'
  
  var ELEM_ITEM = '>*[carousel-item]>*';
  var ELEM_LEFT = 'layui-carousel-left';
  var ELEM_RIGHT = 'layui-carousel-right';
  var ELEM_PREV = 'layui-carousel-prev';
  var ELEM_NEXT = 'layui-carousel-next';
  var ELEM_ARROW = 'layui-carousel-arrow';
  var ELEM_IND = 'layui-carousel-ind';
  
  // 构造器
  var Class = function(options){
    var that = this;
    that.config = $.extend({}, that.config, carousel.config, options);
    that.render();
  };
  
  // 默认配置
  Class.prototype.config = {
    width: '600px',
    height: '280px',
    full: false, // 是否全屏
    arrow: 'hover', // 切换箭头默认显示状态：hover/always/none
    indicator: 'inside', // 指示器位置：inside/outside/none
    autoplay: true, // 是否自动切换
    interval: 3000, // 自动切换的时间间隔，不能低于800ms
    anim: '', // 动画类型：default/updown/fade
    trigger: 'click', // 指示器的触发方式：click/hover
    index: 0 // 初始开始的索引
  };
  
  // 轮播渲染
  Class.prototype.render = function(){
    var that = this;
    var options = that.config;

    // 若 elem 非唯一，则拆分为多个实例
    var elem = $(options.elem);
    if(elem.length > 1){
      layui.each(elem, function(){
        carousel.render($.extend({}, options, {
          elem: this
        }));
      });
      return that;
    }

    // 合并 lay-options 属性上的配置信息
    $.extend(options, lay.options(elem[0]));

    options.elem = $(options.elem);
    if(!options.elem[0]) return;
    that.elemItem = options.elem.find(ELEM_ITEM);
    
    if(options.index < 0) options.index = 0;
    if(options.index >= that.elemItem.length) options.index = that.elemItem.length - 1;
    if(options.interval < 800) options.interval = 800;

    // 是否全屏模式
    if(options.full){
      options.elem.css({
        position: 'fixed',
        width: '100%',
        height: '100%',
        zIndex: 9999
      });
    } else {
      options.elem.css({
        width: options.width,
        height: options.height
      });
    }
    
    options.elem.attr('lay-anim', options.anim);
    
    // 初始焦点状态
    that.elemItem.eq(options.index).addClass(THIS);

    // 指示器等动作
    if(that.elemItem.length <= 1) return;

    that.indicator();
    that.arrow();
    that.autoplay();
    that.events();
  };
  
  // 重置轮播
  Class.prototype.reload = function(options){
    var that = this;
    clearInterval(that.timer);
    that.config = $.extend({}, that.config, options);
    that.render();
  };
  
  // 获取上一个等待条目的索引
  Class.prototype.prevIndex = function(){
    var that = this;
    var options = that.config;
    var prevIndex = options.index - 1;

    if(prevIndex < 0){
      prevIndex = that.elemItem.length - 1;
    }

    return prevIndex;
  };
  
  // 获取下一个等待条目的索引
  Class.prototype.nextIndex = function(){
    var that = this;
    var options = that.config;
    var nextIndex = options.index + 1;

    if(nextIndex >= that.elemItem.length){
      nextIndex = 0;
    }

    return nextIndex;
  };
  
  // 索引递增
  Class.prototype.addIndex = function(num){
    var that = this;
    var options = that.config;
    
    num = num || 1;
    options.index = options.index + num;
      
    // index 不能超过轮播总数量
    if(options.index >= that.elemItem.length){
      options.index = 0;
    }
  };
  
  // 索引递减
  Class.prototype.subIndex = function(num){
    var that = this;
    var options = that.config;
    
    num = num || 1;
    options.index = options.index - num;
      
    // index 不能超过轮播总数量
    if(options.index < 0){
      options.index = that.elemItem.length - 1;
    }
  };
  
  // 自动轮播
  Class.prototype.autoplay = function(){
    var that = this;
    var options = that.config;
    
    if(!options.autoplay) return;
    clearInterval(that.timer);
    
    that.timer = setInterval(function(){
      that.slide();
    }, options.interval);
  };
  
  // 箭头
  Class.prototype.arrow = function(){
    var that = this;
    var options = that.config;
    
    // 模板
    var tplArrow = $([
      '<button class="layui-icon '+ ELEM_ARROW +'" lay-type="sub">'+ (options.anim === 'updown' ? '&#xe619;' : '&#xe603;') +'</button>',
      '<button class="layui-icon '+ ELEM_ARROW +'" lay-type="add">'+ (options.anim === 'updown' ? '&#xe61a;' : '&#xe602;') +'</button>'
    ].join(''));
    
    // 预设基础属性
    options.elem.attr('lay-arrow', options.arrow);
    
    // 避免重复插入
    if(options.elem.find('.'+ELEM_ARROW)[0]){
      options.elem.find('.'+ELEM_ARROW).remove();
    }
    options.elem.append(tplArrow);
    
    // 事件
    tplArrow.on('click', function(){
      var othis = $(this);
      var type = othis.attr('lay-type')
      that.slide(type);
    });
  };

  // 跳转到特定下标
  Class.prototype.goto = function(index){
    var that = this;
    var options = that.config;

    if(index > options.index){
      that.slide('add', index - options.index);
    } else if(index < options.index){
      that.slide('sub', options.index - index);
    }
  }
  
  // 指示器
  Class.prototype.indicator = function(){
    var that = this;
    var options = that.config;
    
    // 模板
    var tplInd = that.elemInd = $(['<div class="'+ ELEM_IND +'"><ul>',
      function(){
        var li = [];
        layui.each(that.elemItem, function(index){
          li.push('<li'+ (options.index === index ? ' class="layui-this"' : '') +'></li>');
        });
        return li.join('');
      }(),
    '</ul></div>'].join(''));
    
    // 预设基础属性
    options.elem.attr('lay-indicator', options.indicator);
    
    // 避免重复插入
    if(options.elem.find('.'+ELEM_IND)[0]){
      options.elem.find('.'+ELEM_IND).remove();
    }
    options.elem.append(tplInd);
    
    if(options.anim === 'updown'){
      tplInd.css('margin-top', -(tplInd.height()/2));
    }
    
    // 事件
    tplInd.find('li').on(options.trigger === 'hover' ? 'mouseover' : options.trigger, function(){
      that.goto($(this).index());
    });
  };
  
  // 滑动切换
  Class.prototype.slide = function(type, num){
    var that = this;
    var elemItem = that.elemItem;
    var options = that.config;
    var thisIndex = options.index;
    var filter = options.elem.attr('lay-filter');
    
    if(that.haveSlide) return;
    
    // 滑动方向
    if(type === 'sub'){
      that.subIndex(num);
      elemItem.eq(options.index).addClass(ELEM_PREV);
      setTimeout(function(){
        elemItem.eq(thisIndex).addClass(ELEM_RIGHT);
        elemItem.eq(options.index).addClass(ELEM_RIGHT);
      }, 50);
    } else { // 默认递增滑
      that.addIndex(num);
      elemItem.eq(options.index).addClass(ELEM_NEXT);
      setTimeout(function(){
        elemItem.eq(thisIndex).addClass(ELEM_LEFT);
        elemItem.eq(options.index).addClass(ELEM_LEFT);
      }, 50);  
    }
    
    // 移除过渡类
    setTimeout(function(){
      elemItem.removeClass(THIS + ' ' + ELEM_PREV + ' ' + ELEM_NEXT + ' ' + ELEM_LEFT + ' ' + ELEM_RIGHT);
      elemItem.eq(options.index).addClass(THIS);
      that.haveSlide = false; // 解锁
    }, 350);
    
    // 指示器焦点
    that.elemInd.find('li').eq(options.index).addClass(THIS)
    .siblings().removeClass(THIS);
    
    that.haveSlide = true;
    
    // 回调返回的参数
    var params = {
      index: options.index,
      prevIndex: thisIndex,
      item: elemItem.eq(options.index)
    };
    
    typeof options.change === 'function' && options.change(params);
    layui.event.call(this, MOD_NAME, 'change('+ filter +')', params);
  };
  
  // 事件处理
  Class.prototype.events = function(){
    var that = this;
    var options = that.config;
    
    if(options.elem.data('haveEvents')) return;
    
    // 移入移出容器
    options.elem.on('mouseenter touchstart', function(){
      if (that.config.autoplay === 'always') return;
      clearInterval(that.timer);
    }).on('mouseleave touchend', function(){
      if (that.config.autoplay === 'always') return;
      that.autoplay();
    });

    var touchEl = options.elem;
    var isVertical = options.anim === 'updown';
    lay.touchSwipe(touchEl, {
      onTouchEnd: function(e, state){
        var duration = Date.now() - state.timeStart;
        var distance = isVertical ? state.distanceY : state.distanceX;
        var speed = distance / duration;
        var shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(distance) > touchEl[isVertical ? 'height' : 'width']() / 3;
        if(shouldSwipe){
          that.slide(distance > 0 ? '' : 'sub');
        }
      }
    })
    
    options.elem.data('haveEvents', true);
  };
  
  // 核心入口
  carousel.render = function(options){
    return new Class(options);
  };
  
  exports(MOD_NAME, carousel);
});

 
/**
 * rate 评分评星组件
 */

layui.define(['jquery', 'lay'],function(exports){
  "use strict";

  var $ = layui.jquery;
  var lay = layui.lay;

  // 外部接口
  var rate = {
    config: {},
    index: layui.rate ? (layui.rate.index + 10000) : 0,

    //设置全局项
    set: function(options){
      var that = this;
      that.config = $.extend({}, that.config, options);
      return that;
    },

    //事件
    on: function(events, callback){
      return layui.onevent.call(this, MOD_NAME, events, callback);
    }
  }

  // 操作当前实例
  var thisRate = function () {
    var that = this;
    var options = that.config;

    return {
      setvalue: function (value) {
        that.setvalue.call(that, value);
      },
      config: options
    }
  };

  //字符常量
  var MOD_NAME = 'rate';
  var ELEM_VIEW = 'layui-rate';
  var ICON_RATE = 'layui-icon-rate';
  var ICON_RATE_SOLID = 'layui-icon-rate-solid';
  var ICON_RATE_HALF = 'layui-icon-rate-half';
  var ICON_SOLID_HALF = 'layui-icon-rate-solid layui-icon-rate-half';
  var ICON_SOLID_RATE = 'layui-icon-rate-solid layui-icon-rate';
  var ICON_HALF_RATE = 'layui-icon-rate layui-icon-rate-half';

  //构造器
  var Class = function (options) {
    var that = this;
    that.index = ++rate.index;
    that.config = $.extend({}, that.config, rate.config, options);
    that.render();
  };

  //默认配置
  Class.prototype.config = {
    length: 5,  //初始长度
    text: false,  //是否显示评分等级
    readonly: false,  //是否只读
    half: false,  //是否可以半星
    value: 0, //星星选中个数
    theme: '' //主题颜色
  };

  //评分渲染
  Class.prototype.render = function(){
    var that = this;
    var options = that.config;

    // 若 elem 非唯一，则拆分为多个实例
    var elem = $(options.elem);
    if(elem.length > 1){
      layui.each(elem, function(){
        rate.render($.extend({}, options, {
          elem: this
        }));
      });
      return that;
    }

    // 合并 lay-options 属性上的配置信息
    $.extend(options, lay.options(elem[0]));

    // 自定义主题
    var style = options.theme ? ('style="color: '+ options.theme + ';"') : '';

    options.elem = $(options.elem);

    //最大值不能大于总长度
    if(options.value > options.length){
      options.value = options.length;
    }

    //如果没有选择半星的属性，却给了小数的数值，统一向上或向下取整
    if(parseInt(options.value) !== options.value){
      if(!options.half){
        options.value = (Math.ceil(options.value) - options.value) < 0.5 ? Math.ceil(options.value): Math.floor(options.value)
      }
    }

    //组件模板
    var temp = '<ul class="layui-rate" '+ (options.readonly ? 'readonly' : '') +'>';
    for(var i = 1;i <= options.length;i++){
      var item = '<li class="layui-inline"><i class="layui-icon '
        + (i>Math.floor(options.value)?ICON_RATE:ICON_RATE_SOLID)
      + '" '+ style +'></i></li>';

      if(options.half&&parseInt(options.value) !== options.value&&i == Math.ceil(options.value)){
        temp = temp + '<li><i class="layui-icon layui-icon-rate-half" '+ style +'></i></li>';
      }else{
        temp = temp +item;
      }
    }
    temp += '</ul>' + (options.text ? ('<span class="layui-inline">'+ options.value + '星') : '') + '</span>';

    //开始插入替代元素
    var othis = options.elem;
    var hasRender = othis.next('.' + ELEM_VIEW);

    //生成替代元素
    hasRender[0] && hasRender.remove(); //如果已经渲染，则Rerender

    that.elemTemp = $(temp);

    options.span = that.elemTemp.next('span');

    options.setText && options.setText(options.value);

    othis.html(that.elemTemp);

    othis.addClass("layui-inline");

    //如果不是只读，那么进行触控事件
    if(!options.readonly) that.action();

  };

  //评分重置
  Class.prototype.setvalue = function(value){
    var that = this;
    var options = that.config;

    options.value = value ;
    that.render();
  };

  //li触控事件
  Class.prototype.action = function(){
    var that = this;
    var options = that.config;
    var _ul = that.elemTemp;
    var wide = _ul.find("i").width();
    var liElems =  _ul.children("li");

    liElems.each(function(index){
      var ind = index + 1;
      var othis = $(this);

      //点击
      othis.on('click', function(e){
        //将当前点击li的索引值赋给value
        options.value = ind;
        if(options.half){
          //获取鼠标在li上的位置
          var x = e.pageX - $(this).offset().left;
          if(x <= wide / 2){
            options.value = options.value - 0.5;
          }
        }

        if(options.text)  _ul.next("span").text(options.value + "星");

        options.choose && options.choose(options.value);
        options.setText && options.setText(options.value);
      });

      //移入
      othis.on('mousemove', function(e){
        _ul.find("i").each(function(){
          $(this).addClass(ICON_RATE).removeClass(ICON_SOLID_HALF)
        });
        _ul.find("i:lt(" + ind + ")").each(function(){
          $(this).addClass(ICON_RATE_SOLID).removeClass(ICON_HALF_RATE)
        });
        // 如果设置可选半星，那么判断鼠标相对li的位置
        if(options.half){
          var x = e.pageX - $(this).offset().left;
          if(x <= wide / 2){
            othis.children("i").addClass(ICON_RATE_HALF).removeClass(ICON_RATE_SOLID)
          }
        }
      })

      //移出
      othis.on('mouseleave', function(){
        _ul.find("i").each(function(){
          $(this).addClass(ICON_RATE).removeClass(ICON_SOLID_HALF)
        });
        _ul.find("i:lt(" + Math.floor(options.value) + ")").each(function(){
          $(this).addClass(ICON_RATE_SOLID).removeClass(ICON_HALF_RATE)
        });
        //如果设置可选半星，根据分数判断是否有半星
        if(options.half){
          if(parseInt(options.value) !== options.value){
            _ul.children("li:eq(" + Math.floor(options.value) + ")").children("i").addClass(ICON_RATE_HALF).removeClass(ICON_SOLID_RATE)
          }
        }
      })

    })

    lay.touchSwipe(_ul, {
      onTouchMove: function(e, state){
        if(Date.now() - state.timeStart <= 200) return;
        var pageX = e.touches[0].pageX;
        var rateElemWidth = _ul.width();
        var itemElemWidth = rateElemWidth / options.length; // 单颗星的宽度
        var offsetX = pageX - _ul.offset().left;
        var num = offsetX / itemElemWidth; // 原始值
        var remainder = num % 1;
        var integer = num - remainder;

        // 最终值
        var score = remainder <= 0.5 && options.half ? integer + 0.5 : Math.ceil(num);
        if(score > options.length) score = options.length;
        if(score < 0) score = 0;

        liElems.each(function(index){
          var iconElem = $(this).children('i');
          var isActiveIcon = (Math.ceil(score) - index === 1);
          var needSelect = Math.ceil(score) > index;
          var shouldHalfIcon = (score - index === 0.5);

          if(needSelect){
            // 设置选中样式
            iconElem.addClass(ICON_RATE_SOLID).removeClass(ICON_HALF_RATE);
            if(options.half && shouldHalfIcon){
              iconElem.addClass(ICON_RATE_HALF).removeClass(ICON_RATE_SOLID);
            }
          }else{
            // 恢复初始样式
            iconElem.addClass(ICON_RATE).removeClass(ICON_SOLID_HALF);
          }

          // 设置缩放样式
          iconElem.toggleClass('layui-rate-hover', isActiveIcon);
        });

        // 更新最终值
        options.value = score;
        if(options.text)  _ul.next("span").text(options.value + "星");
        options.setText && options.setText(options.value);
      },
      onTouchEnd: function(e, state){
        if(Date.now() - state.timeStart <= 200) return;
        _ul.find('i').removeClass('layui-rate-hover');
        options.choose && options.choose(options.value);
        options.setText && options.setText(options.value);
      }
    });
  };

  //事件处理
  Class.prototype.events = function () {
    var that = this;
    //var options = that.config;
  };

  //核心入口
  rate.render = function(options){
    var inst = new Class(options);
    return thisRate.call(inst);
  };

  exports(MOD_NAME, rate);
})
/**
 * flow 流加载组件
 */


layui.define('jquery', function(exports){
  "use strict";

  var $ = layui.$, Flow = function(options){}
  ,ELEM_MORE = 'layui-flow-more'
  ,ELEM_LOAD = '<i class="layui-anim layui-anim-rotate layui-anim-loop layui-icon ">&#xe63e;</i>';

  //主方法
  Flow.prototype.load = function(options){
    var that = this, page = 0, lock, isOver, lazyimg, timer;
    options = options || {};

    var elem = $(options.elem); if(!elem[0]) return;
    var scrollElem = $(options.scrollElem || document); // 滚动条所在元素
    var threshold = 'mb' in options ? options.mb : 50; // 临界距离
    var isAuto = 'isAuto' in options ? options.isAuto : true; // 否自动滚动加载
    var end = options.end || '没有更多了'; // “末页”显示文案
    var direction = options.direction || 'bottom';
    var isTop = direction === 'top';

    //滚动条所在元素是否为document
    var notDocument = options.scrollElem && options.scrollElem !== document;

    //加载更多
    var ELEM_TEXT = '<cite>加载更多</cite>'
    ,more = $('<div class="layui-flow-more"><a href="javascript:;">'+ ELEM_TEXT +'</a></div>');

    if(!elem.find('.layui-flow-more')[0]){
      elem[isTop ? 'prepend' : 'append'](more);
    }

    //加载下一个元素
    var next = function(html, over){
      var scrollHeightStart = notDocument ? scrollElem.prop('scrollHeight') : document.documentElement.scrollHeight;
      var scrollTopStart = scrollElem.scrollTop();
      html = $(html);
      more[isTop ? 'after' : 'before'](html);
      over = over == 0 ? true : null;
      over ? more.html(end) : more.find('a').html(ELEM_TEXT);
      isOver = over;
      lock = null;
      lazyimg && lazyimg();
      if(isTop){
        var scrollHeightEnd = notDocument ? scrollElem.prop('scrollHeight') : document.documentElement.scrollHeight;
        if(page === 1){
          // 首次渲染后滑动到底部
          scrollElem.scrollTop(scrollHeightEnd);
        }else if(page > 1){
          var nextElementHeight = scrollHeightEnd - scrollHeightStart;
          scrollElem.scrollTop(scrollTopStart + nextElementHeight);
        }
      }
    };

    //触发请求
    var done = function(){
      lock = true;
      more.find('a').html(ELEM_LOAD);
      typeof options.done === 'function' && options.done(++page, next);
    };

    done();

    //不自动滚动加载
    more.find('a').on('click', function(){
      var othis = $(this);
      if(isOver) return;
      lock || done();
    });

    //如果允许图片懒加载
    if(options.isLazyimg){
      lazyimg = that.lazyimg({
        elem: options.elem + ' img'
        ,scrollElem: options.scrollElem
        ,direction: options.direction
      });
    }

    if(!isAuto) return that;

    scrollElem.on('scroll', function(){
      var othis = $(this), top = othis.scrollTop();

      if(timer) clearTimeout(timer);
      if(isOver || !elem.width()) return; //如果已经结束，或者元素处于隐藏状态，则不执行滚动加载

      timer = setTimeout(function(){
        //计算滚动所在容器的可视高度
        var height = notDocument ? othis.height() : $(window).height();

        //计算滚动所在容器的实际高度
        var scrollHeight = notDocument
          ? othis.prop('scrollHeight')
        : document.documentElement.scrollHeight;

        //临界点
        if(!isTop ? scrollHeight - top - height <= threshold : top <= threshold){
          lock || done();
        }
      }, 100);
    });

    return that;
  };

  //图片懒加载
  Flow.prototype.lazyimg = function(options){
    var that = this, index = 0, haveScroll;
    options = options || {};

    var scrollElem = $(options.scrollElem || document); //滚动条所在元素
    var elem = options.elem || 'img';
    var direction = options.direction || 'bottom';
    var isTop = direction === 'top';

    //滚动条所在元素是否为document
    var notDocument = options.scrollElem && options.scrollElem !== document;

    //显示图片
    var show = function(item, height){
      var start = scrollElem.scrollTop(), end = start + height;
      var elemTop = notDocument ? function(){
        return item.offset().top - scrollElem.offset().top + start;
      }() : item.offset().top;

      /* 始终只加载在当前屏范围内的图片 */
      if((isTop ? elemTop + item.height() : elemTop) >= start && elemTop <= end){
        if(item.attr('lay-src')){
          var src = item.attr('lay-src');
          layui.img(src, function(){
            var next = that.lazyimg.elem.eq(index);
            item.attr('src', src).removeAttr('lay-src');

            /* 当前图片加载就绪后，检测下一个图片是否在当前屏 */
            next[0] && render(next);
            index++;
          }, function(){
            var next = that.lazyimg.elem.eq(index);
            item.removeAttr('lay-src');
          });
        }
      }
    }, render = function(othis, scroll){

      //计算滚动所在容器的可视高度
      var height = notDocument ? (scroll||scrollElem).height() : $(window).height();
      var start = scrollElem.scrollTop(), end = start + height;

      that.lazyimg.elem = $(elem);

      if(othis){
        show(othis, height);
      } else {
        //计算未加载过的图片
        for(var i = 0; i < that.lazyimg.elem.length; i++){
          var item = that.lazyimg.elem.eq(i), elemTop = notDocument ? function(){
            return item.offset().top - scrollElem.offset().top + start;
          }() : item.offset().top;

          show(item, height);
          index = i;

          //如果图片的top坐标，超出了当前屏，则终止后续图片的遍历
          if(elemTop > end) break;
        }
      }
    };

    render();

    if(!haveScroll){
      var timer;
      scrollElem.on('scroll', function(){
        var othis = $(this);
        if(timer) clearTimeout(timer)
        timer = setTimeout(function(){
          render(null, othis);
        }, 50);
      });
      haveScroll = true;
    }
    return render;
  };

  //暴露接口
  exports('flow', new Flow());
});
/**
 * code
 * Code 预览组件
 */

layui.define(['lay', 'util', 'element', 'form'], function(exports){
  "use strict";

  var $ = layui.$;
  var util = layui.util;
  var element = layui.element;
  var form = layui.form;
  var layer = layui.layer;
  var hint = layui.hint();

  // 常量
  var CONST = {
    ELEM_VIEW: 'layui-code-view',
    ELEM_TAB: 'layui-tab',
    ELEM_HEADER: 'layui-code-header',
    ELEM_FULL: 'layui-code-full',
    ELEM_PREVIEW: 'layui-code-preview',
    ELEM_ITEM: 'layui-code-item',
    ELEM_SHOW: 'layui-show',
    ELEM_LINE: 'layui-code-line',
    ELEM_LINE_NUM: 'layui-code-line-number',
    ELEM_LN_MODE: 'layui-code-ln-mode',
    CDDE_DATA_CLASS: 'LayuiCodeDataClass',
    LINE_RAW_WIDTH: 45, // 行号初始宽度，需与 css 保持一致
  };

  // 默认参数项
  var config = {
    elem: '', // 元素选择器
    about: '', // 代码栏右上角信息
    ln: true, // 代码区域是否显示行号
    header: false, // 是否显示代码栏头部区域
    encode: true, // 是否对 code 进行编码（若开启预览，则强制开启）
    copy: true, // 是否开启代码区域复制功能图标
    // 默认文本
    text: {
      code: util.escape('</>'),
      preview: 'Preview',
    },
    wordWrap: true, // 是否自动换行
    lang: 'text', // 指定语言类型
    highlighter: false, // 是否开启语法高亮，'hljs','prism','shiki'
    langMarker: false, // 代码区域是否显示语言类型标记
  };

  // 初始索引
  var codeIndex = layui.code ? (layui.code.index + 10000) : 0;

  // 去除尾部空格
  var trimEnd = function(str){
    return String(str).replace(/\s+$/, '');
  }
  // 保留首行缩进
  var trim = function(str){
    return trimEnd(str).replace(/^\n|\n$/, '');
  };

  // export api
  exports('code', function(options, mode){
    options = $.extend(true, {}, config, options);

    // 返回对象
    var ret = {
      config: options,
      reload: function(opts) { // 重载
        layui.code(this.updateOptions(opts));
      },
      updateOptions: function(opts) { // 更新属性（选项）
        opts = opts || {};
        delete opts.elem;
        return $.extend(true, options, opts);
      },
      reloadCode: function(opts) { // 仅重载 code
        layui.code(this.updateOptions(opts), 'reloadCode');
      }
    };

    // 若 elem 非唯一
    var elem = $(options.elem);
    if(elem.length > 1){
      // 是否正向渲染
      layui.each(options.obverse ? elem : elem.get().reverse(), function(){
        layui.code($.extend({}, options, {
          elem: this
        }), mode);
      });
      return ret;
    }

    // 目标元素是否存在
    var othis = options.elem = $(options.elem);
    if(!othis[0]) return ret;

    // 合并属性上的参数，并兼容旧版本属性写法 lay-*
    $.extend(true, options, lay.options(othis[0]), function(obj){
      var attrs = ['title', 'height', 'encode', 'skin', 'about'];
      layui.each(attrs, function(i, attr){
        var value = othis.attr('lay-'+ attr);
        if(typeof value === 'string'){
          obj[attr] = value;
        }
      })
      return obj;
    }({}));

    // codeRender 需要关闭编码
    // 未使用 codeRender 时若开启了预览，则强制开启编码
    options.encode = (options.encode || options.preview) && !options.codeRender;

    // 获得初始 code
    options.code = options.code || function(){
      var arr = [];
      var textarea = othis.children('textarea');

      // 若内容放置在 textarea 中
      textarea.each(function(){
        arr.push(trim(this.value));
      });

      // 内容直接放置在元素外层
      if(arr.length === 0){
        arr.push(trim(othis.html()));
      }

      return arr.join('');
    }();

    // 创建 code 行结构
    var createCode = function(html) {
      // codeRender
      if(typeof options.codeRender === 'function') {
        html = options.codeRender(String(html), options);
      }

      // code 行
      var lines = String(html).split(/\r?\n/g);

      // 包裹 code 行结构
      html = $.map(lines, function(line, num) {
        return [
          '<div class="'+ CONST.ELEM_LINE +'">',
            (
              options.ln ? [
                '<div class="'+ CONST.ELEM_LINE_NUM +'">',
                  (util.digit(num + 1) + '.'),
                '</div>',
              ].join('') : ''
            ),
            '<div class="layui-code-line-content">',
              (line || ' '),
            '</div>',
          '</div>'
        ].join('');
      });

      return {
        lines: lines,
        html: html
      };
    };

    // 原始 code
    var rawCode = options.code;

    // 最终 code
    var finalCode = function(code) {
      return typeof options.codeParse === 'function' ?
        options.codeParse(code, options) :
      code;
    };

    // 仅重载 code
    if (mode === 'reloadCode') {
      return othis.children('.layui-code-wrap').html(
        createCode(finalCode(rawCode)).html
      ), ret;
    }

    // 自增索引
    var index = layui.code.index = ++codeIndex;
    othis.attr('lay-code-index', index);

    // 初始化 className
    var hasDataClass = CONST.CDDE_DATA_CLASS in othis.data();
    if (hasDataClass) {
      othis.attr('class', othis.data(CONST.CDDE_DATA_CLASS) || '');
    }

    // 记录初始 className
    if (!hasDataClass) {
      othis.data(CONST.CDDE_DATA_CLASS, othis.attr('class'));
    }

    // 工具栏
    var tools = {
      copy: {
        className: 'file-b',
        title: ['复制代码'],
        event: function(obj){
          var code = util.unescape(finalCode(options.code));

          // 写入剪切板
          lay.clipboard.writeText({
            text: code,
            done: function() {
              layer.msg('已复制', {icon: 1});
            },
            error: function() {
              layer.msg('复制失败', {icon: 2});
            }
          });

          typeof options.onCopy === 'function' && options.onCopy(code);
        }
      }
    };

    // 移除包裹结构
    var unwrap = (function fn() {
      var elemViewHas = othis.parent('.' + CONST.ELEM_PREVIEW);
      var elemTabHas = elemViewHas.children('.'+ CONST.ELEM_TAB);
      var elemPreviewViewHas = elemViewHas.children('.' + CONST.ELEM_ITEM +'-preview');

      // 移除旧结构
      elemTabHas.remove(); // 移除 tab
      elemPreviewViewHas.remove(); // 移除预览区域
      if (elemViewHas[0]) othis.unwrap(); // 移除外层容器

      return fn;
    })();

    // 是否开启预览
    if(options.preview){
      var FILTER_VALUE = 'LAY-CODE-DF-'+ index;
      var layout = options.layout || ['code', 'preview'];
      var isIframePreview = options.preview === 'iframe';

      // 追加 Tab 组件
      var elemView = $('<div class="'+ CONST.ELEM_PREVIEW +'">');
      var elemTabView = $('<div class="layui-tab layui-tab-brief">');
      var elemHeaderView = $('<div class="layui-tab-title">');
      var elemPreviewView = $('<div class="'+ [
        CONST.ELEM_ITEM,
        CONST.ELEM_ITEM +'-preview',
        'layui-border'
      ].join(' ') +'">');
      var elemToolbar = $('<div class="layui-code-tools"></div>');


      if(options.id) elemView.attr('id', options.id);
      elemView.addClass(options.className);
      elemTabView.attr('lay-filter', FILTER_VALUE);

      // 标签头
      layui.each(layout, function(i, v){
        var li = $('<li lay-id="'+ v +'">');
        if(i === 0) li.addClass('layui-this');
        li.html(options.text[v]);
        elemHeaderView.append(li);
      });

      // 工具栏
      $.extend(tools, {
        'full': {
          className: 'screen-full',
          title: ['最大化显示', '还原显示'],
          event: function(obj){
            var el = obj.elem;
            var elemView = el.closest('.'+ CONST.ELEM_PREVIEW);
            var classNameFull = 'layui-icon-'+ this.className;
            var classNameRestore = 'layui-icon-screen-restore';
            var title = this.title;
            var htmlElem = $('html,body');
            var ELEM_SCROLLBAR_HIDE = 'layui-scrollbar-hide';

            if(el.hasClass(classNameFull)){
              elemView.addClass(CONST.ELEM_FULL);
              el.removeClass(classNameFull).addClass(classNameRestore);
              el.attr('title', title[1]);
              htmlElem.addClass(ELEM_SCROLLBAR_HIDE);
            } else {
              elemView.removeClass(CONST.ELEM_FULL);
              el.removeClass(classNameRestore).addClass(classNameFull);
              el.attr('title', title[0]);
              htmlElem.removeClass(ELEM_SCROLLBAR_HIDE);
            }
          }
        },
        'window': {
          className: 'release',
          title: ['在新窗口预览'],
          event: function(obj){
            util.openWin({
              content: finalCode(options.code)
            });
          }
        }
      });

      // copy
      if(options.copy){
        if(layui.type(options.tools) === 'array'){
          // 若 copy 未存在于 tools 中，则追加到最前
          if(options.tools.indexOf('copy') === -1){
            options.tools.unshift('copy');
          }
        } else {
          options.tools = ['copy'];
        }
      }

      // 工具栏事件
      elemToolbar.on('click', '>i', function(){
        var oi = $(this);
        var type = oi.data('type');
        var parameters = {
          elem: oi,
          type: type,
          options: options, // 当前属性选项
          rawCode: options.code, // 原始 code
          finalCode: util.unescape(finalCode(options.code)) // 最终 code
        };

        // 内部 tools event
        tools[type] && typeof tools[type].event === 'function' && tools[type].event(parameters);

        // 外部 tools event
        typeof options.toolsEvent === 'function' && options.toolsEvent(parameters);
      });

      // 增加工具栏
      if (options.addTools && options.tools) {
        options.tools = [].concat(options.tools, options.addTools);
      }

      // 渲染工具栏
      layui.each(options.tools, function(i, v){
        var viso = typeof v === 'object'; // 若为 object 值，则可自定义更多属性
        var tool = viso ? v : (
          tools[v] || {
            className: v,
            title: [v]
          }
        );

        var className = tool.className || tool.type;
        var title = tool.title || [''];
        var type = viso ? ( tool.type || className ) : v;

        if (!type) return;

        // 若非内置 tool，则合并到 tools 中
        if (!tools[type]) {
          var obj = {};
          obj[type] = tool;
          $.extend(tools, obj);
        }

        elemToolbar.append(
          '<i class="layui-icon layui-icon-'+ className +'" data-type="'+ type +'" title="'+ title[0] +'"></i>'
        );
      });

      othis.addClass(CONST.ELEM_ITEM).wrap(elemView); // 包裹外层容器
      elemTabView.append(elemHeaderView); // 追加标签头
      options.tools && elemTabView.append(elemToolbar); // 追加工具栏
      othis.before(elemTabView); // 追加标签结构

      // 追加预览
      if(isIframePreview){
        elemPreviewView.html('<iframe allowtransparency="true" frameborder="0"></iframe>');
      }

      // 执行预览
      var runPreview = function(thisItemBody){
        var iframe = thisItemBody.children('iframe')[0];

        // 是否 iframe 方式预览
        if(isIframePreview && iframe){
          iframe.srcdoc = finalCode(options.code);
        } else {
          thisItemBody.html(options.code);
        }

        // 当前实例预览完毕后的回调
        setTimeout(function(){
          typeof options.done === 'function' && options.done({
            container: thisItemBody,
            options: options,
            render: function(){
              form.render(thisItemBody.find('.layui-form'));
              element.render();
            }
          });
        },3);
      };

      if(layout[0] === 'preview'){
        elemPreviewView.addClass(CONST.ELEM_SHOW);
        othis.before(elemPreviewView);
        runPreview(elemPreviewView);
      } else {
        othis.addClass(CONST.ELEM_SHOW).after(elemPreviewView);
      }

      // 内容项初始化样式
      options.previewStyle = [options.style, options.previewStyle].join('');
      elemPreviewView.attr('style', options.previewStyle);

      // tab change
      element.on('tab('+ FILTER_VALUE +')', function(data){
        var $this = $(this);
        var thisElem = $(data.elem).closest('.'+ CONST.ELEM_PREVIEW);
        var elemItemBody = thisElem.find('.'+ CONST.ELEM_ITEM);
        var thisItemBody = elemItemBody.eq(data.index);

        elemItemBody.removeClass(CONST.ELEM_SHOW);
        thisItemBody.addClass(CONST.ELEM_SHOW);

        if($this.attr('lay-id') === 'preview'){
          runPreview(thisItemBody);
        }

        setCodeLayout();
      });
    }

    // 创建 code 容器
    var codeElem = $('<code class="layui-code-wrap"></code>'); // 此处的闭合标签是为了兼容 IE8

    // 添加主容器 className
    othis.addClass(function(arr) {
      if (!options.wordWrap) arr.push('layui-code-nowrap');
      return arr.join(' ')
    }(['layui-code-view layui-border-box']));

    // code 主题风格
    var theme = options.theme || options.skin;
    if (theme) {
      othis.removeClass('layui-code-theme-dark layui-code-theme-light');
      othis.addClass('layui-code-theme-'+ theme);
    }

    // 添加高亮必要的 className
    if (options.highlighter) {
      othis.addClass([
        options.highlighter,
        'language-' + options.lang,
        'layui-code-hl'
      ].join(' '));
    }

    // 获取 code 行结构
    var createCodeRst = createCode(
      options.encode ? util.escape(finalCode(rawCode)) : rawCode // 是否编码
    );
    var lines = createCodeRst.lines;

    // 插入 code
    othis.html(codeElem.html(createCodeRst.html));

    // 插入行号边栏
    if (options.ln) {
      othis.append('<div class="layui-code-ln-side"></div>');
    }

    // 兼容旧版本 height 属性
    if (options.height) {
      codeElem.css('max-height', options.height);
    }

    // code 区域样式
    options.codeStyle = [options.style, options.codeStyle].join('');
    if (options.codeStyle) {
      codeElem.attr('style', function(i, val) {
        return (val || '') + options.codeStyle;
      });
    }

    // 动态设置样式
    var cssRules = [
      {
        selector: '>.layui-code-wrap>.layui-code-line{}',
        setValue: function(item, value) {
          item.style['padding-left'] = value + 'px';
        }
      },
      {
        selector: '>.layui-code-wrap>.layui-code-line>.layui-code-line-number{}',
        setValue: function(item, value) {
         item.style.width = value + 'px';
        }
      },
      {
        selector: '>.layui-code-ln-side{}',
        setValue: function(item, value) {
          item.style.width = value + 'px';
        }
      }
    ];

    // 生成初始 style 元素
    var styleElem = lay.style({
      target: othis[0],
      id: 'DF-code-'+ index,
      text: $.map($.map(cssRules, function(val){
        return val.selector;
      }), function(val, i) {
        return ['.layui-code-view[lay-code-index="'+ index + '"]', val].join(' ');
      }).join('')
    })

    // 动态设置 code 布局
    var setCodeLayout = (function fn() {
      if (options.ln) {
        var multiLine = Math.floor(lines.length / 100);
        var lineElem = codeElem.children('.'+ CONST.ELEM_LINE);
        var width = lineElem.last().children('.'+ CONST.ELEM_LINE_NUM).outerWidth();

        othis.addClass(CONST.ELEM_LN_MODE);

        // 若超出 100 行
        if (multiLine && width > CONST.LINE_RAW_WIDTH) {
          lay.getStyleRules(styleElem, function(item, i) {
            try {
              cssRules[i].setValue(item, width);
            } catch(e) { }
          });
        }
      }

      return fn;
    })();

    // 创建 code header
    if (options.header) {
      var headerElem = $('<div class="'+ CONST.ELEM_HEADER +'"></div>');
      headerElem.html(options.title || options.text.code);
      othis.prepend(headerElem);
    }

    // 创建 code 区域固定条
    var elemFixbar = $('<div class="layui-code-fixbar"></div>');

    // 若开启复制，且未开启预览，则单独生成复制图标
    if(options.copy && !options.preview){
      var copyElem = $(['<span class="layui-code-copy">',
        '<i class="layui-icon layui-icon-file-b" title="复制"></i>',
      '</span>'].join(''));

      // 点击复制
      copyElem.on('click', function(){
        tools.copy.event();
      });

      elemFixbar.append(copyElem);
    }

    // 创建 language marker
    if (options.langMarker) {
      elemFixbar.append('<span class="layui-code-lang-marker">' + options.lang + '</span>');
    }

    // 创建 about 自定义内容
    if (options.about) {
      elemFixbar.append(options.about);
    }

    // 生成 code fixbar
    othis.append(elemFixbar);

    // code 渲染完毕后的回调
    if (!options.preview) {
      setTimeout(function(){
        typeof options.done === 'function' && options.done({});
      },3);
    }

    // 所有实例渲染完毕后的回调
    if(options.elem.length === index + 1){
      typeof options.allDone === 'function' && options.allDone();
    }

    return ret;
  });
});

// 若为源码版，则自动加载该组件依赖的 css 文件
if(!layui['layui.all']){
  layui.addcss('modules/code.css?v=6', 'skincodecss');
}

//# sourceMappingURL=layui.js.map
