class Formtastic::SemanticFormBuilder

Constants

INLINE_ERROR_TYPES
RESERVED_COLUMNS

Attributes

template[RW]

Public Instance Methods

buttons(*args, &block) click to toggle source

Creates a fieldset and ol tag wrapping for form buttons / actions as list items. See inputs documentation for a full example. The fieldset's default class attriute is set to “buttons”.

See inputs for html attributes and special options.

# File lib/formtastic.rb, line 313
def buttons(*args, &block)
  html_options = args.extract_options!
  html_options[:class] ||= "buttons"

  if block_given?
    field_set_and_list_wrapping(html_options, &block)
  else
    args = [:commit] if args.empty?
    contents = args.map { |button_name| send(:"#{button_name}_button") }
    field_set_and_list_wrapping(html_options, contents)
  end
end
commit_button(*args) click to toggle source

Creates a submit input tag with the value “Save [model name]” (for existing records) or “Create [model name]” (for new records) by default:

<%= form.commit_button %> => <input name="commit" type="submit" value="Save Post" />

The value of the button text can be overridden:

<%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
<%= form.commit_button :label => "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />

And you can pass html atributes down to the input, with or without the button text:

<%= form.commit_button :button_html => { :class => "pretty" } %> => <input name="commit" type="submit" value="Save Post" class="pretty {create|update|submit}" />
# File lib/formtastic.rb, line 339
def commit_button(*args)
  options = args.extract_options!
  text = options.delete(:label) || args.shift

  if @object && (@object.respond_to?(:persisted?) || @object.respond_to?(:new_record?))
    if @object.respond_to?(:persisted?) # ActiveModel
      key = @object.persisted? ? :update : :create
    else # Rails 2
      key = @object.new_record? ? :create : :update
    end

    # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
    # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
    # if there's no i18n, which is pretty crappy.  In this circumstance we want to detect this
    # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
    if @object.class.model_name.respond_to?(:human)
      object_name = @object.class.model_name.human
    else
      object_human_name = @object.class.human_name                # default is UserPost => "Userpost", but i18n may do better ("User post")
      crappy_human_name = @object.class.name.humanize             # UserPost => "Userpost"
      decent_human_name = @object.class.name.underscore.humanize  # UserPost => "User post"
      object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
    end
  else
    key = :submit
    object_name = @object_name.to_s.send(label_str_method)
  end

  text = (localized_string(key, text, :action, :model => object_name) ||
          ::Formtastic::I18n.t(key, :model => object_name)) unless text.is_a?(::String)

  button_html = options.delete(:button_html) || {}
  button_html.merge!(:class => [button_html[:class], key].compact.join(' '))

  wrapper_html_class = ['commit'] # TODO: Add class reflecting on form action.
  wrapper_html = options.delete(:wrapper_html) || {}
  wrapper_html[:class] = (wrapper_html_class << wrapper_html[:class]).flatten.compact.join(' ')

  accesskey = (options.delete(:accesskey) || default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
  button_html = button_html.merge(:accesskey => accesskey) if accesskey
  template.content_tag(:li, Formtastic::Util.html_safe(submit(text, button_html)), wrapper_html)
end
input(method, options = {}) click to toggle source

Returns a suitable form input for the given method, using the database column information and other factors (like the method name) to figure out what you probably want.

Options:

  • :as - override the input type (eg force a :string to render as a :password field)

  • :label - use something other than the method name as the label text, when false no label is printed

  • :required - specify if the column is required (true) or not (false)

  • :hint - provide some text to hint or help the user provide the correct information for a field

  • :input_html - provide options that will be passed down to the generated input

  • :wrapper_html - provide options that will be passed down to the li wrapper

Input Types:

Most inputs map directly to one of ActiveRecord's column types by default (eg #string_input), but there are a few special cases and some simplification (:integer, :float and :decimal columns all map to a single #numeric_input, for example).

  • :select (a select menu for associations) - default to association names

  • :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations

  • :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations

  • :time_zone (a select menu with time zones)

  • :password (a password input) - default for :string column types with 'password' in the method name

  • :text (a textarea) - default for :text column types

  • :date (a date select) - default for :date column types

  • :datetime (a date and time select) - default for :datetime and :timestamp column types

  • :time (a time select) - default for :time column types

  • :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)

  • :string (a text field) - default for :string column types

  • :numeric (a text field, like string) - default for :integer, :float and :decimal column types

  • :email (an email input) - default for :string column types with 'email' as the method name.

  • :url (a url input) - default for :string column types with 'url' as the method name.

  • :phone (a tel input) - default for :string column types with 'phone' or 'fax' in the method name.

  • :search (a search input) - default for :string column types with 'search' as the method name.

  • :country (a select menu of country names) - requires a country_select plugin to be installed

  • :email (an email input) - New in HTML5 - needs to be explicitly provided with :as => :email

  • :url (a url input) - New in HTML5 - needs to be explicitly provided with :as => :url

  • :phone (a tel input) - New in HTML5 - needs to be explicitly provided with :as => :phone

  • :search (a search input) - New in HTML5 - needs to be explicity provided with :as => :search

  • :country (a select menu of country names) - requires a country_select plugin to be installed

  • :hidden (a hidden field) - creates a hidden field (added for compatibility)

