bean fields core integration - grails/grails-core GitHub Wiki

This page is for the discussion of proposed merging of a successor to the bean-fields plugin into Grails core.

Goals

  • Application and plugins can render fields for bean properties with simple tags that include Label and per-field errors
  • The markup surrounding the fields must be configurable by the application and plugin developer, preferably with scope for overriding by the application developer i.e. to make GSPs from plugins visually and markup coherent with their own templates
  • The functionality should be based around decorating the "field" markup and providing the smarts for property and constrain-related attributes, error display etc.
  • It should be possible to render all the fields for a given bean type using a GSP fragment located explicitly or by convention (depending on user's need)
  • All smart property-related behaviour to be overridable by the tag invocation using attributes e.g. override/suppress auto-generated "id", "name", "isRequired", "requiredIndicator", "errorClass", "value", and option lists for selects and so on.
  • Mechanism for plugins to provide default field renderings (to be decorated) for specific property types - hooking into the "smart field selection" logic

Levels of modularity

  1. The top-level GSP, e.g. a scaffolded create.gsp or edit.gsp. This is responsible for selecting the fields to be used and laying them out in relation to one another.
  2. The markup surrounding each input. This would include label tags, any surrounding div or other container, per-field error messages, etc. This would be the same for the majority of fields in a project but needs to be customisable for special cases.
  3. The input itself. The input rendered should be appropriate to the property type, e.g. select for enums, checkbox for booleans, etc. However, this needs to be customisable on a per-field basis and a per-type basis across the project. i.e. if I want to use radio inputs for a particular enum instead of a select I should be able to configure that once and have it used in any form that renders an input for that enum type.

Use cases

Scaffolding

Grails scaffolding could use a modular, convention-driven way to render a form for a domain instance. The existing create.gsp and edit.gsp iterate over the persistent properties and instead of using the monolithic renderEditor.template can use a tag such as <bean:field bean="${domainInstance}" property="foo"/>. The exact markup this renders can be customised on a per-property basis falling back to a default with automatic input type selection based on the property type.

Extensibility by plugins

A good example is the Joda-Time plugin being able to register default input rendering for the types it supports which gets picked up so that <bean:field bean="${domainInstance}" property="jodaLocalDateProperty"/> renders the correct input with the same surrounding markup in terms of labels, mandatory indicators, etc. as any other field. Again it should be possible for a user to override how this is done.

Embedded properties

Currently these are not handled by scaffolding and it's problematic to do so within the limitations of renderEditor.template. It would be useful to have recursive rendering so that the properties of embedded types could be grouped inside a fieldset for example.

HTML5 constraints

The bean-fields plugin currently adds mandatory indicators but with HTML5 we can mark fields up with the required boolean attribute. Also there are other constraint-type attributes such as pattern, min, max and range that could be automatically rendered on inputs where appropriate. This implies we probably need access to the ConstrainedProperty instance inside the templates and a standard way of converting that to HTML attributes.

Mandatory indicators

bean-fields allows for adding a mandatory indicator but it can be a bit inflexible if you want to do something like adding a class to the surrounding div element and using CSS content generation to apply the indicator. Supporting this kind of flexibility is essential. CSS can also use the :required and :optional pseudo-classes in current browsers so hardcoding asterisk characters in the markup is increasingly non-optimal.

Thoughts on implementation

  • Can we use 2 levels of template, one to render the input itself and one to render the whole field (i.e. including surrounding markup)? The latter would be passed the output of the former much as g:radioGroup passes the rendered radio button to the tag body.
  • Input templates need to be overrideable by class/property or by property type. e.g. if I want the name field on my Person domain class to use a different type of input to the default for a String I can. Specifying templates by property type would mean plugins can easily extend the rendering, e.g. Joda-time can just register a bunch of templates for the org.joda.time.* types and they will just work. Config like grails.inputs.com.myapp.Book.author = "templatename" or Config like grails.inputs.java.lang.String = "templatename" (might be ambiguity there)? I know Marc isn't keen on using templates here but I'm not sure how else we can allow for extensibility by plugins. Any suggestions welcome.
  • Field templates need to be overrideable by class/property. I can see edge cases for overriding by property type but I think it would be pretty unusual. Config like grails.fields.com.myapp.Book.author = "templatename"?
  • Preferably the exact same set of attributes should be passed to both 'levels' of template.

Template resolution

I spiked the resolution of templates following discussions with Marc. See code here. Code needs refactoring so that some of the internal stuff done by GroovyPagesTemplateEngine and RenderTagLib is accessible as right now I'm essentially doing template resolution twice.

Tag & template parameters

The tag needs to accept the following parameters:

  • bean: required the domain class / command instance
  • property: required the property name
  • label: label text that overrides the default
  • labelKey: i18n key for label text that overrides the default
  • value: an override for the actual bean property value
  • default: a default value that is used if the actual property value is null

The following parameters should be passed to the top-level template:

  • bean: the domain class / command instance
  • property: the name of the property
  • value: the raw value of the property on the bean
  • constraints: the ConstrainedProperty instance
  • label: the label text resolved from the message source by convention
  • errors: a Collection<String> of error messages (pre-resolved)
⚠️ **GitHub.com Fallback** ⚠️