class Mechanize::Form

This class encapsulates a form parsed out of an HTML page. Each type of input field available in a form can be accessed through this object.

Examples

Find a form and print out its fields

form = page.forms.first # => Mechanize::Form
form.fields.each { |f| puts f.name }

Set the input field 'name' to “Aaron”

form['name'] = 'Aaron'
puts form['name']

Constants

CRLF

Attributes

action[RW]
buttons[R]
checkboxes[R]
elements[R]
encoding[RW]

Character encoding of form data (i.e. UTF-8)

enctype[RW]

Content-Type for form data (i.e. application/x-www-form-urlencoded)

fields[R]
file_uploads[R]
form_node[R]
ignore_encoding_error[RW]

When true, character encoding errors will never be never raised on form submission. Default is false

method[RW]
name[RW]
node[R]
page[R]
radiobuttons[R]

Public Class Methods

new(node, mech = nil, page = nil) click to toggle source
# File lib/mechanize/form.rb, line 42
def initialize(node, mech = nil, page = nil)
  @enctype = node['enctype'] || 'application/x-www-form-urlencoded'
  @node             = node
  @action           = Mechanize::Util.html_unescape(node['action'])
  @method           = (node['method'] || 'GET').upcase
  @name             = node['name']
  @clicked_buttons  = []
  @page             = page
  @mech             = mech

  @encoding = node['accept-charset'] || (page && page.encoding) || nil
  @ignore_encoding_error = false
  parse
end

Public Instance Methods

[](field_name) click to toggle source

Fetch the value of the first input field with the name passed in. Example:

puts form['name']
# File lib/mechanize/form.rb, line 240
def [](field_name)
  f = field(field_name)
  f && f.value
end
[]=(field_name, value) click to toggle source

Set the value of the first input field with the name passed in. Example:

form['name'] = 'Aaron'
# File lib/mechanize/form.rb, line 247
def []=(field_name, value)
  f = field(field_name)
  if f
    f.value = value
  else
    add_field!(field_name, value)
  end
end
add_button_to_query(button) click to toggle source

This method adds a button to the query. If the form needs to be submitted with multiple buttons, pass each button to this method.

# File lib/mechanize/form.rb, line 369
def add_button_to_query(button)
  unless button.node.document == @node.document then
    message =
      "#{button.inspect} does not belong to the same page as " \
      "the form #{@name.inspect} in #{@page.uri}"

    raise ArgumentError, message
  end

  @clicked_buttons << button
end
add_field!(field_name, value = nil) click to toggle source

Add a field with field_name and value

# File lib/mechanize/form.rb, line 200
def add_field!(field_name, value = nil)
  fields << Field.new({'name' => field_name}, value)
end
at() click to toggle source

Shorthand for node.at.

See also Nokogiri::XML::Node#at for details.

# File lib/mechanize/form.rb, line 183
  
at_css() click to toggle source

Shorthand for node.at_css.

See also Nokogiri::XML::Node#at_css for details.

# File lib/mechanize/form.rb, line 190
  
at_xpath() click to toggle source

Shorthand for node.at_xpath.

See also Nokogiri::XML::Node#at_xpath for details.

# File lib/mechanize/form.rb, line 197
def_delegators :node, :search, :css, :xpath, :at, :at_css, :at_xpath
build_query(buttons = []) click to toggle source

This method builds an array of arrays that represent the query parameters to be used with this form. The return value can then be used to create a query string for this form.

# File lib/mechanize/form.rb, line 299
def build_query(buttons = [])
  query = []
  @mech.log.info("form encoding: #{encoding}") if @mech && @mech.log

  save_hash_field_order

  successful_controls = []

  (fields + checkboxes).reject do |f|
    f.node["disabled"]
  end.sort.each do |f|
    case f
    when Mechanize::Form::CheckBox
      if f.checked
        successful_controls << f
      end
    when Mechanize::Form::Field
      successful_controls << f
    end
  end

  radio_groups = {}
  radiobuttons.each do |f|
    fname = from_native_charset(f.name)
    radio_groups[fname] ||= []
    radio_groups[fname] << f
  end

  # take one radio button from each group
  radio_groups.each_value do |g|
    checked = g.select(&:checked)

    if checked.uniq.size > 1 then
      values = checked.map(&:value).join(', ').inspect
      name = checked.first.name.inspect
      raise Mechanize::Error,
            "radiobuttons #{values} are checked in the #{name} group, " \
            "only one is allowed"
    else
      successful_controls << checked.first unless checked.empty?
    end
  end

  @clicked_buttons.each { |b|
    successful_controls << b
  }

  successful_controls.sort.each do |ctrl| # DOM order
    qval = proc_query(ctrl)
    query.push(*qval)
  end

  query
end
button_with!(criteria)() click to toggle source

