Skip to content

Commit 1688a12

Browse files
committed
Roll your own decorators!
Refactored so that all decorators (can) share a common factory method. This means rolling your own or overriding/exteding bootstrap-decorator is as simple as writing some templates and doing a config.
1 parent 0b45455 commit 1688a12

File tree

9 files changed

+128
-150
lines changed

9 files changed

+128
-150
lines changed

karma.conf.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ module.exports = function(config) {
1919
'http://code.angularjs.org/1.2.14/angular-mocks.js',
2020
'bower_components/tv4/tv4.js',
2121
'src/module.js',
22-
'src/**/*.js',
22+
'src/services/*.js',
23+
'src/directives/**/*.js',
2324
'src/**/*.html',
2425
'test/*.js'
2526
],

src/bootstrap-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ <h3>Model</h3>
4040
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
4141
<script type="text/javascript" src="module.js"></script>
4242
<script type="text/javascript" src="services/schema-form.js"></script>
43+
<script type="text/javascript" src="services/decorators.js"></script>
4344
<script type="text/javascript" src="directives/decorators/bootstrap/bootstrap-decorator.js"></script>
4445
<script type="text/javascript" src="directives/schema-form.js"></script>
4546
<script type="text/javascript" src="directives/schema-validate.js"></script>

src/directives/decorator.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/directives/decorators/bootstrap/actions.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<input ng-repeat-start="item in form.items"
33
type="submit"
44
class="btn btn-primary"
5-
value="item.title"
5+
value="{{item.title}}"
66
ng-if="item.type === 'submit'">
77
<button ng-repeat-end class="btn btn-default" ng-if="item.type !== 'submit'">{{item.title}}</button>
88
</div>
Lines changed: 18 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,25 @@
1-
angular.module('schemaForm').directive('bootstrapDecorator',
2-
['$parse','$compile','$http','$templateCache',
3-
function($parse, $compile, $http, $templateCache){
4-
5-
var templateUrl = function(form) {
6-
//readonly is a special case
1+
angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(decoratorsProvider){
2+
3+
decoratorsProvider.create('bootstrapDecorator',{
4+
textarea: 'directives/decorators/bootstrap/textarea.html',
5+
fieldset: 'directives/decorators/bootstrap/fieldset.html',
6+
section: 'directives/decorators/bootstrap/section.html',
7+
actions: 'directives/decorators/bootstrap/actions.html',
8+
select: 'directives/decorators/bootstrap/select.html',
9+
checkbox: 'directives/decorators/bootstrap/checkbox.html',
10+
checkboxes: 'directives/decorators/bootstrap/checkboxes.html',
11+
number: 'directives/decorators/bootstrap/default.html',
12+
submit: 'directives/decorators/bootstrap/submit.html',
13+
'default': 'directives/decorators/bootstrap/default.html'
14+
},[
15+
function(form){
716
if (form.readonly && form.key && form.type !== 'fieldset') {
817
return 'directives/decorators/bootstrap/readonly.html';
918
}
10-
if (form.type === 'textarea') {
11-
return 'directives/decorators/bootstrap/textarea.html';
12-
}
13-
if (form.type === 'fieldset') {
14-
return 'directives/decorators/bootstrap/fieldset.html';
15-
}
16-
if (form.type === 'section') {
17-
return 'directives/decorators/bootstrap/section.html';
18-
}
19-
if (form.type === 'actions') {
20-
return 'directives/decorators/bootstrap/actions.html';
21-
}
22-
if (form.type === 'select') {
23-
return 'directives/decorators/bootstrap/select.html';
24-
}
25-
if (form.type === 'checkbox') {
26-
return 'directives/decorators/bootstrap/checkbox.html';
27-
}
28-
if (form.type === 'checkboxes') {
29-
return 'directives/decorators/bootstrap/checkboxes.html';
30-
}
31-
if (form.type === 'number') {
32-
return 'directives/decorators/bootstrap/default.html';
33-
}
34-
if (form.type === 'submit') {
35-
return 'directives/decorators/bootstrap/submit.html';
36-
}
37-
38-
return 'directives/decorators/bootstrap/default.html';
39-
};
40-
41-
42-
43-
return {
44-
restrict: 'AE',
45-
replace: true,
46-
transclude: false,
47-
scope: true,
48-
link: function(scope,element,attrs) {
49-
50-
//rebind our part of the form to the scope.
51-
var once = scope.$watch(attrs.form,function(form){
52-
scope.form = form;
19+
}
20+
]);
5321

54-
//ok let's replace that template!
55-
//We do this manually since we need to bind ng-model properly and also
56-
//for fieldsets to recurse properly.
57-
$http.get(templateUrl(form),{ cache: $templateCache }).then(function(res){
58-
var template = res.data.replace(/\$\$value\$\$/g,'model.'+form.key);
59-
$compile(template)(scope,function(clone){
60-
element.replaceWith(clone);
61-
});
62-
});
63-
once();
64-
});
22+
}]);
6523

66-
//Keep error prone logic from the template
67-
scope.showTitle = function() {
68-
return scope.form && scope.form.notitle !== true && scope.form.title;
69-
};
7024

71-
scope.checkboxValuesToList = function(values){
72-
var lst = [];
73-
angular.forEach(values,function(v,k){
74-
if (v) {
75-
lst.push(k);
76-
}
77-
});
78-
return lst;
79-
};
80-
}
81-
};
82-
}]);
8325

