andrewroth-client_side_validations 3.2.0.beta.4
Sign up to get free protection for your applications and to get access to all the features.
- data/client_side_validations.gemspec +28 -0
- data/lib/client_side_validations.rb +18 -0
- data/lib/client_side_validations/action_view.rb +14 -0
- data/lib/client_side_validations/action_view/form_builder.rb +176 -0
- data/lib/client_side_validations/action_view/form_helper.rb +105 -0
- data/lib/client_side_validations/action_view/form_tag_helper.rb +12 -0
- data/lib/client_side_validations/active_model.rb +68 -0
- data/lib/client_side_validations/active_model/acceptance.rb +10 -0
- data/lib/client_side_validations/active_model/exclusion.rb +15 -0
- data/lib/client_side_validations/active_model/format.rb +10 -0
- data/lib/client_side_validations/active_model/inclusion.rb +15 -0
- data/lib/client_side_validations/active_model/length.rb +26 -0
- data/lib/client_side_validations/active_model/numericality.rb +33 -0
- data/lib/client_side_validations/active_model/presence.rb +10 -0
- data/lib/client_side_validations/active_record.rb +12 -0
- data/lib/client_side_validations/active_record/middleware.rb +50 -0
- data/lib/client_side_validations/active_record/uniqueness.rb +28 -0
- data/lib/client_side_validations/core_ext.rb +3 -0
- data/lib/client_side_validations/core_ext/range.rb +10 -0
- data/lib/client_side_validations/core_ext/regexp.rb +14 -0
- data/lib/client_side_validations/engine.rb +6 -0
- data/lib/client_side_validations/files.rb +8 -0
- data/lib/client_side_validations/generators.rb +12 -0
- data/lib/client_side_validations/generators/rails_validations.rb +15 -0
- data/lib/client_side_validations/middleware.rb +105 -0
- data/lib/client_side_validations/version.rb +3 -0
- data/lib/generators/client_side_validations/copy_assets_generator.rb +56 -0
- data/lib/generators/client_side_validations/install_generator.rb +22 -0
- data/lib/generators/templates/client_side_validations/initializer.rb +11 -0
- data/vendor/assets/javascripts/rails.validations.js +424 -0
- metadata +213 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "client_side_validations/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "andrewroth-client_side_validations"
|
7
|
+
s.version = ClientSideValidations::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Brian Cardarella"]
|
10
|
+
s.email = ["bcardarella@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/bcardarella/client_side_validations"
|
12
|
+
s.summary = %q{Client Side Validations}
|
13
|
+
s.description = %q{Client Side Validations}
|
14
|
+
|
15
|
+
s.files = `git ls-files -- {lib/*,vendor/*,*.gemspec}`.split("\n")
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency 'rails', '~> 3.2.0'
|
19
|
+
s.add_development_dependency 'sqlite3'
|
20
|
+
s.add_development_dependency 'mocha'
|
21
|
+
|
22
|
+
# For QUnit testing
|
23
|
+
s.add_development_dependency 'sinatra', '~> 1.0'
|
24
|
+
s.add_development_dependency 'shotgun'
|
25
|
+
s.add_development_dependency 'thin'
|
26
|
+
s.add_development_dependency 'json'
|
27
|
+
s.add_development_dependency 'coffee-script'
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ClientSideValidations
|
2
|
+
module Config
|
3
|
+
class << self
|
4
|
+
attr_accessor :uniqueness_validator_disabled
|
5
|
+
@uniqueness_validator_disabled = false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'client_side_validations/active_model' if defined?(::ActiveModel)
|
11
|
+
require 'client_side_validations/active_record' if defined?(::ActiveRecord)
|
12
|
+
require 'client_side_validations/action_view' if defined?(::ActionView)
|
13
|
+
if defined?(::Rails)
|
14
|
+
require 'client_side_validations/generators'
|
15
|
+
require 'client_side_validations/middleware'
|
16
|
+
require 'client_side_validations/engine'
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ClientSideValidations::ActionView
|
2
|
+
module Helpers
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'client_side_validations/core_ext'
|
7
|
+
require 'client_side_validations/action_view/form_helper'
|
8
|
+
require 'client_side_validations/action_view/form_tag_helper'
|
9
|
+
require 'client_side_validations/action_view/form_builder'
|
10
|
+
|
11
|
+
ActionView::Base.send(:include, ClientSideValidations::ActionView::Helpers::FormHelper)
|
12
|
+
ActionView::Base.send(:include, ClientSideValidations::ActionView::Helpers::FormTagHelper)
|
13
|
+
ActionView::Helpers::FormBuilder.send(:include, ClientSideValidations::ActionView::Helpers::FormBuilder)
|
14
|
+
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module ClientSideValidations::ActionView::Helpers
|
2
|
+
module FormBuilder
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
(base.field_helpers.map(&:to_s) - %w(apply_form_for_options! label check_box radio_button fields_for hidden_field)).each do |selector|
|
6
|
+
base.class_eval <<-RUBY_EVAL
|
7
|
+
def #{selector}_with_client_side_validations(method, options = {})
|
8
|
+
apply_client_side_validators(method, options)
|
9
|
+
options.delete(:validate)
|
10
|
+
#{selector}_without_client_side_validations(method, options)
|
11
|
+
end
|
12
|
+
RUBY_EVAL
|
13
|
+
|
14
|
+
base.class_eval { alias_method_chain selector, :client_side_validations }
|
15
|
+
end
|
16
|
+
|
17
|
+
base.class_eval do
|
18
|
+
alias_method_chain :initialize, :client_side_validations
|
19
|
+
alias_method_chain :fields_for, :client_side_validations
|
20
|
+
alias_method_chain :check_box, :client_side_validations
|
21
|
+
alias_method_chain :radio_button, :client_side_validations
|
22
|
+
alias_method_chain :select, :client_side_validations
|
23
|
+
alias_method_chain :collection_select, :client_side_validations
|
24
|
+
alias_method_chain :grouped_collection_select, :client_side_validations
|
25
|
+
alias_method_chain :time_zone_select, :client_side_validations
|
26
|
+
|
27
|
+
def self.client_side_form_settings(options, form_helper)
|
28
|
+
{
|
29
|
+
:type => self.to_s,
|
30
|
+
:input_tag => form_helper.class.field_error_proc.call(%{<span id="input_tag" />}, Struct.new(:error_message, :tag_id).new([], "")),
|
31
|
+
:label_tag => form_helper.class.field_error_proc.call(%{<label id="label_tag" />}, Struct.new(:error_message, :tag_id).new([], ""))
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize_with_client_side_validations(object_name, object, template, options, proc)
|
38
|
+
initialize_without_client_side_validations(object_name, object, template, options, proc)
|
39
|
+
@options[:validators] = {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def fields_for_with_client_side_validations(record_or_name_or_array, *args, &block)
|
43
|
+
options = args.extract_options!
|
44
|
+
options[:validate] ||= @options[:validate] if @options[:validate] && !options.key?(:validate)
|
45
|
+
fields_for_without_client_side_validations(record_or_name_or_array, *(args << options), &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_box_with_client_side_validations(method, options = {}, checked_value = "1", unchecked_value = "0")
|
49
|
+
apply_client_side_validators(method, options)
|
50
|
+
check_box_without_client_side_validations(method, options, checked_value, unchecked_value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def radio_button_with_client_side_validations(method, tag_value, options = {})
|
54
|
+
apply_client_side_validators(method, options)
|
55
|
+
radio_button_without_client_side_validations(method, tag_value, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def select_with_client_side_validations(method, choices, options = {}, html_options = {})
|
59
|
+
apply_client_side_validators(method, html_options)
|
60
|
+
select_without_client_side_validations(method, choices, options, html_options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def collection_select_with_client_side_validations(method, collection, value_method, text_method, options = {}, html_options = {})
|
64
|
+
apply_client_side_validators(method, html_options)
|
65
|
+
collection_select_without_client_side_validations(method, collection, value_method, text_method, options, html_options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def grouped_collection_select_with_client_side_validations(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
|
69
|
+
apply_client_side_validators(method, html_options)
|
70
|
+
grouped_collection_select_without_client_side_validations(method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def time_zone_select_with_client_side_validations(method, priority_zones = nil, options = {}, html_options = {})
|
74
|
+
apply_client_side_validators(method, html_options)
|
75
|
+
time_zone_select_without_client_side_validations(method, priority_zones = nil, options, html_options)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def apply_client_side_validators(method, options = {})
|
81
|
+
if @options[:validate] && options[:validate] != false && validators = filter_validators(method, options[:validate])
|
82
|
+
options.merge!("data-validate" => true)
|
83
|
+
name = options[:name] || "#{@object_name}[#{method}]"
|
84
|
+
child_index = @options[:child_index] ? "(\\d+|#{Regexp.escape(@options[:child_index])})" : "\\d+"
|
85
|
+
name = name.gsub(/_attributes\]\[#{child_index}\]/, '_attributes][]')
|
86
|
+
@options[:validators].merge!("#{name}#{options[:multiple] ? "[]" : nil}" => validators)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def filter_validators(method, filters)
|
91
|
+
if validators = @object.client_side_validation_hash[method]
|
92
|
+
unfiltered_validators = validators.inject({}) do |unfiltered_validators, validator|
|
93
|
+
unfiltered_validators[validator.first] = validator.last
|
94
|
+
if has_filter_for_validator?(validator, filters)
|
95
|
+
if filter_validator?(validator, filters)
|
96
|
+
unfiltered_validators.delete(validator.first)
|
97
|
+
elsif force_validator_despite_conditional?(validator, filters) && !can_run_validator?(validator, method)
|
98
|
+
unfiltered_validators.delete(validator.first)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
if (conditional = (validator.last[:if] || validator.last[:unless]))
|
102
|
+
result = case conditional
|
103
|
+
when Symbol
|
104
|
+
if @object.respond_to?(conditional)
|
105
|
+
@object.send(conditional)
|
106
|
+
else
|
107
|
+
raise(ArgumentError, "unknown method called '#{conditional}'")
|
108
|
+
end
|
109
|
+
when String
|
110
|
+
eval(conditional)
|
111
|
+
when Proc
|
112
|
+
conditional.call(@object)
|
113
|
+
end
|
114
|
+
|
115
|
+
# :if was specified and result is false OR :unless was specified and result was true
|
116
|
+
if (validator.last[:if] && !result) || (validator.last[:unless] && result)
|
117
|
+
unfiltered_validators.delete(validator.first)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
unfiltered_validators[validator.first].delete(:if) if unfiltered_validators[validator.first]
|
122
|
+
unfiltered_validators[validator.first].delete(:unless) if unfiltered_validators[validator.first]
|
123
|
+
unfiltered_validators
|
124
|
+
end
|
125
|
+
|
126
|
+
unfiltered_validators.empty? ? nil : unfiltered_validators
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def has_filter_for_validator?(validator, filters)
|
131
|
+
filters && (filters == true || filters.key?(validator.first))
|
132
|
+
end
|
133
|
+
|
134
|
+
def filter_validator?(validator, filters)
|
135
|
+
filters != true && filters[validator.first] == false
|
136
|
+
end
|
137
|
+
|
138
|
+
def force_validator_despite_conditional?(validator, filters)
|
139
|
+
filters == true || filters[validator.first] == true
|
140
|
+
end
|
141
|
+
|
142
|
+
def can_run_validator?(validator, method)
|
143
|
+
result = true
|
144
|
+
if_result = run_if_validator(validator.last[:if], method)
|
145
|
+
unless_result = run_unless_validator(validator.last[:unless], method)
|
146
|
+
result = result && if_result unless if_result.nil?
|
147
|
+
result = result && unless_result unless unless_result.nil?
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
def run_if_validator(conditional, method)
|
152
|
+
if conditional
|
153
|
+
if conditional.is_a?(Symbol)
|
154
|
+
conditional_method_is_change_method?(conditional, method) ? true : !!@object.send(conditional)
|
155
|
+
else
|
156
|
+
!!conditional.call(@object)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def run_unless_validator(conditional, method)
|
162
|
+
if conditional
|
163
|
+
if conditional.is_a?(Symbol)
|
164
|
+
conditional_method_is_change_method?(conditional, method) ? true : !@object.send(conditional)
|
165
|
+
else
|
166
|
+
!conditional.call(@object)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def conditional_method_is_change_method?(conditional, method)
|
172
|
+
conditional.to_sym == "#{method}_changed?".to_sym
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module ClientSideValidations::ActionView::Helpers
|
2
|
+
module FormHelper
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
def form_for(record_or_name_or_array, *args, &proc)
|
6
|
+
options = args.extract_options!
|
7
|
+
if options[:validate]
|
8
|
+
|
9
|
+
# Always turn off HTML5 Validations
|
10
|
+
options[:html] ||= {}
|
11
|
+
options[:html][:novalidate] = 'novalidate'
|
12
|
+
|
13
|
+
case record_or_name_or_array
|
14
|
+
when String, Symbol
|
15
|
+
raise ClientSideValidations::ActionView::Helpers::FormHelper::Error, 'Using form_for(:name, @resource) is deprecated in Rails and is not supported with ClientSideValidations. Please use form_for(@resource, :as => :name) instead.'
|
16
|
+
when Array
|
17
|
+
object = record_or_name_or_array.last
|
18
|
+
else
|
19
|
+
object = record_or_name_or_array
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@validators = {}
|
24
|
+
|
25
|
+
# Order matters here. Rails mutates the options object
|
26
|
+
script = client_side_form_settings(object, options)
|
27
|
+
form = super(record_or_name_or_array, *(args << options), &proc)
|
28
|
+
|
29
|
+
# Because of the load order requirement above this sub is necessary
|
30
|
+
# Would be nice to not do this
|
31
|
+
script = insert_validators_into_script(script)
|
32
|
+
|
33
|
+
if assign_script_to_content_for(options[:validate], script)
|
34
|
+
form.html_safe
|
35
|
+
else
|
36
|
+
"#{form}#{script}".html_safe
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def assign_script_to_content_for(name, script)
|
41
|
+
if name && name != true
|
42
|
+
content_for(name) { script.html_safe }
|
43
|
+
true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def apply_form_for_options!(object_or_array, options)
|
48
|
+
super
|
49
|
+
options[:html][:validate] = true if options[:validate]
|
50
|
+
end
|
51
|
+
|
52
|
+
def fields_for(record_or_name_or_array, *args, &block)
|
53
|
+
output = super
|
54
|
+
@validators.merge!(args.last[:validators]) if @validators
|
55
|
+
output
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def insert_validators_into_script(script)
|
61
|
+
# There is probably a more performant way of doing this
|
62
|
+
# But using String#sub has some issues. Undocumented "features"
|
63
|
+
if script
|
64
|
+
script = script.split(/"validator_hash"/)
|
65
|
+
script = "#{script[0]}#{@validators.to_json}#{script[1]}"
|
66
|
+
end
|
67
|
+
|
68
|
+
script
|
69
|
+
end
|
70
|
+
|
71
|
+
def client_side_form_settings(object, options)
|
72
|
+
if options[:validate]
|
73
|
+
builder = options[:builder] || ActionView::Base.default_form_builder
|
74
|
+
|
75
|
+
if options[:html] && options[:html][:id]
|
76
|
+
var_name = options[:html][:id]
|
77
|
+
else
|
78
|
+
if Rails.version >= '3.2.0'
|
79
|
+
var_name = if object.respond_to?(:persisted?) && object.persisted?
|
80
|
+
options[:as] ? "edit_#{options[:as]}" : [options[:namespace], dom_id(object, :edit)].compact.join("_")
|
81
|
+
else
|
82
|
+
options[:as] ? "new_#{options[:as]}" : [options[:namespace], dom_id(object)].compact.join("_")
|
83
|
+
end
|
84
|
+
else
|
85
|
+
# This is to maintain backward compatibility with Rails 3.1
|
86
|
+
# see: https://github.com/rails/rails/commit/e29773f885fd500189ffd964550ae20061d745ba#commitcomment-948052
|
87
|
+
var_name = if object.respond_to?(:persisted?) && object.persisted?
|
88
|
+
options[:as] ? "#{options[:as]}_edit" : dom_id(object, :edit)
|
89
|
+
else
|
90
|
+
options[:as] ? "#{options[:as]}_new" : dom_id(object)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
content_tag(:script) do
|
98
|
+
"//<![CDATA[\nwindow.ClientSideValidations.forms['#{var_name}'] = #{builder.client_side_form_settings(options, self).merge(:validators => 'validator_hash').to_json};\n//]]>".html_safe
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ClientSideValidations::ActionView::Helpers
|
2
|
+
module FormTagHelper
|
3
|
+
private
|
4
|
+
def html_options_for_form(url_for_options, options, *parameters_for_url)
|
5
|
+
options.stringify_keys!
|
6
|
+
html_options = {}
|
7
|
+
html_options['data-validate'] = options.delete('validate') if options['validate']
|
8
|
+
html_options.merge!(super(url_for_options, options, *parameters_for_url))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'client_side_validations/core_ext'
|
2
|
+
|
3
|
+
module ClientSideValidations::ActiveModel
|
4
|
+
module Validator
|
5
|
+
|
6
|
+
def client_side_hash(model, attribute)
|
7
|
+
options = self.options.dup
|
8
|
+
{ :message => model.errors.generate_message(attribute, message_type, options) }.merge(options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:allow_blank, :if, :unless]))
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy_conditional_attributes(to, from)
|
12
|
+
[:if, :unless].each { |key| to[key] = from[key] if from[key].present? }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def message_type
|
18
|
+
kind
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Validations
|
23
|
+
def client_side_validation_hash
|
24
|
+
@client_side_validation_hash ||= _validators.inject({}) do |attr_hash, attr|
|
25
|
+
unless [nil, :block].include?(attr[0])
|
26
|
+
|
27
|
+
validator_hash = attr[1].inject({}) do |kind_hash, validator|
|
28
|
+
client_side_hash = validator.client_side_hash(self, attr[0])
|
29
|
+
# Yeah yeah, #new_record? is not part of ActiveModel :p
|
30
|
+
if can_use_for_client_side_validation?(client_side_hash, validator) && uniqueness_validations_allowed_or_not_applicable?(validator)
|
31
|
+
kind_hash.merge!(validator.kind => client_side_hash.except(:on))
|
32
|
+
else
|
33
|
+
kind_hash.merge!({})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if validator_hash.present?
|
38
|
+
attr_hash.merge!(attr[0] => validator_hash)
|
39
|
+
else
|
40
|
+
attr_hash
|
41
|
+
end
|
42
|
+
else
|
43
|
+
attr_hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def can_use_for_client_side_validation?(client_side_hash, validator)
|
51
|
+
((self.respond_to?(:new_record?) && validator.options[:on] == (self.new_record? ? :create : :update)) || validator.options[:on].nil?) && validator.kind != :block
|
52
|
+
end
|
53
|
+
|
54
|
+
def uniqueness_validations_allowed_or_not_applicable?(validator)
|
55
|
+
validator.kind != :uniqueness || !ClientSideValidations::Config.uniqueness_validator_disabled
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
ActiveModel::Validator.send(:include, ClientSideValidations::ActiveModel::Validator)
|
61
|
+
ActiveModel::Validations.send(:include, ClientSideValidations::ActiveModel::Validations)
|
62
|
+
|
63
|
+
%w{acceptance exclusion inclusion length format numericality presence}.each do |validator|
|
64
|
+
require "client_side_validations/active_model/#{validator}"
|
65
|
+
validator.capitalize!
|
66
|
+
eval "ActiveModel::Validations::#{validator}Validator.send(:include, ClientSideValidations::ActiveModel::#{validator})"
|
67
|
+
end
|
68
|
+
|