Class: Formtastic::SemanticFormBuilder

Inherits:
ActionView::Helpers::FormBuilder
  • Object
show all
Defined in:
lib/formtastic.rb

Constant Summary collapse

RESERVED_COLUMNS =
[:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
INLINE_ERROR_TYPES =
[:sentence, :list, :first]
@@default_text_field_size =
50
@@all_fields_required_by_default =
true
@@include_blank_for_select_by_default =
true
@@required_string =
proc { %{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>} }
@@optional_string =
''
@@inline_errors =
:sentence
@@label_str_method =
:humanize
@@collection_label_methods =
%w[to_label display_name full_name name title username login value to_s]
@@inline_order =
[ :input, :hints, :errors ]
@@file_methods =
[ :file?, :public_filename ]
@@priority_countries =
["Australia", "Canada", "United Kingdom", "United States"]
@@i18n_lookups_by_default =
false
@@default_commit_button_accesskey =
nil

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#templateObject

Returns the value of attribute template.



30
31
32
# File 'lib/formtastic.rb', line 30

def template
  @template
end

Instance Method Details

Add a link to add more partials

 # Example:
 <% semantic_form_for @post do |post| %>
   <%= post.input :title %>
   <% post.inputs :name => 'Authors', :id => 'authors' do %>
     <%= post.add_associated_link "+ Author", :authors, :partial => 'authors/add_author' %>
   <% end %>
 <% end %>

 # app/views/authors/_add_author.html.erb
 <% f.input :name %>

 # Output:
<form ...>
  <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
  <fieldset class="inputs" id="authors"><legend><span>Authors</span></legend><ol>
    <a href="#" onclick="if (typeof formtastic_next_author_id == 'undefined') ....return false;">+ Author</a>
  </ol></fieldset>
</form>

if no partial name as give, it will use the association name 'authors' #=> app/views/posts/_authors.html.erb
you can use :container to customize the container of nesteds #=> :container => '#my_authors'
:before_function will be executed before the add method
:after_function will be executed after


444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/formtastic.rb', line 444

def add_associated_link(name, association, opts = {})
  object = @object.send(association).build
  associated_name = extract_option_or_class_name(opts, :name, object)
  variable = "formtastic_next_#{associated_name}_id"

  opts.symbolize_keys!
  partial = opts.delete(:partial) || associated_name
  container = opts.delete(:expression) || "'#{opts.delete(:container) || '#'+associated_name.pluralize}'"
  before_function = opts.delete(:before_function) || ''
  after_function  = opts.delete(:after_function)  || ''

  form = render_associated_form(object, :partial => partial)
  form.gsub!(/attributes_(\d+)/, '__idx__')
  form.gsub!(/\[(\d+)\]/, '__idxx__')

  function = "#{before_function};if (typeof #{variable} == 'undefined') #{variable} = #{$1 || 0};
  $(#{container}).append(#{form.to_json}.replace(/__idx__/g, 'attributes_' +
  #{variable}).replace(/__idxx__/g, '[' + #{variable}++ + ']'));#{after_function}"

  template.link_to_function(name, function, opts)
end

#association_name(class_name) ⇒ Object



555
556
557
# File 'lib/formtastic.rb', line 555

def association_name(class_name)
  @object.respond_to?("#{class_name}_attributes=") ? class_name : class_name.pluralize
end

#buttons(*args, &block) ⇒ Object Also known as: button_field_set

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.



280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/formtastic.rb', line 280

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) ⇒ Object

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 "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
<%= form.commit_button :class => "pretty" %> => <input name="commit" type="submit" value="Save Post" class="pretty {create|update|submit}" />


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/formtastic.rb', line 309

def commit_button(*args)
  options = args.extract_options!
  text = options.delete(:label) || args.shift

  if @object
    key = @object.new_record? ? :create : :update
    object_name = @object.class.human_name
  else
    key = :submit
    object_name = @object_name.to_s.send(@@label_str_method)
  end

  text = (self.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(' '))
  element_class = ['commit', options.delete(:class)].compact.join(' ') # TODO: Add class reflecting on form action.
  accesskey = (options.delete(:accesskey) || @@default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
  button_html = button_html.merge(:accesskey => accesskey) if accesskey  
  template.(:li, self.submit(text, button_html), :class => element_class)
end

#extract_option_or_class_name(hash, option, object) ⇒ Object



559
560
561
# File 'lib/formtastic.rb', line 559

def extract_option_or_class_name(hash, option, object)
  (hash.delete(option) || object.class.name.split('::').last.underscore)
end

#inline_errors_for(method, options = nil) ⇒ Object Also known as: errors_on

Generates error messages for the given method. Errors can be shown as list, as sentence or just the first error can be displayed. If :none is set, no error is shown.

This method is also aliased as errors_on, so you can call on your custom inputs as well:

semantic_form_for :post do |f|
  f.text_field(:body)
  f.errors_on(:body)
end


612
613
614
615
616
617
618
619
# File 'lib/formtastic.rb', line 612

def inline_errors_for(method, options = nil) #:nodoc:
  if render_inline_errors?
    errors = @object.errors[method.to_sym]
    send(:"error_#{@@inline_errors}", [*errors]) if errors.present?
  else
    nil
  end
end

#input(method, options = {}) ⇒ Object

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

  • :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 :secret, :value => "Hello" %>
    <%= form.input :name, :label => "Full Name" %>
    <%= form.input :manager_id, :as => :radio %>
    <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
    <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
  <% end %>
<% end %>


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/formtastic.rb', line 77

def input(method, options = {})
  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 @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?

  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 = @@inline_order.dup
  input_parts.delete(:errors) if options[:as] == :hidden

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

  return template.(:li, list_item_content, wrapper_html)
end

#inputs(*args, &block) ⇒ Object Also known as: input_field_set

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).



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/formtastic.rb', line 249

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  = self.association_columns(:belongs_to)
      args += self.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) ⇒ Object

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>


