# FluxValidator - AJAX Form Validation for Rails # Copyright (c) 2007 Peter Gumeson # # FluxValidator is freely distributable under the terms of an MIT license #------------------------------------ class String # Return a reference to the class or module matching the String def to_class self.split('::').inject(Object) { |s,k| Kernel.const_get(k) } rescue nil end end #------------------------------------ # validations.rb module ActiveRecord::Validations::ClassMethods # Overrides the validation rule that validates a password or email address # field with a confirmation. Except this version adds the error message # to the confirmation field instead of the defined field. def validates_confirmation_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) attr_accessor *(attr_names.map { |n| "#{n}_confirmation" }) validates_each(attr_names, configuration) do |record, attr_name, value| record.errors.add("#{attr_name}_confirmation", "doesn't match #{attr_name}") unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation") end end end #------------------------------------ # active_record_helper.rb module ActionView class Base @@field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" } cattr_accessor :field_error_proc @@field_blank_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" } cattr_accessor :field_blank_error_proc end module Helpers class InstanceTag attr_reader :wrapper_id def tag(name, options) @wrapper_id = options["id"]+'_field' if options["id"] wrap_element = (options["id"].nil? || name.to_s == 'div' || name.to_s == 'span') ? false : true if wrap_element && object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name)) elsif wrap_element blank_error_wrapping(tag_without_error_wrapping(name, options)) else tag_without_error_wrapping(name, options) end end def content_tag(name, value, options) @wrapper_id = options["id"]+'_field' if options["id"] wrap_element = (options["id"].nil? || name.to_s == 'div' || name.to_s == 'span') ? false : true if wrap_element && object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name)) elsif wrap_element blank_error_wrapping(content_tag_without_error_wrapping(name, value, options)) else content_tag_without_error_wrapping(name, value, options) end end 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 blank_error_wrapping(to_date_select_tag_without_error_wrapping(options)) end end 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 blank_error_wrapping(to_datetime_select_tag_without_error_wrapping(options)) end end 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 blank_error_wrapping(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) : Base.field_blank_error_proc.call(html_tag, self) end def blank_error_wrapping(html_tag) Base.field_blank_error_proc.call(html_tag, self) end end end end #------------------------------------ # form_helper.rb module ActionView::Helpers module FormHelper # Returns a string containing the error message attached to the +method+ on the +object+, if one exists. # This error message is wrapped in a SPAN tag, which can be specialized to include both a +prepend_text+ and +append_text+ # to properly introduce the error. Examples (post has an error message "can't be empty" on the title attribute): # # <%= error_message_on "post", "title" %> => #
can't be empty # # <%= error_message_on "post", "title", {:class => "inputError"}, "Title simply ", " (or it won't work)" %> => # Title simply can't be empty (or it won't work) def error_message_on(object_name, method, options = {}, prepend_text = "", append_text = "") if options.instance_of?(String) prepend_text = options append_text = prepend_text options = {} options[:css_class] = append_text end InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_error_message_on_tag(options, prepend_text, append_text) end # Returns a list containing the error messages attached to the +method+ on the +object+, if one exists. # Each error message is wrapped in a LI tag, which can be specialized to include both a +prepend_text+ and +append_text+ # to properly introduce the error. # <%= error_messages_on "post", "title" %> => # def error_messages_on(object_name, method, options = {}, prepend_text = "", append_text = "") InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_error_messages_on_tag(options, prepend_text, append_text) end # Returns the inner html for error_messages_on def inner_error_messages_on(object_name, method, prepend_text = '', append_text = '') prepend_text = "#{method.to_s.humanize.downcase} " if method if (obj = instance_variable_get("@#{object_name}")) && (errors = obj.errors.on(method)) errors = [errors] unless errors.is_a?(Array) error_messages = errors.map {|error| content_tag(:li, "#{prepend_text}#{error}#{append_text}") } content_tag(:ul, error_messages) else '' end end def instance_variable(name) instance_variable_get("@#{name}") end end class InstanceTag #:nodoc: def to_error_message_on_tag(options = {}, prepend_text = "", append_text = "") options = options.stringify_keys add_error_id(options) options["class"] ||= "formError" prepend_text = "#{@method_name.to_s.humanize.downcase} " if @method_name if (obj = @template_object.instance_variable(@object_name)) && (errors = obj.errors.on(@method_name)) content_tag("span", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", options) else '' end end def to_error_messages_on_tag(options = {}, prepend_text = "", append_text = "") options = options.stringify_keys add_errors_id(options) options["class"] ||= "formErrors" if (obj = @template_object.instance_variable(@object_name)) && (errors = obj.errors.on(@method_name)) inner = @template_object.inner_error_messages_on(@object_name, @method_name, prepend_text, append_text) else options.delete("class") inner = '' end content_tag(:span, inner, options) end private def add_error_id(options) if options.has_key?("index") options["id"] ||= "#{@object_name}_#{options["index"]}_#{@method_name}_error" options.delete("index") elsif defined?(@auto_index) options["id"] ||= "#{@object_name}_#{@auto_index}_#{@method_name}_error" else options["id"] ||= "#{@object_name}_#{@method_name}_error" end end def add_errors_id(options) if options.has_key?("index") options["id"] ||= "#{@object_name}_#{options["index"]}_#{@method_name}_errors" options.delete("index") elsif defined?(@auto_index) options["id"] ||= "#{@object_name}_#{@auto_index}_#{@method_name}_errors" else options["id"] ||= "#{@object_name}_#{@method_name}_errors" end end end class FormBuilder #:nodoc: self.field_helpers << "error_message_on" self.field_helpers << "error_messages_on" self.field_helpers << "inner_error_messages_on" def error_message_on(method, options = {:class => "formError"}, prepend_text = "", append_text = "") if options.instance_of?(String) prepend_text = options append_text = prepend_text options = {} options[:css_class] = append_text end @template.error_message_on(@object_name, method, options.merge(:object => @object), prepend_text, append_text) end def error_messages_on(method, options = {:class => "formErrors"}, prepend_text = "", append_text = "") @template.error_messages_on(@object_name, method, options.merge(:object => @object), prepend_text, append_text) end end end #------------------------------------ # form__tag_helper.rb module ActionView::Helpers::FormTagHelper def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block) html_options = options.stringify_keys html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") html_options["action"] = url_for(url_for_options, *parameters_for_url) method_tag = "" case method = html_options.delete("method").to_s when /^get$/i # must be case-insentive, but can't use downcase as might be nil html_options["method"] = "get" options.merge!({:method => "get".to_sym}) when /^post$/i, "", nil html_options["method"] = "post" options.merge!({:method => "post".to_sym}) else html_options["method"] = "post" method_tag = content_tag(:span, tag(:input, :type => "hidden", :name => "_method", :value => method), :style => 'margin:0;padding:0') options.merge!({:method => method.to_sym}) end validation_tag = "" result = ActionController::Routing::Routes.recognize_path(html_options["action"], options) validation_path = url_for(:controller => result[:controller], :action => "validate_#{result[:action]}_form") validation_tag = content_tag(:div, tag(:input, :type => "hidden", :name => "_validation", :value => validation_path), :style => 'margin:0;padding:0') if block_given? content = capture(&block) concat(tag(:form, html_options, true) + method_tag + validation_tag, block.binding) concat(content, block.binding) concat("", block.binding) else tag(:form, html_options, true) + method_tag + validation_tag end end end #------------------------------------ # date_helper.rb class ActionView::Helpers::InstanceTag def options_with_prefix(position, options) prefix = "#{@object_name}" if options[:index] prefix << "[#{options[:index]}]" elsif @auto_index prefix << "[#{@auto_index}]" end @wrapper_id = "#{prefix}_#{@method_name}_field" options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]") end end