Same as button_with but raises an ElementNotFoundError if no button matches criteria

# File lib/mechanize/form.rb, line 465
  
button_with(criteria)() click to toggle source

Find one button that matches criteria Example:

form.button_with(:value => /submit/).value = 'hello'
# File lib/mechanize/form.rb, line 459
  
buttons_with(criteria)() click to toggle source

Find all buttons that match criteria Example:

form.buttons_with(:value => /submit/).each do |button|
  button.value = 'hello!'
end
# File lib/mechanize/form.rb, line 474
elements_with :button
checkbox_with(criteria)() click to toggle source

Find one checkbox that matches criteria Example:

form.checkbox_with(:name => /woo/).check
# File lib/mechanize/form.rb, line 531
  
checkboxes_with(criteria)() click to toggle source

Find all checkboxes that match criteria Example:

form.checkboxes_with(:name => /woo/).each do |field|
  field.check
end
# File lib/mechanize/form.rb, line 546
elements_with :checkbox,   :checkboxes
click_button(button = buttons.first) click to toggle source

Submit form using button. Defaults to the first button.

# File lib/mechanize/form.rb, line 276
def click_button(button = buttons.first)
  submit(button)
end
css() click to toggle source

Shorthand for node.css.

See also Nokogiri::XML::Node#css for details.

# File lib/mechanize/form.rb, line 169
  
delete_field!(field_name) click to toggle source

Removes all fields with name field_name.

# File lib/mechanize/form.rb, line 424
def delete_field!(field_name)
  @fields.delete_if{ |f| f.name == field_name}
end
dom_class() click to toggle source

This method is a shortcut to get form's DOM class. Common usage:

page.form_with(:dom_class => "foorm")

Note that you can also use :class to get to this method:

page.form_with(:class => "foorm")

However, attribute values are compared literally as string, so form_with(class: “a”) does not match a form with class=“a b”. Use form_with(css: “form.a”) instead.

# File lib/mechanize/form.rb, line 151
def dom_class
  @node['class']
end
dom_id() click to toggle source

This method is a shortcut to get form's DOM id. Common usage:

page.form_with(:dom_id => "foorm")

Note that you can also use :id to get to this method:

page.form_with(:id => "foorm")
# File lib/mechanize/form.rb, line 139
def dom_id
  @node['id']
end
field_with!(criteria)() click to toggle source

Same as field_with but raises an ElementNotFoundError if no field matches criteria

# File lib/mechanize/form.rb, line 441
  
field_with(criteria)() click to toggle source

Find one field that matches criteria Example:

form.field_with(:id => "exact_field_id").value = 'hello'
# File lib/mechanize/form.rb, line 435
  
fields_with(criteria)() click to toggle source

Find all fields that match criteria Example:

form.fields_with(:value => /foo/).each do |field|
  field.value = 'hello!'
end
# File lib/mechanize/form.rb, line 450
elements_with :field
file_upload_with(criteria)() click to toggle source

Find one file upload field that matches criteria Example:

form.file_upload_with(:file_name => /picture/).value = 'foo'
# File lib/mechanize/form.rb, line 483
  
file_uploads_with(criteria)() click to toggle source

Find all file upload fields that match criteria Example:

form.file_uploads_with(:file_name => /picutre/).each do |field|
  field.value = 'foo!'
end
# File lib/mechanize/form.rb, line 498
elements_with :file_upload
has_field?(field_name) click to toggle source

Returns whether or not the form contains a field with field_name

# File lib/mechanize/form.rb, line 58
def has_field?(field_name)
  fields.any? { |f| f.name == field_name }
end
Also aliased as: has_key?
has_key?(field_name)
Alias for: has_field?
has_value?(value) click to toggle source

Returns whether or not the form contains a field with value

# File lib/mechanize/form.rb, line 65
def has_value?(value)
  fields.any? { |f| f.value == value }
end
hidden_field?(field_name) click to toggle source

Returns whether or not the form contains a Hidden field named field_name

# File lib/mechanize/form.rb, line 125
def hidden_field?(field_name)
  hiddens.find { |f| f.name == field_name }
end
hiddens() click to toggle source

Returns all fields of type Hidden

# File lib/mechanize/form.rb, line 95
def hiddens
  @hiddens ||= fields.select { |f| f.class == Hidden }
end
keygens() click to toggle source

Returns all fields of type Keygen

# File lib/mechanize/form.rb, line 105
def keygens
  @keygens ||= fields.select { |f| f.class == Keygen }
end
keys() click to toggle source

Returns all field names (keys) for this form

# File lib/mechanize/form.rb, line 70
def keys
  fields.map(&:name)
end
method_missing(meth, *args) click to toggle source

Treat form fields like accessors.

