Forms

Lecture Notes for CS 142
Fall 2010
John Ousterhout

  • Readings for this topic:
    • Chapter 20 of Agile Web Development with Rails, plus Sections 23.4-23.8.

Form basics

  • HTML mechanism for user input
    • A collection of HTML elements
    • A protocol for communicating data to the server
  • Form:
    • Collection of elements
    • Each element has a name and a value (strings)
    • Different types of elements provide different ways for the user to edit the value (text entry, checkbox, menu, etc.)
  • Simple form example:
    <form action="/x/y/z" method="POST">
      Value1: <input type="text" name="value1"/><br />
      Value2: <input type="text" name="value2" value="47"/><br />
      <input type="submit" value="Submit"/>
    </form>
    
    • <form> element: overall container for form elements
      • action specifies a URL to invoke when the form is submitted.
      • method attribute indicates which HTTP method to use when communicating with the server; defaults to GET but should always be POST (GET limits size of posted data)
      • Arbitrary HTML OK inside <form>
      • Can have more than one form in a page
    • <input> elements: controls for entering data.
      • type attribute specifies which of several controls to use. text is a simple one-line text entry.
      • name attribute: used to identify this particular value when posting form to server.
      • value attribute specifies initial value for this element.
    • <input type="submit"> creates a button for submitting the form.
      • value attribute specifies text to display in button.
  • Other form elements: see formElements.html for examples.
  • When the submit button is clicked:
    • HTTP POST request is made to the URL given by the action attribute of the <form> element.
    • Body of the request contains name-value pairs from the <input> elements, URL-encoded (looks like query data in a URL).
    • Result is an HTML page that replaces the form page.
  • On the server side:
    • Like any other HTTP request.
    • Rails makes the form data available through the params hash.

Form flow

  • Problem #1:
    • Consider an ordering system:
      • One or more pages of forms
      • Final form to confirm order
      • "Thank you for your order" page
    • Use Back button to return to the thank-you page
  • Solution:
    • One URL displays form
    • POST goes to a second URL
    • Redirect after POST:
      • POST page doesn't generate HTML
      • After redirection, POST URL disappears from browser history list
  • Divide URLs:
    • Those that display information (including forms) (GET method)
    • Those that invoke modifications (POST method)
    • Don't mix these two
  • Problem #2:
    • Invalid form data detected by server during POST
    • Desired effect:
      • Redisplay the form page
      • Display error messages about the problems (ideally, display messages next to the offending form elements)
      • Retain all of the data that the user entered in the form

Rails form support

  • Ties in nicely to the ORM system
    • One model object holds one database record
    • Form used to edit part or all of a record
    • Form helpers: methods to generate HTML elements for forms: you don't have to write HTML directly.
    • Validation: mechanism for detecting errors in input data.
  • Form helpers example (student record):
    • Controller:
      @student = Student.find(params[:id])
      
    • View:
      <% form_for(:student, :url => {:action => :modify,
          :id => @student.id}) do |form| %>
        <table class="form">
          <tr>
            <td class="label">Name:<td>
            <td><%= form.text_field(:name) %><td>
          </tr>
          <tr>
            <td class="label">Date of birth:<td>
            <td><%= form.text_field(:birth) %><td>
          </tr>
          <tr>
            <td class="label">Grade-point average:<td>
            <td><%= form.text_field(:gpa) %><td>
          </tr>
          <tr>
            <td class="label">Graduation year:<td>
            <td><%= form.text_field(:grad) %><td>
          </tr>
        <table>
        <%= submit_tag "Modify Student" %>
      <% end %>
      
    • :student argument to form_for: identifies both the model class and the name of a variable containing data
    • :url argument to form_for: URL where form will get posted
    • form.text_field: returns an HTML input element of type text, provides initial value from corresponding attribute of @student:
      <input id="student_name" name="student[name]" size="30"
              type="text" value="Wendy Wilson" />
      
  • Handling post:
    def modify
      @student = Student.find(params[:id])
      if @student.update_attributes(params[:student]) then
        redirect_to(:action => :show)
      else
        render(:action => :edit)
      end
    end
    
    • Form data automatically available in params: nested hash named after the form.
    • update_attributes copies values from the form data into the model object and saves the model object.
  • Error handling in Rails forms:
    • Validation:
      class Student < ActiveRecord::Base
        def validate
          if (gpa < 0) || (gpa > 4.0) then
            errors.add(:gpa, "must be between 0.0 and 4.0")
          end
        end
        
        validates_format_of :birth, :with=> /\d\d\d\d-\d\d-\d\d/,
            :message => "must have format YYYY-MM-DD"
      end
      
      • Validation methods get invoked whenever the object is saved.
      • errors.add saves an error message related to a particular attribute of the object.
      • Several built-in validators, such as validates_format_of.
      • If there is a validation error, methods such as save and update_attributes abort and return false.
    • After a validation error, the action method explicitly re-renders the form.
    • Form helpers:
      • error_messages_for generates HTML for all the error messages for an object
      • form.text_field and similar methods add an extra <div class="fieldWithErrors"> around any form elements for which there are error messages: use CSS to display differently.

File uploads with Rails

  • If you use <input type="file">, then you must request a different protocol for posting data:
    <form method="POST" enctype="multipart/form-data">
    
  • Rails knows how to handle this format (some frameworks don't):
    • Normal form data available in the normal way
    • Siphons off uploaded files to files on disk; the params value for this form element is an object with lots of methods.
  • In Rails, add an extra argument to the form_for method:
    form_for(... :url => {...}, :html => {:multipart => true})
    
  • Form helper to generate form element:
    form.file_field :image
    
  • read method: returns contents of uploaded file.
  • original_filename method: returns file name as selected in the browser.