Skip to content

Commit ffa4626

Browse files
authored
Merge pull request #186 from AtlasOfLivingAustralia/feature/issue177
Added support for key/value typed constraints #177
2 parents 327d1c4 + c761430 commit ffa4626

File tree

3 files changed

+59
-36
lines changed

3 files changed

+59
-36
lines changed

grails-app/assets/javascripts/forms.js

+46-28
Original file line numberDiff line numberDiff line change
@@ -666,26 +666,25 @@ function orEmptyArray(v) {
666666
};
667667

668668
};
669-
670-
function applyIncludeExclude(metadata, outputModel, observable, initialConstraints) {
669+
function applyIncludeExclude(metadata, outputModel, dataModelItem, initialConstraints) {
671670
var path = metadata.constraints.excludePath || metadata.constraints.includePath;
672671

673-
var currentValue = observable();
674-
var selectedSoFar = [];
675-
outputModel.eachValueForPath(path, function (val) {
676-
if (_.isArray(val)) {
677-
selectedSoFar = selectedSoFar.concat(val);
678-
} else if (val != null) {
679-
selectedSoFar.push(val);
680-
}
681-
});
672+
var currentValue = dataModelItem();
673+
var selectedSoFar = outputModel.getModelValuesForPath(path);
682674

683675
if (metadata.constraints.excludePath) {
684-
return _.filter(initialConstraints, function (value) {
676+
var filteredConstraints = _.filter(initialConstraints, function (value) {
677+
// Handle object valued constraints - however the functions are attached after the first call to
678+
// the computed observable, so we need to guard against that.
679+
value = _.isFunction(dataModelItem.constraintValue) ? dataModelItem.constraintValue(value) : value;
685680
var isCurrentSelection = _.isArray(currentValue) ? currentValue.indexOf(value) >= 0 : value == currentValue;
686-
return (isCurrentSelection || selectedSoFar.indexOf(value) < 0);
681+
var include = (isCurrentSelection || selectedSoFar.indexOf(value) < 0);
682+
return include;
687683
});
684+
return filteredConstraints;
688685
} else {
686+
// Note that the "includePath" option does not support object (key/value) typed constraints as
687+
// it needs to collect values from other form selections which won't be object typed.
689688
var constraints = initialConstraints.concat(selectedSoFar);
690689
var currentSelection = _.isArray(currentValue) ? currentValue : [currentValue];
691690
for (var i = 0; i < currentSelection.length; i++) {
@@ -697,9 +696,7 @@ function orEmptyArray(v) {
697696

698697
return constraints;
699698
}
700-
701699
}
702-
703700
/**
704701
* Implements the constraints specified on a single data model item using the "constraints" attribute in the metadata.
705702
* Also provides access to global configuration and context for components that need it.
@@ -775,7 +772,20 @@ function orEmptyArray(v) {
775772
valueProperty = metadata.constraints.valueProperty || valueProperty;
776773
textProperty = metadata.constraints.textProperty || textProperty;
777774
}
778-
775+
// These need to be defined before the computed constraint is defined to support correct evaluation
776+
// of the excludePath function.
777+
self.constraintValue = function (constraint) {
778+
if (_.isObject(constraint)) {
779+
return constraint[valueProperty];
780+
}
781+
return constraint;
782+
};
783+
self.constraintText = function(constraint) {
784+
if (_.isObject(constraint)) {
785+
return constraint[textProperty];
786+
}
787+
return constraint;
788+
};
779789
self.constraints = [];
780790
var includeExcludeDefined = metadata.constraints.excludePath || metadata.constraints.includePath;
781791
// Support existing configuration style.
@@ -830,18 +840,8 @@ function orEmptyArray(v) {
830840
}
831841
}
832842

833-
self.constraints.value = function (constraint) {
834-
if (_.isObject(constraint)) {
835-
return constraint[valueProperty];
836-
}
837-
return constraint;
838-
};
839-
self.constraints.text = function (constraint) {
840-
if (_.isObject(constraint)) {
841-
return constraint[textProperty];
842-
}
843-
return constraint;
844-
};
843+
self.constraints.value = self.constraintValue;
844+
self.constraints.text = self.constraintText;
845845
self.constraints.label = function(value) {
846846

847847
if (!value && ko.isObservable(self)) {
@@ -1267,6 +1267,24 @@ function orEmptyArray(v) {
12671267
self.iterateOverPath(pathAsArray, callback, self.data);
12681268
}
12691269

1270+
/**
1271+
* Iterates over every instance of the DataModelItem defined by path and returns an array containing
1272+
* the values found at each item.
1273+
* @param path the path to the data model item of interest (e.g. list.nestedList.textValue1)
1274+
* @returns {*[]}
1275+
*/
1276+
self.getModelValuesForPath = function(path) {
1277+
var values = [];
1278+
self.eachValueForPath(path, function (val) {
1279+
if (_.isArray(val)) {
1280+
values = values.concat(val);
1281+
} else if (val != null) {
1282+
values.push(val);
1283+
}
1284+
});
1285+
return values;
1286+
}
1287+
12701288
/**
12711289
* Recursively iterates over each element in the supplied pathAsArray, taking into
12721290
* account when values are lists.

grails-app/controllers/au/org/ala/ecodata/forms/PreviewController.groovy

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import org.grails.io.support.Resource
88

99
class PreviewController {
1010

11+
static responseFormats = ['json', 'xml']
12+
1113
private static String EXAMPLE_MODEL = 'example.json'
1214
private static String EXAMPLE_MODELS_PATH = '/example_models/'
1315
private static String EXAMPLE_DATA_PATH = '/example_data/'

src/test/js/spec/DataModelItemSpec.js

+11-8
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,9 @@ describe("DataModelItem Spec", function () {
148148

149149
var invokedWithPath;
150150
var vals = ['1', '3'];
151-
var customContext = _.extend({}, context, {outputModel: { eachValueForPath: function(path, callback) {
151+
var customContext = _.extend({}, context, {outputModel: { getModelValuesForPath: function(path) {
152152
invokedWithPath = path;
153-
for (var i=0; i<vals.length; i++) {
154-
callback(vals[i]);
155-
}
153+
return _.flatten(vals);
156154
}}});
157155

158156
var dataItem = ko.observable().extend({metadata:{metadata:metadata, context:customContext, config:config}});
@@ -165,6 +163,12 @@ describe("DataModelItem Spec", function () {
165163
vals = [['1', '2'], ['3']];
166164
dataItem = ko.observableArray().extend({metadata:{metadata:metadata, context:customContext, config:config}});
167165
expect(dataItem.constraints()).toEqual(['4']);
166+
167+
168+
vals = ['v1'];
169+
metadata.constraints.default = [{text:'t1', id:'v1'}, {text:'t2', id:'v2'}];
170+
dataItem = ko.observableArray().extend({metadata:{metadata:metadata, context:customContext, config:config}});
171+
expect(dataItem.constraints()).toEqual([{text:'t2', id:'v2'}]);
168172
});
169173

170174

@@ -182,11 +186,10 @@ describe("DataModelItem Spec", function () {
182186

183187
var invokedWithPath;
184188
var vals = ['1', '3'];
185-
var customContext = _.extend({}, context, {outputModel: { eachValueForPath: function(path, callback) {
189+
var customContext = _.extend({}, context, {outputModel: { getModelValuesForPath: function(path) {
186190
invokedWithPath = path;
187-
for (var i=0; i<vals.length; i++) {
188-
callback(vals[i]);
189-
}
191+
console.log(_.flatten(vals));
192+
return _.flatten(vals);
190193
}}});
191194

192195
var dataItem = ko.observable().extend({metadata:{metadata:metadata, context:customContext, config:config}});

0 commit comments

Comments
 (0)