Calls superclass method
# File lib/mechanize/form.rb, line 257
def method_missing(meth, *args)
  (method = meth.to_s).chomp!('=')

  if field(method)
    return field(method).value if args.empty?
    return field(method).value = args[0]
  end

  super
end
radiobutton_with(criteria)() click to toggle source

Find one radio button that matches criteria Example:

form.radiobutton_with(:name => /woo/).check
# File lib/mechanize/form.rb, line 507
  
radiobuttons_with(criteria)() click to toggle source

Find all radio buttons that match criteria Example:

form.radiobuttons_with(:name => /woo/).each do |field|
  field.check
end
# File lib/mechanize/form.rb, line 522
elements_with :radiobutton
request_data() click to toggle source

This method calculates the request data to be sent back to the server for this form, depending on if this is a regular post, get, or a multi-part post,

# File lib/mechanize/form.rb, line 393
def request_data
  query_params = build_query()

  case @enctype.downcase
  when /^multipart\/form-data/
    boundary = rand_string(20)
    @enctype = "multipart/form-data; boundary=#{boundary}"

    delimiter = "--#{boundary}\r\n"

    data = ::String.new

    query_params.each do |k,v|
      if k
        data << delimiter
        param_to_multipart(k, v, data)
      end
    end

    @file_uploads.each do |f|
      data << delimiter
      file_to_multipart(f, data)
    end

    data << "--#{boundary}--\r\n"
  else
    Mechanize::Util.build_query_string(query_params)
  end
end
reset() click to toggle source

This method allows the same form to be submitted second time with the different submit button being clicked.

# File lib/mechanize/form.rb, line 383
def reset
  # In the future, should add more functionality here to reset the form values to their defaults.
  @clicked_buttons = []
end
reset_button?(button_name) click to toggle source

Returns whether or not the form contains a Reset button named button_name

# File lib/mechanize/form.rb, line 115
def reset_button?(button_name)
  resets.find { |f| f.name == button_name }
end
resets() click to toggle source

Returns all buttons of type Reset

# File lib/mechanize/form.rb, line 85
def resets
  @resets ||= buttons.select { |f| f.class == Reset }
end
save_hash_field_order() click to toggle source

This method adds an index to all fields that have Hash nodes. This enables field sorting to maintain order.

# File lib/mechanize/form.rb, line 356
def save_hash_field_order
  index = 0

  fields.each do |field|
    if Hash === field.node
      field.index = index
      index += 1
    end
  end
end
set_fields(fields = {}) click to toggle source

This method sets multiple fields on the form. It takes a list of fields which are name, value pairs.

If there is more than one field found with the same name, this method will set the first one found. If you want to set the value of a duplicate field, use a value which is a Hash with the key as the index in to the form. The index is zero based.

For example, to set the second field named 'foo', you could do the following:

form.set_fields :foo => { 1 => 'bar' }
# File lib/mechanize/form.rb, line 217
def set_fields fields = {}
  fields.each do |name, v|
    case v
    when Hash
      v.each do |index, value|
        self.fields_with(:name => name.to_s)[index].value = value
      end
    else
      value = nil
      index = 0

      [v].flatten.each do |val|
        index = val.to_i if value
        value = val unless value
      end

      self.fields_with(:name => name.to_s)[index].value = value
    end
  end
end
submit(button = nil, headers = {}) click to toggle source

Submit the form. Does not include the button as a form parameter. Use click_button or provide button as a parameter.

# File lib/mechanize/form.rb, line 270
def submit button = nil, headers = {}
  @mech.submit(self, button, headers)
end
submit_button?(button_name) click to toggle source

Returns whether or not the form contains a Submit button named button_name

# File lib/mechanize/form.rb, line 110
def submit_button?(button_name)
  submits.find { |f| f.name == button_name }
end
submits() click to toggle source

Returns all buttons of type Submit

# File lib/mechanize/form.rb, line 80
def submits
  @submits ||= buttons.select { |f| f.class == Submit }
end
text_field?(field_name) click to toggle source

Returns whether or not the form contains a Text field named field_name

# File lib/mechanize/form.rb, line 120
def text_field?(field_name)
  texts.find { |f| f.name == field_name }
end
textarea_field?(field_name) click to toggle source

Returns whether or not the form contains a Textarea named field_name

# File lib/mechanize/form.rb, line 130
def textarea_field?(field_name)
  textareas.find { |f| f.name == field_name }
end
textareas() click to toggle source

Returns all fields of type Textarea

# File lib/mechanize/form.rb, line 100
def textareas
  @textareas ||= fields.select { |f| f.class == Textarea }
end
texts() click to toggle source

Returns all fields of type Text

# File lib/mechanize/form.rb, line 90
def texts
  @texts ||= fields.select { |f| f.class == Text }
end
values() click to toggle source

