client_side_validations 8.0.2 → 9.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fed0feff4a513664392a746868722e04d5812444
4
- data.tar.gz: b0c93e7e5a06f28666f63241b9a446378ce39376
3
+ metadata.gz: 9517f4c6685202d257a67fed4948d6ddc6663104
4
+ data.tar.gz: 2a185c425e483fc2b2776da0715c9312bb6abf0c
5
5
  SHA512:
6
- metadata.gz: d92f7babb26e804be9a632593c70319dff02af7a9332f185eb6fbc9a894dc16195729eb7df31d50fea765d8c16a1b89f93f9337879347ace22654334e09b87d1
7
- data.tar.gz: 6aef78c42b2ab083780fdcdc4379597b6a370e026b5ad7b3b96e84bf85fdc1591457a77c4b5b2bb15c5a0f4c5b3fafef76df31eeaf6d59f846fd80bd9aea51fb
6
+ metadata.gz: 746dabde16b38f4b5941016d38ada3108ea3884b1c20ae655bf24aa3dddcc4cfd752403c77dfd09beba577180df71ff696f1809b1ac8d12c71d2c24364453f3d
7
+ data.tar.gz: 7a6527a1c051063414572982dac3c3372e301a04f94b766d56f26b396214110deeda09e7947ea1bbdc1c0a5df81f2277642abe193b17c3ef897b0203c44e4214
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.0.0 (2017-01-31)
4
+
5
+ * Unobtrusive JavaScript
6
+
3
7
  ## 8.0.2 (2017-01-31)
4
8
 
5
9
  * Under the hood improvements