582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/formtastic.rb', line 582

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))

  # special case for boolean (checkbox) labels, which have a nested input
  text = (options.delete(:label_prefix_for_nested_input) || "") + text

  input_name = options.delete(:input_name) || method
  super(input_name, text, options)
end

Stolen from Attribute_fu (github.com/giraffesoft/attribute_fu)

Rails 2.3 Patches from http://github.com/odadata/attribute_fu

Dinamically add and remove nested forms for a has_many relation.

Add a link to remove the associated partial
  # Example:
  <% semantic_form_for @post do |post| %>
    <%= post.input :title %>
    <% post.inputs :name => 'Authors', :id => 'authors' do %>
      <%= post.add_associated_link "+ Author", :authors, :partial => 'authors/add_author' %>
      <%= post.render_associated_form :authors, :partial => 'authors/add_author' %>
    <% end %>
  <% end %>

  # app/views/authors/_add_author.html.erb
  <div class="author">
    <%= f.input :name %>
    <%= f.remove_link "Remove" %>
  </div>

 # Output:
 <form ...>
   <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
   <fieldset class="inputs" id="authors"><legend><span>Authors</span></legend><ol>
     <a href="#" onclick="if (typeof formtastic_next_author_id == 'undefined') ....return false;">+ Author</a>
     <div class="author">
       <li ...><label ...></label><input id="post_authors_name" maxlength="255"
         name="post[authors][name]" size="50" type="text" /></li>
       <input id="post_authors__delete" name="post[authors][_delete]" type="hidden" />
       <a href="#" onclick="$(this).parents('.author').hide(); $(this).prev(':input').val('1');; return false;">Remove</a>
    </div>
   </ol></fieldset>
 </form>

Opts:

 * :selector, id of element that will disappear(.hide()), if no id as give, will use css class
     association.class.name last word in downcase #=> '.author'
   f.remove_link "Remove", :selector => '#my_own_element_id'

 * :function, a funcion to execute before hide element and set _delete to 1
   f.remove_link "Remove", :function => "alert('Removing Author')"

 NOTE: remove_link must be put in the partial, not in main template


407
408
409
410
411
412
413
414
415
416
# File 'lib/formtastic.rb', line 407

