Skip to content

Commit 2fc6056

Browse files
authored
Merge pull request #4 from formly-js/error-handling
Implements enhanced error handling and the display of error messages.
2 parents 90eadbd + 5f0437a commit 2fc6056

File tree

9 files changed

+174
-7
lines changed

9 files changed

+174
-7
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-formly-bootstrap",
3-
"version": "2.0.2",
3+
"version": "2.2.0",
44
"description": "A bootstrap based form input bundle for Vue Formly",
55
"main": "dist/vue-formly-bootstrap.js",
66
"scripts": {

src/components/errorDisplay.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<span v-if="message" class="help-block">{{message}}</span>
3+
</template>
4+
5+
<script>
6+
export default {
7+
props: ['field', 'form'],
8+
computed: {
9+
message(){
10+
let message = false;
11+
if ( !( this.field in this.form.$errors ) || this.form[ this.field ].$active || !this.form[this.field].$dirty ) return message;
12+
let errors = this.form.$errors[ this.field ];
13+
Object.keys( errors ).some( errorKey => {
14+
if ( typeof errors[ errorKey ] != 'boolean' ){
15+
message = errors[ errorKey ];
16+
return true;
17+
}
18+
});
19+
return message;
20+
}
21+
}
22+
}
23+
</script>

src/fields/baseField.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import errorDisplay from '../components/errorDisplay.vue';
12
export default
23
{
34
props: [
@@ -9,7 +10,8 @@ export default
910
created: function(){
1011
let state = {
1112
'$dirty': false,
12-
'$active': false
13+
'$active': false,
14+
'$hasError': false
1315
};
1416
this.$set(this.form, this.field.key, state);
1517
},
@@ -39,5 +41,22 @@ export default
3941
onKeydown: function(e){
4042
this.runFunction('onKeydown', e);
4143
}
44+
},
45+
computed: {
46+
hasError: function(){
47+
if ( this.form[ this.field.key ].$dirty == false || this.form[ this.field.key ].$active == true ) {
48+
return false;
49+
}
50+
let errors = this.form.$errors[ this.field.key ];
51+
let hasErrors = false;
52+
Object.keys( errors ).forEach( err => {
53+
if ( errors[err] !== false ) hasErrors = true;
54+
});
55+
this.$set(this.form[ this.field.key ], '$hasError', hasErrors);
56+
return hasErrors;
57+
}
58+
},
59+
components: {
60+
'error-display': errorDisplay
4261
}
4362
};

src/fields/fieldInput.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<template>
2-
<div class="form-group formly-input" :class="[ to.inputType, {'formly-has-value': model[field.key], 'formly-has-focus': form[field.key].$active}]">
2+
<div class="form-group formly-input" :class="[ to.inputType, {'formly-has-value': model[field.key], 'formly-has-focus': form[field.key].$active, 'has-error': hasError}]">
33
<label v-if="to.label" :for="to.id ? to.id : null">{{to.label}}</label>
44
<input class="form-control" :class="to.classes" :id="to.id ? to.id : null" type="text" v-model="model[field.key]" @blur="onBlur" @focus="onFocus" @click="onClick" @change="onChange" @keyup="onKeyup" @keydown="onKeydown" v-formly-atts="to.atts" v-formly-input-type="to.inputType">
5+
<error-display :form="form" :field="field.key"></error-display>
56
</div>
67
</template>
78

src/fields/fieldList.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<template>
2-
<div class="checkbox formly-list" :id="to.id" :class="to.classes">
2+
<div class="checkbox formly-list" :id="to.id" :class="[to.classes, {'has-error': hasError}]">
33

44
<label v-for="option in field.options">
55
<input v-if="!to.inputType || to.inputType == 'checkbox'" type="checkbox" v-model="model[field.key]" :value="option.value || option" @blur="onBlur" @focus="onFocus" @click="onClick" @change="onChange" @keyup="onKeyup" @keydown="onKeydown" v-formly-atts="to.atts">
66
<input v-if="to.inputType == 'radio'" type="radio" v-model="model[field.key]" :value="option.value || option" @blur="onBlur" @focus="onFocus" @click="onClick" @change="onChange" @keyup="onKeyup" @keydown="onKeydown" v-formly-atts="to.atts">
77
{{option.label || option}}
88
</label>
9-
9+
<error-display :form="form" :field="field.key"></error-display>
1010
</div>
1111
</template>
1212

src/fields/fieldSelect.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<template>
2-
<div class="form-group formly-select">
2+
<div class="form-group formly-select" :class="{'has-error': hasError}">
33
<label v-if="to.label" :for="to.id ? to.id : null">{{to.label}}</label>
44
<select class="form-control" :class="to.classes" :id="to.id ? to.id : null" v-model="model[field.key]" @blur="onBlur" @focus="onFocus" @click="onClick" @change="onChange" @keyup="onKeyup" @keydown="onKeydown" v-formly-atts="to.atts">
55
<option v-for="option in field.options" :value="option.value || option">{{option.label || option}}</option>
66
</select>
7+
<error-display :form="form" :field="field.key"></error-display>
78
</div>
89
</template>
910

src/fields/fieldTextarea.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<template>
2-
<div class="form-group formly-textarea">
2+
<div class="form-group formly-textarea" :class="{'formly-has-value': model[field.key], 'formly-has-focus': form[field.key].$active, 'has-error': hasError}">
33
<label v-if="to.label" :for="to.id ? to.id : null">{{to.label}}</label>
44
<textarea class="form-control" :class="to.classes" :id="to.id ? to.id : null" v-model="model[field.key]" @blur="onBlur" @focus="onFocus" @click="onClick" @change="onChange" @keyup="onKeyup" @keydown="onKeydown" v-formly-atts="to.atts"></textarea>
5+
<error-display :form="form" :field="field.key"></error-display>
56
</div>
67
</template>
78

test/unit/specs/ErrorDisplay.spec.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import chai from 'chai';
2+
import {expect} from 'chai';
3+
import sinonChai from 'sinon-chai';
4+
import sinon from 'sinon';
5+
import Vue from 'vue';
6+
import errorDisplay from 'src/components/errorDisplay.vue';
7+
8+
let el, vm, data;
9+
10+
function createVue(){
11+
el = document.createElement('div');
12+
vm = new Vue({
13+
data: data,
14+
template: '<error-display :field="key" :form="form"></error-display>',
15+
components: {
16+
'error-display': errorDisplay
17+
}
18+
}).$mount(el);
19+
20+
return [el, vm];
21+
}
22+
23+
describe("errorDisplay.vue", () => {
24+
it("should display errors", () => {
25+
data = {
26+
form: {
27+
'$errors': {
28+
test: {
29+
foo: 'This is a test'
30+
}
31+
}
32+
},
33+
key: 'test'
34+
};
35+
36+
createVue();
37+
expect(vm.$el.textContent).to.equal( data.form.$errors.test.foo );
38+
});
39+
40+
it('should only display the first error', () => {
41+
42+
data = {
43+
form: {
44+
'$errors': {
45+
test: {
46+
foo: false,
47+
bar: 'This is a test',
48+
foobar: 'testing'
49+
}
50+
}
51+
},
52+
key: 'test'
53+
};
54+
55+
createVue();
56+
expect(vm.$el.textContent).to.equal( data.form.$errors.test.bar );
57+
58+
});
59+
60+
it('should show nothing at all without errors', () => {
61+
62+
data = {
63+
form: {
64+
'$errors': {
65+
test: {
66+
foo: false
67+
}
68+
}
69+
},
70+
key: 'test'
71+
};
72+
73+
createVue();
74+
expect(vm.$el.textContent).to.equal( '' );
75+
76+
});
77+
});

test/unit/specs/index.spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,29 @@ describe('Bootstrap Field Inputs', () => {
177177
};
178178
});
179179

