Skip to content

Commit 1a2adc5

Browse files
committed
Merge branch 'feature/extendme' into development
2 parents 1b483a3 + 21ca189 commit 1a2adc5

File tree

3 files changed

+196
-88
lines changed

3 files changed

+196
-88
lines changed

karma.conf.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ module.exports = function(config) {
3737
reporters: ['progress','coverage','growler'],
3838

3939
preprocessors: {
40-
'src/*.js': 'coverage',
41-
'src/**/*.html': 'ng-html2js'
40+
'src/**/*.js': ['coverage'],
41+
'src/**/*.html': ['ng-html2js']
4242
},
4343

4444
// optionally, configure the reporter

src/services/schema-form.js

Lines changed: 133 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,20 @@
33
* This service is not that useful outside of schema form directive
44
* but makes the code more testable.
55
*/
6-
angular.module('schemaForm').factory('schemaForm',[function(){
7-
var service = {};
6+
angular.module('schemaForm').provider('schemaForm',[function(){
87

9-
service.merge = function(schema,form,ignore) {
10-
form = form || ["*"];
11-
12-
var stdForm = service.defaults(schema,ignore);
13-
14-
//simple case, we have a "*", just put the stdForm there
15-
var idx = form.indexOf("*");
16-
if (idx !== -1) {
17-
form = form.slice(0,idx)
18-
.concat(stdForm.form)
19-
.concat(form.slice(idx+1));
20-
return form;
21-
}
22-
23-
//ok let's merge!
24-
//We look at the supplied form and extend it with schema standards
25-
var lookup = stdForm.lookup;
26-
return form.map(function(obj){
27-
28-
//handle the shortcut with just a name
29-
if (typeof obj === 'string') {
30-
obj = { key: obj };
31-
}
32-
33-
//if it's a type with items, merge 'em!
34-
if (obj.items) {
35-
obj.items = service.merge(schema,obj.items,ignore);
36-
}
37-
38-
39-
//extend with std form from schema.
40-
if (obj.key && lookup[obj.key]) {
41-
return angular.extend(lookup[obj.key],obj);
8+
var defaultFormDefinition = function(name,schema,options){
9+
var rules = defaults[schema.type];
10+
if (rules) {
11+
var def;
12+
for (var i=0;i<rules.length; i++) {
13+
def = rules[i](name,schema,options);
14+
//first handler in list that actually returns something is our handler!
15+
if (def) {
16+
return def;
17+
}
4218
}
43-
44-
return obj;
45-
});
19+
}
4620
};
4721

4822
//Creates a form object with all common properties
@@ -98,7 +72,7 @@ angular.module('schemaForm').factory('schemaForm',[function(){
9872
}
9973
};
10074

101-
var bool = function(name,schema,options) {
75+
var checkbox = function(name,schema,options) {
10276
if (schema.type === 'boolean') {
10377
var f = stdFormObj(schema,options);
10478
f.key = options.path;
@@ -172,64 +146,139 @@ angular.module('schemaForm').factory('schemaForm',[function(){
172146

173147
};
174148

175-
//TODO: make it pluginable, i.e. others can register handlers
176-
//TODO: maybe make first selection of handlers by type to optmize
177-
//Order has importance. First handler returning an form snippet will be used
178-
var defaults = [
179-
text,
180-
select,
181-
fieldset,
182-
number,
183-
integer,
184-
bool,
185-
checkboxes
186-
];
149+
//First sorted by schema type then a list.
150+
//Order has importance. First handler returning an form snippet will be used.
151+
var defaults = {
152+
string: [ select, text ],
153+
object: [ fieldset],
154+
number: [ number ],
155+
integer: [ integer ],
156+
boolean: [ checkbox ],
157+
array: [ checkboxes ]
158+
};
187159

188160

189-
var defaultFormDefinition = function(name,schema,options){
190-
var def;
191-
for (var i=0;i<defaults.length; i++) {
192161

193-
def = defaults[i](name,schema,options);
194-
//first handler in list that actually returns something is our handler!
195-
if (def) {
196-
return def;
197-
}
162+
/**
163+
* Provider API
164+
*/
165+
this.defaults = defaults;
166+
167+
/**
168+
* Append default form rule
169+
* @param {string} type json schema type
170+
* @param {Function} rule a function(propertyName,propertySchema,options) that returns a form definition or undefined
171+
*/
172+
this.appendRule = function(type,rule) {
173+
if (!defaults[type]) {
174+
defaults[type] = [];
198175
}
176+
defaults[type].push(rule);
199177
};
200178

179+
/**
180+
* Prepend default form rule
181+
* @param {string} type json schema type
182+
* @param {Function} rule a function(propertyName,propertySchema,options) that returns a form definition or undefined
183+
*/
184+
this.prependRule = function(type,rule) {
185+
if (!defaults[type]) {
186+
defaults[type] = [];
187+
}
188+
defaults[type].unshift(rule);
189+
};
201190

202191
/**
203-
* Create form defaults from schema
192+
* Utility function to create a standard form object.
193+
* This does *not* set the type of the form but rather all shared attributes.
194+
* You probably want to start your rule with creating the form with this method
195+
* then setting type and any other values you need.
196+
* @param {Object} schema
197+
* @param {Object} options
198+
* @return {Object} a form field defintion
204199
*/
205-
service.defaults = function(schema,ignore) {
206-
var form = [];
207-
var lookup = {}; //Map path => form obj for fast lookup in merging
208-
ignore = ignore || {};
200+
this.createStandardForm = stdFormObj;
201+
/* End Provider API */
209202

210-
if (schema.type === "object") {
211-
angular.forEach(schema.properties,function(v,k){
212-
if (ignore[k] !== true) {
213-
var required = schema.required && schema.required.indexOf(k) !== -1;
214-
var def = defaultFormDefinition(k,v,{
215-
path: k, //path to this property in dot notation. Root object has no name
216-
lookup: lookup, //extra map to register with. Optimization for merger.
217-
ignore: ignore, //The ignore list of paths (sans root level name)
218-
required: required //Is it required? (v4 json schema style)
219-
});
220-
if (def) {
221-
form.push(def);
222-
}
223-
}
203+
204+
this.$get = function(){
205+
206+
var service = {};
207+
208+
service.merge = function(schema,form,ignore) {
209+
form = form || ["*"];
210+
211+
var stdForm = service.defaults(schema,ignore);
212+
213+
//simple case, we have a "*", just put the stdForm there
214+
var idx = form.indexOf("*");
215+
if (idx !== -1) {
216+
form = form.slice(0,idx)
217+
.concat(stdForm.form)
218+
.concat(form.slice(idx+1));
219+
return form;
220+
}
221+
222+
//ok let's merge!
223+
//We look at the supplied form and extend it with schema standards
224+
var lookup = stdForm.lookup;
225+
return form.map(function(obj){
226+
227+
//handle the shortcut with just a name
228+
if (typeof obj === 'string') {
229+
obj = { key: obj };
230+
}
231+
232+
//if it's a type with items, merge 'em!
233+
if (obj.items) {
234+
obj.items = service.merge(schema,obj.items,ignore);
235+
}
236+
237+
238+
//extend with std form from schema.
239+
if (obj.key && lookup[obj.key]) {
240+
return angular.extend(lookup[obj.key],obj);
241+
}
242+
243+
return obj;
224244
});
245+
};
246+
247+
248+
249+
/**
250+
* Create form defaults from schema
251+
*/
252+
service.defaults = function(schema,ignore) {
253+
var form = [];
254+
var lookup = {}; //Map path => form obj for fast lookup in merging
255+
ignore = ignore || {};
256+
257+
if (schema.type === "object") {
258+
angular.forEach(schema.properties,function(v,k){
259+
if (ignore[k] !== true) {
260+
var required = schema.required && schema.required.indexOf(k) !== -1;
261+
var def = defaultFormDefinition(k,v,{
262+
path: k, //path to this property in dot notation. Root object has no name
263+
lookup: lookup, //extra map to register with. Optimization for merger.
264+
ignore: ignore, //The ignore list of paths (sans root level name)
265+
required: required //Is it required? (v4 json schema style)
266+
});
267+
if (def) {
268+
form.push(def);
269+
}
270+
}
271+
});
225272

226-
} else {
227-
throw new Exception('Not implemented. Only type "object" allowed at root level of schema.');
228-
}
273+
} else {
274+
throw new Error('Not implemented. Only type "object" allowed at root level of schema.');
275+
}
276+
277+
return { form: form, lookup: lookup };
278+
};
229279

230-
return { form: form, lookup: lookup };
231-
};
232280

281+
return service;
282+
};
233283

234-
return service;
235284
}]);

test/schema-form-test.js

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
chai.should();
33

44
describe('Schema form',function(){
5-
beforeEach(module('templates'));
6-
beforeEach(module('schemaForm'));
75

86
describe('directive',function(){
7+
beforeEach(module('templates'));
8+
beforeEach(module('schemaForm'));
9+
10+
911

1012
var exampleSchema = {
1113
"type": "object",
@@ -719,6 +721,9 @@ describe('Schema form',function(){
719721

720722

721723
describe('service',function(){
724+
beforeEach(module('templates'));
725+
beforeEach(module('schemaForm'));
726+
722727
it('should generate default form def from a schema',function(){
723728
inject(function(schemaForm){
724729

@@ -836,6 +841,57 @@ describe('Schema form',function(){
836841
});
837842
});
838843

844+
it('should be extendable with new defaults',function(){
845+
module(function(schemaFormProvider){
846+
schemaFormProvider.prependRule('string',function(name,schema,options){
847+
if (schema.format === 'foobar') {
848+
var f = schemaFormProvider.createStandardForm(schema,options);
849+
f.type = 'foobar';
850+
return f;
851+
}
852+
});
853+
854+
schemaFormProvider.appendRule('string',function(name,schema,options){
855+
var f = schemaFormProvider.createStandardForm(schema,options);
856+
f.type = 'notused';
857+
return f;
858+
});
859+
});
860+
861+
inject(function(schemaForm){
862+
863+
var schema = {
864+
"type": "object",
865+
"properties": {
866+
"name": {
867+
"title": "Name",
868+
"format": "foobar",
869+
"description": "Gimme yea name lad",
870+
"type": "string"
871+
},
872+
"gender": {
873+
"title": "Choose",
874+
"type": "string",
875+
"enum": [
876+
"undefined",
877+
"null",
878+
"NaN",
879+
]
880+
}
881+
}
882+
};
883+
884+
//no form is implicitly ['*']
885+
var defaults = schemaForm.defaults(schema).form;
886+
defaults[0].type.should.be.equal('foobar');
887+
defaults[0].title.should.be.equal('Name');
888+
defaults[1].type.should.be.equal('select');
889+
defaults[1].title.should.be.equal('Choose');
890+
891+
});
892+
});
893+
894+
839895

840896
it('should ignore parts of schema in ignore list',function(){
841897
inject(function(schemaForm){
@@ -912,6 +968,9 @@ describe('Schema form',function(){
912968
});
913969

914970
describe('decorator factory service',function(){
971+
beforeEach(module('templates'));
972+
beforeEach(module('schemaForm'));
973+
915974
it('should enable you to create new decorator directives',function(){
916975
module(function(schemaFormDecoratorsProvider){
917976
schemaFormDecoratorsProvider.createDecorator('foobar',{ 'foo':'/bar.html' },[angular.noop]);

0 commit comments

Comments
 (0)