PageSourceSearch

https://www.lnc.nc/sites/all/modules/clientside_validation/clientside_validation.js?tlvu41

js lnc.nc collected 2026-09-24 19:35:22 UTC 64,856 bytes, 1,692 lines download raw bytes

1/*jshint strict:true, browser:true, curly:true, eqeqeq:true, expr:true, forin:true, latedef:true, newcap:true, noarg:true, trailing: true, undef:true, unused:true */
2/*global Drupal: true, jQuery: true, XRegExp:true*/
3/**
4 * File:        clientside_validation.js
5 * Version:     7.x-1.x
6 * Description: Add clientside validation rules
7 * Author:      Attiks
8 * Language:    Javascript
9 * Project:     clientside_validation
10 * @module clientside_validation
11 */
12
13
14(/** @lends Drupal */function ($) {
15  /**
16   * Drupal.behaviors.clientsideValidation.
17   *
18   * Attach clientside validation to the page or rebind the rules in case of AJAX calls.
19   * @extends Drupal.behaviors
20   * @fires clientsideValidationInitialized
21   */
22  "use strict";
23  Drupal.behaviors.clientsideValidation = {
24    attach: function (context) {
25      if (!Drupal.myClientsideValidation) {
26        if (Drupal.settings.clientsideValidation) {
27          Drupal.myClientsideValidation = new Drupal.clientsideValidation();
28        }
29      }
30      else {
31        if (typeof(Drupal.settings.clientsideValidation.forms) === 'undefined') {
32          return;
33        }
34        var update = false;
35        $.each(Drupal.settings.clientsideValidation.forms, function (f) {
36          if ($(context).find('#' + f).length || $(context).is('#' + f)) {
37            update = true;
38          }
39        });
40        //update settings
41        if (update) {
42          Drupal.myClientsideValidation.data = Drupal.settings.clientsideValidation;
43          Drupal.myClientsideValidation.forms = Drupal.myClientsideValidation.data.forms;
44          Drupal.myClientsideValidation.groups = Drupal.myClientsideValidation.data.groups;
45          Drupal.myClientsideValidation.bindForms();
46        }
47      }
48
49      /**
50       * Let other modules know we are ready.
51       * @event clientsideValidationInitialized
52       * @name clientsideValidationInitialized
53       * @memberof Drupal.clientsideValidation
54       */
55      $.event.trigger('clientsideValidationInitialized');
56    }
57  };
58
59  /**
60   * Drupal.clientsideValidation.
61   * This module adds clientside validation for all forms and webforms using jquery.validate
62   * Don't forget to read the README
63   *
64   * @class Drupal.clientsideValidation
65   * @see https://github.com/jzaefferer/jquery-validation
66   * @fires clientsideValidationAddCustomRules
67   */
68  Drupal.clientsideValidation = function() {
69    var self = this;
70    if (typeof window.time !== 'undefined') {
71      // activate by setting clientside_validation_add_js_timing
72      self.time = window.time;
73    }
74    else {
75      self.time = {
76        start: function() {},
77        stop: function() {},
78        report: function() {}
79      };
80    }
81    self.time.start('1. clientsideValidation');
82
83    /**
84     * prefix to use
85     * @memberof Drupal.clientsideValidation
86     * @type string
87     * @readonly
88     * @private
89     */
90    this.prefix = 'clientsidevalidation-';
91
92    /**
93     * local copy of settings
94     * @memberof Drupal.clientsideValidation
95     * @type array
96     * @readonly
97     * @private
98     */
99    this.data = $.extend(true, {}, Drupal.settings.clientsideValidation);
100
101    /**
102     * local reference of all defined forms
103     * @memberof Drupal.clientsideValidation
104     * @type array
105     * @readonly
106     */
107    this.forms = this.data.forms;
108
109    /**
110     * list of all defined validators
111     * @memberof Drupal.clientsideValidation
112     * @type array
113     * @readonly
114     */
115    this.validators = {};
116
117    /**
118     * groups used for radios/checkboxes
119     * @memberof Drupal.clientsideValidation
120     * @type array
121     * @readonly
122     * @private
123     */
124    this.groups = this.data.groups;
125
126    // disable class and attribute rules defined by jquery.validate
127    $.validator.classRules = function() {
128      return {};
129    };
130    $.validator.attributeRules = function() {
131      return {};
132    };
133
134    /**
135     * add extra rules not defined in jquery.validate
136     * @see jquery.validate
137     */
138    this.addExtraRules();
139
140    /**
141     * bind all rules to all forms
142     * @see Drupal.clientsideValidation.prototype.bindForms
143     */
144    this.bindForms();
145    self.time.stop('1. clientsideValidation');
146    self.time.report();
147  };
148
149  /**
150   * findVerticalTab helper.
151   * @memberof Drupal.clientsideValidation
152   * @private
153   */
154  Drupal.clientsideValidation.prototype.findVerticalTab = function(element) {
155    element = $(element);
156
157    // Check for the vertical tabs fieldset and the verticalTab data
158    var fieldset = element.parents('fieldset.vertical-tabs-pane');
159    if ((fieldset.size() > 0) && (typeof(fieldset.data('verticalTab')) !== 'undefined')) {
160      var tab = $(fieldset.data('verticalTab').item[0]).find('a');
161      if (tab.size()) {
162        return tab;
163      }
164    }
165
166    // Return null by default
167    return null;
168  };
169
170  /**
171   * findHorizontalTab helper.
172   * @memberof Drupal.clientsideValidation
173   * @private
174   */
175  Drupal.clientsideValidation.prototype.findHorizontalTab = function(element) {
176    element = $(element);
177
178    // Check for the vertical tabs fieldset and the verticalTab data
179    var fieldset = element.parents('fieldset.horizontal-tabs-pane');
180    if ((fieldset.size() > 0) && (typeof(fieldset.data('horizontalTab')) !== 'undefined')) {
181      var tab = $(fieldset.data('horizontalTab').item[0]).find('a');
182      if (tab.size()) {
183        return tab;
184      }
185    }
186
187    // Return null by default
188    return null;
189  };
190
191  /**
192   * Bind all forms.
193   * @memberof Drupal.clientsideValidation
194   * @public
195   */
196  Drupal.clientsideValidation.prototype.bindForms = function(){
197    var self = this;
198    var groupkey;
199    if (typeof(self.forms) === 'undefined') {
200      return;
201    }
202    self.time.start('2. bindForms');
203    // unset invalid forms
204    $.each (self.forms, function (f) {
205      if ($('#' + f).length < 1) {
206        delete self.forms[f];
207      }
208    });
209    $.each (self.forms, function(f) {
210      var errorel = self.prefix + f + '-errors';
211      // Remove any existing validation stuff
212      if (self.validators[f]) {
213        // Doesn't work :: $('#' + f).rules('remove');
214        var form = $('#' + f).get(0);
215        if (typeof(form) !== 'undefined') {
216          $.removeData(form, 'validator');
217        }
218      }
219
220      if('checkboxrules' in self.forms[f]){
221        self.time.start('checkboxrules_groups');
222        groupkey = "";
223        $.each (self.forms[f].checkboxrules, function(r) {
224          groupkey = r + '_group';
225          self.groups[f][groupkey] = [];
226          $.each(this, function(){
227            $(this[2]).find('input[type=checkbox]').each(function(){
228              self.groups[f][groupkey].push($(this).attr('name'));
229            });
230          });
231        });
232        self.time.stop('checkboxrules_groups');
233      }
234
235      if('daterangerules' in self.forms[f]){
236        self.time.start('daterangerules');
237        groupkey = "";
238        $.each (self.forms[f].daterangerules, function(r) {
239          groupkey = r + '_group';
240          self.groups[f][groupkey] = [];
241          $.each(this, function(){
242            $('#' + f + ' #' + r + ' :input').not('input[type=image]').each(function(){
243              self.groups[f][groupkey].push($(this).attr('name'));
244            });
245          });
246        });
247        self.time.stop('daterangerules');
248      }
249
250      if('dateminrules' in self.forms[f]){
251        self.time.start('dateminrules');
252        groupkey = "";
253        $.each (self.forms[f].dateminrules, function(r) {
254          groupkey = r + '_group';
255          self.groups[f][groupkey] = [];
256          $.each(this, function(){
257            $('#' + f + ' #' + r + ' :input').not('input[type=image]').each(function(){
258              self.groups[f][groupkey].push($(this).attr('name'));
259            });
260          });
261        });
262        self.time.stop('dateminrules');
263      }
264
265      if('datemaxrules' in self.forms[f]){
266        self.time.start('datemaxrules');
267        groupkey = "";
268        $.each (self.forms[f].datemaxrules, function(r) {
269          groupkey = r + '_group';
270          self.groups[f][groupkey] = [];
271          $.each(this, function(){
272            $('#' + f + ' #' + r + ' :input').not('input[type=image]').each(function(){
273              self.groups[f][groupkey].push($(this).attr('name'));
274            });
275          });
276        });
277        self.time.stop('datemaxrules');
278      }
279
280
281      // Add basic settings
282      // todo: find cleaner fix
283      // ugly fix for nodes in colorbox
284      if(typeof $('#' + f).validate === 'function') {
285        var validate_options = {
286          errorClass: 'error',
287          groups: self.groups[f],
288          errorElement: self.forms[f].general.errorElement,
289          unhighlight: function(element, errorClass, validClass) {
290            var tab;
291
292            // If this is a pending element skip handling.
293            if (typeof this.pending[element.name] != 'undefined') {
294              return;
295            }
296
297            // Default behavior
298            $(element).removeClass(errorClass).addClass(validClass);
299
300            // Sort the classes out for the tabs - we only want to remove the
301            // highlight if there are no inputs with errors...
302            var fieldset = $(element).parents('fieldset.vertical-tabs-pane');
303            if (fieldset.size() && fieldset.find('.' + errorClass).not('label').size() === 0) {
304              tab = self.findVerticalTab(element);
305              if (tab) {
306                tab.removeClass(errorClass).addClass(validClass);
307              }
308            }
309
310            // Same for horizontal tabs
311            fieldset = $(element).parents('fieldset.horizontal-tabs-pane');
312            if (fieldset.size() && fieldset.find('.' + errorClass).not('label').size() === 0) {
313              tab = self.findHorizontalTab(element);
314              if (tab) {
315                tab.removeClass(errorClass).addClass(validClass);
316              }
317            }
318
319            /**
320             * Let other modules know this / which / an element is valid.
321             * @event clientsideValidationValid
322             * @name clientsideValidationValid
323             * @memberof Drupal.clientsideValidation
324             */
325            $(element).trigger('clientsideValidationValid');
326            $.event.trigger('clientsideValidationValid', [element]);
327          },
328          highlight: function(element, errorClass, validClass) {
329            // Default behavior
330            $(element).addClass(errorClass).removeClass(validClass);
331
332            // Sort the classes out for the tabs
333            var tab = self.findVerticalTab(element);
334            if (tab) {
335              tab.addClass(errorClass).removeClass(validClass);
336            }
337            tab = self.findHorizontalTab(element);
338            if (tab) {
339              tab.addClass(errorClass).removeClass(validClass);
340            }
341
342            /**
343             * Let other modules know this / which / an element is invalid.
344             * @event clientsideValidationInvalid
345             * @name clientsideValidationInvalid
346             * @memberof Drupal.clientsideValidation
347             */
348            $(element).trigger('clientsideValidationInvalid');
349            $.event.trigger('clientsideValidationInvalid', [element]);
350          },
351          invalidHandler: function(form, validator) {
352            var tab;
353            if (validator.errorList.length > 0) {
354              // Check if any of the errors are in the selected tab
355              var errors_in_selected = false;
356              for (var i = 0; i < validator.errorList.length; i++) {
357                tab = self.findVerticalTab(validator.errorList[i].element);
358                if (tab && tab.parent().hasClass('selected')) {
359                  errors_in_selected = true;
360                  break;
361                }
362              }
363
364              // Only focus the first tab with errors if the selected tab doesn't have
365              // errors itself. We shouldn't hide a tab that contains errors!
366              if (!errors_in_selected) {
367                tab = self.findVerticalTab(validator.errorList[0].element);
368                if (tab) {
369                  tab.click();
370                }
371              }
372
373              // Same for vertical tabs
374              // Check if any of the errors are in the selected tab
375              errors_in_selected = false;
376              for (i = 0; i < validator.errorList.length; i++) {
377                tab = self.findHorizontalTab(validator.errorList[i].element);
378                if (tab && tab.parent().hasClass('selected')) {
379                  errors_in_selected = true;
380                  break;
381                }
382              }
383
384              // Only focus the first tab with errors if the selected tab doesn't have
385              // errors itself. We shouldn't hide a tab that contains errors!
386              if (!errors_in_selected) {
387                tab = self.findHorizontalTab(validator.errorList[0].element);
388                if (tab) {
389                  tab.click();
390                }
391              }
392              if (self.forms[f].general.scrollTo) {
393                if (!$('html, body').hasClass('cv-scrolling')) {
394                  var x;
395                  if ($("#" + errorel).length) {
396                    $("#" + errorel).show();
397                    x = $("#" + errorel).offset().top - $("#" + errorel).height() - 100; // provides buffer in viewport
398                  }
399                  else {
400                    var first_element = $(validator.errorList[0].element);
401                    var hidden_element = first_element.is(":hidden");
402                    if (hidden_element) {
403                      var original_visibility = first_element.css('visibility');
404                      var original_position = first_element.css('position');
405                      first_element.css('visibility', 'hidden');
406                      first_element.css('position', 'absolute');
407                      first_element.show();
408                    }
409                    x = first_element.offset().top - first_element.height() - 100;
410                    if (hidden_element) {
411                      first_element.hide();
412                      first_element.css('position', original_position);
413                      first_element.css('visibility', original_visibility);
414                    }
415                  }
416                  $('html, body').addClass('cv-scrolling').animate(
417                    {scrollTop: x},
418                    {
419                      duration: self.forms[f].general.scrollSpeed,
420                      complete: function () {
421                        $('html, body').removeClass('cv-scrolling')
422                      }
423                    }
424                  );
425                  $('.wysiwyg-toggle-wrapper a').each(function() {
426                    $(this).click();
427                    $(this).click();
428                  });
429                }
430              }
431
432              /**
433               * Notify that the form contains errors.
434               * @event clientsideValidationFormHasErrors
435               * @name clientsideValidationFormHasErrors
436               * @memberof Drupal.clientsideValidation
437               */
438              $.event.trigger('clientsideValidationFormHasErrors', [form.target]);
439            }
440          }
441        };
442
443        switch (parseInt(self.forms[f].errorPlacement, 10)) {
444          case 0: // CLIENTSIDE_VALIDATION_JQUERY_SELECTOR
445            if ($(self.forms[f].errorJquerySelector).length) {
446              if (!$(self.forms[f].errorJquerySelector + ' #' + errorel).length) {
447                $('<div id="' + errorel + '" class="messages error clientside-error"><ul></ul></div>').prependTo(self.forms[f].errorJquerySelector).hide();
448              }
449            }
450            else if (!$('#' + errorel).length) {
451              $('<div id="' + errorel + '" class="messages error clientside-error"><ul></ul></div>').insertBefore('#' + f).hide();
452            }
453            validate_options.errorContainer = '#' + errorel;
454            validate_options.errorLabelContainer = '#' + errorel + ' ul';
455            validate_options.wrapper = 'li';
456            break;
457          case 1: // CLIENTSIDE_VALIDATION_TOP_OF_FORM
458            if (!$('#' + errorel).length) {
459              $('<div id="' + errorel + '" class="messages error clientside-error"><ul></ul></div>').insertBefore('#' + f).hide();
460            }
461            validate_options.errorContainer = '#' + errorel;
462            validate_options.errorLabelContainer = '#' + errorel + ' ul';
463            validate_options.wrapper = 'li';
464            break;
465          case 2: // CLIENTSIDE_VALIDATION_BEFORE_LABEL
466            validate_options.errorPlacement = function(error, element) {
467              var parents;
468              if (element.is(":radio")) {
469                parents = element.parents(".form-type-checkbox-tree");
470                if(parents.length) {
471                  error.insertBefore(parents.find("label").first());
472                }
473                else {
474                  parents = element.parents('.form-radios').prev('label');
475                  if (!parents.length) {
476                    parents = 'label[for="'+ element.attr('id') +'"]';
477                  }
478                  error.insertBefore(parents);
479                }
480              }
481              else if (element.is(":checkbox")) {
482                parents = element.parents(".form-type-checkbox-tree");
483                if(parents.length) {
484                  error.insertBefore(parents.find("label").first());
485                }
486                else {
487                  parents = element.parents('.form-radios').prev('label');
488                  if (!parents.length) {
489                    parents = 'label[for="'+ element.attr('id') +'"]';
490                  }
491                  error.insertBefore(parents);
492                }
493              }
494              else {
495                error.insertBefore('label[for="'+ element.attr('id') +'"]');
496              }
497            };
498            break;
499          case 3: // CLIENTSIDE_VALIDATION_AFTER_LABEL
500            validate_options.errorPlacement = function(error, element) {
501              var parents;
502              if (element.is(":radio")) {
503                parents = element.parents(".form-type-checkbox-tree");
504                if(parents.length) {
505                  error.insertAfter(parents.find("label").first());
506                }
507                else {
508                  parents = element.parents('.form-radios').prev('label');
509                  if (!parents.length) {
510                    parents = 'label[for="'+ element.attr('id') +'"]';
511                  }
512                  error.insertAfter(parents);
513                }
514              }
515              else if (element.is(":checkbox")) {
516                parents = element.parents(".form-type-checkbox-tree");
517                if(parents.length) {
518                  error.insertAfter(parents.find("label").first());
519                }
520                else {
521                  parents = element.parents('.form-checkboxes').prev('label');
522                  if (!parents.length) {
523                    parents = 'label[for="'+ element.attr('id') +'"]';
524                  }
525                  error.insertAfter(parents);
526                }
527              }
528              else {
529                error.insertAfter('label[for="'+ element.attr('id') +'"]');
530              }
531            };
532            break;
533          case 4: // CLIENTSIDE_VALIDATION_BEFORE_INPUT
534            validate_options.errorPlacement = function(error, element) {
535              error.insertBefore(element);
536            };
537            break;
538          case 5: // CLIENTSIDE_VALIDATION_AFTER_INPUT
539            validate_options.errorPlacement = function(error, element) {
540              var parents;
541              if (element.is(":radio")) {
542                parents = element.parents(".form-type-checkbox-tree");
543                if(parents.length) {
544                  error.insertAfter(parents);
545                }
546                else {
547                  parents = element.parents('.form-radios');
548                  if (!parents.length) {
549                    parents = element;
550                  }
551                  error.insertAfter(parents);
552                }
553              }
554              else if (element.is(":checkbox")) {
555                parents = element.parents(".form-type-checkbox-tree");
556                if(parents.length) {
557                  error.insertAfter(parents);
558                }
559                else {
560                  parents = element.parents('.form-checkboxes');
561                  if (!parents.length) {
562                    parents = element;
563                  }
564                  error.insertAfter(parents);
565                }
566              }
567              else if (element.next('div.grippie').length) {
568                error.insertAfter(element.next('div.grippie'));
569              } else {
570                error.insertAfter(element);
571              }
572            };
573            break;
574          case 6: // CLIENTSIDE_VALIDATION_TOP_OF_FIRST_FORM
575            if ($('div.messages--error').length) {
576              if ($('div.messages--error').attr('id').length) {
577                errorel = $('div.messages--error').attr('id');
578              }
579              else {
580                $('div.messages--error').attr('id', errorel);
581              }
582            }
583            else if ($('div.messages.error').length) {
584              if ($('div.messages.error').attr('id').length) {
585                errorel = $('div.messages.error').attr('id');
586              }
587              else {
588                $('div.messages.error').attr('id', errorel);
589              }
590            }
591            else if (!$('#' + errorel).length) {
592              $('<div id="' + errorel + '" class="messages error clientside-error"><ul></ul></div>').insertBefore('#' + f).hide();
593            }
594            validate_options.errorContainer = '#' + errorel;
595            validate_options.errorLabelContainer = '#' + errorel + ' ul';
596            validate_options.wrapper = 'li';
597            break;
598          case 7: // CLIENTSIDE_VALIDATION_CUSTOM_ERROR_FUNCTION
599            validate_options.errorPlacement = function (error, element) {
600              var func = self.forms[f].customErrorFunction;
601              Drupal.myClientsideValidation[func](error, element);
602            };
603            break;
604        }
605
606        if (!self.forms[f].includeHidden) {
607          validate_options.ignore = ':input:hidden';
608        }
609        else {
610          validate_options.ignore = '';
611        }
612        if(self.forms[f].general.validateTabs) {
613          if($('.vertical-tabs-pane input').length) {
614            validate_options.ignore += ' :not(.vertical-tabs-pane :input, .horizontal-tabs-pane :input)';
615          }
616        }
617        else {
618          validate_options.ignore += (validate_options.ignore === '') ? '.horizontal-tab-hidden :input' : ', .horizontal-tab-hidden :input';
619        }
620        //Since we can only give boolean false to onsubmit, onfocusout and onkeyup, we need
621        //a lot of if's (boolean true can not be passed to these properties).
622        if (!Boolean(parseInt(self.forms[f].general.validateOnSubmit, 10))) {
623          validate_options.onsubmit = false;
624        }
625        if (!Boolean(parseInt(self.forms[f].general.validateOnBlur, 10))) {
626          validate_options.onfocusout = false;
627        }
628        if (Boolean(parseInt(self.forms[f].general.validateOnBlurAlways, 10))) {
629          validate_options.onfocusout = function(element) {
630            if ( !this.checkable(element) ) {
631              this.element(element);
632            }
633          };
634        }
635        if (!Boolean(parseInt(self.forms[f].general.validateOnKeyUp, 10))) {
636          validate_options.onkeyup = false;
637        }
638        // Only apply this setting if errorplacement is set to the top of the form
639        if (parseInt(self.forms[f].general.showMessages, 10) > 0 && parseInt(self.forms[f].errorPlacement, 10) === 1) {
640          var showMessages = parseInt(self.forms[f].general.showMessages, 10);
641          // Show only last message
642          if (showMessages === 2) {
643            validate_options.showErrors = function() {
644              var allErrors = this.errors();
645              var i;
646              this.toHide = allErrors;
647              $(':input.' + this.settings.errorClass).removeClass(this.settings.errorClass);
648              for ( i = this.errorList.length -1; this.errorList[i]; i++ ) {
649                var error = this.errorList[i];
650                this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
651                this.showLabel( error.element, error.message );
652              }
653              if( this.errorList.length ) {
654                this.toShow = this.toShow.add( this.containers );
655              }
656              if (this.settings.success) {
657                for ( i = 0; this.successList[i]; i++ ) {
658                  this.showLabel( this.successList[i] );
659                }
660              }
661              if (this.settings.unhighlight) {
662                var elements;
663                for ( i = 0, elements = this.validElements(); elements[i]; i++ ) {
664                  this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
665                }
666              }
667              this.toHide = this.toHide.not( this.toShow );
668              this.hideErrors();
669              this.addWrapper( this.toShow ).show();
670            };
671          }
672          // Show only first message
673          else if(showMessages === 1) {
674            validate_options.showErrors = function() {
675              var allErrors = this.errors();
676              var i;
677              var elements;
678              if (this.settings.unhighlight) {
679                var firstErrorElement = this.clean($(allErrors[0]).attr('for'));
680                //for attr points to name or id
681                if (typeof firstErrorElement === 'undefined') {
682                  firstErrorElement = this.clean('#' + $(allErrors[0]).attr('for'));
683                }
684                for (i = 0, elements = this.elements().not($(firstErrorElement)); elements[i]; i++) {
685                  this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
686                }
687              }
688
689              for ( i = 0; this.errorList[i] && i<1; i++ ) {
690                var error = this.errorList[i];
691                this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
692                this.showLabel( error.element, error.message );
693              }
694              if( this.errorList.length ) {
695                this.toShow = this.toShow.add( this.containers );
696              }
697              if (this.settings.success) {
698                for ( i = 0; this.successList[i]; i++ ) {
699                  this.showLabel( this.successList[i] );
700                }
701              }
702              if (this.settings.unhighlight) {
703                for ( i = 0, elements = this.validElements(); elements[i]; i++ ) {
704                  this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
705                }
706              }
707
708              this.toHide = this.toHide.not( this.toShow );
709              this.hideErrors();
710              this.addWrapper( this.toShow ).show();
711              allErrors = this.errors();
712              allErrors.splice(0,1);
713              this.toHide = allErrors;
714              this.hideErrors();
715            };
716          }
717        }
718        /**
719         * Let other modules alter the validation options for this form.
720         * @event clientsideValidationAlterOptions
721         * @name clientsideValidationAlterOptions
722         * @memberof Drupal.clientsideValidation
723         */
724        $.event.trigger('clientsideValidationAlterOptions', [validate_options, f]);
725        self.validators[f] = $('#' + f).validate(validate_options);
726
727        // Disable HTML5 validation
728        if (!Boolean(parseInt(self.forms[f].general.disableHtml5Validation, 10))) {
729          $('#' + f).removeAttr('novalidate');
730        }
731        else {
732          $('#' + f).attr('novalidate', 'novalidate');
733        }
734        // Bind all rules
735        self.bindRules(f);
736
737      }
738    });
739  self.time.stop('2. bindForms');
740  };
741
742  /**
743   * Bind all rules.
744   * @memberof Drupal.clientsideValidation
745   */
746  Drupal.clientsideValidation.prototype.bindRules = function(formid){
747    var self = this;
748    self.time.start('3. bindRules');
749    var $form = $('#' + formid);
750    var hideErrordiv = function(){
751      //wait just one milisecond until the error div is updated
752      window.setTimeout(function(){
753        var visibles = 0;
754        // @TODO: check settings
755        $(".clientside-error ul li").each(function(){
756          if($(this).is(':visible')){
757            visibles++;
758          }
759          else {
760            $(this).remove();
761          }
762        });
763        if(visibles < 1){
764          $(".clientside-error").hide();
765        }
766      }, 1);
767    };
768    if('checkboxrules' in self.forms[formid]){
769      self.time.start('checkboxrules');
770      $.each (self.forms[formid].checkboxrules, function(r) {
771        var $checkboxes = $form.find(this.checkboxgroupminmax[2]).find('input[type="checkbox"]');
772        if ($checkboxes.length) {
773          var identifier = 'require-one-' + this.checkboxgroupminmax[2].substring(1);
774          var min = this.checkboxgroupminmax[0];
775          var message = this.messages.checkboxgroupminmax;
776          $checkboxes.addClass(identifier);
777          $checkboxes.each(function(){
778            var $checkbox = $(this);
779            var newrule = {
780              require_from_group: [min, '.' + identifier]
781            };
782            $checkbox.rules("add", newrule);
783            $checkbox.change(hideErrordiv);
784
785            if (typeof self.validators[formid].settings.messages[$checkbox.attr('name')] === 'undefined') {
786              self.validators[formid].settings.messages[$checkbox.attr('name')] = {};
787            }
788            $.extend(self.validators[formid].settings.messages[$checkbox.attr('name')], {
789              require_from_group: message
790            });
791          });
792
793          if (typeof self.validators[formid].settings.messages['.' + identifier] === 'undefined') {
794            self.validators[formid].settings.messages['.' + identifier] = {};
795          }
796          $.extend(self.validators[formid].settings.messages['.' + identifier], {
797            require_from_group: message
798          });
799        }
800      });
801      self.time.stop('checkboxrules');
802    }
803    if('daterangerules' in self.forms[formid]){
804      self.time.start('daterangerules');
805      $.each (self.forms[formid].daterangerules, function(r) {
806        $form.find('#' + r).find('input, select').not('input[type=image]').each(function(){
807          // Make sure we are working with the copy of rules object.
808          var rule = $.extend(true, {}, self.forms[formid].daterangerules[r]);
809          if (typeof self.validators[formid].settings.messages[r] === 'undefined') {
810            self.validators[formid].settings.messages[r] = {};
811          }
812          $.extend(self.validators[formid].settings.messages[r], rule.messages);
813          delete rule.messages;
814          $(this).rules("add", rule);
815          $(this).blur(hideErrordiv);
816        });
817      });
818      self.time.stop('daterangerules');
819    }
820
821    if('dateminrules' in self.forms[formid]){
822      self.time.start('dateminrules');
823      $.each (self.forms[formid].dateminrules, function(r) {
824        $form.find('#' + r).find('input, select').not('input[type=image]').each(function(){
825          // Make sure we are working with the copy of rules object.
826          var rule = $.extend(true, {}, self.forms[formid].dateminrules[r]);
827          if (typeof self.validators[formid].settings.messages[r] === 'undefined') {
828            self.validators[formid].settings.messages[r] = {};
829          }
830          $.extend(self.validators[formid].settings.messages[r], rule.messages);
831          delete rule.messages;
832          $(this).rules("add", rule);
833          $(this).blur(hideErrordiv);
834        });
835      });
836      self.time.stop('dateminrules');
837    }
838
839    if('datemaxrules' in self.forms[formid]){
840      self.time.start('datemaxrules');
841      $.each (self.forms[formid].datemaxrules, function(r) {
842        $form.find('#' + r).find('input, select').not('input[type=image]').each(function(){
843          // Make sure we are working with the copy of rules object.
844          var rule = $.extend(true, {}, self.forms[formid].datemaxrules[r]);
845          if (typeof self.validators[formid].settings.messages[r] === 'undefined') {
846            self.validators[formid].settings.messages[r] = {};
847          }
848          $.extend(self.validators[formid].settings.messages[r], rule.messages);
849          delete rule.messages;
850          $(this).rules("add", rule);
851          $(this).blur(hideErrordiv);
852        });
853      });
854      self.time.stop('datemaxrules');
855    }
856
857    if ('rules' in self.forms[formid]) {
858      self.time.start('rules');
859      // Make sure we are working with the copy of rules object.
860      var rules = $.extend(true, {}, self.forms[formid].rules);
861      // :input can be slow, see http://jsperf.com/input-vs-input/2
862      $form.find('input, textarea, select').each(function(idx, elem) {
863        var rule = rules[elem.name];
864        if (rule) {
865          var $elem = $(elem);
866          if (typeof self.validators[formid].settings.messages[elem.name] === 'undefined') {
867            self.validators[formid].settings.messages[elem.name] = {};
868          }
869          $.extend(self.validators[formid].settings.messages[elem.name], rule.messages);
870          delete rule.messages;
871          $elem.rules("add",rule);
872          $elem.change(hideErrordiv);
873        }
874      });
875      self.time.stop('rules');
876    }
877    self.time.stop('3. bindRules');
878  };
879
880  /**
881   * Add extra rules.
882   * @memberof Drupal.clientsideValidation
883  */
884  Drupal.clientsideValidation.prototype.addExtraRules = function(){
885    var self = this;
886
887    $.validator.addMethod("numberDE", function(value, element) {
888      return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(value);
889    });
890
891    $.validator.addMethod("min_comma", function(value, element, param) {
892      var real_val = Number(value.replace(',', '.'));
893      return this.optional(element) || real_val >= param;
894    });
895
896    $.validator.addMethod("max_comma", function(value, element, param) {
897      var real_val = Number(value.replace(',', '.'));
898      return this.optional(element) || real_val <= param;
899    });
900
901    $.validator.addMethod("range_comma", function(value, element, param) {
902      var real_val = Number(value.replace(',', '.'));
903      return this.optional(element) || (real_val >= param[0] && real_val <= param[1]);
904    });
905
906    // Min a and maximum b checkboxes from a group
907    $.validator.addMethod("checkboxgroupminmax", function(value, element, param) {
908      var amountChecked = $(param[2]).find('input:checked').length;
909      return (amountChecked >= param[0] && amountChecked <= param[1]);
910    }, $.format('Minimum {0}, maximum {1}'));
911
912    // Allow integers, same as digits but including a leading '-'
913    $.validator.addMethod("digits_negative", function(value, element) {
914      return this.optional(element) || /^-?\d+$/.test(value);
915    }, $.format('Please enter only digits.'));
916
917    // One of the values
918    $.validator.addMethod("oneOf", function(value, element, param) {
919      for (var p in param.values) {
920        if (param.values[p] === value && param.caseSensitive) {
921          return !param.negate;
922        }
923        else if (param.values[p].toLowerCase() === value.toLowerCase() && !param.caseSensitive) {
924          return !param.negate;
925        }
926      }
927      return param.negate;
928    }, $.format(''));
929
930    $.validator.addMethod("specificVals", function(value, element, param){
931      for (var i in value) {
932        if(param.indexOf(value[i]) === -1) {
933            return false;
934        }
935      }
936      return true;
937    });
938
939    $.validator.addMethod("blacklist", function(value, element, param) {
940      if (typeof(value) !== 'object') {
941        value = value.split(' ');
942      }
943      for (var i in value) {
944        if(param.values.indexOf(value[i]) !== -1) {
945            return param.negate;
946        }
947      }
948      return !param.negate;
949    });
950
951    // Default regular expression support
952    var ajaxPCREfn = function(value, element, param) {
953      var result = false;
954      $.ajax({
955        'url': Drupal.settings.basePath + 'clientside_validation/ajax',
956        'type': "POST",
957        'data': {
958          'value': value,
959          'param': param
960        },
961        'dataType': 'json',
962        'async': false,
963        'success': function(res){
964          result = res;
965        }
966      });
967      if (result.result === false) {
968        if (result.message.length) {
969          $.extend($.validator.messages, {
970            "regexMatchPCRE": result.message
971          });
972        }
973      }
974      return result.result;
975    };
976
977    // Regular expression support using XRegExp
978    var xregexPCREfn = function(value, element, param) {
979      if (window.XRegExp && XRegExp.version ) {
980        try {
981          var result = true;
982          for (var i = 0; i < param.expressions.length; i++) {
983            var reg = param.expressions[i];
984            var delim = reg.lastIndexOf(reg.charAt(0));
985            // Only allow supported modifiers
986            var modraw = reg.substr(delim + 1) || '';
987            var mod = '';
988            if (modraw !== '') {
989              for (var l = 0; l < 6; l++) {
990                if (modraw.indexOf('gimnsx'[l]) !== -1) {
991                  mod += 'gimnsx'[l];
992                }
993              }
994            }
995            reg = reg.substring(1, delim);
996            if (!(new XRegExp(reg, mod).test(value))) {
997              result = false;
998              if (param.messages[i].length) {
999                $.extend($.validator.messages, {
1000                  "regexMatchPCRE": param.messages[i]
1001                });
1002              }
1003            }
1004          }
1005          return result;
1006        }
1007        catch (e) {
1008          return ajaxPCREfn(value, element, param);
1009        }
1010      }
1011      else {
1012        return ajaxPCREfn(value, element, param);
1013      }
1014    };
1015
1016    // Decide which one to use
1017    if (self.data.general.usexregxp) {
1018      $.validator.addMethod("regexMatchPCRE", xregexPCREfn, $.format('The value does not match the expected format.'));
1019    }
1020    else {
1021      $.validator.addMethod("regexMatchPCRE", ajaxPCREfn, $.format('The value does not match the expected format.'));
1022    }
1023
1024    // Unique values
1025    $.validator.addMethod("notEqualTo", function(value, element, param) {
1026      var ret = true;
1027      $.each(param, function (index, selector){
1028        var $target = $(selector);
1029        $target.unbind(".validate-notEqualTo").bind("blur.validate-notEqualTo", function() {
1030          $(element).valid();
1031        });
1032        ret = ret && ($target.val() !== value);
1033      });
1034      return ret;
1035    }, $.format('Please don\'t enter the same value again.'));
1036
1037    $.validator.addMethod("regexMatch", function(value, element, param) {
1038      if (this.optional(element) && value === '') {
1039        return this.optional(element);
1040      }
1041      else {
1042        var modifier = param.regex[1];
1043        var valid_modifiers = ['i', 'g', 'm'];
1044        if ($.inArray(modifier, valid_modifiers) === -1) {
1045          modifier = '';
1046        }
1047        var regexp = new RegExp(param.regex[0], modifier);
1048        if(regexp.test(value)){
1049          return !param.negate;
1050        }
1051        return param.negate;
1052      }
1053
1054    }, $.format('The value does not match the expected format.'));
1055
1056    $.validator.addMethod("captcha", function (value, element, param) {
1057      var result = false;
1058      var sid = $(element).closest('.captcha').find('input[name=captcha_sid]').val();
1059      $.ajax({
1060        'url': Drupal.settings.basePath + 'clientside_validation/captcha',
1061        'type': "POST",
1062        'data': {
1063          'value': value,
1064          'param': [sid, param.captcha_validate, param.token, param.captcha_token]
1065        },
1066        'dataType': 'json',
1067        'async': false,
1068        'success': function(res){
1069          result = res;
1070        }
1071      });
1072      if (result.result === false) {
1073        if (typeof result.message !== 'undefined' && result.message.length) {
1074          $.extend($.validator.messages, {
1075            "captcha": result.message
1076          });
1077        }
1078      }
1079      return result.result;
1080    }, $.format('Wrong answer.'));
1081
1082    $.validator.addMethod("rangewords", function(value, element, param) {
1083      return this.optional(element) || (param[0] <= $.trim(value).split(/\s+/).length && value.split(/\s+/).length <= param[1]);
1084    }, $.format('The value must be between {0} and {1} words long'));
1085
1086    $.validator.addMethod("minwords", function(value, element, param) {
1087      return this.optional(element) || param <= $.trim(value).split(/\s+/).length;
1088    }, $.format('The value must be more than {0} words long'));
1089
1090    $.validator.addMethod("maxwords", function(value, element, param) {
1091      return this.optional(element) || $.trim(value).split(/\s+/).length <= param;
1092    }, $.format('The value must be fewer than {0} words long'));
1093
1094    $.validator.addMethod("plaintext", function(value, element, param){
1095      var result = param.negate ? (value !== strip_tags(value, param.tags)) : (value === strip_tags(value, param.tags));
1096      return this.optional(element) || result;
1097    }, $.format('The value must be plaintext'));
1098
1099    $.validator.addMethod("selectMinlength", function(value, element, param) {
1100      var result = $(element).find('option:selected').length >= param.min;
1101      if (param.negate) {
1102        result = !result;
1103      }
1104      return this.optional(element) || result;
1105    }, $.format('You must select at least {0} values'));
1106
1107    $.validator.addMethod("selectMaxlength", function(value, element, param) {
1108      var result = $(element).find('option:selected').length <= param.max;
1109      if (param.negate) {
1110        result = !result;
1111      }
1112      return this.optional(element) || result;
1113    }, $.format('You must select a maximum of {0} values'));
1114
1115    $.validator.addMethod("selectRangelength", function(value, element, param) {
1116      var result = ($(element).find('option:selected').length >= param.range[0] && $(element).find('option:selected').length <= param.range[1]);
1117      if (param.negate) {
1118        result = !result;
1119      }
1120      return this.optional(element) || result;
1121    }, $.format('You must select at between {0} and {1} values'));
1122
1123    $.validator.addMethod("datemin", function(value, element, param) {
1124      //Assume [month], [day], and [year] ??
1125      var dayelem, monthelem, yearelem, name, $form, element_name;
1126      $form = $(element).closest('form');
1127      element_name = $(element).attr('name');
1128      if (element_name.indexOf('[day]') > 0) {
1129        dayelem = $(element);
1130        name = dayelem.attr('name').replace('[day]', '');
1131        monthelem = $form.find("[name='" + name + "[month]']");
1132        yearelem = $form.find("[name='" + name + "[year]']");
1133      }
1134      else if (element_name.indexOf('[month]') > 0) {
1135        monthelem = $(element);
1136        name = monthelem.attr('name').replace('[month]', '');
1137        dayelem = $form.find("[name='" + name + "[day]']");
1138        yearelem = $form.find("[name='" + name + "[year]']");
1139      }
1140      else if ($(element).attr('name').indexOf('[year]') > 0) {
1141        yearelem = $(element);
1142        name = yearelem.attr('name').replace('[year]', '');
1143        dayelem = $form.find("[name='" + name + "[day]']");
1144        monthelem = $form.find("[name='" + name + "[month]']");
1145      }
1146
1147      if (parseInt(yearelem.val(), 10) < parseInt(param[0], 10)) {
1148        return false;
1149      }
1150      else if (parseInt(yearelem.val(), 10) === parseInt(param[0], 10)){
1151        if (parseInt(monthelem.val(), 10) < parseInt(param[1], 10)){
1152          return false;
1153        }
1154        else if (parseInt(monthelem.val(), 10) === parseInt(param[1], 10)){
1155          if(parseInt(dayelem.val(), 10) < parseInt(param[2], 10)) {
1156            return false;
1157          }
1158        }
1159      }
1160      yearelem.once('daterange', function() {
1161        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1162      }).removeClass('error');
1163      monthelem.once('daterange', function() {
1164        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1165      }).removeClass('error');
1166      dayelem.once('daterange', function() {
1167        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1168      }).removeClass('error');
1169      return true;
1170    });
1171
1172    $.validator.addMethod("datemax", function(value, element, param) {
1173      //Assume [month], [day], and [year] ??
1174      var dayelem, monthelem, yearelem, name, $form, element_name;
1175      $form = $(element).closest('form');
1176      element_name = $(element).attr('name');
1177      if (element_name.indexOf('[day]') > 0) {
1178        dayelem = $(element);
1179        name = dayelem.attr('name').replace('[day]', '');
1180        monthelem = $form.find("[name='" + name + "[month]']");
1181        yearelem = $form.find("[name='" + name + "[year]']");
1182      }
1183      else if (element_name.indexOf('[month]') > 0) {
1184        monthelem = $(element);
1185        name = monthelem.attr('name').replace('[month]', '');
1186        dayelem = $form.find("[name='" + name + "[day]']");
1187        yearelem = $form.find("[name='" + name + "[year]']");
1188      }
1189      else if (element_name.indexOf('[year]') > 0) {
1190        yearelem = $(element);
1191        name = yearelem.attr('name').replace('[year]', '');
1192        dayelem = $form.find("[name='" + name + "[day]']");
1193        monthelem = $form.find("[name='" + name + "[month]']");
1194
1195      }
1196
1197      if (parseInt(yearelem.val(), 10) > parseInt(param[0], 10)) {
1198        return false;
1199      }
1200      else if (parseInt(yearelem.val(), 10) === parseInt(param[0], 10)){
1201        if (parseInt(monthelem.val(), 10) > parseInt(param[1], 10)){
1202          return false;
1203        }
1204        else if (parseInt(monthelem.val(), 10) === parseInt(param[1], 10)){
1205          if(parseInt(dayelem.val(), 10) > parseInt(param[2], 10)) {
1206            return false;
1207          }
1208        }
1209      }
1210      yearelem.once('daterange', function() {
1211        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1212      }).removeClass('error');
1213      monthelem.once('daterange', function() {
1214        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1215      }).removeClass('error');
1216      dayelem.once('daterange', function() {
1217        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1218      }).removeClass('error');
1219      return true;
1220    });
1221
1222    $.validator.addMethod("daterange", function(value, element, param) {
1223      //Assume [month], [day], and [year] ??
1224      var dayelem, monthelem, yearelem, name, $form, element_name;
1225      $form = $(element).closest('form');
1226      element_name = $(element).attr('name');
1227      if (element_name.indexOf('[day]') > 0) {
1228        dayelem = $(element);
1229        name = dayelem.attr('name').replace('[day]', '');
1230        monthelem = $form.find("[name='" + name + "[month]']");
1231        yearelem = $form.find("[name='" + name + "[year]']");
1232      }
1233      else if (element_name.indexOf('[month]') > 0) {
1234        monthelem = $(element);
1235        name = monthelem.attr('name').replace('[month]', '');
1236        dayelem = $form.find("[name='" + name + "[day]']");
1237        yearelem = $form.find("[name='" + name + "[year]']");
1238      }
1239      else if (element_name.indexOf('[year]') > 0) {
1240        yearelem = $(element);
1241        name = yearelem.attr('name').replace('[year]', '');
1242        dayelem = $form.find("[name='" + name + "[day]']");
1243        monthelem = $form.find("[name='" + name + "[month]']");
1244      }
1245
1246      if (parseInt(yearelem.val(), 10) < parseInt(param[0][0], 10)) {
1247        return false;
1248      }
1249      else if (parseInt(yearelem.val(), 10) === parseInt(param[0][0], 10)){
1250        if (parseInt(monthelem.val(), 10) < parseInt(param[0][1], 10)){
1251          return false;
1252        }
1253        else if (parseInt(monthelem.val(), 10) === parseInt(param[0][1], 10)){
1254          if(parseInt(dayelem.val(), 10) < parseInt(param[0][2], 10)) {
1255            return false;
1256          }
1257        }
1258      }
1259
1260      if (parseInt(yearelem.val(), 10) > parseInt(param[1][0], 10)) {
1261        return false;
1262      }
1263      else if (parseInt(yearelem.val(), 10) === parseInt(param[1][0], 10)){
1264        if (parseInt(monthelem.val(), 10) > parseInt(param[1][1], 10)){
1265          return false;
1266        }
1267        else if (parseInt(monthelem.val(), 10) === parseInt(param[1][1], 10)){
1268          if(parseInt(dayelem.val(), 10) > parseInt(param[1][2], 10)) {
1269            return false;
1270          }
1271        }
1272      }
1273      yearelem.once('daterange', function() {
1274        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1275      }).removeClass('error');
1276      monthelem.once('daterange', function() {
1277        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1278      }).removeClass('error');
1279      dayelem.once('daterange', function() {
1280        $(this).change(function(){$(this).trigger('focusout').trigger('blur');});
1281      }).removeClass('error');
1282      return true;
1283    });
1284
1285    $.validator.addMethod("dateFormat", function(value, element, param) {
1286      try{
1287        var parts = value.split(param.splitter);
1288        var expectedpartscount = 0;
1289        var day = parseInt(parts[param.daypos], 10);
1290
1291        var month = parseInt(parts[param.monthpos], 10);
1292        if (isNaN(month)) {
1293          var date_parts = param.format.split(param.splitter);
1294          var full_idx = date_parts.indexOf("F");
1295          var abbr_idx = date_parts.indexOf("M");
1296          var mopos = Math.max(full_idx, abbr_idx);
1297          if (parseInt(mopos) > -1) {
1298            param.monthpos = mopos;
1299            date = new Date(parts[param.monthpos] + " 1, 2000");
1300            month = date.getMonth();
1301          }
1302          else {
1303            if (typeof Drupal.settings.clientsideValidation.general.months[parts[param.monthpos]] !== 'undefined') {
1304              month = Drupal.settings.clientsideValidation.general.months[parts[param.monthpos]];
1305            }
1306            else {
1307              month = new Date(parts[param.monthpos] + " 1, 2000");
1308              month = month.getMonth();
1309            }
1310          }
1311        }
1312        else {
1313          month--;
1314        }
1315
1316        var year = parseInt(parts[param.yearpos], 10);
1317        var date = new Date();
1318        var result = true;
1319        if (param.daypos !== false && parts[param.daypos].toString().length !== parts[param.daypos].length){
1320          result = false;
1321        }
1322        if (param.monthpos !== false && parts[param.monthpos].toString().length !== parts[param.monthpos].length){
1323          result = false;
1324        }
1325        if (param.yearpos !== false && parts[param.yearpos].toString().length !== parts[param.yearpos].length){
1326          result = false;
1327        }
1328        if (param.yearpos !== false){
1329          expectedpartscount++;
1330          date.setFullYear(year);
1331          if (year !== date.getFullYear()) {
1332            result = false;
1333          }
1334        }
1335        if (param.monthpos !== false) {
1336          expectedpartscount++;
1337          date.setMonth(month, 1);
1338          if (month !== date.getMonth()) {
1339            result = false;
1340          }
1341        }
1342        if (param.daypos !== false) {
1343          expectedpartscount++;
1344          date.setDate(day);
1345          if (day !== date.getDate()) {
1346            result = false;
1347          }
1348        }
1349        if (expectedpartscount !== parts.length) {
1350          result = false;
1351        }
1352        return this.optional(element) || result;
1353      } catch(e) {
1354        return this.optional(element) || false;
1355      }
1356    }, $.format('The date is not in a valid format'));
1357
1358    // Require one of several
1359    $.validator.addMethod("requireOneOf", function(value, element, param) {
1360      var ret = false;
1361      if (value === "") {
1362        $.each(param, function(index, name) {
1363          // @TODO: limit to current form
1364          if (!ret && $("[name='" + name + "']").val().length) {
1365            ret = true;
1366          }
1367        });
1368      }
1369      else {
1370        $(element).removeClass("error");
1371        ret = true;
1372      }
1373      $(element).blur(function () {
1374        $.each(param, function(index, name) {
1375          // @TODO: limit to current form
1376          $("[name='" + name + "']").valid();
1377        });
1378      });
1379      return ret;
1380    }, $.format('Please fill in at least one of the fields'));
1381
1382    // Support for Drupal urls.
1383    $.validator.addMethod("drupalURL", function(value, element) {
1384      var result = false;
1385      if (this.settings['name_event'] == 'onkeyup') {
1386        return true;
1387      }
1388      if ($.validator.methods.url.call(this, value, element)) {
1389        return true;
1390      }
1391      $.ajax({
1392        'url': Drupal.settings.basePath + 'clientside_validation/drupalURL',
1393        'type': "POST",
1394        'data': {
1395          'value': value
1396        },
1397        'dataType': 'json',
1398        'async': false,
1399        'success': function(res){
1400          result = res;
1401        }
1402      });
1403      return result.result;
1404    }, $.format('Please fill in a valid url'));
1405
1406    // Support for phone
1407    $.validator.addMethod("phone", function(value, element, param) {
1408      var country_code = param;
1409      var result = false;
1410      $.ajax({
1411        'url': Drupal.settings.basePath + 'clientside_validation/phone',
1412        'type': "POST",
1413        'data': {
1414          'value': value,
1415          'country_code': country_code
1416        },
1417        'dataType': 'json',
1418        'async': false,
1419        'success': function(res){
1420          result = res;
1421        }
1422      });
1423      return this.optional(element) || result.result;
1424
1425    }, $.format('Please fill in a valid phone number'));
1426
1427    // EAN code
1428    $.validator.addMethod("validEAN", function(value, element) {
1429      if (this.optional(element) && value === '') {
1430        return this.optional(element);
1431      }
1432      else {
1433        if (value.length > 13) {
1434          return false;
1435        }
1436        else if (value.length !== 13) {
1437          value = '0000000000000'.substr(0, 13 - value.length).concat(value);
1438        }
1439        if (value === '0000000000000') {
1440          return false;
1441        }
1442        if (isNaN(parseInt(value, 10)) || parseInt(value, 10) === 0) {
1443          return false;
1444        }
1445        var runningTotal = 0;
1446        for (var c = 0; c < 12; c++) {
1447          if (c % 2 === 0) {
1448            runningTotal += 3 * parseInt(value.substr(c, 1), 10);
1449          }
1450          else {
1451            runningTotal += parseInt(value.substr(c, 1), 10);
1452          }
1453        }
1454        var rem = runningTotal % 10;
1455        if (rem !== 0) {
1456          rem = 10 - rem;
1457        }
1458
1459        return rem === parseInt(value.substr(12, 1), 10);
1460
1461      }
1462    }, $.format('Not a valid EAN number.'));
1463
1464    $.validator.addMethod("require_from_group", function(value, element, options) {
1465      var $fields = $(options[1], element.form),
1466        $fieldsFirst = $fields.eq(0),
1467        validator = $fieldsFirst.data("valid_req_grp") ? $fieldsFirst.data("valid_req_grp") : $.extend({}, this),
1468        isValid = $fields.filter(function() {
1469          return validator.elementValue(this);
1470        }).length >= options[0];
1471
1472      // Store the cloned validator for future validation
1473      $fieldsFirst.data("valid_req_grp", validator);
1474
1475      // If element isn't being validated, run each require_from_group field's validation rules
1476      if (!$(element).data("being_validated")) {
1477        $fields.data("being_validated", true);
1478        $fields.each(function() {
1479          if (jQuery.data(this, 'clientside_validation_validated')) {
1480            return;
1481          } else {
1482            jQuery.data(this, 'clientside_validation_validated', true);
1483            validator.element(this);
1484          }
1485        });
1486        $fields.data("being_validated", false);
1487      }
1488      return isValid;
1489    }, $.validator.format("Please fill at least {0} of these fields."));
1490
1491
1492    // Enhances the provided "remote" method by adding our own success and
1493    // progress handling.
1494    $.validator.methods.original_remote = $.validator.methods.remote;
1495    $.validator.methods.remote = function(value, element, param) {
1496      if ( this.optional(element) ) {
1497        return "dependency-mismatch";
1498      }
1499
1500      var previous = this.previousValue(element);
1501      if (!this.settings.messages[element.name] ) {
1502        this.settings.messages[element.name] = {};
1503      }
1504      previous.originalMessage = this.settings.messages[element.name].remote;
1505      this.settings.messages[element.name].remote = previous.message;
1506
1507      param = typeof param === "string" && {url:param} || param;
1508
1509      if ( previous.old === value ) {
1510        return previous.valid;
1511      }
1512
1513      previous.old = value;
1514      var validator = this;
1515      this.startRequest(element);
1516      var data = {};
1517      data[element.name] = value;
1518
1519      $.ajax($.extend(true, {
1520        url: param,
1521        mode: "abort",
1522        port: "validate" + element.name,
1523        dataType: "json",
1524        progress: {
1525          type: 'throbber',
1526          message: Drupal.t('Please wait...')
1527        },
1528        data: data,
1529        // We provide our own success handling to ensure the pending array is
1530        // cleaned before executing showErrors(). That way handling highlight
1531        // unhighlight is easier for pending elements (avoid) flickering.
1532        // However we still need the original success handler of Drupal.ajax to
1533        // do the progress handling.
1534        success: function(response) {
1535          // Remove element from pending list as we now know what state it has.
1536          delete validator.pending[element.name];
1537          validator.settings.messages[element.name].remote = previous.originalMessage;
1538          var valid = response === true || response === "true";
1539          if ( valid ) {
1540            var submitted = validator.formSubmitted;
1541            // Don't invoke this because this will avoid that the highlight /
1542            // unhighlight handlers are called.
1543            // validator.prepareElement(element);
1544            validator.formSubmitted = submitted;
1545            validator.successList.push(element);
1546            delete validator.invalid[element.name];
1547            validator.showErrors();
1548          } else {
1549            var errors = {};
1550            var message = response || validator.defaultMessage( element, "remote" );
1551            errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
1552            validator.invalid[element.name] = true;
1553            validator.showErrors(errors);
1554          }
1555          previous.valid = valid;
1556          validator.stopRequest(element, valid);
1557        },
1558        beforeSend: function () {
1559          // Disable the element that received the change to prevent user interface
1560          // interaction while the Ajax request is in progress. ajax.ajaxing prevents
1561          // the element from triggering a new request, but does not prevent the user
1562          // from changing its value.
1563          $(element).addClass('progress-disabled').attr('disabled', true);
1564
1565          // Insert progressbar or throbber.
1566          if (this.progress.type == 'bar') {
1567            var progressBar = new Drupal.progressBar('ajax-progress-' + element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
1568            if (this.progress.message) {
1569              progressBar.setProgress(-1, this.progress.message);
1570            }
1571            if (this.progress.url) {
1572              progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
1573            }
1574            this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
1575            this.progress.object = progressBar;
1576            $(element).after(this.progress.element);
1577          }
1578          else if (this.progress.type == 'throbber') {
1579            this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
1580            if (this.progress.message) {
1581              $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>');
1582            }
1583            $(element).after(this.progress.element);
1584          }
1585        },
1586        complete: function() {
1587          // Remove the progress element.
1588          if (this.progress.element) {
1589            $(this.progress.element).remove();
1590          }
1591          if (this.progress.object) {
1592            this.progress.object.stopMonitoring();
1593          }
1594          $(element).removeClass('progress-disabled').removeAttr('disabled');
1595        }
1596      }, param));
1597      return "pending";
1598    };
1599
1600
1601    /**
1602     * Allow other modules to add more rules.
1603     * @event clientsideValidationAddCustomRules
1604     * @name clientsideValidationAddCustomRules
1605     * @memberof Drupal.clientsideValidation
1606     */
1607    $.event.trigger('clientsideValidationAddCustomRules');
1608
1609
1610    /**
1611     * strip illegal tags
1612     * @memberof Drupal.clientsideValidation
1613     * @private
1614     */
1615    function strip_tags (input, allowed) {
1616      allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
1617      var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
1618          commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
1619      return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
1620        return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
1621      });
1622    }
1623  };
1624
1625  Drupal.behaviors.ZZZClientsideValidation = {
1626    attach: function () {
1627      function changeAjax(ajax_el) {
1628        var origBeforeSubmit = Drupal.ajax[ajax_el].options.beforeSubmit;
1629        Drupal.ajax[ajax_el].options.beforeSubmit = function (form_values, element, options) {
1630          var ret = origBeforeSubmit(form_values, element, options);
1631          // If this function didn't return anything, just set the return value to true.
1632          // If it did return something, allow it to prevent submit if necessary.
1633          if (typeof ret === 'undefined') {
1634            ret = true;
1635          }
1636          var id = element.is('form') ? element.attr('id') : element.closest('form').attr('id');
1637          if (id && Drupal.myClientsideValidation.validators[id] && Drupal.myClientsideValidation.validators[id].form) {
1638            Drupal.myClientsideValidation.validators[id].onsubmit = false;
1639            ret = ret && Drupal.myClientsideValidation.validators[id].form();
1640            if (!ret) {
1641              Drupal.ajax[ajax_el].ajaxing = false;
1642            }
1643          }
1644          return ret;
1645        };
1646      }
1647      // Set validation for ctools modal forms
1648      for (var ajax_el in Drupal.ajax) {
1649        if (Drupal.ajax.hasOwnProperty(ajax_el) && typeof Drupal.ajax[ajax_el] !== 'undefined') {
1650          var $ajax_el = $(Drupal.ajax[ajax_el].element);
1651          var ajax_form = $ajax_el.is('form') ? $ajax_el.attr('id') : $ajax_el.closest('form').attr('id');
1652          var change_ajax = true;
1653          if (ajax_form != null && typeof Drupal.myClientsideValidation.forms[ajax_form] !== 'undefined') {
1654            change_ajax = Boolean(parseInt(Drupal.myClientsideValidation.forms[ajax_form].general.validateBeforeAjax, 10));
1655          }
1656          if (!$ajax_el.hasClass('cancel') && change_ajax) {
1657            changeAjax(ajax_el);
1658          }
1659        }
1660      }
1661    }
1662  };
1663
1664  /**
1665   * Integrate with the states handling.
1666   *
1667   * Prvides the condtions:
1668   *  * clientside_validation: Value is valid.
1669   *  * clientside_validated: Value was validated actually.
1670   */
1671  if (typeof Drupal.states != 'undefined') {
1672    Drupal.states.Trigger.states.clientside_validation = function (element) {
1673      element
1674        .bind('clientsideValidationValid', function () {
1675          element.trigger({type: 'state:clientside_validation', value: true});
1676          element.trigger({type: 'state:clientside_validated', value: true});
1677        })
1678        .bind('clientsideValidationInvalid', function () {
1679          element.trigger({type: 'state:clientside_validation', value: false});
1680          element.trigger({type: 'state:clientside_validated', value: true});
1681        })
1682        .bind('change keyup', function () {
1683          element.trigger({type: 'state:clientside_validated', value: false});
1684        });
1685
1686      Drupal.states.postponed.push($.proxy(function () {
1687        element.trigger({type: 'state:clientside_validation', value: true});
1688        element.trigger({type: 'state:clientside_validated', value: false});
1689      }, window));
1690    };
1691  }
1692})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.