guardsjs-rails 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/guardsjs-rails.rb +8 -0
- data/lib/guardsjs-rails/version.rb +5 -0
- data/vendor/assets/javascripts/guards.js +1342 -0
- metadata +71 -0
@@ -0,0 +1,1342 @@
|
|
1
|
+
/*!
|
2
|
+
* Guards JavaScript jQuery Plugin v0.7.2
|
3
|
+
* https://github.com/on-site/guards.js
|
4
|
+
*
|
5
|
+
* Copyright 2010-2013, On-Site.com, http://www.on-site.com/
|
6
|
+
* Licensed under the MIT license.
|
7
|
+
*
|
8
|
+
* Includes code for email and phone number validation from the jQuery
|
9
|
+
* Validation plugin. http://docs.jquery.com/Plugins/Validation
|
10
|
+
*
|
11
|
+
* Date: Mon Feb 25 03:47:45 2013 -0800
|
12
|
+
*/
|
13
|
+
|
14
|
+
/**
|
15
|
+
* This plugin is initially inspired by the standard Validation jQuery
|
16
|
+
* plugin (http://docs.jquery.com/Plugins/Validation).
|
17
|
+
*
|
18
|
+
* To guard forms with this plugin, you must specify a set of guards
|
19
|
+
* via $.guards.add(selector).using(guard) or
|
20
|
+
* $.guard(selector).using(guard). These guards are then invoked from
|
21
|
+
* the first one specified to the last one specified.
|
22
|
+
*
|
23
|
+
* Example usage:
|
24
|
+
*
|
25
|
+
* $(function() {
|
26
|
+
* // Change the default error tag wrapper to a div.
|
27
|
+
* $.guards.defaults.tag = "div";
|
28
|
+
*
|
29
|
+
* // Enable the submit guard hook for the form with the "myForm" id.
|
30
|
+
* $("#myForm").enableGuards();
|
31
|
+
*
|
32
|
+
* // Guard that fields with "required" class have a value.
|
33
|
+
* $.guard(".required").using("required");
|
34
|
+
*
|
35
|
+
* // Guard that the text fields don't have the value "invalid" or "bad".
|
36
|
+
* $.guard(":text").using(function(value, element) {
|
37
|
+
* return $.inArray(value, ["invalid", "bad"]) == -1;
|
38
|
+
* }).message("Don't use the keyword 'invalid' or 'bad'.");
|
39
|
+
*
|
40
|
+
* // Guard that fields with "email" class specify at least one
|
41
|
+
* // value, but only show 1 error message if none is specified (but
|
42
|
+
* // still highlight all of the fields).
|
43
|
+
* $.guard(".email").using("oneRequired")
|
44
|
+
* .message("Please specify at least one email.").grouped();
|
45
|
+
*/
|
46
|
+
(function($) {
|
47
|
+
$.guard = function(selector) {
|
48
|
+
return $.guards.add(selector);
|
49
|
+
};
|
50
|
+
|
51
|
+
$.guard.version = "0.7.2";
|
52
|
+
|
53
|
+
$.Guards = function() {
|
54
|
+
var self = this;
|
55
|
+
this._guards = [];
|
56
|
+
|
57
|
+
this.options = {
|
58
|
+
stackErrors: false
|
59
|
+
};
|
60
|
+
|
61
|
+
this.constants = {
|
62
|
+
notChecked: ""
|
63
|
+
};
|
64
|
+
|
65
|
+
var defineGuard = function(aggregator, validator) {
|
66
|
+
return function() {
|
67
|
+
var args = $.makeArray(arguments);
|
68
|
+
return function(value, element) {
|
69
|
+
return self[aggregator](value, function(v) {
|
70
|
+
return self[validator].apply(self, $.merge([v], args));
|
71
|
+
});
|
72
|
+
};
|
73
|
+
};
|
74
|
+
};
|
75
|
+
|
76
|
+
var minMaxMessage = function(formatting, minMaxFormat) {
|
77
|
+
return function(options) {
|
78
|
+
if (self.isNullOrUndefined(options)) {
|
79
|
+
options = {};
|
80
|
+
}
|
81
|
+
|
82
|
+
if (!$.isFunction(minMaxFormat)) {
|
83
|
+
minMaxFormat = function(x) { return x; };
|
84
|
+
}
|
85
|
+
|
86
|
+
var minDefined = !self.isNullOrUndefined(options.min);
|
87
|
+
var maxDefined = !self.isNullOrUndefined(options.max);
|
88
|
+
|
89
|
+
if (minDefined && maxDefined) {
|
90
|
+
return self.format(formatting.minAndMax, minMaxFormat(options.min), minMaxFormat(options.max));
|
91
|
+
}
|
92
|
+
|
93
|
+
if (minDefined) {
|
94
|
+
return self.format(formatting.min, minMaxFormat(options.min));
|
95
|
+
}
|
96
|
+
|
97
|
+
if (maxDefined) {
|
98
|
+
return self.format(formatting.max, minMaxFormat(options.max));
|
99
|
+
}
|
100
|
+
|
101
|
+
if (formatting.invalid) {
|
102
|
+
return formatting.invalid;
|
103
|
+
}
|
104
|
+
|
105
|
+
return self.defaults.messages.undefined;
|
106
|
+
};
|
107
|
+
};
|
108
|
+
|
109
|
+
var arrayMessage = function(formatting) {
|
110
|
+
return function(array) {
|
111
|
+
return self.format(formatting, $.map(array, function(x, i) { return $.trim("" + x); }).join(", "));
|
112
|
+
};
|
113
|
+
};
|
114
|
+
|
115
|
+
this.defaults = {
|
116
|
+
grouped: false,
|
117
|
+
guard: "required",
|
118
|
+
|
119
|
+
guards: {
|
120
|
+
allow: defineGuard("isAllValid", "isAllowed"),
|
121
|
+
always: defineGuard("isAllValid", "always"),
|
122
|
+
different: defineGuard("passThrough", "isDifferent"),
|
123
|
+
disallow: defineGuard("isAllValid", "isDisallowed"),
|
124
|
+
email: defineGuard("isAllValid", "isValidEmail"),
|
125
|
+
"float": defineGuard("isAllValid", "isValidFloat"),
|
126
|
+
"int": defineGuard("isAllValid", "isValidInt"),
|
127
|
+
moneyUS: defineGuard("isAllValid", "isValidMoneyUS"),
|
128
|
+
never: defineGuard("isAllValid", "never"),
|
129
|
+
oneRequired: defineGuard("isAnyValid", "isPresent"),
|
130
|
+
phoneUS: defineGuard("isAllValid", "isValidPhoneUS"),
|
131
|
+
required: defineGuard("isAllValid", "isPresent"),
|
132
|
+
same: defineGuard("passThrough", "isSame"),
|
133
|
+
string: defineGuard("isAllValid", "isValidString")
|
134
|
+
},
|
135
|
+
|
136
|
+
invalidClass: "invalid-field",
|
137
|
+
messageClass: "error-message",
|
138
|
+
|
139
|
+
messages: {
|
140
|
+
allow: arrayMessage("Please enter one of: #{0}."),
|
141
|
+
always: "There was an error.",
|
142
|
+
different: "These values must all be different.",
|
143
|
+
disallow: arrayMessage("Please don't enter: #{0}."),
|
144
|
+
email: "Please enter a valid E-mail address.",
|
145
|
+
|
146
|
+
"float": minMaxMessage({
|
147
|
+
minAndMax: "Please enter a number from #{0} to #{1}.",
|
148
|
+
min: "Please enter a number no less than #{0}.",
|
149
|
+
max: "Please enter a number no greater than #{0}.",
|
150
|
+
invalid: "Please enter a number."
|
151
|
+
}),
|
152
|
+
|
153
|
+
"int": minMaxMessage({
|
154
|
+
minAndMax: "Please enter a number from #{0} to #{1}.",
|
155
|
+
min: "Please enter a number no less than #{0}.",
|
156
|
+
max: "Please enter a number no greater than #{0}.",
|
157
|
+
invalid: "Please enter a number."
|
158
|
+
}),
|
159
|
+
|
160
|
+
moneyUS: minMaxMessage({
|
161
|
+
minAndMax: "Please enter a dollar amount from #{0} to #{1}.",
|
162
|
+
min: "Please enter a dollar amount no less than #{0}.",
|
163
|
+
max: "Please enter a dollar amount no greater than #{0}.",
|
164
|
+
invalid: "Please enter a dollar amount."
|
165
|
+
}, function(x) { return x.toFixed(2); }),
|
166
|
+
|
167
|
+
never: "There was an error.",
|
168
|
+
oneRequired: "Specify at least one.",
|
169
|
+
phoneUS: "Please enter a valid phone number.",
|
170
|
+
required: "This field is required.",
|
171
|
+
same: "These values must all match.",
|
172
|
+
|
173
|
+
string: minMaxMessage({
|
174
|
+
minAndMax: "Please enter a string with length #{0} to #{1}.",
|
175
|
+
min: "Please enter a string with length at least #{0}.",
|
176
|
+
max: "Please enter a string with length no greater than #{0}."
|
177
|
+
}),
|
178
|
+
|
179
|
+
"undefined": "Please fix this field."
|
180
|
+
},
|
181
|
+
|
182
|
+
style: {
|
183
|
+
field: {
|
184
|
+
"background-color": "#ffff66"
|
185
|
+
},
|
186
|
+
|
187
|
+
message: {
|
188
|
+
color: "#ff0000",
|
189
|
+
"margin-left": "10px"
|
190
|
+
}
|
191
|
+
},
|
192
|
+
|
193
|
+
tag: "span",
|
194
|
+
|
195
|
+
target: function(errorElement) {
|
196
|
+
var last = $(this).filter(":last");
|
197
|
+
|
198
|
+
if (last.is(":radio,:checkbox")) {
|
199
|
+
last = $(last[0].nextSibling);
|
200
|
+
}
|
201
|
+
|
202
|
+
errorElement.insertAfter(last);
|
203
|
+
return false;
|
204
|
+
}
|
205
|
+
};
|
206
|
+
};
|
207
|
+
|
208
|
+
$.Guards.prototype.version = "0.7.2";
|
209
|
+
|
210
|
+
// Really old jQuery doesn't have isArray, so use this alias
|
211
|
+
// instead.
|
212
|
+
$.Guards.prototype.isArray = $.isArray;
|
213
|
+
|
214
|
+
if (!$.Guards.prototype.isArray) {
|
215
|
+
var ARRAY_CONSTRUCTOR = [].constructor;
|
216
|
+
var JQUERY_CONSTRUCTOR = jQuery;
|
217
|
+
|
218
|
+
$.Guards.prototype.isArray = function(obj) {
|
219
|
+
// Simplistic, but good enough for guards.
|
220
|
+
return obj.constructor == ARRAY_CONSTRUCTOR || obj.constructor == JQUERY_CONSTRUCTOR;
|
221
|
+
};
|
222
|
+
}
|
223
|
+
|
224
|
+
// Alias for console.log, but check that such a thing exists.
|
225
|
+
$.Guards.prototype.log = function(message) {
|
226
|
+
if (console && console.log) {
|
227
|
+
console.log(message);
|
228
|
+
}
|
229
|
+
};
|
230
|
+
|
231
|
+
// Utility method to trigger live events, but works against any
|
232
|
+
// jQuery version that supports live events.
|
233
|
+
$.Guards.prototype.on = function(selector, event, callback) {
|
234
|
+
if ($.fn.on) {
|
235
|
+
$(document).on(event, selector, callback);
|
236
|
+
} else if ($.fn.delegate) {
|
237
|
+
$(document).delegate(selector, event, callback);
|
238
|
+
} else if ($.fn.live) {
|
239
|
+
$(selector).live(event, callback);
|
240
|
+
} else {
|
241
|
+
this.log("Could not bind live handlers, probably because jQuery is too old.");
|
242
|
+
}
|
243
|
+
};
|
244
|
+
|
245
|
+
// Implementation of $.enableGuards(selector);
|
246
|
+
$.Guards.prototype.enableGuards = function(selector) {
|
247
|
+
var self = this;
|
248
|
+
|
249
|
+
this.on(selector, "submit", function() {
|
250
|
+
return self.guard($(this));
|
251
|
+
});
|
252
|
+
};
|
253
|
+
|
254
|
+
// Implementation of $.liveGuard(selector);
|
255
|
+
$.Guards.prototype.liveGuard = function(selector) {
|
256
|
+
var self = this;
|
257
|
+
this.enableGuards(selector);
|
258
|
+
|
259
|
+
this.on(selector, "change blur", function(e) {
|
260
|
+
var $element = $(e.target);
|
261
|
+
|
262
|
+
if (!$element.is(":guardable")) {
|
263
|
+
return;
|
264
|
+
}
|
265
|
+
|
266
|
+
self.applyGuards(function(guard) {
|
267
|
+
if (guard.isGrouped()) {
|
268
|
+
if (guard.appliesTo($element)) {
|
269
|
+
return $element.parents("form:first").find(":guardable");
|
270
|
+
} else {
|
271
|
+
return false;
|
272
|
+
}
|
273
|
+
} else {
|
274
|
+
return $element;
|
275
|
+
}
|
276
|
+
});
|
277
|
+
});
|
278
|
+
};
|
279
|
+
|
280
|
+
/**
|
281
|
+
* Format all arguments into the first argument. This is a
|
282
|
+
* convenience function similar to the C sprintf function, though
|
283
|
+
* only with simple replacements. Replacements are formatted like
|
284
|
+
* #{i} where i is a zero based index into the additional
|
285
|
+
* arguments passed in to format beyond the first.
|
286
|
+
*
|
287
|
+
* Additional parameters not used will be ignored.
|
288
|
+
*
|
289
|
+
* Including formatting requests for parameters that don't exist
|
290
|
+
* will throw an exception.
|
291
|
+
*
|
292
|
+
* The first argument must be the string that needs to be
|
293
|
+
* formatted. Additional arguments are formatted into that
|
294
|
+
* string.
|
295
|
+
*
|
296
|
+
* If any of the arguments to the format string include a string
|
297
|
+
* that matches the #{i} format, the result could be erroneous.
|
298
|
+
*
|
299
|
+
* Example: $.guards.format("#{2} #{0} #{1}", "hello", "world", 3); // "3 hello world"
|
300
|
+
* Example: $.guards.format("#{0} #{1}", "hello", "world", 3); // "hello world".
|
301
|
+
* Example: $.guards.format("#{2} #{0} #{1}", "hello", "world"); // throws exception
|
302
|
+
*/
|
303
|
+
$.Guards.prototype.format = function() {
|
304
|
+
var str = arguments[0];
|
305
|
+
|
306
|
+
if (arguments.length > 1) {
|
307
|
+
for (var i = 1; i < arguments.length; i++) {
|
308
|
+
var regex = "\\#\\{" + (i - 1) + "\\}";
|
309
|
+
str = str.replace(new RegExp(regex, "g"), arguments[i]);
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
if (/\#\{\d+\}/.test(str)) {
|
314
|
+
throw new Error("Unmatched formatting found!");
|
315
|
+
}
|
316
|
+
|
317
|
+
return str;
|
318
|
+
};
|
319
|
+
|
320
|
+
/**
|
321
|
+
* Add a style element to the document head which will style
|
322
|
+
* elements with errors and their error messages. This will use
|
323
|
+
* $.guards.defaults.style.field and
|
324
|
+
* $.guards.defaults.style.message to determine what styling to
|
325
|
+
* use. These defaults are initialized to a yellow background for
|
326
|
+
* the invalid fields, and red color with a small left margin for
|
327
|
+
* error messages. The selectors used to style these are
|
328
|
+
* determined by $.guards.defaults.invalidClass and
|
329
|
+
* $.guards.defaults.messageClass.
|
330
|
+
*
|
331
|
+
* There are 2 optional arguments allowed. The first is a
|
332
|
+
* selector scope to use, and the second is overrides for styling.
|
333
|
+
* Either, both or neither arguments are allowed.
|
334
|
+
*
|
335
|
+
* With a changed selector scope, the selector for the styles is
|
336
|
+
* scoped to the given value. This can be useful for different
|
337
|
+
* styling on different forms. Note that the keys to the object
|
338
|
+
* in the "field" and "message" keys are used as css styles, and
|
339
|
+
* the values to those keys are the values for those styles.
|
340
|
+
*
|
341
|
+
* The custom style overrides can be used to change the field,
|
342
|
+
* message or both styles.
|
343
|
+
*
|
344
|
+
* Example: $.guards.style();
|
345
|
+
* Example: $.guards.style("#myForm");
|
346
|
+
* Example: $.guards.style({ field: { "color": "#ff0000" } });
|
347
|
+
* Example: $.guards.style({ message: { "color": "#ff6666" } });
|
348
|
+
* Example: $.guards.style("#myForm", { field: { "color": "#ff0000" }, message: { "color": "#ff6666" } });
|
349
|
+
*/
|
350
|
+
$.Guards.prototype.style = function() {
|
351
|
+
$("head").append(this.styleHtml.apply(this, arguments));
|
352
|
+
};
|
353
|
+
|
354
|
+
/**
|
355
|
+
* Retrieve the style html as a string to use for the
|
356
|
+
* $.guards.style() function. The documentation for that function
|
357
|
+
* applies to this as well.
|
358
|
+
*/
|
359
|
+
$.Guards.prototype.styleHtml = function() {
|
360
|
+
var fieldStyle = {};
|
361
|
+
var messageStyle = {};
|
362
|
+
var fieldSelector = "." + this.defaults.invalidClass;
|
363
|
+
var messageSelector = "." + this.defaults.messageClass;
|
364
|
+
var selectorScope, styles;
|
365
|
+
|
366
|
+
if (this.defaults.style && this.defaults.style.field) {
|
367
|
+
fieldStyle = this.defaults.style.field;
|
368
|
+
}
|
369
|
+
|
370
|
+
if (this.defaults.style && this.defaults.style.message) {
|
371
|
+
messageStyle = this.defaults.style.message;
|
372
|
+
}
|
373
|
+
|
374
|
+
if (arguments.length == 1) {
|
375
|
+
if (typeof(arguments[0]) == "string") {
|
376
|
+
selectorScope = arguments[0];
|
377
|
+
} else {
|
378
|
+
styles = arguments[0];
|
379
|
+
}
|
380
|
+
} else if (arguments.length == 2) {
|
381
|
+
selectorScope = arguments[0];
|
382
|
+
styles = arguments[1];
|
383
|
+
}
|
384
|
+
|
385
|
+
if (styles && styles.field) {
|
386
|
+
fieldStyle = styles.field;
|
387
|
+
}
|
388
|
+
|
389
|
+
if (styles && styles.message) {
|
390
|
+
messageStyle = styles.message;
|
391
|
+
}
|
392
|
+
|
393
|
+
var result = "<style>\n";
|
394
|
+
|
395
|
+
var addStyles = function(selector, styles) {
|
396
|
+
result += " " + selector + " {";
|
397
|
+
|
398
|
+
if (styles) {
|
399
|
+
$.each(styles, function(key, value) {
|
400
|
+
result += " " + key + ": " + value + ";";
|
401
|
+
});
|
402
|
+
}
|
403
|
+
|
404
|
+
result += " }\n";
|
405
|
+
};
|
406
|
+
|
407
|
+
if (selectorScope) {
|
408
|
+
fieldSelector = selectorScope + " " + fieldSelector;
|
409
|
+
messageSelector = selectorScope + " " + messageSelector;
|
410
|
+
}
|
411
|
+
|
412
|
+
addStyles(fieldSelector, fieldStyle);
|
413
|
+
addStyles(messageSelector, messageStyle);
|
414
|
+
result += "</style>";
|
415
|
+
return result;
|
416
|
+
};
|
417
|
+
|
418
|
+
/**
|
419
|
+
* This guard test method is intended to always fail, thus it
|
420
|
+
* returns false no matter what.
|
421
|
+
*/
|
422
|
+
$.Guards.prototype.always = function(value) {
|
423
|
+
return false;
|
424
|
+
};
|
425
|
+
|
426
|
+
/**
|
427
|
+
* Return whether or not the value exists in the given allowed
|
428
|
+
* list. The allowed parameter must be an array of valid values.
|
429
|
+
* Blank is considered invalid unless it exists in the list.
|
430
|
+
* Whitespace is ignored.
|
431
|
+
*/
|
432
|
+
$.Guards.prototype.isAllowed = function(value, allowed) {
|
433
|
+
value = $.trim(value);
|
434
|
+
return $.inArray(value, $.map(allowed, function(x, i) { return $.trim("" + x); })) != -1;
|
435
|
+
};
|
436
|
+
|
437
|
+
/**
|
438
|
+
* If the given values is an array, this will return false if the
|
439
|
+
* given fn returns false for any value in the array. If the
|
440
|
+
* given values is not an array, the result of calling the given
|
441
|
+
* fn on that value is returned directly.
|
442
|
+
*
|
443
|
+
* Example: $.guards.isAllValid([true, false, true], function(x) { return x; }); // false
|
444
|
+
* Example: $.guards.isAllValid(true, function(x) { return x; }); // true
|
445
|
+
*/
|
446
|
+
$.Guards.prototype.isAllValid = function(values, fn) {
|
447
|
+
if (this.isArray(values)) {
|
448
|
+
var result = true;
|
449
|
+
|
450
|
+
$.each(values, function(i, x) {
|
451
|
+
if (!fn(x)) {
|
452
|
+
result = false;
|
453
|
+
return false;
|
454
|
+
}
|
455
|
+
});
|
456
|
+
|
457
|
+
return result;
|
458
|
+
}
|
459
|
+
|
460
|
+
return fn(values);
|
461
|
+
};
|
462
|
+
|
463
|
+
/**
|
464
|
+
* If the given values is an array, this will return true if the
|
465
|
+
* given fn returns true for any value in the array. If the given
|
466
|
+
* values is not an array, the result of calling the given fn on
|
467
|
+
* that value is returned directly.
|
468
|
+
*
|
469
|
+
* Example: $.guards.isAllValid([false, false, true], function(x) { return x; }); // true
|
470
|
+
* Example: $.guards.isAllValid(false, function(x) { return x; }); // false
|
471
|
+
*/
|
472
|
+
$.Guards.prototype.isAnyValid = function(values, fn) {
|
473
|
+
if (this.isArray(values)) {
|
474
|
+
var result = false;
|
475
|
+
|
476
|
+
$.each(values, function(i, x) {
|
477
|
+
if (fn(x)) {
|
478
|
+
result = true;
|
479
|
+
return false;
|
480
|
+
}
|
481
|
+
});
|
482
|
+
|
483
|
+
return result;
|
484
|
+
}
|
485
|
+
|
486
|
+
return fn(values);
|
487
|
+
};
|
488
|
+
|
489
|
+
/**
|
490
|
+
* Return true if the value is null, undefined, an empty string,
|
491
|
+
* or a string of just spaces.
|
492
|
+
*/
|
493
|
+
$.Guards.prototype.isBlank = function(value) {
|
494
|
+
return this.isNullOrUndefined(value) || $.trim(value) == "";
|
495
|
+
};
|
496
|
+
|
497
|
+
/**
|
498
|
+
* Return whether all the values in the given array are different.
|
499
|
+
*/
|
500
|
+
$.Guards.prototype.isDifferent = function(values) {
|
501
|
+
if (values.length < 2) {
|
502
|
+
return true;
|
503
|
+
}
|
504
|
+
|
505
|
+
var found = {};
|
506
|
+
var result = true;
|
507
|
+
|
508
|
+
$.each(values, function(i, x) {
|
509
|
+
if (found[x] === true) {
|
510
|
+
result = false;
|
511
|
+
return false;
|
512
|
+
}
|
513
|
+
|
514
|
+
found[x] = true;
|
515
|
+
});
|
516
|
+
|
517
|
+
return result;
|
518
|
+
};
|
519
|
+
|
520
|
+
/**
|
521
|
+
* Return whether or not the value doesn't exist in the given
|
522
|
+
* disallowed list. The disallowed parameter must be an array of
|
523
|
+
* invalid values. Blank is considered valid unless it exists in
|
524
|
+
* the list. Whitespace is ignored.
|
525
|
+
*/
|
526
|
+
$.Guards.prototype.isDisallowed = function(value, disallowed) {
|
527
|
+
return !this.isAllowed(value, disallowed);
|
528
|
+
};
|
529
|
+
|
530
|
+
/**
|
531
|
+
* Return true if the value is null or undefined.
|
532
|
+
*/
|
533
|
+
$.Guards.prototype.isNullOrUndefined = function(value) {
|
534
|
+
return value === null || value === undefined;
|
535
|
+
};
|
536
|
+
|
537
|
+
/**
|
538
|
+
* Return the negation of calling isBlank(value).
|
539
|
+
*/
|
540
|
+
$.Guards.prototype.isPresent = function(value) {
|
541
|
+
return !this.isBlank(value);
|
542
|
+
};
|
543
|
+
|
544
|
+
/**
|
545
|
+
* Return whether all the values in the given array are the same.
|
546
|
+
*/
|
547
|
+
$.Guards.prototype.isSame = function(values) {
|
548
|
+
if (values.length < 2) {
|
549
|
+
return true;
|
550
|
+
}
|
551
|
+
|
552
|
+
var value = values[0];
|
553
|
+
var result = true;
|
554
|
+
|
555
|
+
$.each(values, function(i, x) {
|
556
|
+
if (x != value) {
|
557
|
+
result = false;
|
558
|
+
return false;
|
559
|
+
}
|
560
|
+
});
|
561
|
+
|
562
|
+
return result;
|
563
|
+
};
|
564
|
+
|
565
|
+
/**
|
566
|
+
* Return true if the given value is greater than or equal to
|
567
|
+
* options.min (if options.min is defined) and less than or equal
|
568
|
+
* to options.max (if options.max is defined).
|
569
|
+
*/
|
570
|
+
$.Guards.prototype.isInRange = function(value, options) {
|
571
|
+
if (this.isNullOrUndefined(options)) {
|
572
|
+
options = {};
|
573
|
+
}
|
574
|
+
|
575
|
+
var bigEnough = this.isNullOrUndefined(options.min) || value >= options.min;
|
576
|
+
var smallEnough = this.isNullOrUndefined(options.max) || value <= options.max;
|
577
|
+
return bigEnough && smallEnough;
|
578
|
+
};
|
579
|
+
|
580
|
+
/**
|
581
|
+
* Return whether or not the value is a valid integer.
|
582
|
+
* Appropriate options are min, max, both or neither. Blank is
|
583
|
+
* valid as a number.
|
584
|
+
*/
|
585
|
+
$.Guards.prototype.isValidInt = function(value, options) {
|
586
|
+
value = $.trim(value);
|
587
|
+
|
588
|
+
if (value == "") {
|
589
|
+
return true;
|
590
|
+
}
|
591
|
+
|
592
|
+
if (!/^(-|\+)?\d+$/.test(value)) {
|
593
|
+
return false;
|
594
|
+
}
|
595
|
+
|
596
|
+
value = parseInt(value, 10);
|
597
|
+
return this.isInRange(value, options);
|
598
|
+
};
|
599
|
+
|
600
|
+
/**
|
601
|
+
* Return whether or not the value is a valid float. Appropriate
|
602
|
+
* options are min, max, both or neither. Blank is valid as a
|
603
|
+
* number.
|
604
|
+
*/
|
605
|
+
$.Guards.prototype.isValidFloat = function(value, options) {
|
606
|
+
value = $.trim(value);
|
607
|
+
|
608
|
+
if (value == "") {
|
609
|
+
return true;
|
610
|
+
}
|
611
|
+
|
612
|
+
if (!/^(-|\+)?(\d+)?\.?\d+$/.test(value)) {
|
613
|
+
return false;
|
614
|
+
}
|
615
|
+
|
616
|
+
value = parseFloat(value);
|
617
|
+
return this.isInRange(value, options);
|
618
|
+
};
|
619
|
+
|
620
|
+
/**
|
621
|
+
* Validates the given value is a valid US money value. It
|
622
|
+
* optionally accepts min and max to specify the minimum or
|
623
|
+
* maximum values. Blank is a valid money.
|
624
|
+
*/
|
625
|
+
$.Guards.prototype.isValidMoneyUS = function(value, options) {
|
626
|
+
value = $.trim(value);
|
627
|
+
|
628
|
+
if (value == "") {
|
629
|
+
return true;
|
630
|
+
}
|
631
|
+
|
632
|
+
if (!/^\$?(-|\+)?\$?([\d,]+)?\.?\d+$/.test(value)) {
|
633
|
+
return false;
|
634
|
+
}
|
635
|
+
|
636
|
+
// Only allow 1 $.
|
637
|
+
var $i = value.indexOf("$");
|
638
|
+
if ($i >= 0 && value.indexOf("$", $i + 1) >= 0) {
|
639
|
+
return false;
|
640
|
+
}
|
641
|
+
|
642
|
+
// Ensure if there are commas they are every 3 digits
|
643
|
+
if (value.indexOf(",") >= 0 && !/^\$?(-|\+)?\$?[1-9]\d{0,2}(,\d{3,3})+(\.\d+)?$/.test(value)) {
|
644
|
+
return false;
|
645
|
+
}
|
646
|
+
|
647
|
+
// Ensure no more than 2 digits after decimal
|
648
|
+
if (value.indexOf(".") >= 0 && /\.\d{3,}$/.test(value)) {
|
649
|
+
return false;
|
650
|
+
}
|
651
|
+
|
652
|
+
value = parseFloat(value.replace(/[\$,]/g, ""));
|
653
|
+
return this.isInRange(value, options);
|
654
|
+
};
|
655
|
+
|
656
|
+
/**
|
657
|
+
* Validates the given value is a valid email. If options is
|
658
|
+
* passed with allowDisplay as true, display emails will be
|
659
|
+
* considered valid. A display email differs from a regular email
|
660
|
+
* in that it can be contained with < and > with some text ahead
|
661
|
+
* of that. Thus "John Doe <jdoe@example.com>" would be valid.
|
662
|
+
*/
|
663
|
+
$.Guards.prototype.isValidEmail = function(value, options) {
|
664
|
+
if (options && options.allowDisplay) {
|
665
|
+
var result = /.*\<([^>]+)\>\s*$/.exec(value);
|
666
|
+
|
667
|
+
if (result) {
|
668
|
+
value = result[1];
|
669
|
+
}
|
670
|
+
}
|
671
|
+
|
672
|
+
return value == "" || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
|
673
|
+
};
|
674
|
+
|
675
|
+
/**
|
676
|
+
* Validates the given value is a valid US phone number.
|
677
|
+
*/
|
678
|
+
$.Guards.prototype.isValidPhoneUS = function(value) {
|
679
|
+
value = value.replace(/\s+/g, "");
|
680
|
+
return value == "" || value.length > 9 &&
|
681
|
+
value.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
|
682
|
+
};
|
683
|
+
|
684
|
+
/**
|
685
|
+
* Return whether or not the value is a valid string. Appropriate
|
686
|
+
* options are min or max (or both). Whitespace is not
|
687
|
+
* considered.
|
688
|
+
*/
|
689
|
+
$.Guards.prototype.isValidString = function(value, options) {
|
690
|
+
value = $.trim(value);
|
691
|
+
return this.isValidInt("" + value.length, options);
|
692
|
+
};
|
693
|
+
|
694
|
+
/**
|
695
|
+
* This guard test method is intended to never fail, thus it
|
696
|
+
* returns true no matter what. It is intended to be used to set
|
697
|
+
* up a guard that is triggered manually via triggerError().
|
698
|
+
*/
|
699
|
+
$.Guards.prototype.never = function(value) {
|
700
|
+
return true;
|
701
|
+
};
|
702
|
+
|
703
|
+
/**
|
704
|
+
* This is a utility function to act like isAnyValid or
|
705
|
+
* isAllValid, except instead of aggregating the function results,
|
706
|
+
* it passes the arguments on to the function and returns the
|
707
|
+
* results. It makes the argument an array always.
|
708
|
+
*
|
709
|
+
* Example: $.guards.passThrough([true, false, true], function(x) { return x[1]; }); // false
|
710
|
+
* Example: $.guards.passThrough(true, function(x) { return x[0]; }); // true
|
711
|
+
*/
|
712
|
+
$.Guards.prototype.passThrough = function(values, fn) {
|
713
|
+
if (!this.isArray(values)) {
|
714
|
+
values = [values];
|
715
|
+
}
|
716
|
+
|
717
|
+
return fn(values);
|
718
|
+
};
|
719
|
+
|
720
|
+
/**
|
721
|
+
* Guard all elements with the specified jQuery selector. Using
|
722
|
+
* is implicitly called with $.guards.defaults.guard, which
|
723
|
+
* defaults to "required". Note that it is simpler to use
|
724
|
+
* $.guard(selector) instead of $.guards.add(selector).
|
725
|
+
*
|
726
|
+
* Example: $.guards.add(".validPhone").using("phoneUS");
|
727
|
+
* Example: $.guards.add(".custom").using(function(value, element) {
|
728
|
+
* return value != "invalid";
|
729
|
+
* }).message("Don't use the keyword 'invalid'.");
|
730
|
+
* Example: $.guards.add(".custom").grouped().using(function(values, elements) {
|
731
|
+
* return $.inArray("invalid", values) == -1;
|
732
|
+
* }).target("#custom-error-location").tag("div")
|
733
|
+
* .message("Don't use the keyword 'invalid'.");
|
734
|
+
*/
|
735
|
+
$.Guards.prototype.add = function(selector) {
|
736
|
+
var guard = new $.Guard(selector, this);
|
737
|
+
this._guards.push(guard);
|
738
|
+
return guard;
|
739
|
+
};
|
740
|
+
|
741
|
+
/**
|
742
|
+
* Clear all errors on the form's guard fields, then invoke each
|
743
|
+
* guard on the fields in order and guard them, adding errors
|
744
|
+
* along the way as needed. Once done, focus the first visible
|
745
|
+
* field with an error.
|
746
|
+
*/
|
747
|
+
$.Guards.prototype.guard = function(form) {
|
748
|
+
var fields = form.guardableFields().clearErrors();
|
749
|
+
var result = this.applyGuards(function(guard) { return fields; });
|
750
|
+
fields.filter(":visible:has-error").eq(0).focus();
|
751
|
+
return result;
|
752
|
+
};
|
753
|
+
|
754
|
+
/**
|
755
|
+
* Apply all the guards to the fields returned from the given
|
756
|
+
* callback. The callback will receive the guard, and is expected
|
757
|
+
* to return the fields to guard against. If it returns false,
|
758
|
+
* that guard is skipped and does not affect the return value.
|
759
|
+
*/
|
760
|
+
$.Guards.prototype.applyGuards = function(callback) {
|
761
|
+
var result = true;
|
762
|
+
var self = this;
|
763
|
+
|
764
|
+
$.each(this._guards, function(index, guard) {
|
765
|
+
var fields = callback(guard);
|
766
|
+
|
767
|
+
if (fields !== false && !self.test(guard, fields)) {
|
768
|
+
result = false;
|
769
|
+
}
|
770
|
+
});
|
771
|
+
|
772
|
+
return result;
|
773
|
+
};
|
774
|
+
|
775
|
+
/**
|
776
|
+
* Use the given guard to test the given guarded fields. Errors
|
777
|
+
* will be applied if the field doesn't have an error yet.
|
778
|
+
*/
|
779
|
+
$.Guards.prototype.test = function(guard, fields) {
|
780
|
+
if (guard._grouped) {
|
781
|
+
return guard.test(fields);
|
782
|
+
}
|
783
|
+
|
784
|
+
var result = true;
|
785
|
+
|
786
|
+
fields.each(function() {
|
787
|
+
if (!guard.test(this)) {
|
788
|
+
result = false;
|
789
|
+
}
|
790
|
+
});
|
791
|
+
|
792
|
+
return result;
|
793
|
+
};
|
794
|
+
|
795
|
+
$.Guard = function(selector, guards) {
|
796
|
+
this._guards = guards || $.guards;
|
797
|
+
this._selector = selector;
|
798
|
+
this._grouped = this._guards.defaults.grouped;
|
799
|
+
this._tag = this._guards.defaults.tag;
|
800
|
+
this._messageClass = this._guards.defaults.messageClass;
|
801
|
+
this._invalidClass = this._guards.defaults.invalidClass;
|
802
|
+
this._target = this._guards.defaults.target;
|
803
|
+
this.using(this._guards.defaults.guard);
|
804
|
+
};
|
805
|
+
|
806
|
+
/**
|
807
|
+
* Guard inputs using a specified guard. The guard may be either
|
808
|
+
* a string or a function. When it is a string, it must match one
|
809
|
+
* of the pre-defined guards defined in $.guards.defaults.guards.
|
810
|
+
* The function is expected to have 2 arguments. The first is the
|
811
|
+
* value of the element being guarded, and the second is the
|
812
|
+
* actual element. If grouped is true, it will be an array of all
|
813
|
+
* matched values and all matched elements (the order of values
|
814
|
+
* will match the order of elements). Radio buttons are passed as
|
815
|
+
* separate values and elements, but the value of each will be the
|
816
|
+
* same. Specifically, the value of the checked radio button is
|
817
|
+
* the value used, unless none are checked, in which case
|
818
|
+
* $.guards.constants.notChecked will be used (which is predefined
|
819
|
+
* as an empty string).
|
820
|
+
*
|
821
|
+
* Note that the message is implicitly set when this method is
|
822
|
+
* called. If the guard is a string, the message will be set to
|
823
|
+
* $.guards.defaults.messages[guard]. If it is a function, it
|
824
|
+
* will be set to $.guards.defaults.messages.undefined.
|
825
|
+
*
|
826
|
+
* Example: $.guard(".required").using("required");
|
827
|
+
* Example: $.guard(".required").using(function(value, element) {
|
828
|
+
* return $.inArray("invalid", values) == -1;
|
829
|
+
* });
|
830
|
+
*/
|
831
|
+
$.Guard.prototype.using = function(guard) {
|
832
|
+
if (typeof(guard) == "string") {
|
833
|
+
var args = [];
|
834
|
+
|
835
|
+
if (arguments.length > 1) {
|
836
|
+
args = $.makeArray(arguments).slice(1);
|
837
|
+
}
|
838
|
+
|
839
|
+
var fn = this._guards.defaults.guards[guard];
|
840
|
+
|
841
|
+
if (this._guards.isNullOrUndefined(fn)) {
|
842
|
+
throw new Error("There is no standard guard named '" + guard + "'");
|
843
|
+
}
|
844
|
+
|
845
|
+
this._guard = fn.apply(this._guards.defaults.guards, args);
|
846
|
+
var message = this._guards.defaults.messages[guard];
|
847
|
+
|
848
|
+
if ($.isFunction(message)) {
|
849
|
+
message = message.apply(this._guards.defaults.messages, args);
|
850
|
+
}
|
851
|
+
|
852
|
+
return this.message(message);
|
853
|
+
}
|
854
|
+
|
855
|
+
this._guard = guard;
|
856
|
+
return this.message(this._guards.defaults.messages.undefined);
|
857
|
+
};
|
858
|
+
|
859
|
+
/**
|
860
|
+
* Specify a precondition for this guard. The precondition should
|
861
|
+
* be a function that accepts the element and element value as the
|
862
|
+
* parameters, like a custom guard function. The precondition is
|
863
|
+
* executed before the guard when any given input is about to be
|
864
|
+
* guarded. If the precondition returns false explicitly, the
|
865
|
+
* guard will not be executed and the field will be considered
|
866
|
+
* valid. Any other return value means the precondition passed
|
867
|
+
* (even no return). If the guard is grouped, the parameters will
|
868
|
+
* be the array of values and elements (like for a custom guard
|
869
|
+
* function).
|
870
|
+
*
|
871
|
+
* // Only require this if #other_element is checked.
|
872
|
+
* Example: $.guard(".required").using("required").precondition(function(value, element) {
|
873
|
+
* return $("#other_element").is(":checked");
|
874
|
+
* });
|
875
|
+
*
|
876
|
+
* @since 0.4
|
877
|
+
*/
|
878
|
+
$.Guard.prototype.precondition = function(fn) {
|
879
|
+
this._precondition = fn;
|
880
|
+
return this;
|
881
|
+
};
|
882
|
+
|
883
|
+
/**
|
884
|
+
* Return whether or not this guard is grouped.
|
885
|
+
*/
|
886
|
+
$.Guard.prototype.isGrouped = function() {
|
887
|
+
return this._grouped;
|
888
|
+
};
|
889
|
+
|
890
|
+
/**
|
891
|
+
* Specify whether to group element guarding by passing all values
|
892
|
+
* and elements at once instead of one at a time. When grouped,
|
893
|
+
* only 1 error message is added, and it is added after the last
|
894
|
+
* element. This defaults to $.guards.defaults.grouped. If an
|
895
|
+
* argument is passed, the value is used as the grouped value,
|
896
|
+
* otherwise invoking this method will set grouped to true.
|
897
|
+
*
|
898
|
+
* Example: $.guard(".required").using("required").grouped();
|
899
|
+
* Example: $.guard(".required").using("required").grouped(true);
|
900
|
+
*/
|
901
|
+
$.Guard.prototype.grouped = function() {
|
902
|
+
if (arguments.length == 0) {
|
903
|
+
return this.grouped(true);
|
904
|
+
}
|
905
|
+
|
906
|
+
this._grouped = arguments[0];
|
907
|
+
return this;
|
908
|
+
};
|
909
|
+
|
910
|
+
/**
|
911
|
+
* Set the type of tag to surround the error message with
|
912
|
+
* (defaults to $.guards.defaults.tag, which defaults to span).
|
913
|
+
*
|
914
|
+
* Example: $.guard(".required").using("required").tag("div");
|
915
|
+
*/
|
916
|
+
$.Guard.prototype.tag = function(tag) {
|
917
|
+
this._tag = tag;
|
918
|
+
return this.resetMessageFn();
|
919
|
+
};
|
920
|
+
|
921
|
+
$.Guard.prototype.messageClass = function(messageClass) {
|
922
|
+
this._messageClass = messageClass;
|
923
|
+
return this.resetMessageFn();
|
924
|
+
};
|
925
|
+
|
926
|
+
/**
|
927
|
+
* Set the error message to display on errors. If using is called
|
928
|
+
* with a string, this is implicitly invoked using
|
929
|
+
* $.guards.defaults.messages[usingValue]. If using is called
|
930
|
+
* with a function, this is implicitly invoked using
|
931
|
+
* $.guards.defaults.messages.undefined.
|
932
|
+
*
|
933
|
+
* Example: $.guard(".required").using("required").message("Enter something!");
|
934
|
+
*/
|
935
|
+
$.Guard.prototype.message = function(message) {
|
936
|
+
this._message = message;
|
937
|
+
return this.resetMessageFn();
|
938
|
+
};
|
939
|
+
|
940
|
+
$.Guard.prototype.invalidClass = function(invalidClass) {
|
941
|
+
this._invalidClass = invalidClass;
|
942
|
+
return this;
|
943
|
+
};
|
944
|
+
|
945
|
+
$.Guard.prototype.resetMessageFn = function() {
|
946
|
+
var self = this;
|
947
|
+
return this.messageFn(function() {
|
948
|
+
return $('<' + self._tag + ' class="' + self._messageClass + '"/>').html(self._message);
|
949
|
+
});
|
950
|
+
};
|
951
|
+
|
952
|
+
$.Guard.prototype.messageFn = function(messageFn) {
|
953
|
+
this._messageFn = messageFn;
|
954
|
+
return this;
|
955
|
+
};
|
956
|
+
|
957
|
+
$.Guard.prototype.errorElement = function() {
|
958
|
+
return this._messageFn();
|
959
|
+
};
|
960
|
+
|
961
|
+
$.Guard.prototype.attachError = function(elements, errorElement) {
|
962
|
+
if (this._target && $.isFunction(this._target)) {
|
963
|
+
var result = this._target.call(elements, errorElement);
|
964
|
+
|
965
|
+
if (result !== false) {
|
966
|
+
errorElement.appendTo($(result).eq(0));
|
967
|
+
}
|
968
|
+
} else if (this._target) {
|
969
|
+
errorElement.appendTo($(this._target).eq(0));
|
970
|
+
} else {
|
971
|
+
throw new Error("The target must be a function or selector!");
|
972
|
+
}
|
973
|
+
};
|
974
|
+
|
975
|
+
/**
|
976
|
+
* Set the target for where error messages should be appended to.
|
977
|
+
* By default, the error is placed after the error element, but
|
978
|
+
* when a target is specified, the error is appended within. The
|
979
|
+
* target may be either a selector, function, element or set of
|
980
|
+
* elements, however, only the first element is used as the target
|
981
|
+
* location for errors. If a function is specified, it will be
|
982
|
+
* called when there is a new error with the invalid element (or
|
983
|
+
* set of elements if it is a grouped guard) as the "this"
|
984
|
+
* reference. The returned value should be a single element,
|
985
|
+
* though if an array of elements is returned (or a jQuery
|
986
|
+
* selected set of elements), only the first element will be used
|
987
|
+
* as the target. Alternatively the function can take a single
|
988
|
+
* argument that specifies the error element to add to the DOM,
|
989
|
+
* and the function is expected to add the element and return
|
990
|
+
* false (indicating that it has taken care of adding the error
|
991
|
+
* element).
|
992
|
+
*
|
993
|
+
* The default target is a function that appends the error after
|
994
|
+
* the last element and returns false. The default can be changed
|
995
|
+
* via $.guards.defaults.target.
|
996
|
+
*
|
997
|
+
* Example: $.guard(".required").using("required").target("#my-errors");
|
998
|
+
* Example: $.guard(".required").using("required").target(function() { return $(this).nextAll(".error:eq(0)"); });
|
999
|
+
* Example: $.guard(".required").using("required").target(function(errorElement) {
|
1000
|
+
* errorElement.appendTo($("#myErrors"));
|
1001
|
+
* return false;
|
1002
|
+
* });
|
1003
|
+
*/
|
1004
|
+
$.Guard.prototype.target = function(target) {
|
1005
|
+
this._target = target;
|
1006
|
+
return this;
|
1007
|
+
};
|
1008
|
+
|
1009
|
+
/**
|
1010
|
+
* Determine if this guard applies to the given element (or
|
1011
|
+
* elements).
|
1012
|
+
*/
|
1013
|
+
$.Guard.prototype.appliesTo = function(element) {
|
1014
|
+
return $(element).filter(this._selector).size() > 0;
|
1015
|
+
};
|
1016
|
+
|
1017
|
+
/**
|
1018
|
+
* Using this guard, test the given element. If this guard is
|
1019
|
+
* grouped, the element is expected to actually be all field
|
1020
|
+
* elements. Returns false but doesn't apply the guard if there
|
1021
|
+
* are already errors detected on the element(s). Returns true if
|
1022
|
+
* the selector defined for this guard doesn't apply to this
|
1023
|
+
* element(s). Otherwise, applies the guard and adds an error if
|
1024
|
+
* it fails.
|
1025
|
+
*/
|
1026
|
+
$.Guard.prototype.test = function(element) {
|
1027
|
+
var $elements = $(element).filter(this._selector);
|
1028
|
+
|
1029
|
+
if ($elements.size() == 0) {
|
1030
|
+
return true;
|
1031
|
+
}
|
1032
|
+
|
1033
|
+
if (!this._guards.options.stackErrors && $elements.hasErrors()) {
|
1034
|
+
return false;
|
1035
|
+
}
|
1036
|
+
|
1037
|
+
var result;
|
1038
|
+
|
1039
|
+
// Grouped expects a group of elements, while non-grouped
|
1040
|
+
// expects a single element.
|
1041
|
+
if (this._grouped) {
|
1042
|
+
var values = [];
|
1043
|
+
var elements = [];
|
1044
|
+
|
1045
|
+
$elements.each(function() {
|
1046
|
+
values.push($(this).inputValue(this._guards));
|
1047
|
+
elements.push(this);
|
1048
|
+
});
|
1049
|
+
|
1050
|
+
if (this._precondition && this._precondition(values, elements) === false) {
|
1051
|
+
result = true;
|
1052
|
+
} else {
|
1053
|
+
result = this._guard(values, elements);
|
1054
|
+
}
|
1055
|
+
} else {
|
1056
|
+
var value = $elements.inputValue(this._guards);
|
1057
|
+
|
1058
|
+
if (this._precondition && this._precondition(value, element) === false) {
|
1059
|
+
result = true;
|
1060
|
+
} else {
|
1061
|
+
result = this._guard(value, element);
|
1062
|
+
}
|
1063
|
+
}
|
1064
|
+
|
1065
|
+
if (!result) {
|
1066
|
+
this.triggerError($elements);
|
1067
|
+
}
|
1068
|
+
|
1069
|
+
return result;
|
1070
|
+
};
|
1071
|
+
|
1072
|
+
/**
|
1073
|
+
* Explicitly trigger the error for this guard on all the elements
|
1074
|
+
* provided to this function. The elements are wrapped with a
|
1075
|
+
* jQuery object, so they may be a single element, a list of
|
1076
|
+
* elements, a jQuery selected set of elements, or even a valid
|
1077
|
+
* jQuery selector. Note that the elements don't have to be valid
|
1078
|
+
* for this guard to be applied.
|
1079
|
+
*/
|
1080
|
+
$.Guard.prototype.triggerError = function(elements) {
|
1081
|
+
if (this._grouped) {
|
1082
|
+
$(elements).addSingleError(this);
|
1083
|
+
} else {
|
1084
|
+
$(elements).addError(this);
|
1085
|
+
}
|
1086
|
+
|
1087
|
+
return this;
|
1088
|
+
}
|
1089
|
+
|
1090
|
+
$.GuardError = function(guard, element, errorElement, linked) {
|
1091
|
+
this._guard = guard;
|
1092
|
+
this._element = element;
|
1093
|
+
this._errorElement = errorElement;
|
1094
|
+
this._linked = linked;
|
1095
|
+
this._cleared = false;
|
1096
|
+
};
|
1097
|
+
|
1098
|
+
/**
|
1099
|
+
* Clear this error and any errors linked with it (grouped guards
|
1100
|
+
* and radio buttons cause all elements involved to be linked).
|
1101
|
+
*/
|
1102
|
+
$.GuardError.prototype.clear = function() {
|
1103
|
+
if (this._cleared) {
|
1104
|
+
return;
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
this._errorElement.remove();
|
1108
|
+
var index = $.inArray(this, this._element.errors);
|
1109
|
+
|
1110
|
+
if (index >= 0) {
|
1111
|
+
this._element.errors.splice(index, 1);
|
1112
|
+
}
|
1113
|
+
|
1114
|
+
if (!$(this._element).hasErrorsWithInvalidClass(this._guard._invalidClass)) {
|
1115
|
+
$(this._element).removeClass(this._guard._invalidClass);
|
1116
|
+
}
|
1117
|
+
|
1118
|
+
this._cleared = true;
|
1119
|
+
|
1120
|
+
while (this._linked.length > 0) {
|
1121
|
+
this._linked.shift().clear();
|
1122
|
+
}
|
1123
|
+
};
|
1124
|
+
|
1125
|
+
/**
|
1126
|
+
* Find any applicable fields for this selected item. Applicable
|
1127
|
+
* fields are any inputs, textareas or selects.
|
1128
|
+
*/
|
1129
|
+
$.fn.guardableFields = function() {
|
1130
|
+
return this.find(":guardable");
|
1131
|
+
};
|
1132
|
+
|
1133
|
+
/**
|
1134
|
+
* Return the result of guarding the selected form.
|
1135
|
+
*/
|
1136
|
+
$.fn.guard = function() {
|
1137
|
+
return $.guards.guard(this);
|
1138
|
+
};
|
1139
|
+
|
1140
|
+
/**
|
1141
|
+
* Explicitly trigger the given guard's error all the selected
|
1142
|
+
* elements. Note that the selected elements don't have to be
|
1143
|
+
* valid for this guard to be applied. This is equivalent to
|
1144
|
+
* calling guard.triggerError($this);
|
1145
|
+
*/
|
1146
|
+
$.fn.triggerError = function(guard) {
|
1147
|
+
guard.triggerError(this);
|
1148
|
+
};
|
1149
|
+
|
1150
|
+
/**
|
1151
|
+
* Add a single error message, but mark every selected element as
|
1152
|
+
* in error pointing to the single error message. This differs
|
1153
|
+
* from addError because addError will add a new error message for
|
1154
|
+
* each selected element instead of just 1.
|
1155
|
+
*/
|
1156
|
+
$.fn.addSingleError = function(guard) {
|
1157
|
+
if (this.size() == 0) {
|
1158
|
+
$.guards.log("Attempted to add error to nothing.");
|
1159
|
+
return this;
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
var element = guard.errorElement();
|
1163
|
+
guard.attachError(this, element);
|
1164
|
+
this.addClass(guard._invalidClass);
|
1165
|
+
var linked = [];
|
1166
|
+
|
1167
|
+
return this.each(function() {
|
1168
|
+
if (!this.errors) {
|
1169
|
+
this.errors = [];
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
var error = new $.GuardError(guard, this, element, linked);
|
1173
|
+
linked.push(error);
|
1174
|
+
this.errors.push(error);
|
1175
|
+
});
|
1176
|
+
};
|
1177
|
+
|
1178
|
+
/**
|
1179
|
+
* Add an error message to each of the selected elements, with an
|
1180
|
+
* optional error target to place it. The target can be a
|
1181
|
+
* selector, though it will use the first selected element as the
|
1182
|
+
* target.
|
1183
|
+
*/
|
1184
|
+
$.fn.addError = function(guard) {
|
1185
|
+
var radiosAdded = {};
|
1186
|
+
|
1187
|
+
return this.each(function() {
|
1188
|
+
var $this = $(this);
|
1189
|
+
|
1190
|
+
if ($this.is(":radio")) {
|
1191
|
+
var name = $this.attr("name");
|
1192
|
+
|
1193
|
+
if (radiosAdded[name]) {
|
1194
|
+
return;
|
1195
|
+
}
|
1196
|
+
|
1197
|
+
radiosAdded[name] = true;
|
1198
|
+
var radios = $("input[name='" + name + "']:radio", $this.parents("form"));
|
1199
|
+
radios.addSingleError(guard);
|
1200
|
+
} else {
|
1201
|
+
$this.addSingleError(guard);
|
1202
|
+
}
|
1203
|
+
});
|
1204
|
+
};
|
1205
|
+
|
1206
|
+
/**
|
1207
|
+
* Obtain all errors attached to the selected elements.
|
1208
|
+
*/
|
1209
|
+
$.fn.errors = function() {
|
1210
|
+
var result = [];
|
1211
|
+
|
1212
|
+
this.each(function() {
|
1213
|
+
if (this.errors && this.errors.length > 0) {
|
1214
|
+
result.push.apply(result, this.errors);
|
1215
|
+
}
|
1216
|
+
});
|
1217
|
+
|
1218
|
+
return result;
|
1219
|
+
};
|
1220
|
+
|
1221
|
+
/**
|
1222
|
+
* Clear errors attached to the selected elements.
|
1223
|
+
*/
|
1224
|
+
$.fn.clearErrors = function() {
|
1225
|
+
$.each(this.errors(), function(index, error) {
|
1226
|
+
error.clear();
|
1227
|
+
});
|
1228
|
+
|
1229
|
+
return this;
|
1230
|
+
};
|
1231
|
+
|
1232
|
+
/**
|
1233
|
+
* Determine if any errors exist in the selected elements.
|
1234
|
+
*/
|
1235
|
+
$.fn.hasErrors = function() {
|
1236
|
+
return this.errors().length > 0;
|
1237
|
+
};
|
1238
|
+
|
1239
|
+
$.fn.hasErrorsWithInvalidClass = function(invalidClass) {
|
1240
|
+
var result = false;
|
1241
|
+
|
1242
|
+
$.each(this.errors(), function(i, error) {
|
1243
|
+
if (error._guard._invalidClass == invalidClass) {
|
1244
|
+
result = true;
|
1245
|
+
return false;
|
1246
|
+
}
|
1247
|
+
});
|
1248
|
+
|
1249
|
+
return result;
|
1250
|
+
};
|
1251
|
+
|
1252
|
+
/**
|
1253
|
+
* Obtain the value of the first selected input. This differs
|
1254
|
+
* from val() in that it will properly get the value of a set of
|
1255
|
+
* radio buttons.
|
1256
|
+
*/
|
1257
|
+
$.fn.inputValue = function(guards) {
|
1258
|
+
guards = guards || $.guards;
|
1259
|
+
|
1260
|
+
if (this.is(":radio")) {
|
1261
|
+
var checked = $("input[name='" + this.attr("name") + "']:radio:checked", this.parents("form"));
|
1262
|
+
|
1263
|
+
if (checked.size() == 0) {
|
1264
|
+
return guards.constants.notChecked;
|
1265
|
+
}
|
1266
|
+
|
1267
|
+
return checked.val();
|
1268
|
+
}
|
1269
|
+
|
1270
|
+
if (this.is(":checkbox")) {
|
1271
|
+
if (this.is(":checked")) {
|
1272
|
+
return this.val();
|
1273
|
+
}
|
1274
|
+
|
1275
|
+
return guards.constants.notChecked;
|
1276
|
+
}
|
1277
|
+
|
1278
|
+
return this.val();
|
1279
|
+
};
|
1280
|
+
|
1281
|
+
/**
|
1282
|
+
* Enable guards of this form by attaching a submit button to it
|
1283
|
+
* that returns the result of calling guard(). This will block
|
1284
|
+
* any other submit event handlers and prevent the form from being
|
1285
|
+
* submitted if guarding fails.
|
1286
|
+
*/
|
1287
|
+
$.fn.enableGuards = function() {
|
1288
|
+
return this.submit(function() {
|
1289
|
+
return $(this).guard();
|
1290
|
+
});
|
1291
|
+
};
|
1292
|
+
|
1293
|
+
/**
|
1294
|
+
* Enable guards for any form that matches the given selector.
|
1295
|
+
* This uses live events to catch submit on forms matching the
|
1296
|
+
* selector.
|
1297
|
+
*/
|
1298
|
+
$.enableGuards = function(selector) {
|
1299
|
+
$.guards.enableGuards(selector);
|
1300
|
+
};
|
1301
|
+
|
1302
|
+
/**
|
1303
|
+
* Live guard the form(s) in the given selector. This will bind
|
1304
|
+
* live on change and blur events that will guard the elements
|
1305
|
+
* when they change. It will also guard the form when it is
|
1306
|
+
* submitted.
|
1307
|
+
*/
|
1308
|
+
$.liveGuard = function(selector) {
|
1309
|
+
$.guards.liveGuard(selector);
|
1310
|
+
};
|
1311
|
+
|
1312
|
+
$.extend($.expr[":"], {
|
1313
|
+
"has-error": function(x) {
|
1314
|
+
return new Boolean(x.errors && x.errors.length > 0).valueOf();
|
1315
|
+
},
|
1316
|
+
"guardable": function(x) {
|
1317
|
+
return x.tagName.toLowerCase() == "input" || x.tagName.toLowerCase() == "textarea" || x.tagName.toLowerCase() == "select";
|
1318
|
+
}
|
1319
|
+
});
|
1320
|
+
|
1321
|
+
$.guards = new $.Guards();
|
1322
|
+
|
1323
|
+
$(function() {
|
1324
|
+
// Clear errors when the user expresses intent to fix the
|
1325
|
+
// errors.
|
1326
|
+
var clearFn = function() { $(this).clearErrors(); };
|
1327
|
+
$.guards.on(":has-error", "change", clearFn);
|
1328
|
+
$.guards.on(":has-error:radio,:has-error:checkbox", "mouseup", clearFn);
|
1329
|
+
$.guards.on("select:has-error", "mousedown", clearFn);
|
1330
|
+
|
1331
|
+
// Make sure we don't clear it if there was no error when the
|
1332
|
+
// keydown happened, otherwise a submit on enter will have the
|
1333
|
+
// error flash and then go away on the keyup.
|
1334
|
+
$.guards.on(":has-error", "keydown", function() { this.clearable = true; });
|
1335
|
+
$.guards.on(":has-error", "keyup", function() {
|
1336
|
+
if (this.clearable) {
|
1337
|
+
this.clearable = false;
|
1338
|
+
$(this).clearErrors();
|
1339
|
+
}
|
1340
|
+
});
|
1341
|
+
});
|
1342
|
+
})(jQuery);
|