Skip to content

Commit 769032b

Browse files
committed
Reworked list to array
...to make it more JSON Form compatible. Work in progress, especially no love what so ever has been given to the UI part. Also no reording (yet) and no tabarray support.
1 parent c26b82e commit 769032b

File tree

12 files changed

+1372
-160
lines changed

12 files changed

+1372
-160
lines changed

dist/bootstrap-decorator.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.

dist/schema-form.min.js

Lines changed: 1074 additions & 1 deletion
Large diffs are not rendered by default.

examples/bootstrap-example.html

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,53 @@ <h3>Schema</h3>
6262
angular.module('test',['schemaForm','ui.ace']);
6363

6464
function TestCtrl($scope){
65-
$scope.person = { favorite: 'NaN' };
65+
$scope.person = {
66+
favorite: 'NaN',
67+
arraytest2: ["foo","bar","foo"]
68+
};
6669

6770
$scope.schema = {
6871
"type": "object",
6972
"required": ['name','shoesize'],
7073
"properties": {
74+
"arraytest": {
75+
"title": "arraytest",
76+
"type": "array",
77+
"items": {
78+
"type": "object",
79+
"title": "Names",
80+
"properties": {
81+
"name": { "type": "string", "title": "Your name" },
82+
"nick": { "type": "string", "title": "Your nick", "default": "Yo!" },
83+
}
84+
}
85+
},
86+
"arraytest2": {
87+
"title": "arraytest 2",
88+
"type": "array",
89+
"items": {
90+
"type": "string",
91+
"title": "Name"
92+
}
93+
},
94+
"arraytest3": {
95+
"title": "arraytest 3",
96+
"type": "array",
97+
"items": {
98+
"type": "object",
99+
"title": "Cool list",
100+
"properties": {
101+
"name": { "title": "sublist name", "type": "string" },
102+
"sublist": {
103+
"type": "array",
104+
"items": {
105+
"title": "sublist value", "type": "string"
106+
}
107+
}
108+
}
109+
}
110+
},
111+
71112
"name": {
72113
"title": "Name",
73114
"description": "Gimme yea name lad",
@@ -143,6 +184,16 @@ <h3>Schema</h3>
143184
};
144185

145186
$scope.form = [
187+
{
188+
key: "arraytest",
189+
type: "array",
190+
items: [
191+
"arraytest[].nick",
192+
"arraytest[].name"
193+
]
194+
},
195+
"arraytest2",
196+
"arraytest3",
146197
{
147198
type: "fieldset",
148199
title: "Stuff",

src/directives/array.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Directive that handles the model arrays
3+
*/
4+
angular.module('schemaForm').directive('sfArray', ['sfSelect','schemaForm',
5+
function(sfSelect, schemaForm) {
6+
7+
var setIndex = function(index) {
8+
return function(form) {
9+
if (form.key) {
10+
form.key = form.key.replace('[]','['+index+']');
11+
}
12+
};
13+
};
14+
15+
return {
16+
restrict: 'A',
17+
scope: true,
18+
link: function(scope, element, attrs) {
19+
var formDefCache = {};
20+
21+
// Watch for the form definition and then rewrite it.
22+
// It's the (first) array part of the key, '[]' that needs a number
23+
// corresponding to an index of the form.
24+
var once = scope.$watch(attrs.sfArray, function(form) {
25+
26+
// An array model always needs a key so we know what part of the model
27+
// to look at. This makes us a bit incompatible with JSON Form, on the
28+
// other hand it enables two way binding.
29+
var list = sfSelect(form.key,scope.model);
30+
31+
// Since ng-model happily creates objects in a deep path when setting a
32+
// a value but not arrays we need to create the array.
33+
if (angular.isUndefined(list)) {
34+
list = [];
35+
sfSelect(form.key, scope.model, list);
36+
}
37+
scope.modelArray = list;
38+
39+
// To be more compatible with JSON Form we support an array of items
40+
// in the form definition of "array" (the schema just a value).
41+
// for the subforms code to work this means we wrap everything in a
42+
// section. Unless there is just one.
43+
var subForm = form.items[0];
44+
if (form.items.length > 1) {
45+
subForm = { type: 'section', items: form.items };
46+
}
47+
48+
// We ceate copies of the form on demand, caching them for
49+
// later requests
50+
scope.copyWithIndex = function(index) {
51+
if (!formDefCache[index]) {
52+
if (subForm) {
53+
var copy = angular.copy(subForm);
54+
copy.arrayIndex= index;
55+
schemaForm.traverseForm(copy, setIndex(index));
56+
formDefCache[index] = copy;
57+
}
58+
}
59+
return formDefCache[index];
60+
};
61+
62+
63+
scope.appendToArray = function() {
64+
var len = list.length;
65+
var copy = scope.copyWithIndex(len);
66+
schemaForm.traverseForm(copy, function(part){
67+
if (part.key && angular.isDefined(part.default)) {
68+
sfSelect(part.key, scope.model, part.default);
69+
}
70+
});
71+
72+
// If there are no defaults nothing is added so we need to initialize
73+
// the array. null for basic values, {} or [] for the others.
74+
if (len === list.length) {
75+
var type = sfSelect('schema.items.type',form);
76+
var dflt = null;
77+
if (type === 'object') {
78+
dflt = {};
79+
} else if (type === 'array') {
80+
dflt = [];
81+
}
82+
list.push(dflt);
83+
}
84+
};
85+
86+
scope.deleteFromArray = function(index) {
87+
list.splice(index,1);
88+
};
89+
90+
// Always start with one empty form unless configured otherwise.
91+
if (form.startEmpty !== true && list.length === 0) {
92+
scope.appendToArray();
93+
}
94+
95+
once();
96+
});
97+
}
98+
};
99+
}]);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div sf-array="form">
2+
<h3 ng-show="form.title && form.notitle !== true">{{ form.title }}</h3>
3+
<ul>
4+
<li ng-repeat="item in modelArray track by $index">
5+
<sf-decorator form="copyWithIndex($index)"></sf-decorator>
6+
<button ng-click="deleteFromArray($index)" type="button" class="btn btn-default">-</button>
7+
</li>
8+
</ol>
9+
<button ng-click="appendToArray()" type="button" class="btn btn-default">+</button>
10+
</div>

src/directives/decorators/bootstrap/bootstrap-decorator.js

Lines changed: 1 addition & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(dec
44
decoratorsProvider.createDecorator('bootstrapDecorator',{
55
textarea: base+'textarea.html',
66
fieldset: base+'fieldset.html',
7-
list: base+'list.html',
7+
array: base+'array.html',
88
tabs: base+'tabs.html',
99
section: base+'section.html',
1010
conditional: base+'section.html',
@@ -55,125 +55,4 @@ angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(dec
5555
scope.title = scope.$eval(attrs.title);
5656
}
5757
};
58-
}).directive('sfList',function(){
59-
return {
60-
templateUrl: 'directives/decorators/bootstrap/list-body.html',
61-
62-
link: function(scope,element,attrs) {
63-
var key = scope.form.items.key;
64-
var value = getNested(scope.model, key) || [];
65-
setNested(scope.model, key, value);
66-
67-
scope.value = value;
68-
scope.renderedValue = angular.copy(scope.value);
69-
70-
var storedValue = null;
71-
scope.$on('change', function(evt, val){
72-
var index = findElement(element[0], val.element[0]);
73-
74-
if (index == -1){
75-
// The add input
76-
storedValue = val.value;
77-
} else {
78-
scope.value[index] = val.value;
79-
setNested(scope.model, key, scope.value);
80-
}
81-
});
82-
83-
scope.$on('remove', function(evt, val){
84-
var index = findElement(element[0], val.element[0]);
85-
86-
scope.renderedValue.splice(index, 1);
87-
88-
scope.value.splice(index, 1);
89-
setNested(scope.model, key, scope.value);
90-
val.element[0].parentNode.removeChild(val.element[0]);
91-
});
92-
93-
scope.add = function(){
94-
scope.renderedValue.push(storedValue);
95-
96-
scope.value.push(storedValue);
97-
setNested(scope.model, key, scope.value);
98-
99-
scope.$broadcast('add');
100-
};
101-
}
102-
}
103-
}).directive('sfListItem',function(){
104-
return {
105-
scope: {
106-
'form': '=',
107-
'value': '=',
108-
'newEntry': '@'
109-
},
110-
111-
templateUrl: 'directives/decorators/bootstrap/list-item.html',
112-
113-
link: function(scope,element,attrs) {
114-
var initialValue = angular.copy(scope.value);
115-
116-
scope.form.items.key = 'value';
117-
scope.model = {
118-
value: scope.value
119-
};
120-
121-
scope.$watch('model', function(val){
122-
scope.$emit('change', {
123-
value: val.value,
124-
element: element
125-
});
126-
}, true);
127-
128-
129-
scope.$on('add', function(){
130-
if (scope.newEntry){
131-
scope.model.value = initialValue;
132-
}
133-
});
134-
135-
scope.remove = function() {
136-
scope.$emit('remove', {
137-
element: element
138-
});
139-
};
140-
}
141-
}
14258
});
143-
144-
function findElement(parentEl, element){
145-
var els = parentEl.querySelectorAll('[sf-list-item]');
146-
for (var i=0; i < els.length; i++){
147-
if (els[i].isEqualNode(element)){
148-
if (els[i].tagName === 'DIV'){
149-
return -1;
150-
} else {
151-
return i;
152-
}
153-
}
154-
}
155-
}
156-
157-
function setNested(obj, key, value){
158-
var bits = key.split('.');
159-
for (var i=0; i < bits.length - 1; i++){
160-
if (!obj[bits[i]])
161-
obj[bits[i]] = {};
162-
163-
obj = obj[bits[i]];
164-
}
165-
166-
obj[bits[bits.length - 1]] = value;
167-
}
168-
169-
function getNested(obj, key){
170-
var bits = key.split('.');
171-
for (var i=0; i < bits.length; i++){
172-
obj = obj[bits[i]];
173-
174-
if (!obj){
175-
return undefined;
176-
}
177-
}
178-
return obj;
179-
}

src/directives/decorators/bootstrap/list-body.html

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

src/directives/decorators/bootstrap/list-item.html

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

src/directives/decorators/bootstrap/list.html

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

src/directives/schema-form.js

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,8 @@ FIXME: real documentation
55

66
angular.module('schemaForm')
77
.directive('sfSchema',
8-
['$compile','schemaForm','schemaFormDecorators',
9-
function($compile, schemaForm, schemaFormDecorators){
10-
11-
//recurse through the entire schema.
12-
//FIXME: no support for arrays
13-
var traverse = function(schema,fn,path) {
14-
path = path || "";
15-
fn(schema,path);
16-
angular.forEach(schema.properties,function(prop,name){
17-
traverse(prop,fn,path===""?name:path+'.'+name);
18-
});
19-
};
8+
['$compile','schemaForm','schemaFormDecorators','sfSelect',
9+
function($compile, schemaForm, schemaFormDecorators, sfSelect){
2010

2111
var SNAKE_CASE_REGEXP = /[A-Z]/g;
2212
function snake_case(name, separator){
@@ -84,6 +74,7 @@ function($compile, schemaForm, schemaFormDecorators){
8474
//FIXME: traverse schema and model and set default values.
8575

8676
var merged = schemaForm.merge(schema,form,ignore);
77+
console.log('merged',merged)
8778
var frag = document.createDocumentFragment();
8879

8980
//make the form available to decorators
@@ -105,10 +96,13 @@ function($compile, schemaForm, schemaFormDecorators){
10596
$compile(element.children())(scope);
10697

10798
//ok, now that that is done let's set any defaults
108-
traverse(schema,function(prop,path){
109-
//This is probably not so fast, but a simple solution.
99+
schemaForm.traverseSchema(schema,function(prop,path){
100+
110101
if (angular.isDefined(prop['default'])) {
111-
scope.$eval('model.'+path+' = model.'+path+' || defaultValue',{ defaultValue: prop['default']});
102+
var val = sfSelect(path, scope.model);
103+
if (angular.isUndefined(val)) {
104+
sfSelect(path, scope.model, prop['default']);
105+
}
112106
}
113107
});
114108

0 commit comments

Comments
 (0)