text: /**
   * @Validate.js
   * @author zhangxinxu
   * @version
   * Created: 15-08-19
   */
  
  (function (global, factory) {
      if (typeof define === 'function' && (define.amd || define.cmd)) {
          define(factory);
      } else {
          global.Validate = factory();
      }
  }(this, function (require) {
      if (typeof require == 'function') {
          require('common/ui/ErrorTip');
      } else if (!$().follow) {
          if (window.console) {
              window.console.error('need ErrorTip.js');
          }
  
          return {};
      }
  
      /**
       * 基于HTML5规范的表单验证方法
       * 根据原生属性:type, required, min, max, minlength, maxlength, step, pattern等属性进行验证
       * 支持new实例构造以及jQuery包装器用法，例如：
       * new Validate(el, options);
       * or
       * $().validate();
       * 很多验证规则基于其他UI组件特征
       */
  
  
      // 包装器方法
      $.fn.validate = function(callback, options) {
          return $(this).each(function() {
              $(this).data('validate', new Validate($(this), callback, options));
          });
      };
  
      // 选中某范围文字内容的拓展方法
      $.fn.selectRange = function(start, end) {
          var that = $(this).get(0);
          if (that.createTextRange) {
              var range = that.createTextRange();
              range.collapse(true);
              range.moveEnd('character', end);
              range.moveStart('character', start);
              range.select();
          } else {
              that.focus();
              that.setSelectionRange(start, end);
          }
  
          return $(this);
      };
  
      // 是否含有某布尔属性方法
      $.fn.isProp = function(attr) {
          var prop = $(this).prop(attr) || $(this).attr(attr);
          if (prop || typeof prop == 'string') {
              return true;
          }
  
          return false;
      };
  
      // 全角转半角方法
      $.dbc2sbc = function(str) {
          var result = '';
          var i, code;
          for (i = 0 ; i < str.length; i++) {
              code = str.charCodeAt(i);
              if (code >= 65281 && code <= 65373) {
                  result += String.fromCharCode(str.charCodeAt(i) - 65248);
              } else if (code == 12288) {
                  result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
              } else {
                  result += str.charAt(i);
              }
          }
  
          return result;
      };
  
      // 获得并重置type
      $.getType = function(ele) {
          // 控件的类型
          // 这里不能直接ele.type获取，否则，类似email类型直接就是text类型
          // 但是，有些元素的type是隐式的，例如textarea，因此ele.type还是需要的
          var attrType = ele.getAttribute('type');
          var type = attrType || ele.type || '';
          // 高级版本IE浏览器的向下兼容模式，会忽略email类型
          // 虽然这种情况出现的可能性很小，但是，为了万一
          // 可以使用type='email '解决此问题
          // 所以，才会有下面的正则替换，得到最精准的type类型
          type = type.toLowerCase().replace(/\W+$/, '');
  
          // 对于本来支持的浏览器，需要变成真实的
          if (attrType && attrType != type && $('<input type="' + type + '">').attr('type') == type) {
              try {
                  ele.type = type;
              } catch (e) {
                  //
              }
          }
  
          // 类似_date_这种干掉浏览器默认类型的处理
          return type.replace(/_/g, '');
      };
  
      // 获得字符长度
      // @ele  Object   input/textarea表单控件元素
      // @max  Number   返回满足最大值时候的真实字符长度
      $.getLength = function(ele, max) {
          if (ele.type == 'password') {
              return max ? max : ele.value.length;
          }
          // 语言属性和trim后的值
          var lang = ele.getAttribute('lang');
          var value = $.trim(ele.value);
          if (!lang) {
              return max ? max : value.length;
          }
          if (value == '') {
              return 0;
          }
  
          // 中文和英文计算字符个数的比例
          var ratioCh = 1;
          var ratioEn = 1;
  
          if (/zh/i.test(lang)) {
              // 1个英文半个字符
              ratioEn = 0.5;
          } else if (/en/i.test(lang)) {
              // 1个中文2个字符
              ratioCh = 2;
          }
  
          // 下面是中文或全角等字符正则
          if (!max) {
              var lenOriginCh = value.replace(/[\x00-\xff]/g, '').length;
              var lenOriginEn = value.length - lenOriginCh;
  
              // 根据比例返回最终长度
              return Math.ceil(lenOriginEn * ratioEn) + Math.ceil(lenOriginCh * ratioCh);
          }
          var start = 0;
          var lenMatch = max;
  
          $.each(value.split(''), function(index, letter) {
              if (start >= max) return;
              if (/[\x00-\xff]/.test(letter)) {
                  // 如果字符是中文或全角
                  start += ratioEn;
              } else {
                  // 如果字符是英文
                  start += ratioCh;
              }
  
              if (start >= max) {
                  lenMatch = index + 1;
              }
          });
  
          return lenMatch;
      };
  
      // 电话号码过滤成符合要求的号码，省去用户自己处理
      // 例如我们从某个地方复制电话号码，可能是短横线相连
      // 或者前面带+86
      $.telFilter = function (tel) {
          tel = tel || '';
          tel = tel.replace('+86', '');
          // 如果此时剩余所有数字正好11位
          if (tel.match(/\d/) && tel.match(/\d/g).length == 11) {
              tel = tel.replace(/\D/g, '');
          }
          return tel;
      };
  
      // 一些全局的属性和方法
      $.validate = (function() {
          return {
              reg: {
                  email: '^[a-z0-9._%-]+@([a-z0-9-]+\\.)+[a-z]{2,4}$',
                  number: '^\\-?\\d+(\\.\\d+)?$',
                  url: "^(http|https)\\:\\/\\/[a-z0-9\\-\\.]+\\.[a-z]{2,3}(:[a-z0-9]*)?\\/?([a-z0-9\\-\\._\\:\\?\\,\\'\\/\\\\\\+&amp;%\\$#\\=~])*$",
                  tel: '^1\\d{10}$',
                  zipcode: '^\\d{6}$',
                  date: '^\\d{4}\\-(0\\d|1[0-2])\\-([0-2]\\d|3[0-1])$',
                  time: '^[0-2]\\d\\:[0-5]\\d$',
                  hour: '^[0-2]\\d\\:00$',
                  minute: '^[0-2]\\d\\:[0-5]\\d$',
                  'date-range': '^\\d{4}(\\-\\d{2}){2}\\s至\\s\\d{4}(\\-\\d{2}){2}$',
                  'month-range': '^\\d{4}\\-\\d{2}\\s至\\s\\d{4}\\-\\d{2}$'
              },
  
              prompt: function(result, ele, options) {
  
                  /*
                   * @result  是验证返回的结果，为纯对象
                              {
                                  type: '',
                                  error: ''
                              }
                              有对应的提示信息
                   * @ele     是当前出错的元素, 此参数可选
                              当该属性存在的时候，文字提示内容更丰富与智能
                   * @options 自定义的提示数据，优先级最高, 需要和ele参数配合使用
                  */
                  var defaultPrompt = {
                      name: $.extend({}, {
                          email: '邮箱',
                          tel: '手机号码',
                          url: '网址',
                          zipcode: '邮编',
                          password: '密码',
                          number: '数值',
                          range: '数值',
                          date: '日期',
                          year: '年份',
                          month: '月份',
                          hour: '小时',
                          minute: '分钟',
                          time: '时间',
                          datetime: '日期时间',
                          'date-range': '日期范围',
                          'month-range': '月份范围'
                      }, $.validate.name || {}),
                      ignore: {
                          radio: '请选择一个选项',
                          checkbox: '如果要继续，请选中此框',
                          select: '请选择列表中的一项',
                          'select-one': '请选择列表中的一项',
                          empty: '请填写此字段'
                      },
                      unmatch: {
                          pattern: '内容格式不符合要求',
                          multiple: '某项内容格式不符合要求'
                      },
                      out: {
                          min: '值偏小',
                          max: '值偏大',
                          step: '值不符合要求'
                      },
                      overflow: {
                          minlength: '内容长度偏小',
                          maxlength: '内容长度偏大'
                      }
                  };
  
                  if (!result) {
                      return;
                  }
                  // 最终的提示文字
  
                  // 元素的id, type等
                  var text = '';
                  var id = ele.id;
                  var type = '';
                  var error = result.error;
  
                  if (ele) {
                      type = ele.getAttribute('type') || ele.type || '';
                      type = $.trim(type);
                      if (type == 'select-one') type = 'select';
                  }
  
                  // options对应的提示信息
                  options = options || {};
                  var optionsPrompt = options.prompt || options;
  
                  if (id && $.isArray(options)) {
                      $.each(options, function(index, obj) {
                          if (obj.id == id) {
                              optionsPrompt = obj.prompt;
                          }
                      });
                  }
  
                  // 错误提示关键字
                  var name = defaultPrompt.name[type] || (function() {
                      if (!options.label || !id || /checkbox|radio/.test(type)) return;
                      var text = '';
                      // 从label动态获取提示数据
                      $('label[for=' + id + ']').each(function() {
                          var label = $(this).clone();
                          // 只使用裸露的文字作为提示关键字
                          label.children().remove();
                          // 去除数字和冒号
                          var labelText = $.trim(label.html()).replace(/\d/g, '').replace('：', '');
                          // 内容最长的那个元素作为提示文字
                          if (labelText.length > text.length) {
                              text = labelText;
                          }
                      });
                      // 提示关键字显然不会小于2个字
                      if (text.length >= 2) {
                          return text;
                      }
                  })();
  
                  switch (result.type) {
                      case 'ignore': {
                          // 自定义提示
                          text = optionsPrompt.ignore;
                          // 如果没有对应的自定义提示
                          if (!text) {
                              // 则使用默认的提示：
                              // 1. 根据类型关键字组装的提示
                              if (type && name) {
                                  if (type != 'select') {
                                      text = name + '不能为空';
                                  } else {
                                      text = defaultPrompt.ignore[type] = '您尚未选择' + name;
                                  }
                              } else {
                                  // 2. 默认的ignore几个错误提示
                                  text = defaultPrompt.ignore[error];
                              }
  
                              text = text || defaultPrompt.ignore.empty;
                          }
  
                          break;
                      }
                      case 'unmatch': {
                          // 这里的默认错误提示没有一个和type关联
                          // 因此，逻辑跟上面的是有差异的
  
                          // 首先，优先使用自定义提示是一样的
                          text = optionsPrompt.unmatch;
                          if (!text) {
                              // 然后
                              if (type && name) {
                                  // 1. 试试关键字提示
                                  text = name + '格式不符合要求';
                              } else {
                                  // 2. 宽泛的默认提示
                                  text = defaultPrompt.unmatch[error] || defaultPrompt.unmatch.pattern;
                              }
                              text = text || defaultPrompt.ignore.empty;
                          }
                          break;
                      }
                      case 'out': {
                          // 先看看有没有自定义的提示
                          text = optionsPrompt.out && optionsPrompt.out[error];
  
                          if (!text) {
                              // out可以是range类型, date类型，number类型
                              if (type && name) {
                                  var min = ele.getAttribute('min');
                                  var max = ele.getAttribute('max');
                                  var step = ele.getAttribute('step') * 1 || 1;
  
                                  if (type == 'month-range') {
                                      min = min.slice(0, 7);
                                      max = max.slice(0, 7);
                                  }
  
                                  var textBigger = '必须要大于或等于' + min;
                                  var textSmaller = '必须要小于或等于' + max;
  
                                  if (error == 'min') {
                                      text = name + textBigger;
                                      if (type.slice(-6) == '-range') {
                                          text = '起始日期' + textBigger;
                                      }
                                  } else if (error == 'max') {
                                      text = name + '必须要小于或等于' + max;
                                      if (type.slice(-6) == '-range') {
                                          text = '结束日期' + textSmaller;
                                      }
                                  } else if (error == 'min-max') {
                                      text = '起始日期' + textBigger + '，结束日期' + textSmaller;
                                  } else if (error == 'step') {
                                      if (type == 'number' || type == 'range') {
                                          text = '请输入有效的值。两个最接近的有效值是' + (function() {
                                              min = min * 1;
                                              max = max * 1;
                                              var value = $.trim(ele.value) * 1;
                                              var closest = min;
                                              for (var start = min; start < max; start += step) {
                                                  if (start < value && (start + step) > value) {
                                                      closest = start;
                                                      break;
                                                  }
                                              }
  
                                              return [closest, closest + step].join('和');
                                          })();
                                      } else {
                                          text = '请选择有效的值。' + name + '间隔是' + step;
                                      }
                                  }
                              } else {
                                  // 纯粹使用含糊的默认提示
                                  text = defaultPrompt.out[error];
                              }
                          }
  
                          text = text || defaultPrompt.out.step;
                          break;
                      }
                      case 'overflow': {
                          // 先看看有没有自定义的提示
                          text = optionsPrompt.overflow && optionsPrompt.overflow[error];
                          if (!text) {
                              var minlength = ele.getAttribute('minlength');
                              var maxlength = ele.maxlength || ele.getAttribute('maxlength');
  
                              // 关于字符个数说明
                              var lang = ele.getAttribute('lang');
                              var textCharLength = '';
  
                              if (/zh/i.test(lang)) {
                                  textCharLength = '个汉字(2字母=1汉字)';
                              } else if (/en/i.test(lang)) {
                                  textCharLength = '个字符(1汉字=2字符)';
                              }
  
                              if (error == 'minlength') {
                                  text = '内容长度不能小于' + minlength + textCharLength;
                              } else if (error == 'maxlength') {
                                  text = '内容长度不能大于' + maxlength.replace(/\D/g, '') + textCharLength;
                              }
                          }
                          text = text || defaultPrompt.overflow[error];
                          break;
                      }
                  }
  
                  if ($.isFunction(text)) {
                      text = text.call(ele, $(ele));
                  }
  
                  return text;
              },
  
              /*
              ** 验证一般包括下面几个个方向：
              ** 1. 是否required同时值为空
              ** 2. 是否数据匹配错误(pattern, type)
              ** 3. 是否超出范围(min/max/step)
              ** 4. 内容超出限制(minlength, maxlength)
              ** 下面的这些方法就是上面3个方向判断
              ** 其中，参数ele为DOM对象
              ** 返回数据规则如下：
              ** 如果没有把柄，返回false;
              ** 如果真的有误，则返回错误类别：{
                    type: ''
                 }
              }
              */
  
              /**
               * 判断元素是否为空的验证
               * @param  {Element}  ele 验证的DOM元素
               * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
               */
              isIgnore: function(ele) {
                  // 主要针对required必填或必选属性的验证
                  if (!ele || ele.disabled) return false;
  
                  // 类型
                  var type = $.getType(ele);
  
                  // 此时的值才是准确的值（浏览器可能会忽略文本框中的值，例如number类型输入框中的不合法数值）
                  var el = $(ele);
                  var value = ele.value;
  
                  if (el.isProp('required')) {
  
                      // 根据控件类型进行验证
                      // 1. 单复选框比较特殊，先处理下
                      if (type == 'radio') {
                          // 单选框，只需验证是否必选，同一name单选组只有要一个设置required即可
                          var eleRadios = self;
                          var eleForm = el.parents('form');
  
                          if (ele.name && eleForm.length) {
                              eleRadios = eleForm.find("input[type='radio'][name='" + ele.name + "']");
                          }
  
                          // 如果单选框们没有一个选中，则认为无视了required属性
                          var atLeastOneRadioChecked = false;
  
                          eleRadios.each(function() {
                              if (atLeastOneRadioChecked == false && $(this).prop('checked')) {
                                  atLeastOneRadioChecked = true;
                              }
                          });
  
                          if (atLeastOneRadioChecked == false) {
                              // 没有一个单选框选中
                              return {
                                  type: 'ignore',
                                  error: 'radio'
                              };
                          }
  
                          return false;
                      } else if (type == 'checkbox') {
                          // 复选框是每一个都必须选中
                          if (el.prop('checked') == false) {
                              return {
                                  type: 'ignore',
                                  error: 'checkbox'
                              };
                          }
  
                          return false;
                      } else if (/select/.test(type) && value == '') {
                          return {
                              type: 'ignore',
                              error: 'select'
                          };
                      } else if (type != 'password') {
                          value = $.trim(value);
                      }
  
                      if (value == '') {
                          // 重新赋值，清空可能被浏览器默认认为不合法的值
                          // 但是需要是现代浏览器，低版本IE会不断触发属性改变事件
                          if (history.pushState) ele.value = '';
  
                          // 返回验证结果
                          return {
                              type: 'ignore',
                              error: 'empty'
                          };
                      }
                  }
  
                  return false;
              },
  
              /**
               * 判断元素值的合法性
               * @param  {Element}  ele 验证的DOM元素
               * @param  {RegExp} regex 验证的正则
               * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
               */
              isUnmatch: function(ele, regex, params) {
                  // 禁用元素不参与验证
                  if (!ele || ele.disabled) return false;
  
                  var el = $(ele);
  
                  // 原始值和处理值
                  var inputValue = ele.value;
                  var dealValue = inputValue;
  
                  // 类型
                  var type = $.getType(ele);
  
                  // 特殊控件不参与验证
                  if (/^radio|checkbox|select$/i.test(type)) return false;
  
                  // 全角半角以及前后空格的处理
                  // 适用于type=url/email/tel/zipcode/number/date/time等
                  if (type != 'password') {
                      dealValue = $.trim(inputValue);
                  }
                  if (/^text|textarea|password$/i.test(type) == false) {
                      dealValue = $.dbc2sbc(dealValue);
                  }
                  if (type == 'tel') {
                      dealValue = $.telFilter(dealValue);
                  }
  
                  //  文本框值改变，重新赋值
                  // 即时验证的时候，不赋值，否则，无法输入空格
                  if ($.validate.focusable !== false && $.validate.focusable !== 0 && dealValue != inputValue) {
                      ele.value = dealValue;
                  }
  
                  // 如果没有值，则认为通过，没有错误
                  if (dealValue == '') return false;
  
                  // 获取正则表达式，pattern属性获取优先，然后通过type类型匹配。注意，不处理为空的情况
                  regex = regex || (function() {
                      return el.attr('pattern');
                  })() ||
                  (function() {
                      // 文本框类型处理，可能有管道符——多类型重叠，如手机或邮箱
                      return type && $.map(type.split('|'), function(typeSplit) {
                          var matchRegex = $.validate.reg[typeSplit];
                          if (matchRegex) {
                              return matchRegex;
                          }
                      }).join('|');
                  })();
  
                  // 如果没有正则匹配的表达式，就没有验证的说法，认为出错为false
                  if (!regex) return false;
  
                  // multiple多数据的处理
                  var isMultiple = el.isProp('multiple');
                  var newRegExp = new RegExp(regex, params || 'i');
                  // number,range等类型下multiple是无效的
                  if (isMultiple && /^number|range$/i.test(type) == false) {
                      var isAllPass = true;
                      $.each(dealValue.split(','), function(i, partValue) {
                          partValue = $.trim(partValue);
                          if (isAllPass && !newRegExp.test(partValue)) {
                              isAllPass = false;
                          }
                      });
                      // 有数据没有匹配
                      if (isAllPass == false) {
                          return {
                              type: 'unmatch',
                              error: 'multiple'
                          };
                      }
                  } else if (newRegExp.test(dealValue) == false) {
                      return {
                          type: 'unmatch',
                          error: 'pattern'
                      };
                  }
  
                  return false;
              },
  
              /**
               * 判断数值或日期范围超出
               * @param  {Element}  ele 验证的DOM元素
               * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
               */
              isOut: function(ele) {
                  // 是否数值或日期范围超出
                  if (!ele || ele.disabled || /^radio|checkbox|select$/i.test(ele.type)) return false;
  
                  var el = $(ele);
  
                  var attrMin = el.attr('min');
                  var attrMax = el.attr('max');
                  var attrStep = Number($(ele).attr('step')) || 1;
                  // 值
                  var type = $.getType(ele);
                  var value = ele.value;
  
                  if (type.slice(-6) != '-range') {
                      if (value == '0' || Number(value) == value) {
                          value = value * 1;
                      }
                      if (attrMin && value < attrMin) {
                          return {
                              type: 'out',
                              error: 'min'
                          };
                      } else if (attrMax && value > attrMax) {
                          return {
                              type: 'out',
                              error: 'max'
                          };
                      }
  
                      // number, range类型的范围
  
                      if ((type == 'number' || type == 'range') && attrStep && attrMin &&
                          !/^\d+$/.test(Math.abs(value - attrMin) / attrStep)
                      ) {
                          return {
                              type: 'out',
                              error: 'step'
                          };
                      }
                      // hour, minute, time类型的范围
                      if ((type == 'hour' || type == 'minute' || type == 'time') && attrMin && attrStep) {
                          var minuteValue = value.split(':')[1];
                          var minuteMin = attrMin.split(':')[1];
  
                          if (type == 'hour' && (minuteValue != minuteMin || (value.split(':')[0] - attrMin.split(':')[0]) % attrStep != 0)) {
                              // 小时，则是分钟要一致，小时数正好step
                              return {
                                  type: 'out',
                                  error: 'step'
                              };
                          } else if ((minuteValue - minuteMin) % attrStep !== 0) {
                              return {
                                  type: 'out',
                                  error: 'step'
                              };
                          }
                      }
                  } else {
                      // 时间范围
                      var arrSplitValue = value.split(' ');
                      var error = [];
  
                      // 防止month-range时候，min/max设置的是完整时间，
                      // 如2017-07-07，而不是2017-07
                      if (type == 'month-range') {
                          attrMin = attrMin && attrMin.slice(0, 7);
                          attrMax = attrMax && attrMax.slice(0, 7);
                      }
  
                      if (arrSplitValue.length == 3) {
                          if (attrMin && arrSplitValue[0] < attrMin) {
                              error.push('min');
                          }
                          if (attrMax && arrSplitValue[2] > attrMax) {
                              error.push('max');
                          }
                          if (error.length) {
                              return {
                                  type: 'out',
                                  error: error.join('-')
                              };
                          }
                      }
                  }
  
                  return false;
              },
  
              /**
               * 内容是否超出长度限制的判断
               * @param  {Element}  ele   DOM元素对象
               * @return {Boolean|Object} 返回验证通过布尔值或者出错信息对象
               */
              isOverflow: function(ele) {
                  // 是否内容长度溢出的判断
                  if (!ele || ele.disabled || /^radio|checkbox|select$/i.test(ele.type)) return false;
  
                  var el = $(ele);
                  //  大小限制
                  var attrDataMin = el.attr('minlength');
                  var attrDataMax = ele.maxlength || el.attr('maxlength');
                  // 值
                  var value = ele.value;
  
                  if (value == '') {
                      return false;
                  }
  
                  var length = $.getLength(ele);
  
                  if (attrDataMin && length < attrDataMin) {
                      return {
                          type: 'overflow',
                          error: 'minlength'
                      };
                  }
  
                  if (attrDataMax) {
                      attrDataMax = attrDataMax.replace(/\D/g, '');
                      if (length > attrDataMax) {
                          return {
                              type: 'overflow',
                              error: 'maxlength'
                          };
                      }
                  }
  
                  return false;
              },
  
              /**
               * 判断单个DOM元素是否验证通过
               * @param  {Element}  ele DOM元素对象
               * @return {Boolean}      是否验证通过
               */
              isError: function(ele) {
                  // 1. 元素不存在不验证
                  // 2. 禁用态表单元素不参与验证
                  if (!ele || ele.disabled) {
                      return false;
                  }
  
                  // 3. 提交、重置等元素不参与验证
                  var type = ele.getAttribute('type') || ele.type;
                  var tag = ele.tagName.toLowerCase();
  
                  if (/^button|submit|reset|file|image$/.test(type) == true || tag == 'button') return false;
  
                  return $.validate.isIgnore(ele) || $.validate.isUnmatch(ele) || $.validate.isOut(ele) || $.validate.isOverflow(ele) || false;
              },
  
              /**
               * 系列元素是否验证通过
               * @param  {Element|Object}  el      [description]
               * @param  {Object}  options [description]
               * @return {Boolean}         [description]
               */
              isAllPass: function(el, options) {
                  // el可以是独立的DOM, 也可以是form元素，也可以是jQuery序列元素
                  if (!el) {
                      return true;
                  }
  
                  el = $(el);
                  if (el.is('form')) {
                      el = el.find(':input');
                  }
  
                  // 遍历以及验证
                  var isAllPass = true;
  
                  el.each(function() {
                      var ele = this;
                      // 一些属性
  
                      // 实现现代浏览器下利用:invalid选择器即时响应效果
                      var type = ele.getAttribute('type') || ele.type;
                      var pattern = ele.getAttribute('pattern');
  
                      if (!pattern && type) {
                          pattern = $.validate.reg[type];
                          if (pattern) {
                              ele.setAttribute('pattern', pattern);
                          }
                      }
  
                      // 1. 采用单验证模式，所以，一旦发现isAllPass为false, 我们停止接下来的验证
                      // 2. 禁用态的元素不验证
                      // 3. 提交按钮、重置按钮等不验证
                      if (isAllPass == false || ele.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
  
                      var result = $.validate.isError(ele);
                      // 当我们希望进行全局验证，又不希望出现tips提示的时候
                      // 可以这么调用 $.validate.isAllPass(el, false);
                      if (result && options !== false) {
                          // 提示错误
                          $.validate.errorTip(ele, $.validate.prompt(result, ele, options));
                          isAllPass = false;
                      }
                  });
  
                  return isAllPass;
              },
  
              /**
               * 计数效果
               * @param  {Object} el 需要计数的元素
               * @return {Object}    返回当前上下文
               */
              count: function(el) {
                  if (!el) {
                      return this;
                  }
                  var ele;
                  // 计数相关代码构建
                  // el只能是单个包装器对象，其余的会忽略
                  if (el.get) {
                      ele = el.get(0);
                  } else {
                      ele = el;
                      el = $(el);
                  }
  
                  // 标签名
                  var tag = ele.tagName.toLowerCase();
  
                  // 首先ele需要是表单元素
                  if (tag != 'input' && tag != 'textarea') {
                      return this;
                  }
  
                  // 获得限制大小
                  var min = el.attr('minlength') || 0;
                  var max = ele.maxlength || el.attr('maxlength');
  
                  if (!min && !max) {
                      return this;
                  }
                  // 过滤非数值字符
                  max = max.replace(/\D/g, '');
  
                  // 类名前缀
                  var clPrefix = 'ui-';
                  // 其他类名
                  var cl = clPrefix + tag;
                  var clBox = cl + '-x';
                  var clCount = cl + '-count';
  
                  // 存在下面3种情况
                  // 1. 普通文本框需要变身
                  // 2. 缺少计数label
                  // 3. HTML已经完整
                  var elLabel, elMin;
                  // , elMax
                  if (el.hasClass(cl)) {
                      // 情况1先不支持
                      return this;
                  }
                  // 情况2
                  // ui-input-x
                  //    input
                  //    div.ui-input
                  // 缺少label，也就是数值元素
                  if (el.parent('.' + clBox).length == 0) {
                      return this;
                  }
  
                  // 元素id必须
                  var id = ele.id;
                  if (!id) {
                      // 没有id随机id
                      id = 'id' + (Math.random() + '').replace(/\D/g, '');
                      ele.id = id;
                  }
  
                  elLabel = el.parent().find('.' + clCount);
                  if (elLabel.length == 0) {
                      // 生成并载入计数元素
                      elLabel = $('<label for="' + id + '" class="ui-' + tag + '-count">\
                      <span>' + min + '</span>/<span>' + max + '</span>\
                  </label>');
                      el.parent().append(elLabel);
                  } else {
                      elLabel.attr('for', id);
                  }
                  elMin = elLabel.find('span').eq(0);
                  //elMax = eleLabel.find('span').eq(1);
  
                  // 目标元素
                  //var target = $.validate.getTarget(el);
                  // 计数，标红方法
                  var fnCount = function() {
                      var length = $.getLength(ele);
                      elMin.html(length);
                      if (length > max) {
                          elMin.addClass('red');
                          // 下面注释是因为不需要即时变红，只要文字提示就可以
                          // 当验证组件开启即时验证之后，会标红的
                          //target.addClass('error');
                      } else {
                          elMin.removeClass('red');
                          //target.removeClass('error');
                      }
                  };
                  // 事件
                  var flagCount = 'countBind';
                  if (!el.data(flagCount)) {
                      if ('oninput' in document.createElement('div')) {
                          el.bind('input', function() {
                              fnCount();
                          });
                      } else {
                          ele.attachEvent('onpropertychange', function(event) {
                              if (event && event.propertyName == 'value') {
                                  fnCount();
                              }
                          });
                      }
                      el.data(flagCount, true);
                  }
  
                  fnCount();
  
                  return this;
              },
  
              /**
               * 显示出错信息处理
               * @param  {Object} el      出错提示原始元素（这里可能会进行转移）
               * @param  {String} content 出错提示内容
               * @return {Object}         返回当前上下文
               */
              errorTip: function(el, content) {
                  var target = $.validate.getTarget(el);
                  if (target.length == 0) {
                      return this;
                  }
  
                  var show = function() {
                      target.errorTip(content, {
                          onShow: function(tips) {
                              // 如果tips宽度比较小，居左定位
                              // 否则，使用默认的居中
                              var pffsetx = 0.5 * (tips.width() - target.width());
                              if (pffsetx < 0) {
                                  tips.css('margin-left', 0.5 * (tips.width() - target.width()));
                              } else {
                                  tips.css('margin-left', 0);
                              }
  
                              if ($.validate.focusable === false) {
                                  tips.addClass('none');
                              } else {
                                  tips.removeClass('none');
                              }
                          },
                          onHide: function() {
                              // 前提是开启了即时验证
                              if (!target.parents('form').data('immediate')) {
                                  return;
                              }
                              // 自定义验证方法的即时验证
                              var customValidate = $.validate.el.control.data('customValidate');
                              var result = customValidate && customValidate($.validate.el.control);
                              if ($.validate.isError($.validate.el.control.get(0)) || (typeof result == 'string' && result.length)) {
                                  $.validate.el.target.addClass('error');
                              }
  
                              target.removeAttr('aria-label');
                          }
                      });
  
                      // 即时验证不需要focus与选中
                      if ($.validate.focusable === false || $.validate.focusable === 0) {
                          return;
                      }
                      $.validate.focusable = null;
  
                      var el = $.validate.el.control;
                      var ele = el.get(0);
                      var type = ele.getAttribute('type') || ele.type;
  
                      if (!type) {
                          return;
                      }
  
                      // 如果是内容超出，选中超出的文字内容
                      if (content.indexOf('内容长度') != -1 && content.indexOf('大') != -1) {
                          var value = el.val();
                          var length = value.length;
                          // var lang = el.attr('lang');
                          // 中文和英文计算字符个数的比例
                          // var ratioCh = 1;
                          // var ratioEn = 1;
                          // if (/zh/i.test(lang)) {
                          //     // 1个英文半个字符
                          //     ratioEn = 0.5;
                          // } else if (/en/i.test(lang)) {
                          //     // 1个中文2个字符
                          //     ratioCh = 2;
                          // }
                          // 获得最大长度的索引位置
                          var maxlength = ele.maxlength || el.attr('maxlength').replace(/\D/g, '');
  
                          if (length && maxlength) {
                              el.selectRange($.getLength(ele, maxlength), length);
                          }
                      } else {
                          el.select();
                      }
                  };
  
                  $.validate.el = {
                      control: $(el),
                      target: target
                  };
  
                  // 1. 首先，确保el在屏幕内，且需要预留tips的显示的足够距离，这里使用50像素
                  var rect = target.get(0).getBoundingClientRect();
                  if (rect.top < 50) {
                      $('html, body').animate({
                          'scrollTop': $(window).scrollTop() - (50 - rect.top)
                      }, 200, show);
                  } else if (rect.bottom > $(window).height()) {
                      $('html, body').animate({
                          'scrollTop': $(window).scrollTop() + (rect.bottom - $(window).height())
                      }, 200, show);
                  } else {
                      show();
                  }
  
                  return this;
              },
  
              /**
               * 获得对应展示的元素
               * @param  {Object} el 元素
               * @return {Object}    返回对应的展示元素（可能就是自身）
               */
              getTarget: function(el) {
                  var ele = el;
                  if (!el) return $();
                  if (el.get) {
                      ele = el.get(0);
                  } else {
                      el = $(el);
                  }
                  if (el.length == 0) return;
  
                  var target = el;
  
                  // 根据原生控件元素，确定真实的自定义控件元素
                  // 这里的很多处理跟其他表单UI组件想耦合、关联
                  var type = ele.getAttribute('type') || ele.type;
                  var id = ele.id;
                  var tag = ele.tagName.toLowerCase();
                  // 1. 单复选框
                  if (type == 'radio') {
                      target = el.parent().find('label.ui-radio[for=' + id + ']');
                  } else if (type == 'checkbox') {
                      target = el.parent().find('label.ui-checkbox[for=' + id + ']');
                  // 下拉框
                  } else if (type == 'select' || tag == 'select') {
                      target = el.next('.ui-select');
                  // range范围选择框
                  } else if (type == 'range') {
                      target = el.prev('.ui-range');
                  } else if (type == 'hidden' || el.css('display') == 'none') {
                      if (el.data('target') && el.data('target').size) {
                          target = $.validate.getTarget(el.data('target'));
                      }
                  } else if (type == 'textarea' || tag == 'textarea') {
                      if (el.nextAll('.ui-textarea').length) {
                          target = el.nextAll('.ui-textarea').eq(0);
                      } else if (!document.querySelector && el.parent('.ui-textarea').length) {
                          target = el.parent('.ui-textarea');
                      }
                  } else if (tag == 'input') {
                      if (el.nextAll('.ui-input').length) {
                          target = el.nextAll('.ui-input').eq(0);
                      } else if (!document.querySelector && el.parent('.ui-input').length) {
                          target = el.parent('.ui-input');
                      }
                  }
  
                  return target;
              }
          };
      })();
  
      /**
       * 验证实例方法主体
       * @param {Object}   el       通常值验证的表单元素
       * @param {Function} callback 验证成功的回调
       * @param {Object}   options  可选参数
       */
      var Validate = function(el, callback, options) {
          // el一般指的是form元素
          if (!el.eq) el = $();
  
          callback = callback || $.noop;
  
          // 干掉浏览器默认的验证
          el.attr('novalidate', 'novalidate');
  
          var defaults = {
              // 提交时候是全部出错红色高亮，还是仅第一个，默认是全部
              multiple: true,
              // 是否开启即时验证
              immediate: true,
              // 是否利用label关联元素的innerHTML作为提示关键字
              label: false,
              validate: [
              // 下面为结构示意
              /*{
                  id: '',
                  prompt: {
                      ignore: '',
                      unmatch: '',
                      out: {
                          min: '',
                          max: '',
                          step: ''
                      },
                      overflow: {
                          minlength: '',
                          maxlength: ''
                      }
                  },
                  method: $.noop
              }*/
              ],
              onError: $.noop,
              onSuccess: $.noop
          };
  
          var params = $.extend({}, defaults, options || {});
  
          // 还原禁用的提交和关闭按钮
          el.find(':disabled').each(function() {
              if (/^image|submit$/i.test(this.type)) {
                  $(this).removeAttr('disabled');
              }
          });
  
          var self = this;
          // 干掉默认的提交
          el.on('submit', function(event) {
              if (self.stopValidate) {
                  return;
              }
              event.preventDefault();
  
              if (self.isAllPass() && $.isFunction(callback)) {
                  callback.call(this, self);
              }
  
              return false;
          });
  
  
          // 暴露的数据
          this.el = {
              form: el
          };
  
          this.callback = {
              error: params.onError,
              success: params.onSuccess
          };
  
          this.data = params.validate;
  
          this.boo = {
              multiple: params.multiple,
              immediate: params.immediate,
              label: params.label
          };
  
          // 计数效果
          this.count();
  
          // 手机号过滤等增强体验功能
          this.enhance();
  
          return this;
      };
  
      /**
       * 表单内有计数功能元素的处理
       * @return {Object} 返回当前实例
       */
      Validate.prototype.count = function() {
          // 计数效果
          var self = this;
          // 即时验证
          var form = this.el.form;
          // 遍历计数元素
          form.find('.ui-input-x > input, .ui-textarea-x > textarea').each(function() {
              var ele = this;
              var el = $(ele);
              // 对maxlength进行处理
              var maxlength = el.attr('maxlength');
              if (maxlength) {
                  // 给maxlength设置不合法的值，阻止默认的长度限制
                  try {
                      el.attr('maxlength', '_' + maxlength + '_');
                  } catch(e) {
                      // IE7不能设置不合法的maxlength值 %>_<%
                      el.removeAttr('maxlength')[0].maxlength = maxlength;
                  }
              }
              $.validate.count(el);
          });
  
          return self;
      };
  
      /**
       * 表单内一些元素体验增强处理
       * @return {Object} 返回当前实例
       */
      Validate.prototype.enhance = function () {
          var self = this;
          // 即时验证
          var form = self.el.form;
  
          form.find(':input').each(function() {
              // 元素
              var ele = this;
              // var el = $(ele);
              // type类型筛选
              // var type = ele.type;
              var attrType = ele.getAttribute('type');
  
              // 手机号码粘贴的优化处理
              if (ele.addEventListener && /^checkbox|radio|range$/i.test(attrType) == false) {
                  ele.addEventListener('paste', function (event) {
                      // 剪切板数据对象
                      // 输入框类型
                      var type = this.getAttribute('type') || this.type;
                      // 剪切板数据对象
                      var clipboardData = event.clipboardData || window.clipboardData;
                      // 粘贴内容
                      var paste = '';
                      // 剪切板对象可以获取
                      if (!clipboardData || ele.disabled == true) {
                          return;
                      }
                      // 获取选中的文本内容
                      var textSelected = '';
                      if (window.getSelection) {
                          // 现代浏览器
                          // 直接window.getSelection().toString()对于IE的输入框无效
                          textSelected = this.value.slice(ele.selectionStart, ele.selectionEnd);
                      } else if (document.selection) {
                          // 旧IE浏览器
                          textSelected = document.selection.createRange().text;
                      }
                      // 只有输入框没有数据，或全选状态才处理
                      if (this.value.trim() == '' || textSelected === this.value) {
                          // 阻止冒泡和默认粘贴行为
                          event.preventDefault();
                          event.stopPropagation();
                          // 获取粘贴数据
                          paste = clipboardData.getData('text') || '';
                          // 进行如下处理
                          // 除非是password，其他都过滤前后空格
                          if (type != 'password') {
                              paste = paste.trim();
                          }
                          // 邮箱处理，可能会使用#代替@避免被爬虫抓取
                          if (type == 'email') {
                              paste = paste.replace('#', '@');
                          } else if (type == 'tel') {
                              // 手机号处理
                              paste = $.telFilter(paste);
                          }
  
                          // 插入
                          this.value = paste;
                      }
                      
                      $(this).trigger('input');
                  });
              }
          });
      };
  
      /**
       * 表单即时验证的细节处理
       * @return {Object} 返回当前实例
       */
      Validate.prototype.immediate = function() {
          var self = this;
          // 即时验证
          var form = this.el.form;
  
          if (form.data('immediate')) {
              return this;
          }
  
          form.find(':input').each(function() {
              // 元素
              var ele = this;
              var el = $(ele);
              // type类型筛选
              var type = ele.type;
              var attrType = ele.getAttribute('type');
              // 给每个控件绑定即时验证
              if (type == 'button' || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
  
              // 不同类别不同事件
              if (type == 'radio' || type == 'checkbox') {
                  el.on('click', function() {
                      if (self.boo.immediate == false) return;
                      var isPass = self.isPass($(this));
                      if (isPass) {
                          self.isError($(this), false);
                      }
                  });
              } else if (/select/.test(type) || /range|date|time|hour|minute|hidden/.test(attrType)) {
                  el.on('change', function() {
                      if (self.boo.immediate == false) return;
                      var isPass = self.isPass($(this));
                      if (isPass) {
                          self.isError($(this), false);
                      }
                  });
              } else {
                  // 输入
                  el.on({
                      focus: function() {
                          if (self.boo.immediate) setTimeout(function() {
                              $.validate.focusable = 0;
                              var isPass = self.isPass(el);
                              if (isPass) {
                                  self.isError(el, false);
                              }
                          }, 20);
                      },
                      input: function() {
                          if (self.boo.immediate == false) return;
                          // IE10+下有个bug
                          // focus placeholder变化时候，会触发input事件，我只能呵呵了
                          if (document.msHidden != undefined && this.value == '' && !this.lastvalue && $(this).attr('placeholder')) {
                              this.lastvalue = this.value;
  
                              return;
                          }
  
                          $.validate.focusable = false;
                          var isPass = self.isPass($(this));
                          if (isPass) {
                              self.isError($(this), false);
                              if (window.errorTip) {
                                  window.errorTip.hide();
                              }
                          }
                          this.lastvalue = this.value;
                      }
                  });
  
                  // IE7-IE8 模拟 input
                  if ('oninput' in document.createElement('div') == false) {
                      ele.attachEvent('onpropertychange',  function(event) {
                          if (event && event.propertyName == 'value' && self.boo.immediate) {
                              $.validate.focusable = false;
                              var isPass = self.isPass($(ele));
                              if (isPass) {
                                  self.isError($(ele), false);
                              }
                              $.validate.focusable = true;
                          }
                      });
                  }
              }
          });
  
          form.data('immediate', true);
  
          return this;
      };
  
      /**
       * 表单验证是否有误的处理
       * @param  {Object}  el      需要验证的元素
       * @param  {Boolean} isError 是否有误（可以直接传递结果）
       * @return {Boolean}         返回验证结果
       */
      Validate.prototype.isError = function(el, isError) {
          // 是否有错
          if (!el || !el.get || !el.length) {
              return this;
          }
          var ele = el.get(0);
          var result = isError;
  
          if (typeof isError == 'undefined' && el.is(':disabled') == false) {
              var customValidate = el.data('customValidate');
              result = $.validate.isError(ele) || (customValidate && customValidate(el));
          }
  
          var target = $.validate.getTarget(el);
  
          if (result) {
              // 出错
              target.addClass('error');
          } else if (ele.type == 'radio' && ele.name) {
              this.el.form.find('input[type=radio][name="' + ele.name + '"]').each(function() {
                  $.validate.getTarget($(this)).removeClass('error').removeAttr('aria-label');
              });
          } else {
              target.removeClass('error').removeAttr('aria-label');
          }
  
          return result;
      };
  
      /**
       * 是否通过验证的处理（单个元素）
       * @param  {Object}  el 验证的元素
       * @return {Boolean}    是否验证通过，true表示验证OK，false表示验证有问题
       */
      Validate.prototype.isPass = function(el) {
          if (!el || !el.get || !el.length) return this;
          var ele = el.get(0);
          var id = ele.id;
          var label = this.boo.label;
  
          // 是否使用自定义的验证和提示
          var customValidate = {
              label: label
          };
          if (id && this.data && this.data.length) {
              $.each(this.data, function(index, obj) {
                  if (obj.id == id) {
                      // 最终的提示文字，是否借助label元素
                      // 参数传递路径: here -> $.validate.isAllPass -> $.validate.prompt
                      if (typeof obj.label != 'undefined') {
                          obj.label = label;
                      }
                      // 自定义验证参数
                      customValidate = obj;
                  }
              });
          }
  
          var isPass = $.validate.isAllPass(el, customValidate);
  
          if (isPass == true && customValidate && customValidate.method) {
              var content = customValidate.method.call(ele, el);
              if (typeof content == 'string' && content !== '') {
                  this.errorTip(el, content);
                  isPass = false;
              }
              el.data('customValidate', customValidate.method);
          }
  
          // 回调触发
          this.callback[isPass ? 'success' : 'error'].call(this, el);
  
          return isPass;
      };
  
      /**
       * 表单所有元素验证通过的判断处理
       * @return {Boolean} 是否表单所有元素验证通过
       */
      Validate.prototype.isAllPass = function() {
          var self = this;
          var form = this.el.form;
  
          var isAllPass = true;
  
          $.validate.focusable = true;
  
          form.find(':input').each(function() {
              if (isAllPass == true && self.isPass($(this)) == false) {
                  isAllPass = false;
              }
              if (self.boo.multiple) {
                  // 一次验证多个元素，只有一个出提示，其余标红
                  var input = this;
                  if (!form.data('immediate')) {
                      // self.data是自定义的验证数据
                      $.each(self.data, function(index, obj) {
                          if (obj.id == input.id && obj.method) {
                              $(input).data('customValidate', obj.method);
                          }
                      });
                  }
  
                  self.isError($(this));
              }
          });
  
          // 当有过一次提交之后，开启即时验证
          if (!form.data('immediate') && self.boo.immediate) {
              self.immediate();
          }
  
          return isAllPass;
  
      };
  
      /**
       * 错误提示的出现和处理
       * @param  {Object} el      提示的元素
       * @param  {String} content 提示的内容
       * @return {Object}         返回当前实例
       */
      Validate.prototype.errorTip = function(el, content) {
          $.validate.errorTip(el, content);
  
          return this;
      };
  
  
      return Validate;
  }));
  