Skip to content

Commit 5aa1168

Browse files
committed
Validate when model changes
Also update error messages when model changes even if error state hasn't changed. Also introduces two global options Default values: pristine : {error: true, success: true}, // Should errors and success be visible regardless of $pristine? validateOnRender: false // Should form fields be validated on form render?
1 parent 4ceb03d commit 5aa1168

File tree

6 files changed

+141
-55
lines changed

6 files changed

+141
-55
lines changed

docs/index.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,15 @@ attribute which should be placed along side `sf-schema`.
187187
`sf-options` takes an object with the following possible attributes.
188188
189189
190-
| Attribute | |
191-
|:--------------|:------------------------|
192-
| supressPropertyTitles | by default schema form uses the property name in the schema as a title if none is specified, set this to true to disable that behavior |
193-
| formDefaults | an object that will be used as a default for all form definitions |
194-
| validationMessage | an object or a function that will be used as default validation message for all fields. See [Validation Messages](#validation-messages) for details. |
195-
| setSchemaDefaults | boolean, set to false an no defaults from the schema will be set on the model. |
196-
| destroyStrategy | the default strategy to use for cleaning the model when a form element is removed. see [destroyStrategy](#destroyStrategy) below |
190+
| Attribute | Type | |
191+
|:--------------|:------|:-------------------|
192+
| supressPropertyTitles | boolean |by default schema form uses the property name in the schema as a title if none is specified, set this to true to disable that behavior |
193+
| formDefaults | object | an object that will be used as a default for all form definitions |
194+
| validationMessage | object or function | Object or a function that will be used as default validation message for all fields. See [Validation Messages](#validation-messages) for details. |
195+
| setSchemaDefaults | boolean | Should schema defaults be set on model. |
196+
| destroyStrategy | string | the default strategy to use for cleaning the model when a form element is removed. see [destroyStrategy](#destroyStrategy) below |
197+
| pristine | Object `{errors ,success}` | Sets if errors and success states should be visible when form field are `$pristine`. Default is `{errors: true, success: true}` |
198+
| validateOnRender | boolean | Should form be validated on initial render? Default `false` |
197199
198200
*formDefaults* is mostly useful for setting global [ngModelOptions](#ngmodeloptions)
199201
i.e. changing the entire form to validate on blur.

src/directives/array.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,27 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf
240240
scope.$on('schemaFormValidate', scope.validateArray);
241241

242242
scope.hasSuccess = function() {
243-
return ngModel.$valid && !ngModel.$pristine;
243+
if (scope.options && scope.options.pristine &&
244+
scope.options.pristine.success === false) {
245+
return ngModel.$valid &&
246+
!ngModel.$pristine && !ngModel.$isEmpty(ngModel.$modelValue);
247+
} else {
248+
return ngModel.$valid &&
249+
(!ngModel.$pristine || !ngModel.$isEmpty(ngModel.$modelValue));
250+
}
244251
};
245252

246253
scope.hasError = function() {
247-
return ngModel.$invalid;
254+
if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) {
255+
// Show errors in pristine forms. The default.
256+
// Note that "validateOnRender" option defaults to *not* validate initial form.
257+
// so as a default there won't be any error anyway, but if the model is modified
258+
// from the outside the error will show even if the field is pristine.
259+
return ngModel.$invalid;
260+
} else {
261+
// Don't show errors in pristine forms.
262+
return ngModel.$invalid && !ngModel.$pristine;
263+
}
248264
};
249265

250266
scope.schemaError = function() {

src/directives/field.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,20 +107,35 @@ angular.module('schemaForm').directive('sfField',
107107
return (expression && $interpolate(expression)(locals));
108108
};
109109

110-
//This works since we ot the ngModel from the array or the schema-validate directive.
110+
//This works since we get the ngModel from the array or the schema-validate directive.
111111
scope.hasSuccess = function() {
112112
if (!scope.ngModel) {
113113
return false;
114114
}
115-
return scope.ngModel.$valid &&
115+
if (scope.options && scope.options.pristine &&
116+
scope.options.pristine.success === false) {
117+
return scope.ngModel.$valid &&
118+
!scope.ngModel.$pristine && !scope.ngModel.$isEmpty(scope.ngModel.$modelValue);
119+
} else {
120+
return scope.ngModel.$valid &&
116121
(!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue));
122+
}
117123
};
118124

119125
scope.hasError = function() {
120126
if (!scope.ngModel) {
121127
return false;
122128
}
123-
return scope.ngModel.$invalid && !scope.ngModel.$pristine;
129+
if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) {
130+
// Show errors in pristine forms. The default.
131+
// Note that "validateOnRender" option defaults to *not* validate initial form.
132+
// so as a default there won't be any error anyway, but if the model is modified
133+
// from the outside the error will show even if the field is pristine.
134+
return scope.ngModel.$invalid;
135+
} else {
136+
// Don't show errors in pristine forms.
137+
return scope.ngModel.$invalid && !scope.ngModel.$pristine;
138+
}
124139
};
125140

126141
/**
@@ -178,7 +193,8 @@ angular.module('schemaForm').directive('sfField',
178193
scope.$broadcast('schemaFormValidate');
179194
}
180195
}
181-
});
196+
}
197+
);
182198

183199
// Clean up the model when the corresponding form field is $destroy-ed.
184200
// Default behavior can be supplied as a globalOption, and behavior can be overridden

src/directives/message.js

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,60 +15,80 @@ angular.module('schemaForm').directive('sfMessage',
1515
scope.$watch(attrs.sfMessage, function(msg) {
1616
if (msg) {
1717
message = $sanitize(msg);
18-
if (scope.ngModel) {
19-
update(scope.ngModel.$valid);
20-
} else {
21-
update();
22-
}
18+
update(!!scope.ngModel);
2319
}
2420
});
2521
}
2622

27-
var update = function(valid) {
28-
if (valid && !scope.hasError()) {
29-
element.html(message);
30-
} else {
31-
var errors = [];
32-
angular.forEach(((scope.ngModel && scope.ngModel.$error) || {}), function(status, code) {
33-
if (status) {
34-
// if true then there is an error
35-
// Angular 1.3 removes properties, so we will always just have errors.
36-
// Angular 1.2 sets them to false.
37-
errors.push(code);
38-
}
39-
});
23+
var currentMessage;
24+
// Only call html() if needed.
25+
var setMessage = function(msg) {
26+
if (msg !== currentMessage) {
27+
element.html(msg);
28+
currentMessage = msg;
29+
}
30+
};
4031

41-
// In Angular 1.3 we use one $validator to stop the model value from getting updated.
42-
// this means that we always end up with a 'schemaForm' error.
43-
errors = errors.filter(function(e) { return e !== 'schemaForm'; });
32+
var update = function(checkForErrors) {
33+
if (checkForErrors) {
34+
if (!scope.hasError()) {
35+
setMessage(message);
36+
} else {
37+
var errors = [];
38+
angular.forEach(scope.ngModel && scope.ngModel.$error, function(status, code) {
39+
if (status) {
40+
// if true then there is an error
41+
// Angular 1.3 removes properties, so we will always just have errors.
42+
// Angular 1.2 sets them to false.
43+
errors.push(code);
44+
}
45+
});
4446

45-
// We only show one error.
46-
// TODO: Make that optional
47-
var error = errors[0];
47+
// In Angular 1.3 we use one $validator to stop the model value from getting updated.
48+
// this means that we always end up with a 'schemaForm' error.
49+
errors = errors.filter(function(e) { return e !== 'schemaForm'; });
4850

49-
if (error) {
50-
element.html(sfErrorMessage.interpolate(
51-
error,
52-
scope.ngModel.$modelValue,
53-
scope.ngModel.$viewValue,
54-
scope.form,
55-
scope.options && scope.options.validationMessage
56-
));
57-
} else {
58-
element.html(message);
51+
// We only show one error.
52+
// TODO: Make that optional
53+
var error = errors[0];
54+
55+
if (error) {
56+
setMessage(sfErrorMessage.interpolate(
57+
error,
58+
scope.ngModel.$modelValue,
59+
scope.ngModel.$viewValue,
60+
scope.form,
61+
scope.options && scope.options.validationMessage
62+
));
63+
} else {
64+
setMessage(message);
65+
}
5966
}
67+
} else {
68+
setMessage(message);
6069
}
6170
};
6271

6372
// Update once.
6473
update();
6574

66-
scope.$watchCollection('ngModel.$error', function() {
67-
if (scope.ngModel) {
68-
update(scope.ngModel.$valid);
75+
var once = scope.$watch('ngModel',function(ngModel) {
76+
if (ngModel) {
77+
// We also listen to changes of the model via parsers and formatters.
78+
// This is since both the error message can change and given a pristine
79+
// option to not show errors the ngModel.$error might not have changed
80+
// but we're not pristine any more so we should change!
81+
ngModel.$parsers.push(function(val) { update(true); return val; });
82+
ngModel.$formatters.push(function(val) { update(true); return val; });
83+
once();
6984
}
7085
});
7186

87+
// We watch for changes in $error
88+
scope.$watchCollection('ngModel.$error', function() {
89+
update(!!scope.ngModel);
90+
});
91+
7292
}
7393
};
7494
}]);

src/directives/schema-validate.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse
2424
sfSelect(path, scope.model, ngModel.$modelValue);
2525
});
2626
});
27-
}
27+
};
28+
2829

2930
// Validate against the schema.
3031

3132
var validate = function(viewValue) {
33+
//console.log('validate called', viewValue)
3234
//Still might be undefined
3335
if (!form) {
3436
return viewValue;
@@ -40,7 +42,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse
4042
}
4143

4244
var result = sfValidator.validate(form, viewValue);
43-
45+
//console.log('result is', result)
4446
// Since we might have different tv4 errors we must clear all
4547
// errors that start with tv4-
4648
Object.keys(ngModel.$error)
@@ -95,6 +97,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse
9597
// updating if we've found an error.
9698
if (ngModel.$validators) {
9799
ngModel.$validators.schemaForm = function() {
100+
//console.log('validators called.')
98101
// Any error and we're out of here!
99102
return !Object.keys(ngModel.$error).some(function(e) { return e !== 'schemaForm';});
100103
};
@@ -138,6 +141,20 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse
138141
}
139142
};
140143

144+
var first = true;
145+
ngModel.$formatters.push(function(val) {
146+
147+
// When a form first loads this will be called for each field.
148+
// we usually don't want that.
149+
if (ngModel.$pristine && first &&
150+
(!scope.options || scope.options.validateOnRender !== true)) {
151+
first = false;
152+
return val;
153+
}
154+
validate(ngModel.$modelValue);
155+
return val;
156+
});
157+
141158
// Listen to an event so we can validate the input on request
142159
scope.$on('schemaFormValidate', scope.validateField);
143160

src/services/decorators.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,35 @@ angular.module('schemaForm').provider('schemaFormDecorators',
125125
return (expression && $interpolate(expression)(locals));
126126
};
127127

128-
//This works since we ot the ngModel from the array or the schema-validate directive.
128+
//This works since we get the ngModel from the array or the schema-validate directive.
129129
scope.hasSuccess = function() {
130130
if (!scope.ngModel) {
131131
return false;
132132
}
133-
return scope.ngModel.$valid &&
133+
if (scope.options && scope.options.pristine &&
134+
scope.options.pristine.success === false) {
135+
return scope.ngModel.$valid &&
136+
!scope.ngModel.$pristine && !scope.ngModel.$isEmpty(scope.ngModel.$modelValue);
137+
} else {
138+
return scope.ngModel.$valid &&
134139
(!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue));
140+
}
135141
};
136142

137143
scope.hasError = function() {
138144
if (!scope.ngModel) {
139145
return false;
140146
}
141-
return scope.ngModel.$invalid && !scope.ngModel.$pristine;
147+
if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) {
148+
// Show errors in pristine forms. The default.
149+
// Note that "validateOnRender" option defaults to *not* validate initial form.
150+
// so as a default there won't be any error anyway, but if the model is modified
151+
// from the outside the error will show even if the field is pristine.
152+
return scope.ngModel.$invalid;
153+
} else {
154+
// Don't show errors in pristine forms.
155+
return scope.ngModel.$invalid && !scope.ngModel.$pristine;
156+
}
142157
};
143158

144159
/**

0 commit comments

Comments
 (0)