diff --git a/grails-app/assets/javascripts/forms-knockout-bindings.js b/grails-app/assets/javascripts/forms-knockout-bindings.js
index 027d107..b27ffa2 100644
--- a/grails-app/assets/javascripts/forms-knockout-bindings.js
+++ b/grails-app/assets/javascripts/forms-knockout-bindings.js
@@ -1146,6 +1146,21 @@
ecodata.forms.OutputListSupport.apply(target, [options.metadata, options.constructorFunction, options.context, options.userAddedRows, options.config]);
};
+ /**
+ * The role of this extender is to provide a function the view model can use to render a list of
+ * values selected using a multi select component (select2Many / selectMany) that have also used a
+ * label/value configuration for the options.
+ * @param target the observable.
+ * @param options unused
+ */
+ ko.extenders.toReadOnlyString = function(target, options) {
+ target.toReadOnlyString = function() {
+ var values = ko.utils.unwrapObservable(target);
+ var labels = target.constraints && _.isFunction(target.constraints.label) ? _.map(values, target.constraints.label) : values;
+ return labels.join(', ');
+ }
+ }
+
/**
* This is kind of a hack to make the closure config object available to the any components that use the model.
*/
diff --git a/grails-app/assets/javascripts/forms.js b/grails-app/assets/javascripts/forms.js
index da771c3..1f9b4df 100644
--- a/grails-app/assets/javascripts/forms.js
+++ b/grails-app/assets/javascripts/forms.js
@@ -1050,7 +1050,7 @@ function orEmptyArray(v) {
if (match) {
return self.constraints.text(match);
}
- return '';
+ return value || '';
}
}
diff --git a/grails-app/taglib/au/org/ala/ecodata/forms/ModelJSTagLib.groovy b/grails-app/taglib/au/org/ala/ecodata/forms/ModelJSTagLib.groovy
index 52ea12a..bc31395 100644
--- a/grails-app/taglib/au/org/ala/ecodata/forms/ModelJSTagLib.groovy
+++ b/grails-app/taglib/au/org/ala/ecodata/forms/ModelJSTagLib.groovy
@@ -732,7 +732,8 @@ class ModelJSTagLib {
}
def stringListViewModel(JSModelRenderContext ctx) {
- observableArray(ctx)
+ String extender = '{toReadOnlyString:true}'
+ observableArray(ctx, [extender])
}
def setViewModel(JSModelRenderContext ctx) {
diff --git a/src/main/groovy/au/org/ala/ecodata/forms/ViewModelWidgetRenderer.groovy b/src/main/groovy/au/org/ala/ecodata/forms/ViewModelWidgetRenderer.groovy
index f8cfd00..2f7ac82 100644
--- a/src/main/groovy/au/org/ala/ecodata/forms/ViewModelWidgetRenderer.groovy
+++ b/src/main/groovy/au/org/ala/ecodata/forms/ViewModelWidgetRenderer.groovy
@@ -72,9 +72,23 @@ class ViewModelWidgetRenderer implements ModelWidgetRenderer {
context.writer << ""
}
+ /**
+ * The binding looks for the toReadOnlyString function because there exist some misconfigurations that
+ * use a "text" data model item with a selectMany/select2Many view. This is a workaround to prevent exceptions.
+ */
+ private static String selectManyBindingString(WidgetRenderContext context) {
+ '_.isFunction(('+context.source+' || []).toReadOnlyString) ? '+ context.source+'.toReadOnlyString() : ('+context.source+'() || []).join(", ")'
+ }
+
@Override
void renderSelectMany(WidgetRenderContext context) {
- context.databindAttrs.add 'text', '('+context.source+'() || []).join(", ")'
+ context.databindAttrs.add 'text',selectManyBindingString(context)
+ context.writer << ""
+ }
+
+ @Override
+ void renderSelect2Many(WidgetRenderContext context) {
+ context.databindAttrs.add 'text', selectManyBindingString(context)
context.writer << ""
}
@@ -195,12 +209,6 @@ class ViewModelWidgetRenderer implements ModelWidgetRenderer {
context.writer << """\$.00"""
}
- @Override
- void renderSelect2Many(WidgetRenderContext context) {
- context.databindAttrs.add 'text', '('+context.source+'() || []).join(", ")'
- context.writer << ""
- }
-
@Override
void renderMultiInput(WidgetRenderContext context) {
context.databindAttrs.add 'text', '('+context.source+'() || []).join(", ")'
diff --git a/src/test/js/spec/DataModelItemSpec.js b/src/test/js/spec/DataModelItemSpec.js
index e60f0c7..c479824 100644
--- a/src/test/js/spec/DataModelItemSpec.js
+++ b/src/test/js/spec/DataModelItemSpec.js
@@ -90,7 +90,7 @@ describe("DataModelItem Spec", function () {
dataItem('2');
expect(dataItem.constraints.label()).toEqual('2')
expect(dataItem.constraints.label('3')).toEqual('3')
- expect(dataItem.constraints.label("does not exist")).toEqual('');
+ expect(dataItem.constraints.label("does not exist")).toEqual('does not exist'); // Support for tags and historical constraints that have been removed.
var objectConstraints = [{text:'label 1', value:'1'}, {text:'label 2', value:'2'}, {text:'label 3', value:'3'}];
metadata.constraints = {
@@ -104,7 +104,7 @@ describe("DataModelItem Spec", function () {
dataItem('2');
expect(dataItem.constraints.label()).toEqual('label 2')
expect(dataItem.constraints.label('3')).toEqual('label 3')
- expect(dataItem.constraints.label("does not exist")).toEqual('');
+ expect(dataItem.constraints.label("does not exist")).toEqual('does not exist'); // Support for tags and historical constraints that have been removed.
metadata.constraints = {
type:"pre-populated",
@@ -128,7 +128,6 @@ describe("DataModelItem Spec", function () {
deferred.resolve(objectConstraints).then(function() {
expect(dataItem.constraints.label()).toEqual('label 2')
expect(dataItem.constraints.label('3')).toEqual('label 3')
- expect(dataItem.constraints.label("does not exist")).toEqual('');
done();
});