data/README.md CHANGED
@@ -72,9 +72,7 @@ Rails `FormBuilders`. Please see the [Plugin wiki page](https://github.com/DavyJ
72
72
  (feel free to add your own)
73
73
 
74
74
  * [SimpleForm](https://github.com/DavyJonesLocker/client_side_validations-simple_form)
75
- * [Formtastic](https://github.com/DavyJonesLocker/client_side_validations-formtastic)
76
75
  * [Mongoid](https://github.com/DavyJonesLocker/client_side_validations-mongoid)
77
- * [MongoMapper](https://github.com/DavyJonesLocker/client_side_validations-mongo_mapper)
78
76
 
79
77
  ## Usage ##
80
78
 
@@ -98,21 +96,6 @@ In your `FormBuilder` you only need to enable validations:
98
96
 
99
97
  That should be enough to get you going.
100
98
 
101
- By default the validators will be serialized and embedded in a
102
- `<script>` tag following the `<form>` tag. If you would like to render
103
- that `<script>` tag elsewhere you can do this by passing a name to
104
- `:validate`
105
-
106
- ```erb
107
- <%= form_for @user, validate: :user_validators do |f| %>
108
- ```
109
-
110
- The `<script`> tag is added to `content_for()` with the name you passed,
111
- so you can simply render that anywhere you like:
112
-
113
- ```erb
114
- <%= yield(:user_validators) %>
115
- ```
116
99
 
117
100
  ## Conditional Validators ##
118
101
 
@@ -187,20 +170,15 @@ You can even turn them off per fieldset:
187
170
  ...
188
171
  ```
189
172
 
190
- ## Understanding the embedded `<script>` tag ##
173
+ ## Understanding the client side validations data attribute ##
191
174
 
192
- A rendered form with validations will always have a `<script>` appended
193
- directly after:
194
-
195
- ```html
196
- <script>//<![CDATA[if(window.ClientSideValidations==undefined)window.ClientSideValidations={};if(window.ClientSideValidations.forms==undefined)window.ClientSideValidations.forms={};window.ClientSideValidations.forms['new_person'] = {"type":"ActionView::Helpers::FormBuilder","input_tag":"<div class=\"field_with_errors\"><span id=\"input_tag\" /><label for=\"\" class=\"message\"></label></div>","label_tag":"<div class=\"field_with_errors\"><label id=\"label_tag\" /></div>","validators":{"person[name]":{"inclusion":[{"message":"is not included in the list","in":["Happy"]}]}}};//]]></script>
197
- ```
175
+ A rendered form with validations will always have a `data-client-side-validations` attribute.
198
176
 
199
- This script registers a new form object on `ClientSideValidations.form`. The key is equal to the ID of the form that is rendered. The objects it contains will have different keys depending upon the `FormBuilder` being used. However, `type` and `validators` will always be present.
177
+ The objects it contains will have different keys depending upon the `FormBuilder` being used. However, `html_settings` and `validators` will always be present.
200
178
 
201
- ### `type` ###
179
+ ### `html_settings` ###
202
180
 
203
- This will always be equal to the class of the `FormBuilder` that did the rendering. The type will be used by the JavaScript to determine how to `add` and `remove` the error messages. If you create a new `FormBuilder` you will need to write your own handlers for adding and removing.
181
+ This will always contain the type to the class of the `FormBuilder` that did the rendering. The type will be used by the JavaScript to determine how to `add` and `remove` the error messages. If you create a new `FormBuilder`, you will need to write your own handlers for adding and removing.
204
182
 
205
183
  ### `validators` ###
206
184
 
@@ -7,9 +7,7 @@ end
7
7
 
8
8
  require 'client_side_validations/core_ext'
9
9
  require 'client_side_validations/action_view/form_helper'
10
- require 'client_side_validations/action_view/form_tag_helper'
11
10
  require 'client_side_validations/action_view/form_builder'
12
11
 
13
12
  ActionView::Base.send(:include, ClientSideValidations::ActionView::Helpers::FormHelper)
14
- ActionView::Base.send(:include, ClientSideValidations::ActionView::Helpers::FormTagHelper)
15
13
  ActionView::Helpers::FormBuilder.send(:prepend, ClientSideValidations::ActionView::Helpers::FormBuilder)
@@ -9,40 +9,29 @@ module ClientSideValidations
9
9
  def form_for(record, options = {}, &block)
10
10
  return super unless options[:validate]
11
11
 
12
- turn_off_html5_validations! options
12
+ # We are not going to use super here, because we need
13
+ # to inject the csv options in a data attribute in a clean way.
14
+ # So we basically reimplement the whole form_for method
15
+ raise ArgumentError, 'Missing block' unless block_given?
16
+ html_options = options[:html] ||= {}
13
17
 
14
- case record
15
- when String, Symbol
16
- raise ClientSideValidations::ActionView::Helpers::FormHelper::Error, 'Using form_for(:name, @resource) is not supported with ClientSideValidations. Please use form_for(@resource, as: :name) instead.'
17
- else
18
- object = record.is_a?(Array) ? record.last : record
19
- object_name = options[:as] || model_name_from_record_or_class(object).param_key
20
- end
18
+ # Moving the switch statement to another method to
19
+ # lower complexity
20
+ object, object_name = check_record(record, options)
21
21
 
22
22
  @validators = {}
23
23
 
24
- # Order matters here. Rails mutates the `options` object
25
- form = super
24
+ apply_html_options! options, html_options
26
25
 
27
- build_bound_validators! options
28
26
  builder = instantiate_builder(object_name, object, options)
29
- script = client_side_form_settings(options, builder)
27
+ output = capture(builder, &block)
28
+ html_options[:multipart] ||= builder.multipart?
30
29
 
31
- if assign_script_to_content_for(options[:validate], script)
32
- form
33
- else
34
- form << script
35
- end
36
- end
37
-
38
- def assign_script_to_content_for(name, script)
39
- return false if name == true
40
-
41
- # rubocop:disable OutputSafety
42
- content_for name, script.html_safe
43
- # rubocop:enable OutputSafety
30
+ build_bound_validators! options
44
31
 
45
- true
32
+ apply_csv_html_options! html_options, options, builder
33
+ html_options = html_options_for_form(options[:url] || {}, html_options)
34
+ form_tag_with_body(html_options, output)
46
35
  end
47
36
 
48
37
  def apply_form_for_options!(record, object, options)
@@ -61,6 +50,20 @@ module ClientSideValidations
61
50
 
62
51
  private
63
52
 
53
+ def check_record(record, options)
54
+ case record
55
+ when String, Symbol
56
+ raise ClientSideValidations::ActionView::Helpers::FormHelper::Error, 'Using form_for(:name, @resource) is not supported with ClientSideValidations. Please use form_for(@resource, as: :name) instead.'
57
+ else
58
+ object = record.is_a?(Array) ? record.last : record
59
+ raise ArgumentError, 'First argument in form cannot contain nil or be empty' unless object
60
+ object_name = options[:as] || model_name_from_record_or_class(object).param_key
61
+ apply_form_for_options!(record, object, options)
62
+ end
63
+
64
+ [object, object_name]
65
+ end
66
+
64
67
  def build_bound_validators!(options)
65
68
  return unless @validators
66
69
 
@@ -90,26 +93,35 @@ module ClientSideValidations
90
93
  end
91
94
  end
92
95
 
93
- def client_side_form_settings(options, builder)
94
- javascript_tag "if(window.ClientSideValidations===undefined)window.ClientSideValidations={};window.ClientSideValidations.disabled_validators=#{ClientSideValidations::Config.disabled_validators.to_json};window.ClientSideValidations.number_format=#{number_format.to_json};if(window.ClientSideValidations.patterns===undefined)window.ClientSideValidations.patterns = {};window.ClientSideValidations.patterns.numericality=#{numericality_patterns};#{"if(window.ClientSideValidations.remote_validators_prefix===undefined)window.ClientSideValidations.remote_validators_prefix='#{ClientSideValidations::Config.root_path.sub(%r{/+\Z}, '')}';" if ClientSideValidations::Config.root_path.present?}if(window.ClientSideValidations.forms===undefined)window.ClientSideValidations.forms={};window.ClientSideValidations.forms['#{options[:html]['id']}'] = #{builder.client_side_form_settings(options, self).merge(validators: construct_validators).to_json};"
95
- end
96
-
97
96
  def number_format
98
- @number_format ||=
99
- if ClientSideValidations::Config.number_format_with_locale && defined?(I18n)
100
- I18n.t('number.format').slice :separator, :delimiter
101
- else
102
- { separator: '.', delimiter: ',' }
103
- end
97
+ if ClientSideValidations::Config.number_format_with_locale && defined?(I18n)
98
+ I18n.t('number.format').slice :separator, :delimiter
99
+ else
100
+ { separator: '.', delimiter: ',' }
101
+ end
104
102
  end
105
103
 
106
- def numericality_patterns
107
- "/^(-|\\+)?(?:\\d+|\\d{1,3}(?:\\#{number_format[:delimiter]}\\d{3})+)(?:\\#{number_format[:separator]}\\d*)?$/"
104
+ def apply_html_options!(options, html_options)
105
+ # Turn off HTML5 validations
106
+ html_options[:novalidate] = 'novalidate'
107
+
108
+ html_options[:data] = options.delete(:data) if options.key?(:data)
109
+ html_options[:remote] = options.delete(:remote) if options.key?(:remote)
110
+ html_options[:method] = options.delete(:method) if options.key?(:method)
111
+ html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.key?(:enforce_utf8)
112
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
108
113
  end
109
114
 
110
- def turn_off_html5_validations!(options)
111
- options[:html] ||= {}
112
- options[:html][:novalidate] = 'novalidate'
115
+ def apply_csv_html_options!(html_options, options, builder)
116
+ html_options.delete :validate
117
+
118
+ csv_options = {
119
+ html_settings: builder.client_side_form_settings(options, self),
120
+ number_format: number_format,
121
+ validators: construct_validators
122
+ }
123
+
124
+ html_options['data-client-side-validations'] = csv_options.to_json
113
125
  end
114
126
  end
115
127
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ClientSideValidations
3
- VERSION = '8.0.2'.freeze
3
+ VERSION = '9.0.0'.freeze
4
4
  end
@@ -1,12 +1,12 @@
1
1
 
2
2
  /*!
3
- * Client Side Validations - v8.0.2 (https://github.com/DavyJonesLocker/client_side_validations)
3
+ * Client Side Validations - v9.0.0 (https://github.com/DavyJonesLocker/client_side_validations)
4
4
  * Copyright (c) 2017 Geremia Taglialatela, Brian Cardarella
5
5
  * Licensed under MIT (http://opensource.org/licenses/mit-license.php)
6
6
  */
7
7
 
8
8
  (function() {
9
- var $, validateElement, validateForm, validatorsFor,
9
+ var $, ClientSideValidations, initializeOnEvent, validateElement, validateForm, validatorsFor,
10
10
  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
11
11
 
12
12
  $ = jQuery;
@@ -50,6 +50,8 @@
50
50
  }
51
51
  };
52
52
 
53
+ initializeOnEvent = (window.Turbolinks != null) && window.Turbolinks.supported ? window.Turbolinks.EVENTS != null ? 'page:change' : 'turbolinks:load' : 'ready';
54
+
53
55
  validatorsFor = function(name, validators) {
54
56
  var captures, validator, validator_name;
55
57
  if (captures = name.match(/\[(\w+_attributes)\].*\[(\w+)\]$/)) {
@@ -135,471 +137,434 @@
135
137
  return afterValidate();
136
138
  };
137
139
 
138
- if (window.ClientSideValidations === void 0) {
139
- window.ClientSideValidations = {};
140
- }
141
-
142
- if (window.ClientSideValidations.forms === void 0) {
143
- window.ClientSideValidations.forms = {};
144
- }
145
-
146
- window.ClientSideValidations.selectors = {
147
- inputs: ':input:not(button):not([type="submit"])[name]:visible:enabled',
148
- validate_inputs: ':input:enabled:visible[data-validate]',
149
- forms: 'form[data-validate]'
150
- };
151
-
152
- window.ClientSideValidations.reset = function(form) {
153
- var $form, key;
154
- $form = $(form);
155
- ClientSideValidations.disable(form);
156
- for (key in form.ClientSideValidations.settings.validators) {
157
- form.ClientSideValidations.removeError($form.find("[name='" + key + "']"));
158
- }
159
- return ClientSideValidations.enablers.form(form);
160
- };
161
-
162
- window.ClientSideValidations.disable = function(target) {
163
- var $target;
164
- $target = $(target);
165
- $target.off('.ClientSideValidations');
166
- if ($target.is('form')) {
167
- return ClientSideValidations.disable($target.find(':input'));
168
- } else {
169
- $target.removeData('valid');
170
- $target.removeData('changed');
171
- return $target.filter(':input').each(function() {
172
- return $(this).removeAttr('data-validate');
173
- });
174
- }
175
- };
176
-
177
- window.ClientSideValidations.enablers = {
178
- form: function(form) {
179
- var $form, binding, event, ref;
180
- $form = $(form);
181
- form.ClientSideValidations = {
182
- settings: window.ClientSideValidations.forms[$form.attr('id')],
183
- addError: function(element, message) {
184
- return ClientSideValidations.formBuilders[form.ClientSideValidations.settings.type].add(element, form.ClientSideValidations.settings, message);
140
+ ClientSideValidations = {
141
+ callbacks: {
142
+ element: {
143
+ after: function(element, eventData) {},
144
+ before: function(element, eventData) {},
145
+ fail: function(element, message, addError, eventData) {
146
+ return addError();
185
147
  },
186
- removeError: function(element) {
187
- return ClientSideValidations.formBuilders[form.ClientSideValidations.settings.type].remove(element, form.ClientSideValidations.settings);
148
+ pass: function(element, removeError, eventData) {
149
+ return removeError();
188
150
  }
189
- };
190
- ref = {
191
- 'submit.ClientSideValidations': function(eventData) {
192
- if (!$form.isValid(form.ClientSideValidations.settings.validators)) {
193
- eventData.preventDefault();
194
- eventData.stopImmediatePropagation();
151
+ },
152
+ form: {
153
+ after: function(form, eventData) {},
154
+ before: function(form, eventData) {},
155
+ fail: function(form, eventData) {},
156
+ pass: function(form, eventData) {}
157
+ }
158
+ },
159
+ enablers: {
160
+ form: function(form) {
161
+ var $form, binding, event, ref;
162
+ $form = $(form);
163
+ form.ClientSideValidations = {
164
+ settings: $form.data('clientSideValidations'),
165
+ addError: function(element, message) {
166
+ return ClientSideValidations.formBuilders[form.ClientSideValidations.settings.html_settings.type].add(element, form.ClientSideValidations.settings.html_settings, message);
167
+ },
168
+ removeError: function(element) {
169
+ return ClientSideValidations.formBuilders[form.ClientSideValidations.settings.html_settings.type].remove(element, form.ClientSideValidations.settings.html_settings);
195
170
  }
196
- },
197
- 'ajax:beforeSend.ClientSideValidations': function(eventData) {
198
- if (eventData.target === this) {
199
- $form.isValid(form.ClientSideValidations.settings.validators);
171
+ };
172
+ ref = {
173
+ 'submit.ClientSideValidations': function(eventData) {
174
+ if (!$form.isValid(form.ClientSideValidations.settings.validators)) {
175
+ eventData.preventDefault();
176
+ eventData.stopImmediatePropagation();
177
+ }
178
+ },
179
+ 'ajax:beforeSend.ClientSideValidations': function(eventData) {
180
+ if (eventData.target === this) {
181
+ $form.isValid(form.ClientSideValidations.settings.validators);
182
+ }
183
+ },
184
+ 'form:validate:after.ClientSideValidations': function(eventData) {
185
+ ClientSideValidations.callbacks.form.after($form, eventData);
186
+ },
187
+ 'form:validate:before.ClientSideValidations': function(eventData) {
188
+ ClientSideValidations.callbacks.form.before($form, eventData);
189
+ },
190
+ 'form:validate:fail.ClientSideValidations': function(eventData) {
191
+ ClientSideValidations.callbacks.form.fail($form, eventData);
192
+ },
193
+ 'form:validate:pass.ClientSideValidations': function(eventData) {
194
+ ClientSideValidations.callbacks.form.pass($form, eventData);
200
195
  }
201
- },
202
- 'form:validate:after.ClientSideValidations': function(eventData) {
203
- ClientSideValidations.callbacks.form.after($form, eventData);
204
- },
205
- 'form:validate:before.ClientSideValidations': function(eventData) {
206
- ClientSideValidations.callbacks.form.before($form, eventData);
207
- },
208
- 'form:validate:fail.ClientSideValidations': function(eventData) {
209
- ClientSideValidations.callbacks.form.fail($form, eventData);
210
- },
211
- 'form:validate:pass.ClientSideValidations': function(eventData) {
212
- ClientSideValidations.callbacks.form.pass($form, eventData);
196
+ };
197
+ for (event in ref) {
198
+ binding = ref[event];
199
+ $form.on(event, binding);
200
+ }
201
+ return $form.find(ClientSideValidations.selectors.inputs).each(function() {
202
+ return ClientSideValidations.enablers.input(this);
203
+ });
204
+ },
205
+ input: function(input) {
206
+ var $form, $input, binding, event, form, ref;
207
+ $input = $(input);
208
+ form = input.form;
209
+ $form = $(form);
210
+ ref = {
211
+ 'focusout.ClientSideValidations': function() {
212
+ $(this).isValid(form.ClientSideValidations.settings.validators);
213
+ },
214
+ 'change.ClientSideValidations': function() {
215
+ $(this).data('changed', true);
216
+ },
217
+ 'element:validate:after.ClientSideValidations': function(eventData) {
218
+ ClientSideValidations.callbacks.element.after($(this), eventData);
219
+ },
220
+ 'element:validate:before.ClientSideValidations': function(eventData) {
221
+ ClientSideValidations.callbacks.element.before($(this), eventData);
222
+ },
223
+ 'element:validate:fail.ClientSideValidations': function(eventData, message) {
224
+ var element;
225
+ element = $(this);
226
+ ClientSideValidations.callbacks.element.fail(element, message, function() {
227
+ return form.ClientSideValidations.addError(element, message);
228
+ }, eventData);
229
+ },
230
+ 'element:validate:pass.ClientSideValidations': function(eventData) {
231
+ var element;
232
+ element = $(this);
233
+ ClientSideValidations.callbacks.element.pass(element, function() {
234
+ return form.ClientSideValidations.removeError(element);
235
+ }, eventData);
236
+ }
237
+ };
238
+ for (event in ref) {
239
+ binding = ref[event];
240
+ $input.filter(':not(:radio):not([id$=_confirmation])').each(function() {
241
+ return $(this).attr('data-validate', true);
242
+ }).on(event, binding);
213
243
  }
214
- };
215
- for (event in ref) {
216
- binding = ref[event];
217
- $form.on(event, binding);
244
+ $input.filter(':checkbox').on('change.ClientSideValidations', function() {
245
+ $(this).isValid(form.ClientSideValidations.settings.validators);
246
+ });
247
+ return $input.filter('[id$=_confirmation]').each(function() {
248
+ var confirmationElement, element, ref1, results;
249
+ confirmationElement = $(this);
250
+ element = $form.find("#" + (this.id.match(/(.+)_confirmation/)[1]) + ":input");
251
+ if (element[0]) {
252
+ ref1 = {
253
+ 'focusout.ClientSideValidations': function() {
254
+ element.data('changed', true).isValid(form.ClientSideValidations.settings.validators);
255
+ },
256
+ 'keyup.ClientSideValidations': function() {
257
+ element.data('changed', true).isValid(form.ClientSideValidations.settings.validators);
258
+ }
259
+ };
260
+ results = [];
261
+ for (event in ref1) {
262
+ binding = ref1[event];
263
+ results.push($("#" + (confirmationElement.attr('id'))).on(event, binding));
264
+ }
265
+ return results;
266
+ }
267
+ });
218
268
  }
219
- return $form.find(ClientSideValidations.selectors.inputs).each(function() {
220
- return ClientSideValidations.enablers.input(this);
221
- });
222
269
  },
223
- input: function(input) {
224
- var $form, $input, binding, event, form, ref;
225
- $input = $(input);
226
- form = input.form;
227
- $form = $(form);
228
- ref = {
229
- 'focusout.ClientSideValidations': function() {
230
- $(this).isValid(form.ClientSideValidations.settings.validators);
270
+ formBuilders: {
271
+ 'ActionView::Helpers::FormBuilder': {
272
+ add: function(element, settings, message) {
273
+ var form, inputErrorField, label, labelErrorField;
274
+ form = $(element[0].form);
275
+ if (element.data('valid') !== false && (form.find("label.message[for='" + (element.attr('id')) + "']")[0] == null)) {
276
+ inputErrorField = $(settings.input_tag);
277
+ labelErrorField = $(settings.label_tag);
278
+ label = form.find("label[for='" + (element.attr('id')) + "']:not(.message)");
279
+ if (element.attr('autofocus')) {
280
+ element.attr('autofocus', false);
281
+ }
282
+ element.before(inputErrorField);
283
+ inputErrorField.find('span#input_tag').replaceWith(element);
284
+ inputErrorField.find('label.message').attr('for', element.attr('id'));
285
+ labelErrorField.find('label.message').attr('for', element.attr('id'));
286
+ labelErrorField.insertAfter(label);
287
+ labelErrorField.find('label#label_tag').replaceWith(label);
288
+ }
289
+ return form.find("label.message[for='" + (element.attr('id')) + "']").text(message);
231
290
  },
232
- 'change.ClientSideValidations': function() {
233
- $(this).data('changed', true);
291
+ remove: function(element, settings) {
292
+ var errorFieldClass, form, inputErrorField, label, labelErrorField;
293
+ form = $(element[0].form);
294
+ errorFieldClass = $(settings.input_tag).attr('class');
295
+ inputErrorField = element.closest("." + (errorFieldClass.replace(/\ /g, ".")));
296
+ label = form.find("label[for='" + (element.attr('id')) + "']:not(.message)");
297
+ labelErrorField = label.closest("." + errorFieldClass);
298
+ if (inputErrorField[0]) {
299
+ inputErrorField.find("#" + (element.attr('id'))).detach();
300
+ inputErrorField.replaceWith(element);
301
+ label.detach();
302
+ return labelErrorField.replaceWith(label);
303
+ }
304
+ }
305
+ }
306
+ },
307
+ patterns: {
308
+ numericality: function(number_format) {
309
+ return new RegExp("^(-|\\+)?(?:\\d+|\\d{1,3}(?:\\" + number_format.delimiter + "\\d{3})+)(?:\\" + number_format.separator + "\\d*)?$");
310
+ }
311
+ },
312
+ selectors: {
313
+ inputs: ':input:not(button):not([type="submit"])[name]:visible:enabled',
314
+ validate_inputs: ':input:enabled:visible[data-validate]',
315
+ forms: 'form[data-client-side-validations]'
316
+ },
317
+ validators: {
318
+ all: function() {
319
+ return $.extend({}, local, remote);
320
+ },
321
+ local: {
322
+ absence: function(element, options) {
323
+ if (!/^\s*$/.test(element.val() || '')) {
324
+ return options.message;
325
+ }
234
326
  },
235
- 'element:validate:after.ClientSideValidations': function(eventData) {
236
- ClientSideValidations.callbacks.element.after($(this), eventData);
327
+ presence: function(element, options) {
328
+ if (/^\s*$/.test(element.val() || '')) {
329
+ return options.message;
330
+ }
237
331
  },
238
- 'element:validate:before.ClientSideValidations': function(eventData) {
239
- ClientSideValidations.callbacks.element.before($(this), eventData);
332
+ acceptance: function(element, options) {
333
+ var ref;
334
+ switch (element.attr('type')) {
335
+ case 'checkbox':
336
+ if (!element.prop('checked')) {
337
+ return options.message;
338
+ }
339
+ break;
340
+ case 'text':
341
+ if (element.val() !== (((ref = options.accept) != null ? ref.toString() : void 0) || '1')) {
342
+ return options.message;
343
+ }
344
+ }
240
345
  },
241
- 'element:validate:fail.ClientSideValidations': function(eventData, message) {
242
- var element;
243
- element = $(this);
244
- ClientSideValidations.callbacks.element.fail(element, message, function() {
245
- return form.ClientSideValidations.addError(element, message);
246
- }, eventData);
346
+ format: function(element, options) {
347
+ var message;
348
+ message = this.presence(element, options);
349
+ if (message) {
350
+ if (options.allow_blank === true) {
351
+ return;
352
+ }
353
+ return message;
354
+ }
355
+ if (options["with"] && !new RegExp(options["with"].source, options["with"].options).test(element.val())) {
356
+ return options.message;
357
+ }
358
+ if (options.without && new RegExp(options.without.source, options.without.options).test(element.val())) {
359
+ return options.message;
360
+ }
247
361
  },
248
- 'element:validate:pass.ClientSideValidations': function(eventData) {
249
- var element;
250
- element = $(this);
251
- ClientSideValidations.callbacks.element.pass(element, function() {
252
- return form.ClientSideValidations.removeError(element);
253
- }, eventData);
254
- }
255
- };
256
- for (event in ref) {
257
- binding = ref[event];
258
- $input.filter(':not(:radio):not([id$=_confirmation])').each(function() {
259
- return $(this).attr('data-validate', true);
260
- }).on(event, binding);
261
- }
262
- $input.filter(':checkbox').on('change.ClientSideValidations', function() {
263
- $(this).isValid(form.ClientSideValidations.settings.validators);
264
- });
265
- return $input.filter('[id$=_confirmation]').each(function() {
266
- var confirmationElement, element, ref1, results;
267
- confirmationElement = $(this);
268
- element = $form.find("#" + (this.id.match(/(.+)_confirmation/)[1]) + ":input");
269
- if (element[0]) {
270
- ref1 = {
271
- 'focusout.ClientSideValidations': function() {
272
- element.data('changed', true).isValid(form.ClientSideValidations.settings.validators);
273
- },
274
- 'keyup.ClientSideValidations': function() {
275
- element.data('changed', true).isValid(form.ClientSideValidations.settings.validators);
362
+ numericality: function(element, options) {
363
+ var $form, CHECKS, check, checkValue, fn, number_format, operator, val;
364
+ $form = $(element[0].form);
365
+ val = $.trim(element.val());
366
+ number_format = $form[0].ClientSideValidations.settings.number_format;
367
+ if (!ClientSideValidations.patterns.numericality(number_format).test(val)) {
368
+ if (options.allow_blank === true && this.presence(element, {
369
+ message: options.messages.numericality
370
+ })) {
371
+ return;
276
372
  }
277
- };
278
- results = [];
279
- for (event in ref1) {
280
- binding = ref1[event];
281
- results.push($("#" + (confirmationElement.attr('id'))).on(event, binding));
373
+ return options.messages.numericality;
282
374
  }
283
- return results;
284
- }
285
- });
286
- }
287
- };
288
-
289
- window.ClientSideValidations.validators = {
290
- all: function() {
291
- return $.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote);
292
- },
293
- local: {
294
- absence: function(element, options) {
295
- if (!/^\s*$/.test(element.val() || '')) {
296
- return options.message;
297
- }
298
- },
299
- presence: function(element, options) {
300
- if (/^\s*$/.test(element.val() || '')) {
301
- return options.message;
302
- }
303
- },
304
- acceptance: function(element, options) {
305
- var ref;
306
- switch (element.attr('type')) {
307
- case 'checkbox':
308
- if (!element.prop('checked')) {
309
- return options.message;
375
+ val = val.replace(new RegExp("\\" + number_format.delimiter, 'g'), '').replace(new RegExp("\\" + number_format.separator, 'g'), '.');
376
+ if (options.only_integer && !/^[+-]?\d+$/.test(val)) {
377
+ return options.messages.only_integer;
378
+ }
379
+ CHECKS = {
380
+ greater_than: '>',
381
+ greater_than_or_equal_to: '>=',
382
+ equal_to: '==',
383
+ less_than: '<',
384
+ less_than_or_equal_to: '<='
385
+ };
386
+ for (check in CHECKS) {
387
+ operator = CHECKS[check];
388
+ if (!(options[check] != null)) {
389
+ continue;
310
390
  }
311
- break;
312
- case 'text':
313
- if (element.val() !== (((ref = options.accept) != null ? ref.toString() : void 0) || '1')) {
314
- return options.message;
391
+ checkValue = !isNaN(parseFloat(options[check])) && isFinite(options[check]) ? options[check] : $form.find("[name*=" + options[check] + "]").length === 1 ? $form.find("[name*=" + options[check] + "]").val() : void 0;
392
+ if ((checkValue == null) || checkValue === '') {
393
+ return;
394
+ }
395
+ fn = new Function("return " + val + " " + operator + " " + checkValue);
396
+ if (!fn()) {
397
+ return options.messages[check];
315
398
  }
316
- }
317
- },
318
- format: function(element, options) {
319
- var message;
320
- message = this.presence(element, options);
321
- if (message) {
322
- if (options.allow_blank === true) {
323
- return;
324
- }
325
- return message;
326
- }
327
- if (options["with"] && !new RegExp(options["with"].source, options["with"].options).test(element.val())) {
328
- return options.message;
329
- }
330
- if (options.without && new RegExp(options.without.source, options.without.options).test(element.val())) {
331
- return options.message;
332
- }
333
- },
334
- numericality: function(element, options) {
335
- var CHECKS, check, checkValue, fn, form, operator, val;
336
- val = $.trim(element.val());
337
- if (!ClientSideValidations.patterns.numericality.test(val)) {
338
- if (options.allow_blank === true && this.presence(element, {
339
- message: options.messages.numericality
340
- })) {
341
- return;
342
399
  }
343
- return options.messages.numericality;
344
- }
345
- val = val.replace(new RegExp("\\" + ClientSideValidations.number_format.delimiter, 'g'), '').replace(new RegExp("\\" + ClientSideValidations.number_format.separator, 'g'), '.');
346
- if (options.only_integer && !/^[+-]?\d+$/.test(val)) {
347
- return options.messages.only_integer;
348
- }
349
- CHECKS = {
350
- greater_than: '>',
351
- greater_than_or_equal_to: '>=',
352
- equal_to: '==',
353
- less_than: '<',
354
- less_than_or_equal_to: '<='
355
- };
356
- form = $(element[0].form);
357
- for (check in CHECKS) {
358
- operator = CHECKS[check];
359
- if (!(options[check] != null)) {
360
- continue;
400
+ if (options.odd && !(parseInt(val, 10) % 2)) {
401
+ return options.messages.odd;
361
402
  }
362
- checkValue = !isNaN(parseFloat(options[check])) && isFinite(options[check]) ? options[check] : form.find("[name*=" + options[check] + "]").length === 1 ? form.find("[name*=" + options[check] + "]").val() : void 0;
363
- if ((checkValue == null) || checkValue === '') {
364
- return;
403
+ if (options.even && (parseInt(val, 10) % 2)) {
404
+ return options.messages.even;
365
405
  }
366
- fn = new Function("return " + val + " " + operator + " " + checkValue);
367
- if (!fn()) {
368
- return options.messages[check];
406
+ },
407
+ length: function(element, options) {
408
+ var CHECKS, blankOptions, check, fn, message, operator, tokenized_length, tokenizer;
409
+ tokenizer = options.js_tokenizer || "split('')";
410
+ tokenized_length = new Function('element', "return (element.val()." + tokenizer + " || '').length")(element);
411
+ CHECKS = {
412
+ is: '==',
413
+ minimum: '>=',
414
+ maximum: '<='
415
+ };
416
+ blankOptions = {};
417
+ blankOptions.message = options.is ? options.messages.is : options.minimum ? options.messages.minimum : void 0;
418
+ message = this.presence(element, blankOptions);
419
+ if (message) {
420
+ if (options.allow_blank === true) {
421
+ return;
422
+ }
423
+ return message;
369
424
  }
370
- }
371
- if (options.odd && !(parseInt(val, 10) % 2)) {
372
- return options.messages.odd;
373
- }
374
- if (options.even && (parseInt(val, 10) % 2)) {
375
- return options.messages.even;
376
- }
377
- },
378
- length: function(element, options) {
379
- var CHECKS, blankOptions, check, fn, message, operator, tokenized_length, tokenizer;
380
- tokenizer = options.js_tokenizer || "split('')";
381
- tokenized_length = new Function('element', "return (element.val()." + tokenizer + " || '').length")(element);
382
- CHECKS = {
383
- is: '==',
384
- minimum: '>=',
385
- maximum: '<='
386
- };
387
- blankOptions = {};
388
- blankOptions.message = options.is ? options.messages.is : options.minimum ? options.messages.minimum : void 0;
389
- message = this.presence(element, blankOptions);
390
- if (message) {
391
- if (options.allow_blank === true) {
392
- return;
425
+ for (check in CHECKS) {
426
+ operator = CHECKS[check];
427
+ if (!options[check]) {
428
+ continue;
429
+ }
430
+ fn = new Function("return " + tokenized_length + " " + operator + " " + options[check]);
431
+ if (!fn()) {
432
+ return options.messages[check];
433
+ }
393
434
  }
394
- return message;
395
- }
396
- for (check in CHECKS) {
397
- operator = CHECKS[check];
398
- if (!options[check]) {
399
- continue;
435
+ },
436
+ exclusion: function(element, options) {
437
+ var lower, message, option, ref, upper;
438
+ message = this.presence(element, options);
439
+ if (message) {
440
+ if (options.allow_blank === true) {
441
+ return;
442
+ }
443
+ return message;
400
444
  }
401
- fn = new Function("return " + tokenized_length + " " + operator + " " + options[check]);
402
- if (!fn()) {
403
- return options.messages[check];
445
+ if (options["in"]) {
446
+ if (ref = element.val(), indexOf.call((function() {
447
+ var i, len, ref1, results;
448
+ ref1 = options["in"];
449
+ results = [];
450
+ for (i = 0, len = ref1.length; i < len; i++) {
451
+ option = ref1[i];
452
+ results.push(option.toString());
453
+ }
454
+ return results;
455
+ })(), ref) >= 0) {
456
+ return options.message;
457
+ }
404
458
  }
405
- }
406
- },
407
- exclusion: function(element, options) {
408
- var lower, message, option, ref, upper;
409
- message = this.presence(element, options);
410
- if (message) {
411
- if (options.allow_blank === true) {
412
- return;
459
+ if (options.range) {
460
+ lower = options.range[0];
461
+ upper = options.range[1];
462
+ if (element.val() >= lower && element.val() <= upper) {
463
+ return options.message;
464
+ }
413
465
  }
414
- return message;
415
- }
416
- if (options["in"]) {
417
- if (ref = element.val(), indexOf.call((function() {
418
- var i, len, ref1, results;
419
- ref1 = options["in"];
420
- results = [];
421
- for (i = 0, len = ref1.length; i < len; i++) {
422
- option = ref1[i];
423
- results.push(option.toString());
466
+ },
467
+ inclusion: function(element, options) {
468
+ var lower, message, option, ref, upper;
469
+ message = this.presence(element, options);
470
+ if (message) {
471
+ if (options.allow_blank === true) {
472
+ return;
424
473
  }
425
- return results;
426
- })(), ref) >= 0) {
427
- return options.message;
474
+ return message;
428
475
  }
429
- }
430
- if (options.range) {
431
- lower = options.range[0];
432
- upper = options.range[1];
433
- if (element.val() >= lower && element.val() <= upper) {
476
+ if (options["in"]) {
477
+ if (ref = element.val(), indexOf.call((function() {
478
+ var i, len, ref1, results;
479
+ ref1 = options["in"];
480
+ results = [];
481
+ for (i = 0, len = ref1.length; i < len; i++) {
482
+ option = ref1[i];
483
+ results.push(option.toString());
484
+ }
485
+ return results;
486
+ })(), ref) >= 0) {
487
+ return;
488
+ }
434
489
  return options.message;
435
490
  }
436
- }
437
- },
438
- inclusion: function(element, options) {
439
- var lower, message, option, ref, upper;
440
- message = this.presence(element, options);
441
- if (message) {
442
- if (options.allow_blank === true) {
443
- return;
444
- }
445
- return message;
446
- }
447
- if (options["in"]) {
448
- if (ref = element.val(), indexOf.call((function() {
449
- var i, len, ref1, results;
450
- ref1 = options["in"];
451
- results = [];
452
- for (i = 0, len = ref1.length; i < len; i++) {
453
- option = ref1[i];
454
- results.push(option.toString());
491
+ if (options.range) {
492
+ lower = options.range[0];
493
+ upper = options.range[1];
494
+ if (element.val() >= lower && element.val() <= upper) {
495
+ return;
455
496
  }
456
- return results;
457
- })(), ref) >= 0) {
458
- return;
497
+ return options.message;
459
498
  }
460
- return options.message;
461
- }
462
- if (options.range) {
463
- lower = options.range[0];
464
- upper = options.range[1];
465
- if (element.val() >= lower && element.val() <= upper) {
466
- return;
499
+ },
500
+ confirmation: function(element, options) {
501
+ var regex;
502
+ regex = new RegExp("^" + (element.val()) + "$", options.case_sensitive ? '' : 'i');
503
+ if (!regex.test($("#" + (element.attr('id')) + "_confirmation").val())) {
504
+ return options.message;
467
505
  }
468
- return options.message;
469
- }
470
- },
471
- confirmation: function(element, options) {
472
- var regex;
473
- regex = new RegExp("^" + (element.val()) + "$", options.case_sensitive ? '' : 'i');
474
- if (!regex.test($("#" + (element.attr('id')) + "_confirmation").val())) {
475
- return options.message;
476
- }
477
- },
478
- uniqueness: function(element, options) {
479
- var form, matches, name, name_prefix, name_suffix, valid, value;
480
- name = element.attr('name');
481
- if (/_attributes\]\[\d/.test(name)) {
482
- matches = name.match(/^(.+_attributes\])\[\d+\](.+)$/);
483
- name_prefix = matches[1];
484
- name_suffix = matches[2];
485
- value = element.val();
486
- if (name_prefix && name_suffix) {
487
- form = element.closest('form');
488
- valid = true;
489
- form.find(":input[name^=\"" + name_prefix + "\"][name$=\"" + name_suffix + "\"]").each(function() {
490
- if ($(this).attr('name') !== name) {
491
- if ($(this).val() === value) {
492
- valid = false;
493
- return $(this).data('notLocallyUnique', true);
494
- } else {
495
- if ($(this).data('notLocallyUnique')) {
496
- return $(this).removeData('notLocallyUnique').data('changed', true);
506
+ },
507
+ uniqueness: function(element, options) {
508
+ var form, matches, name, name_prefix, name_suffix, valid, value;
509
+ name = element.attr('name');
510
+ if (/_attributes\]\[\d/.test(name)) {
511
+ matches = name.match(/^(.+_attributes\])\[\d+\](.+)$/);
512
+ name_prefix = matches[1];
513
+ name_suffix = matches[2];
514
+ value = element.val();
515
+ if (name_prefix && name_suffix) {
516
+ form = element.closest('form');
517
+ valid = true;
518
+ form.find(":input[name^=\"" + name_prefix + "\"][name$=\"" + name_suffix + "\"]").each(function() {
519
+ if ($(this).attr('name') !== name) {
520
+ if ($(this).val() === value) {
521
+ valid = false;
522
+ return $(this).data('notLocallyUnique', true);
523
+ } else {
524
+ if ($(this).data('notLocallyUnique')) {
525
+ return $(this).removeData('notLocallyUnique').data('changed', true);
526
+ }
497
527
  }
498
528
  }
529
+ });
530
+ if (!valid) {
531
+ return options.message;
499
532
  }
500
- });
501
- if (!valid) {
502
- return options.message;
503
533
  }
504
534
  }
505
535
  }
506
- }
536
+ },
537
+ remote: {}
507
538
  },
508
- remote: {}
509
- };
510
-
511
- window.ClientSideValidations.remote_validators_url_for = function(validator) {
512
- if (ClientSideValidations.remote_validators_prefix != null) {
513
- return "//" + window.location.host + "/" + ClientSideValidations.remote_validators_prefix + "/validators/" + validator;
514
- } else {
515
- return "//" + window.location.host + "/validators/" + validator;
516
- }
517
- };
518
-
519
- window.ClientSideValidations.disableValidators = function() {
520
- var func, ref, results, validator;
521
- if (window.ClientSideValidations.disabled_validators === void 0) {
522
- return;
523
- }
524
- ref = window.ClientSideValidations.validators.remote;
525
- results = [];
526
- for (validator in ref) {
527
- func = ref[validator];
528
- if (indexOf.call(window.ClientSideValidations.disabled_validators, validator) >= 0) {
529
- results.push(delete window.ClientSideValidations.validators.remote[validator]);
539
+ disable: function(target) {
540
+ var $target;
541
+ $target = $(target);
542
+ $target.off('.ClientSideValidations');
543
+ if ($target.is('form')) {
544
+ return ClientSideValidations.disable($target.find(':input'));
530
545
  } else {
531
- results.push(void 0);
532
- }
533
- }
534
- return results;
535
- };
536
-
537
- window.ClientSideValidations.formBuilders = {
538
- 'ActionView::Helpers::FormBuilder': {
539
- add: function(element, settings, message) {
540
- var form, inputErrorField, label, labelErrorField;
541
- form = $(element[0].form);
542
- if (element.data('valid') !== false && (form.find("label.message[for='" + (element.attr('id')) + "']")[0] == null)) {
543
- inputErrorField = $(settings.input_tag);
544
- labelErrorField = $(settings.label_tag);
545
- label = form.find("label[for='" + (element.attr('id')) + "']:not(.message)");
546
- if (element.attr('autofocus')) {
547
- element.attr('autofocus', false);
548
- }
549
- element.before(inputErrorField);
550
- inputErrorField.find('span#input_tag').replaceWith(element);
551
- inputErrorField.find('label.message').attr('for', element.attr('id'));
552
- labelErrorField.find('label.message').attr('for', element.attr('id'));
553
- labelErrorField.insertAfter(label);
554
- labelErrorField.find('label#label_tag').replaceWith(label);
555
- }
556
- return form.find("label.message[for='" + (element.attr('id')) + "']").text(message);
557
- },
558
- remove: function(element, settings) {
559
- var errorFieldClass, form, inputErrorField, label, labelErrorField;
560
- form = $(element[0].form);
561
- errorFieldClass = $(settings.input_tag).attr('class');
562
- inputErrorField = element.closest("." + (errorFieldClass.replace(/\ /g, ".")));
563
- label = form.find("label[for='" + (element.attr('id')) + "']:not(.message)");
564
- labelErrorField = label.closest("." + errorFieldClass);
565
- if (inputErrorField[0]) {
566
- inputErrorField.find("#" + (element.attr('id'))).detach();
567
- inputErrorField.replaceWith(element);
568
- label.detach();
569
- return labelErrorField.replaceWith(label);
570
- }
571
- }
572
- }
573
- };
574
-
575
- window.ClientSideValidations.patterns = {
576
- numericality: /^(-|\+)?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/
577
- };
578
-
579
- window.ClientSideValidations.callbacks = {
580
- element: {
581
- after: function(element, eventData) {},
582
- before: function(element, eventData) {},
583
- fail: function(element, message, addError, eventData) {
584
- return addError();
585
- },
586
- pass: function(element, removeError, eventData) {
587
- return removeError();
546
+ $target.removeData('valid');
547
+ $target.removeData('changed');
548
+ return $target.filter(':input').each(function() {
549
+ return $(this).removeAttr('data-validate');
550
+ });
588
551
  }
589
552
  },
590
- form: {
591
- after: function(form, eventData) {},
592
- before: function(form, eventData) {},
593
- fail: function(form, eventData) {},
594
- pass: function(form, eventData) {}
553
+ reset: function(form) {
554
+ var $form, key;
555
+ $form = $(form);
556
+ ClientSideValidations.disable(form);
557
+ for (key in form.ClientSideValidations.settings.validators) {
558
+ form.ClientSideValidations.removeError($form.find("[name='" + key + "']"));
559
+ }
560
+ return ClientSideValidations.enablers.form(form);
595
561
  }
596
562
  };
597
563
 
598
- window.ClientSideValidations.event = (window.Turbolinks != null) && window.Turbolinks.supported ? window.Turbolinks.EVENTS != null ? 'page:change' : 'turbolinks:load' : 'ready';
599
-
600
- $(document).on(window.ClientSideValidations.event, function() {
601
- ClientSideValidations.disableValidators();
564
+ $(document).on(initializeOnEvent, function() {
602
565
  return $(ClientSideValidations.selectors.forms).validate();
603
566
  });
604
567
 
568
+ window.ClientSideValidations = ClientSideValidations;
569
+
605
570
  }).call(this);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: client_side_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.2
4
+ version: 9.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geremia Taglialatela
@@ -270,7 +270,6 @@ files:
270
270
  - lib/client_side_validations/action_view.rb
271
271
  - lib/client_side_validations/action_view/form_builder.rb
272
272
  - lib/client_side_validations/action_view/form_helper.rb
273
- - lib/client_side_validations/action_view/form_tag_helper.rb
274
273
  - lib/client_side_validations/active_model.rb
275
274
  - lib/client_side_validations/active_model/absence.rb
276
275
  - lib/client_side_validations/active_model/acceptance.rb
@@ -1,16 +0,0 @@
1
- module ClientSideValidations
2
- module ActionView
3
- module Helpers
4
- module FormTagHelper
5
- private
6
-
7
- def html_options_for_form(url_for_options, options)
8
- options.stringify_keys!
9
- html_options = {}
10
- html_options['data-validate'] = options.delete('validate') if options['validate']
11
- html_options.merge!(super(url_for_options, options))
12
- end
13
- end
14
- end
15
- end
16
- end