Building HTML Forms
Struts includes a page construction tag library to build HTML forms. In our sample application, page construction tags are prefixed with html. Here is the Struts code to render the input elements for a person's first and last names:
Download code/appfuse_people/web/pages/personForm.jsp
<label for="firstName" class= "desc">First Name </label> <html:errors property="firstName"/> <html:text property="firstName" styleId= "firstName" styleClass="text medium"/>
<label for="lastName" class= "desc">Last Name </label> <html:errors property="lastName"/> <html:text property="lastName" styleId="lastName" styleClass="text medium"/>
You should notice several points here. First, there is a seamless back-and-forth between custom tags and plain HTML. The label tags are HTML, but the html-prefixed tags invoke custom code. The html:errors tag looks up error messages on a named form property. The html:text tag generates an HTML input type="text". The styleId and styleClass attributes become id and class attributes on the generated input elements.
The Rails scaffold code for editing the properties of a person lives at app/views/people/_form.rhtml:
Download code/people/app/views/people/Jorm.rhtml
<p><label for="person_first_name">First name</label><br/> <%= text_field 'person', 'first_name' %><%= error_message_on 'person', 'first_name' %></p>
<p><label for="person_last_name">Last name</label><br/> <%= text_field 'person', 'last_name' %><%= error_message_on 'person', 'last_name' %></p>
Where the Struts version used page construction tags, the Rails version uses built-in form helpers. The error_message_on method emits validation errors. The text_field method takes two arguments here. Rails combines the two arguments to create a parameter name, so text_field 'person', 'first_name' becomes a parameter named person[first_name]. When the parameters are submitted to the server, Rails automatically converts parameters named in this fashion into a hash. This is convenient, since ActiveRecord constructors take a hash argument.
The Struts form validation has two features absent in Rails:
- Validations can be checked with client-side JavaScript.
- Error messages are internationalized.
If you need these features in your Rails application, you will have to find a plugin or roll your own. We have found that Rails has advantages in other areas that outweigh these deficiencies, but it is important to be aware of them when estimating development effort for a project. Rails includes form helper methods for every kind of HTML form input. Here are some examples of form helpers:
Download code/rails_xt/app/views/examples/forms.rhtml
<h2>Form Fields Bound To Query Params</h2>
<td><label for="sample_text">Text</label></td> <td><%= text_field isample, :text %></td> </tr> <tr>
<td><label for="sample_password">Passsword</label></td>
<td><%= password_field isample, ipassword, {:style=>'color:blue;'} %></td> </tr> <tr>
<td><label for="sample_area">Text Area</label></td> <td><%= text_area isample, :area, {:size=>'10x10'} %></td> </tr> </table>
On line 6, you see the two-argument version of a form tag helper. The first argument is an instance variable name, and the second argument is the name of an accessor on that instance. Rails will populate the initial value from the instance variable and will convert the query string arguments into a hash named for the instance variable. On line 10, you see the three-argument version. The third argument is a hash that is converted into attributes on the tag. Here we set a CSS style.
On line 14, you see another three argument helper. This time, however, the third argument is not an HTML attribute. The argument is size=>'10x10', but a proper textarea expects rows and cols. Here Rails has provided a shortcut. You can specify separate rows and cols if you want, but Rails has special-cased the options handling for text_area_tag to convert size arguments into rows and cols. As you are writing view code, look out for helpers such as these that can simplify common tasks.
Rails form helpers are validation aware. If a model object has validation errors, the Rails form helpers will put form tags for invalid data inside a <div class="fieldWithErrors">. You can then use CSS styles to call attention to errors. (The Rails scaffold adds a red border). Several other form helpers are worth mentioning, and we'll get to them shortly. First, we have to do something about all the ugly HTML code we just showed you. Each one of those form fields looks the same: Each is wrapped in tr and td, and each is preceded by a label. The HTML around each field is extremely repetitive. Surely there must be a better way!
Rails provides the form_for helper to reduce the repetitiveness of form code. The previous form looks like this if you use form_for:
Download code/rails_xt/app/views/examples/form_for.rhtml
<h2>Form Fields Bound To Query Params</h2>
<tr><td><label for="sample_text">Text</label></td> <td><%= f.text_field :text %></td></tr>
<tr><td><label for="sample_password">Password</label></td>
<td><%= f.password_field :password, {:style=>'color:blue;'} %></td></tr>
<tr><td><label for="sample_area">Text Area</label></td>
<td><%= f.text_area :area, {:size=>'10x10'} %></td></tr>
Notice that form_for uses an ERb evaluation block (<% %>) instead of an ERb output block (<%= %>). form_for takes a block parameter, which is the form object. In the block, you can use the block parameter and omit the first argument to each field helper; for example, you'd use f.text_field instead of text_field :sample.
That is not much of an improvement so far. The real power of form_for comes in conjunction with form builders. A form builder gets to override how each form element is built. Here is a custom form builder that automatically generates all the tr, td, and label goo around our HTML fields:
Download code/rails_xt/app/helpers/tabular_form_builder.rb
class TabularFormBuilder < ActionView::Helpers::FormBuilder (field_helpers - %w(hidden_field)).each do |selector| src = <<-END_SRC
def #{selector}(field, *args, &proc) "<tr>" +
"<td><label for='\#{field}'>\#{field.to_s.humanize}:</label></td>" + "<td>" + super + "</td>" + "</tr>" end END_SRC
class_eval src, __FILE__, __LINE__ end def submit_tag(value) "<tr>" +
"<td><input name='commit' type='submit' value='#{value}'/></td>"+ "</tr>" end end
This code looks a bit tricky, but what it does is not that complex. The call to field_helpers returns all the field helper names. Then, the src string creates a new definition for each method that includes the formatting we want. Finally, submit_tag is special-cased because we do not want a label for submit tags.
To use our custom form builder, we need to pass a named option to the third parameter for form_for. We will capture this in a new helper method named tabular_form_for:
Download code/rails_xt/app/helpers/application_helper.rb
require 'tabular_form_builder' module ApplicationHelper def tabular_form_for(name, object, options, &proc) concat("<table>", proc.binding)
form_for(name, object, options.merge(:builder => TabularFormBuilder), &proc) concat("</table>", proc.binding) end end
The calls to concat are the ERb version of puts, and the call to form_for sets the :builder to be our TabularFormBuilder.
Using the TabularFormBuilder, our form code simplifies to the following:
Download code/rails_xt/app/views/examples/tabular_form_for.rhtml
<h2>Tabular Form For</h2>
<%= f.password_field :password, {:style=>'color:blue;'} %> <%= f.text_area :area, {:size=>'10x10'} %>
That is an enormous improvement in readability. And although the FormBuilder code looks tricky, you do not have to start from scratch. We pasted the src block from the FormBuilder base class and tweaked it until we liked the results.
Sometimes you want a form that is not associated with a model object. Methods such as text_field assume an associated model object, but Rails has a second set of methods that do not scope query parameters to a particular object. Instead of text_field, you use text_field_for. Similarly named methods exist for all the other form input types.
Post a comment