guardsjs-rails 0.7.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  module GuardsJS
2
2
  module Rails
3
- VERSION = "0.7.2"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Guards JavaScript jQuery Plugin v0.7.2
2
+ * Guards JavaScript jQuery Plugin v1.0.0
3
3
  * https://github.com/on-site/guards.js
4
4
  *
5
5
  * Copyright 2010-2013, On-Site.com, http://www.on-site.com/
@@ -8,51 +8,52 @@
8
8
  * Includes code for email and phone number validation from the jQuery
9
9
  * Validation plugin. http://docs.jquery.com/Plugins/Validation
10
10
  *
11
- * Date: Mon Feb 25 03:47:45 2013 -0800
11
+ * Date: Wed Apr 17 01:43:25 2013 -0700
12
12
  */
13
13
 
14
14
  /**
15
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();
16
+ * plugin (http://plugins.jquery.com/validation/).
45
17
  */
46
18
  (function($) {
19
+ /*jshint devel:true, jquery:true */
20
+ "use strict";
21
+
22
+ /**
23
+ * @page Global Functions
24
+ * @section guard
25
+ * @signature jQuery.guard(selector)
26
+ * @since 1.0.0
27
+ *
28
+ * <p>
29
+ * Guard elements with the given selector when the form is guarded. This is the way to add
30
+ * new guards to form inputs. It returns a <a href="guard_type.html"><code>Guards</code></a>
31
+ * instance which has chainable methods to define the attributes of the guard.
32
+ * </p>
33
+ *
34
+ * <div class="example">
35
+ * <div class="display">
36
+ * <script>
37
+ * $.guard(".guarded").using("required").message("Please provide a value.");
38
+ * </script>
39
+ *
40
+ * <p>
41
+ * <input class="guarded" type="text" /><br />
42
+ * <small>Required field</small>
43
+ * </p>
44
+ * </div>
45
+ * </div>
46
+ */
47
47
  $.guard = function(selector) {
48
48
  return $.guards.add(selector);
49
49
  };
50
50
 
51
- $.guard.version = "0.7.2";
51
+ $.guard.version = "1.0.0";
52
52
 
53
53
  $.Guards = function() {
54
54
  var self = this;
55
55
  this._guards = [];
56
+ this.named = {};
56
57
 
57
58
  this.options = {
58
59
  stackErrors: false
@@ -62,120 +63,36 @@
62
63
  notChecked: ""
63
64
  };
64
65
 
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
- }
66
+ this.defaults = {
67
+ grouped: false,
68
+ guard: "required",
69
+ invalidClass: "invalid-field",
96
70
 
97
- if (maxDefined) {
98
- return self.format(formatting.max, minMaxFormat(options.max));
99
- }
71
+ liveCallback: function(e) {
72
+ var $element = $(e.target);
100
73
 
101
- if (formatting.invalid) {
102
- return formatting.invalid;
74
+ if (!$element.is(":guardable")) {
75
+ return;
103
76
  }
104
77
 
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",
78
+ $element.clearErrors();
118
79
 
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")
80
+ self.applyGuards(function(guard) {
81
+ if (guard.isGrouped()) {
82
+ if (guard.appliesTo($element)) {
83
+ return self.parentContext($element).find(":guardable");
84
+ } else {
85
+ return false;
86
+ }
87
+ } else {
88
+ return $element;
89
+ }
90
+ });
134
91
  },
135
92
 
136
- invalidClass: "invalid-field",
137
93
  messageClass: "error-message",
138
94
 
139
95
  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
96
  "undefined": "Please fix this field."
180
97
  },
181
98
 
@@ -190,36 +107,605 @@
190
107
  }
191
108
  },
192
109
 
110
+ submitCallback: function() { return self.guard($(this)); },
193
111
  tag: "span",
194
112
 
195
113
  target: function(errorElement) {
196
114
  var last = $(this).filter(":last");
197
115
 
198
- if (last.is(":radio,:checkbox")) {
116
+ if (last.is(":radio,:checkbox") && last[0].nextSibling) {
199
117
  last = $(last[0].nextSibling);
200
118
  }
201
119
 
120
+ var next = last.next();
121
+
122
+ while (next.size() > 0 && next[0].isGuardError) {
123
+ last = next;
124
+ next = last.next();
125
+ }
126
+
202
127
  errorElement.insertAfter(last);
203
128
  return false;
204
129
  }
205
130
  };
131
+
132
+ /**
133
+ * @page Named Guards
134
+ * @section allow
135
+ * @since 1.0.0
136
+ *
137
+ * <p>
138
+ * Only values found in the given list are considered valid. Anything else triggers a failure.
139
+ * This guard <strong>requires</strong> an array parameter of the valid values.
140
+ * </p>
141
+ *
142
+ * <div class="example">
143
+ * <div class="display">
144
+ * <script>
145
+ * $.guard(".primary-color").using("allow", ["red", "yellow", "blue"]);
146
+ * </script>
147
+ *
148
+ * <p>
149
+ * <input class="primary-color" type="text" /><br />
150
+ * <small>Allowed values: red, yellow, blue</small>
151
+ * </p>
152
+ * </div>
153
+ * </div>
154
+ */
155
+ this.name("allow").using(this.aggregate(this.isAllValid, this.isAllowed)).message(this.arrayMessage("Please enter one of: #{0}."));
156
+
157
+ /**
158
+ * @page Named Guards
159
+ * @section always
160
+ * @since 1.0.0
161
+ *
162
+ * <p>
163
+ * Always fail, no matter what. For this guard to pass, either the guard must be removed, or
164
+ * the element(s) guarded must be removed. No parameters are accepted.
165
+ * </p>
166
+ *
167
+ * <div class="example">
168
+ * <div class="display">
169
+ * <script>
170
+ * $.guard(".always").using("always");
171
+ * </script>
172
+ *
173
+ * <p>
174
+ * <input class="always" type="text" /><br />
175
+ * <small>Always fails, no matter what</small>
176
+ * </p>
177
+ * </div>
178
+ * </div>
179
+ */
180
+ this.name("always").using(this.aggregate(this.isAllValid, this.always)).message("There was an error.");
181
+
182
+ /**
183
+ * @page Named Guards
184
+ * @section different
185
+ * @since 1.0.0
186
+ *
187
+ * <p>
188
+ * This is a grouped guard where every field must have a different value.
189
+ * </p>
190
+ *
191
+ * <div class="example">
192
+ * <div class="display">
193
+ * <script>
194
+ * $.guard(".unique").using("different");
195
+ * </script>
196
+ *
197
+ * <p>
198
+ * <input class="unique" type="text" value="Unique Required" /><br />
199
+ * <input class="unique" type="text" value="Unique Required" /><br />
200
+ * <small>Each value must be unique</small>
201
+ * </p>
202
+ * </div>
203
+ * </div>
204
+ */
205
+ this.name("different").grouped().using(this.aggregate(this.passThrough, this.isDifferent)).message("These values must all be different.");
206
+
207
+ /**
208
+ * @page Named Guards
209
+ * @section disallow
210
+ * @since 1.0.0
211
+ *
212
+ * <p>
213
+ * Guard against specific values. This guard <strong>requires</strong> an array parameter
214
+ * of the invalid values.
215
+ * </p>
216
+ *
217
+ * <div class="example">
218
+ * <div class="display">
219
+ * <script>
220
+ * $.guard(".not-primary-color").using("disallow", ["red", "yellow", "blue"]);
221
+ * </script>
222
+ *
223
+ * <p>
224
+ * <input class="not-primary-color" type="text" value="red" /><br />
225
+ * <small>Disallowed values: red, yellow, blue</small>
226
+ * </p>
227
+ * </div>
228
+ * </div>
229
+ */
230
+ this.name("disallow").using(this.aggregate(this.isAllValid, this.isDisallowed)).message(this.arrayMessage("Please don't enter: #{0}."));
231
+
232
+ /**
233
+ * @page Named Guards
234
+ * @section email
235
+ * @since 1.0.0
236
+ *
237
+ * <p>
238
+ * Guard for a valid email address. An empty value is ignored, so only once a value exists will
239
+ * this guard start checking for an email address. An optional argument of
240
+ * <code>{ allowDisplay: true }</code> is allowed that may specify whether display emails of the
241
+ * form <code>John Doe &lt;john@example.com&gt;</code> are allowed.
242
+ * </p>
243
+ *
244
+ * <div class="example">
245
+ * <div class="display">
246
+ * <script>
247
+ * $.guard(".email1").using("email");
248
+ * $.guard(".email2").using("email", { allowDisplay: true });
249
+ * </script>
250
+ *
251
+ * <p>
252
+ * <input class="email1" type="text" value="invalid" /><br />
253
+ * <small>Email address of the form "john@example.com"</small>
254
+ * </p>
255
+ *
256
+ * <p>
257
+ * <input class="email2" type="text" value="Still &lt;invalid&gt;" /><br />
258
+ * <small>Email address of the form "John Doe &lt;john@example.com&gt;"</small>
259
+ * </p>
260
+ * </div>
261
+ * </div>
262
+ */
263
+ this.name("email").using(this.aggregate(this.isAllValid, this.isValidEmail)).message("Please enter a valid E-mail address.");
264
+
265
+ /**
266
+ * @page Named Guards
267
+ * @section float
268
+ * @since 1.0.0
269
+ *
270
+ * <p>
271
+ * Guard for a floating point number. Optionally, an object parameter may be passed with
272
+ * <code>min</code> and/or <code>max</code>. Min will restrict the minimum value, while
273
+ * max restricts the maximum. An empty value is considered valid.
274
+ * </p>
275
+ *
276
+ * <div class="example">
277
+ * <div class="display">
278
+ * <script>
279
+ * $.guard(".float1").using("float");
280
+ * $.guard(".float2").using("float", { min: -5.5 });
281
+ * $.guard(".float3").using("float", { max: 42.0 });
282
+ * $.guard(".float4").using("float", { min: 0.0, max: 10.0 });
283
+ * </script>
284
+ *
285
+ * <p>
286
+ * <input class="float1" type="text" value="not valid" /><br />
287
+ * <small>A number of any value</small>
288
+ * </p>
289
+ *
290
+ * <p>
291
+ * <input class="float2" type="text" value="-10.5" /><br />
292
+ * <small>A number no smaller than -5.5</small>
293
+ * </p>
294
+ *
295
+ * <p>
296
+ * <input class="float3" type="text" value="64.32" /><br />
297
+ * <small>A number no bigger than 42</small>
298
+ * </p>
299
+ *
300
+ * <p>
301
+ * <input class="float4" type="text" value="11" /><br />
302
+ * <small>A number from 0 to 10</small>
303
+ * </p>
304
+ * </div>
305
+ * </div>
306
+ */
307
+ this.name("float").using(this.aggregate(this.isAllValid, this.isValidFloat)).message(this.minMaxMessage({
308
+ minAndMax: "Please enter a number from #{0} to #{1}.",
309
+ min: "Please enter a number no less than #{0}.",
310
+ max: "Please enter a number no greater than #{0}.",
311
+ invalid: "Please enter a number."
312
+ }));
313
+
314
+ /**
315
+ * @page Named Guards
316
+ * @section int
317
+ * @since 1.0.0
318
+ *
319
+ * <p>
320
+ * Guard for an integer number. Optionally, an object parameter may be passed with
321
+ * <code>min</code> and/or <code>max</code>. Min will restrict the minimum value, while
322
+ * max restricts the maximum. An empty value is considered valid.
323
+ * </p>
324
+ *
325
+ * <div class="example">
326
+ * <div class="display">
327
+ * <script>
328
+ * $.guard(".int1").using("int");
329
+ * $.guard(".int2").using("int", { min: -5 });
330
+ * $.guard(".int3").using("int", { max: 42 });
331
+ * $.guard(".int4").using("int", { min: 0, max: 10 });
332
+ * </script>
333
+ *
334
+ * <p>
335
+ * <input class="int1" type="text" value="not valid" /><br />
336
+ * <small>An integer of any value</small>
337
+ * </p>
338
+ *
339
+ * <p>
340
+ * <input class="int2" type="text" value="-10.5" /><br />
341
+ * <small>An integer no smaller than -5</small>
342
+ * </p>
343
+ *
344
+ * <p>
345
+ * <input class="int3" type="text" value="64.32" /><br />
346
+ * <small>An integer no bigger than 42</small>
347
+ * </p>
348
+ *
349
+ * <p>
350
+ * <input class="int4" type="text" value="11" /><br />
351
+ * <small>An integer from 0 to 10</small>
352
+ * </p>
353
+ * </div>
354
+ * </div>
355
+ */
356
+ this.name("int").using(this.aggregate(this.isAllValid, this.isValidInt)).message(this.minMaxMessage({
357
+ minAndMax: "Please enter a number from #{0} to #{1}.",
358
+ min: "Please enter a number no less than #{0}.",
359
+ max: "Please enter a number no greater than #{0}.",
360
+ invalid: "Please enter a number."
361
+ }));
362
+
363
+ /**
364
+ * @page Named Guards
365
+ * @section moneyUS
366
+ * @since 1.0.0
367
+ *
368
+ * <p>
369
+ * Guard for a US dollar amount. Optionally, an object parameter may be passed with
370
+ * <code>min</code> and/or <code>max</code>. Min will restrict the minimum value, while
371
+ * max restricts the maximum. An empty value is considered valid.
372
+ * </p>
373
+ *
374
+ * <div class="example">
375
+ * <div class="display">
376
+ * <script>
377
+ * $.guard(".money1").using("moneyUS");
378
+ * $.guard(".money2").using("moneyUS", { min: -5.50 });
379
+ * $.guard(".money3").using("moneyUS", { max: 42.02 });
380
+ * $.guard(".money4").using("moneyUS", { min: 0, max: 10 });
381
+ * </script>
382
+ *
383
+ * <p>
384
+ * <input class="money1" type="text" value="not valid" /><br />
385
+ * <small>US money of any value</small>
386
+ * </p>
387
+ *
388
+ * <p>
389
+ * <input class="money2" type="text" value="-$10.55" /><br />
390
+ * <small>US money no smaller than -$5.50</small>
391
+ * </p>
392
+ *
393
+ * <p>
394
+ * <input class="money3" type="text" value="$64.32" /><br />
395
+ * <small>US money no bigger than $42.02</small>
396
+ * </p>
397
+ *
398
+ * <p>
399
+ * <input class="money4" type="text" value="$11" /><br />
400
+ * <small>US money from $0 to $10</small>
401
+ * </p>
402
+ * </div>
403
+ * </div>
404
+ */
405
+ this.name("moneyUS").using(this.aggregate(this.isAllValid, this.isValidMoneyUS)).message(this.minMaxMessage({
406
+ minAndMax: "Please enter a dollar amount from #{0} to #{1}.",
407
+ min: "Please enter a dollar amount no less than #{0}.",
408
+ max: "Please enter a dollar amount no greater than #{0}.",
409
+ invalid: "Please enter a dollar amount."
410
+ }, function(x) { return x.toFixed(2); }));
411
+
412
+ /**
413
+ * @page Named Guards
414
+ * @section never
415
+ * @since 1.0.0
416
+ *
417
+ * <p>
418
+ * Never fail, no matter what. For this guard to fail, it must be manually triggered via
419
+ * <a href="guard_type.html#triggerError"><code>guard.triggerError(selector)</code></a>.
420
+ * This guard can be useful for marking a field as having an error immediately when the page
421
+ * loads (such as for a server detected error).
422
+ * </p>
423
+ *
424
+ * <div class="example">
425
+ * <div class="display">
426
+ * <script>
427
+ * var guard = $.guard(".never").using("never");
428
+ * $(function() { guard.triggerError(".never"); });
429
+ * </script>
430
+ *
431
+ * <p>
432
+ * <input class="never" type="text" /><br />
433
+ * <small>Never fails, except manually</small>
434
+ * </p>
435
+ * </div>
436
+ * </div>
437
+ */
438
+ this.name("never").using(this.aggregate(this.isAllValid, this.never)).message("There was an error.");
439
+
440
+ /**
441
+ * @page Named Guards
442
+ * @section oneRequired
443
+ * @since 1.0.0
444
+ *
445
+ * <p>
446
+ * This is a grouped guard where a single field of all the selected fields must have a value.
447
+ * </p>
448
+ *
449
+ * <div class="example">
450
+ * <div class="display">
451
+ * <script>
452
+ * $.guard(".oneRequired").using("oneRequired");
453
+ * </script>
454
+ *
455
+ * <p>
456
+ * <input class="oneRequired" type="text" value="" /><br />
457
+ * <input class="oneRequired" type="text" value="" /><br />
458
+ * <small>One value is required</small>
459
+ * </p>
460
+ * </div>
461
+ * </div>
462
+ */
463
+ this.name("oneRequired").grouped().using(this.aggregate(this.isAnyValid, this.isPresent)).message("Specify at least one.");
464
+
465
+ /**
466
+ * @page Named Guards
467
+ * @section phoneUS
468
+ * @since 1.0.0
469
+ *
470
+ * <p>
471
+ * The guarded field is considered valid if no value is given, or if the value given appears
472
+ * to be a valid US phone number. The number must include an area code. Whitespace is ignored.
473
+ * </p>
474
+ *
475
+ * <div class="example">
476
+ * <div class="display">
477
+ * <script>
478
+ * $.guard(".phone").using("phoneUS");
479
+ * </script>
480
+ *
481
+ * <p>
482
+ * <input class="phone" type="text" value="555-1234" /><br />
483
+ * <small>A valid US phone number like (555) 555-1234</small>
484
+ * </p>
485
+ * </div>
486
+ * </div>
487
+ */
488
+ this.name("phoneUS").using(this.aggregate(this.isAllValid, this.isValidPhoneUS)).message("Please enter a valid phone number.");
489
+
490
+ /**
491
+ * @page Named Guards
492
+ * @section required
493
+ * @since 1.0.0
494
+ *
495
+ * <p>
496
+ * These guarded fields must have a value to pass. Only whitespace is not considered a value.
497
+ * If no named or custom guard is defined, this is the default guard used.
498
+ * </p>
499
+ *
500
+ * <div class="example">
501
+ * <div class="display">
502
+ * <script>
503
+ * $.guard(".required1").using("required");
504
+ * $.guard(".required2");
505
+ * </script>
506
+ *
507
+ * <p>
508
+ * <input class="required1" type="text" /><br />
509
+ * <small>A value is required</small>
510
+ * </p>
511
+ *
512
+ * <p>
513
+ * <input class="required2" type="text" /><br />
514
+ * <small>A value is required</small>
515
+ * </p>
516
+ * </div>
517
+ * </div>
518
+ */
519
+ this.name("required").using(this.aggregate(this.isAllValid, this.isPresent)).message("This field is required.");
520
+
521
+ /**
522
+ * @page Named Guards
523
+ * @section same
524
+ * @since 1.0.0
525
+ *
526
+ * <p>
527
+ * This is a grouped guard where every field must have the same value. For example, this guard can
528
+ * be used to implement a password confirmation field.
529
+ * </p>
530
+ *
531
+ * <div class="example">
532
+ * <div class="display">
533
+ * <script>
534
+ * $.guard(".same").using("same");
535
+ * </script>
536
+ *
537
+ * <p>
538
+ * <input class="same" type="text" value="Same Required" /><br />
539
+ * <input class="same" type="text" value="The Same Required" /><br />
540
+ * <small>Each value must be the same</small>
541
+ * </p>
542
+ * </div>
543
+ * </div>
544
+ */
545
+ this.name("same").grouped().using(this.aggregate(this.passThrough, this.isSame)).message("These values must all match.");
546
+
547
+ /**
548
+ * @page Named Guards
549
+ * @section string
550
+ * @since 1.0.0
551
+ *
552
+ * <p>
553
+ * Validate the length of the string provided. This requires an object parameter with
554
+ * <code>min</code> and/or <code>max</code>. Min will restrict the minimum length, while
555
+ * max restricts the maximum length.
556
+ * </p>
557
+ *
558
+ * <div class="example">
559
+ * <div class="display">
560
+ * <script>
561
+ * $.guard(".string1").using("string", { min: 3 });
562
+ * $.guard(".string2").using("string", { max: 7 });
563
+ * $.guard(".string3").using("string", { min: 2, max: 4 });
564
+ * </script>
565
+ *
566
+ * <p>
567
+ * <input class="string1" type="text" value="I" /><br />
568
+ * <small>A string with at lease 3 characters</small>
569
+ * </p>
570
+ *
571
+ * <p>
572
+ * <input class="string2" type="text" value="Hello World" /><br />
573
+ * <small>A string with no more than 7 characters</small>
574
+ * </p>
575
+ *
576
+ * <p>
577
+ * <input class="string3" type="text" value="Goodnight Moon" /><br />
578
+ * <small>A string with at least 2 characters and no more than 5</small>
579
+ * </p>
580
+ * </div>
581
+ * </div>
582
+ */
583
+ this.name("string").using(this.aggregate(this.isAllValid, this.isValidString)).message(this.minMaxMessage({
584
+ minAndMax: "Please enter a string with length #{0} to #{1}.",
585
+ min: "Please enter a string with length at least #{0}.",
586
+ max: "Please enter a string with length no greater than #{0}."
587
+ }));
206
588
  };
