avalon2
Version:
an elegant efficient express mvvm framework
251 lines (233 loc) • 8.96 kB
JavaScript
import { avalon, isObject, platform } from '../seed/core'
var valiDir = avalon.directive('validate', {
diff: function(validator) {
var vdom = this.node
if (vdom.validator) {
return
}
if (isObject(validator)) {
//注意,这个Form标签的虚拟DOM有两个验证对象
//一个是vmValidator,它是用户VM上的那个原始子对象,也是一个VM
//一个是validator,它是vmValidator.$model, 这是为了防止IE6-8添加子属性时添加的hack
//也可以称之为safeValidate
vdom.validator = validator
validator = platform.toJson(validator)
validator.vdom = vdom
validator.dom = vdom.dom
for (var name in valiDir.defaults) {
if (!validator.hasOwnProperty(name)) {
validator[name] = valiDir.defaults[name]
}
}
validator.fields = validator.fields || []
vdom.vmValidator = validator
return true
}
},
update: function(vdom) {
var vmValidator = vdom.vmValidator
var validator = vdom.validator
var dom = vdom.dom
dom._ms_validate_ = vmValidator
collectFeild(vdom.children, vmValidator.fields, vmValidator)
var type = window.netscape ? 'keypress' : 'focusin'
avalon.bind(document, type, findValidator)
//为了方便用户手动执行验证,我们需要为原始vmValidate上添加一个onManual方法
function onManual() {
var v = this
v && valiDir.validateAll.call(v, v.onValidateAll)
}
try {
var fn = vmValidator.onManual = onManual.bind(vmValidator)
validator.onManual = fn
} catch (e) {
avalon.warn('要想使用onManual方法,必须在validate对象预定义一个空的onManual函数')
}
delete vdom.vmValidator
dom.setAttribute('novalidate', 'novalidate')
/* istanbul ignore if */
if (vmValidator.validateAllInSubmit) {
avalon.bind(dom, 'submit', validateAllInSubmitFn)
}
},
validateAll: function(callback) {
var validator = this
var vdom = this.vdom
var fields = validator.fields = []
collectFeild(vdom.children, fields, validator)
var fn = typeof callback === 'function' ? callback : validator.onValidateAll
var promises = validator.fields.filter(function(field) {
var el = field.dom
return el && !el.disabled && validator.dom.contains(el)
}).map(function(field) {
return valiDir.validate(field, true)
})
var uniq = {}
return Promise.all(promises).then(function(array) {
var reasons = array.concat.apply([], array)
if (validator.deduplicateInValidateAll) {
reasons = reasons.filter(function(reason) {
var el = reason.element
var uuid = el.uniqueID || (el.uniqueID = setTimeout('1'))
if (uniq[uuid]) {
return false
} else {
return uniq[uuid] = true
}
})
}
fn.call(vdom.dom, reasons) //这里只放置未通过验证的组件
})
},
validate: function(field, isValidateAll, event) {
var promises = []
var value = field.value
var elem = field.dom
/* istanbul ignore if */
if (typeof Promise !== 'function') { //avalon-promise不支持phantomjs
avalon.warn('浏览器不支持原生Promise,请下载并<script src=url>引入\nhttps://github.com/RubyLouvre/avalon/blob/master/test/promise.js')
}
/* istanbul ignore if */
if (elem.disabled)
return
var rules = field.vdom.rules
var ngs = [],
isOk = true
if (!(rules.norequired && value === '')) {
for (var ruleName in rules) {
var ruleValue = rules[ruleName]
if (ruleValue === false)
continue
var hook = avalon.validators[ruleName]
var resolve
promises.push(new Promise(function(a, b) {
resolve = a
}))
var next = function(a) {
var reason = {
element: elem,
data: field.data,
message: elem.getAttribute('data-' + ruleName + '-message') || elem.getAttribute('data-message') || hook.message,
validateRule: ruleName,
getMessage: getMessage
}
if (a) {
resolve(true)
} else {
isOk = false
ngs.push(reason)
resolve(false)
}
}
field.data = {}
field.data[ruleName] = ruleValue
hook.get(value, field, next)
}
}
//如果promises不为空,说明经过验证拦截器
return Promise.all(promises).then(function(array) {
if (!isValidateAll) {
var validator = field.validator
if (isOk) {
validator.onSuccess.call(elem, [{
data: field.data,
element: elem
}], event)
} else {
validator.onError.call(elem, ngs, event)
}
validator.onComplete.call(elem, ngs, event)
}
return ngs
})
}
});
//https://github.com/RubyLouvre/avalon/issues/1977
function getValidate(dom) {
while (dom.tagName !== 'FORM') {
dom = dom.parentNode
}
return dom._ms_validate_
}
function validateAllInSubmitFn(e) {
e.preventDefault()
var v = getValidate(e.target)
if (v && v.onManual) {
v.onManual()
}
}
function collectFeild(nodes, fields, validator) {
for (var i = 0, vdom; vdom = nodes[i++];) {
var duplex = vdom.rules && vdom.duplex
if (duplex) {
fields.push(duplex)
bindValidateEvent(duplex, validator)
} else if (vdom.children) {
collectFeild(vdom.children, fields, validator)
} else if (Array.isArray(vdom)) {
collectFeild(vdom, fields, validator)
}
}
}
function findValidator(e) {
var dom = e.target
var duplex = dom._ms_duplex_
var vdom = (duplex || {}).vdom
if (duplex && vdom.rules && !duplex.validator) {
var msValidator = getValidate(dom)
if (msValidator && avalon.Array.ensure(msValidator.fields, duplex)) {
bindValidateEvent(duplex, msValidator)
}
}
}
function singleValidate(e) {
var dom = e.target
var duplex = dom._ms_duplex_
var msValidator = getValidate(e.target)
msValidator && msValidator.validate(duplex, 0, e)
}
function bindValidateEvent(field, validator) {
var node = field.dom
if (field.validator) {
return
}
field.validator = validator
/* istanbul ignore if */
if (validator.validateInKeyup && (!field.isChanged && !field.debounceTime)) {
avalon.bind(node, 'keyup', singleValidate)
}
/* istanbul ignore if */
if (validator.validateInBlur) {
avalon.bind(node, 'blur', singleValidate)
}
/* istanbul ignore if */
if (validator.resetInFocus) {
avalon.bind(node, 'focus', function(e) {
var dom = e.target
var field = dom._ms_duplex_
var validator = getValidate(e.target)
validator && validator.onReset.call(dom, e, field)
})
}
}
var rformat = /\\?{{([^{}]+)\}}/gm
function getMessage() {
var data = this.data || {}
return this.message.replace(rformat, function(_, name) {
return data[name] == null ? '' : data[name]
})
}
valiDir.defaults = {
validate: valiDir.validate,
onError: avalon.noop,
onSuccess: avalon.noop,
onComplete: avalon.noop,
onManual: avalon.noop,
onReset: avalon.noop,
onValidateAll: avalon.noop,
validateInBlur: true, //@config {Boolean} true,在blur事件中进行验证,触发onSuccess, onError, onComplete回调
validateInKeyup: true, //@config {Boolean} true,在keyup事件中进行验证,触发onSuccess, onError, onComplete回调
validateAllInSubmit: true, //@config {Boolean} true,在submit事件中执行onValidateAll回调
resetInFocus: true, //@config {Boolean} true,在focus事件中执行onReset回调,
deduplicateInValidateAll: false //@config {Boolean} false,在validateAll回调中对reason数组根据元素节点进行去重
}