Binding a Grails date from params in a controller
Asked Answered
C

6

42

Why is it so hard to extract the date from the view via the params in a grails controller?

I don't want to extract the date by hand like this:

instance.dateX = parseDate(params["dateX_value"])//parseDate is from my helper class

I just want to use instance.properties = params.

In the model the type is java.util.Date and in the params is all the information: [dateX_month: 'value', dateX_day: 'value', ...]

I searched on the net and found nothing on this. I hoped that Grails 1.3.0 could help but still the same thing.

I can't and will not believe that extracting the date by hand is necessary!

Concern answered 20/5, 2010 at 8:7 Comment(2)
note that in recent (2.0.x) versions of Grails there is a bug that affects date binding: jira.grails.org/browse/GRAILS-9165Lattimer
For posterity: note that the Joda Time plugin implements databinding automatically.Nally
A
89

Grails Version >= 2.3

A setting in Config.groovy defines the date formats which will be used application-wide when binding params to a Date

grails.databinding.dateFormats = [
        'MMddyyyy', 'yyyy-MM-dd HH:mm:ss.S', "yyyy-MM-dd'T'hh:mm:ss'Z'"
]

The formats specified in grails.databinding.dateFormats will be attempted in the order in which they are included in the List.

You can override these application-wide formats for an individual command object using @BindingFormat

import org.grails.databinding.BindingFormat

class Person { 
    @BindingFormat('MMddyyyy') 
    Date birthDate 
}

Grails Version < 2.3

i can't and will not belief that extracting the date by hand is nessesary!

Your stubbornness is rewarded, it has been possible to bind a date directly since long before Grails 1.3. The steps are:

(1) Create a class that registers an editor for your date format

import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
import org.springframework.beans.propertyeditors.CustomDateEditor
import java.text.SimpleDateFormat

public class CustomDateEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        String dateFormat = 'yyyy/MM/dd'
        registry.registerCustomEditor(Date, new CustomDateEditor(new SimpleDateFormat(dateFormat), true))
    }
}

(2) Make Grails aware of this date editor by registering the following bean in grails-app/conf/spring/resources.groovy

beans = {
    customPropertyEditorRegistrar(CustomDateEditorRegistrar)
}

(3) Now when you send a date in a parameter named foo in the format yyyy/MM/dd it will automatically be bound to a property named foo using either:

myDomainObject.properties = params

or

