guardsjs-rails 0.7.2 → 1.0.0

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