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.
Files changed (31) hide show
  1. data/client_side_validations.gemspec +28 -0
  2. data/lib/client_side_validations.rb +18 -0
  3. data/lib/client_side_validations/action_view.rb +14 -0
  4. data/lib/client_side_validations/action_view/form_builder.rb +176 -0
  5. data/lib/client_side_validations/action_view/form_helper.rb +105 -0
  6. data/lib/client_side_validations/action_view/form_tag_helper.rb +12 -0
  7. data/lib/client_side_validations/active_model.rb +68 -0
  8. data/lib/client_side_validations/active_model/acceptance.rb +10 -0
  9. data/lib/client_side_validations/active_model/exclusion.rb +15 -0
  10. data/lib/client_side_validations/active_model/format.rb +10 -0
  11. data/lib/client_side_validations/active_model/inclusion.rb +15 -0
  12. data/lib/client_side_validations/active_model/length.rb +26 -0
  13. data/lib/client_side_validations/active_model/numericality.rb +33 -0
  14. data/lib/client_side_validations/active_model/presence.rb +10 -0
  15. data/lib/client_side_validations/active_record.rb +12 -0
  16. data/lib/client_side_validations/active_record/middleware.rb +50 -0
  17. data/lib/client_side_validations/active_record/uniqueness.rb +28 -0
  18. data/lib/client_side_validations/core_ext.rb +3 -0
  19. data/lib/client_side_validations/core_ext/range.rb +10 -0
  20. data/lib/client_side_validations/core_ext/regexp.rb +14 -0
  21. data/lib/client_side_validations/engine.rb +6 -0
  22. data/lib/client_side_validations/files.rb +8 -0
  23. data/lib/client_side_validations/generators.rb +12 -0
  24. data/lib/client_side_validations/generators/rails_validations.rb +15 -0
  25. data/lib/client_side_validations/middleware.rb +105 -0
  26. data/lib/client_side_validations/version.rb +3 -0
  27. data/lib/generators/client_side_validations/copy_assets_generator.rb +56 -0
  28. data/lib/generators/client_side_validations/install_generator.rb +22 -0
  29. data/lib/generators/templates/client_side_validations/initializer.rb +11 -0
  30. data/vendor/assets/javascripts/rails.validations.js +424 -0
  31. metadata +213 -0
