Skip to content

Commit ce5534b

Browse files
committed
Moved builders and new array directive into repo
From the new bootstrap implementation.
1 parent 33ce3f4 commit ce5534b

File tree

3 files changed

+228
-11
lines changed

3 files changed

+228
-11
lines changed

src/directives/array.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/**
22
* Directive that handles the model arrays
3+
* DEPRECATED with the new builder use the sfNewArray instead.
34
*/
45
angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sfValidator', 'sfPath',
56
function(sfSelect, schemaForm, sfValidator, sfPath) {

src/directives/newArray.js

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* Directive that handles the model arrays
3+
*/
4+
angular.module('schemaForm').directive('sfNewArray', ['sfSelect', 'sfPath', function(sel, sfPath) {
5+
return {
6+
scope: false,
7+
link: function(scope, element, attrs) {
8+
scope.min = 0;
9+
10+
scope.modelArray = scope.$eval(attrs.sfNewArray);
11+
12+
// We need to have a ngModel to hook into validation. It doesn't really play well with
13+
// arrays though so we both need to trigger validation and onChange.
14+
// So we watch the value as well. But watching an array can be tricky. We wan't to know
15+
// when it changes so we can validate,
16+
var watchFn = function() {
17+
//scope.modelArray = modelArray;
18+
scope.modelArray = scope.$eval(attrs.sfNewArray);
19+
// validateField method is exported by schema-validate
20+
if (scope.validateField) {
21+
scope.validateField();
22+
}
23+
};
24+
25+
var onChangeFn = function() {
26+
if (scope.form && scope.form.onChange) {
27+
if (angular.isFunction(form.onChange)) {
28+
form.onChange(ctrl.$modelValue, form);
29+
} else {
30+
scope.evalExpr(form.onChange, {'modelValue': ctrl.$modelValue, form: form});
31+
}
32+
}
33+
};
34+
35+
// We need the form definition to make a decision on how we should listen.
36+
var once = scope.$watch('form', function(form) {
37+
if (!form) {
38+
return;
39+
}
40+
41+
// Always start with one empty form unless configured otherwise.
42+
// Special case: don't do it if form has a titleMap
43+
if (!form.titleMap && form.startEmpty !== true && (!scope.modelArray || scope.modelArray.length === 0)) {
44+
scope.appendToArray();
45+
}
46+
47+
// If we have "uniqueItems" set to true, we must deep watch for changes.
48+
if (scope.form && scope.form.schema && scope.form.schema.uniqueItems === true) {
49+
scope.$watch(attrs.sfNewArray, watchFn, true);
50+
51+
// We still need to trigger onChange though.
52+
scope.$watch([attrs.sfNewArray, attrs.sfNewArray + '.length'], onChangeFn);
53+
54+
} else {
55+
// Otherwise we like to check if the instance of the array has changed, or if something
56+
// has been added/removed.
57+
if (scope.$watchGroup) {
58+
scope.$watchGroup([attrs.sfNewArray, attrs.sfNewArray + '.length'], function() {
59+
watchFn();
60+
onChangeFn();
61+
});
62+
} else {
63+
// Angular 1.2 support
64+
scope.$watch(attrs.sfNewArray, function() {
65+
watchFn();
66+
onChangeFn();
67+
});
68+
scope.$watch(attrs.sfNewArray + '.length', function() {
69+
watchFn();
70+
onChangeFn();
71+
});
72+
}
73+
}
74+
75+
// Title Map handling
76+
// If form has a titleMap configured we'd like to enable looping over
77+
// titleMap instead of modelArray, this is used for intance in
78+
// checkboxes. So instead of variable number of things we like to create
79+
// a array value from a subset of values in the titleMap.
80+
// The problem here is that ng-model on a checkbox doesn't really map to
81+
// a list of values. This is here to fix that.
82+
if (form.titleMap && form.titleMap.length > 0) {
83+
scope.titleMapValues = [];
84+
85+
// We watch the model for changes and the titleMapValues to reflect
86+
// the modelArray
87+
var updateTitleMapValues = function(arr) {
88+
scope.titleMapValues = [];
89+
arr = arr || [];
90+
91+
form.titleMap.forEach(function(item) {
92+
scope.titleMapValues.push(arr.indexOf(item.value) !== -1);
93+
});
94+
};
95+
//Catch default values
96+
updateTitleMapValues(scope.modelArray);
97+
98+
// TODO: Refactor and see if we can get rid of this watch by piggy backing on the
99+
// validation watch.
100+
scope.$watchCollection('modelArray', updateTitleMapValues);
101+
102+
//To get two way binding we also watch our titleMapValues
103+
scope.$watchCollection('titleMapValues', function(vals, old) {
104+
if (vals && vals !== old) {
105+
var arr = scope.modelArray;
106+
107+
// Apparently the fastest way to clear an array, readable too.
108+
// http://jsperf.com/array-destroy/32
109+
while (arr.length > 0) {
110+
arr.pop();
111+
}
112+
form.titleMap.forEach(function(item, index) {
113+
if (vals[index]) {
114+
arr.push(item.value);
115+
}
116+
});
117+
118+
// Time to validate the rebuilt array.
119+
// validateField method is exported by schema-validate
120+
if (scope.validateField) {
121+
scope.validateField();
122+
}
123+
}
124+
});
125+
}
126+
127+
once();
128+
});
129+
130+
scope.appendToArray = function() {
131+
132+
var empty;
133+
134+
// Same old add empty things to the array hack :(
135+
if (scope.form && scope.form.schema) {
136+
if (scope.form.schema.items) {
137+
if (scope.form.schema.items.type === 'object') {
138+
empty = {};
139+
} else if (scope.form.schema.items.type === 'array') {
140+
empty = [];
141+
}
142+
}
143+
}
144+
145+
var model = scope.modelArray;
146+
if (!model) {
147+
// Create and set an array if needed.
148+
var selection = sfPath.parse(attrs.sfNewArray);
149+
model = [];
150+
sel(selection, scope, model);
151+
scope.modelArray = model;
152+
}
153+
model.push(empty);
154+
155+
return model;
156+
};
157+
158+
scope.deleteFromArray = function(index) {
159+
var model = scope.modelArray;
160+
if (model) {
161+
model.splice(index, 1);
162+
}
163+
return model;
164+
};
165+
}
166+
};
167+
}]);

src/services/builder.js

+60-11
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,55 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
100100
}
101101
}
102102
}
103+
},
104+
condition: function(args) {
105+
// Do we have a condition? Then we slap on an ng-if on all children,
106+
// but be nice to existing ng-if.
107+
if (args.form.condition) {
108+
var evalExpr = 'evalExpr(' + args.path +
109+
'.contidion, { model: model, "arrayIndex": $index})';
110+
if (args.form.key) {
111+
var strKey = sfPathProvider.stringify(args.form.key);
112+
evalExpr = 'evalExpr(' + args.path + '.condition,{ model: model, "arrayIndex": $index, ' +
113+
'"modelValue": model' + (strKey[0] === '[' ? '' : '.') + strKey + '})';
114+
}
115+
116+
var children = args.fieldFrag.children;
117+
for (var i = 0; i < children.length; i++) {
118+
var child = children[i];
119+
var ngIf = child.getAttribute('ng-if');
120+
child.setAttribute(
121+
'ng-if',
122+
ngIf ?
123+
'(' + ngIf +
124+
') || (' + evalExpr + ')'
125+
: evalExpr
126+
);
127+
}
128+
}
129+
},
130+
array: function(args) {
131+
var items = args.fieldFrag.querySelector('[schema-form-array-items]');
132+
if (items) {
133+
state = angular.copy(args.state);
134+
state.keyRedaction = state.keyRedaction || 0;
135+
state.keyRedaction += args.form.key.length + 1;
136+
137+
// Special case, an array with just one item in it that is not an object.
138+
// So then we just override the modelValue
139+
if (args.form.schema && args.form.schema.items &&
140+
args.form.schema.items.type &&
141+
args.form.schema.items.type.indexOf('object') === -1 &&
142+
args.form.schema.items.type.indexOf('array') === -1) {
143+
var strKey = sfPathProvider.stringify(args.form.key).replace(/"/g, '&quot;') + '[$index]';
144+
state.modelValue = 'modelArray[$index]';
145+
} else {
146+
state.modelName = 'item';
147+
}
148+
149+
var childFrag = args.build(args.form.items, args.path + '.items', state);
150+
items.appendChild(childFrag);
151+
}
103152
}
104153
};
105154
this.builders = builders;
@@ -188,17 +237,17 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
188237
};
189238

190239
return {
191-
/**
192-
* Builds a form from a canonical form definition
193-
*/
194-
build: function(form, decorator, slots, lookup) {
195-
return build(form, decorator, function(url) {
196-
return $templateCache.get(url);
197-
}, slots, undefined, undefined, lookup);
198-
199-
},
200-
builder: builders,
201-
internalBuild: build
240+
/**
241+
* Builds a form from a canonical form definition
242+
*/
243+
build: function(form, decorator, slots, lookup) {
244+
return build(form, decorator, function(url) {
245+
return $templateCache.get(url);
246+
}, slots, undefined, undefined, lookup);
247+
248+
},
249+
builder: builders,
250+
internalBuild: build
202251
};
203252
}];
204253

0 commit comments

Comments
 (0)