//===============================================================================
// * Namespace:   FormValidator
// * Description: Form framework to handle validation and wizard style page
// *              navigation with client and server validation routines.
// * Author:      Denis Zenkovich
// *              Modified by Paul Carroll
//===============================================================================
var FormValidator = new function () {

    var _Form = null;
    var _NextBtn = null;
    var _PreviousBtn = null;
    var _SubmitBtn = null;
    var _Pages = [];
    var _CurrentPage = null;
    var _CurrentPageIdx = 0;
    var _ClassNames = {
        Validating: 'validationInProcess',
        Active: 'currentField',
        Passed: 'searchSuccess',
        Failed: 'validationError',
        Counter: 'countTextSpan',
        Messages: 'messagesContainer',
        MError: 'errorMessage',
        MSuccess: 'successMessage',
        MCurrent: 'currentMessage',
        FormMessages: 'formMessagesContainer'
    };
    var _Cache = []; //cache of ajax validators


    /* PRIVATE: Calls the current page's pass routine
    ==================================================*/
    var _pass = function (pass, Data) { //evaluate the return for onpass
        if (typeof pass == 'function')
            return pass(Data);

        return true;
    };

    /* PRIVATE: Called the current page's fail routine
    ==================================================*/
    var _fail = function (fail, Data) { //evaluate the return for onfail
        if (typeof fail == 'function')
            return fail(Data);

        return false;
    };

    /* PRIVATE: 
    ==================================================*/
    var _searchCache = function (Call) {
        for (var I = 0; I < _Cache.length; I++) {
            if ($.param(_Cache[I].Call) == $.param(Call))
                return _Cache[I];
        }
        return false;
    };

    /* PRIVATE: Function to move to the next page 
    ==================================================*/
    var _nextPage = function () {

        _CurrentPage.validate();

        if (_CurrentPage.Valid === true) {
            // Shutdown the current page
            _CurrentPage.Deactivate();

            // Move to the next page
            _CurrentPage = _Pages[++_CurrentPageIdx];
            _CurrentPage.Activate();

            if (_CurrentPage.Options.onPageActivated !== undefined) {
                _CurrentPage.Options.onPageActivated();
            }
        }

        _updateButtonVisibility();

        return false;
    };

    /* PRIVATE: Function to move to the previous page
    ==================================================*/
    var _previousPage = function () {
        _CurrentPage.Deactivate();

        // Move the current page back one
        _CurrentPage = _Pages[--_CurrentPageIdx];
        _CurrentPage.Activate();

        if (_CurrentPage.Options.onPreviousPageActivated !== undefined) {
          _CurrentPage.Options.onPreviousPageActivated();
        }

        _updateButtonVisibility();
    };

    /* PRIVATE: Function to submit the current form
    ==================================================*/
    var _submitForm = function () {

        _CurrentPage.validate();
        
        if (_CurrentPage.Valid === true) {
            _Form.submit();
        }

        return false;
    };

    /* PRIVATE: Updates the pager button visibility 
    *          depending where the user currently is.
    ==================================================*/
    var _updateButtonVisibility = function () {
        // If the next or previous buttons were not provided
        // then just show the submit and bail
        if (_NextBtn === null || _PreviousBtn === null) {
            if (_SubmitBtn !== null) _SubmitBtn.show();
            return;
        }

        // Update the previous button visibility
        _CurrentPageIdx > 0 ? _PreviousBtn.show() : _PreviousBtn.hide();

        // Update the next button visibility
        _CurrentPageIdx < _Pages.length - 1 ? _NextBtn.show() : _NextBtn.hide();

        // Update the submit button visibility
        _CurrentPageIdx == _Pages.length - 1 ? _SubmitBtn.show() : _SubmitBtn.hide();
    };

    /* PUBLIC: Initialises the namespace functionality
    *         for the wizard.
    ==================================================*/
    this.Initialise = function (formId, previousBtnId, nextBtnId, submitBtnId) {

        _Form = $("#" + formId);

        if (nextBtnId !== null && nextBtnId !== undefined) {
            _NextBtn = $("#" + nextBtnId);
            _NextBtn.click(jQuery.proxy(_nextPage, this));
        }

        if (_previousPage !== null && _previousPage !== undefined) {
            _PreviousBtn = $("#" + previousBtnId);
            _PreviousBtn.click(jQuery.proxy(_previousPage, this));
        }

        _SubmitBtn = $("#" + submitBtnId);
        _SubmitBtn.click(jQuery.proxy(_submitForm, this));
    };

    /* PUBLIC: Defines the available validators (used
    *         by the assisstant class declared further on).
    ==================================================*/
    this.Validators = {
        required: function (Val, Data, pass, fail) {
            return Val ? _pass(pass, Data) : _fail(fail, Data);
        },
        minlength: function (Val, Data, pass, fail) {
            return Val.toString().length < Data.length ? _fail(fail, Data) : _pass(pass, Data);
        },
        maxlength: function (Val, Data, pass, fail) {
            return Val.toString().length > Data.length ? _fail(fail, Data) : _pass(pass, Data);
        },
        alphanum: function (Val, Data, pass, fail) {
            return /^\w+$/.test(Val) ? _pass(pass, Data) : _fail(fail, Data);
        },
        regexp: function (Val, Data, pass, fail) {
            return Data.regexp.test(Val) ? _pass(pass, Data) : _fail(fail, Data);
        },
        digits: function (Val, Data, pass, fail) {
            return /^\d+$/.test(Val) ? _pass(pass, Data) : _fail(fail, Data);
        },
        number: function (Val, Data, pass, fail) {
            return /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(Val) ? _pass(pass, Data) : _fail(fail, Data);
        },
        email: function (Val, Data, pass, fail) {
            // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
            return /^((([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(Val)
            ? _pass(pass, Data) : _fail(fail, Data);
        },
        url: function (Val, Data, pass, fail) {
            // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
            return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(Val)
            ? _pass(pass, Data) : _fail(fail, Data);
        },
        equalTo: function (Val, Data, pass, fail) {
            var ValEq = Data.value.jquery ? Data.value.val() : Data.value;
            return Val == ValEq ? _pass(pass, Data) : _fail(fail, Data);
        },
        ajax: function (Val, Data, pass, fail) {
            var onAjax = function (Json) {
                //If cached Call var is defined above
                if (Data.cache) {
                    _Cache.push({ Call: Call, Resp: Json, Created: new Date() });
                }

                var Resp = null;

                try {
                    Resp = eval(Json);
                }
                catch (E) {
                    Data.error = '<pre>' + Json + '</pre>';
                    _fail(fail, Data);
                }

                Data.Resp = Resp;

                if (Resp.valid) {
                    _pass(pass, Data);
                }
                else {
                    _fail(fail, Data);
                }
            }
            if (Data.cache) {
                var Call = { Data: Data, Value: Val };
                var Cached = _searchCache(Call);
                if (Cached) {
                    onAjax(Cached.Resp);
                    return;
                }
            }

            if (Data.multipleParam) {
              //$.post(Data.url, { value: Val.join(";") }, onAjax);
              $.ajax({
                type: 'POST',
                async: false,
                url: Data.url,
                data: { value: Val.join(";") },
                success: onAjax
              });
            } else {
              //$.post(Data.url, { value: Val }, onAjax);
              $.ajax({
                type: 'POST',
                async: false,
                url: Data.url,
                data: { value: Val },
                success: onAjax
              });
            }
        }
    };

    /* PUBLIC: Call this from your form init code to 
    *         add another page manager instance to 
    *         the manager. Automatically initialises
    *         the current page if not already set.
    ==================================================*/
    this.AddPage = function (page) {
        _Pages.push(page);

        if (_CurrentPage == null) {
            _CurrentPageIdx = 0;
            _CurrentPage = page;
            _CurrentPage.Activate();
        }

        _updateButtonVisibility();
    };

    /* PUBLIC: Get accessor to lookup a defined class
    *         name.
    ==================================================*/
    this.className = function (Type) {
        return _ClassNames[Type];
    }

    /* PUBLIC: 
    ==================================================*/
    this.setClassNames = function (Obj) {
        for (var I in _ClassNames) {
            if (Obj[I])
                _ClassNames[I] = Obj[I];
        }
    }

    /* PUBLIC: Validate - works as a queue of validator 
    *         checks, main reason for the queue - ajax 
    *         validators
    ==================================================*/
    this.validate = function (Value, Rules, onReady) {
        var Num = 0;
        var Validators = []; //array of validator names

        for (var I in Rules)
            Validators.push(I);

        if (!Validators.length)
            onReady(true);

        var pass = function (Data) {
            Num++;

            //stop validate process
            if (Num == Validators.length) {
                if (typeof onReady == 'function') onReady(true, Data);
            }
            else {
                check(Validators[Num]);
            }
        };

        var fail = function (Data) {
            if (typeof onReady == 'function')
                onReady(false, Data);
        };

        var check = function (Name) {
            if (!FormValidator.Validators[Name])
                throw "Unknown FormValidator validator named: " + Name;

            Rules[Name].Validator = Name;
            if (Rules[Name].multipleParam) {
              FormValidator.Validators[Name](Value, Rules[Name], pass, fail);
            } else {
              var validateValue;

              if (Value instanceof Array) {
                validateValue = Value[0];
              } else {
                validateValue = Value;
              }

              FormValidator.Validators[Name](validateValue, Rules[Name], pass, fail);
            }
        };

        check(Validators[0]);
    };
} ();

//===============================================================================
// * Class:       FormValidator.Page
// * Description: Provides functionality to define a page instance for the 
// *              validation manager.
// * Author:      Denis Zenkovich
// *              Modified by Paul Carroll
//===============================================================================
FormValidator.Page = function (options) {

    var self = this;
    var _SkipValidation = false;

    this.scrollToFirstInvalid = true;
    this.Options = options;
    this.Control = $('#' + options.pageId);
    this.Message = this.Control.find('.' + FormValidator.className('FormMessages'));
    this.Valid = true;

    /* PRIVATE: Get accessor for the first invalid 
    *          on the page.
    ==================================================*/
    var _findFirstInvalid = function () {

        $.each(self.Options.elements, function (i, elem) {
            if (!elem.Valid) {
                return elem;
            }
        });

        return null;
    };

    /* PUBLIC: Call this to validate the elements on the
    *         page. Check the 'Valid' public property
    *         after for the result.
    ==================================================*/
    this.validate = function () {


        if (_SkipValidation) {
            this.Valid = true;
            _SkipValidation = false;
            return true;
        }

        this.Valid = true;
        this.Message.html('Checking...');

        //next();
        for (var i = 0; i <= self.Options.elements.length; i++) {
          if (i > 0 && !self.Options.elements[i - 1].Valid) {
            self.Valid = false;
          }

          //passed through and all are valid
          if (i == self.Options.elements.length) {
            self.doneValidation();
            return;
          }

          //start next element validator
          if (self.Options.elements[i].validate) {
            if (self.Options.elements[i].IsVisible()) {
              self.Options.elements[i].validate();
            } else {
              self.Options.elements[i].Valid = true;
            }
          }
        }

        this.Valid = false;
        return false; //don't let form to submit
    };

    /* PUBLIC: 
    ==================================================*/
    this.doneValidation = function () {
        this.Message.html('');

        if (this.Valid) {
            _SkipValidation = true;
        }
        else {
            if (this.scrollToFirstInvalid) {
                var El = _findFirstInvalid();

                if (El) {
                    El.Container.get(0).scrollIntoView(true);
                }
            }

        }
    };

    /* PUBLIC: Call this to activate the page.
    *         Shows the page container and sets up 
    *         event listeners on elements
    ==================================================*/
    this.Activate = function () {
        this.Control.show();

        $.each(self.Options.elements, function (i, elem) {
            if (elem.Activate !== undefined) {
                elem.Activate();
            }
        });
    };

    /* PUBLIC: Call this to deativate the page.
    *         Hides the page container and removes the 
    *         event listeners on elements.
    ==================================================*/
    this.Deactivate = function () {
        this.Control.hide();

        $.each(self.Options.elements, function (i, elem) {
            if (elem.Activate !== undefined) {
                elem.Deactivate();
            }
        });
    };
}


//===============================================================================
// * Class:       FormValidator.Element
// * Description: Simple input element - field that you can assign validation rules on
// * Author:      Denis Zenkovich
// *              Modified by Paul Carroll
//===============================================================================
FormValidator.Element = function (Id, Data) {

    this.ID = Id;

    var self = this;
    var _Delay = 3000;

    this.Data = Data;
    this.Container = $('#' + Id);
    this.Input = this.Container.find('input').length == 0 ? this.Container.find('textarea') : this.Container.find('input');
    this.Counter = this.Container.find('.' + FormValidator.className('Counter'));
    this.Message = this.Container.find('.' + FormValidator.className('Messages'));
    this.Message.Orig = this.Message.html();
    this.Valid = false;


    /* PUBLIC: Called to apply validators to the input
    ==================================================*/
    this.validate = function (onReady) {
        var ready = function (Valid, Data) {
            self.Container.removeClass(FormValidator.className('Validating'));

            if (!Valid) {
                self.error(Data);
                self.Valid = false;
            }
            else {

                self.Valid = true;
                self.error();
                self.Container.addClass(FormValidator.className('Passed'));
                self.Message.addClass(FormValidator.className('MSuccess'));
                //after delay - remove the green background but keep the message
                window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Passed')) }, _Delay);
                //If shortUrl = premiumURL show this message
                if ((Data.Validator == "ajax") & (Data.url == "/AjaxCommon/IsValidShortcutUrl")) {
                    if (self.Data.valid) {
                        if (!Data.Resp.isPremium) {
                            self.Data.valid = "Congratulations, this shortcut URL is available.";
                            self.Message.html(self.Data.valid);
                        }
                        else {
                            self.Data.valid = "Congratulations, this shortcut URL is available, but it is a premium address which is $5/m. <br /><br /><input type='checkbox' id='acceptPremiumURL' name='acceptPremiumURL' /> <label for='firstname'>Accept this cost.</label> ";
                            self.Message.html(self.Data.valid);
                        }
                    }
                }
                else {
                    if (self.Data.valid) {
                        self.Message.html(self.Data.valid);
                    }
                }
            }

            if (typeof onReady == 'function') {
                onReady();
            }
        };
        this.Container.addClass(FormValidator.className('Validating'));
        this.Message.html('Checking...');

        if (this.Input.length < 2) {
         FormValidator.validate(this.Input.val(), this.Data.rules, ready);
       } else {
         var values = new Array();

         for (var i = 0; i < this.Input.length; i++) {
           values.push(this.Input[i].value)
         }

         FormValidator.validate(values, this.Data.rules, ready);
        }
    };

    /* PUBLIC: Removes the error indicators and shows
    *         help while the user corrects any errors.
    ==================================================*/
    this.focus = function () {
        this.error();
        this.Container.removeClass(FormValidator.className('Passed'));
        this.Container.addClass(FormValidator.className('Active'));
        this.Message.addClass(FormValidator.className('MCurrent'));
    };

    /* PUBLIC: Applies the validators and sets the appropriate
    *         error classes on the container.
    ==================================================*/
    this.blur = function () {
        this.validate();
        this.Container.removeClass(FormValidator.className('Active'));
        this.Message.removeClass(FormValidator.className('MCurrent'));
    };

    /* PUBLIC: 
    ==================================================*/
    this.error = function (Data) {
        this.Message.removeClass(FormValidator.className('MError') + " " + FormValidator.className('MSuccess') + " " + FormValidator.className('MCurrent'));

        //no data = clear error
        if (!Data) {
            this.Message.addClass(FormValidator.className('MCurrent')).html(this.Message.Orig);
            this.Container.removeClass(FormValidator.className('Failed'));
            return;
        }

        this.Message.addClass(FormValidator.className('MError'));

        if (Data.Validator == 'ajax' && Data.Resp.error) {
            this.Message.html(Data.Resp.error);
        }
        else {
            this.Message.html(Data.error);
        }

        this.Container.addClass(FormValidator.className('Failed'));
        //after delay - remove the red background but keep the message
        window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Failed')) }, _Delay);
    };

    /* PUBLIC: 
    ==================================================*/
    this.Activate = function () {
        this.Input.focus(function () { self.focus() }).blur(function () { self.blur() });
    };

    /* PUBLIC: 
    ==================================================*/
    this.Deactivate = function () {
        this.Input.focus(function () { });
    };

    /* PUBLIC: 
    ==================================================*/
    this.IsVisible = function () {
      return $("#" + this.ID).is(':visible')
    };
};

    //===============================================================================
    // * Class:       FormValidator.Element
    // * Description: Simple input element - field that you can assign validation rules on
    // * Author:      Denis Zenkovich
    // *              Modified by Paul Carroll
    //===============================================================================
    FormValidator.FileElement = function (Id, Data) {

      this.ID = Id;

      var self = this;
      var _Delay = 3000;

      this.Data = Data;
      this.Container = $('#' + Id);
      this.Input = this.Container.find('input');
      this.Counter = this.Container.find('.' + FormValidator.className('Counter'));
      this.Message = this.Container.find('.' + FormValidator.className('Messages'));
      this.Message.Orig = this.Message.html();
      this.Valid = false;


      /* PUBLIC: Called to apply validators to the input
      ==================================================*/
      this.validate = function (onReady) {
        var ready = function (Valid, Data) {
          self.Container.removeClass(FormValidator.className('Validating'));

          if (!Valid) {
            self.error(Data);
            self.Valid = false;
          }
          else {
            self.Valid = true;
            self.error();
            self.Container.addClass(FormValidator.className('Passed'));
            self.Message.addClass(FormValidator.className('MSuccess'));
            //after delay - remove the green background but keep the message
            window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Passed')) }, _Delay);

            if (self.Data.valid) {
              self.Message.html(self.Data.valid);
            }
          }
          if (typeof onReady == 'function') onReady();
        };
        this.Container.addClass(FormValidator.className('Validating'));
        this.Message.html('Checking...');
        FormValidator.validate(this.Input.val(), this.Data.rules, ready);
        //FormValidator.validate(this.Container.find('input').val(), this.Data.rules, ready);
      };

      /* PUBLIC: Removes the error indicators and shows
      *         help while the user corrects any errors.
      ==================================================*/
      this.focus = function () {
        this.error();
        this.Container.removeClass(FormValidator.className('Passed'));
        this.Container.addClass(FormValidator.className('Active'));
        this.Message.addClass(FormValidator.className('MCurrent'));
      };

      /* PUBLIC: 
      ==================================================*/
      this.change = function () {
        this.validate();
        this.Container.removeClass(FormValidator.className('Active'));
        this.Message.removeClass(FormValidator.className('MCurrent'));
      };

      /* PUBLIC: Applies the validators and sets the appropriate
      *         error classes on the container.
      ==================================================*/
      this.blur = function () {
        this.validate();
        this.Container.removeClass(FormValidator.className('Active'));
        this.Message.removeClass(FormValidator.className('MCurrent'));
      };

      /* PUBLIC: 
      ==================================================*/
      this.error = function (Data) {
        this.Message.removeClass(FormValidator.className('MError') + " " + FormValidator.className('MSuccess') + " " + FormValidator.className('MCurrent'));

        //no data = clear error
        if (!Data) {
          this.Message.addClass(FormValidator.className('MCurrent')).html(this.Message.Orig);
          this.Container.removeClass(FormValidator.className('Failed'));
          return;
        }

        this.Message.addClass(FormValidator.className('MError'));

        if (Data.Validator == 'ajax' && Data.Resp.error) {
          this.Message.html(Data.Resp.error);
        }
        else {
          this.Message.html(Data.error);
        }

        this.Container.addClass(FormValidator.className('Failed'));
        //after delay - remove the red background but keep the message
        window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Failed')) }, _Delay);
      };

      /* PUBLIC: 
      ==================================================*/
      this.Activate = function () {
        this.Input.focus(function () { self.focus() }).change(function () { self.blur() });
      };

      /* PUBLIC: 
      ==================================================*/
      this.Deactivate = function () {
        this.Input.change(function () { });
      };

      /* PUBLIC: 
      ==================================================*/
      this.IsVisible = function () {
        return $("#" + this.ID).is(':visible')
      };
    };

//===============================================================================
// * Class:       FormValidator.SelectElement
// * Description: Select drop down element - field that you can assign validation 
// *              rules on
// * Author:      Denis Zenkovich
// *              Modified by Paul Carroll
//===============================================================================
FormValidator.SelectElement = function (Id, Data) {

    this.ID = Id;

    var self = this;
    var _Delay = 3000;

    this.Data = Data;
    this.Container = $('#' + Id);
    this.Input = this.Container.find('select');
    this.Counter = this.Container.find('.' + FormValidator.className('Counter'));
    this.Message = this.Container.find('.' + FormValidator.className('Messages'));
    this.Message.Orig = this.Message.html();
    this.Valid = false;

    /* PUBLIC: 
    ==================================================*/
    this.validate = function (onReady) {
        var ready = function (Valid, Data) {
            self.Container.removeClass(FormValidator.className('Validating'));

            if (!Valid) {
                self.error(Data);
                self.Valid = false;
            }
            else {
                self.Valid = true;
                self.error();
                self.Container.addClass(FormValidator.className('Passed'));
                self.Message.addClass(FormValidator.className('MSuccess'));
                //after delay - remove the green background but keep the message
                window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Passed')) }, _Delay);

                if (self.Data.valid) {
                    self.Message.html(self.Data.valid);
                }
            }
            if (typeof onReady == 'function') onReady();
        };
        this.Container.addClass(FormValidator.className('Validating'));
        this.Message.html('Checking...');
        FormValidator.validate(this.Input.val(), this.Data.rules, ready);
    };

    /* PUBLIC: 
    ==================================================*/
    this.focus = function () {
        this.error();
        this.Container.removeClass(FormValidator.className('Passed'));
        this.Container.addClass(FormValidator.className('Active'));
        this.Message.addClass(FormValidator.className('MCurrent'));
    };

    /* PUBLIC: 
    ==================================================*/
    this.blur = function () {
        this.validate();
        this.Container.removeClass(FormValidator.className('Active'));
        this.Message.removeClass(FormValidator.className('MCurrent'));
    };

    /* PUBLIC: 
    ==================================================*/
    this.error = function (Data) {
        this.Message.removeClass(FormValidator.className('MError') + " " + FormValidator.className('MSuccess') + " " + FormValidator.className('MCurrent'));
        if (!Data) { //no data = clear error
            this.Message.addClass(FormValidator.className('MCurrent')).html(this.Message.Orig);
            this.Container.removeClass(FormValidator.className('Failed'));
            return;
        }
        this.Message.addClass(FormValidator.className('MError'));
        if (Data.Validator == 'ajax' && Data.Resp.error) this.Message.html(Data.Resp.error);
        else this.Message.html(Data.error);
        this.Container.addClass(FormValidator.className('Failed'));
        //after delay - remove the red background but keep the message
        window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Failed')) }, _Delay);
    };

    /* PUBLIC: 
    ==================================================*/
    this.Activate = function () {
        this.Input.focus(function () { self.focus() }).blur(function () { self.blur() });
    };

    /* PUBLIC: 
    ==================================================*/
    this.Deactivate = function () {
        this.Input.focus(function () { });
      };

    /* PUBLIC: 
    ==================================================*/
    this.IsVisible = function () {
      return $("#" + this.ID).is(':visible')
    };
};


//===============================================================================
// * Class:       FormValidator.CheckElement
// * Description: Checkbox/Radio input element - field that you can assign 
// *              validation rules on
// * Author:      Denis Zenkovich
//===============================================================================
FormValidator.CheckElement = function (Id, Data) {

    this.ID = Id;

    var self = this;
    var _Delay = 3000;

    this.Data = Data;
    this.Container = $('#' + Id);
    this.Input = this.Container.find('input');
    this.Counter = this.Container.find('.' + FormValidator.className('Counter'));
    this.Message = this.Container.find('.' + FormValidator.className('Messages'));
    this.Message.Orig = this.Message.html();
    this.Valid = false;

    /* PUBLIC: 
    ==================================================*/
    this.validate = function (onReady) {
        var ready = function (Valid, Data) {
            self.Container.removeClass(FormValidator.className('Validating'));

            if (!Valid) {
                self.error(Data);
                self.Valid = false;
            }
            else {
                self.Valid = true;
                self.error();
                self.Container.addClass(FormValidator.className('Passed'));
                self.Message.addClass(FormValidator.className('MSuccess'));
                //after delay - remove the green background but keep the message
                window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Passed')) }, _Delay);

                if (self.Data.valid) {
                    self.Message.html(self.Data.valid);
                }
            }

            if (typeof onReady == 'function') {
                onReady();
            }
        };

        this.Container.addClass(FormValidator.className('Validating'));
        this.Message.html('Checking...');

        FormValidator.validate(this.Input.is(':checked'), this.Data.rules, ready);
    };

    /* PUBLIC: 
    ==================================================*/
    this.error = function (Data) {
        this.Message.removeClass(FormValidator.className('MError') + " " + FormValidator.className('MSuccess') + " " + FormValidator.className('MCurrent'));

        //no data = clear error
        if (!Data) {
            this.Message.addClass(FormValidator.className('MCurrent')).html(this.Message.Orig);
            this.Container.removeClass(FormValidator.className('Failed'));
            return;
        }

        this.Message.addClass(FormValidator.className('MError'));

        if (Data.Validator == 'ajax' && Data.Resp.error) {
            this.Message.html(Data.Resp.error);
        }
        else {
            this.Message.html(Data.error);
        }

        this.Container.addClass(FormValidator.className('Failed'));
        //after delay - remove the red background but keep the message
        window.setTimeout(function () { self.Container.removeClass(FormValidator.className('Failed')) }, _Delay);
    };

    /* PUBLIC: 
    ==================================================*/
    this.IsVisible = function () {
      return $("#" + this.ID).is(':visible')
    };

};


//===============================================================================
// * Class:       FormValidator.AutoComplete
// * Description: Extended simple input element - has autocomplete drop down
// * Author:      Denis Zenkovich
//===============================================================================
FormValidator.AutoComplete = function () {
    this.superclass = FormValidator.Element(Id);
    $.extend(this, this.superclass);

    //TODO

}