new MyDomainClass(params)
Abbasid answered 20/5, 2010 at 9:2 Comment(7)
thanx for the quick answer, i tried this before (already had the customPropertyEditorRegistrar in my spring resources, and i saw the old stackoverflow question you mentioned above) but this does not work for me somehow. my class looks like this: class CustomDateEditorRegistrar implements PropertyEditorRegistrar { public void registerCustomEditors(PropertyEditorRegistry registry) { String dateFormat = 'dd.MM.yyyy' registry.registerCustomEditor(Date, new CustomDateEditor(new SimpleDateFormat(dateFormat), true)) } } and the debugger is picking it up properly.Concern
is there maybe a problem with my dateformat (dots)? or something?Concern
i'm getting the following warning: project/src/java/CustomEditorRegistrar.java uses or overrides a deprecated API. is there a 'new' way of doing this?Austine
@Austine I've no idea. You'll need to make some small changes to the code above if you're going to use Java instead of GroovyNabataean
sorry don, i posted the comment in the wrong question :( my comment was for this question: https://mcmap.net/q/391311/-grails-date-unmarshallingAustine
@Don, great answer however to the OP's point, it seems absurd that this is required for a Date. Perhaps if you want a non-standard Date format. However, it seems there should be a default format that just works without having to add anything custom.Felker
I think it is worth mentioning that not all these solutions work if you want your application to be internationalized. If you do, you can use @BindingFormat(code='default.date.format') or see the answer from @mpccolorado.Grenadines
S
15

Grails 2.1.1 has new method in params for easy null safe parsing.

def val = params.date('myDate', 'dd-MM-yyyy')
// or a list for formats
def val = params.date('myDate', ['yyyy-MM-dd', 'yyyyMMdd', 'yyMMdd']) 
// or the format read from messages.properties via the key 'date.myDate.format'
def val = params.date('myDate')

Find it in doc here

Stidham answered 29/11, 2012 at 3:26 Comment(2)
I consider this reading a date from params rather than "binding a date"Nabataean
Seems to be the best answer. But I tested it and found an issue. Grails raises a "No message found under code date.myDate.format" exception if I don't have the code in my messages.properties. I think the engine should search for a general format (default.date.format) before raising such exception.Schnorrer
L
12

Grails Version >= 3.x

You can set in application.yml the date formats following this syntax:

grails:
  databinding:
    dateFormats:
      - 'dd/MM/yyyy'
      - 'dd/MM/yyyy HH:mm:ss'
      - 'yyyy-MM-dd HH:mm:ss.S'
      - "yyyy-MM-dd'T'hh:mm:ss'Z'"
      - "yyyy-MM-dd HH:mm:ss.S z"
      - "yyyy-MM-dd'T'HH:mm:ssX"
Leftwards answered 24/8, 2016 at 18:15 Comment(0)
E
2

Have you tried using any of the Grails date picker plugins?

Ive had good experiences with the calendar plugin.

(When using the calendar plugin) When you submit the request of the date selection you can automatically bind the query parameter to the domain object you want to populate with the request.

E.g.

new DomainObject(params)

You can also parse a "yyyy/MM/dd" date string like so...

new Date().parse("yyyy/MM/dd", "2010/03/18")
Ellersick answered 21/5, 2010 at 0:10 Comment(3)
I think calling new Date.parse() explicitly is exactly what he wants to avoid. AFAIK, if you bind the parameters directly, you still need to register a custom date editor (as described in my reply). Otherwise, how could the databinder possibly know which field is the month and which is the year (for example). I agree that the calendar plugin is the best of the date-picker plugins available.Nabataean
Yep, definitely go with the parameter binding approach. new Date().parse() can be very useful elsewhere though.Ellersick
my approach right now is very bad i think. i am first removing the date value from the params like this: (params.remove("datefield")) than i am doing something like this: instance.datefield = hlpr.exDate(params["datefield_value"] as String) i know that sounds really strange but thats somehow the only way that it works right now...Concern
W
2

@Don Thanks for the answer above.

Here's an alternative to the custom editor that checks first date time then date format.

Groovy, just add semi colons back in for java

import java.text.DateFormat
import java.text.ParseException
import org.springframework.util.StringUtils
import java.beans.PropertyEditorSupport

class CustomDateTimeEditor extends PropertyEditorSupport {
    private final java.text.DateFormat dateTimeFormat
    private final java.text.DateFormat dateFormat
    private final boolean allowEmpty

    public CustomDateTimeEditor(DateFormat dateTimeFormat, DateFormat dateFormat, boolean allowEmpty) {
        this.dateTimeFormat = dateTimeFormat
        this.dateFormat = dateFormat
        this.allowEmpty = allowEmpty
    }

    /**
     * Parse the Date from the given text, using the specified DateFormat.
     */
    public void setAsText(String   text) throws IllegalArgumentException   {
        if (this.allowEmpty && !StringUtils.hasText(text)) {
            // Treat empty String as null value.
            setValue(null)
        }
        else {
            try {
                setValue(this.dateTimeFormat.parse(text))
            }
            catch (ParseException dtex) {
                try {
                    setValue(this.dateFormat.parse(text))
                }
                catch ( ParseException dex ) {
                    throw new IllegalArgumentException  ("Could not parse date: " + dex.getMessage() + " " + dtex.getMessage() )
                }
            }
        }
    }

    /**
     * Format the Date as String, using the specified DateFormat.
     */
    public String   getAsText() {
        Date   value = (Date) getValue()
        return (value != null ? this.dateFormat.format(value) : "")
    }
}
Whitton answered 27/6, 2012 at 13:59 Comment(0)
H
1

Grails Version >= 2.3

A localeAware version to convert strings to date

In src/groovy:

package test

import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest
import org.grails.databinding.converters.ValueConverter
import org.springframework.context.MessageSource
import org.springframework.web.servlet.LocaleResolver

import javax.servlet.http.HttpServletRequest
import java.text.SimpleDateFormat

class StringToDateConverter implements ValueConverter {
    MessageSource messageSource
    LocaleResolver localeResolver

    @Override
    boolean canConvert(Object value) {
        return value instanceof String
    }

    @Override
    Object convert(Object value) {
        String format = messageSource.getMessage('default.date.format', null, "dd/MM/yyyy", getLocale())
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format)
        return simpleDateFormat.parse(value)
    }

    @Override
    Class<?> getTargetType() {
        return Date.class
    }

    protected Locale getLocale() {
        def locale
        def request = GrailsWebRequest.lookup()?.currentRequest
        if(request instanceof HttpServletRequest) {
            locale = localeResolver?.resolveLocale(request)
        }
        if(locale == null) {
            locale = Locale.default
        }
        return locale
    }
}

In conf/spring/resources.groovy:

beans = {
    defaultDateConverter(StringToDateConverter){
        messageSource = ref('messageSource')
        localeResolver = ref('localeResolver')
    }
}

The bean's name 'defaultDateConverter' is really important (to override the default date converter)

Homework answered 10/4, 2015 at 2:24 Comment(1)
Nice solution that changes all data binding across the app AND is locale-aware. I'm amazed Grails doesn't do this by default as it would fit with convention over configuration.Grenadines

© 2022 - 2024 — McMap. All rights reserved.