src/directives/decorators/bootstrap/readonly.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="form-group">
2-
<label ng-show="showTitle()">{{form.title}}</label>
2+
<label ng-show="showTitle()">{{form.title}} **READONLY**</label>
33
<input ng-if="form.type !== 'textarea'" type="text" disabled class="form-control" value="{{$$value$$}}">
44
<textarea ng-if="form.type === 'textarea'" disabled class="form-control">{{$$value$$}}</textarea>
55
<span class="help-block" ng-show="form.description">{{form.description}} </span>

src/directives/schema-form.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ function($compile, schemaForm){
7777
var n = document.createElement(attrs.sfDecorator || 'bootstrap-decorator');
7878
n.setAttribute('type',obj.type);
7979
n.setAttribute('form','schemaForm.form['+i+']');
80-
//n.setAttribute('schema','schemaForm.schema');
81-
//if (obj.key) {
82-
// n.setAttribute('value','model.'+obj.key);
83-
//}
8480
frag.appendChild(n);
8581

8682
});

src/services/decorators.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider',function($compileProvider){
2+
3+
var directives = {};
4+
5+
6+
var templateUrl = function(name,form) {
7+
var directive = directives[name];
8+
9+
//rules first
10+
var rules = directive.rules;
11+
for (var i = 0; i< rules.length; i++) {
12+
var res = rules[i](form);
13+
if (res) {
14+
return res;
15+
}
16+
}
17+
18+
//then check mapping
19+
if (directive.mappings[form.type]) {
20+
return directive.mappings[form.type];
21+
}
22+
23+
//try default
24+
return directive.mappings['default'];
25+
};
26+
27+
28+
/**
29+
* Create a decorator directive
30+
* @param {string} name directive name (CamelCased)
31+
* @param {Object} mappings, an object that maps "type" => "templateUrl"
32+
* @param {Array} rules (optional) a list of functions, function(form){}, that are each tried in turn,
33+
* if they return a string then that is used as the templateUrl. Rules come before
34+
* mappings.
35+
*/
36+
this.create = function(name,mappings,rules){
37+
directives[name] = {
38+
mappings: mappings || {},
39+
rules: rules || []
40+
};
41+
42+
$compileProvider.directive(name,['$parse','$compile','$http','$templateCache',
43+
function($parse, $compile, $http, $templateCache){
44+
45+
return {
46+
restrict: 'AE',
47+
replace: true,
48+
transclude: false,
49+
scope: true,
50+
link: function(scope,element,attrs) {
51+
//rebind our part of the form to the scope.
52+
var once = scope.$watch(attrs.form,function(form){
53+
scope.form = form;
54+
55+
//ok let's replace that template!
56+
//We do this manually since we need to bind ng-model properly and also
57+
//for fieldsets to recurse properly.
58+
var url = templateUrl(name,form);
59+
$http.get(url,{ cache: $templateCache }).then(function(res){
60+
var template = res.data.replace(/\$\$value\$\$/g,'model.'+form.key);
61+
$compile(template)(scope,function(clone){
62+
element.replaceWith(clone);
63+
});
64+
});
65+
once();
66+
});
67+
68+
//Keep error prone logic from the template
69+
scope.showTitle = function() {
70+
return scope.form && scope.form.notitle !== true && scope.form.title;
71+
};
72+
73+
scope.checkboxValuesToList = function(values){
74+
var lst = [];
75+
angular.forEach(values,function(v,k){
76+
if (v) {
77+
lst.push(k);
78+
}
79+
});
80+
return lst;
81+
};
82+
}
83+
};
84+
}]);
85+
};
86+
87+
/**
88+
* Getter for directive mappings
89+
* Can be used to override a mapping or add a rule
90+
* @param {string} name
91+
* @return {Object} rules and mappings { rules: [],mappings: {}}
92+
*/
93+
this.directive = function(name) {
94+
return directives[name];
95+
};
96+
97+
98+
//Service is just a getter for directive mappings and rules
99+
this.$get = function(){
100+
return function(name) {
101+
return directives[name];
102+
};
103+
};
104+
105+
}]);

