-
Notifications
You must be signed in to change notification settings - Fork 652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] oneOf/anyOf support #505
base: development
Are you sure you want to change the base?
Changes from all commits
2072fca
e7863b6
c013170
15e67b7
e952803
8145236
16a48b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<div sf-form-select="form" class="schema-form-form-select {{form.htmlClass}}"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has now moved to https://github.com/json-schema-form/angular-schema-form-bootstrap There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably create a simple one for core. |
||
<legend ng-class="{'sr-only': !showTitle() }">{{ form.title }}</legend> | ||
<div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div> | ||
<select ng-model="selectedForm" | ||
ng-disabled="form.readonly" | ||
class="form-control {{form.fieldHtmlClass}}" | ||
ng-options="form.items.indexOf(item) as item.title for item in form.items"> | ||
</select> | ||
<sf-decorator ng-repeat="item in form.items" form="item" ng-show="selectedForm == $index"></sf-decorator> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/** | ||
* Directive that handles the model arrays | ||
*/ | ||
angular.module('schemaForm').directive('sfFormSelect', ['sfSelect', 'schemaForm', 'sfValidator', 'sfPath', | ||
function(sfSelect, schemaForm, sfValidator, sfPath) { | ||
|
||
return { | ||
restrict: 'A', | ||
scope: true, | ||
require: '?ngModel', | ||
link: function(scope, element, attrs, ngModel) { | ||
scope.selectedForm = 0; | ||
// Keeps the model data for each form | ||
var formData = []; | ||
// TODO: Watch the model value and pick a form to match it | ||
scope.$parent.$watch(attrs.sfFormSelect, function(form) { | ||
if (!form) { | ||
return; | ||
} | ||
|
||
var model = sfSelect(form.key, scope.model); | ||
|
||
// Watch the model value and change to a form that matches it | ||
var key = sfPath.normalize(form.key); | ||
scope.$parent.$watch('model' + key, function (value) { | ||
model = scope.modelData = value; | ||
// If the selected form still validates, make sure we don't change it | ||
if (angular.isNumber(scope.selectedForm)) { | ||
var r = sfValidator.validate(form.items[scope.selectedForm], model); | ||
if (r.valid) return; | ||
} | ||
// Search for a form that is valid for the given model data | ||
for (var i = 0; i < form.items.length; i++) { | ||
var result = sfValidator.validate(form.items[i], model); | ||
if (result.valid) { | ||
scope.selectedForm = i; | ||
break; | ||
} | ||
} | ||
}); | ||
|
||
// Restore data associated with selected form | ||
scope.$watch('selectedForm', function(selectedForm, oldForm) { | ||
formData[oldForm] = model; | ||
if (formData[selectedForm]) sfSelect(form.key, scope.model, formData[selectedForm]); | ||
// TODO: Fill defaults if we don't have data for this form | ||
}); | ||
}); | ||
} | ||
}; | ||
} | ||
]); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,14 +6,16 @@ | |
angular.module('schemaForm').provider('schemaForm', | ||
['sfPathProvider', function(sfPathProvider) { | ||
var stripNullType = function(type) { | ||
if (Array.isArray(type) && type.length == 2) { | ||
if (type[0] === 'null') | ||
return type[1]; | ||
if (type[1] === 'null') | ||
return type[0]; | ||
if (Array.isArray(type) && type.length > 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has now moved to https://github.com/json-schema-form/json-schema-form-core |
||
var filtered = type.filter(function (type) { | ||
return type !== 'null' | ||
}); | ||
if (filtered.length == 1) | ||
return filtered[0]; | ||
return filtered; | ||
} | ||
return type; | ||
} | ||
}; | ||
|
||
//Creates an default titleMap list from an enum, i.e. a list of strings. | ||
var enumToTitleMap = function(enm) { | ||
|
@@ -43,8 +45,71 @@ angular.module('schemaForm').provider('schemaForm', | |
return titleMap; | ||
}; | ||
|
||
var extendSchemas = function(obj1, obj2) { | ||
obj1 = angular.extend({}, obj1); | ||
obj2 = angular.extend({}, obj2); | ||
|
||
var self = this; | ||
var extended = {}; | ||
angular.forEach(obj1, function(val,prop) { | ||
// If this key is also defined in obj2, merge them | ||
if(typeof obj2[prop] !== 'undefined') { | ||
// Required arrays should be unioned together | ||
if(prop === 'required' && typeof val === 'object' && Array.isArray(val)) { | ||
// Union arrays and unique | ||
extended.required = val.concat(obj2[prop]).reduce(function(p, c) { | ||
if (p.indexOf(c) < 0) p.push(c); | ||
return p; | ||
}, []); | ||
} | ||
// Type should be intersected and is either an array or string | ||
else if(prop === 'type' && (typeof val === 'string' || Array.isArray(val))) { | ||
// Make sure we're dealing with arrays | ||
if(typeof val === 'string') val = [val]; | ||
if(typeof obj2.type === 'string') obj2.type = [obj2.type]; | ||
|
||
|
||
extended.type = val.filter(function(n) { | ||
return obj2.type.indexOf(n) !== -1; | ||
}); | ||
|
||
// If there's only 1 type, use a string instead of array | ||
if(extended.type.length === 1) { | ||
extended.type = extended.type[0]; | ||
} | ||
} | ||
// All other arrays should be intersected (enum, etc.) | ||
else if(typeof val === 'object' && Array.isArray(val)){ | ||
extended[prop] = val.filter(function(n) { | ||
return obj2[prop].indexOf(n) !== -1; | ||
}); | ||
} | ||
// Objects should be recursively merged | ||
else if(typeof val === 'object' && val !== null) { | ||
extended[prop] = extendSchemas(val, obj2[prop]); | ||
} | ||
// Otherwise, use the first value | ||
else { | ||
extended[prop] = val; | ||
} | ||
} | ||
// Otherwise, just use the one in obj1 | ||
else { | ||
extended[prop] = val; | ||
} | ||
}); | ||
// Properties in obj2 that aren't in obj1 | ||
angular.forEach(obj2, function(val, prop) { | ||
if(typeof obj1[prop] === 'undefined') { | ||
extended[prop] = val; | ||
} | ||
}); | ||
|
||
return extended; | ||
}; | ||
|
||
var defaultFormDefinition = function(name, schema, options) { | ||
var rules = defaults[stripNullType(schema.type)]; | ||
var rules = defaults['any'].concat(defaults[stripNullType(schema.type)]); | ||
if (rules) { | ||
var def; | ||
for (var i = 0; i < rules.length; i++) { | ||
|
@@ -228,9 +293,52 @@ angular.module('schemaForm').provider('schemaForm', | |
|
||
}; | ||
|
||
var formselect = function(name, schema, options) { | ||
var types = stripNullType(schema.type); | ||
if (!(schema.oneOf || schema.anyOf || angular.isArray(types))) return; | ||
var f = stdFormObj(name, schema, options); | ||
f.type = 'formselect'; | ||
f.key = options.path; | ||
var schemas = []; | ||
// TODO: What if there are more than one of these keys in the same schema? | ||
if (angular.isArray(types)) { | ||
angular.forEach(types, function(type) { | ||
schemas.push(extendSchemas({type: type}, schema)); | ||
}) | ||
} else { | ||
angular.forEach(schema.anyOf || schema.oneOf, function(value) { | ||
var extended = extendSchemas(value, schema); | ||
delete extended.oneOf; | ||
delete extended.anyOf; | ||
schemas.push(extended) | ||
}) | ||
} | ||
f.items = []; | ||
angular.forEach(schemas, function(s) { | ||
var subForm = defaultFormDefinition(name, s, options); | ||
subForm.notitle = true; | ||
f.items.push(subForm); | ||
}); | ||
|
||
return f; | ||
}; | ||
|
||
var allof = function(name, schema, options) { | ||
if (schema.allOf) { | ||
var extended = schema; | ||
var allOf = schema.allOf; | ||
delete schema.allOf; | ||
angular.forEach(allOf, function(s) { | ||
extended = extendSchemas(s, extended); | ||
}); | ||
return defaultFormDefinition(name, extended, options); | ||
} | ||
}; | ||
|
||
//First sorted by schema type then a list. | ||
//Order has importance. First handler returning an form snippet will be used. | ||
var defaults = { | ||
any: [allof, formselect], | ||
string: [select, text], | ||
object: [fieldset], | ||
number: [number], | ||
|
@@ -434,7 +542,6 @@ angular.module('schemaForm').provider('schemaForm', | |
path = path || []; | ||
|
||
var traverse = function(schema, fn, path) { | ||
fn(schema, path); | ||
angular.forEach(schema.properties, function(prop, name) { | ||
var currentPath = path.slice(); | ||
currentPath.push(name); | ||
|
@@ -446,6 +553,24 @@ angular.module('schemaForm').provider('schemaForm', | |
var arrPath = path.slice(); arrPath.push(''); | ||
traverse(schema.items, fn, arrPath); | ||
} | ||
if (schema.dependencies) { | ||
angular.forEach(schema.dependencies, function(value, key) { | ||
if(typeof value === "object" && !(Array.isArray(value))) { | ||
traverse(value, fn, path); | ||
} | ||
}); | ||
} | ||
if (schema.not) { | ||
traverse(schema.not, fn, path) | ||
} | ||
angular.forEach(['allOf', 'oneOf', 'anyOf'], function(prop) { | ||
if (schema[prop]) { | ||
angular.forEach(schema[prop], function(value) { | ||
traverse(value, fn, path); | ||
}) | ||
} | ||
}); | ||
fn(schema, path); | ||
}; | ||
|
||
traverse(schema, fn, path || []); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has now moved to https://github.com/json-schema-form/angular-schema-form-bootstrap