require 'cgi' require File.dirname(__FILE__) + '/form_helper' module ActionView class Base @@field_error_proc = Proc.new{ |html_tag, instance| "
#{html_tag}
" } cattr_accessor :field_error_proc end module Helpers # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This # is a great of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form. # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html module ActiveRecordHelper # Returns a default input tag for the type of object returned by the method. For example, let's say you have a model # that has an attribute +title+ of type VARCHAR column, and this instance holds "Hello World": # input("post", "title") => # def input(record_name, method, options = {}) InstanceTag.new(record_name, method, self).to_tag(options) end # Returns an entire form with all needed input tags for a specified Active Record object. For example, let's say you # have a table model Post with attributes named title of type VARCHAR and body of type TEXT: # form("post") # That line would yield a form like the following: #
#

#
# #

#

#
# #

# #
# # It's possible to specialize the form builder by using a different action name and by supplying another # block renderer. For example, let's say you have a model Entry with an attribute message of type VARCHAR: # # form("entry", :action => "sign", :input_block => # Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}
" }) => # #
# Message: #
# #
# # It's also possible to add additional content to the form by giving it a block, such as: # # form("entry", :action => "sign") do |form| # form << content_tag("b", "Department") # form << collection_select("department", "id", @departments, "id", "name") # end def form(record_name, options = {}) record = instance_variable_get("@#{record_name}") options = options.symbolize_keys options[:action] ||= record.new_record? ? "create" : "update" action = url_for(:action => options[:action], :id => record) submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize contents = '' contents << hidden_field(record_name, :id) unless record.new_record? contents << all_input_tags(record, record_name, options) yield contents if block_given? contents << submit_tag(submit_value) content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil) end # Returns a string containing the error message attached to the +method+ on the +object+ if one exists. # This error message is wrapped in a DIV tag, which can be extended to include a +prepend_text+ and/or +append_text+ # (to properly explain the error), and a +css_class+ to style it accordingly. As an example, let's say you have a model # +post+ that has an error message on the +title+ attribute: # # <%= error_message_on "post", "title" %> => #
can't be empty
# # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> => #
Title simply can't be empty (or it won't work).
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError") if (obj = instance_variable_get("@#{object}")) && (errors = obj.errors.on(method)) content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class) else '' end end # Returns a string with a DIV containing all of the error messages for the objects located as instance variables by the names # given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are # provided. # # This DIV can be tailored by the following options: # # * header_tag - Used for the header of the error div (default: h2) # * id - The id of the error div (default: errorExplanation) # * class - The class of the error div (default: errorExplanation) # * object_name - The object name to use in the header, or # any text that you prefer. If object_name is not set, the name of # the first object will be used. # # To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model: # # error_messages_for 'user' # # To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which # be the name in the header. # # error_messages_for 'user_common', 'user', :object_name => 'user' # # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors # instance yourself and set it up. View the source of this method to see how easy it is. def error_messages_for(*params) options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {} objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact count = objects.inject(0) {|sum, object| sum + object.errors.count } unless count.zero? html = {} [:id, :class].each do |key| if options.include?(key) value = options[key] html[key] = value unless value.blank? else html[key] = 'errorExplanation' end end header_message = "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] || params.first).to_s.gsub('_', ' ')} from being saved" error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } } content_tag(:div, content_tag(options[:header_tag] || :h2, header_message) << content_tag(:p, 'There were problems with the following fields:') << content_tag(:ul, error_messages), html ) else '' end end private def all_input_tags(record, record_name, options) input_block = options[:input_block] || default_input_block record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n") end def default_input_block Proc.new { |record, column| %(


#{input(record, column.name)}

) } end end class InstanceTag #:nodoc: def to_tag(options = {}) case column_type when :string field_type = @method_name.include?("password") ? "password" : "text" to_input_field_tag(field_type, options) when :text to_text_area_tag(options) when :integer, :float, :decimal to_input_field_tag("text", options) when :date to_date_select_tag(options) when :datetime, :timestamp to_datetime_select_tag(options) when :time to_time_select_tag(options) when :boolean to_boolean_select_tag(options) end end alias_method :tag_without_error_wrapping, :tag def tag(name, options) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name)) else tag_without_error_wrapping(name, options) end end alias_method :content_tag_without_error_wrapping, :content_tag def content_tag(name, value, options) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name)) else content_tag_without_error_wrapping(name, value, options) end end alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag def to_date_select_tag(options = {}) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) else to_date_select_tag_without_error_wrapping(options) end end alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag def to_datetime_select_tag(options = {}) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) else to_datetime_select_tag_without_error_wrapping(options) end end alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag def to_time_select_tag(options = {}) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) else to_time_select_tag_without_error_wrapping(options) end end def error_wrapping(html_tag, has_error) has_error ? Base.field_error_proc.call(html_tag, self) : html_tag end def error_message object.errors.on(@method_name) end def column_type object.send("column_for_attribute", @method_name).type end end end end