grailsgrails-plugingsp

Grails Field Plugin: how to access bean from custom layout template for f:display tag


I am trying to create a scaffolding template so that my calls to

<f:display bean="someBean" />

render with nicer link text for fields that link to a related entity. Given a domain class that looks like this:

class SomeDomainClass {

    String name

    SomeDomainClass parent

    static constraints = {}

}

the default layout template for a <f:display bean="someBean"> will render the "parent" field as an link with value "[class name]: [record id]", e.g. the first record in the database the parent link would render as <a href="...">SomeDomainClass: 1</a>

I am trying to use a convention where entities that have a "name" field defined the link will be rendered using entity.name and otherwise fall back to the entity.toString() method that is used by default. In the example above, for an entity linked to a parent named "THE_PARENT" I would want the link to be rendered as <a href="...">THE_PARENT</a>. I know it's possible to achieve this by overriding the toString() method in the domain class, but thought it would be nice to allow the toString method to be independent of the view requirements (it'll be useful to have ID in logs).

I have succeeded at getting links to show up this way for tables using this template grails-app/views/templates/_fields/_table.gsp.

<table>
    <thead>
         <tr>
            <g:each in="${domainProperties}" var="p" status="i">
                <g:sortableColumn property="${p.property}" title="${p.label}" />
            </g:each>
        </tr>
    </thead>
    <tbody>
        <g:each in="${collection}" var="bean" status="i">
            <tr>
                <g:each in="${domainProperties}" var="p" status="j">
                    <td><f:display bean="${bean}" property="${p.property}" displayStyle="${displayStyle?:'table'}" theme="${theme}"/></td>
                </g:each>
            </tr>
        </g:each>
    </tbody>
</table>

Together with displayWrappers for oneToOne, oneToMany, and manyToOne relationships (just showing grails-app/views/_fields/oneToOne/_displayWrapper.gsp for brevity):

<g:if test="${value}">
    <g:link method="GET" resource="${value}">${value.hasProperty('name') ? value.name : "${value.class.simpleName} ${value.id}" }</g:link>
</g:if>

However, with this setup calls to <f:display bean="someBean"/> are still rendering with the default toString method rather than one of the new templates. Looking at the source it looks like a call to f:display will internally call the list tag without the bean context ( the "bean" attr gets removed here ). My workaround has been to override the table template to simulate the way f:display renders a list in a way that has access to the bean context. This is what my workaround template for f:table looks like:

<table style="border: none">
    <tbody>
        <g:each in="${collection}" var="bean" status="i">
            <g:each in="${domainProperties}" var="p" status="j">
                <tr class="${(i % 2) == 0 ? 'even' : 'odd'}">
                    <td style="text-align: right">${p.label}</td>
                    <td><f:display bean="${bean}" property="${p.property}" displayStyle="${displayStyle?:'table'}" theme="${theme}"/></td>
                </tr>
            </g:each>
        </g:each>
    </tbody>
</table>

I feel like it should be possible to customize the <f:display> template to render related record links the way I want without resorting to use the <f:table> tag, but I haven't been able to figure it out.


Solution

  • You have 2 options:

    1. Annotate Domain class with @ToString
    2. Implement method toString in the domain class

    E.g:

    @ToString(includeNames = true, includePackage = false, includes = 'username,hDate')
    class SomeDomain ...
    

    E.g:

    class AnotherDomain {
        String firstName
        String middleName
        String lastName
    
        @Override
        String toString() {
            "${firstName}${middleName ? ' ' + middleName : ''} ${lastName}"
        }
    
    }