Example:

<% semantic_form_for @employee do |form| %>
  <% form.inputs do -%>
    <%= form.input :name, :label => "Full Name" %>
    <%= form.input :manager, :as => :radio %>
    <%= form.input :secret, :as => :password, :input_html => { :value => "xxxx" } %>
    <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
    <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
    <%= form.input :email %>
    <%= form.input :website, :as => :url, :hint => "You may wish to omit the http://" %>
  <% end %>
<% end %>
# File lib/formtastic.rb, line 109
def input(method, options = {})
  options = options.dup # Allow options to be shared without being tainted by Formtastic
  
  options[:required] = method_required?(method) unless options.key?(:required)
  options[:as]     ||= default_input_type(method, options)

  html_class = [ options[:as], (options[:required] ? :required : :optional) ]
  html_class << 'error' if has_errors?(method, options)

  wrapper_html = options.delete(:wrapper_html) || {}
  wrapper_html[:id]  ||= generate_html_id(method)
  wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')

  if options[:input_html] && options[:input_html][:id]
    options[:label_html] ||= {}
    options[:label_html][:for] ||= options[:input_html][:id]
  end

  input_parts = (custom_inline_order[options[:as]] || inline_order).dup
  input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden

  list_item_content = input_parts.map do |type|
    send(:"inline_#{type}_for", method, options)
  end.compact.join("\n")

  return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html)
end
inputs(*args, &block) click to toggle source

Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc), or with a list of fields. These two examples are functionally equivalent:

# With a block:
<% semantic_form_for @post do |form| %>
  <% form.inputs do %>
    <%= form.input :title %>
    <%= form.input :body %>
  <% end %>
<% end %>

# With a list of fields:
<% semantic_form_for @post do |form| %>
  <%= form.inputs :title, :body %>
<% end %>

# Output:
<form ...>
  <fieldset class="inputs">
    <ol>
      <li class="string">...</li>
      <li class="text">...</li>
    </ol>
  </fieldset>
</form>

Quick Forms

When called without a block or a field list, an input is rendered for each column in the model's database table, just like Rails' scaffolding. You'll obviously want more control than this in a production application, but it's a great way to get started, then come back later to customise the form with a field list or a block of inputs. Example:

<% semantic_form_for @post do |form| %>
  <%= form.inputs %>
<% end %>

With a few arguments:
<% semantic_form_for @post do |form| %>
  <%= form.inputs "Post details", :title, :body %>
<% end %>

Options

All options (with the exception of :name/:title) are passed down to the fieldset as HTML attributes (id, class, style, etc). If provided, the :name/:title option is passed into a legend tag inside the fieldset.

# With a block:
<% semantic_form_for @post do |form| %>
  <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
    ...
  <% end %>
<% end %>

# With a list (the options must come after the field list):
<% semantic_form_for @post do |form| %>
  <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
<% end %>

# ...or the equivalent:
<% semantic_form_for @post do |form| %>
  <%= form.inputs "Create a new post", :title, :body, :style => "border:1px;" %>
<% end %>

It's basically a fieldset!

Instead of hard-coding fieldsets & legends into your form to logically group related fields, use inputs:

<% semantic_form_for @post do |f| %>
  <% f.inputs do %>
    <%= f.input :title %>
    <%= f.input :body %>
  <% end %>
  <% f.inputs :name => "Advanced", :id => "advanced" do %>
    <%= f.input :created_at %>
    <%= f.input :user_id, :label => "Author" %>
  <% end %>
  <% f.inputs "Extra" do %>
    <%= f.input :update_at %>
  <% end %>
<% end %>

# Output:
<form ...>
  <fieldset class="inputs">
    <ol>
      <li class="string">...</li>
      <li class="text">...</li>
    </ol>
  </fieldset>
  <fieldset class="inputs" id="advanced">
    <legend><span>Advanced</span></legend>
    <ol>
      <li class="datetime">...</li>
      <li class="select">...</li>
    </ol>
  </fieldset>
  <fieldset class="inputs">
    <legend><span>Extra</span></legend>
    <ol>
      <li class="datetime">...</li>
    </ol>
  </fieldset>
</form>

Nested attributes

As in Rails, you can use #semantic_fields_for to nest attributes:

<% semantic_form_for @post do |form| %>
  <%= form.inputs :title, :body %>

  <% form.semantic_fields_for :author, @bob do |author_form| %>
    <% author_form.inputs do %>
      <%= author_form.input :first_name, :required => false %>
      <%= author_form.input :last_name %>
    <% end %>
  <% end %>
<% end %>

But this does not look formtastic! This is equivalent:

<% semantic_form_for @post do |form| %>
  <%= form.inputs :title, :body %>
  <% form.inputs :for => [ :author, @bob ] do |author_form| %>
    <%= author_form.input :first_name, :required => false %>
    <%= author_form.input :last_name %>
  <% end %>
<% end %>

And if you don't need to give options to your input call, you could do it in just one line:

<% semantic_form_for @post do |form| %>
  <%= form.inputs :title, :body %>
  <%= form.inputs :first_name, :last_name, :for => @bob %>
<% end %>

Just remember that calling inputs generates a new fieldset to wrap your inputs. If you have two separate models, but, semantically, on the page they are part of the same fieldset, you should use #semantic_fields_for instead (just as you would do with Rails' form builder).

# File lib/formtastic.rb, line 283
def inputs(*args, &block)
  title = field_set_title_from_args(*args)
  html_options = args.extract_options!
  html_options[:class] ||= "inputs"
  html_options[:name] = title

  if html_options[:for] # Nested form
    inputs_for_nested_attributes(*(args << html_options), &block)
  elsif block_given?
    field_set_and_list_wrapping(*(args << html_options), &block)
  else
    if @object && args.empty?
      args  = association_columns(:belongs_to)
      args += content_columns
      args -= RESERVED_COLUMNS
      args.compact!
    end
    legend = args.shift if args.first.is_a?(::String)
    contents = args.collect { |method| input(method.to_sym) }
    args.unshift(legend) if legend.present?

    field_set_and_list_wrapping(*((args << html_options) << contents))
  end
end
label(method, options_or_text=nil, options=nil) click to toggle source

Generates the label for the input. It also accepts the same arguments as Rails label method. It has three options that are not supported by Rails label method:

  • :required - Appends an abbr tag if :required is true

  • :label - An alternative form to give the label content. Whenever label

    is false, a blank string is returned.
  • :input_name - Gives the input to match for. This is needed when you want to

    to call f.label :authors but it should match :author_ids.

Examples

f.label :title # like in rails, except that it searches the label on I18n API too

f.label :title, "Your post title"
f.label :title, :label => "Your post title" # Added for formtastic API

f.label :title, :required => true # Returns <label>Title<abbr title="required">*</abbr></label>
Calls superclass method
# File lib/formtastic.rb, line 427
def label(method, options_or_text=nil, options=nil)
  if options_or_text.is_a?(Hash)
    return "" if options_or_text[:label] == false
    options = options_or_text
    text = options.delete(:label)
  else
    text = options_or_text
    options ||= {}
  end

  text = localized_string(method, text, :label) || humanized_attribute_name(method)
  text += required_or_optional_string(options.delete(:required))
  text = Formtastic::Util.html_safe(text)

  # special case for boolean (checkbox) labels, which have a nested input
  if options.key?(:label_prefix_for_nested_input)
    text = options.delete(:label_prefix_for_nested_input) + text
  end

  input_name = options.delete(:input_name) || method
  super(input_name, text, options)
end
semantic_errors(*args) click to toggle source

Generates error messages for given method names and for base. You can pass a hash with html options that will be added to ul tag

Examples

f.semantic_errors # This will show only errors on base
f.semantic_errors :state # This will show errors on base and state
f.semantic_errors :state, :class => "awesome" # errors will be rendered in ul.awesome
# File lib/formtastic.rb, line 480
def semantic_errors(*args)
  html_options = args.extract_options!
  full_errors = args.inject([]) do |array, method|
    attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
    errors = Array(@object.errors[method.to_sym]).to_sentence
    errors.present? ? array << [attribute, errors].join(" ") : array ||= []
  end
  full_errors << @object.errors[:base]
  full_errors.flatten!
  full_errors.compact!
  return nil if full_errors.blank?
  html_options[:class] ||= "errors"
  template.content_tag(:ul, html_options) do
    Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
  end
end
semantic_fields_for(record_or_name_or_array, *args, &block) click to toggle source

A thin wrapper around fields_for to set :builder => Formtastic::SemanticFormBuilder for nesting forms:

# Example:
<% semantic_form_for @post do |post| %>
  <% post.semantic_fields_for :author do |author| %>
    <% author.inputs :name %>
  <% end %>
<% end %>

# Output:
<form ...>
  <fieldset class="inputs">
    <ol>
      <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
    </ol>
  </fieldset>
</form>
# File lib/formtastic.rb, line 401
def semantic_fields_for(record_or_name_or_array, *args, &block)
  opts = args.extract_options!
  opts[:builder] ||= self.class
  args.push(opts)
  fields_for(record_or_name_or_array, *args, &block)
end

Protected Instance Methods

association_primary_key(method) click to toggle source
# File lib/formtastic.rb, line 546
def association_primary_key(method)
  reflection = reflection_for(method)
  reflection.options[:foreign_key] if reflection && !reflection.options[:foreign_key].blank?
  :"#{method}_id"
end
boolean_input(method, options) click to toggle source

Outputs a label containing a checkbox and the label text. The label defaults to the column name (method name) and can be altered with the :label option. :checked_value and :unchecked_value options are also available.

# File lib/formtastic.rb, line 1255
def boolean_input(method, options)
  html_options  = options.delete(:input_html) || {}
  checked_value = options.delete(:checked_value) || '1'
  unchecked_value = options.delete(:unchecked_value) || '0'

  html_options[:id] = html_options[:id] || generate_html_id(method, "")
  input = template.check_box_tag(
    "#{@object_name}[#{method}]",
    checked_value,
    (@object && @object.send(:"#{method}")),
    html_options
  )
  
  options = options_for_label(options)
  options[:for] ||= html_options[:id]

  # the label() method will insert this nested input into the label at the last minute
  options[:label_prefix_for_nested_input] = input

  template.hidden_field_tag("#{@object_name}[#{method}]", unchecked_value, :id => nil) << label(method, options)