207
589
 
208
- $.Guards.prototype.version = "0.7.2";
590
+ /**
591
+ * @page Guards Type
592
+ * @section version
593
+ * @signature jQuery.guards.version
594
+ * @since 1.0.0
595
+ *
596
+ * <p>
597
+ * This version of guards.js library as a string, like <code>"1.0.0"</code>.
598
+ * </p>
599
+ */
600
+ $.Guards.prototype.version = "1.0.0";
601
+
602
+ $.Guards.prototype.parentContext = function(element) {
603
+ var $element = $(element);
604
+ var context = $element.parents("form:first");
209
605
 
210
- // Really old jQuery doesn't have isArray, so use this alias
211
- // instead.
212
- $.Guards.prototype.isArray = $.isArray;
606
+ if (context.size() == 0) {
607
+ context = $element.parents("*:last");
608
+ }
213
609
 
214
- if (!$.Guards.prototype.isArray) {
215
- var ARRAY_CONSTRUCTOR = [].constructor;
216
- var JQUERY_CONSTRUCTOR = jQuery;
610
+ return context;
611
+ };
217
612
 
218
- $.Guards.prototype.isArray = function(obj) {
219
- // Simplistic, but good enough for guards.
220
- return obj.constructor == ARRAY_CONSTRUCTOR || obj.constructor == JQUERY_CONSTRUCTOR;
613
+ /**
614
+ * @page Guards Type
615
+ * @section name
616
+ * @signature jQuery.guards.name(guardName)
617
+ * @since 1.0.0
618
+ *
619
+ * <p>
620
+ * Name a guard. This behaves the same way as
621
+ * <a href="global_functions.html#guard"><code>$.guard(selector)</code></a>, except it uses
622
+ * the parameter to define a named guard with the given name, instead of defining a new
623
+ * guard affecting the given selector. Any attributes applied to it will be passed on to
624
+ * any guards that utilize this named guard. A named guard may be utilized by passing the
625
+ * name on to the <a href="guard_type.html#using"><code>using</code></a> method.
626
+ * </p>
627
+ *
628
+ * <div class="example">
629
+ * <div class="display">
630
+ * <script>
631
+ * $.guards.name("notTest").using(function(value, element) {
632
+ * return value !== "test";
633
+ * }).message("Must not be test.");
634
+ * $.guard(".named").using("notTest");
635
+ * </script>
636
+ *
637
+ * <p>
638
+ * <input class="named" type="text" value="test" /><br />
639
+ * <small>Anything except 'test'</small>
640
+ * </p>
641
+ * </div>
642
+ * </div>
643
+ */
644
+ $.Guards.prototype.name = function(name) {
645
+ var guard = new $.Guard(null, this, true);
646
+ this.named[name] = guard;
647
+ return guard;
648
+ };
649
+
650
+ $.Guards.prototype.aggregate = function(aggregator, validator) {
651
+ var self = this;
652
+
653
+ var result = function() {
654
+ var args = $.makeArray(arguments);
655
+
656
+ return function(value) {
657
+ return aggregator.call(self, value, function(v) {
658
+ return validator.apply(self, $.merge([v], args));
659
+ });
660
+ };
221
661
  };
222
- }
662
+
663
+ result.acceptsArguments = true;
664
+ return result;
665
+ };
666
+
667
+ $.Guards.prototype.arrayMessage = function(formatting) {
668
+ var self = this;
669
+
670
+ return function(array) {
671
+ return self.format(formatting, $.map(array, function(x) { return $.trim("" + x); }).join(", "));
672
+ };
673
+ };
674
+
675
+ $.Guards.prototype.minMaxMessage = function(formatting, minMaxFormat) {
676
+ var self = this;
677
+
678
+ return function(options) {
679
+ if (self.isNullOrUndefined(options)) {
680
+ options = {};
681
+ }
682
+
683
+ if (!$.isFunction(minMaxFormat)) {
684
+ minMaxFormat = function(x) { return x; };
685
+ }
686
+
687
+ var minDefined = !self.isNullOrUndefined(options.min);
688
+ var maxDefined = !self.isNullOrUndefined(options.max);
689
+
690
+ if (minDefined && maxDefined) {
691
+ return self.format(formatting.minAndMax, minMaxFormat(options.min), minMaxFormat(options.max));
692
+ }
693
+
694
+ if (minDefined) {
695
+ return self.format(formatting.min, minMaxFormat(options.min));
696
+ }
697
+
698
+ if (maxDefined) {
699
+ return self.format(formatting.max, minMaxFormat(options.max));
700
+ }
701
+
702
+ if (formatting.invalid) {
703
+ return formatting.invalid;
704
+ }
705
+
706
+ return self.defaults.messages["undefined"];
707
+ };
708
+ };
223
709
 
224
710
  // Alias for console.log, but check that such a thing exists.
225
711
  $.Guards.prototype.log = function(message) {
@@ -242,39 +728,40 @@
242
728
  }
243
729
  };
