# 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" %> =>
#
- can't be empty
- must have at least 2 characters
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