Skip to content

Commit 1c51997

Browse files
committed
Changed from using ng-required to tv4js
This also means required, minItems and maxItems validates on arrays. Aslo added an event 'schemaFormValidate' that forces validation of all fields. Useful from a submit button for instance.
1 parent 64f0415 commit 1c51997

File tree

10 files changed

+148
-46
lines changed

10 files changed

+148
-46
lines changed

examples/data/array.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
"schema": {
33
"type": "object",
44
"title": "Comment",
5+
"required": ["comments"],
56
"properties": {
67
"comments": {
78
"type": "array",
9+
"maxItems": 2,
810
"items": {
911
"type": "object",
1012
"properties": {
@@ -33,7 +35,7 @@
3335
"form": [
3436
{
3537
"type": "help",
36-
"helpvalue": "<h4>Array Example</h4><p>Try adding a couple of forms, reorder by drag'n'drop.</p>"
38+
"helpvalue": "<h4>Array Example</h4><p>Try adding a couple of forms, reorder by drag'n'drop.</p>"
3739
},
3840
{
3941
"key": "comments",

src/directives/array.js

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* Directive that handles the model arrays
33
*/
4-
angular.module('schemaForm').directive('sfArray', ['sfSelect','schemaForm',
5-
function(sfSelect, schemaForm) {
4+
angular.module('schemaForm').directive('sfArray', ['sfSelect','schemaForm','sfValidator',
5+
function(sfSelect, schemaForm, sfValidator) {
66

77
var setIndex = function(index) {
88
return function(form) {
@@ -15,7 +15,8 @@ function(sfSelect, schemaForm) {
1515
return {
1616
restrict: 'A',
1717
scope: true,
18-
link: function(scope, element, attrs) {
18+
require: '?ngModel',
19+
link: function(scope, element, attrs, ngModel) {
1920
var formDefCache = {};
2021

2122
// Watch for the form definition and then rewrite it.
@@ -74,10 +75,10 @@ function(sfSelect, schemaForm) {
7475
});
7576

7677
// If there are no defaults nothing is added so we need to initialize
77-
// the array. null for basic values, {} or [] for the others.
78+
// the array. undefined for basic values, {} or [] for the others.
7879
if (len === list.length) {
7980
var type = sfSelect('schema.items.type',form);
80-
var dflt = null;
81+
var dflt;
8182
if (type === 'object') {
8283
dflt = {};
8384
} else if (type === 'array') {
@@ -107,9 +108,6 @@ function(sfSelect, schemaForm) {
107108
if (form.titleMap && form.titleMap.length > 0) {
108109
scope.titleMapValues = [];
109110

110-
111-
112-
113111
// We watch the model for changes and the titleMapValues to reflect
114112
// the modelArray
115113
var updateTitleMapValues = function(arr) {
@@ -146,6 +144,56 @@ function(sfSelect, schemaForm) {
146144
});
147145
}
148146

147+
148+
// If there is a ngModel present we need to validate when asked.
149+
if (ngModel) {
150+
var error;
151+
152+
// Listen to an event so we can validate the input on request
153+
scope.$on('schemaFormValidate',function(payload) {
154+
// The actual content of the array is validated by each field
155+
// so we settle for checking validations specific to arrays
156+
157+
// Since we prefill with empty arrays we can get the funny situation
158+
// where the array is required but empty in the gui but still validates.
159+
// Thats why we check the length.
160+
var result = sfValidator.validate(
161+
form,
162+
scope.modelArray.length > 0 ? scope.modelArray : undefined
163+
);
164+
console.log(result.error)
165+
if (result.valid === false &&
166+
result.error &&
167+
(result.error.dataPath === '' ||
168+
result.error.dataPath === '/'+form.key[form.key.length - 1])) {
169+
console.log('setting invlid')
170+
// Set viewValue to trigger $dirty on field. If someone knows a
171+
// a better way to do it please tell.
172+
ngModel.$setViewValue(scope.modelArray);
173+
error = result.error;
174+
ngModel.$setValidity('schema', false);
175+
176+
} else {
177+
ngModel.$setValidity('schema', true);
178+
}
179+
180+
});
181+
182+
183+
scope.hasSuccess = function(){
184+
return ngModel.$valid && !ngModel.$pristine;
185+
};
186+
187+
scope.hasError = function(){
188+
return ngModel.$invalid;
189+
};
190+
191+
scope.schemaError = function() {
192+
return error;
193+
};
194+
195+
}
196+
149197
once();
150198
});
151199
}

src/directives/decorators/bootstrap/array.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div sf-array="form">
1+
<div sf-array="form" ng-model="$$value$$">
22
<h3 ng-show="form.title && form.notitle !== true">{{ form.title }}</h3>
33
<ul class="list-group" ng-model="modelArray" ui-sortable>
44
<li class="list-group-item" ng-repeat="item in modelArray track by $index">
@@ -18,4 +18,7 @@ <h3 ng-show="form.title && form.notitle !== true">{{ form.title }}</h3>
1818
{{ form.add || 'Add'}}
1919
</button>
2020
</div>
21+
<div class="help-block"
22+
ng-show="(hasError() && errorMessage(schemaError())) || form.description"
23+
ng-bind-html="(hasError() && errorMessage(schemaError())) || form.description"></div>
2124
</div>

src/directives/decorators/bootstrap/checkbox.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
<input type="checkbox"
44
sf-changed="form"
55
ng-model="$$value$$"
6-
schema-validate="form.schema"
7-
ng-required="form.required">
6+
schema-validate="form"
87
<span ng-bind-html="form.title"></span>
98
</label>
109

src/directives/decorators/bootstrap/datepicker/datepicker.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
style="background-color: white"
66
type="date"
77
class="form-control"
8-
ng-required="form.required"
9-
schema-validate="form.schema"
8+
schema-validate="form"
109
ng-model="$$value$$"
1110
pick-a-date
1211
min-date="form.minDate"

src/directives/decorators/bootstrap/default.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
sf-changed="form"
77
placeholder="{{form.placeholder}}"
88
class="form-control"
9-
ng-required="form.required"
109
ng-model="$$value$$"
11-
schema-validate="form.schema">
10+
schema-validate="form">
1211
<span ng-if="form.feedback !== false"
1312
class="form-control-feedback"
1413
ng-class="evalInScope(form.feedback) || {'glyphicon': true, 'glyphicon-ok': hasSuccess(), 'glyphicon-remove': hasError() }"></span>

src/directives/decorators/bootstrap/select.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
<select ng-model="$$value$$"
66
sf-changed="form"
77
class="form-control"
8-
schema-validate="form.schema"
9-
ng-required="form.required"
8+
schema-validate="form"
109
ng-options="item.value as item.name for item in form.titleMap">
1110
</select>
1211
<div class="help-block"

src/directives/decorators/bootstrap/textarea.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
<label ng-show="showTitle()">{{form.title}}</label>
33
<textarea class="form-control"
44
sf-changed="form"
5-
ng-required="form.required"
65
ng-model="$$value$$"
7-
schema-validate="form.schema"></textarea>
6+
schema-validate="form"></textarea>
87
<!-- disabled until we unbreak the css
98
<span ng-if="form.feedback !== false"
109
class="form-control-feedback"

src/directives/schema-validate.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* global tv4 */
2-
angular.module('schemaForm').directive('schemaValidate',function(){
2+
angular.module('schemaForm').directive('schemaValidate',['sfValidator',function(sfValidator){
33
return {
44
restrict: 'A',
55
scope: false,
@@ -10,50 +10,50 @@ angular.module('schemaForm').directive('schemaValidate',function(){
1010
scope.ngModel = ngModel;
1111

1212
var error = null;
13-
var schema = scope.$eval(attrs.schemaValidate);
14-
15-
ngModel.$parsers.unshift(function(viewValue) {
16-
if (!schema) {
17-
schema = scope.$eval(attrs.schemaValidate);
13+
var form = scope.$eval(attrs.schemaValidate);
14+
// Validate against the schema.
15+
var validate = function(viewValue) {
16+
if (!form) {
17+
form = scope.$eval(attrs.schemaValidate);
1818
}
1919

20-
//Still might be undefined, especially if form has no schema...
21-
if (!schema) {
20+
//Still might be undefined
21+
if (!form) {
2222
return viewValue;
2323
}
2424

25-
//required is handled by ng-required
26-
if (angular.isUndefined(viewValue)) {
25+
// Is required is handled by ng-required?
26+
if (angular.isDefined(attrs.ngRequired) && angular.isUndefined(viewValue)) {
2727
return undefined;
2828
}
2929

30-
//Type cast and validate against schema.
31-
//Basic types of json schema sans array and object
32-
var value = viewValue;
33-
if (schema.type === 'integer') {
34-
value = parseInt(value,10);
35-
} else if (schema.type === 'number') {
36-
value = parseFloat(value,10);
37-
} else if (schema.type === 'boolean' && typeof viewValue === 'string') {
38-
if (viewValue === 'true') {
39-
value = true;
40-
} else if (viewValue === 'false') {
41-
value = false;
42-
}
30+
// An empty field gives us the an empty string, which JSON schema
31+
// happily accepts as a proper defined string, but an empty field
32+
// for the user should trigger "required". So we set it to undefined.
33+
if (viewValue === "") {
34+
viewValue = undefined;
4335
}
4436

45-
var result = tv4.validateResult(value,schema);
37+
var result = sfValidator.validate(form, viewValue);
38+
4639
if (result.valid) {
4740
// it is valid
4841
ngModel.$setValidity('schema', true);
49-
error = null;
5042
return viewValue;
5143
} else {
5244
// it is invalid, return undefined (no model update)
5345
ngModel.$setValidity('schema', false);
5446
error = result.error;
5547
return undefined;
5648
}
49+
};
50+
51+
// Unshift onto parsers of the ng-model.
52+
ngModel.$parsers.unshift(validate);
53+
54+
// Listen to an event so we can validate the input on request
55+
scope.$on('schemaFormValidate',function() {
56+
ngModel.$commitViewValue(true);
5757
});
5858

5959
//This works since we now we're inside a decorator and that this is the decorators scope.
@@ -72,4 +72,4 @@ angular.module('schemaForm').directive('schemaValidate',function(){
7272

7373
}
7474
};
75-
});
75+
}]);

src/services/validator.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* Common code for validating a value against its form and schema definition */
2+
/* global tv4 */
3+
angular.module('schemaForm').factory('sfValidator',[function(){
4+
5+
var validator = {};
6+
7+
/**
8+
* Validate a value against its form definition and schema.
9+
* The value should either be of proper type or a string, some type
10+
* coercion is applied.
11+
*
12+
* @param {Object} form A merged form definition, i.e. one with a schema.
13+
* @param {Any} value the value to validate.
14+
* @return a tv4js result object.
15+
*/
16+
validator.validate = function(form, value) {
17+
18+
var schema = form.schema;
19+
20+
//Type cast and validate against schema.
21+
//Basic types of json schema sans array and object
22+
if (schema.type === 'integer') {
23+
value = parseInt(value,10);
24+
} else if (schema.type === 'number') {
25+
value = parseFloat(value,10);
26+
} else if (schema.type === 'boolean' && typeof value === 'string') {
27+
if (value === 'true') {
28+
value = true;
29+
} else if (value === 'false') {
30+
value = false;
31+
}
32+
}
33+
34+
// Version 4 of JSON Schema has the required property not on the
35+
// property itself but on the wrapping object. Since we like to test
36+
// only this property we wrap it in a fake object.
37+
var wrap = { type: "object", "properties": { }};
38+
var propName = form.key[form.key.length - 1];
39+
wrap.properties[propName] = schema;
40+
41+
if (form.required) {
42+
wrap.required = [propName];
43+
}
44+
var valueWrap = {};
45+
if (angular.isDefined(value)) {
46+
valueWrap[propName] = value;
47+
}
48+
49+
return tv4.validateResult(valueWrap, wrap);
50+
51+
};
52+
53+
return validator;
54+
}]);

0 commit comments

Comments
 (0)