Grails 1.1 Web Application Development
上QQ阅读APP看书,第一时间看更新

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:

  1. Convert the data from an HTTP request into your domain model
  2. Validate whether the information entered is acceptable for the application
  3. If there are problems with the information, stop executing and let the user know what the problems were
  4. If the data is acceptable, persist the data to the database
  5. 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 spring Errors 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 the save 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 called staticView 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, the create GSP will be rendered with the message object available for rendering.
  • render(template: 'simpleTemplate', model: [data: myData]): This will render a template GSP file with the data provided in the model.

    Tip

    Templates are used to contain re-usable sections of view rendering logic. You will cover them in more detail as the application grows to a size where it is necessary to remove duplicated presentation code.

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:

Feedback to the user

In addition, validation of user input should also be working:

Feedback to the user

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.