UNPKG

lu2

Version:

Simple and flexible UI component library based on native HTML and JavaScript

1,403 lines (1,239 loc) 60.7 kB
/** * @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) {