end
check_boxes_input(method, options) click to toggle source

Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list items, one for each possible choice in the belongs_to association. Each li contains a label and a check_box input.

This is an alternative for has many and has and belongs to many associations.

Example:

f.input :author, :as => :check_boxes

Output:

<fieldset>
  <legend class="label"><label>Authors</label></legend>
  <ol>
    <li>
      <input type="hidden" name="book[author_id][1]" value="">
      <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
    </li>
    <li>
      <input type="hidden" name="book[author_id][2]" value="">
      <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
    </li>
  </ol>
</fieldset>

Notice that the value of the checkbox is the same as the id and the hidden field has empty value. You can override the hidden field value using the unchecked_value option.

You can customize the options available in the set by passing in a collection (Array) of ActiveRecord objects through the :collection option. If not provided, the choices are found by inferring the parent's class name from the method name and simply calling all on it (Author.all in the example above).

Examples:

f.input :author, :as => :check_boxes, :collection => @authors
f.input :author, :as => :check_boxes, :collection => Author.all
f.input :author, :as => :check_boxes, :collection => [@justin, @kate]

The :label_method option allows you to customize the label for each checkbox two ways:

  • by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)

  • by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together

Examples:

f.input :author, :as => :check_boxes, :label_method => :full_name
f.input :author, :as => :check_boxes, :label_method => :login
f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }

The :value_method option provides the same customization of the value attribute of each checkbox input tag.

Examples:

f.input :author, :as => :check_boxes, :value_method => :full_name
f.input :author, :as => :check_boxes, :value_method => :login
f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }

Formtastic works around a bug in rails handling of check box collections by not generating the hidden fields for state checking of the checkboxes The :hidden_fields option provides a way to re-enable these hidden inputs by setting it to true.

f.input :authors, :as => :check_boxes, :hidden_fields => false
f.input :authors, :as => :check_boxes, :hidden_fields => true

Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label combination to contain a class with the value of the radio button (useful for applying specific CSS or Javascript to a particular checkbox).

# File lib/formtastic.rb, line 1152
def check_boxes_input(method, options)
  collection = find_collection_for_column(method, options)
  html_options = options.delete(:input_html) || {}

  input_name      = generate_association_input_name(method)
  hidden_fields   = options.delete(:hidden_fields)
  value_as_class  = options.delete(:value_as_class)
  unchecked_value = options.delete(:unchecked_value) || ''
  html_options    = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
  input_ids       = []

  selected_values = find_selected_values_for_column(method, options)
  disabled_option_is_present = options.key?(:disabled)
  disabled_values = [*options[:disabled]] if disabled_option_is_present

  li_options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}

  list_item_content = collection.map do |c|
    label = c.is_a?(Array) ? c.first : c
    value = c.is_a?(Array) ? c.last : c
    input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
    input_ids << input_id

    html_options[:checked] = selected_values.include?(value)
    html_options[:disabled] = disabled_values.include?(value) if disabled_option_is_present
    html_options[:id] = input_id

    li_content = template.content_tag(:label,
      Formtastic::Util.html_safe("#{create_check_boxes(input_name, html_options, value, unchecked_value, hidden_fields)} #{escape_html_entities(label)}"),
      :for => input_id
    )

    li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
    template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
  end

  fieldset_content = legend_tag(method, options)
  fieldset_content << create_hidden_field_for_check_boxes(input_name, value_as_class) unless hidden_fields
  fieldset_content << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
  template.content_tag(:fieldset, fieldset_content)
end
country_input(method, options) click to toggle source

Outputs a country select input, wrapping around a regular country_select helper. Rails doesn't come with a country_select helper by default any more, so you'll need to install the “official” plugin, or, if you wish, any other country_select plugin that behaves in the same way.

The Rails plugin iso-3166-country-select plugin can be found “here”:github.com/rails/iso-3166-country-select.

By default, Formtastic includes a handfull of english-speaking countries as “priority counties”, which you can change to suit your market and user base (see README for more info on config).

Examples:

f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
# File lib/formtastic.rb, line 1237
def country_input(method, options)
  raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless respond_to?(:country_select)

  html_options = options.delete(:input_html) || {}
  priority_countries = options.delete(:priority_countries) || self.priority_countries

  field_id = generate_html_id(method, "")
  html_options[:id] ||= field_id
  label_options = options_for_label(options)
  label_options[:for] ||= html_options[:id]

  label(method, label_options) <<
  country_select(method, priority_countries, strip_formtastic_options(options), html_options)
end
date_input(method, options) click to toggle source

Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list items (li), one for each fragment for the date (year, month, day). Each li contains a label (eg “Year”) and a select box. Overwriting the label is possible by adding the :labels option. :labels should be a hash with the field (e.g. day) as key and the label text as value. See #date_or_datetime_input for a more detailed output example.

