class RPH::FormAssistant::FormBuilder
* provides several convenient helpers (see helpers.rb) and an infrastructure to easily add your own * method_missing hook to wrap content "on the fly" * optional: automatically attach labels to field helpers * optional: format fields using partials (extremely extensible)
Usage:
<% form_for @project, :builder => RPH::FormAssistant::FormBuilder do |form| %> // fancy form stuff <% end %> - or - <% form_assistant_for @project do |form| %> // fancy form stuff <% end %> - or - # in config/intializers/form_assistant.rb ActionView::Base.default_form_builder = RPH::FormAssistant::FormBuilder
Attributes
ignore_errors[RW]
ignore_labels[RW]
ignore_templates[RW]
template_root[RW]
fallback_template[RW]
used if no other template is available
Public Class Methods
assist(helper_name)
click to toggle source
Calls superclass method
# File lib/form_assistant/form_builder.rb, line 204 def self.assist(helper_name) define_method(helper_name) do |field, *args| options = (helper_name == 'check_box' ? args.shift : args.extract_options!) || {} label_options = extract_options_for_label(field, options) template_options = extract_options_for_template(helper_name, options) extra_locals = options.delete(:locals) || {} # build out the label element (if desired) label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options) # grab the tip, if any tip = options.delete(:tip) # is the field required? required = !!options.delete(:required) # ensure that we don't have any custom options pass through field_options = options.except(:label, :template, :tip, :required) # call the original render for the element super_args = helper_name == 'check_box' ? args.unshift(field_options) : args.push(field_options) element = super(field, *super_args) return element if template_options[:template] === false # return the helper with an optional label if templates are not to be used return render_element(element, field, helper_name, options, label_options[:label] === false) if self.class.ignore_templates # render the partial template from the desired template root render_partial_for(element, field, label, tip, template_options[:template], helper_name, required, extra_locals, args) end end
view_path()
click to toggle source
# File lib/form_assistant/form_builder.rb, line 60 def view_path if Rails.configuration.respond_to?(:view_path) return Rails.configuration.view_path else return Rails.configuration.paths['app/views'].first end end
Public Instance Methods
column_type(field)
click to toggle source
# File lib/form_assistant/form_builder.rb, line 313 def column_type(field) object.class.columns_hash[field.to_s].type rescue :string end
fields_for_with_form_assistant(record_or_name_or_array, *args, &proc)
click to toggle source
since fields_for() doesn't inherit the builder from form_for, we need to provide a means to set the builder automatically (works with nesting, too)
Usage: simply call fields_for() on the builder object
<% form_assistant_for @project do |form| %> <%= form.text_field :title %> <% form.fields_for :tasks do |task_fields| %> <%= task_fields.text_field :name %> <% end %> <% end %>
# File lib/form_assistant/form_builder.rb, line 328 def fields_for_with_form_assistant(record_or_name_or_array, *args, &proc) options = args.extract_options! # hand control over to the original #fields_for() fields_for_without_form_assistant(record_or_name_or_array, *(args << options.merge!(:builder => self.class)), &proc) end
input(field, *args)
click to toggle source
# File lib/form_assistant/form_builder.rb, line 290 def input(field, *args) helper_name = case column_type(field) when :string field.to_s.include?('password') ? :password_field : :text_field when :text ; :text_area when :integer, :float, :decimal ; :text_field when :date ; :date_select when :datetime, :timestamp ; :datetime_select when :time ; :time_select when :boolean ; :check_box else ; :text_field end send(helper_name, field, *args) end
inputs(*args)
click to toggle source
# File lib/form_assistant/form_builder.rb, line 306 def inputs(*args) options = args.extract_options! args.flatten.map do |field| input(field, options.dup) end.join('') end
partial(name, options={})
click to toggle source
Renders a partial, passing the form object as a local variable named 'form' <%= form.partial 'shared/new', :locals => { :whatever => @whatever } %>
# File lib/form_assistant/form_builder.rb, line 284 def partial(name, options={}) (options[:locals] ||= {}).update :form => self options.update :partial => name @template.render options end
widget(*args, &block)
click to toggle source
# File lib/form_assistant/form_builder.rb, line 257 def widget(*args, &block) options = args.extract_options! fields = args.shift || nil field = Array === fields ? fields.first : fields label_options = extract_options_for_label(field, options) template_options = extract_options_for_template(self.fallback_template, options) label = label_options[:label] === false ? nil : self.label(field, label_options.delete(:text), label_options) tip = options.delete(:tip) locals = options.delete(:locals) required = !!options.delete(:required) if block_given? element = without_assistance do @template.capture(&block) end else element = nil end partial = render_partial_for(element, fields, label, tip, template_options[:template], 'widget', required, locals, args) RPH::FormAssistant::Rules.binding_required? ? @template.concat(partial, block.binding) : @template.concat(partial) end
without_assistance(options={}) { || ... }
click to toggle source
# File lib/form_assistant/form_builder.rb, line 243 def without_assistance(options={}, &block) # TODO - allow options to only turn off templates and/or labels ignore_labels, ignore_templates = self.class.ignore_labels, self.class.ignore_templates begin self.class.ignore_labels, self.class.ignore_templates = true, true result = yield ensure self.class.ignore_labels, self.class.ignore_templates = ignore_labels, ignore_templates end result end
Protected Instance Methods
extract_options_for_label(field, options={})
click to toggle source
# File lib/form_assistant/form_builder.rb, line 151 def extract_options_for_label(field, options={}) label_options = {} # consider the global setting for labels and # allow for turning labels off on a per-helper basis # <%= form.text_field :title, :label => false %> if self.class.ignore_labels || options[:label] === false || field.blank? label_options[:label] = false else # ensure that the :label option is a Hash from this point on options[:label] ||= {} # allow for a cleaner way of setting label text # <%= form.text_field :whatever, :label => 'Whatever Title' %> label_options.merge!(options[:label].is_a?(String) ? {:text => options[:label]} : options[:label]) # allow for a more convenient way to set common label options # <%= form.text_field :whatever, :label_id => 'dom_id' %> # <%= form.text_field :whatever, :label_class => 'required' %> # <%= form.text_field :whatever, :label_text => 'Whatever' %> %w(id class text).each do |option| label_option = "label_#{option}".to_sym label_options.merge!(option.to_sym => options.delete(label_option)) if options[label_option] end # Ensure we have default label text # (since Rails' label() does not currently respect I18n) label_options[:text] ||= object.class.human_attribute_name(field.to_s) end label_options end
extract_options_for_template(helper_name, options={})
click to toggle source
# File lib/form_assistant/form_builder.rb, line 184 def extract_options_for_template(helper_name, options={}) template_options = {} if options.has_key?(:template) && options[:template].kind_of?(FalseClass) template_options[:template] = false else # grab the template template = options.delete(:template) || helper_name.to_s template = self.fallback_template unless template_exists?(template) template_options[:template] = template end template_options end
render_element(element, field, name, options, ignore_label = false)
click to toggle source
render the element with an optional label (does not use the templates)
# File lib/form_assistant/form_builder.rb, line 131 def render_element(element, field, name, options, ignore_label = false) return element if ignore_label # need to consider if the shortcut label option was used # i.e. <%= form.text_field :title, :label => 'Project Title' %> text, label_options = if options[:label].is_a?(String) [options.delete(:label), {}] else [options[:label].delete(:text), options.delete(:label)] end # consider trailing labels if %w(check_box radio_button).include?(name) label_options[:class] = (label_options[:class].to_s + ' inline').strip element + label(field, text, label_options) else label(field, text, label_options) + element end end
render_partial_for(element, field, label, tip, template, helper, required, extra_locals, args)
click to toggle source
renders the appropriate partial located in the template root
# File lib/form_assistant/form_builder.rb, line 123 def render_partial_for(element, field, label, tip, template, helper, required, extra_locals, args) errors = self.class.ignore_errors ? nil : error_message_for(field) locals = (extra_locals || {}).merge(:element => element, :field => field, :builder => self, :object => object, :object_name => object_name, :label => label, :errors => errors, :tip => tip, :helper => helper, :required => required) @template.render :partial => "#{self.class.template_root}/#{template}.html.erb", :locals => locals end
Private Instance Methods
error_message_for(fields)
click to toggle source
get the error messages (if any) for a field
# File lib/form_assistant/form_builder.rb, line 81 def error_message_for(fields) errors = [] fields = [fields] unless Array === fields fields.each do |field| next unless has_errors?(field) errors += if RPH::FormAssistant::Rules.has_I18n_support? full_messages_for(field) else human_field_name = field.to_s.humanize errors += [*object.errors[field]].map do |error| "#{human_field_name} #{error}" end end end errors.empty? ? nil : RPH::FormAssistant::FieldErrors.new(errors) end
full_messages_for(field)
click to toggle source
Returns full error messages for given field (uses I18n)
# File lib/form_assistant/form_builder.rb, line 102 def full_messages_for(field) attr_name = object.class.human_attribute_name(field.to_s) object.errors[field].inject([]) do |full_messages, message| next unless message full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message end end
has_errors?(field)
click to toggle source
returns true if a field is invalid
# File lib/form_assistant/form_builder.rb, line 112 def has_errors?(field) !(object.nil? || object.errors[field].blank?) end
template_exists?(template)
click to toggle source
checks to make sure the template exists
# File lib/form_assistant/form_builder.rb, line 117 def template_exists?(template) File.exists?(File.join(self.class.template_root(true), "_#{template}.html.erb")) end