def remove_link(name, *args)
  options = args.extract_options!
  css_selector = options.delete(:selector) || ".#{@object.class.name.split("::").last.underscore}"

  function = options.delete(:function) || ""
  function    << ";$(this).parents('#{css_selector}').hide(); $(this).prev(':input').val('1');"

  out = hidden_field(:_delete)
  out += template.link_to_function(name, function, *args.push(options))
end

#render_associated_form(associated, opts = {}) ⇒ Object

Render associated form

Example:

<% semantic_form_for @post do |post| %>
  <%= post.input :title %>
  <% post.inputs :name => 'Authors', :id => 'authors' do %>
    <%= post.add_associated_link "+ Author", :authors, :partial => 'authors/add_author' %>
    <%= post.render_associated_form :authors, :partial => 'authors/add_author' %>
  <% end %>
<% end %>

Partial: app/views/authors/_add_author.html.erb

<% f.input :name %>

Output:

<form ...>
  <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
  <fieldset class="inputs" id="authors"><legend><span>Authors</span></legend><ol>
    <a href="#" onclick="if (typeof formtastic_next_author_id == 'undefined') ....return false;">+ Author</a>
    <li class="string required" ...><label ...></label><input id="post_authors_name" maxlength="255"
    name="post[authors][name]" size="50" type="text" value="Renan T. Fernandes" /></li>
  </ol></fieldset>
</form>

Opts:

* :partial => Render a given partial, if no one is given, try to find a partial
              with association name in the controller folder.

post.render_associated_form :authors, :partial => 'author'
^ try to render app/views/posts/_author.html.erb

post.render_associated_form :authors, :partial => 'authors/author'
^ try to render app/views/authors/_author.html.erb

NOTE: Partial need to use ‘f’ as formtastic reference. Example:

    <%= f.input :name %> #=> render author name association

* :new    => make N empty partials if it is a new record. Example:

post.render_associated_form :authors, :new => 2
^ make 2 empty authors partials

* :edit   => show only X partial if is editing a record. Example:

post.render_associated_form :authors, :edit => 3
^ if record have 1 author, make 2 new empty partials
NOTE: :edit not conflicts with :new; :new is for new records only

* :new_in_edit  => show X new partial if is editing a record. Example:

post.render_associated_form :authors, :new_in_edit => 2
^ make more 2 new partial for record
NOTE: this option is ignored if :edit is seted

Example:

post.render_associated_form :authors, :edit => 2, :new_in_edit => 100
^ if record have 1 author, make more one partial


529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/formtastic.rb', line 529

def render_associated_form(associated, opts = {})
  associated = @object.send(associated.to_s) if associated.is_a? Symbol
  associated = associated.is_a?(Array) ? associated : [associated] # preserve association proxy if this is one

  opts.symbolize_keys!
  (opts[:new] - associated.select(&:new_record?).length).times  { associated.build } if opts[:new]  and @object.new_record? == true
  if opts[:edit] and @object.new_record? == false
    (opts[:edit] - associated.count).times { associated.build }
  elsif opts[:new_in_edit] and @object.new_record? == false
    opts[:new_in_edit].times { associated.build }
  end

  unless associated.empty?
    name = extract_option_or_class_name(opts, :name, associated.first)
    partial = opts[:partial] || name
    local_assign_name = partial.split('/').last.split('.').first

    output = associated.map do |element|
      fields_for(association_name(name), element, (opts[:fields_for] || {}).merge(:name => name)) do |f|
        template.render({:partial => "#{partial}", :locals => {local_assign_name.to_sym => element, :f => f}.merge(opts[:locals] || {})}.merge(opts[:render] || {}))
      end
    end
  output.join
  end
end

#semantic_fields_for(record_or_name_or_array, *args, &block) ⇒ Object

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>


351
352
353
354
355
356
# File 'lib/formtastic.rb', line 351

def semantic_fields_for(record_or_name_or_array, *args, &block)
  opts = args.extract_options!
  opts[:builder] ||= Formtastic::SemanticFormHelper.builder
  args.push(opts)
  fields_for(record_or_name_or_array, *args, &block)
end