diff --git a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy
index b518e4bc..5dd70440 100644
--- a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy
+++ b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy
@@ -18,6 +18,7 @@ package grails.plugin.formfields
import grails.core.GrailsApplication
import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
import groovy.xml.MarkupBuilder
import org.apache.commons.lang.StringUtils
import org.grails.buffer.FastStringWriter
@@ -44,6 +45,7 @@ import java.text.NumberFormat
import static FormFieldsTemplateService.toPropertyNameFormat
+@Slf4j
class FormFieldsTagLib {
static final namespace = 'f'
@@ -615,6 +617,9 @@ class FormFieldsTagLib {
def message = keysInPreferenceOrder.findResult { key ->
message(code: key, default: null) ?: null
}
+ if(log.traceEnabled && !message) {
+ log.trace("i18n missing translation for one of ${keysInPreferenceOrder}")
+ }
message ?: defaultMessage
}
diff --git a/src/docs/asciidoc/customizingFieldRendering.adoc b/src/docs/asciidoc/customizingFieldRendering.adoc
index 0feddca5..3a465041 100644
--- a/src/docs/asciidoc/customizingFieldRendering.adoc
+++ b/src/docs/asciidoc/customizingFieldRendering.adoc
@@ -279,7 +279,8 @@ NOTE: If the `bean` attribute was not supplied to `f:field` then `bean`, `type`,
If the `label` attribute is not supplied to the `f:field` tag then the label string passed to the field template is resolved by convention. The plugin uses the following order of preference for the label:
-* An i18n message using the key '_beanClass_._path_`.label`'. For example when using `` the plugin will try the i18n key `person.address.city.label`. If the property path contains any index it is removed so `` would use the key `author.books.title.label`.
+* An i18n message using the key '_beanClass_._path_.label'. For example when using `` the plugin will try the i18n key `author.book.title.label`. If the property path contains any index it is removed so `` would use the key `author.books.title.label`.
+* For classes using the same bean class as properties, it is possible to get a key without the class name prefixed. If the configuration value `grails.plugin.fields.i18n.addPathFromRoot` is set to `true` (default: `false`). _Example_: a class `Publisher` has two `Address` properties `authorAddress` and `printAddress`. With `addPathFromRoot=true` they will share the key `address.city.label`. The same goes if `Author` and `Publisher` had a `Book book`, the key would be `book.title.label`, and if they both had a `List books` the key would be `books.title.label`
* An i18n message using the key '_objectType_._propertyName_`.label`'. For example when using `` the plugin will try the i18n key `address.city.label`.
* The natural property name. For example when using `` the plugin will use the label `"Date Of Birth"`.
diff --git a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy
index a13ad163..ac0415e5 100644
--- a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy
+++ b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy
@@ -74,14 +74,18 @@ class BeanPropertyAccessorFactory implements GrailsApplicationAware {
DomainProperty domainProperty = resolvePropertyFromPathComponents(beanWrapper, pathElements, params)
if (domainProperty != null) {
- new DelegatingBeanPropertyAccessorImpl(bean, params.value, params.propertyType as Class, pathFromRoot, domainProperty)
+ new DelegatingBeanPropertyAccessorImpl(bean, params.value, params.propertyType as Class, pathFromRoot, domainProperty, addPathFromRoot)
} else {
new BeanPropertyAccessorImpl(params)
}
}
- private DomainProperty resolvePropertyFromPathComponents(BeanWrapper beanWrapper, List pathElements, Map params) {
+ private boolean getAddPathFromRoot() {
+ grailsApplication.config.getProperty('grails.plugin.fields.i18n.addPathFromRoot', Boolean)
+ }
+
+ private DomainProperty resolvePropertyFromPathComponents(BeanWrapper beanWrapper, List pathElements, Map params) {
String propertyName = pathElements.remove(0)
PersistentEntity beanClass = resolveDomainClass(beanWrapper.wrappedClass)
Class propertyType = resolvePropertyType(beanWrapper, beanClass, propertyName)
diff --git a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy
index 64938b16..003fc3e3 100644
--- a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy
+++ b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy
@@ -69,6 +69,10 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor {
grailsApplication.config.getProperty("grails.databinding.$paramName", Boolean, defaultParamValue)
}
+ private boolean getAddPathFromRoot() {
+ grailsApplication.config.getProperty('grails.plugin.fields.i18n.addPathFromRoot', Boolean, false)
+ }
+
List getBeanSuperclasses() {
getSuperclassesAndInterfaces(beanType)
}
@@ -78,10 +82,15 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor {
}
List getLabelKeys() {
- [
- "${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''),
- "${GrailsNameUtils.getPropertyName(beanType.simpleName)}.${propertyName}.label"
- ].unique() as List
+ List labelKeys = []
+
+ labelKeys << "${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, '')
+ if(addPathFromRoot) {
+ labelKeys << "${pathFromRoot}.label".replaceAll(/\[(.+)\]/, '')
+ }
+ labelKeys << "${GrailsNameUtils.getPropertyName(beanType.simpleName)}.${propertyName}.label".toString()
+
+ return labelKeys.unique() as List
}
String getDefaultLabel() {
diff --git a/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy b/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy
index e941158d..409a38e1 100644
--- a/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy
+++ b/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy
@@ -30,8 +30,9 @@ class DelegatingBeanPropertyAccessorImpl implements BeanPropertyAccessor {
final Class beanType
final String propertyName
final Class propertyType
+ final boolean addPathFromRoot
- DelegatingBeanPropertyAccessorImpl(Object rootBean, Object value, Class propertyType, String pathFromRoot, DomainProperty domainProperty) {
+ DelegatingBeanPropertyAccessorImpl(Object rootBean, Object value, Class propertyType, String pathFromRoot, DomainProperty domainProperty, boolean addPathFromRoot) {
this.rootBean = rootBean
this.value = value
this.pathFromRoot = pathFromRoot
@@ -39,6 +40,7 @@ class DelegatingBeanPropertyAccessorImpl implements BeanPropertyAccessor {
this.propertyType = propertyType
this.propertyName = domainProperty.name
this.beanType = domainProperty.beanType
+ this.addPathFromRoot = addPathFromRoot
}
@Override
@@ -97,6 +99,9 @@ class DelegatingBeanPropertyAccessorImpl implements BeanPropertyAccessor {
List labelKeys = []
if (rootBean) {
labelKeys.add("${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''))
+ if (addPathFromRoot) {
+ labelKeys.add("${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''))
+ }
}
labelKeys.addAll(domainProperty.labelKeys)
labelKeys.unique() as List
diff --git a/src/test/groovy/grails/plugin/formfields/BuildsAccessorFactory.groovy b/src/test/groovy/grails/plugin/formfields/BuildsAccessorFactory.groovy
index d5fb99f4..efb43045 100644
--- a/src/test/groovy/grails/plugin/formfields/BuildsAccessorFactory.groovy
+++ b/src/test/groovy/grails/plugin/formfields/BuildsAccessorFactory.groovy
@@ -21,6 +21,7 @@ abstract class BuildsAccessorFactory extends Specification implements GrailsWebU
proxyHandler = new DefaultProxyHandler()
grailsDomainClassMappingContext = ref("grailsDomainClassMappingContext")
fieldsDomainPropertyFactory = dpf
+ grailsApplication = ref('grailsApplication')
}
}
}
diff --git a/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy b/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy
index e19cc7cf..2c3b1b67 100644
--- a/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy
+++ b/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy
@@ -158,7 +158,7 @@ class DomainClassPropertyAccessorSpec extends BuildsAccessorFactory {
propertyAccessor.domainProperty == null
}
- @Issue('https://github.com/grails-fields-plugin/grails-fields/issues/37')
+ @Issue('https://github.com/gpc/fields/issues/37')
void "resolves constraints of the '#property' property when the intervening path is null"() {
given:
def book = new Book()
@@ -214,7 +214,7 @@ class DomainClassPropertyAccessorSpec extends BuildsAccessorFactory {
propertyAccessor.constraints.inList == ["USA", "UK", "Canada"]
}
- @Issue('https://github.com/grails-fields-plugin/grails-fields/issues/38')
+ @Issue('https://github.com/gpc/fields/issues/38')
void "label keys for '#property' are '#labels'"() {
given:
def bean = beanType.list().find { it.class == beanType }
@@ -232,6 +232,25 @@ class DomainClassPropertyAccessorSpec extends BuildsAccessorFactory {
Author | 'books[0].title' | ['author.books.title.label', 'book.title.label']
}
+ @Issue('https://github.com/gpc/fields/issues/340')
+ void "label keys for '#property' are '#labels' when addPathFromRoot == true"() {
+ given:
+ config.setAt('grails.plugin.fields.i18n.addPathFromRoot', true)
+ def bean = beanType.list().find { it.class == beanType}
+ def propertyAccessor = factory.accessorFor(bean, property)
+
+ expect:
+ propertyAccessor.labelKeys == labels
+
+ where:
+ beanType | property | labels
+ Person | 'name' | ['person.name.label', 'name.label']
+ Person | 'dateOfBirth' | ['person.dateOfBirth.label', 'dateOfBirth.label']
+ Person | 'address' | ['person.address.label', 'address.label']
+ Person | 'address.city' | ['person.address.city.label', 'address.city.label']
+ Author | 'books[0].title' | ['author.books.title.label', 'books.title.label', 'book.title.label']
+ }
+
void "default label for '#property' is '#label'"() {
given:
def bean = beanType.list().first()
@@ -292,7 +311,7 @@ class DomainClassPropertyAccessorSpec extends BuildsAccessorFactory {
propertyAccessor.invalid
}
- @Issue('https://github.com/grails-fields-plugin/grails-fields/issues/160')
+ @Issue('https://github.com/gpc/fields/issues/160')
void "resolves transient property"() {
given:
def propertyAccessor = factory.accessorFor(person, "transientText")
@@ -309,7 +328,7 @@ class DomainClassPropertyAccessorSpec extends BuildsAccessorFactory {
propertyAccessor.constraints.nullable
}
- @Issue('https://github.com/grails-fields-plugin/grails-fields/issues/160')
+ @Issue('https://github.com/gpc/fields/issues/160')
void "resolves id property that has no constraints"() {
given:
def propertyAccessor = factory.accessorFor(person, "id")