Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync master #350

Merged
merged 9 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 62 additions & 34 deletions grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import grails.core.GrailsApplication
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import groovy.xml.MarkupBuilder
import groovy.xml.MarkupBuilderHelper
import org.apache.commons.lang.StringUtils
import org.grails.buffer.FastStringWriter
import org.grails.datastore.mapping.model.MappingContext
Expand Down Expand Up @@ -167,6 +168,10 @@ class FormFieldsTagLib {
* @attr widget Specify the folder inside _fields where to look up for the widget template.
* @attr templates Specify the folder inside _fields where to look up for the wrapper and widget template.
* @attr theme Theme name
* @attr css html css attribute for wrapper (default: field-contain)
* @attr divClass class for optional div that will be added if set and contain the widget (default: no div created)
* @attr invalidClass class added to wrapper that if property is invalid. (default: error) Also available for widget.
* @attr requiredClass class added to wrapper when property is required. (default: required) Also available for widget.
*/
def field = { attrs, body ->
attrs = beanStack.innerAttributes + attrs
Expand All @@ -190,26 +195,32 @@ class FormFieldsTagLib {

String prefixAttribute = formFieldsTemplateService.getWidgetPrefix() ?: 'widget-'
attrs.each { k, v ->

if (k?.startsWith(prefixAttribute))
if (k?.startsWith(prefixAttribute)) {
widgetAttrs[k.replace(prefixAttribute, '')] = v
else
} else {
wrapperAttrs[k] = v
}
}

List classes = [widgetAttrs['class'] ?: '']
if (model.invalid) classes << (widgetAttrs.remove('invalidClass') ?: '')
if (model.required) classes << (widgetAttrs.remove('requiredClass') ?: '')
widgetAttrs['class'] = classes.join(' ').trim()
if (widgetAttrs['class'].isEmpty()) {
widgetAttrs.remove('class')
}
if (hasBody(body)) {
model.widget = raw(body(model + [attrs: widgetAttrs] + widgetAttrs))
} else {
model.widget = renderWidget(propertyAccessor, model, widgetAttrs, widgetFolder ?: templatesFolder, theme)
}


String templateName = formFieldsTemplateService.getTemplateFor("wrapper")
Map template = formFieldsTemplateService.findTemplate(propertyAccessor, templateName, fieldFolder ?: templatesFolder, theme)
if (template) {
out << render(template: template.path, plugin: template.plugin, model: model + [attrs: wrapperAttrs] + wrapperAttrs)
} else {
out << renderDefaultField(model)
out << renderDefaultField(model, wrapperAttrs)
}
}
}
Expand Down Expand Up @@ -338,7 +349,7 @@ class FormFieldsTagLib {
String template = attrs.remove('template') ?: 'list'

List properties = resolvePersistentProperties(domainClass, attrs)
out << render(template: "/templates/_fields/$template", model: [domainClass: domainClass, domainProperties: properties]) { prop ->
out << render(template: "/templates/_fields/$template", model: attrs + [domainClass: domainClass, domainProperties: properties]) { prop ->
BeanPropertyAccessor propertyAccessor = resolveProperty(bean, prop.name)
Map model = buildModel(propertyAccessor, attrs, 'HTML')
out << raw(renderDisplayWidget(propertyAccessor, model, attrs, templatesFolder, theme))
Expand All @@ -356,10 +367,11 @@ class FormFieldsTagLib {

String prefixAttribute = formFieldsTemplateService.getWidgetPrefix() ?: 'widget-'
attrs.each { k, v ->
if (k?.startsWith(prefixAttribute))
if (k?.startsWith(prefixAttribute)) {
widgetAttrs[k.replace(prefixAttribute, '')] = v
else
} else {
wrapperAttrs[k] = v
}
}


Expand Down Expand Up @@ -389,8 +401,9 @@ class FormFieldsTagLib {
Map widgetAttrs = [:]
attrs.each { k, v ->
String prefixAttribute = formFieldsTemplateService.getWidgetPrefix()
if (k?.startsWith(prefixAttribute))
if (k?.startsWith(prefixAttribute)) {
widgetAttrs[k.replace(prefixAttribute, '')] = v
}
}
return widgetAttrs
}
Expand Down Expand Up @@ -425,11 +438,9 @@ class FormFieldsTagLib {
private List<String> resolvePropertyNames(PersistentEntity domainClass, Map attrs) {
if (attrs.containsKey('order')) {
return getList(attrs.order)
}
else if (attrs.containsKey('properties')) {
} else if (attrs.containsKey('properties')) {
return getList(attrs.remove('properties'))
} else {

List<String> properties = resolvePersistentProperties(domainClass, attrs, true)*.name
int maxProperties = attrs.containsKey('maxProperties') ? attrs.remove('maxProperties').toInteger() : 7
if (maxProperties && properties.size() > maxProperties) {
Expand Down Expand Up @@ -474,8 +485,7 @@ class FormFieldsTagLib {
String errorMsg = null
try {
errorMsg = error instanceof MessageSourceResolvable ? messageSource.getMessage(error, locale) : messageSource.getMessage(error.toString(), null, locale)
}
catch (NoSuchMessageException ignored) {
} catch (NoSuchMessageException ignored) {
// no-op
}
// unresolved message codes fallback to the defaultMessage and this should
Expand Down Expand Up @@ -541,8 +551,9 @@ class FormFieldsTagLib {

private String resolvePrefix(prefixAttribute) {
def prefix = resolvePageScopeVariable(prefixAttribute) ?: prefixAttribute ?: beanStack.prefix
if (prefix && !prefix.endsWith('.'))
if (prefix && !prefix.endsWith('.')) {
prefix = prefix + '.'
}
prefix ?: ''
}

Expand Down Expand Up @@ -617,34 +628,45 @@ class FormFieldsTagLib {
def message = keysInPreferenceOrder.findResult { key ->
message(code: key, default: null) ?: null
}
if(log.traceEnabled && !message) {
if (log.traceEnabled && !message) {
log.trace("i18n missing translation for one of ${keysInPreferenceOrder}")
}
message ?: defaultMessage
}

private CharSequence renderDefaultField(Map model) {
List classes = ['fieldcontain']
if (model.invalid) classes << 'error'
if (model.required) classes << 'required'

protected CharSequence renderDefaultField(Map model, Map attrs = [:]) {
List classes = [attrs['class'] ?: 'fieldcontain']
if (model.invalid) classes << (attrs.remove('invalidClass') ?: 'error')
if (model.required) classes << (attrs.remove('requiredClass') ?: 'required')
attrs['class'] = classes.join(' ').trim()
Writer writer = new FastStringWriter()
new MarkupBuilder(writer).div(class: classes.join(' ')) {
label(for: (model.prefix ?: '') + model.property, model.label) {
def mb = new MarkupBuilder(writer)
mb.setDoubleQuotes(true)
mb.div(class: attrs['class']) {
label(class: attrs.labelClass, for: (model.prefix ?: '') + model.property, model.label) {
if (model.required) {
span(class: 'required-indicator', '*')
}
}
// TODO: encoding information of widget gets lost - don't use MarkupBuilder
def widget = model.widget
if (widget != null) {
mkp.yieldUnescaped widget
if (attrs.divClass) {
div(class: attrs.divClass) {
renderWidget(mkp, model)
}
} else {
renderWidget(mkp, model)
}

}
writer.buffer
}

private void renderWidget(MarkupBuilderHelper mkp, Map model) {
// TODO: encoding information of widget gets lost - don't use MarkupBuilder
def widget = model.widget
if (widget != null) {
mkp.yieldUnescaped widget
}
}

CharSequence renderDefaultInput(Map model, Map attrs = [:]) {
renderDefaultInput(null, model, attrs)
}
Expand Down Expand Up @@ -713,18 +735,24 @@ class FormFieldsTagLib {
if (!attrs.type) {
if (constrained?.inList) {
attrs.from = constrained.inList
if (!model.required) attrs.noSelection = ["": ""]
if (!model.required) {
attrs.noSelection = ["": ""]
}
return g.select(attrs)
} else if (constrained?.password) {
attrs.type = "password"
attrs.remove('value')
} else if (constrained?.email) attrs.type = "email"
else if (constrained?.url) attrs.type = "url"
else attrs.type = "text"
} else if (constrained?.email) {
attrs.type = "email"
} else if (constrained?.url) {
attrs.type = "url"
} else {
attrs.type = "text"
}
}

if (constrained?.matches) attrs.pattern = constrained.matches
if (constrained?.maxSize) attrs.maxlength = constrained.maxSize
if (constrained?.matches) { attrs.pattern = constrained.matches }
if (constrained?.maxSize) { attrs.maxlength = constrained.maxSize }

if (constrained?.widget == 'textarea') {
attrs.remove('type')
Expand Down
8 changes: 4 additions & 4 deletions grails-app/views/templates/_fields/_list.gsp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<ol class="property-list ${domainClass.decapitalizedName}">
<ol class="${pageScope['class']?:'property-list'} ${domainClass.decapitalizedName}">
<g:each in="${domainProperties}" var="p">
<li class="fieldcontain">
<span id="${p.name}-label" class="property-label"><g:message code="${domainClass.decapitalizedName}.${p.name}.label" default="${p.defaultLabel}" /></span>
<div class="property-value" aria-labelledby="${p.name}-label">${body(p)}</div>
<li class="${listItemClass?:'fieldcontain'}">
<span id="${p.name}-label" class="${labelClass?:'property-label'}"><g:message code="${domainClass.decapitalizedName}.${p.name}.label" default="${p.defaultLabel}" /></span>
<div class="${valueClass?:'property-value'}" aria-labelledby="${p.name}-label">${body(p)}</div>
</li>
</g:each>
</ol>
6 changes: 3 additions & 3 deletions grails-app/views/templates/_fields/_table.gsp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<table>
<table<g:if test="${pageScope['class']}"> class="${pageScope['class']}"</g:if>>
<thead>
<tr>
<g:each in="${domainProperties}" var="p" status="i">
Expand All @@ -11,10 +11,10 @@
<tr class="${(i % 2) == 0 ? 'even' : 'odd'}">
<g:each in="${domainProperties}" var="p" status="j">
<g:if test="${j==0}">
<td><g:link method="GET" resource="${bean}"><f:display bean="${bean}" property="${p.property}" displayStyle="${displayStyle?:'table'}" theme="${theme}"/></g:link></td>
<td><g:link method="GET" resource="${bean}" controller="${controller}"><f:display bean="${bean}" property="${p.property}" displayStyle="${displayStyle?:'table'}" theme="${theme}"/></g:link></td>
</g:if>
<g:else>
<td><f:display bean="${bean}" property="${p.property}" displayStyle="${displayStyle?:'table'}" theme="${theme}"/></td>
<td><f:display bean="${bean}" property="${p.property}" displayStyle="${displayStyle?:'table'}" theme="${theme}"/></td>
</g:else>
</g:each>
</tr>
Expand Down
Loading