180+
describe('Errors', () => {
181+
it('should receive an error state', (done) => {
182+
data.fields[0].type = 'input';
183+
data.fields[0].required = true;
184+
createForm();
185+
186+
expect(data.form['test'].$hasError).to.be.false;
187+
trigger(vm.$el.querySelectorAll('input')[0], 'focus');
188+
expect(data.form['test'].$hasError).to.be.false;
189+
expect(data.form['test'].$active).to.be.true;
190+
191+
trigger(vm.$el.querySelectorAll('input')[0], 'change');
192+
trigger(vm.$el.querySelectorAll('input')[0], 'blur');
193+
expect(data.form['test'].$dirty).to.be.true;
194+
expect(data.form['test'].$active).to.be.false;
195+
setTimeout( () => {
196+
expect(data.form['test'].$hasError).to.be.true;
197+
expect(vm.$el.querySelectorAll('.has-error')).to.be.length(1);
198+
done();
199+
}, 0);
200+
});
201+
});
202+
180203
describe('Input', () => {
181204

182205
describe('functions',() =>{
@@ -445,6 +468,28 @@ describe('Bootstrap Field Inputs', () => {
445468
done();
446469
}, 0);
447470
});
471+
472+
it('only shows a checkbox', () => {
473+
data.fields[0].type = 'list';
474+
data.fields[0].options = ['one', 'two'];
475+
createForm();
476+
let inputs = vm.$el.querySelectorAll('input');
477+
expect(inputs).to.be.length(2);
478+
expect(inputs[0].type).to.equal('checkbox');
479+
expect(inputs[1].type).to.equal('checkbox');
480+
});
481+
482+
it('only shows a radio box', () => {
483+
data.fields[0].type = 'list';
484+
data.fields[0].templateOptions.inputType = 'radio';
485+
data.fields[0].options = ['one', 'two'];
486+
createForm();
487+
let inputs = vm.$el.querySelectorAll('input');
488+
expect(inputs).to.be.length(2);
489+
expect(inputs[0].type).to.equal('radio');
490+
expect(inputs[1].type).to.equal('radio');
491+
});
492+
448493
});
449494

450495
});

0 commit comments

Comments
 (0)