Skip to content

Commit d9137c6

Browse files
authored
Merge pull request #178 from AtlasOfLivingAustralia/feature/issue177
Feature/issue177
2 parents e3648eb + 36326b4 commit d9137c6

File tree

10 files changed

+566
-49
lines changed

10 files changed

+566
-49
lines changed

grails-app/assets/javascripts/forms.js

+92-8
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,6 @@ function orEmptyArray(v) {
243243
};
244244
}();
245245

246-
247-
248246
function arrayFunction(array, expression, reducer, memo) {
249247
var parsedExpression = exprEval.Parser.parse(expression);
250248
var variables = parsedExpression.variables();
@@ -751,12 +749,54 @@ function orEmptyArray(v) {
751749
}
752750
else if (_.isObject(metadata.constraints)) {
753751
if (metadata.constraints.type == 'computed') {
754-
self.constraints = ko.computed(function () {
755-
var rule = _.find(metadata.constraints.options, function (option) {
756-
return ecodata.forms.expressionEvaluator.evaluateBoolean(option.condition, context);
752+
if (_.isArray(metadata.constraints.options)) {
753+
self.constraints = ko.computed(function () {
754+
var rule = _.find(metadata.constraints.options, function (option) {
755+
return ecodata.forms.expressionEvaluator.evaluateBoolean(option.condition, context);
756+
});
757+
return rule ? rule.value : metadata.constraints.default;
757758
});
758-
return rule ? rule.value : metadata.constraints.default;
759-
});
759+
}
760+
// This configuration takes a set of default constraints then either
761+
// adds values based on selections from other model items in the form or
762+
// removes selections based on other model items in the form. The main use
763+
// case this was developed for was to only allow each contraint to be selected once
764+
// in a form.
765+
else if (metadata.constraints.excludePath || metadata.constraints.includePath) {
766+
var defaultConstraints = metadata.constraints.default || [];
767+
var path = metadata.constraints.excludePath || metadata.constraints.includePath;
768+
self.constraints = ko.computed(function() {
769+
var currentValue = self();
770+
var selectedSoFar = [];
771+
context.outputModel.eachValueForPath(path, function(val) {
772+
if (_.isArray(val)) {
773+
selectedSoFar = selectedSoFar.concat(val);
774+
}
775+
else if (val != null) {
776+
selectedSoFar.push(val);
777+
}
778+
});
779+
780+
if (metadata.constraints.excludePath) {
781+
return _.filter(defaultConstraints, function (value) {
782+
var isCurrentSelection = _.isArray(currentValue) ? currentValue.indexOf(value) >= 0 : value == currentValue;
783+
return (isCurrentSelection || selectedSoFar.indexOf(value) < 0);
784+
});
785+
}
786+
else {
787+
var constraints = defaultConstraints.concat(selectedSoFar);
788+
var currentSelection = _.isArray(currentValue) ? currentValue : [currentValue];
789+
for (var i=0; i<currentSelection.length; i++) {
790+
791+
if (currentSelection[i] != null && constraints.indexOf(currentSelection[i]) < 0) {
792+
constraints.push(currentSelection[i]);
793+
}
794+
}
795+
796+
return constraints;
797+
}
798+
});
799+
}
760800
}
761801
else if (metadata.constraints.type == 'pre-populated') {
762802
var defaultConstraints = metadata.constraints.defaults || [];
@@ -769,7 +809,7 @@ function orEmptyArray(v) {
769809
constraintsInititaliser.resolve();
770810
});
771811
}
772-
else if (metadata.constraints.type == 'literal' || metadata.contraints.literal) {
812+
else if (metadata.constraints.type == 'literal' || metadata.constraints.literal) {
773813
self.constraints = [].concat(metadata.constraints.literal);
774814
}
775815
}
@@ -1197,6 +1237,50 @@ function orEmptyArray(v) {
11971237
return deferred;
11981238
};
11991239

1240+
/**
1241+
* Invokes the callback function for each value of the specified path in this model.
1242+
* The path must treat array/list valued items as a simple property name (without
1243+
* an index) - each array in the path will be iterated over so the callback may be
1244+
* invoked more than once.
1245+
* @param path a string specifying the path to the item of interest e.g. 'list1.list2.property'
1246+
* @param callback a function taking a single argument which will be the value of the property
1247+
* specified by path.
1248+
*/
1249+
self.eachValueForPath = function(path, callback) {
1250+
var pathAsArray = path.split('.');
1251+
self.iterateOverPath(pathAsArray, callback, self.data);
1252+
}
1253+
1254+
/**
1255+
* Recursively iterates over each element in the supplied pathAsArray, taking into
1256+
* account when values are lists.
1257+
* @param pathAsArray
1258+
* @param callback
1259+
* @param dataModel
1260+
*/
1261+
self.iterateOverPath = function(pathAsArray, callback, dataModel) {
1262+
var modelItem = ko.utils.unwrapObservable(dataModel[pathAsArray[0]]);
1263+
1264+
function nextPath(model) {
1265+
var nextPath = pathAsArray.slice(1);
1266+
self.iterateOverPath(nextPath, callback, model);
1267+
}
1268+
// If the current model is an array, iterate over all elements in the array.
1269+
if (_.isArray(modelItem)) {
1270+
for (var i=0; i<modelItem.length; i++) {
1271+
nextPath(modelItem[i]);
1272+
}
1273+
}
1274+
// Otherwise, just move deeper into the object hierarchy
1275+
else if (pathAsArray.length > 1) {
1276+
nextPath(modelItem);
1277+
}
1278+
// If we are at the final element of the path, we have reached the value we are after, so
1279+
// invoke the callback with this value.
1280+
else if (pathAsArray.length == 1) {
1281+
callback(modelItem);
1282+
}
1283+
}
12001284

12011285
self.attachDocument = function (target) {
12021286
var url = config.documentUpdateUrl || fcConfig.documentUpdateUrl;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
{
2+
"modelName": "Constraints example",
3+
"dataModel": [
4+
{
5+
"dataType": "number",
6+
"name": "number1"
7+
},
8+
{
9+
"dataType": "text",
10+
"name": "notes"
11+
},
12+
{
13+
"dataType": "list",
14+
"name": "list",
15+
"columns": [
16+
{
17+
"name": "value1",
18+
"dataType": "text"
19+
},
20+
{
21+
"name": "nestedList",
22+
"dataType": "list",
23+
"columns": [
24+
{
25+
"name": "value2",
26+
"dataType": "text",
27+
"constraints": {
28+
"type": "computed",
29+
"excludePath": "list.nestedList.value2",
30+
"default": [
31+
"c1",
32+
"c2",
33+
"c3",
34+
"c4"
35+
]
36+
}
37+
},
38+
{
39+
"name": "value3",
40+
"dataType": "stringList",
41+
"constraints": {
42+
"type": "computed",
43+
"includePath": "list.nestedList.value2",
44+
"default": [
45+
"Default value"
46+
]
47+
}
48+
}
49+
]
50+
}
51+
]
52+
}
53+
],
54+
"viewModel": [
55+
{
56+
"type": "row",
57+
"items": [
58+
{
59+
"type": "literal",
60+
"source": "<p>This example illustrates the use of computed constraints</p><p>The 'Value 2' field will only allow each item in the dropdown to be selected once, no matter how many times 'Value 2' appears on the page.</p><p>For each selection made in a 'Value 2' field, that value will be added as a selectable option in the 'Value 3' field.</p>"
61+
}
62+
]
63+
},
64+
{
65+
"type": "row",
66+
"items": [
67+
{
68+
"type": "number",
69+
"source": "number1",
70+
"preLabel": "number1"
71+
}
72+
]
73+
},
74+
{
75+
"type": "row",
76+
"items": [
77+
{
78+
"type": "textarea",
79+
"source": "notes",
80+
"preLabel": "notes"
81+
}
82+
]
83+
},
84+
{
85+
"type": "repeat",
86+
"source": "list",
87+
"userAddedRows": true,
88+
"items": [
89+
{
90+
"type": "row",
91+
"items": [
92+
{
93+
"preLabel": "Value1",
94+
"type": "selectOne",
95+
"source": "value1"
96+
}
97+
]
98+
},
99+
{
100+
"type": "table",
101+
"source": "nestedList",
102+
"userAddedRows": true,
103+
"columns": [
104+
{
105+
"type": "selectOne",
106+
"title": "Value 2",
107+
"source": "value2"
108+
},
109+
{
110+
"type": "selectMany",
111+
"title": "Value 3",
112+
"source": "value3"
113+
}
114+
]
115+
}
116+
]
117+
}
118+
]
119+
}

karma.conf.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ module.exports = function (config) {
9797

9898
// Continuous Integration mode
9999
// if true, Karma captures browsers, runs the tests and exits
100-
singleRun: true
100+
singleRun: true,
101+
102+
listenAddress: '127.0.0.1'
101103
});
102104
};

0 commit comments

Comments
 (0)