Skip to content

Commit

Permalink
Merge pull request #8 from formly-js/validate_promises
Browse files Browse the repository at this point in the history
Validate promises
  • Loading branch information
matt-sanders authored May 9, 2017
2 parents 0a18b45 + 677ba9a commit 05c392c
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 85 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ flycheck_*.el

node_modules/
coverage/
test/unit/coverage
test/unit/coverage

shrinkwrap.yaml
112 changes: 58 additions & 54 deletions src/components/FormlyField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,69 @@
methods: {
validate:function(){
//first check if we need to create a field
if ( !this.form.$errors[this.field.key] ) this.$set(this.form.$errors, this.field.key, {});
if ( !this.field.templateOptions ) this.$set(this.field, 'templateOptions', {});
//check for required fields. This whole setting,unsetting thing seems kind of wrong though..
//there might be a more 'vue-ey' way to do this...
if ( this.field.required ){
if ( !this.form.$errors[this.field.key].required ) this.$set(this.form.$errors[ this.field.key ], 'required', true);
setError(this.form, this.field.key, 'required', !this.model[ this.field.key ]) ;
}
//if we've got nothing left then return
if ( !this.field.validators ) return;
//set these for the validators so we don't have to use 'this' in them
let model = this.model;
let field = this.field;
Object.keys(this.field.validators).forEach((validKey) => {
if ( !this.form.$errors[this.field.key][validKey] ) this.$set(this.form.$errors[ this.field.key ], validKey, false);
if ( !this.field.required && !this.model[ this.field.key ] ) {
setError(this.form, this.field.key, validKey, false);
return;
return new Promise((resolve, reject)=>{
//first check if we need to create a field
if ( !this.form.$errors[this.field.key] ) this.$set(this.form.$errors, this.field.key, {});
if ( !this.field.templateOptions ) this.$set(this.field, 'templateOptions', {});
//check for required fields. This whole setting,unsetting thing seems kind of wrong though..
//there might be a more 'vue-ey' way to do this...
if ( this.field.required ){
if ( !this.form.$errors[this.field.key].required ) this.$set(this.form.$errors[ this.field.key ], 'required', true);
setError(this.form, this.field.key, 'required', !this.model[ this.field.key ]) ;
}
//if we've got nothing left then return
if ( !this.field.validators ) return resolve();
let validator = this.field.validators[validKey];
let validatorMessage = false;
//set these for the validators so we don't have to use 'this' in them
let model = this.model;
let field = this.field;
Object.keys(this.field.validators).forEach((validKey) => {
if ( !this.form.$errors[this.field.key][validKey] ) this.$set(this.form.$errors[ this.field.key ], validKey, false);
if ( !this.field.required && !this.model[ this.field.key ] ) {
setError(this.form, this.field.key, validKey, false);
return resolve();
}
if ( typeof validator === 'object' ){
if ( !( 'message' in validator ) ){
console.error( "Looks like you've set a validator object without setting a message. If you don't need to explicity set the message just define the validator as either an expression or a function. Refer to the docs for more info");
} else {
validatorMessage = validator.message;
validator = validator.expression;
let validator = this.field.validators[validKey];
let validatorMessage = false;
if ( typeof validator === 'object' ){
if ( !( 'message' in validator ) ){
console.error( "Looks like you've set a validator object without setting a message. If you don't need to explicity set the message just define the validator as either an expression or a function. Refer to the docs for more info");
} else {
validatorMessage = validator.message;
validator = validator.expression;
}
}
}
let label = ( 'templateOptions' in this.field ) && ( 'label' in this.field.templateOptions ) ? this.field.templateOptions.label : '';
validatorMessage = parseValidationString( validKey, validatorMessage, label, model[ this.field.key ] );
let label = ( 'templateOptions' in this.field ) && ( 'label' in this.field.templateOptions ) ? this.field.templateOptions.label : '';
validatorMessage = parseValidationString( validKey, validatorMessage, label, model[ this.field.key ] );
let valid = false;
if ( typeof validator === 'function' ){
//set the asynchronous flag so that we know it's going
let asyncKey = '$async_'+validKey;
this.$set(this.form.$errors[ this.field.key ], asyncKey, true);
// setup for async validation
validator(field, model, (asyncValid = false, asyncValidatorMessage = validatorMessage) => {
// whenever validation is done via a function we will assume it's asynchronous and will require next() to be called
// this way it doesn't matter if it's async or not, next() should always be called
setError(this.form, this.field.key, validKey, !asyncValid, asyncValidatorMessage);
this.$set(this.form.$errors[ this.field.key ], asyncKey, false);
});
} else {
let res = new Function('model', 'field', 'return '+validator+';' );
valid = !res.call({}, model, field);
setError(this.form, this.field.key, validKey, valid, validatorMessage);
}
let valid = false;
if ( typeof validator === 'function' ){
//set the asynchronous flag so that we know it's going
let asyncKey = '$async_'+validKey;
this.$set(this.form.$errors[ this.field.key ], asyncKey, true);
// setup for async validation
validator(field, model, (asyncValid = false, asyncValidatorMessage = validatorMessage) => {
// whenever validation is done via a function we will assume it's asynchronous and will require next() to be called
// this way it doesn't matter if it's async or not, next() should always be called
setError(this.form, this.field.key, validKey, !asyncValid, asyncValidatorMessage);
this.$set(this.form.$errors[ this.field.key ], asyncKey, false);
resolve();
});
} else {
let res = new Function('model', 'field', 'return '+validator+';' );
valid = !res.call({}, model, field);
setError(this.form, this.field.key, validKey, valid, validatorMessage);
resolve();
}
});
});
}
},
Expand Down
70 changes: 43 additions & 27 deletions src/components/FormlyForm.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
<template>
<fieldset>
<formly-field v-for="field in fields" :form.sync="form" :model.sync="model" :field="field"></formly-field>
<formly-field :ref="field.key" v-for="field in fields" :form.sync="form" :model.sync="model" :field="field"></formly-field>
<slot></slot>
</fieldset>
</template>

<script>
export default {
props: ['form', 'model', 'fields'],
created(){
export default {
methods: {
validate(){
return new Promise((resolve, reject) => {
let target = this.fields.length;
let count = 0;
this.fields.forEach( field => {
this.$set( this.form[ field.key ], '$dirty', true );
this.$refs[ field.key ][0].validate()
.then(()=>{
count++;
if( target == count ) resolve();
});
});
});
}
},
props: ['form', 'model', 'fields'],
created(){
//make sure that the 'value' is always set
this.fields.forEach( field => {
if ( typeof this.model[ field.key ] == 'undefined' ) this.$set(this.model, field.key, '');
});
//make sure that the 'value' is always set
this.fields.forEach( field => {
if ( typeof this.model[ field.key ] == 'undefined' ) this.$set(this.model, field.key, '');
});
//set our validation options
this.$set(this.form, '$errors', {});
this.$set(this.form, '$valid', true);
//set our validation options
this.$set(this.form, '$errors', {});
this.$set(this.form, '$valid', true);
this.$watch('form.$errors', function(val){
let valid = true;
Object.keys(this.form.$errors).forEach((key)=>{
let errField = this.form.$errors[key];
Object.keys(errField).forEach((errKey) => {
if ( errField[errKey] ) valid = false;
})
});
this.form.$valid = valid;
}, {
deep: true
});
}
}
this.$watch('form.$errors', function(val){
let valid = true;
Object.keys(this.form.$errors).forEach((key)=>{
let errField = this.form.$errors[key];
Object.keys(errField).forEach((errKey) => {
if ( errField[errKey] ) valid = false;
})
});
this.form.$valid = valid;
}, {
deep: true
});
}
}
</script>
33 changes: 33 additions & 0 deletions test/unit/specs/FormlyField.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ const expect = chai.expect;
import Vue from 'vue';
import FormlyField from 'src/components/FormlyField.vue';
import Utils from 'src/util';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
chai.use(sinonChai);

let el, vm;

Expand Down Expand Up @@ -167,6 +170,36 @@ describe('FormlyField', () => {
return createForm('<formly-field :form.sync="form" :field="fields[0]" :model="model"></formly-field>', data);
};

it('should return a promise', (done)=>{
let data = {
form: {
$valid: true,
$errors: {}
},
model: {
search: ''
},
fields: [
{
key: 'search',
type: 'test',
required: true
}
]
};
createValidField(data);
let cb = sinon.spy();

let prom = vm.$children[0].validate();
expect(typeof prom.then).to.equal('function');

prom.then(()=>cb());
setTimeout(()=>{
cb.should.be.called;
done();
});
});

it('should handle required values', (done) => {

let data = {
Expand Down
55 changes: 55 additions & 0 deletions test/unit/specs/FormlyForm.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,60 @@ describe('FormlyForm', () => {
done();
});
});

it('validate()', (done)=>{
let formlyFieldSpy = sinon.spy();
let ValidField = Vue.extend({
template: '<h1>ValidationField</h1>',
props: ['form', 'model', 'field'],
methods: {
validate(){
return new Promise(function(resolve, reject){
formlyFieldSpy();
resolve();
});
}
}
});

let data = {
form: {
validTest: {
$dirty: false
},
validTest2: {
$dirty: false
}
},
model: {validTest:'', validTest2: ''},
fields: [{
key: 'validTest',
type: 'input'
},
{
key: 'validTest2',
type: 'input'
}]
};

el = document.createElement('DIV');
document.body.appendChild(el);
Vue.component('formly-field', ValidField);
vm = new Vue({
data: data,
template: '<formly-form :form="form" :model="model" :fields="fields"></formly-form>'
}).$mount(el);


let spy = sinon.spy();

let prom = vm.$children[0].validate();
prom.then(()=>spy());
setTimeout(()=>{
spy.should.be.calledOnce;
formlyFieldSpy.should.be.calledTwice;
done();
});
});
});

11 changes: 8 additions & 3 deletions webpack.build.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@ module.exports = [
libraryTarget: "umd"
},

plugins: [
new webpack.optimize.DedupePlugin(),
plugins: [
new webpack.DefinePlugin({
'process.env' : {
NODE_ENV : JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.DedupePlugin(),
new webpack.BannerPlugin(banner, {
raw: true
}),
Expand Down Expand Up @@ -81,7 +86,7 @@ module.exports = [
],

module: {
loaders: loaders
loaders: loaders
},

resolve: {
Expand Down

0 comments on commit 05c392c

Please sign in to comment.