Some of Rails' options for select_date are supported, but not everything yet, see documentation of #date_or_datetime_input() for more information.

# File lib/formtastic.rb, line 962
def date_input(method, options)
  options = set_include_blank(options)
  date_or_datetime_input(method, options.merge(:discard_hour => true))
end
date_or_datetime_input(method, options) click to toggle source

Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a legend (for what would normally be considered the label), and an ordered list of list items for year, month, day, hour, etc, each containing a label and a select. Example:

<fieldset>

<legend>Created At</legend>
<ol>
  <li>
    <label for="user_created_at_1i">Year</label>
    <select id="user_created_at_1i" name="user[created_at(1i)]">
      <option value="2003">2003</option>
      ...
      <option value="2013">2013</option>
    </select>
  </li>
  <li>
    <label for="user_created_at_2i">Month</label>
    <select id="user_created_at_2i" name="user[created_at(2i)]">
      <option value="1">January</option>
      ...
      <option value="12">December</option>
    </select>
  </li>
  <li>
    <label for="user_created_at_3i">Day</label>
    <select id="user_created_at_3i" name="user[created_at(3i)]">
      <option value="1">1</option>
      ...
      <option value="31">31</option>
    </select>
  </li>
</ol>

</fieldset>

This is an absolute abomination, but so is the official Rails select_date().

Options:

* @:order => [:month, :day, :year]@
* @:include_seconds@ => true@
* @:discard_(year|month|day|hour|minute) => true@
* @:include_blank => true@
* @:labels => {}@
# File lib/formtastic.rb, line 1036
def date_or_datetime_input(method, options)
  position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
  i18n_date_order = ::I18n.t(:order, :scope => [:date])
  i18n_date_order = nil unless i18n_date_order.is_a?(Array)
  inputs   = options.delete(:order) || i18n_date_order || [:year, :month, :day]
  inputs   = [] if options[:ignore_date]
  labels   = options.delete(:labels) || {}

  time_inputs = [:hour, :minute]
  time_inputs << :second if options[:include_seconds]

  list_items_capture = ""
  hidden_fields_capture = ""

  datetime = @object.send(method) if @object && @object.send(method)

  html_options = options.delete(:input_html) || {}
  input_ids    = []

  (inputs + time_inputs).each do |input|
    input_ids << input_id = generate_html_id(method, "#{position[input]}i")

    field_name = "#{method}(#{position[input]}i)"
    if options[:"discard_#{input}"]
      break if time_inputs.include?(input)

      hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
      hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
    else
      opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
      item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])

      list_items_capture << template.content_tag(:li, Formtastic::Util.html_safe([
          !item_label_text.blank? ? template.content_tag(:label, Formtastic::Util.html_safe(item_label_text), :for => input_id) : "",
          template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
        ].join(""))
      )
    end
  end

  hidden_fields_capture << field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_items_capture)
end
datetime_input(method, options) click to toggle source

Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li contains a label (eg “Year”) and a select box. Overwriting the label is possible by adding the :labels option. :labels should be a hash with the field (e.g. day) as key and the label text as value. See #date_or_datetime_input for a more detailed output example.

Some of Rails' options for select_date are supported, but not everything yet, see documentation of #date_or_datetime_input() for more information.

# File lib/formtastic.rb, line 975
def datetime_input(method, options)
  options = set_include_blank(options)
  date_or_datetime_input(method, options)
end
detect_group_association(method, group_by) click to toggle source

Detects the method to call for fetching group members from the groups when grouping select options

# File lib/formtastic.rb, line 1560
def detect_group_association(method, group_by)
  object_to_method_reflection = reflection_for(method)
  method_class = object_to_method_reflection.klass

  method_to_group_association = method_class.reflect_on_association(group_by)
  group_class = method_to_group_association.klass

  # This will return in the normal case
  return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)

  # This is for belongs_to associations named differently than their class
  # form.input :parent, :group_by => :customer
  # eg.
  # class Project
  #   belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
  #   belongs_to :customer
  # end
  # class Customer
  #   has_many :projects
  # end
  group_method = method_class.to_s.underscore.pluralize.to_sym
  return group_method if group_class.reflect_on_association(group_method) # :projects

  # This is for has_many associations named differently than their class
  # eg.
  # class Project
  #   belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
  #   belongs_to :customer
  # end
  # class Customer
  #   has_many :tasks, :class_name => 'Project', :foreign_key => 'customer_id'
  # end
  possible_associations =  group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
  return possible_associations.first.name.to_sym if possible_associations.count == 1

  raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"

end
detect_label_and_value_method!(collection, options = {}) click to toggle source

Detects the label and value methods from a collection using methods set in collection_label_methods and collection_value_methods. For some ruby core classes sensible defaults have been defined. It will use and delete the options :label_method and :value_methods when present.

# File lib/formtastic.rb, line 1532
def detect_label_and_value_method!(collection, options = {})
  sample = collection.first || collection.last

  case sample
  when Array
    label, value = :first, :last
  when Integer
    label, value = :to_s, :to_i
  when String, NilClass
    label, value = :to_s, :to_s
  end

  # Order of preference: user supplied method, class defaults, auto-detect
  label = options[:label_method] || label || collection_label_methods.find { |m| sample.respond_to?(m) }
  value = options[:value_method] || value || collection_value_methods.find { |m| sample.respond_to?(m) }

  [label, value]
