andrewroth-client_side_validations 3.2.0.beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|