Returns all field values for this form

# File lib/mechanize/form.rb, line 75
def values
  fields.map(&:value)
end
xpath() click to toggle source

Shorthand for node.xpath.

See also Nokogiri::XML::Node#xpath for details.

# File lib/mechanize/form.rb, line 176
  

Private Instance Methods

file_to_multipart(file, buf = ::String.new) click to toggle source
# File lib/mechanize/form.rb, line 675
def file_to_multipart(file, buf = ::String.new)
  file_name = file.file_name ? ::File.basename(file.file_name) : ''

  body = buf <<
         "Content-Disposition: form-data; name=\"".freeze <<
         mime_value_quote(file.name) <<
         "\"; filename=\"".freeze <<
         mime_value_quote(file_name) <<
         "\"\r\nContent-Transfer-Encoding: binary\r\n".freeze

  if file.file_data.nil? and file.file_name
    file.file_data = File.binread(file.file_name)
    file.mime_type =
      WEBrick::HTTPUtils.mime_type(file.file_name,
                                   WEBrick::HTTPUtils::DefaultMimeTypes)
  end

  if file.mime_type
    body << "Content-Type: ".freeze << file.mime_type << CRLF
  end

  body << CRLF

  if file_data = file.file_data
    if file_data.respond_to? :read
      body << file_data.read.force_encoding(Encoding::ASCII_8BIT)
    else
      body << file_data.b
    end
  end

  body << CRLF
end
from_native_charset(str) click to toggle source
# File lib/mechanize/form.rb, line 290
def from_native_charset str
  Mechanize::Util.from_native_charset(str, encoding, @ignore_encoding_error,
                                      @mech && @mech.log)
end
mime_value_quote(str) click to toggle source
# File lib/mechanize/form.rb, line 662
def mime_value_quote(str)
  str.b.gsub(/(["\r\\])/, '\\\\\1')
end
param_to_multipart(name, value, buf = ::String.new) click to toggle source
# File lib/mechanize/form.rb, line 666
def param_to_multipart(name, value, buf = ::String.new)
  buf <<
    "Content-Disposition: form-data; name=\"".freeze <<
    mime_value_quote(name) <<
    "\"\r\n\r\n".freeze <<
    value.b <<
    CRLF
end
parse() click to toggle source
# File lib/mechanize/form.rb, line 578
def parse
  @fields       = []
  @buttons      = []
  @file_uploads = []
  @radiobuttons = []
  @checkboxes   = []

  # Find all input tags
  @node.search('input').each do |node|
    type = (node['type'] || 'text').downcase
    name = node['name']
    next if name.nil? && !%w[submit button image].include?(type)
    case type
    when 'radio'
      @radiobuttons << RadioButton.new(node, self)
    when 'checkbox'
      @checkboxes << CheckBox.new(node, self)
    when 'file'
      @file_uploads << FileUpload.new(node, nil)
    when 'submit'
      @buttons << Submit.new(node)
    when 'button'
      @buttons << Button.new(node)
    when 'reset'
      @buttons << Reset.new(node)
    when 'image'
      @buttons << ImageButton.new(node)
    when 'hidden'
      @fields << Hidden.new(node, node['value'] || '')
    when 'text'
      @fields << Text.new(node, node['value'] || '')
    when 'textarea'
      @fields << Textarea.new(node, node['value'] || '')
    else
      @fields << Field.new(node, node['value'] || '')
    end
  end

  # Find all textarea tags
  @node.search('textarea').each do |node|
    next unless node['name']
    @fields << Textarea.new(node, node.inner_text)
  end

  # Find all select tags
  @node.search('select').each do |node|
    next unless node['name']
    if node.has_attribute? 'multiple'
      @fields << MultiSelectList.new(node)
    else
      @fields << SelectList.new(node)
    end
  end

  # Find all submit button tags
  # FIXME: what can I do with the reset buttons?
  @node.search('button').each do |node|
    type = (node['type'] || 'submit').downcase
    next if type == 'reset'
    @buttons << Button.new(node)
  end

  # Find all keygen tags
  @node.search('keygen').each do |node|
    @fields << Keygen.new(node, node['value'] || '')
  end
end
proc_query(field) click to toggle source

This method is sub-method of build_query. It converts charset of query value of fields into expected one.

# File lib/mechanize/form.rb, line 282
def proc_query(field)
  return unless field.query_value
  field.query_value.map{|(name, val)|
    [from_native_charset(name), from_native_charset(val.to_s)]
  }
end
rand_string(len = 10) click to toggle source
# File lib/mechanize/form.rb, line 655
def rand_string(len = 10)
  chars = ("a".."z").to_a + ("A".."Z").to_a
  string = ::String.new
  1.upto(len) { |i| string << chars[rand(chars.size-1)] }
  string
end