end
email_input(method, options) click to toggle source

Outputs a label and a standard Rails email field inside the wrapper.

# File lib/formtastic.rb, line 683
def email_input(method, options)
  basic_input_helper(:email_field, :email, method, options)
end
error_keys(method, options) click to toggle source
# File lib/formtastic.rb, line 499
def error_keys(method, options)
  @methods_for_error ||= {}
  @methods_for_error[method] ||= begin
    methods_for_error = [method.to_sym]
    methods_for_error << file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if is_file?(method, options)
    methods_for_error << [association_primary_key(method)] if association_macro_for_method(method) == :belongs_to
    methods_for_error.flatten.compact.uniq
  end
end
file_input(method, options) click to toggle source

Outputs a label and a standard Rails file field inside the wrapper.

# File lib/formtastic.rb, line 678
def file_input(method, options)
  basic_input_helper(:file_field, :file, method, options)
end
find_selected_values_for_column(method, options) click to toggle source

Used by check_boxes input. The selected values will be set by retrieving the value through the association.

If the collection is not a hash or an array of strings, fixnums or symbols, we use value_method to retrieve an array with the values

# File lib/formtastic.rb, line 1199
def find_selected_values_for_column(method, options)
  if object.respond_to?(method)
    collection = [object.send(method)].compact.flatten
    label, value = detect_label_and_value_method!(collection, options)
    [*collection.map { |o| send_or_call(value, o) }].compact
  else
    []
  end
end
get_maxlength_for(method) click to toggle source
# File lib/formtastic.rb, line 1677
def get_maxlength_for(method)
  validation = validations_for(method).find do |validation|
    (validation.respond_to?(:macro) && validation.macro == :validates_length_of) || # Rails 2 validation
    (validation.respond_to?(:kind) && validation.kind == :length) # Rails 3 validator
  end

  if validation
    validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
  else
    nil
  end
end
has_errors?(method, options) click to toggle source
# File lib/formtastic.rb, line 509
def has_errors?(method, options)
  methods_for_error = error_keys(method,options)
  @object && @object.respond_to?(:errors) && methods_for_error.any?{|error| !@object.errors[error].blank?}
end
hidden_input(method, options) click to toggle source

Outputs a hidden field inside the wrapper, which should be hidden with CSS. Additionals options can be given using :input_hml. Should :input_html not be specified every option except for formtastic options will be sent straight to hidden input element.

# File lib/formtastic.rb, line 707
def hidden_input(method, options)
  options ||= {}
  html_options = options.delete(:input_html) || strip_formtastic_options(options)
  html_options[:id] ||= generate_html_id(method, "")
  hidden_field(method, html_options)
end
inline_input_for(method, options) click to toggle source

Generates an input for the given method using the type supplied with :as.

# File lib/formtastic.rb, line 1278
def inline_input_for(method, options)
  send(:"#{options.delete(:as)}_input", method, options)
end
is_file?(method, options = {}) click to toggle source
# File lib/formtastic.rb, line 1467
def is_file?(method, options = {})
  @files ||= {}
  @files[method] ||= (options[:as].present? && options[:as] == :file) || begin
    file = @object.send(method) if @object && @object.respond_to?(method)
    file && file_methods.any?{|m| file.respond_to?(m)}
  end
end
legend_tag(method, options = {}) click to toggle source

Generates the legend for radiobuttons and checkboxes

# File lib/formtastic.rb, line 1410
def legend_tag(method, options = {})
  if options[:label] == false
    Formtastic::Util.html_safe("")
  else
    text = localized_string(method, options[:label], :label) || humanized_attribute_name(method)
    text += required_or_optional_string(options.delete(:required))
    text = Formtastic::Util.html_safe(text)
    template.content_tag :legend, template.label_tag(nil, text, :for => nil), :class => :label
  end
end
model_name() click to toggle source
# File lib/formtastic.rb, line 1820
def model_name
  @object.present? ? @object.class.name : @object_name.to_s.classify
end
normalize_model_name(name) click to toggle source
# File lib/formtastic.rb, line 1824
def normalize_model_name(name)
  if name =~ /(.+)\[(.+)\]/
    [$1, $2]
  else
    [name]
  end
end
numeric_input(method, options) click to toggle source

Outputs a label and standard Rails text field inside the wrapper.

# File lib/formtastic.rb, line 668
def numeric_input(method, options)
  basic_input_helper(:text_field, :numeric, method, options)
end
options_require_validation?(options) click to toggle source

Determines whether the given options evaluate to true

# File lib/formtastic.rb, line 629
def options_require_validation?(options) #nodoc
  allow_blank = options[:allow_blank]
  return !allow_blank unless allow_blank.nil?
  if_condition = !options[:if].nil?
  condition = if_condition ? options[:if] : options[:unless]

  condition = if condition.respond_to?(:call)
                condition.call(@object)
              elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
                @object.send(condition)
              else
                condition
              end

  if_condition ? !!condition : !condition