@@ -0,0 +1,22 @@
1
+ require 'generators/client_side_validations/copy_assets_generator'
2
+
3
+ module ClientSideValidations
4
+ module Generators
5
+ class InstallGenerator < CopyAssetsGenerator
6
+
7
+ def copy_initializer
8
+ source_paths << File.expand_path('../../templates/client_side_validations', __FILE__)
9
+ copy_file 'initializer.rb', 'config/initializers/client_side_validations.rb'
10
+ end
11
+
12
+ private
13
+
14
+ def self.installation_message
15
+ "Copies initializer into config/initializers and #{super.downcase}"
16
+ end
17
+
18
+ desc installation_message
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,11 @@
1
+ # ClientSideValidations Initializer
2
+
3
+ # Uncomment the following block if you want each input field to have the validation messages attached.
4
+ # ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
5
+ # unless html_tag =~ /^<label/
6
+ # %{<div class="field_with_errors">#{html_tag}<label for="#{instance.send(:tag_id)}" class="message">#{instance.error_message.first}</label></div>}.html_safe
7
+ # else
8
+ # %{<div class="field_with_errors">#{html_tag}</div>}.html_safe
9
+ # end
10
+ # end
11
+
@@ -0,0 +1,424 @@
1
+ (function() {
2
+ var $, validateElement, validateForm, validatorsFor,
3
+ __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
4
+
5
+ $ = jQuery;
6
+
7
+ $.fn.validate = function() {
8
+ return this.filter('form[data-validate]').each(function() {
9
+ var addError, binding, event, form, removeError, settings, _ref, _ref2;
10
+ form = $(this);
11
+ settings = window.ClientSideValidations.forms[form.attr('id')];
12
+ addError = function(element, message) {
13
+ return ClientSideValidations.formBuilders[settings.type].add(element, settings, message);
14
+ };
15
+ removeError = function(element) {
16
+ return ClientSideValidations.formBuilders[settings.type].remove(element, settings);
17
+ };
18
+ form.submit(function() {
19
+ return form.isValid(settings.validators);
20
+ });
21
+ _ref = {
22
+ 'ajax:beforeSend': function(eventData) {
23
+ if (eventData.target === this) return form.isValid(settings.validators);
24
+ },
25
+ 'form:validate:after': function(eventData) {
26
+ return ClientSideValidations.callbacks.form.after(form, eventData);
27
+ },
28
+ 'form:validate:before': function(eventData) {
29
+ return ClientSideValidations.callbacks.form.before(form, eventData);
30
+ },
31
+ 'form:validate:fail': function(eventData) {
32
+ return ClientSideValidations.callbacks.form.fail(form, eventData);
33
+ },
34
+ 'form:validate:pass': function(eventData) {
35
+ return ClientSideValidations.callbacks.form.pass(form, eventData);
36
+ }
37
+ };
38
+ for (event in _ref) {
39
+ binding = _ref[event];
40
+ form.bind(event, binding);
41
+ }
42
+ _ref2 = {
43
+ 'focusout': function() {
44
+ return $(this).isValid(settings.validators);
45
+ },
46
+ 'change': function() {
47
+ return $(this).data('changed', true);
48
+ },
49
+ 'element:validate:after': function(eventData) {
50
+ return ClientSideValidations.callbacks.element.after($(this), eventData);
51
+ },
52
+ 'element:validate:before': function(eventData) {
53
+ return ClientSideValidations.callbacks.element.before($(this), eventData);
54
+ },
55
+ 'element:validate:fail': function(eventData, message) {
56
+ var element;
57
+ element = $(this);
58
+ return ClientSideValidations.callbacks.element.fail(element, message, function() {
59
+ return addError(element, message);
60
+ }, eventData);
61
+ },
62
+ 'element:validate:pass': function(eventData) {
63
+ var element;
64
+ element = $(this);
65
+ return ClientSideValidations.callbacks.element.pass(element, function() {
66
+ return removeError(element);
67
+ }, eventData);
68
+ }
69
+ };
70
+ for (event in _ref2) {
71
+ binding = _ref2[event];
72
+ form.find('[data-validate="true"]:input:enabled:not(:radio)').live(event, binding);
73
+ }
74
+ form.find('[data-validate="true"]:checkbox').live('click', function() {
75
+ return $(this).isValid(settings.validators);
76
+ });
77
+ return form.find('[id*=_confirmation]').each(function() {
78
+ var binding, confirmationElement, element, event, _ref3, _results;
79
+ confirmationElement = $(this);
80
+ element = form.find("#" + (this.id.match(/(.+)_confirmation/)[1]) + "[data-validate='true']:input");
81
+ if (element[0]) {
82
+ _ref3 = {
83
+ 'focusout': function() {
84
+ return element.data('changed', true).isValid(settings.validators);
85
+ },
86
+ 'keyup': function() {
87
+ return element.data('changed', true).isValid(settings.validators);
88
+ }
89
+ };
90
+ _results = [];
91
+ for (event in _ref3) {
92
+ binding = _ref3[event];
93
+ _results.push($("#" + (confirmationElement.attr('id'))).live(event, binding));
94
+ }
95
+ return _results;
96
+ }
97
+ });
98
+ });
99
+ };
100
+
101
+ $.fn.isValid = function(validators) {
102
+ var obj;
103
+ obj = $(this[0]);
104
+ if (obj.is('form')) {
105
+ return validateForm(obj, validators);
106
+ } else {
107
+ return validateElement(obj, validatorsFor(this[0].name, validators));
108
+ }
109
+ };
110
+
111
+ validatorsFor = function(name, validators) {
112
+ name = name.replace(/_attributes\]\[\d+\]/g, "_attributes][]");
113
+ return validators[name];
114
+ };
115
+
116
+ validateForm = function(form, validators) {
117
+ var valid;
118
+ form.trigger('form:validate:before');
119
+ valid = true;
120
+ form.find('[data-validate="true"]:input:enabled').each(function() {
121
+ if ($(this).isValid(validators)) return valid = false;
122
+ });
123
+ if (valid) {
124
+ form.trigger('form:validate:pass');
125
+ } else {
126
+ form.trigger('form:validate:fail');
127
+ }
128
+ form.trigger('form:validate:after');
129
+ return valid;
130
+ };
131
+
132
+ validateElement = function(element, validators) {
133
+ var context, fn, kind, message, valid, _ref;
134
+ element.trigger('element:validate:before');
135
+ if (element.data('changed') !== false) {
136
+ valid = true;
137
+ element.data('changed', false);
138
+ context = ClientSideValidations.validators.local;
139
+ for (kind in context) {
140
+ fn = context[kind];
141
+ if (validators[kind] && (message = fn.call(context, element, validators[kind]))) {
142
+ element.trigger('element:validate:fail', message).data('valid', false);
143
+ valid = false;
144
+ break;
145
+ }
146
+ }
147
+ if (valid) {
148
+ context = ClientSideValidations.validators.remote;
149
+ for (kind in context) {
150
+ fn = context[kind];
151
+ if (validators[kind] && (message = fn.call(context, element, validators[kind]))) {
152
+ element.trigger('element:validate:fail', message).data('valid', false);
153
+ valid = false;
154
+ break;
155
+ }
156
+ }
157
+ }
158
+ if (valid) {
159
+ element.data('valid', null);
160
+ element.trigger('element:validate:pass');
161
+ }
162
+ }
163
+ element.trigger('element:validate:after');
164
+ return (_ref = element.data('valid') === false) != null ? _ref : {
165
+ "false": true
166
+ };
167
+ };
168
+
169
+ $(function() {
170
+ return $('form[data-validate]').validate();
171
+ });
172
+
173
+ window.ClientSideValidations = {
174
+ forms: {},
175
+ validators: {
176
+ all: function() {
177
+ return jQuery.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote);
178
+ },
179
+ local: {
180
+ presence: function(element, options) {
181
+ if (/^\s*$/.test(element.val() || '')) return options.message;
182
+ },
183
+ acceptance: function(element, options) {
184
+ var _ref;
185
+ switch (element.attr('type')) {
186
+ case 'checkbox':
187
+ if (!element.attr('checked')) return options.message;
188
+ break;
189
+ case 'text':
190
+ if (element.val() !== (((_ref = options.accept) != null ? _ref.toString() : void 0) || '1')) {
191
+ return options.message;
192
+ }
193
+ }
194
+ },
195
+ format: function(element, options) {
196
+ var message;
197
+ message = this.presence(element, options);
198
+ if (message) {
199
+ if (options.allow_blank === true) return;
200
+ return message;
201
+ }
202
+ if (options["with"] && !options["with"].test(element.val())) {
203
+ return options.message;
204
+ }
205
+ if (options.without && options.without.test(element.val())) {
206
+ return options.message;
207
+ }
208
+ },
209
+ numericality: function(element, options) {
210
+ var CHECKS, check, fn, operator;
211
+ if (!/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/.test(element.val())) {
212
+ return options.messages.numericality;
213
+ }
214
+ if (options.only_integer && !/^[+-]?\d+$/.test(element.val())) {
215
+ return options.messages.only_integer;
216
+ }
217
+ CHECKS = {
218
+ greater_than: '>',
219
+ greater_than_or_equal_to: '>=',
220
+ equal_to: '==',
221
+ less_than: '<',
222
+ less_than_or_equal_to: '<='
223
+ };
224
+ for (check in CHECKS) {
225
+ operator = CHECKS[check];
226
+ if (!(options[check] != null)) continue;
227
+ fn = new Function("return " + (element.val()) + " " + operator + " " + options[check]);
228
+ if (!fn()) return options.messages[check];
229
+ }
230
+ if (options.odd && !(parseInt(element.val(), 10) % 2)) {
231
+ return options.messages.odd;
232
+ }
233
+ if (options.even && (parseInt(element.val(), 10) % 2)) {
234
+ return options.messages.even;
235
+ }
236
+ },
237
+ length: function(element, options) {
238
+ var CHECKS, blankOptions, check, fn, message, operator, tokenized_length, tokenizer;
239
+ tokenizer = options.js_tokenizer || "split('')";
240
+ tokenized_length = new Function('element', "return (element.val()." + tokenizer + " || '').length")(element);
241
+ CHECKS = {
242
+ is: '==',
243
+ minimum: '>=',
244
+ maximum: '<='
245
+ };
246
+ blankOptions = {};
247
+ blankOptions.message = options.is ? options.messages.is : options.minimum ? options.messages.minimum : void 0;
248
+ message = this.presence(element, blankOptions);
249
+ if (message) {
250
+ if (options.allow_blank === true) return;
251
+ return message;
252
+ }
253
+ for (check in CHECKS) {
254
+ operator = CHECKS[check];
255
+ if (!options[check]) continue;
256
+ fn = new Function("return " + tokenized_length + " " + operator + " " + options[check]);
257
+ if (!fn()) return options.messages[check];
258
+ }
259
+ },
260
+ exclusion: function(element, options) {
261
+ var lower, message, o, upper, _ref;
262
+ message = this.presence(element, options);
263
+ if (message) {
264
+ if (options.allow_blank === true) return;
265
+ return message;
266
+ }
267
+ if (options["in"]) {
268
+ if (_ref = element.val(), __indexOf.call((function() {
269
+ var _i, _len, _ref2, _results;
270
+ _ref2 = options["in"];
271
+ _results = [];
272
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
273
+ o = _ref2[_i];
274
+ _results.push(o.toString());
275
+ }
276
+ return _results;
277
+ })(), _ref) >= 0) {
278
+ return options.message;
279
+ }
280
+ }
281
+ if (options.range) {
282
+ lower = options.range[0];
283
+ upper = options.range[1];
284
+ if (element.val() >= lower && element.val() <= upper) {
285
+ return options.message;
286
+ }
287
+ }
288
+ },
289
+ inclusion: function(element, options) {
290
+ var lower, message, o, upper, _ref;
291
+ message = this.presence(element, options);
292
+ if (message) {
293
+ if (options.allow_blank === true) return;
294
+ return message;
295
+ }
296
+ if (options["in"]) {
297
+ if (_ref = element.val(), __indexOf.call((function() {
298
+ var _i, _len, _ref2, _results;
299
+ _ref2 = options["in"];
300
+ _results = [];
301
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
302
+ o = _ref2[_i];
303
+ _results.push(o.toString());
304
+ }
305
+ return _results;
306
+ })(), _ref) >= 0) {
307
+ return;
308
+ }
309
+ return options.message;
310
+ }
311
+ if (options.range) {
312
+ lower = options.range[0];
313
+ upper = options.range[1];
314
+ if (element.val() >= lower && element.val() <= upper) return;
315
+ return options.message;
316
+ }
317
+ },
318
+ confirmation: function(element, options) {
319
+ if (element.val() !== jQuery("#" + (element.attr('id')) + "_confirmation").val()) {
320
+ return options.message;
321
+ }
322
+ }
323
+ },
324
+ remote: {
325
+ uniqueness: function(element, options) {
326
+ var data, key, message, name, scope_value, scoped_element, scoped_name, _ref;
327
+ message = ClientSideValidations.validators.local.presence(element, options);
328
+ if (message) {
329
+ if (options.allow_blank === true) return;
330
+ return message;
331
+ }
332
+ data = {};
333
+ data.case_sensitive = !!options.case_sensitive;
334
+ if (options.id) data.id = options.id;
335
+ if (options.scope) {
336
+ data.scope = {};
337
+ _ref = options.scope;
338
+ for (key in _ref) {
339
+ scope_value = _ref[key];
340
+ scoped_name = element.attr('name').replace(/\[\w+\]$/, "[" + key + "]");
341
+ scoped_element = jQuery("[name='" + scoped_name + "']");
342
+ if (scoped_element[0] && scoped_element.val() !== scope_value) {
343
+ data.scope[key] = scoped_element.val();
344
+ scoped_element.unbind("change." + element.id).bind("change." + element.id, function() {
345
+ element.trigger('change');
346
+ return element.trigger('focusout');
347
+ });
348
+ } else {
349
+ data.scope[key] = scope_value;
350
+ }
351
+ }
352
+ }
353
+ if (/_attributes\]/.test(element.attr('name'))) {
354
+ name = element.attr('name').match(/\[\w+_attributes\]/g).pop().match(/\[(\w+)_attributes\]/).pop();
355
+ name += /(\[\w+\])$/.exec(element.attr('name'))[1];
356
+ } else {
357
+ name = element.attr('name');
358
+ }
359
+ if (options['class']) name = options['class'] + '[' + name.split('[')[1];
360
+ data[name] = element.val();
361
+ if (jQuery.ajax({
362
+ url: '/validators/uniqueness',
363
+ data: data,
364
+ async: false
365
+ }).status === 200) {
366
+ return options.message;
367
+ }
368
+ }
369
+ }
370
+ },
371
+ formBuilders: {
372
+ 'ActionView::Helpers::FormBuilder': {
373
+ add: function(element, settings, message) {
374
+ var inputErrorField, label, labelErrorField;
375
+ if (element.data('valid') !== false && !(jQuery("label.message[for='" + (element.attr('id')) + "']")[0] != null)) {
376
+ inputErrorField = jQuery(settings.input_tag);
377
+ labelErrorField = jQuery(settings.label_tag);
378
+ label = jQuery("label[for='" + (element.attr('id')) + "']:not(.message)");
379
+ if (element.attr('autofocus')) element.attr('autofocus', false);
380
+ element.before(inputErrorField);
381
+ inputErrorField.find('span#input_tag').replaceWith(element);
382
+ inputErrorField.find('label.message').attr('for', element.attr('id'));
383
+ labelErrorField.find('label.message').attr('for', element.attr('id'));
384
+ label.replaceWith(labelErrorField);
385
+ labelErrorField.find('label#label_tag').replaceWith(label);
386
+ }
387
+ return jQuery("label.message[for='" + (element.attr('id')) + "']").text(message);
388
+ },
389
+ remove: function(element, settings) {
390
+ var errorFieldClass, inputErrorField, label, labelErrorField;
391
+ errorFieldClass = jQuery(settings.input_tag).attr('class');
392
+ inputErrorField = element.closest("." + errorFieldClass);
393
+ label = jQuery("label[for='" + (element.attr('id')) + "']:not(.message)");
394
+ labelErrorField = label.closest("." + errorFieldClass);
395
+ if (inputErrorField[0]) {
396
+ inputErrorField.find("#" + (element.attr('id'))).detach();
397
+ inputErrorField.replaceWith(element);
398
+ label.detach();
399
+ return labelErrorField.replaceWith(label);
400
+ }
401
+ }
402
+ }
403
+ },
404
+ callbacks: {
405
+ element: {
406
+ after: function(element, eventData) {},
407
+ before: function(element, eventData) {},
408
+ fail: function(element, message, addError, eventData) {
409
+ return addError();
410
+ },
411
+ pass: function(element, removeError, eventData) {
412
+ return removeError();
413
+ }
414
+ },
415
+ form: {
416
+ after: function(form, eventData) {},
417
+ before: function(form, eventData) {},
418
+ fail: function(form, eventData) {},
419
+ pass: function(form, eventData) {}
420
+ }
421
+ }
422
+ };
423
+
424
+ }).call(this);