///
/**
* binder validator
* @author vfasky
*
**/
'use strict'
import * as $ from 'jquery'
import * as mcore from 'mcore3'
import 'form-serializer'
if (typeof String.prototype.trim === 'undefined') {
(String).prototype.trim = function (x) {
return $.trim(x)
}
}
// 是否字母
const _isAlphabetReg = /^[A-Za-z]+$/
// 是否中文
const _isChrStr = /[^x00-xff]/
// 是否邮箱
const _isEmailReg = /^(?:[a-z0-9]+[_\-+.]+)*[a-z0-9]+@(?:([a-z0-9]+-?)*[a-z0-9]+.)+([a-z]{2,})+$/i
// 是否日期 20120409 | 2012-04-09 | 2012/04/09 | 2012.04.09 | 以上各种无 0 的状况
const _isDateReg = /^([1-2]\d{3})([-/.])?(1[0-2]|0?[1-9])([-/.])?([1-2]\d|3[01]|0?[1-9])$/
// 是否手机
const _isMobileReg = /^1[3-9]\d{9}$/
// 是否是数字和字母
const _isDigitOrAlphaReg = /^(\d|\w)+$/
/**
* 检查座机
* 座机:仅中国座机支持;区号可有 3、4位数并且以 0 开头;电话号不以 0 开头,最 8 位数,最少 7 位数
* 但 400/800 除头开外,适应电话,电话本身是 7 位数
* 0755-29819991 | 0755 29819991 | 400-6927972 | 4006927927 | 800...
*/
const _isTelReg = /^(?:(?:0\d{2,3}[- ]?[1-9]\d{6,7})|(?:[48]00[- ]?[1-9]\d{6}))$/
// 检查url
const _urlCheck = (function () {
let protocols = '((https?|s?ftp|irc[6s]?|git|afp|telnet|smb):\\/\\/)?'
let userInfo = '([a-z0-9]\\w*(\\:[\\S]+)?\\@)?'
let domain = '(?:localhost|(?:[a-z0-9]+(?:[-\\w]*[a-z0-9])?(?:\\.[a-z0-9][-\\w]*[a-z0-9])*)*\\.[a-z]{2,})'
let port = '(:\\d{1,5})?'
let ip = '\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}'
let address = '(\\/\\S*)?'
let domainType = [protocols, userInfo, domain, port, address]
let ipType = [protocols, userInfo, ip, port, address]
let rDomain = new RegExp('^' + domainType.join('') + '$', 'i')
let rIP = new RegExp('^' + ipType.join('') + '$', 'i')
return function (x) {
return rDomain.test(x) || rIP.test(x)
}
})()
let _rule = {
// 不能为空
required: function (x: any = '', rule = null) {
if (rule == null) return String(x).trim().length > 0
if (x.$form.find(rule).val()) {
return String(x).trim().length > 0
}
return true
},
// trim
trim: function (x = '') {
return {
type: 'formatter',
val: String(x).trim()
}
},
// 是否字母
isAlphabet: (x = '') => _isAlphabetReg.test(String(x)),
// 是否是数字和字母
onlyDigitAndAlpha: (x = '') => _isDigitOrAlphaReg.test(String(x)),
// 最小长度
minlength: function (x: string, len: number) {
len = Number(len)
x = String(x).trim()
return x.length >= len
},
// 最大长度
maxlength: function (x: string, len: number) {
len = Number(len)
x = String(x).trim()
return x.length <= len
},
// 最大中文长度
maxChrLen: function (x: string, len: number) {
len = Number(len)
x = String(x).trim()
let strLen = 0
for (let i = 0; i < x.length; i++) {
let v = x[i]
if (v.match(_isChrStr)) {
strLen += 1
}
else {
strLen += 0.5
}
}
return strLen <= len
}, // 最小中文长度
minChrLen: function (x: string, len: number) {
len = Number(len)
x = String(x).trim()
let strLen = 0
for (let i = 0; i < x.length; i++) {
let v = x[i]
if (v.match(_isChrStr)) {
strLen += 1
}
else {
strLen += 0.5
}
}
return strLen >= len
},
// 只能是数字
isNumber: (x) => $.isNumeric(String(x)),
// 是否为整数
isInteger: (x) => Number(x) % 1 === 0,
// 最小值
min: (x: number, min: number) => Number(x) >= Number(min),
// 最小值
max: (x: number, max: number) => Number(x) <= Number(max),
// 是否相符
equals: function (x: any, value: any) {
if (value instanceof $) value = value.val()
if (x instanceof $) x = x.val()
return String(x) == String(value)
},
// 是否邮箱
isEmail: (x) => _isEmailReg.test(String(x)),
// 检查日期
isDate: function (x) {
if (_isDateReg.test(String(x))) return false
let taste = _isDateReg.exec(String(x))
let year = Number(taste[1])
let month = Number(taste[3]) - 1
let day = Number(taste[5])
let d = new Date(year, month, day)
return year === d.getFullYear() && month === d.getMonth() && day === d.getDate()
},
// 是否手机
isMobile: (x) => _isMobileReg.test(String(x)),
// 是否座机
isTel: (x) => _isTelReg.test(String(x)),
// url
isUrl: (x) => _urlCheck(String(x))
}
let _errMsg = {
required: '不能为空',
isNumber: '只能是数字',
isAlphabet: '只能是字母',
onlyDigitAndAlpha: '只能是字母和数字',
minlength: (len) => `最小 ${len} 位字符`,
minChrLen: (len) => `最小 ${len} 个中文 或 ${Number(len) * 2} 个英文`,
maxChrLen: (len) => `最多 ${len} 个中文 或 ${Number(len) * 2} 个英文`,
maxlength: (len) => `最多 ${len} 位字符`,
min: (min) => `数值要大于 ${min}`,
max: (max) => `数值要小于 ${max}`,
equals: '两次输入的值不相符',
isEmail: '邮箱格式不正确',
isInteger: '数值必须是整数',
isDate: '日期格式不正确',
isMobile: '手机格式不正确',
isTel: '座机格式不正确'
}
// 解释验证规则
function parseValidator($el: JQuery, rules = [], $form: JQuery) {
let name = $el.attr('name')
if (!name) return
let validatorAttr = $el.attr('validator').trim()
if (!validatorAttr) return
mcore.util.each(validatorAttr.split('|'), (v) => {
let diyErr
let err
let ix = String(v).indexOf(' err:')
if (ix != -1) {
let eT = v.split(' err:')
v = eT[0]
diyErr = eT[1]
}
let args: any[] = $.grep(v.split(' '), (s: string) => {
return $.trim(s).length > 0
})
let ruleType = args[0]
let checkRule = _rule[ruleType]
if (!checkRule) {
console.log("validator rule: #{ruleType} undefined")
return
}
if (diyErr) {
err = diyErr
} else {
if ($.isFunction(_errMsg[ruleType])) {
let msgArgs = args.slice(0)
msgArgs.splice(0, 1)
err = _errMsg[ruleType].apply($el[0], msgArgs)
} else {
err = _errMsg[ruleType] || 'error'
}
}
args[0] = $el
if (ruleType == 'equals') args[1] = $form.find(args[1]).eq(0)
rules.push({
name: name,
type: ruleType,
rule: checkRule,
args: args,
err: err
})
})
}
// 取规则
function getRules($form: JQuery) {
let rules = []
$form.find('[validator]').each(function () {
parseValidator($(this), rules, $form)
})
return rules
}
// 取对应name的值
function getNameValue(data, name: string, $el: JQuery) {
name = String(name)
if (-1 == name.indexOf('[')) {
return data[name] || ''
}
return $el.val().trim()
}
/**
* 表单验证
* @example
* ```html
*
* ```
* > **注意** 有 `name` 属性的元素才会验证
*
* ```ts
* import 'mcore-ext/binder/validator'
*
* // view.ts
* export default class Read extends View {
* static get viewName () {
* return 'test'
* }
*
* // errInfo 错误信息,如果没有错误,为 null
* // data 验证成功后,表单的数据
* save(errInfo, data: any) {
* }
* }
* ```
* ##### 验证规则
* | 名称 | 描述 | 参数 |
* |---------------|---------------|---------------|
* | trim | 去两边空白 | 无 |
* | required | 必填 | 无 |
* | isEmail | 是否邮箱 | 无 |
* | isUrl | 是否连接 | 无 |
* | isDate | 验证日期 20120409 , 2012-04-09 , 2012/04/09 , 2012.04.09 | 无 |
* | isAlphabet | 是否字母 | 无 |
* | isChrStr | 是否中文 | 无 |
* | isMobile | 是否手机 | 无 |
* | isNumber | 是否数字 | 无 |
* | isInteger | 是否整数 | 无 |
* | isTel | 是否座机 | 无 |
* | onlyDigitAndAlpha | 只能数字和字母 | 无 |
* | equals | 两次输入的值不相符, 通常用于 `重复密码` | cssSelect {string} 要与之相等的元素,在当前 form 中查找 |
* | min | 数值要小于等于${value} | value {number} |
* | max | 数值要大于等于${value} | value {number} |
* | maxLength | 最多 ${len} 位字符 | len {number} |
* | minLength | 最小 ${len} 位字符 | len {number} |
* | minChrLen | 最小 ${len} 个中文 或 ${len * 2} 个英文 | len {number} |
* | maxChrLen | 最多 ${len} 个中文 或 ${len * 2} 个英文 | len {number} |
*
*/
export let register = mcore.Template.binders['validator'] = mcore.Template.binders['validated'] = {
update: function (el: HTMLElement, value) {
if (el.tagName.toLowerCase() == 'form') {
el.setAttribute('novalidate', 'novalidate')
return
}
el.setAttribute('validator', value)
},
init: function (el, value) {
if (el.tagName.toLowerCase() != 'form' || !el._element) {
return el.setAttribute('validator', value)
}
let callback = mcore.Template.strToFun(el, value) || function () { }
let proxyEnv = mcore.Template.getEnv(el)
let $form = $(el)
// 禁用 html5 验证
el.setAttribute('novalidate', 'novalidate')
function validatorForm(callback = function (err, data) { }) {
let rules = getRules($form)
let data = ($form).serializeObject()
let err = null
$.each(rules, (k, v) => {
let $el = v.args[0]
let _value = getNameValue(data, v.name, $el)
if (v.type != 'required' && (_value == '' || _value == undefined)) return
let value = {
toString: () => String(_value),
toNumber: () => Number(_value),
$el: $el,
$form: $form
}
v.args[0] = value
let checkRes = v.rule.apply(null, v.args)
if (false === checkRes) {
err = {
$el: $el,
err: v.err,
$form: $form
}
return false
}
if (checkRes.type && checkRes.type === 'formatter') {
$el.val(checkRes.val)
data[v.name] = checkRes.val
}
})
return callback(err, data)
}
// 注入上下文
if (proxyEnv && !proxyEnv[value + 'Check']) {
proxyEnv[value + 'Check'] = function () {
return new Promise((resolve, reject) => {
validatorForm((err, data) => {
if (err) return reject(err)
resolve(data)
})
})
}
}
$form.off('submit.mc-binder-validator').on('submit.mc-binder-validator', () => {
try {
validatorForm(callback)
} catch (err) {
console.error(err.stack)
}
return false
})
}
}
/**
* 添加验证规则
* @param x 规则名称
* @param fun 验证函数
* @param errMsg 失败提示
* @example
* ```ts
* import * as validator from 'mcore-ext/binder/validator'
*
* validator.add('eq0', (x)=> {
* return x === 0
* }, '不等于 0 ')
*
* validator.add('eqX', (x)=> {
* return x === 0
* }, (x) => `不等于 ${x}`)
* ```
* #### use
*
* ```html
*
* ```
*/
export function add(x: string, fun, errMsg: string | any = '') {
_rule[x] = fun
if (errMsg) _errMsg[x] = errMsg
}
/**
* 调用验证规则
* @example
* ```ts
* import * as validator from 'mcore-ext/binder/validator'
*
* console.log(validator.check('isEmail', 'xxx@gmail.com')) // return true
* console.log(validator.check('isEmail', 'xxx@gmailcom')) // return false
* console.log(validator.check('minLength', 'test', 10)) // return false
* ```
*/
export function check(...args): boolean {
if (args.length < 2) return false
let type = args[0]
args.splice(0, 1)
if (!_rule[type]) return false
return _rule[type].apply(null, args)
}
/**
* 添加或替换错误提示信息
* **注** 通常用于 i18n
*/
export function addErrMsg(type: string, msg: string | any) {
_errMsg[type] = msg
}