test/schema-form-test.js

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,6 @@ describe('Schema form',function(){
225225

226226
$compile(tmpl)(scope);
227227
$rootScope.$apply();
228-
229228
tmpl.children().length.should.be.equal(2);
230229
tmpl.children().eq(0).is('div.form-group').should.be.true;
231230
tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true;
@@ -582,39 +581,6 @@ describe('Schema form',function(){
582581
});
583582

584583

585-
describe('decorator directive',function(){
586-
it('should decorate',function(){
587-
588-
inject(function($compile,$rootScope){
589-
var scope = $rootScope.$new();
590-
scope.obj = {};
591-
592-
var tmpl = angular.element('<schema-form-decorator title="foobar"><input type="text"></schema-form-decorator>');
593-
594-
$compile(tmpl)(scope);
595-
$rootScope.$apply();
596-
597-
tmpl.is('div.decorator').should.be.true;
598-
tmpl.children().length.should.be.eq(3);
599-
tmpl.find('input').length.should.be.eq(1);
600-
tmpl.find('.description').hasClass('ng-hide').should.be.true;
601-
602-
tmpl = angular.element('<schema-form-decorator description="Hell yea!"><input type="text"></schema-form-decorator>');
603-
604-
$compile(tmpl)(scope);
605-
$rootScope.$apply();
606-
607-
tmpl.is('div.decorator').should.be.true;
608-
tmpl.children().length.should.be.eq(3);
609-
tmpl.find('input').length.should.be.eq(1);
610-
tmpl.find('.description').hasClass('ng-hide').should.be.false;
611-
tmpl.find('.description').text().should.be.equal('Hell yea!');
612-
613-
});
614-
});
615-
616-
});
617-
618584
describe('service',function(){
619585
it('should generate default form def from a schema',function(){
620586
inject(function(schemaForm){
@@ -803,21 +769,6 @@ describe('Schema form',function(){
803769
f.type = 'password';
804770
schemaForm.merge(schema,[{ key: 'name',title: 'Foobar',type: 'password'}]).should.be.deep.equal([f]);
805771

806-
807-
var form = [
808-
"name",
809-
{
810-
title: 'Choose',
811-
key: "gender",
812-
type: "select",
813-
titleMap: {
814-
"undefined": "undefined",
815-
"null": "null",
816-
"NaN": "NaN"
817-
}
818-
}
819-
];
820-
821772
});
822773
});
823774

0 commit comments

Comments
 (0)