Skip to content

Commit

Permalink
Merge pull request #349 from codeconsole/bootstrap-prep
Browse files Browse the repository at this point in the history
Allow customization of default wrapper in order to support Bootstrap without requiring templates
  • Loading branch information
codeconsole committed Sep 15, 2024
2 parents a84f8fa + 668a132 commit 17beafa
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 41 deletions.
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

0 comments on commit 17beafa

Please sign in to comment.