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(); });