244
730
 
731
+ // Utility method to remove live events, but works against any
732
+ // jQuery version that supports live events.
733
+ $.Guards.prototype.off = function(selector, event, callback) {
734
+ if ($.fn.off) {
735
+ $(document).off(event, selector, callback);
736
+ } else if ($.fn.undelegate) {
737
+ $(document).undelegate(selector, event, callback);
738
+ } else if ($.fn.die) {
739
+ $(selector).die(event, callback);
740
+ } else {
741
+ this.log("Could not unbind live handlers, probably because jQuery is too old.");
742
+ }
743
+ };
744
+
245
745
  // Implementation of $.enableGuards(selector);
246
746
  $.Guards.prototype.enableGuards = function(selector) {
247
- var self = this;
747
+ this.on(selector, "submit", this.defaults.submitCallback);
748
+ };
248
749
 
249
- this.on(selector, "submit", function() {
250
- return self.guard($(this));
251
- });
750
+ // Implementation of $.disableGuards(selector);
751
+ $.Guards.prototype.disableGuards = function(selector) {
752
+ this.off(selector, "submit", this.defaults.submitCallback);
252
753
  };
253
754
 
254
755
  // Implementation of $.liveGuard(selector);
255
756
  $.Guards.prototype.liveGuard = function(selector) {
256
- var self = this;
257
757
  this.enableGuards(selector);
758
+ this.on(selector, "change blur", this.defaults.liveCallback);
759
+ };
258
760
 
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
- });
761
+ // Implementation of $.disableLiveGuard(selector);
762
+ $.Guards.prototype.disableLiveGuard = function(selector) {
763
+ this.disableGuards(selector);
764
+ this.off(selector, "change blur", this.defaults.liveCallback);
278
765
  };
279
766
 
280
767
  /**
@@ -318,44 +805,55 @@
318
805
  };
319
806
 
320
807
  /**
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" } });
808
+ * @page Guards Type
809
+ * @section style
810
+ * @signature jQuery.guards.style([customStyling])
811
+ * @since 1.0.0
812
+ *
813
+ * <p>
814
+ * Insert a style element to the document head that will style guard errors and invalid fields.
815
+ * This will default to styling <code>.invalid-field</code> with a background color of
816
+ * <code>#ffff66</code> and <code>.error-message</code> with a color of <code>#ff0000</code>
817
+ * and a left margin of <code>10px</code>.
818
+ * </p>
819
+ *
820
+ * <p>
821
+ * There are 2 optional arguments for this method. The first is an optional css scope to restrict
822
+ * the styling affects with. The second is an object expected to contain a <code>field</code>
823
+ * and/or <code>message</code> property with css styles desired for that aspect of the styling.
824
+ * The <code>field</code> property will add styling for invalid fields while the <code>message</code>
825
+ * property will add styling for error messages. The properties of these objects may contain any
826
+ * key that is a valid css attribute, with an appropriate value.
827
+ * </p>
828
+ *
829
+ * <div class="example">
830
+ * <div class="display">
831
+ * <script>
832
+ * $.guards.style("#styled-fields", {
833
+ * message: {
834
+ * "color": "#ee0000",
835
+ * "font-weight": "bold"
836
+ * },
837
+ * field: {
838
+ * "background-color": "#ffe099"
839
+ * }
840
+ * });
841
+ *
842
+ * $.guard(".styled-field").using("required");
843
+ * </script>
844
+ *
845
+ * <p id="styled-fields">
846
+ * <input class="styled-field" type="text" />
847
+ * </p>
848
+ * </div>
849
+ * </div>
349
850
  */
350
851
  $.Guards.prototype.style = function() {
351
852
  $("head").append(this.styleHtml.apply(this, arguments));
352
853
  };
