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

Commit

Permalink
feat(radio): support radio button validaton
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon committed Dec 29, 2015
1 parent 78ef385 commit 848f3f5
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 3 deletions.
6 changes: 5 additions & 1 deletion src/directives/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default function (Vue) {
let field = this.field = _.camelize(this.arg)
if (this.el.type === 'checkbox') {
this.validation = validator.manageMultipleValidation(field, vm, this.el)
} else if (this.el.type === 'radio') {
this.validation = validator.manageRadioValidation(field, vm, this.el)
} else {
this.validation = validator.addValidation(field, vm, this.el)
}
Expand All @@ -32,7 +34,7 @@ export default function (Vue) {
}

this.on('blur', _.bind(validation.listener, validation))
if (this.el.type === 'checkbox') {
if (this.el.type === 'checkbox' || this.el.type === 'radio') {
this.on('change', _.bind(validation.listener, validation))
} else {
this.on('input', _.bind(validation.listener, validation))
Expand Down Expand Up @@ -80,6 +82,8 @@ export default function (Vue) {

if (this.el.type === 'checkbox') {
this.validator.unmanageMultipleValidation(this.field, this.el)
} else if (this.el.type === 'radio') {
this.validator.unmanageRadioValidation(this.field, this.el)
} else {
this.validator.removeValidation(this.field)
}
Expand Down
148 changes: 148 additions & 0 deletions src/radio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import util, { empty, each, trigger } from './util'
import Validation from './validation'


/**
* RadioValidation class
*/

export default class RadioValidation extends Validation {

constructor (field, vm, el, validator) {
super(field, vm, el, validator)

this._init = el.checked
this._inits = []
}

manageElement (el) {
let item = {
el: el,
init: el.checked,
value: el.value
}
this._inits.push(item)
this._validator.validate()
}

unmanageElement (el) {
let found = -1
each(this._inits, (item, index) => {
if (item.el === el) {
found = index
}
})
if (found === -1) { return false }

this._inits.splice(found, 1)
this._validator.validate()
return true
}

listener (e) {
if (e.relatedTarget &&
(e.relatedTarget.tagName === 'A' || e.relatedTarget.tagName === 'BUTTON')) {
return
}

if (e.type === 'blur') {
this.touched = true
}

if (!this.dirty && this._checkModified(e.target)) {
this.dirty = true
}

this.modified = this._checkModified(e.target)

this._validator.validate()
}

_checkModified (target) {
if (this._inits.length === 0) {
return target.checked
} else {
let modified = false
each(this._inits, (item, index) => {
if (!modified) {
modified = (item.init !== item.el.checked)
}
})
return modified
}
}

validate () {
const _ = util.Vue.util

let results = {}
let messages = {}
let valid = true

each(this._validators, (descriptor, name) => {
let asset = this._resolveValidator(name)
let validator = null
let msg = null

if (_.isPlainObject(asset)) {
if (asset.check && typeof asset.check === 'function') {
validator = asset.check
}
if (asset.message) {
msg = asset.message
}
} else if (typeof asset === 'function') {
validator = asset
}

if (descriptor.msg) {
msg = descriptor.msg
}

if (validator) {
let ret = validator.call(this._vm, this._getValue(), descriptor.arg)
if (!ret) {
valid = false
if (msg) {
messages[name] = typeof msg === 'function'
? msg.call(this._vm, this.field, descriptor.arg)
: msg
}
}
results[name] = !ret
}
}, this)

trigger(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(messages)) {
props.messages = messages
}
_.extend(results, props)

return results
}

_getValue () {
if (this._inits.length === 0) {
return this._init
} else {
let vals = []
each(this._inits, (item, index) => {
if (item.el.checked) {
vals.push(item.el.value)
}
})
return vals
}
}
}
41 changes: 39 additions & 2 deletions src/validator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import util, { empty, each, pull } from './util'
import Validation from './validation'
import MultipleValidation from './multiple_validation'
import RadioValidation from './radio'


/**
Expand All @@ -16,6 +17,7 @@ export default class Validator {
this._dir = dir
this._validations = {}
this._multipleValidations = {}
this._radioValidations = {}
this._groups = groups
this._groupValidations = {}

Expand All @@ -37,13 +39,18 @@ export default class Validator {
// TODO: should be improved performance (use cache)
get validations () {
const extend = util.Vue.util.extend

let ret = {}
extend(ret, this._validations)

each(this._multipleValidations, (dataset, key) => {
ret[key] = dataset.validation
}, this)

each(this._radioValidations, (dataset, key) => {
ret[key] = dataset.validation
}, this)

return ret
}

Expand Down Expand Up @@ -82,10 +89,35 @@ export default class Validator {
}
}

manageRadioValidation (field, vm, el) {
let validationSet = this._radioValidations[field]
if (!validationSet) {
let validation = new RadioValidation(field, vm, el, this)
validationSet = { validation: validation, elements: 0 }
this._radioValidations[field] = validationSet
}

validationSet.elements++
validationSet.validation.manageElement(el)
return validationSet.validation
}

unmanageRadioValidation (field, el) {
let validationSet = this._radioValidations[field]
if (validationSet) {
validationSet.elements--
validationSet.validation.unmanageElement(el)
if (validationSet.elements === 0) {
util.Vue.delete(this._scope, field)
this._radioValidations[field] = null
}
}
}

addGroupValidation (group, field) {
const indexOf = util.Vue.util.indexOf

let validation = this._validations[field] || this._multipleValidations[field].validation
let validation = this._validations[field] || this._multipleValidations[field].validation || this._radioValidations[field].validation
let validations = this._groupValidations[group]
if (validations) {
if (!~indexOf(validations, validation)) {
Expand All @@ -95,7 +127,7 @@ export default class Validator {
}

removeGroupValidation (group, field) {
let validation = this._validations[field] || this._multipleValidations[field].validation
let validation = this._validations[field] || this._multipleValidations[field].validation || this._radioValidations[field].validation
let validations = this._groupValidations[group]
if (validations) {
pull(validations, validation)
Expand All @@ -112,6 +144,11 @@ export default class Validator {
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)
}

setupScope () {
Expand Down
1 change: 1 addition & 0 deletions test/specs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ require('./multiple')
require('./messages')
require('./lazy')
require('./checkbox')
require('./radio')
88 changes: 88 additions & 0 deletions test/specs/radio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import assert from 'power-assert'
import Vue from 'vue'
import { trigger } from '../../src/util'


describe('radio', () => {
let el, vm

beforeEach(() => {
el = document.createElement('div')
})


describe('normal', () => {
beforeEach((done) => {
el.innerHTML =
'<validator name="validator1">' +
'<form novalidate>' +
'<fieldset>' +
'<label for="radio1">radio1</label>' +
'<input type="radio" id="radio1" name="r1" checked value="foo" v-validate:field1="{ required: true }">' +
'<label for="radio2">radio2</label>' +
'<input type="radio" id="radio2" name="r1" value="bar" v-validate:field1>' +
'</fieldset>' +
'<fieldset>' +
'<label for="radio3">radio3</label>' +
'<input type="radio" id="radio3" name="r2" value="buz" v-validate:field2="{ required: true }">' +
'<label for="radio4">radio4</label>' +
'<input type="radio" id="radio4" name="r2" value="hoge" v-validate:field2>' +
'</fieldset>' +
'</form>' +
'</validator>'
vm = new Vue({
el: el
})
vm.$nextTick(done)
})

it('should be validated', (done) => {
// default
assert(vm.$validator1.field1.required === false)
assert(vm.$validator1.field1.valid === true)
assert(vm.$validator1.field1.touched === false)
assert(vm.$validator1.field1.dirty === false)
assert(vm.$validator1.field1.modified === false)
assert(vm.$validator1.field2.required === true)
assert(vm.$validator1.field2.valid === false)
assert(vm.$validator1.field2.touched === false)
assert(vm.$validator1.field2.dirty === false)
assert(vm.$validator1.field2.modified === false)
assert(vm.$validator1.valid === false)
assert(vm.$validator1.touched === false)
assert(vm.$validator1.dirty === false)
assert(vm.$validator1.modified === false)

let radio1 = el.getElementsByTagName('input')[0]
let radio2 = el.getElementsByTagName('input')[1]
let radio3 = el.getElementsByTagName('input')[2]
let radio4 = el.getElementsByTagName('input')[3]

// change
radio2.checked = true
radio3.checked = true
trigger(radio2, 'change')
trigger(radio2, 'blur')
trigger(radio3, 'change')
trigger(radio3, 'blur')
vm.$nextTick(() => {
assert(vm.$validator1.field1.required === false)
assert(vm.$validator1.field1.valid === true)
assert(vm.$validator1.field1.touched === true)
assert(vm.$validator1.field1.dirty === true)
assert(vm.$validator1.field1.modified === true)
assert(vm.$validator1.field2.required === false)
assert(vm.$validator1.field2.valid === true)
assert(vm.$validator1.field2.touched === true)
assert(vm.$validator1.field2.dirty === true)
assert(vm.$validator1.field2.modified === true)
assert(vm.$validator1.valid === true)
assert(vm.$validator1.touched === true)
assert(vm.$validator1.dirty === true)
assert(vm.$validator1.modified === true)

done()
})
})
})
})

0 comments on commit 848f3f5

Please sign in to comment.