end
password_input(method, options) click to toggle source

Outputs a label and standard Rails password field inside the wrapper.

# File lib/formtastic.rb, line 663
def password_input(method, options)
  basic_input_helper(:password_field, :password, method, options)
end
phone_input(method, options) click to toggle source

Outputs a label and a standard Rails phone field inside the wrapper.

# File lib/formtastic.rb, line 688
def phone_input(method, options)
  basic_input_helper(:phone_field, :phone, method, options)
end
radio_input(method, options) click to toggle source

Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list items, one for each possible choice in the belongs_to association. Each li contains a label and a radio input.

Example:

f.input :author, :as => :radio

Output:

<fieldset>
  <legend><span>Author</span></legend>
  <ol>
    <li>
      <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
    </li>
    <li>
      <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
    </li>
  </ol>
</fieldset>

You can customize the choices available in the radio button set by passing in a collection (an Array or Hash) through the :collection option. If not provided, the choices are found by reflecting on the association (Author.all in the example above).

Examples:

f.input :author, :as => :radio, :collection => @authors
f.input :author, :as => :radio, :collection => Author.all
f.input :author, :as => :radio, :collection => [@justin, @kate]
f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]

The :label_method option allows you to customize the label for each radio button two ways:

  • by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)

  • by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together

Examples:

f.input :author, :as => :radio, :label_method => :full_name
f.input :author, :as => :radio, :label_method => :login
f.input :author, :as => :radio, :label_method => :full_name_with_post_count
f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }

The :value_method option provides the same customization of the value attribute of each option tag.

Examples:

f.input :author, :as => :radio, :value_method => :full_name
f.input :author, :as => :radio, :value_method => :login
f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }

Finally, you can set :value_as_class => true if you want the li wrapper around each radio button / label combination to contain a class with the value of the radio button (useful for applying specific CSS or Javascript to a particular radio button).

# File lib/formtastic.rb, line 924
def radio_input(method, options)
  collection   = find_collection_for_column(method, options)
  html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})

  input_name = generate_association_input_name(method)
  value_as_class = options.delete(:value_as_class)
  input_ids = []

  list_item_content = collection.map do |c|
    label = c.is_a?(Array) ? c.first : c
    value = c.is_a?(Array) ? c.last  : c
    input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
    input_ids << input_id

    html_options[:id] = input_id

    li_content = template.content_tag(:label,
      Formtastic::Util.html_safe("#{radio_button(input_name, value, html_options)} #{escape_html_entities(label)}"),
      :for => input_id
    )

    li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
    template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
  end

  template.content_tag(:fieldset,
    legend_tag(method, options) << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
  )
end
render_inline_errors?() click to toggle source
# File lib/formtastic.rb, line 514
def render_inline_errors?
  @object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(inline_errors)
end
search_input(method, options) click to toggle source

Outputs a label and a standard Rails search field inside the wrapper.

# File lib/formtastic.rb, line 698
def search_input(method, options)
  basic_input_helper(:search_field, :search, method, options)
end
select_input(method, options) click to toggle source

Outputs a label and a select box containing options from the parent (belongs_to, has_many, has_and_belongs_to_many) association. If an association is has_many or has_and_belongs_to_many the select box will be set as multi-select and size = 5

Example (belongs_to):

f.input :author

<label for="book_author_id">Author</label>
<select id="book_author_id" name="book[author_id]">
  <option value=""></option>
  <option value="1">Justin French</option>
  <option value="2">Jane Doe</option>
</select>

Example (has_many):

f.input :chapters

<label for="book_chapter_ids">Chapters</label>
<select id="book_chapter_ids" name="book[chapter_ids]">
  <option value=""></option>
  <option value="1">Chapter 1</option>
  <option value="2">Chapter 2</option>
</select>

Example (has_and_belongs_to_many):

f.input :authors

<label for="book_author_ids">Authors</label>
<select id="book_author_ids" name="book[author_ids]">
  <option value=""></option>
  <option value="1">Justin French</option>
  <option value="2">Jane Doe</option>
</select>

You can customize the options available in the select by passing in a collection. A collection can be given as an Array, a Hash or as a String (containing pre-rendered HTML options). If not provided, the choices are found by inferring the parent's class name from the method name and simply calling all on it (VehicleOwner.all in the example above).

Examples:

f.input :author, :collection => @authors
f.input :author, :collection => Author.all
f.input :author, :collection => [@justin, @kate]
f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
f.input :author, :collection => grouped_options_for_select(["North America",[["United States","US"],["Canada","CA"]]])

The :label_method option allows you to customize the text label inside each option tag two ways:

  • by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)

  • by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together

Examples:

f.input :author, :label_method => :full_name
f.input :author, :label_method => :login
f.input :author, :label_method => :full_name_with_post_count
f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }

The :value_method option provides the same customization of the value attribute of each option tag.

Examples:

f.input :author, :value_method => :full_name
f.input :author, :value_method => :login
f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }

You can pass html_options to the select tag using :input_html => {}

Examples:

