Flash-Scoped Messages Helper
Dependency :
compile ":flash-helper:0.9.3"
Summary
Simplifies and standardizes the process of adding/reading messages in the flash scope, particularly i18n messages that must be retrieved from the messages.properties files. It provides the following features: Automatically resolves i18n messages when message keys are stored in flash scope Optionally enforces the use of a limited number of flash keys (e.g. info, error, warning) Supports adding multiple messages to the same flash key Allows a Locale and default message argument to be provided when resolving i18n messages Provides a taglib that can be used to retrieve messages added to the flash
Installation
grails install-plugin flash-helper
Description
Overview
This plugin simplifies and standardizes the process of adding/reading messages in the flash scope, particularly i18n messages that must be retrieved from the messages*.properties files. It provides the following features:- Automatically resolves i18n messages when message keys are stored in flash scope
- Optionally enforces the use of a limited set of flash keys (e.g. info, error, warning)
- Supports adding multiple messages to the same flash key
- Allows a Locale and default message argument to be provided when resolving i18n messages
- Provides a taglib that can be used to retrieve messages added to the flash
- Ensures that objects stored in flash scope (e.g. information messages) are only displayed once
The Problem
A common pattern for usage of the flash scope is to store messages using a limited set of keys, e.g. "info", "error", "warning". Messages placed into the flash under one of these keys may be displayed using a GSP template such as<g:if test="${flash.info}"> <div class="info">${flash.info}</div> </g:if>
<g:if test="${flash.info}"> <g:each in="${flash.info}" var="msg"> <div class="info">${msg}</div> </g:each> </g:if>
flash.key = "message" flash.args = ["arg1", "arg2"] flash.default = "default message"
message tag<g:message code="${flash.key}" args="${flash.args}" default="${flash.default}"/>
- Storing/retrieving i18n messages should be simpler
- Mistyping the key when storing/retrieving the message would (silently) cause a message display failure
- No support for storing multiple messages under the same key
The Solution
Storing Flash Messages
This plugin adds an objectflashHelper to every controller that may be used to:Store multiple messages under the same key
flashHelper.info "Some Message" flashHelper.info "Some Message2"
Easy storage/retrieval of i18n messages
After a method call such asflashHelper.info "key2"When retrieving parametrized messages from the resource bundles, additional Locale and default message arguments may be provided. If a default message is provided, the default message will be stored in flash if the key does not exist in the relevant messsages*.properties file. Refer to the "Complete Example" section below for details.
Easy storage/retrieval of i18n messages with arguments
Similarly, a i18n message with arguments may be stored in the flash scope with a method call such as// Store a a single message that takes two arguments under 'info' key flashHelper.info key1: ['arg1', 'arg2']
When retrieving parametrized messages from the resource bundles, additional Locale and default message arguments may be provided. If a default message is provided, the default message will be stored in flash if the key does not exist in the relevant messsages*.properties file. Refer to the "Complete Example" section below for details.
Message Argument Resolution
Assume you have the following resources configurednot.found={0} not found
person.label=Personperson.label (e.g. using the messageSource Spring bean) and providing the result along with the key not.found to the flashHelper as described in the previous section. Fortunately, since version 0.6 this can be performed in a single step by providing true as the final argument, e.g.flashHelper.info not.found: 'person.label', trueNamed Arguments API
When invoking theflashHelper through the default API with a large number of arguments, the readability of the code suffers. An example of this is when you supply a message code, arguments, a locale, a default message, and enable message argument resolution:flashHelper.info 'code': ['arg1', 'arg2'], Locale.FRENCH, "default message", true
flashHelper.info(
msgs: ['code': ['arg1', 'arg2']],
locale: Locale.FRENCH,
default: "default message",
resolveArgs: true,
codeMustResolve: true)codeMustResolve argument specifies what should happen if a message code is not found in the resource bundle (and no default message is provided). If this argument is true and exception is thrown, if false the code itself will be used as the message.(In the case of the standard API this behavior cannot be specified, instead the following rule is used: an exception is always thrown if message arguments are provided, and the key itself is always used if they are not)Message Key Standardization
If you add a property such as:// Value of this property may be either a string or list of strings
flashHelper.keys = ['message', 'error']Config.groovy the flashHelper object will throw a FlashKeyException if you try to use the flashHelper to store a message in the flash scope using any other key. If this property is not present, there is no restriction on the keys that may be used.Regardless of whether or not this property is configured, any key may be used when bypassing flashHelper and accessing the flash object directly.Reading Flash Messages
TheflashHelper stores all messages added to a particular flash key within a list. If a message is added under the "info" key:flashHelper.info "my message"${flash.info}[my message]
${flash.info.join("<br/>")}my message
my message<br/> my message2<br/> my message3
<br/> passed to the join() method above, may be replaced by any other separator string. As an alternative to using the join() method to display messages added with the flashHelper one may instead use the tag library described in the following section.Tag Library
The plugin provides 2 tags under theflashMsg namespaceflashMsg:msg
<flashMsg:msg key="info" sep=" "/>
join method (described above), i.e. it uses a separator string to join all messages stored under a particular flash key.
- The
keyattribute is mandatory and indicates the flash key under which the message(s) is stored - The
sepattribute is optional and indicates the separator string to use when joining the messages (if only a single message is stored under the key, this attribute is unused). If this argument is not provided the tag will use the value of theflashHelper.separatorproperty inConfig.groovyIf neither thesepattribute nor the configuration property exist, a default separator<br/>will be used. - The
keyNotFoundattribute is described in the "Invalid Key Handling" section below
flashMsg:msgBody
Renders the tag body for each message stored under the provided flash key. For example, if the messagefoo is stored under the flash key myKey the following invocation of this tag<flashMsg:msgBody key="myKey">
I said ${it}<br/>
</flashMsg:msgBody>I said foo<br/>
foo and bar are stored under this key, the tag above will render
I said foo<br/>I said bar<br/>
key attribute, this tag also supports the keyNotFound attribute described in the following section.Invalid Key Handling
If thekey value provided to either tag does not exist, the default behavior is to continue without warning or error. This default behavior can be changed either per-invocation, or application-wide. To customize the default behavior on a per-invocation basis, set the value of the keyNotFound attribute to one of the following:
- warn - log a warning if an invalid key is provided
- error - thow a
FlashKeyExceptionif an invalid key is provided - ignore - do nothing if an invalid key is provided (the default)
Config.groovy
// Set the property below to "error", "warn", or "ignore" flashHelper.keyNotFound = 'error'
Flash Object Removal
Both tags provide an optional remove attribute which defaults to false. If set to true, an object is removed from flash scope after it is displayed with either tag. This is useful when an object is added to the flash scope followed by an invocation of the controller's render method (rather than redirect) In the normal course of events, the object would remain in flash scope for the next request, which could cause it to be displayed twice.Setting the remove attribute to true ensures that an object in flash scope is only displayed once even when the controller does not perform a redirectExamples
Controller Code
These examples are taken fromFlashHelperTestControllerTestsThe test classes are an excellent source of information about how to use theflashHelperobject and the tag library.
// Store literal message under "info" key flashHelper.info "this is a message"// Store message under "info" that must be retrieved from resource bundles flashHelper.info 'key1'// Store message under "info" that must be retrieved from messages_fr.properties flashHelper.info 'key1', Locale.FRENCH// Store message under "info" that must be retrieved from resource bundles. // Provide a default message in case the key is not found flashHelper.info 'key1', 'default'// Store message under "info" that must be retrieved from messages_fr.properties // Provide a default message in case key is not found. // The order of the default message and Locale arguments may be transposed flashHelper.info 'key1', 'default', Locale.FRENCH// Store parametrized messages under "info" that must be retrieved from resource bundles flashHelper.info key4: ['arg1', 'arg2']// Store parametrized messages under "info" that must be retrieved from resource bundles // Provide default messages in case keys are not found flashHelper.info key4: ['arg1', 'arg2'], "default"// Store parametrized messages under "info" that must be retrieved from messages_fr.properties flashHelper.info key4: ['arg1', 'arg2'], Locale.FRENCH// Store parametrized messages under "info" that must be retrieved from messages_fr.properties // Provide default messages in case keys are not found flashHelper.info key4: ['arg1', 'arg2'], Locale.FRENCH, "default"// Argument resolution - lookup 'resolvableArg' in the resource bundle and use the result as an // argument for the message with key 'key4' flashHelper.info key4: 'resolvableArg', true// Use the named arguments API flashHelper.info(msgs: "literal message") flashHelper.info(msgs: ['key': 'arg'], locale: Locale.FRENCH) flashHelper.info(msgs: ['key': ['arg1', 'arg2']], locale: Locale.FRENCH)
Minor Features
Clear Flash
flashHelper.clear()
flash object directly.Method Chaining
All the methods offlashHelper return this which enables calls to flashHelper to be chained together, e.g.flashHelper.info("msg1").warn("msg3").error("foo")
Feedback
Suggestions for improvements or bugs may be sent to the e-mail address in the plugin descriptor file. Patches are welcome, but please use the unit/integration tests provided to verify that the patch hasn't broken anything.Version History
- 0.9.5 - Upgraded to Grails 2.2.0. No previous version of this plugin will work for Grails >= 2.2.0
- 0.9.4 - Use
LocaleContextHolder.localefor default locale - 0.9.3 - Upgraded to Grails 2.1.0 and latest version of release plugin
- 0.9 - Upgraded to Grails 2.0.4 and changed taglib namespace from
flashtoflashMsgbecause the former collided with theflashobject when you try to invoke the tag using thenamespace.tagName()syntax. - 0.8 - Removed ability to store multiple messages under the same key in a single method call. The value of this feature isn't worth the complexity it adds to the API / implementation / documentation
- 0.7.6 - removed redundant code - no functional changes
- 0.7.5 - Changes to the named arguments API
- 0.7 - Added named arguments API
- 0.6.5 - Refactoring and performance improvements - no functional changes
- 0.6 - Added argument resolution
- 0.5.1 - Changed package names - no functional changes
- 0.5 - Added
removeattribute to tags - 0.4.1 - Upgraded to Grails 1.3.6
- 0.4 - Added
keyNotFoundattribute to tags - 0.3 - Added
flash:msgBodytag - 0.2.1 - Upgraded to Grails 1.2.2 and fixed problem with controller dependency version
- 0.2 - Fixed bugs and added tag library, support for default messages, method chaining, and clear()
- 0.1 - Initial release