Skip to content

Commit 3d34d41

Browse files
committed
Unbreak validation
Validation was broken, mainly for numbers and date. This is an effort to do it in a more sane way. It means we append our validation last, after angular has done transformation. It has some side issues right now. Mainly typing charachters into a number field that is also required will give you a "required" warning instead of "this is not a number".
1 parent 0b84c61 commit 3d34d41

File tree

7 files changed

+150
-106
lines changed

7 files changed

+150
-106
lines changed

dist/schema-form.js

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ angular.module('schemaForm').provider('schemaFormDecorators',
271271
return scope.form.validationMessage[schemaError.code] ||
272272
scope.form.validationMessage['default'];
273273
} else {
274-
return scope.form.validationMessage.required ||
274+
return scope.form.validationMessage.number ||
275275
scope.form.validationMessage['default'] ||
276276
scope.form.validationMessage;
277277
}
@@ -282,8 +282,8 @@ angular.module('schemaForm').provider('schemaFormDecorators',
282282
return schemaError.message; //use tv4.js validation message
283283
}
284284

285-
//Otherwise we only use required so it must be it.
286-
return 'Required';
285+
//Otherwise we only have input number not being a number
286+
return 'Not a number';
287287

288288
};
289289
}
@@ -893,26 +893,26 @@ angular.module('schemaForm').factory('sfValidator', [function() {
893893
* @return a tv4js result object.
894894
*/
895895
validator.validate = function(form, value) {
896-
896+
if (!form) {
897+
return {valid: true};
898+
}
897899
var schema = form.schema;
898900

899901
if (!schema) {
900-
//Nothings to Validate
901-
return value;
902+
return {valid: true};
902903
}
903904

904-
//Type cast and validate against schema.
905-
//Basic types of json schema sans array and object
906-
if (schema.type === 'integer') {
907-
value = parseInt(value, 10);
908-
} else if (schema.type === 'number') {
909-
value = parseFloat(value, 10);
910-
} else if (schema.type === 'boolean' && typeof value === 'string') {
911-
if (value === 'true') {
912-
value = true;
913-
} else if (value === 'false') {
914-
value = false;
915-
}
905+
// Input of type text and textareas will give us a viewValue of ''
906+
// when empty, this is a valid value in a schema and does not count as something
907+
// that breaks validation of 'required'. But for our own sanity an empty field should
908+
// not validate if it's required.
909+
if (value === '') {
910+
value = undefined;
911+
}
912+
913+
// Numbers fields will give a null value, which also means empty field
914+
if (form.type === 'number' && value === null) {
915+
value = undefined;
916916
}
917917

918918
// Version 4 of JSON Schema has the required property not on the
@@ -929,7 +929,6 @@ angular.module('schemaForm').factory('sfValidator', [function() {
929929
if (angular.isDefined(value)) {
930930
valueWrap[propName] = value;
931931
}
932-
933932
return tv4.validateResult(valueWrap, wrap);
934933

935934
};
@@ -1295,59 +1294,69 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', functio
12951294
return {
12961295
restrict: 'A',
12971296
scope: false,
1297+
// We want the link function to be *after* the input directives link function so we get access
1298+
// the parsed value, ex. a number instead of a string
1299+
priority: 1000,
12981300
require: 'ngModel',
12991301
link: function(scope, element, attrs, ngModel) {
13001302
//Since we have scope false this is the same scope
13011303
//as the decorator
13021304
scope.ngModel = ngModel;
13031305

13041306
var error = null;
1305-
var form = scope.$eval(attrs.schemaValidate);
1306-
// Validate against the schema.
1307-
var validate = function(viewValue) {
1307+
1308+
var getForm = function() {
13081309
if (!form) {
13091310
form = scope.$eval(attrs.schemaValidate);
13101311
}
1312+
return form;
1313+
};
1314+
var form = getForm();
13111315

1312-
//Still might be undefined
1313-
if (!form) {
1314-
return viewValue;
1315-
}
1316+
// Validate against the schema.
13161317

1317-
// Is required is handled by ng-required?
1318-
if (angular.isDefined(attrs.ngRequired) && angular.isUndefined(viewValue)) {
1319-
return undefined;
1320-
}
1318+
// Get in last of the parses so the parsed value has the correct type.
1319+
if (ngModel.$validators) { // Angular 1.3
1320+
ngModel.$validators.schema = function(value) {
1321+
var result = sfValidator.validate(getForm(), value);
1322+
error = result.error;
1323+
return result.valid;
1324+
};
1325+
} else {
13211326

1322-
// An empty field gives us the an empty string, which JSON schema
1323-
// happily accepts as a proper defined string, but an empty field
1324-
// for the user should trigger "required". So we set it to undefined.
1325-
if (viewValue === '') {
1326-
viewValue = undefined;
1327-
}
1327+
// Angular 1.2
1328+
ngModel.$parsers.push(function(viewValue) {
1329+
form = getForm();
1330+
//Still might be undefined
1331+
if (!form) {
1332+
return viewValue;
1333+
}
13281334

1329-
var result = sfValidator.validate(form, viewValue);
1335+
var result = sfValidator.validate(form, viewValue);
13301336

1331-
if (result.valid) {
1332-
// it is valid
1333-
ngModel.$setValidity('schema', true);
1334-
return viewValue;
1335-
} else {
1336-
// it is invalid, return undefined (no model update)
1337-
ngModel.$setValidity('schema', false);
1338-
error = result.error;
1339-
return undefined;
1340-
}
1341-
};
1337+
if (result.valid) {
1338+
// it is valid
1339+
ngModel.$setValidity('schema', true);
1340+
return viewValue;
1341+
} else {
1342+
// it is invalid, return undefined (no model update)
1343+
ngModel.$setValidity('schema', false);
1344+
error = result.error;
1345+
return undefined;
1346+
}
1347+
});
1348+
}
13421349

1343-
// Unshift onto parsers of the ng-model.
1344-
ngModel.$parsers.unshift(validate);
13451350

13461351
// Listen to an event so we can validate the input on request
13471352
scope.$on('schemaFormValidate', function() {
13481353

1349-
if (ngModel.$commitViewValue) {
1350-
ngModel.$commitViewValue(true);
1354+
if (ngModel.$validate) {
1355+
ngModel.$validate();
1356+
if (ngModel.$invalid) { // The field must be made dirty so the error message is displayed
1357+
ngModel.$dirty = true;
1358+
ngModel.$pristine = false;
1359+
}
13511360
} else {
13521361
ngModel.$setViewValue(ngModel.$viewValue);
13531362
}

dist/schema-form.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/bootstrap-example.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ <h3>Schema</h3>
8080
<script type="text/javascript" src="../bower_components/angular-spectrum-colorpicker/dist/angular-spectrum-colorpicker.min.js"></script>
8181

8282

83-
<script type="text/javascript" src="../dist/schema-form.min.js"></script>
83+
<script type="text/javascript" src="../dist/schema-form.js"></script>
8484
<script type="text/javascript" src="../dist/bootstrap-decorator.min.js"></script>
8585
<script type="text/javascript" src="../dist/bootstrap-datepicker.min.js"></script>
8686
<script type="text/javascript" src="../bower_components/angular-schema-form-colorpicker/bootstrap-colorpicker.min.js"></script>
@@ -96,6 +96,7 @@ <h3>Schema</h3>
9696

9797
$scope.tests = [
9898
{ name: "Simple", data: 'data/simple.json' },
99+
{ name: "Basic JSON Schema Type", data: 'data/types.json' },
99100
{ name: "Complex Key Support", data: 'data/complex-keys.json' },
100101
{ name: "Array", data: 'data/array.json' },
101102
{ name: "Tab Array", data: 'data/tabarray.json' },

examples/data/types.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"schema": {
3+
"type": "object",
4+
"title": "Types",
5+
"properties": {
6+
"string": {
7+
"type": "string",
8+
"minLength": 3
9+
},
10+
"integer": {
11+
"type": "integer"
12+
},
13+
"number": {
14+
"type": "number"
15+
},
16+
"boolean": {
17+
"type": "boolean"
18+
}
19+
},
20+
"required": ["number"]
21+
},
22+
"form": [
23+
"*"
24+
]
25+
}

src/directives/schema-validate.js

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,69 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', functio
22
return {
33
restrict: 'A',
44
scope: false,
5+
// We want the link function to be *after* the input directives link function so we get access
6+
// the parsed value, ex. a number instead of a string
7+
priority: 1000,
58
require: 'ngModel',
69
link: function(scope, element, attrs, ngModel) {
710
//Since we have scope false this is the same scope
811
//as the decorator
912
scope.ngModel = ngModel;
1013

1114
var error = null;
12-
var form = scope.$eval(attrs.schemaValidate);
13-
// Validate against the schema.
14-
var validate = function(viewValue) {
15+
16+
var getForm = function() {
1517
if (!form) {
1618
form = scope.$eval(attrs.schemaValidate);
1719
}
20+
return form;
21+
};
22+
var form = getForm();
1823

19-
//Still might be undefined
20-
if (!form) {
21-
return viewValue;
22-
}
24+
// Validate against the schema.
2325

24-
// Is required is handled by ng-required?
25-
if (angular.isDefined(attrs.ngRequired) && angular.isUndefined(viewValue)) {
26-
return undefined;
27-
}
26+
// Get in last of the parses so the parsed value has the correct type.
27+
if (ngModel.$validators) { // Angular 1.3
28+
ngModel.$validators.schema = function(value) {
29+
var result = sfValidator.validate(getForm(), value);
30+
error = result.error;
31+
return result.valid;
32+
};
33+
} else {
2834

29-
// An empty field gives us the an empty string, which JSON schema
30-
// happily accepts as a proper defined string, but an empty field
31-
// for the user should trigger "required". So we set it to undefined.
32-
if (viewValue === '') {
33-
viewValue = undefined;
34-
}
35+
// Angular 1.2
36+
ngModel.$parsers.push(function(viewValue) {
37+
form = getForm();
38+
//Still might be undefined
39+
if (!form) {
40+
return viewValue;
41+
}
3542

36-
var result = sfValidator.validate(form, viewValue);
43+
var result = sfValidator.validate(form, viewValue);
3744

38-
if (result.valid) {
39-
// it is valid
40-
ngModel.$setValidity('schema', true);
41-
return viewValue;
42-
} else {
43-
// it is invalid, return undefined (no model update)
44-
ngModel.$setValidity('schema', false);
45-
error = result.error;
46-
return undefined;
47-
}
48-
};
45+
if (result.valid) {
46+
// it is valid
47+
ngModel.$setValidity('schema', true);
48+
return viewValue;
49+
} else {
50+
// it is invalid, return undefined (no model update)
51+
ngModel.$setValidity('schema', false);
52+
error = result.error;
53+
return undefined;
54+
}
55+
});
56+
}
4957

50-
// Unshift onto parsers of the ng-model.
51-
ngModel.$parsers.unshift(validate);
5258

5359
// Listen to an event so we can validate the input on request
5460
scope.$on('schemaFormValidate', function() {
5561

56-
if (ngModel.$commitViewValue) {
57-
ngModel.$commitViewValue(true);
62+
if (ngModel.$validate) {
63+
ngModel.$validate();
64+
if (ngModel.$invalid) { // The field must be made dirty so the error message is displayed
65+
ngModel.$dirty = true;
66+
ngModel.$pristine = false;
67+
}
5868
} else {
5969
ngModel.$setViewValue(ngModel.$viewValue);
6070
}

src/services/decorators.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ angular.module('schemaForm').provider('schemaFormDecorators',
145145
return scope.form.validationMessage[schemaError.code] ||
146146
scope.form.validationMessage['default'];
147147
} else {
148-
return scope.form.validationMessage.required ||
148+
return scope.form.validationMessage.number ||
149149
scope.form.validationMessage['default'] ||
150150
scope.form.validationMessage;
151151
}
@@ -156,8 +156,8 @@ angular.module('schemaForm').provider('schemaFormDecorators',
156156
return schemaError.message; //use tv4.js validation message
157157
}
158158

159-
//Otherwise we only use required so it must be it.
160-
return 'Required';
159+
//Otherwise we only have input number not being a number
160+
return 'Not a number';
161161

162162
};
163163
}

src/services/validator.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,26 @@ angular.module('schemaForm').factory('sfValidator', [function() {
1414
* @return a tv4js result object.
1515
*/
1616
validator.validate = function(form, value) {
17-
17+
if (!form) {
18+
return {valid: true};
19+
}
1820
var schema = form.schema;
1921

2022
if (!schema) {
21-
//Nothings to Validate
22-
return value;
23+
return {valid: true};
2324
}
2425

25-
//Type cast and validate against schema.
26-
//Basic types of json schema sans array and object
27-
if (schema.type === 'integer') {
28-
value = parseInt(value, 10);
29-
} else if (schema.type === 'number') {
30-
value = parseFloat(value, 10);
31-
} else if (schema.type === 'boolean' && typeof value === 'string') {
32-
if (value === 'true') {
33-
value = true;
34-
} else if (value === 'false') {
35-
value = false;
36-
}
26+
// Input of type text and textareas will give us a viewValue of ''
27+
// when empty, this is a valid value in a schema and does not count as something
28+
// that breaks validation of 'required'. But for our own sanity an empty field should
29+
// not validate if it's required.
30+
if (value === '') {
31+
value = undefined;
32+
}
33+
34+
// Numbers fields will give a null value, which also means empty field
35+
if (form.type === 'number' && value === null) {
36+
value = undefined;
3737
}
3838

3939
// Version 4 of JSON Schema has the required property not on the
@@ -50,7 +50,6 @@ angular.module('schemaForm').factory('sfValidator', [function() {
5050
if (angular.isDefined(value)) {
5151
valueWrap[propName] = value;
5252
}
53-
5453
return tv4.validateResult(valueWrap, wrap);
5554

5655
};

0 commit comments

Comments
 (0)