f.input :authors, :input_html => {:size => 20, :multiple => true}

By default, all select inputs will have a blank option at the top of the list. You can add a prompt with the :prompt option, or disable the blank option with :include_blank => false.

You can group the options in optgroup elements by passing the :group_by option (Note: only tested for belongs_to relations)

Examples:

f.input :author, :group_by => :continent

All the other options should work as expected. If you want to call a custom method on the group item. You can include the option:group_label_method Examples:

f.input :author, :group_by => :continents, :group_label_method => :something_different
# File lib/formtastic.rb, line 810
def select_input(method, options)
  html_options = options.delete(:input_html) || {}
  html_options[:multiple] = html_options[:multiple] || options.delete(:multiple)
  html_options.delete(:multiple) if html_options[:multiple].nil?

  reflection = reflection_for(method)
  if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
    html_options[:multiple] = true if html_options[:multiple].nil?
    html_options[:size]     ||= 5
    options[:include_blank] ||= false
  end
  options = set_include_blank(options)
  input_name = generate_association_input_name(method)
  html_options[:id] ||= generate_html_id(input_name, "")

  select_html = if options[:group_by]
    # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
    # The formtastic user however shouldn't notice this too much.
    raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
    label, value = detect_label_and_value_method!(raw_collection, options)
    group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
    group_label_method = options[:group_label_method] || detect_label_method(group_collection)
    group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
    group_association = options[:group_association] || detect_group_association(method, options[:group_by])

    # Here comes the monster with 8 arguments
    grouped_collection_select(input_name, group_collection,
                                   group_association, group_label_method,
                                   value, label,
                                   strip_formtastic_options(options), html_options)
  else
    collection = find_collection_for_column(method, options)

    select(input_name, collection, strip_formtastic_options(options), html_options)
  end

  label_options = options_for_label(options).merge(:input_name => input_name)
  label_options[:for] ||= html_options[:id]
  label(method, label_options) << select_html
end
send_or_call(duck, object) click to toggle source
# File lib/formtastic.rb, line 1832
def send_or_call(duck, object)
  if duck.is_a?(Proc)
    duck.call(object)
  else
    object.send(duck)
  end
end
set_include_blank(options) click to toggle source
# File lib/formtastic.rb, line 1840
def set_include_blank(options)
  unless options.key?(:include_blank) || options.key?(:prompt)
    options[:include_blank] = include_blank_for_select_by_default
  end
  options
end
string_input(method, options) click to toggle source

Outputs a label and standard Rails text field inside the wrapper.

# File lib/formtastic.rb, line 658
def string_input(method, options)
  basic_input_helper(:text_field, :string, method, options)
end
text_input(method, options) click to toggle source

Ouputs a label and standard Rails text area inside the wrapper.

# File lib/formtastic.rb, line 673
def text_input(method, options)
  basic_input_helper(:text_area, :text, method, options)
end
time_input(method, options) click to toggle source

Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list items (li), one for each fragment for the time (hour, minute, second). Each li contains a label (eg “Hour”) and a select box. Overwriting the label is possible by adding the :labels option. :labels should be a hash with the field (e.g. day) as key and the label text as value. See #date_or_datetime_input for a more detailed output example.

Some of Rails' options for select_time are supported, but not everything yet, see documentation of #date_or_datetime_input() for more information.

# File lib/formtastic.rb, line 988
def time_input(method, options)
  options = set_include_blank(options)
  date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
end
time_zone_input(method, options) click to toggle source

Outputs a timezone select input as Rails' time_zone_select helper. You can give priority zones as option.

Examples:

f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
# File lib/formtastic.rb, line 857
def time_zone_input(method, options)
  html_options = options.delete(:input_html) || {}
  field_id = generate_html_id(method, "")
  html_options[:id] ||= field_id
  label_options = options_for_label(options)
  label_options[:for] ||= html_options[:id]
  label(method, label_options) <<
  time_zone_select(method, options.delete(:priority_zones),
    strip_formtastic_options(options), html_options)
end
url_input(method, options) click to toggle source

Outputs a label and a standard Rails url field inside the wrapper.

# File lib/formtastic.rb, line 693
def url_input(method, options)
  basic_input_helper(:url_field, :url, method, options)
end
validations_for(method, mode = :active) click to toggle source

Returns the active validations for the given method or an empty Array if no validations are found for the method.

By default, the if/unless options of the validations are evaluated and only the validations that should be run for the current object state are returned. Pass :all to the last parameter to return :all validations regardless of if/unless options.

Requires the ValidationReflection plugin to be present or an ActiveModel. Returns an epmty Array if neither is the case.

# File lib/formtastic.rb, line 1653
def validations_for(method, mode = :active)
  # ActiveModel?
  validations = if @object && @object.class.respond_to?(:validators_on)
    @object.class.validators_on(method)
  else
    # ValidationReflection plugin?
    if @object && @object.class.respond_to?(:reflect_on_validations_for)
      @object.class.reflect_on_validations_for(method)
    else
      []
    end
  end

  validations = validations.select do |validation|
    (validation.options.present? ? options_require_validation?(validation.options) : true)
  end unless mode == :all

  return validations
end