353
854
 
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
- */
855
+ // Retrieve the style html as a string to use for the $.guards.style() function.
856
+ // The documentation for that function applies to this as well.
359
857
  $.Guards.prototype.styleHtml = function() {
360
858
  var fieldStyle = {};
361
859
  var messageStyle = {};
@@ -371,13 +869,13 @@
371
869
  messageStyle = this.defaults.style.message;
372
870
  }
373
871
 
374
- if (arguments.length == 1) {
375
- if (typeof(arguments[0]) == "string") {
872
+ if (arguments.length === 1) {
873
+ if (typeof(arguments[0]) === "string") {
376
874
  selectorScope = arguments[0];
377
875
  } else {
378
876
  styles = arguments[0];
379
877
  }
380
- } else if (arguments.length == 2) {
878
+ } else if (arguments.length === 2) {
381
879
  selectorScope = arguments[0];
382
880
  styles = arguments[1];
383
881
  }
@@ -419,7 +917,7 @@
419
917
  * This guard test method is intended to always fail, thus it
420
918
  * returns false no matter what.
421
919
  */
422
- $.Guards.prototype.always = function(value) {
920
+ $.Guards.prototype.always = function() {
423
921
  return false;
424
922
  };
425
923
 
@@ -431,7 +929,7 @@
431
929
  */
432
930
  $.Guards.prototype.isAllowed = function(value, allowed) {
433
931
  value = $.trim(value);
434
- return $.inArray(value, $.map(allowed, function(x, i) { return $.trim("" + x); })) != -1;
932
+ return $.inArray(value, $.map(allowed, function(x) { return $.trim("" + x); })) !== -1;
435
933
  };
436
934
 
437
935
  /**
@@ -444,7 +942,7 @@
444
942
  * Example: $.guards.isAllValid(true, function(x) { return x; }); // true
445
943
  */
446
944
  $.Guards.prototype.isAllValid = function(values, fn) {
447
- if (this.isArray(values)) {
945
+ if ($.isArray(values)) {
448
946
  var result = true;
449
947
 
450
948
  $.each(values, function(i, x) {
@@ -466,11 +964,11 @@
466
964
  * values is not an array, the result of calling the given fn on
467
965
  * that value is returned directly.
468
966
  *
469
- * Example: $.guards.isAllValid([false, false, true], function(x) { return x; }); // true
470
- * Example: $.guards.isAllValid(false, function(x) { return x; }); // false
967
+ * Example: $.guards.isAnyValid([false, false, true], function(x) { return x; }); // true
968
+ * Example: $.guards.isAnyValid(false, function(x) { return x; }); // false
471
969
  */
472
970
  $.Guards.prototype.isAnyValid = function(values, fn) {
473
- if (this.isArray(values)) {
971
+ if ($.isArray(values)) {
474
972
  var result = false;
475
973
 
476
974
  $.each(values, function(i, x) {
@@ -491,7 +989,7 @@
491
989
  * or a string of just spaces.
492
990
  */
493
991
  $.Guards.prototype.isBlank = function(value) {
494
- return this.isNullOrUndefined(value) || $.trim(value) == "";
992
+ return this.isNullOrUndefined(value) || $.trim(value) === "";
495
993
  };
496
994
 
497
995
  /**
@@ -553,7 +1051,7 @@
553
1051
  var result = true;
554
1052
 
555
1053
  $.each(values, function(i, x) {
556
- if (x != value) {
1054
+ if (x !== value) {
557
1055
  result = false;
558
1056
  return false;
559
1057
  }
@@ -585,7 +1083,7 @@
585
1083
  $.Guards.prototype.isValidInt = function(value, options) {
586
1084
  value = $.trim(value);
587
1085
 
588
- if (value == "") {
1086
+ if (value === "") {
589
1087
  return true;
590
1088
  }
591
1089
 
@@ -605,7 +1103,7 @@
605
1103
  $.Guards.prototype.isValidFloat = function(value, options) {
606
1104
  value = $.trim(value);
607
1105
 
608
- if (value == "") {
1106
+ if (value === "") {
609
1107
  return true;
610
1108
  }
611
1109
 
@@ -625,7 +1123,7 @@
625
1123
  $.Guards.prototype.isValidMoneyUS = function(value, options) {
626
1124
  value = $.trim(value);
627
1125
 
628
- if (value == "") {
1126
+ if (value === "") {
629
1127
  return true;
630
1128
  }
631
1129
 
@@ -662,14 +1160,14 @@
662
1160
  */
663
1161
  $.Guards.prototype.isValidEmail = function(value, options) {
664
1162
  if (options && options.allowDisplay) {
665
- var result = /.*\<([^>]+)\>\s*$/.exec(value);
1163
+ var result = /.*<([^>]+)>\s*$/.exec(value);
666
1164
 
667
1165
  if (result) {
668
1166
  value = result[1];
669
1167
  }
670
1168
  }
671
1169
 
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);
1170
+ 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
1171
  };
674
1172
 
675
1173
  /**
@@ -677,7 +1175,7 @@
677
1175
  */
678
1176
  $.Guards.prototype.isValidPhoneUS = function(value) {
679
1177
  value = value.replace(/\s+/g, "");
680
- return value == "" || value.length > 9 &&
1178
+ return value === "" || value.length > 9 &&
681
1179
  value.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
682
1180
  };
683
1181
 
@@ -696,7 +1194,7 @@
696
1194
  * returns true no matter what. It is intended to be used to set
697
1195
  * up a guard that is triggered manually via triggerError().
698
1196
  */
699
- $.Guards.prototype.never = function(value) {
1197
+ $.Guards.prototype.never = function() {
700
1198
  return true;
701
1199
  };
702
1200
 
@@ -710,7 +1208,7 @@
710
1208
  * Example: $.guards.passThrough(true, function(x) { return x[0]; }); // true
711
1209
  */
712
1210
  $.Guards.prototype.passThrough = function(values, fn) {
713
- if (!this.isArray(values)) {
1211
+ if (!$.isArray(values)) {
714
1212
  values = [values];
715
1213
  }
716
1214
 
@@ -746,7 +1244,7 @@
746
1244
  */
747
1245
  $.Guards.prototype.guard = function(form) {
748
1246
  var fields = form.guardableFields().clearErrors();
749
- var result = this.applyGuards(function(guard) { return fields; });
1247
+ var result = this.applyGuards(function() { return fields; });
750
1248
  fields.filter(":visible:has-error").eq(0).focus();
751
1249
  return result;
752
1250
  };
@@ -777,7 +1275,7 @@
777
1275
  * will be applied if the field doesn't have an error yet.
778
1276
  */
779
1277
  $.Guards.prototype.test = function(guard, fields) {
780
- if (guard._grouped) {
1278
+ if (guard.isGrouped()) {
781
1279
  return guard.test(fields);
782
1280
  }
783
1281
 
@@ -792,114 +1290,221 @@
792
1290
  return result;
793
1291
  };
794
1292
 
795
- $.Guard = function(selector, guards) {
1293
+ $.Guard = function(selector, guards, named) {
1294
+ this._named = named;
796
1295
  this._guards = guards || $.guards;
797
1296
  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);
1297
+ this._guard = null;
1298
+
1299
+ if (!named) {
1300
+ this.using(this._guards.defaults.guard);
1301
+ }
1302
+ };
1303
+
1304
+ $.Guard.prototype.cloneGuard = function(guard, args) {
1305
+ var self = this;
1306
+ var namedGuard = this._guards.named[guard];
1307
+
1308
+ if (this._guards.isNullOrUndefined(namedGuard)) {
1309
+ throw new Error("There is no named guard '" + guard + "'");
1310
+ }
1311
+
1312
+ var copyAttribute = function(attribute) {
1313
+ if (self[attribute] !== undefined || namedGuard[attribute] === undefined) {
1314
+ return;
1315
+ }
1316
+
1317
+ self[attribute] = namedGuard[attribute];
1318
+ };
1319
+
1320
+ copyAttribute("_grouped");
1321
+ copyAttribute("_tag");
1322
+ copyAttribute("_messageClass");
1323
+ copyAttribute("_invalidClass");
1324
+ copyAttribute("_target");
1325
+ copyAttribute("_precondition");
1326
+ this._guard = namedGuard._guard;
1327
+ this.name = guard;
1328
+
1329
+ if (this._guard.acceptsArguments) {
1330
+ this._guard = this._guard.apply(this._guards, args);
1331
+ }
1332
+
1333
+ if ($.isFunction(namedGuard._message)) {
1334
+ return this.message(namedGuard._message.apply(this._guards, args));
1335
+ } else {
1336
+ return this.message(namedGuard._message);
1337
+ }
804
1338
  };
805
1339
 
806
1340
  /**
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
- * });
1341
+ * @page Guard Type
1342
+ * @section using
1343
+ * @signature guard.using(name | customFunction)
1344
+ * @since 1.0.0
1345
+ *
1346
+ * <p>
1347
+ * Guard inputs with the specified name guard, or with a custom function. If the first argument
1348
+ * provided is a string, it must correspond to a named guard. This may be one of the
1349
+ * <a href="named_guards.html"><code>default named guards</code></a>, or a guard named via
1350
+ * <a href="guards_type.html#name"><code>$.guards.name(name)</code></a>. Additional arguments
1351
+ * may be given as options to the named guard. All attributes specified by the named guard
1352
+ * will be copied over when the <code>using</code> method is invoked.
1353
+ * </p>
1354
+ *
1355
+ * <p>
1356
+ * Alternatively, a function may be provided as the custom guard function. This function is invoked
1357
+ * when guards are being tested with the value of the element guarded, and the element being
1358
+ * guarded. If the guard is grouped, it will be an array of values with the corresponding array
1359
+ * of elements.
1360
+ * </p>
1361
+ *
1362
+ * <div class="example">
1363
+ * <div class="display">
1364
+ * <script>
1365
+ * $.guard(".using1").using("required");
1366
+ * $.guard(".using2").using(function(value, element) {
1367
+ * return value !== "invalid";
1368
+ * }).message("Custom guard.");
1369
+ * $.guard(".using3").grouped().using(function(values, elements) {
1370
+ * return $.inArray("test", values) >= 0;
1371
+ * }).message("One must be 'test'");
1372
+ * </script>
1373
+ *
1374
+ * <p>
1375
+ * <input class="using1" type="text" /><br />
1376
+ * <small>Required field</small>
1377
+ * </p>
1378
+ *
1379
+ * <p>
1380
+ * <input class="using2" type="text" value="invalid" /><br />
1381
+ * <small>Field must not be 'invalid'</small>
1382
+ * </p>
1383
+ *
1384
+ * <p>
1385
+ * <input class="using3" type="text" value="Something" /><br />
1386
+ * <input class="using3" type="text" value="Somewhere" /><br />
1387
+ * <small>One must be 'test'</small>
1388
+ * </p>
1389
+ * </div>
1390
+ * </div>
830
1391
  */
831
1392
  $.Guard.prototype.using = function(guard) {
832
- if (typeof(guard) == "string") {
1393
+ if (typeof(guard) === "string") {
833
1394
  var args = [];
834
1395
 
835
1396
  if (arguments.length > 1) {
836
1397
  args = $.makeArray(arguments).slice(1);
837
1398
  }
838
1399
 
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];
1400
+ return this.cloneGuard(guard, args);
1401
+ }
847
1402
 
848
- if ($.isFunction(message)) {
849
- message = message.apply(this._guards.defaults.messages, args);
850
- }
1403
+ this._guard = guard;
1404
+ return this.message(this._guards.defaults.messages["undefined"]);
1405
+ };
851
1406
 
852
- return this.message(message);
1407
+ $.Guard.prototype.getPrecondition = function() {
1408
+ if (this._precondition === undefined) {
1409
+ return this._guards.defaults.precondition;
853
1410
  }
854
1411
 
855
- this._guard = guard;
856
- return this.message(this._guards.defaults.messages.undefined);
1412
+ return this._precondition;
857
1413
  };
858
1414
 
859
1415
  /**
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
1416
+ * @page Guard Type
1417
+ * @section precondition
1418
+ * @signature guard.precondition(preconditionFunction)
1419
+ * @since 1.0.0
1420
+ *
1421
+ * <p>
1422
+ * Specify a precondition for this guard. A parameter is required with the precondition
1423
+ * function. This function accepts the element and element value as the parameters, like
1424
+ * a custom guard function. The precondition is executed before the guard when any given
1425
+ * input is about to be guarded. If the precondition returns false explicitly, the guard
1426
+ * will not be executed and the field will be considered valid. Any other return value
1427
+ * means the precondition passed (even no return). If the guard is grouped, the parameters
1428
+ * will be the array of values and elements (like for a custom guard function).
1429
+ * </p>
1430
+ *
1431
+ * <div class="example">
1432
+ * <div class="display">
1433
+ * <script>
1434
+ * $.guard(".with-precondition").using("required").precondition(function(value, element) {
1435
+ * return $("#precondition-checkbox").is(":checked");
1436
+ * });
1437
+ * </script>
1438
+ *
1439
+ * <p>
1440
+ * <input type="checkbox" id="precondition-checkbox" checked="checked" />
1441
+ * <label for="precondition-checkbox">Guard this field</label><br />
1442
+ * <input class="with-precondition" type="text" /><br />
1443
+ * <small>Guarded with <code>required</code> if the checkbox is checked</small>
1444
+ * </p>
1445
+ * </div>
1446
+ * </div>
877
1447
  */
878
1448
  $.Guard.prototype.precondition = function(fn) {
879
1449
  this._precondition = fn;
880
1450
  return this;
881
1451
  };
882
1452
 
883
- /**
884
- * Return whether or not this guard is grouped.
885
- */
886
1453
  $.Guard.prototype.isGrouped = function() {
1454
+ if (this._grouped === undefined) {
1455
+ return this._guards.defaults.grouped;
1456
+ }
1457
+
887
1458
  return this._grouped;
888
1459
  };
889
1460
 
890
1461
  /**
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);
1462
+ * @page Guard Type
1463
+ * @section grouped
1464
+ * @signature guard.grouped([true | false])
1465
+ * @since 1.0.0
1466
+ *
1467
+ * <p>
1468
+ * Mark this guard as being grouped. A grouped guard will guard all affected elements
1469
+ * at once, instead of individually. Each guarded element with an error will still be marked
1470
+ * as an error, but only one error message will be added. Custom guard functions will receive
1471
+ * all elements and their values at once instead of individually. By default, a guard is
1472
+ * not considered grouped. Name guards, however, carry their grouped status on, so a guard
1473
+ * using <a href="named_guards.html#oneRequired"><code>oneRequired</code></a>,
1474
+ * <a href="named_guards.html#different"><code>different</code></a>, and
1475
+ * <a href="named_guards.html#same"><code>same</code></a> will be grouped by default.
1476
+ * </p>
1477
+ *
1478
+ * <p>
1479
+ * If no argument is given, the guard will be marked as grouped, otherwise the parameter is
1480
+ * exoected to be a boolean indicating whether the guard should be grouped.
1481
+ * </p>
1482
+ *
1483
+ * <div class="example">
1484
+ * <div class="display">
1485
+ * <script>
1486
+ * $.guard(".grouped1").using("oneRequired").grouped(false).message("No longer grouped.");
1487
+ * $.guard(".grouped2").grouped().using(function(values, elements) {
1488
+ * return $.inArray("test", values) >= 0;
1489
+ * }).message("One must be 'test'");
1490
+ * </script>
1491
+ *
1492
+ * <p>
1493
+ * <input class="grouped1" type="text" /><br />
1494
+ * <input class="grouped1" type="text" /><br />
1495
+ * <small>These are effectively guarded with 'required' now</small>
1496
+ * </p>
1497
+ *
1498
+ * <p>
1499
+ * <input class="grouped2" type="text" value="Something" /><br />
1500
+ * <input class="grouped2" type="text" value="Somewhere" /><br />
1501
+ * <small>One must be 'test'</small>
1502
+ * </p>
1503
+ * </div>
1504
+ * </div>
900
1505
  */
901
1506
  $.Guard.prototype.grouped = function() {
902
- if (arguments.length == 0) {
1507
+ if (arguments.length === 0) {
903
1508
  return this.grouped(true);
904
1509
  }
905
1510
 
@@ -907,36 +1512,160 @@
907
1512
  return this;
908
1513
  };
909
1514
 
1515
+ $.Guard.prototype.getTag = function() {
1516
+ if (this._tag === undefined) {
1517
+ return this._guards.defaults.tag;
1518
+ }
1519
+
1520
+ return this._tag;
1521
+ };
1522
+
910
1523
  /**
911
- * Set the type of tag to surround the error message with
912
- * (defaults to $.guards.defaults.tag, which defaults to span).
1524
+ * @page Guard Type
1525
+ * @section tag
1526
+ * @signature guard.tag(htmlTag)
1527
+ * @since 1.0.0
1528
+ *
1529
+ * <p>
1530
+ * Change the tag type that surrounds the error message. By default, a <code>span</code> tag is
1531
+ * used.
1532
+ * </p>
1533
+ *
1534
+ * <div class="example">
1535
+ * <div class="display">
1536
+ * <script>
1537
+ * $.guard(".custom-tag").using("required").tag("div");
1538
+ * </script>
913
1539
  *
914
- * Example: $.guard(".required").using("required").tag("div");
1540
+ * <p>
1541
+ * <input class="custom-tag" type="text" />
1542
+ * </p>
1543
+ * </div>
1544
+ * </div>
915
1545
  */
916
1546
  $.Guard.prototype.tag = function(tag) {
917
1547
  this._tag = tag;
918
1548
  return this.resetMessageFn();
919
1549
  };
920
1550
 
1551
+ $.Guard.prototype.getMessageClass = function() {
1552
+ if (this._messageClass === undefined) {
1553
+ return this._guards.defaults.messageClass;
1554
+ }
1555
+
1556
+ return this._messageClass;
1557
+ };
1558
+
1559
+ /**
1560
+ * @page Guard Type
1561
+ * @section messageClass
1562
+ * @signature guard.messageClass(cssClass)
1563
+ * @since 1.0.0
1564
+ *
1565
+ * <p>
1566
+ * Change what class is used for error messages added due to failed guards. By default, the
1567
+ * error element has the class <code>error-message</code>, but that class will not be userd
1568
+ * if a different one is specified with this method.
1569
+ * </p>
1570
+ *
1571
+ * <div class="example">
1572
+ * <div class="display">
1573
+ * <style>
1574
+ * .green-message { color: #00aa00; }
1575
+ * .blue-message { color: #0000aa; }
1576
+ * </style>
1577
+ *
1578
+ * <script>
1579
+ * $.guard(".custom-message-class1").using("required").messageClass("green-message");
1580
+ * $.guard(".custom-message-class2").using("required").messageClass("blue-message");
1581
+ * </script>
1582
+ *
1583
+ * <p>
1584
+ * <input class="custom-message-class1" type="text" />
1585
+ * </p>
1586
+ *
1587
+ * <p>
1588
+ * <input class="custom-message-class2" type="text" />
1589
+ * </p>
1590
+ * </div>
1591
+ * </div>
1592
+ */
921
1593
  $.Guard.prototype.messageClass = function(messageClass) {
922
1594
  this._messageClass = messageClass;
923
1595
  return this.resetMessageFn();
924
1596
  };
925
1597
 
926
1598
  /**
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.
1599
+ * @page Guard Type
1600
+ * @section message
1601
+ * @signature guard.message(errorMessage)
1602
+ * @since 1.0.0
1603
+ *
1604
+ * <p>
1605
+ * Customize the error message displayed if the guard fails. The
1606
+ * <a href="named_guards.html"><code>default named guards</code></a>
1607
+ * have messages already defined, but they may be changed with this method.
1608
+ * </p>
932
1609
  *
933
- * Example: $.guard(".required").using("required").message("Enter something!");
1610
+ * <div class="example">
1611
+ * <div class="display">
1612
+ * <script>
1613
+ * $.guard(".message").using("required").message("This error message is customized.");
1614
+ * </script>
1615
+ *
1616
+ * <p>
1617
+ * <input class="message" type="text" />
1618
+ * </p>
1619
+ * </div>
1620
+ * </div>
934
1621
  */
935
1622
  $.Guard.prototype.message = function(message) {
936
1623
  this._message = message;
937
1624
  return this.resetMessageFn();
938
1625
  };
939
1626
 
1627
+ $.Guard.prototype.getInvalidClass = function() {
1628
+ if (this._invalidClass === undefined) {
1629
+ return this._guards.defaults.invalidClass;
1630
+ }
1631
+
1632
+ return this._invalidClass;
1633
+ };
1634
+
1635
+ /**
1636
+ * @page Guard Type
1637
+ * @section invalidClass
1638
+ * @signature guard.invalidClass(cssClass)
1639
+ * @since 1.0.0
1640
+ *
1641
+ * <p>
1642
+ * Change what class is added to invalid fields. By default, the invalid class added is
1643
+ * <code>invalid</code>, but that class will not be added if a different one is specified
1644
+ * with this method.
1645
+ * </p>
1646
+ *
1647
+ * <div class="example">
1648
+ * <div class="display">
1649
+ * <style>
1650
+ * .green-invalid { background-color: #66ff66; }
1651
+ * .blue-invalid { background-color: #6666ff; }
1652
+ * </style>
1653
+ *
1654
+ * <script>
1655
+ * $.guard(".custom-invalid1").using("required").invalidClass("green-invalid");
1656
+ * $.guard(".custom-invalid2").using("required").invalidClass("blue-invalid");
1657
+ * </script>
1658
+ *
1659
+ * <p>
1660
+ * <input class="custom-invalid1" type="text" />
1661
+ * </p>
1662
+ *
1663
+ * <p>
1664
+ * <input class="custom-invalid2" type="text" />
1665
+ * </p>
1666
+ * </div>
1667
+ * </div>
1668
+ */
940
1669
  $.Guard.prototype.invalidClass = function(invalidClass) {
941
1670
  this._invalidClass = invalidClass;
942
1671
  return this;
@@ -945,7 +1674,7 @@
945
1674
  $.Guard.prototype.resetMessageFn = function() {
946
1675
  var self = this;
947
1676
  return this.messageFn(function() {
948
- return $('<' + self._tag + ' class="' + self._messageClass + '"/>').html(self._message);
1677
+ return $('<' + self.getTag() + ' class="' + self.getMessageClass() + '"/>').html(self._message);
949
1678
  });
950
1679
  };
951
1680
 
@@ -955,78 +1684,119 @@
955
1684
  };
956
1685
 
957
1686
  $.Guard.prototype.errorElement = function() {
958
- return this._messageFn();
1687
+ var element = this._messageFn();
1688
+ element[0].isGuardError = true;
1689
+ return element;
959
1690
  };
960
1691
 
961
1692
  $.Guard.prototype.attachError = function(elements, errorElement) {
962
- if (this._target && $.isFunction(this._target)) {
963
- var result = this._target.call(elements, errorElement);
1693
+ var target = this.getTarget();
1694
+
1695
+ if (target && $.isFunction(target)) {
1696
+ var result = target.call(elements, errorElement);
964
1697
 
965
1698
  if (result !== false) {
966
1699
  errorElement.appendTo($(result).eq(0));
967
1700
  }
968
- } else if (this._target) {
969
- errorElement.appendTo($(this._target).eq(0));
1701
+ } else if (target) {
1702
+ errorElement.appendTo($(target).eq(0));
970
1703
  } else {
971
1704
  throw new Error("The target must be a function or selector!");
972
1705
  }
973
1706
  };
974
1707
 
1708
+ $.Guard.prototype.getTarget = function() {
1709
+ if (this._target === undefined) {
1710
+ return this._guards.defaults.target;
1711
+ }
1712
+
1713
+ return this._target;
1714
+ };
1715
+
975
1716
  /**
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
- * });
1717
+ * @page Guard Type
1718
+ * @section target
1719
+ * @signature guard.target(selector | targetingFunction)
1720
+ * @since 1.0.0
1721
+ *
1722
+ * <p>
1723
+ * Specify where the error will be placed in the DOM when this guard fails. The argument can be
1724
+ * a jQuery selector, element, set of elements, jQuery selected set of elements, or a function.
1725
+ * If the argument is anything except a function, it will be passed to the jQuery function and
1726
+ * the first element will be retrieved and used as the place to append the error message.
1727
+ * If it is a function, the function may either insert the error message itself, or return the
1728
+ * location to place the error message.
1729
+ * </p>
1730
+ *
1731
+ * <p>
1732
+ * When provided a function, the function will be called when an error has happened. The function's
1733
+ * <code>this</code> reference will be set to the error element (or set of elements in the case of
1734
+ * a grouped guard) that had the error. The argument will be the error message element that will be
1735
+ * appended. When <code>false</code> is returned, the function is expected to have inserted the
1736
+ * provided error message in the DOM. Otherwise, the return value is expected to be a jQuery
1737
+ * selector, element, set of elements or jQuery selected set of elements of which the first will
1738
+ * have the error element appended to it.
1739
+ * </p>
1740
+ *
1741
+ * <p>
1742
+ * The default behavior is to append the error message after the last error element that is guarded.
1743
+ * If the last element is a radio button or checkbox, it will be appended after the first sibling
1744
+ * of the radio button or checkbox, which is expected to be the label for the radio button or
1745
+ * checkbox. If there is already a guard error message there, it will be appended after the last
1746
+ * guard error message (so guard messages show up in the proper order as they are specified).
1747
+ * </p>
1748
+ *
1749
+ * <div class="example">
1750
+ * <div class="display">
1751
+ * <script>
1752
+ * $.guard(".custom-target1").using("required").target("#custom-target-1-error");
1753
+ * $.guard(".custom-target2").using("required").target(function(errorMessage) {
1754
+ * return $(this).nextAll(".custom-target-error:first");
1755
+ * });
1756
+ * $.guard(".custom-target3").using("required").target(function(errorMessage) {
1757
+ * $(this).nextAll(".custom-target-error:first").append(errorMessage);
1758
+ * return false;
1759
+ * });
1760
+ * </script>
1761
+ *
1762
+ * <p>
1763
+ * <input class="custom-target1" type="text" />
1764
+ * Error message targeted with selector:
1765
+ * <span id="custom-target-1-error"></span>
1766
+ * </p>
1767
+ *
1768
+ * <p>
1769
+ * <input class="custom-target2" type="text" />
1770
+ * Error message targeted with function:
1771
+ * <span class="custom-target-error"></span>
1772
+ * </p>
1773
+ *
1774
+ * <p>
1775
+ * <input class="custom-target3" type="text" />
1776
+ * Error message inserted manually:
1777
+ * <span class="custom-target-error"></span>
1778
+ * </p>
1779
+ * </div>
1780
+ * </div>
1003
1781
  */
1004
1782
  $.Guard.prototype.target = function(target) {
1005
1783
  this._target = target;
1006
1784
  return this;
1007
1785
  };
1008
1786
 
1009
- /**
1010
- * Determine if this guard applies to the given element (or
1011
- * elements).
1012
- */
1787
+ // Determine if the guard applies to given element(s)
1013
1788
  $.Guard.prototype.appliesTo = function(element) {
1014
1789
  return $(element).filter(this._selector).size() > 0;
1015
1790
  };
1016
1791
 
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
- */
1792
+ // Tests this guard against element(s). Element(s) should be field elements. Returns false
1793
+ // but doesn't apply guard if there are already errors detected. Returns true if the selector
1794
+ // defined for this guard doesn't apply to this element(s). Otherwise applies and adds an
1795
+ // error if it fails.
1026
1796
  $.Guard.prototype.test = function(element) {
1027
1797
  var $elements = $(element).filter(this._selector);
1028
1798
 
1029
- if ($elements.size() == 0) {
1799
+ if ($elements.size() === 0) {
1030
1800
  return true;
1031
1801
  }
1032
1802
 
@@ -1034,31 +1804,31 @@
1034
1804
  return false;
1035
1805
  }
1036
1806
 
1037
- var result;
1807
+ var result, elements, values;
1038
1808
 
1039
1809
  // Grouped expects a group of elements, while non-grouped
1040
1810
  // expects a single element.
1041
- if (this._grouped) {
1042
- var values = [];
1043
- var elements = [];
1811
+ if (this.isGrouped()) {
1812
+ values = [];
1813
+ elements = [];
1044
1814
 
1045
1815
  $elements.each(function() {
1046
1816
  values.push($(this).inputValue(this._guards));
1047
1817
  elements.push(this);
1048
1818
  });
1049
-
1050
- if (this._precondition && this._precondition(values, elements) === false) {
1051
- result = true;
1052
- } else {
1053
- result = this._guard(values, elements);
1054
- }
1055
1819
  } else {
1056
- var value = $elements.inputValue(this._guards);
1820
+ values = $elements.inputValue(this._guards);
1821
+ elements = element;
1822
+ }
1057
1823
 
1058
- if (this._precondition && this._precondition(value, element) === false) {
1059
- result = true;
1060
- } else {
1061
- result = this._guard(value, element);
1824
+ if (!this.testPrecondition(values, elements)) {
1825
+ result = true;
1826
+ } else {
1827
+ try {
1828
+ result = this._guard(values, elements);
1829
+ } catch(e) {
1830
+ this._guards.log("A guard threw an error: " + e);
1831
+ result = false;
1062
1832
  }
1063
1833
  }
1064
1834
 
@@ -1069,23 +1839,104 @@
1069
1839
  return result;
1070
1840
  };
1071
1841
 
1842
+ // Test the precondition, if there is one. Returns true if there is none, or if it
1843
+ // doesn't return false. Returns false if the precondition throws an exception or if
1844
+ // the precondition returns false. No return, undefined, null, 0 or anything else is
1845
+ // considered passing.
1846
+ $.Guard.prototype.testPrecondition = function(values, elements) {
1847
+ var precondition = this.getPrecondition();
1848
+
1849
+ if (!precondition) {
1850
+ return true;
1851
+ }
1852
+
1853
+ try {
1854
+ return precondition(values, elements) !== false;
1855
+ } catch(e) {
1856
+ this._guards.log("A precondition threw an error: " + e);
1857
+ return false;
1858
+ }
1859
+ };
1860
+
1072
1861
  /**
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.
1862
+ * @page Guard Type
1863
+ * @section triggerError
1864
+ * @signature guard.triggerError([selector])
1865
+ * @since 1.0.0
1866
+ *
1867
+ * <p>
1868
+ * Exlicitly trigger an error for this guard on all elements provided to this function.
1869
+ * The argument provided is wrapped as a jQuery object, so it may be a selector, jQuery
1870
+ * object, element, or array of elements (or anything valid for a jQuery object). Note
1871
+ * that the elements provided will have the guard applied, regardless of whether they
1872
+ * match the guard selector.
1873
+ * </p>
1874
+ *
1875
+ * <p>
1876
+ * This method may alternatively be invoked with no arguments. If this is done, the
1877
+ * selector used with the guard is used to select the elents to trigger the guard error.
1878
+ * </p>
1879
+ *
1880
+ * <div class="example">
1881
+ * <div class="display">
1882
+ * <script>
1883
+ * var guard1 = $.guard(".nothingToSelect").using("never").message("Error #1");
1884
+ * var guard2 = $.guard(".triggerError2").using("never").message("Error #2");
1885
+ * $(function() {
1886
+ * guard1.triggerError(".triggerError1");
1887
+ * guard2.triggerError();
1888
+ * });
1889
+ * </script>
1890
+ *
1891
+ * <p>
1892
+ * <input class="triggerError1" type="text" /><br />
1893
+ * <small>Triggered with a different selector</small>
1894
+ * </p>
1895
+ *
1896
+ * <p>
1897
+ * <input class="triggerError2" type="text" /><br />
1898
+ * <small>Triggered using the guard's selector</small>
1899
+ * </p>
1900
+ * </div>
1901
+ * </div>
1079
1902
  */
1080
- $.Guard.prototype.triggerError = function(elements) {
1081
- if (this._grouped) {
1903
+ $.Guard.prototype.triggerError = function() {
1904
+ var elements;
1905
+
1906
+ if (arguments.length === 0) {
1907
+ elements = this._selector;
1908
+ } else if (arguments.length === 1) {
1909
+ elements = arguments[0];
1910
+ } else {
1911
+ throw new Error("Expected 0 or 1 argument to triggerError, got " + arguments.length);
1912
+ }
1913
+
1914
+ if (this.isGrouped()) {
1082
1915
  $(elements).addSingleError(this);
1083
1916
  } else {
1084
1917
  $(elements).addError(this);
1085
1918
  }
1086
1919
 
1087
1920
  return this;
1088
- }
1921
+ };
1922
+
1923
+ $.Guard.prototype.sendEvent = function(name, selectedElements, forForm, errorMessageElement) {
1924
+ var event = $.Event(name);
1925
+ event.guard = this;
1926
+ event.errorElements = selectedElements.toArray();
1927
+ var target = selectedElements;
1928
+
1929
+ if (forForm) {
1930
+ target = target.parents("form");
1931
+ }
1932
+
1933
+ if (errorMessageElement) {
1934
+ event.errorMessage = $(errorMessageElement)[0];
1935
+ }
1936
+
1937
+ target.trigger(event);
1938
+ return event;
1939
+ };
1089
1940
 
1090
1941
  $.GuardError = function(guard, element, errorElement, linked) {
1091
1942
  this._guard = guard;
@@ -1111,8 +1962,8 @@
1111
1962
  this._element.errors.splice(index, 1);
1112
1963
  }
1113
1964
 
1114
- if (!$(this._element).hasErrorsWithInvalidClass(this._guard._invalidClass)) {
1115
- $(this._element).removeClass(this._guard._invalidClass);
1965
+ if (!$(this._element).hasErrorsWithInvalidClass(this._guard.getInvalidClass())) {
1966
+ $(this._element).removeClass(this._guard.getInvalidClass());
1116
1967
  }
1117
1968
 
1118
1969
  this._cleared = true;
@@ -1131,7 +1982,41 @@
1131
1982
  };
1132
1983
 
1133
1984
  /**
1134
- * Return the result of guarding the selected form.
1985
+ * @page jQuery Methods
1986
+ * @section guard
1987
+ * @signature selected.guard()
1988
+ * @since 1.0.0
1989
+ *
1990
+ * <p>
1991
+ * Clear any guard errors present on the form fields within the selected form (or other
1992
+ * containing element around form elements), and then test each element in order against
1993
+ * each guard in the order the guards were defined. If any of the fields had an error,
1994
+ * focus the first such field.
1995
+ * </p>
1996
+ *
1997
+ * <div class="example">
1998
+ * <div class="display">
1999
+ * <script>
2000
+ * $.guard(".guarded-field").using("required");
2001
+ * $(function() {
2002
+ * $("#invoke-guard").click(function() {
2003
+ * $("#guard-container").guard();
2004
+ * return false;
2005
+ * });
2006
+ * });
2007
+ * </script>
2008
+ *
2009
+ * <div id="guard-container">
2010
+ * <p>
2011
+ * <input class="guarded-field" type="text" />
2012
+ * </p>
2013
+ * </div>
2014
+ *
2015
+ * <p>
2016
+ * <input id="invoke-guard" type="button" value="Test Guards" />
2017
+ * </p>
2018
+ * </div>
2019
+ * </div>
1135
2020
  */
1136
2021
  $.fn.guard = function() {
1137
2022
  return $.guards.guard(this);
@@ -1154,17 +2039,29 @@
1154
2039
  * each selected element instead of just 1.
1155
2040
  */
1156
2041
  $.fn.addSingleError = function(guard) {
1157
- if (this.size() == 0) {
2042
+ if (this.size() === 0) {
1158
2043
  $.guards.log("Attempted to add error to nothing.");
1159
2044
  return this;
1160
2045
  }
1161
2046
 
2047
+ // Don't add the error if it is already there.
2048
+ if (this.hasError(guard)) {
2049
+ return this;
2050
+ }
2051
+
2052
+ var guardErrorPrevented = guard.sendEvent("guardError", this).isDefaultPrevented();
2053
+ var guardFormErrorPrevented = guard.sendEvent("guardFormError", this, true).isDefaultPrevented();
2054
+
2055
+ if (guardErrorPrevented || guardFormErrorPrevented) {
2056
+ return this;
2057
+ }
2058
+
1162
2059
  var element = guard.errorElement();
1163
2060
  guard.attachError(this, element);
1164
- this.addClass(guard._invalidClass);
2061
+ this.addClass(guard.getInvalidClass());
1165
2062
  var linked = [];
1166
2063
 
1167
- return this.each(function() {
2064
+ this.each(function() {
1168
2065
  if (!this.errors) {
1169
2066
  this.errors = [];
1170
2067
  }
@@ -1173,6 +2070,10 @@
1173
2070
  linked.push(error);
1174
2071
  this.errors.push(error);
1175
2072
  });
2073
+
2074
+ guard.sendEvent("afterGuardError", this, false, element);
2075
+ guard.sendEvent("afterGuardFormError", this, true, element);
2076
+ return this;
1176
2077
  };
1177
2078
 
1178
2079
  /**
@@ -1195,7 +2096,8 @@
1195
2096
  }
1196
2097
 
1197
2098
  radiosAdded[name] = true;
1198
- var radios = $("input[name='" + name + "']:radio", $this.parents("form"));
2099
+ var context = guard._guards.parentContext($this);
2100
+ var radios = $("input[name='" + name + "']:radio", context);
1199
2101
  radios.addSingleError(guard);
1200
2102
  } else {
1201
2103
  $this.addSingleError(guard);
@@ -1219,7 +2121,38 @@
1219
2121
  };
1220
2122
 
1221
2123
  /**
1222
- * Clear errors attached to the selected elements.
2124
+ * @page jQuery Methods
2125
+ * @section clearErrors
2126
+ * @signature selected.clearErrors()
2127
+ * @since 1.0.0
2128
+ *
2129
+ * <p>
2130
+ * Clear any guard errors on the selected elements.
2131
+ * </p>
2132
+ *
2133
+ * <div class="example">
2134
+ * <div class="display">
2135
+ * <script>
2136
+ * $(function() {
2137
+ * $.guard(".field-to-clear").using("required").triggerError();
2138
+ * $("#clear-errors").click(function() {
2139
+ * $(".field-to-clear").clearErrors();
2140
+ * return false;
2141
+ * });
2142
+ * });
2143
+ * </script>
2144
+ *
2145
+ * <div>
2146
+ * <p>
2147
+ * <input class="field-to-clear" type="text" />
2148
+ * </p>
2149
+ * </div>
2150
+ *
2151
+ * <p>
2152
+ * <input id="clear-errors" type="button" value="Clear Errors" />
2153
+ * </p>
2154
+ * </div>
2155
+ * </div>
1223
2156
  */
1224
2157
  $.fn.clearErrors = function() {
1225
2158
  $.each(this.errors(), function(index, error) {
@@ -1229,6 +2162,23 @@
1229
2162
  return this;
1230
2163
  };
1231
2164
 
2165
+ /**
2166
+ * Determine if the given guard already has an error in the
2167
+ * selected elements.
2168
+ */
2169
+ $.fn.hasError = function(guard) {
2170
+ var result = false;
2171
+
2172
+ $.each(this.errors(), function(i, error) {
2173
+ if (error._guard === guard) {
2174
+ result = true;
2175
+ return false;
2176
+ }
2177
+ });
2178
+
2179
+ return result;
2180
+ };
2181
+
1232
2182
  /**
1233
2183
  * Determine if any errors exist in the selected elements.
1234
2184
  */
@@ -1240,7 +2190,7 @@
1240
2190
  var result = false;
1241
2191
 
1242
2192
  $.each(this.errors(), function(i, error) {
1243
- if (error._guard._invalidClass == invalidClass) {
2193
+ if (error._guard.getInvalidClass() === invalidClass) {
1244
2194
  result = true;
1245
2195
  return false;
1246
2196
  }
@@ -1258,9 +2208,9 @@
1258
2208
  guards = guards || $.guards;
1259
2209
 
1260
2210
  if (this.is(":radio")) {
1261
- var checked = $("input[name='" + this.attr("name") + "']:radio:checked", this.parents("form"));
2211
+ var checked = $("input[name='" + this.attr("name") + "']:radio:checked", guards.parentContext(this));
1262
2212
 
1263
- if (checked.size() == 0) {
2213
+ if (checked.size() === 0) {
1264
2214
  return guards.constants.notChecked;
1265
2215
  }
1266
2216
 
@@ -1285,58 +2235,240 @@
1285
2235
  * submitted if guarding fails.
1286
2236
  */
1287
2237
  $.fn.enableGuards = function() {
1288
- return this.submit(function() {
1289
- return $(this).guard();
1290
- });
2238
+ return this.bind("submit", $.guards.defaults.submitCallback);
1291
2239
  };
1292
2240
 
1293
2241
  /**
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.
2242
+ * Disable form submit callbacks set up via the enableGuards
2243
+ * function.
2244
+ */
2245
+ $.fn.disableGuards = function() {
2246
+ return this.unbind("submit", $.guards.defaults.submitCallback);
2247
+ };
2248
+
2249
+ /**
2250
+ * @page Global Functions
2251
+ * @section enableGuards
2252
+ * @signature jQuery.enableGuards(selector)
2253
+ * @since 1.0.0
2254
+ *
2255
+ * <p>
2256
+ * Enable guards for a given selector. This will turn on live submit events to guard
2257
+ * the children of the selected forms/elements. Since these are live events, this
2258
+ * function need not be called when the elements actually exist (so they need not be in
2259
+ * a DOM onready handler).
2260
+ * </p>
2261
+ *
2262
+ * <div class="example not-auto-guarded">
2263
+ * <div class="display">
2264
+ * <script>
2265
+ * $.guard("input.guardable").using("required");
2266
+ * $.enableGuards("form.guardable");
2267
+ * </script>
2268
+ *
2269
+ * <form class="guardable">
2270
+ * <p>
2271
+ * <input class="guardable" type="text" />
2272
+ * </p>
2273
+ *
2274
+ * <p>
2275
+ * <input type="submit" />
2276
+ * </p>
2277
+ * </form>
2278
+ * </div>
2279
+ * </div>
1297
2280
  */
1298
2281
  $.enableGuards = function(selector) {
1299
2282
  $.guards.enableGuards(selector);
1300
2283
  };
1301
2284
 
1302
2285
  /**
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.
2286
+ * @page Global Functions
2287
+ * @section disableGuards
2288
+ * @signature jQuery.disableGuards(selector)
2289
+ * @since 1.0.0
2290
+ *
2291
+ * <p>
2292
+ * Disable guards that was previously enabled for the given selector.
2293
+ * </p>
2294
+ *
2295
+ * <div class="example not-auto-guarded">
2296
+ * <div class="display">
2297
+ * <script>
2298
+ * $.guard("input.guardable2").using("required");
2299
+ * $.enableGuards("form.guardable2");
2300
+ * $(function() {
2301
+ * $("#disable-guards").click(function() {
2302
+ * $.disableGuards("form.guardable2");
2303
+ * return false;
2304
+ * });
2305
+ * });
2306
+ * </script>
2307
+ *
2308
+ * <form class="guardable2">
2309
+ * <p>
2310
+ * <input class="guardable2" type="text" />
2311
+ * </p>
2312
+ *
2313
+ * <p>
2314
+ * <input type="submit" />
2315
+ * </p>
2316
+ *
2317
+ * <p>
2318
+ * <input id="disable-guards" type="button" value="Disable Guards" />
2319
+ * </p>
2320
+ * </form>
2321
+ * </div>
2322
+ * </div>
2323
+ */
2324
+ $.disableGuards = function(selector) {
2325
+ $.guards.disableGuards(selector);
2326
+ };
2327
+
2328
+ /**
2329
+ * @page Global Functions
2330
+ * @section liveGuard
2331
+ * @signature jQuery.liveGuard(selector)
2332
+ * @since 1.0.0
2333
+ *
2334
+ * <p>
2335
+ * Enable live guards for a given selector. This will turn on live submit, change and blur
2336
+ * events to guard the children of the selected forms/elements. Since these are live events,
2337
+ * this function need not be called when the elements actually exist (so they need not be in
2338
+ * a DOM onready handler).
2339
+ * </p>
2340
+ *
2341
+ * <div class="example not-auto-guarded">
2342
+ * <div class="display">
2343
+ * <script>
2344
+ * $.guard(".live-guarded").using("required");
2345
+ * $.liveGuard(".live-guard");
2346
+ * </script>
2347
+ *
2348
+ * <form class="live-guard">
2349
+ * <p>
2350
+ * <input class="live-guarded" type="text" />
2351
+ * </p>
2352
+ *
2353
+ * <p>
2354
+ * <input type="submit" />
2355
+ * </p>
2356
+ * </form>
2357
+ * </div>
2358
+ * </div>
1307
2359
  */
1308
2360
  $.liveGuard = function(selector) {
1309
2361
  $.guards.liveGuard(selector);
1310
2362
  };
1311
2363
 
2364
+ /**
2365
+ * @page Global Functions
2366
+ * @section disableLiveGuard
2367
+ * @signature jQuery.disableLiveGuard(selector)
2368
+ * @since 1.0.0
2369
+ *
2370
+ * <p>
2371
+ * Disable live guards that was previously enabled for the given selector.
2372
+ * </p>
2373
+ *
2374
+ * <div class="example not-auto-guarded">
2375
+ * <div class="display">
2376
+ * <script>
2377
+ * $.guard(".live-guarded2").using("required");
2378
+ * $.liveGuard(".live-guard2");
2379
+ * $(function() {
2380
+ * $("#disable-live-guards").click(function() {
2381
+ * $.disableLiveGuard(".live-guard2");
2382
+ * return false;
2383
+ * });
2384
+ * });
2385
+ * </script>
2386
+ *
2387
+ * <form class="live-guard2">
2388
+ * <p>
2389
+ * <input class="live-guarded2" type="text" />
2390
+ * </p>
2391
+ *
2392
+ * <p>
2393
+ * <input type="submit" />
2394
+ * </p>
2395
+ *
2396
+ * <p>
2397
+ * <input id="disable-live-guards" type="button" value="Disable Guards" />
2398
+ * </p>
2399
+ * </form>
2400
+ * </div>
2401
+ * </div>
2402
+ */
2403
+ $.disableLiveGuard = function(selector) {
2404
+ $.guards.disableLiveGuard(selector);
2405
+ };
2406
+
1312
2407
  $.extend($.expr[":"], {
2408
+ /**
2409
+ * @page jQuery Methods
2410
+ * @section :has-error
2411
+ * @signature jQuery("selector:has-error")
2412
+ * @since 1.0.0
2413
+ *
2414
+ * <p>
2415
+ * This is a jQuery selector that can be used to select elements that currently have an error.
2416
+ * </p>
2417
+ *
2418
+ * <div class="example">
2419
+ * <div class="display">
2420
+ * <script>
2421
+ * $.guard(".field-to-select").using("required");
2422
+ * $(function() {
2423
+ * $("#select-errors").click(function() {
2424
+ * var count = $(".field-to-select:has-error").size();
2425
+ * $("#selected-error-count").text("Number of errors: " + count);
2426
+ * return false;
2427
+ * });
2428
+ * });
2429
+ * </script>
2430
+ *
2431
+ * <div>
2432
+ * <p>
2433
+ * <input class="field-to-select" type="text" />
2434
+ * </p>
2435
+ *
2436
+ * <p>
2437
+ * <input class="field-to-select" type="text" />
2438
+ * </p>
2439
+ * </div>
2440
+ *
2441
+ * <p>
2442
+ * <input id="select-errors" type="button" value="Count errors" /><br />
2443
+ * <span id="selected-error-count"></span>
2444
+ * </p>
2445
+ * </div>
2446
+ * </div>
2447
+ */
1313
2448
  "has-error": function(x) {
1314
- return new Boolean(x.errors && x.errors.length > 0).valueOf();
2449
+ return !!(x.errors && x.errors.length > 0);
1315
2450
  },
1316
2451
  "guardable": function(x) {
1317
- return x.tagName.toLowerCase() == "input" || x.tagName.toLowerCase() == "textarea" || x.tagName.toLowerCase() == "select";
2452
+ return x.tagName.toLowerCase() === "input" || x.tagName.toLowerCase() === "textarea" || x.tagName.toLowerCase() === "select";
1318
2453
  }
1319
2454
  });
1320
2455
 
1321
2456
  $.guards = new $.Guards();
1322
2457
 
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
- });
2458
+ // Clear errors when the user expresses intent to fix the errors.
2459
+ var clearFn = function() { $(this).clearErrors(); };
2460
+ $.guards.on(":has-error", "change", clearFn);
2461
+ $.guards.on(":has-error:radio,:has-error:checkbox", "mouseup", clearFn);
2462
+ $.guards.on("select:has-error", "mousedown", clearFn);
2463
+
2464
+ // Make sure we don't clear it if there was no error when the
2465
+ // keydown happened, otherwise a submit on enter will have the
2466
+ // error flash and then go away on the keyup.
2467
+ $.guards.on(":has-error", "keydown", function() { this.clearable = true; });
2468
+ $.guards.on(":has-error", "keyup", function() {
2469
+ if (this.clearable) {
2470
+ this.clearable = false;
2471
+ $(this).clearErrors();
2472
+ }
1341
2473
  });
1342
2474
  })(jQuery);