Skip to content
This repository has been archived by the owner on Dec 25, 2017. It is now read-only.

Commit

Permalink
feat(async): support async validation
Browse files Browse the repository at this point in the history
Closes #156
  • Loading branch information
kazupon committed Mar 4, 2016
1 parent 54984d7 commit a21a0af
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 115 deletions.
11 changes: 11 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,14 @@ export function trigger (el, event, args) {
// non-attached form controls can throw errors
try { el.dispatchEvent(e) } catch (e) {}
}

/**
* Forgiving check for a promise
*
* @param {Object} p
* @return {Boolean}
*/

export function isPromise (p) {
return p && typeof p.then === 'function'
}
122 changes: 87 additions & 35 deletions src/validations/base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import util, { empty, each, trigger } from '../util'
import util, { empty, each, trigger, isPromise } from '../util'


/**
Expand Down Expand Up @@ -126,14 +126,16 @@ export default class BaseValidation {
this._validator.validate()
}

validate () {
validate (cb) {
const self = this
const _ = util.Vue.util
const vm = this._vm

let results = {}
let errors = []
let valid = true

each(this._validators, (descriptor, name) => {
this._runValidators((descriptor, name, done) => {
let asset = this._resolveValidator(name)
let validator = null
let msg = null
Expand All @@ -154,42 +156,48 @@ export default class BaseValidation {
}

if (validator) {
let ret = validator.call(this._vm, this._getValue(this._el), descriptor.arg)
if (!ret) {
valid = false
if (msg) {
let error = { validator: name }
error.message = typeof msg === 'function'
? msg.call(this._vm, this.field, descriptor.arg)
: msg
errors.push(error)
results[name] = error.message
let value = this._getValue(this._el)
this._invokeValidator(vm, validator, value, descriptor.arg, (ret) => {
if (!ret) {
valid = false
if (msg) {
let error = { validator: name }
error.message = typeof msg === 'function'
? msg.call(vm, self.field, descriptor.arg)
: msg
errors.push(error)
results[name] = error.message
} else {
results[name] = !ret
}
} else {
results[name] = !ret
}
} else {
results[name] = !ret
}

done()
})
} else {
done()
}
}, this)

this._fireEvent(this._el, valid ? 'valid' : 'invalid')

let props = {
valid: valid,
invalid: !valid,
touched: this.touched,
untouched: !this.touched,
dirty: this.dirty,
pristine: !this.dirty,
modified: this.modified
}
if (!empty(errors)) {
props.errors = errors
}
_.extend(results, props)
}, () => { // finished
this._fireEvent(this._el, valid ? 'valid' : 'invalid')

let props = {
valid: valid,
invalid: !valid,
touched: this.touched,
untouched: !this.touched,
dirty: this.dirty,
pristine: !this.dirty,
modified: this.modified
}
if (!empty(errors)) {
props.errors = errors
}
_.extend(results, props)

return results
cb(results)
})
}

resetFlags () {
Expand Down Expand Up @@ -240,9 +248,53 @@ export default class BaseValidation {
trigger(el, type, args)
}

_runValidators (fn, cb) {
const validators = this._validators
const length = Object.keys(validators).length

let count = 0
each(validators, (descriptor, name) => {
fn(descriptor, name, () => {
++count
count >= length && cb()
})
})
}

_invokeValidator (vm, validator, val, arg, cb) {
let future = validator.call(vm, val, arg)
if (typeof future === 'function') { // function
if (future.resolved) {
// cached
cb(future.resolved)
} else if (future.requested) {
// pool callbacks
future.pendingCallbacks.push(cb)
} else {
future.requested = true
let cbs = future.pendingCallbacks = [cb]
future(() => { // resolve
future.resolved = true
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i](true)
}
}, () => { // reject
cb(false)
})
}
} else if (isPromise(future)) { // promise
future.then(() => { // resolve
cb(true)
}).catch(() => { // reject
cb(false)
})
} else { // sync
cb(future)
}
}

_resolveValidator (name) {
const resolveAsset = util.Vue.util.resolveAsset
return resolveAsset(this._vm.$options, 'validators', name)
}

}
84 changes: 46 additions & 38 deletions src/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,21 @@ export default class Validator {

if (validation) {
validation.willUpdateFlags(touched)
let res = validation.validate()
util.Vue.set(this._scope, field, res)
validation.validate((results) => {
util.Vue.set(this._scope, field, results)

if (this._scope.dirty) {
this._fireEvent('dirty')
}
if (this._scope.dirty) {
this._fireEvent('dirty')
}

if (this._modified !== this._scope.modified) {
this._fireEvent('modified', this._scope.modified)
this._modified = this._scope.modified
}
if (this._modified !== this._scope.modified) {
this._fireEvent('modified', this._scope.modified)
this._modified = this._scope.modified
}

let valid = this._scope.valid
this._fireEvent((valid ? 'valid' : 'invalid'))
let valid = this._scope.valid
this._fireEvent((valid ? 'valid' : 'invalid'))
})
}
}
}
Expand Down Expand Up @@ -172,7 +173,6 @@ export default class Validator {
}, this)
}

// TODO: should be improved performance (use cache)
get validations () {
const extend = util.Vue.util.extend

Expand Down Expand Up @@ -338,37 +338,33 @@ export default class Validator {
}
}

validate () {
each(this._validations, (validation, key) => {
let res = validation.validate()
util.Vue.set(this._scope, key, res)
}, this)
validate (cb) {
const self = this

each(this._checkboxValidations, (dataset, key) => {
let res = dataset.validation.validate()
util.Vue.set(this._scope, key, res)
}, this)

each(this._radioValidations, (dataset, key) => {
let res = dataset.validation.validate()
util.Vue.set(this._scope, key, res)
}, this)
this._runValidates((validation, key, done) => {
validation.validate((results) => {
util.Vue.set(self._scope, key, results)
done()
})
}, () => { // finished
if (this._scope.touched) {
this._fireEvent('touched')
}

if (this._scope.touched) {
this._fireEvent('touched')
}
if (this._scope.dirty) {
this._fireEvent('dirty')
}

if (this._scope.dirty) {
this._fireEvent('dirty')
}
if (this._modified !== this._scope.modified) {
this._fireEvent('modified', this._scope.modified)
this._modified = this._scope.modified
}

if (this._modified !== this._scope.modified) {
this._fireEvent('modified', this._scope.modified)
this._modified = this._scope.modified
}
let valid = this._scope.valid
this._fireEvent((valid ? 'valid' : 'invalid'))

let valid = this._scope.valid
this._fireEvent((valid ? 'valid' : 'invalid'))
cb && cb()
})
}

setupScope () {
Expand Down Expand Up @@ -426,6 +422,18 @@ export default class Validator {
}, this)
}

_runValidates (fn, cb) {
const length = Object.keys(this.validations).length

let count = 0
each(this.validations, (validation, key) => {
fn(validation, key, () => {
++count
count >= length && cb()
})
})
}

_walkValidations (validations, property, condition) {
const hasOwn = util.Vue.util.hasOwn
let ret = condition
Expand Down
Loading

0 comments on commit a21a0af

Please sign in to comment.