Handling user input
You have now created a page that users can enter message data into. The next step is to handle the information that is submitted from this page. The operations you need to perform when the data is received are as follows:
- Convert the data from an HTTP request into your domain model
- Validate whether the information entered is acceptable for the application
- If there are problems with the information, stop executing and let the user know what the problems were
- If the data is acceptable, persist the data to the database
- Inform the user that the information has been saved
This is the sort of thing that slows down the development of the web application, but with Grails, it really is a breeze. Make the following highlighted changes to MessageController.groovy:
package app class MessageController { def create = { return [ message: new Message() ] } def save = { def message = new Message( params ) if( !message.hasErrors() && message.save() ) { flash.toUser = "Message [${message.title}] has been added." redirect( action: 'create' ) } else { render( view: 'create', model:[message: message] ) } } }
The save
action performs all the work of binding the request to the domain model, validating the user input, persisting the data to the database and informing the user. Not bad going for very few lines of code!
Binding the request to the domain
On the first line of the save
action, a new Message
instance is created from the parameters sent on the request. The params
property is a map that has been dynamically added to the controller at runtime by Grails. It contains all the request parameters as key/value entries in the map. The default Groovy constructor, which takes a map, is used to populate the properties of Message
from the params
map.
When a new instance of a domain class is created from a map, basic type checking validation takes place. If any property on the request is different from those expected by the domain class, then a new instance of the class will still be created and the details of the type conversion errors will be added to the errors
property of the new instance.
Validate and save
The next line of code in the save
action performs the validation and attempts to persist the users message to the database.
if(!message.hasErrors() && message.save() ) {
Grails adds a number of dynamic methods and properties to domain classes at runtime. The following are related to the validation of domain classes:
errors
— a property that contains an instance of the springErrors
interface: http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/validation/Errors.html. This property is populated as a result of data binding or validation.hasErrors
— a method to determine if there are validation errors on an instance of a domain class.validate
— a method to perform validation of the data stored in an instance of a domain class against the constraints defined on the domain class.
When the hasErrors
method is called in the save
action, the properties of the message instance will not have been validated against the constraints yet. As mentioned above, only type conversion validation has taken place. However, when the save
method is executed, the validate
method is called before the domain class instance is persisted to the database.
Grails adds a save
method onto all domain classes to persist the contents of the object to the database. This method takes two named parameters, validate
and flush
.
- The
validate
parameter tells thesave
method whether to validate the properties of the object against the validation constraints. If the validation fails, the method returns null and does not persist the data. This is set to true by default. - The
flush
parameter tells Hibernate whether to make the changes directly in the database, or to batch a number of database operations up together. The default is false. This allows Hibernate to control when it persists information to the database.
Flash scope
If the save
method executes without any validation errors, then our action has been successful, and we can tell the user:
flash.userMessage = "Message [${message.title}] has been added." redirect( action: 'create' )
It is common practice to perform a redirect after making an update to persistent storage so that, if a user performs a refresh in their browser, the same data will not be sent again. The problem with this approach is that all the state that exists on the server when the operation took place has been lost because of the redirect. Enter flash scope!
Flash scope has been introduced to provide a scope that holds data between two consecutive HTTP requests. This means that you can construct a message for the user after the data has been saved, when you have the necessary context, and then add these messages to the flash scope so that they are available to the redirected request.
Redirect
Grails adds a redirect
method, which of course sends a redirect command to the client browser, onto controller classes as syntactic sugar to simplify the operation. When performing a redirect, there are a number of options available.
You can specify the URI to be redirected to:
redirect( uri: "home/index" )
In your application, this would send the user to http://localhost:8080/teamwork/home/index
. Or you can specify the entire URL to redirect to:
redirect( url: "http://grails.codehaus.org" )
If you specify a controller and an action, then Grails will work out the URL to redirect the users to:
redirect( controller: "user", action: "list" )
This would send the user to http://localhost:8080/teamwork/user/list
. Alternatively, if you specify only the action:
redirect( action: "save" )
Then the current controller will be assumed. If the above example was called from an action in UserController
, then the user would be sent to http://localhost:8080/teamwork/user/save
. Finally, you can specify an id
parameter, which will become a part of the URL. You can also specify any number of additional parameters as a Map
will be converted into parameters on the query string of the URL:
redirect( controller: "user", action:"show", id:2, params: [order: "desc"] )
Now the user will be redirected to: http://localhost:8080/teamwork/user/show/2?order=desc
.
Going back to the application:
redirect( action: 'create' )
This will redirect the user to the Post Message screen once the current message has been created.
Render validation errors
If there are problems with the message, then the validation will fail when the save
method is called. You will need to show the information back to the user so that they can correct it.
render( view: 'create', model:[message: message] )
Here, you are telling Grails to render the create
GSP with the message object that failed the validation.
The render
method is another dynamic method that Grails adds on to all the controllers. It provides a concise syntax to determine what should be used to render information for the user. Here are some examples:
render 'A basic bit of text to be displayed:
This will render some basic text to the response.render(view: 'staticView'):
This will render a GSP calledstaticView
under the directory for the current controller. No model has been passed to the view. So only data on the default scopes will be available to the page.render(view: 'create', model: [message: message]):
From your example, thecreate
GSP will be rendered with the message object available for rendering.render(template: 'simpleTemplate', model: [data: myData]):
This will render atemplate
GSP file with the data provided in the model.
Feedback to the user
Now that the users can create messages, you need to change the form to give some feedback so that the user knows what is going on. Modify message/create.gsp
to show the success message that is added to flash scope if the message is created. In order to show the validation errors that may have stopped a message being created, you can use the following code:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head> ... </head>
<body>
<g:if test="${flash.toUser}"> <div id="userMessage" class="info"> ${flash.toUser} </div> </g:if> <g:hasErrors bean="${message}"> <div class="validationerror"> <g:renderErrors bean="${message}" as="list"/> </div> </g:hasErrors>
<g:form action="save">
...
</g:form>
</body>
</html>
The section of highlighted code checks to see if there is a message for the user in flash scope. If so, the message is displayed. Remember that a userMessage
is added to flash scope when a message is successfully saved by the save
action on MessageController
.
The second section renders a list of the validation errors that occurred when the form was last submitted. The g:hasErrors
tag checks whether the errors
property for the specified object contains any errors. The g:renderErrors
tag renders the errors associated with the bean, using a default message for each error as an unordered list.
You should now be able to go back to the application and create messages as shown in the screenshot:
In addition, validation of user input should also be working:
You have now implemented the first part of one of your user goals — being able to create messages that can be shared with other users of the application. You have managed it with two very simple classes and another equally simple GSP. The next step is to allow all the messages that have been created to be viewed.