<!DOCTYPE html>
<html ng-app="rmaApp">

  <head>
    <link data-require="bootstrap-css@3.1.1" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
    <script data-require="angular.js@1.4.5" data-semver="1.4.5" src="https://code.angularjs.org/1.4.5/angular.js"></script>
    <script data-require="angular-resource@1.4.5" data-semver="1.4.5" src="https://code.angularjs.org/1.4.5/angular-resource.js"></script>
    <script src="autovalidate.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <div class="form-group">
      <sel-phone-number label="Country Code and Phone Number" name="technicalContactPhone" id="technicalContactPhone" ng-model="technicalContactPhone" required="true"></sel-phone-number>
    </div>
  </body>

</html>
(function() {
  'use strict';
  var app = angular.module('rmaApp', ['jcs-autoValidate','ngResource']);

  app.run(['$q', '$rootScope', 'validator', 'defaultErrorMessageResolver',
    function($q, $rootScope, validator, defaultErrorMessageResolver) {
      // turn off angular auto validate icons.
      validator.setValidElementStyling(false);
      defaultErrorMessageResolver.getErrorMessages().then(function(errorMessages) {
       errorMessages.phone = 'Please enter a valid phone number, including Country Calling Code and Area Code';
      });
    }
  ]);

  app.factory('validatePhoneService', validatePhoneService);

  validatePhoneService.$inject = ['$q', '$resource'];

  function validatePhoneService($q, $resource) {
    var phoneService = {
      countryCallingCode: '',
      phone: '',
      checkPhone: checkPhone,
      parsePhone: parsePhone
    };

    return phoneService;

    // Checks the phone to see if it is valid by passing it to the server where it is ran against the phone number validator from Google.
    function checkPhone(countryCallingCode, phone) {
      var deferred = $q.defer();
      if (angular.isDefined(phone) && phone.length > 0) {
        var parsedPhone = parsePhone(countryCallingCode, phone);
        console.log('checking phone via service');
        return $resource('https://www1.selinc.com/api/phone/validate/:phoneccc/:phone/').get({
          phoneccc: parsedPhone.countryCallingCode,
          phone: parsedPhone.phone
        }).$promise.then(function(data) {
          if (data.valid) {
            phoneService.countryCallingCode = data.countryCallingCode;
            phoneService.phone = data.phone;
            deferred.resolve();
            return deferred.promise;
          } else {
            console.log("data not valid", data);
            deferred.reject();
            return deferred.promise;
          }
        }, function(error) {
          console.log(error);
          deferred.reject(error);
          return deferred.promise;
        });
      } else {
        deferred.reject();
        return deferred.promise;
      }
    }

    // Parse a phone number country calling code and phone number body into an object.
    // If there is no phone number country calling code, attempt to find it in the body.
    function parsePhone(countryCallingCode, phone) {
      // Split the phone number up into an array for later use.
      // The delimiter
      var d = '',
        phoneArray = [],
        result = {
          countryCallingCode: '',
          phone: phone
        };
      // What delimiter is being used.
      if (phone.indexOf(' ') > -1) {
        d = ' ';
      }
      if (phone.indexOf('.') > -1) {
        d = '.';
      }
      if (phone.indexOf('-') > -1) {
        d = '-';
      }
      // Split the phone number into an array by the delimiter
      phoneArray = phone.split(d);

      // Try to set country calling code to the form value
      result.countryCallingCode = countryCallingCode;
      if (angular.isDefined(result.countryCallingCode) && result.countryCallingCode !== '') {
        // Remove the + from the ccc if there.
        result.countryCallingCode = result.countryCallingCode.replace('+', '');
      } else {
        // Set ccc to the first element in the array, that should be the country calling code
        result.countryCallingCode = phoneArray[0];

        if (angular.isDefined(result.countryCallingCode) && result.countryCallingCode !== '') {
          // Get rid of the + if it is there.
          result.countryCallingCode = result.countryCallingCode.replace('+', '');
          // Remove the ccc from the phone array then rejoin it using a period and set it to the element
          phoneArray.splice(0, 1);
          result.phone = phoneArray.join('.');
        }
      }

      return result;
    }
  }

  app.directive('selPhoneNumber', selPhoneNumber);

  selPhoneNumber.$inject = ['validatePhoneService'];

  function selPhoneNumber(validatePhoneService) {
    // Usage:
    //     <sel-phone-number id="SomeId" ng-model="{countryCallingCode: '', phone:''}" required="true"></sel-phone-number>
    // Creates:
    // 
    var directive = {
      scope: {
        label: "@",
        id: "@",
        required: "=?" // Are fields required.
      },
      link: link,
      restrict: 'E',
      require: 'ngModel',
      template: '<div class="form-group"><label class="control-label" for="{{id}}">{{label}}</label><div class="input-group">	<input type="text" name="country-code" class="form-control" style="width:60px" ng-model="countryCallingCode" ng-required="required" uib-tooltip="Country Calling Code.  Example: +1 for US" tooltip-trigger="focus" tooltip-placement="top" readonly onfocus="this.removeAttribute(\'readonly\');">		<span class="input-group-addon" style="width:0; border-left: 0; border-right: 0;"><b>.</b></span>		<input type="tel" name="phone" class="form-control" id="{{id}}" ng-model="phone" ng-required="required" uib-tooltip="Phone Number.  Example: 123.555.1212" tooltip-trigger="focus" tooltip-placement="top">	</div></div>'
    };
    return directive;

    function link(scope, element, attrs, ctrl) {
      // If user does not pass in required, then assume it is not required (if we don't do this, it will be undefined)
      if (scope.required === undefined) {
        scope.required = false;
      }

      scope.countryCallingCode = '';
      scope.phone = '';

      // Checks the phone values when there is a data load into the field.
      ctrl.$render = function() {
        if (ctrl.$viewValue !== undefined && ctrl.$viewValue !== null) {
          scope.countryCallingCode = ctrl.$viewValue.countryCallingCode;
          scope.phone = ctrl.$viewValue.phone;
        }
      };

      scope.$watch("phone", function() {
        console.log("checking from watch");
        checkPhone();
      });

      function checkPhone() {
        console.log("checking");
        if (!angular.isDefined(scope.phone) || scope.phone.length === 0) {
          return;
        }

        validatePhoneService.checkPhone(scope.countryCallingCode, scope.phone).then(function() {
          scope.countryCallingCode = validatePhoneService.countryCallingCode;
          scope.phone = validatePhoneService.phone;
          ctrl.$setViewValue({
            countryCallingCode: scope.countryCallingCode,
            phone: scope.phone
          });
          ctrl.removeExternalValidation('phone');
        }, function() {
          ctrl.setExternalValidation('phone', 'validateSelPhone');
        });
      }
    }
  }
})();
/* Styles go here */

/*
 * angular-auto-validate - v1.19.3 - 2015-11-30
 * https://github.com/jonsamwell/angular-auto-validate
 * Copyright (c) 2015 Jon Samwell (http://www.jonsamwell.com)
 */
(function (String, angular) {
    'use strict';

angular.module('jcs-autoValidate', []);

function ValidatorFn() {
  var elementStateModifiers = {},
    enableValidElementStyling = true,
    enableInvalidElementStyling = true,
    enableFirstInvalidElementScrollingOnSubmit = false,
    validationEnabled = true,

    toBoolean = function (value) {
      var v;
      if (value && value.length !== 0) {
        v = value.toLowerCase();
        value = !(v === 'f' || v === '0' || v === 'false');
      } else {
        value = false;
      }

      return value;
    },

    getAttributeValue = function (el, attrName) {
      var val;

      if (el !== undefined) {
        val = el.attr(attrName) || el.attr('data-' + attrName);
      }

      return val;
    },

    attributeExists = function (el, attrName) {
      var exists;

      if (el !== undefined) {
        exists = el.attr(attrName) !== undefined || el.attr('data-' + attrName) !== undefined;
      }

      return exists;
    },

    getBooleanAttributeValue = function (el, attrName) {
      return toBoolean(getAttributeValue(el, attrName));
    },

    validElementStylingEnabled = function (el) {
      return enableValidElementStyling && !getBooleanAttributeValue(el, 'disable-valid-styling');
    },

    autoValidateEnabledOnControl = function (el) {
      return !getBooleanAttributeValue(el, 'disable-auto-validate');
    },

    invalidElementStylingEnabled = function (el) {
      return enableInvalidElementStyling && !getBooleanAttributeValue(el, 'disable-invalid-styling');
    };

  /**
   * @ngdoc function
   * @name validator#enable
   * @methodOf validator
   *
   * @description
   * By default auto validate will validate all forms and elements with an ngModel directive on.  By
   * setting enabled to false you will explicitly have to opt in to enable validation on forms and child
   * elements.
   *
   * Note: this can be overridden by add the 'auto-validate-enabled="true/false' attribute to a form.
   *
   * Example:
   * <pre>
   *  app.config(function (validator) {
   *    validator.enable(false);
   *  });
   * </pre>
   *
   * @param {Boolean} isEnabled true to enable, false to disable.
   */
  this.enable = function (isEnabled) {
    validationEnabled = isEnabled;
  };

  /**
   * @ngdoc function
   * @name validator#isEnabled
   * @methodOf validator
   *
   * @description
   * Returns true if the library is enabeld.
   *
   * @return {Boolean} true if enabled, otherwise false.
   */
  this.isEnabled = function () {
    return validationEnabled;
  };

  /**
   * @ngdoc function
   * @name validator#setDefaultElementModifier
   * @methodOf validator
   *
   * @description
   * Sets the default element modifier that will be used by the validator
   * to change an elements UI state.  Please ensure the modifier has been registered
   * before setting it as default.
   *
   * Note: this can be changed by setting the
   * element modifier attribute on the input element 'data-element-modifier="myCustomModifier"'
   *
   * Example:
   * <pre>
   *  app.config(function (validator) {
   *    validator.setDefaultElementModifier('myCustomModifier');
   *  });
   * </pre>
   *
   * @param {string} key The key name of the modifier.
   */
  this.setDefaultElementModifier = function (key) {
    if (elementStateModifiers[key] === undefined) {
      throw new Error('Element modifier not registered: ' + key);
    }

    this.defaultElementModifier = key;
  };

  /**
   * @ngdoc function
   * @name validator#registerDomModifier
   * @methodOf validator
   *
   * @description
   * Registers an object that adheres to the elementModifier interface and is
   * able to modifier an elements dom so that appears valid / invalid for a specific
   * scenario i.e. the Twitter Bootstrap css framework, Foundation CSS framework etc.
   *
   * Example:
   * <pre>
   *  app.config(function (validator) {
   *    validator.registerDomModifier('customDomModifier', {
   *      makeValid: function (el) {
   *          el.removeClass(el, 'invalid');
   *          el.addClass(el, 'valid');
   *      },
   *      makeInvalid: function (el, err, domManipulator) {
   *          el.removeClass(el, 'valid');
   *          el.addClass(el, 'invalid');
   *      }
   *    });
   *  });
   * </pre>
   *
   * @param {string} key The key name of the modifier
   * @param {object} modifier An object which implements the elementModifier interface
   */
  this.registerDomModifier = function (key, modifier) {
    elementStateModifiers[key] = modifier;
  };

  /**
   * @ngdoc function
   * @name validator#setErrorMessageResolver
   * @methodOf validator
   *
   * @description
   * Registers an object that adheres to the elementModifier interface and is
   * able to modifier an elements dom so that appears valid / invalid for a specific
   * scenario i.e. the Twitter Bootstrap css framework, Foundation CSS framework etc.
   *
   * Example:
   * <pre>
   *  app.config(function (validator) {
   *    validator.setErrorMessageResolver(function (errorKey, el) {
   *      var defer = $q.defer();
   *      // resolve the correct error from the given key and resolve the returned promise.
   *      return defer.promise();
   *    });
   *  });
   * </pre>
   *
   * @param {function} resolver A method that returns a promise with the resolved error message in.
   */
  this.setErrorMessageResolver = function (resolver) {
    this.errorMessageResolver = resolver;
  };

  /**
   * @ngdoc function
   * @name validator#getErrorMessage
   * @methodOf validator
   *
   * @description
   * Resolves the error message for the given error type.
   *
   * @param {String} errorKey The error type.
   * @param {Element} el The UI element that is the focus of the error.
   * It is provided as the error message may need information from the element i.e. ng-min (the min allowed value).
   */
  this.getErrorMessage = function (errorKey, el) {
    var defer;
    if (this.errorMessageResolver === undefined) {
      throw new Error('Please set an error message resolver via the setErrorMessageResolver function before attempting to resolve an error message.');
    }

    if (attributeExists(el, 'disable-validation-message')) {
      defer = angular.injector(['ng']).get('$q').defer();
      defer.resolve('');
      return defer.promise;
    } else {
      return this.errorMessageResolver(errorKey, el);
    }
  };

  /**
   * @ngdoc function
   * @name validator#setValidElementStyling
   * @methodOf validator
   *
   * @description
   * Globally enables valid element visual styling.  This is enabled by default.
   *
   * @param {Boolean} enabled True to enable style otherwise false.
   */
  this.setValidElementStyling = function (enabled) {
    enableValidElementStyling = enabled;
  };

  /**
   * @ngdoc function
   * @name validator#setInvalidElementStyling
   * @methodOf validator
   *
   * @description
   * Globally enables invalid element visual styling.  This is enabled by default.
   *
   * @param {Boolean} enabled True to enable style otherwise false.
   */
  this.setInvalidElementStyling = function (enabled) {
    enableInvalidElementStyling = enabled;
  };

  /**
   * @ngdoc function
   * @name validator#setFirstInvalidElementScrollingOnSubmit
   * @methodOf validator
   *
   * @description
   * Globally enables first invalid element scrolling on form submit. This is disabled by default.
   *
   * @param enabled {Boolean} enabled True to enable scrolling otherwise false.
   */
  this.setFirstInvalidElementScrollingOnSubmit = function (enabled) {
    enableFirstInvalidElementScrollingOnSubmit = enabled;
  };

  this.firstInvalidElementScrollingOnSubmitEnabled = function () {
    return enableFirstInvalidElementScrollingOnSubmit;
  };


  this.getDomModifier = function (el) {
    var modifierKey = (el !== undefined ? el.attr('element-modifier') : this.defaultElementModifier) ||
      (el !== undefined ? el.attr('data-element-modifier') : this.defaultElementModifier) ||
      this.defaultElementModifier;

    if (modifierKey === undefined) {
      throw new Error('Please set a default dom modifier via the setDefaultElementModifier method on the validator class.');
    }

    return elementStateModifiers[modifierKey];
  };

  this.makeValid = function (el) {
    if (autoValidateEnabledOnControl(el)) {
      if (validElementStylingEnabled(el)) {
        this.getDomModifier(el).makeValid(el);
      } else {
        this.makeDefault(el);
      }
    }
  };

  this.makeInvalid = function (el, errorMsg) {
    if (autoValidateEnabledOnControl(el)) {
      if (invalidElementStylingEnabled(el)) {
        this.getDomModifier(el).makeInvalid(el, errorMsg);
      } else {
        this.makeDefault(el);
      }
    }
  };

  this.makeDefault = function (el) {
    if (autoValidateEnabledOnControl(el)) {
      var dm = this.getDomModifier(el);
      if (dm.makeDefault) {
        dm.makeDefault(el);
      }
    }
  };

  this.waitForAsyncValidators = function (el) {
    if (autoValidateEnabledOnControl(el)) {
      var dm = this.getDomModifier(el);
      if (dm.waitForAsyncValidators) {
        dm.waitForAsyncValidators(el);
      }
    }
  };

  this.defaultFormValidationOptions = {
    forceValidation: false,
    disabled: false,
    validateNonVisibleControls: false,
    removeExternalValidationErrorsOnSubmit: true,
    validateOnFormSubmit: false,
    waitForAsyncValidators: true
  };

  this.$get = [
    function () {
      return this;
    }
  ];
}

angular.module('jcs-autoValidate').provider('validator', ValidatorFn);

function Bootstrap3ElementModifierFn($log) {
  var customCss = [
    '<style>' +
    '.glyphicon-spin-jcs {' +
    '-webkit-animation: spin 1000ms infinite linear;' +
    'animation: spin 1000ms infinite linear;' +
    '}' +
    '@-webkit-keyframes spin {' +
    '0% {' +
    '-webkit-transform: rotate(0deg);' +
    'transform: rotate(0deg);' +
    '}' +
    '100% {' +
    '-webkit-transform: rotate(359deg);' +
    'transform: rotate(359deg);' +
    '}' +
    '}' +
    '@keyframes spin {' +
    '0% {' +
    '-webkit-transform: rotate(0deg);' +
    'transform: rotate(0deg);' +
    '}' +
    '100% {' +
    '-webkit-transform: rotate(359deg);' +
    'transform: rotate(359deg);' +
    '}' +
    '}' +
    '</style>'
  ].join('');

  angular.element(document.body).append(angular.element(customCss));

  var reset = function (el) {
      angular.forEach(el.find('span'), function (spanEl) {
        spanEl = angular.element(spanEl);
        if (spanEl.hasClass('error-msg') || spanEl.hasClass('form-control-feedback') || spanEl.hasClass('control-feedback')) {
          spanEl.remove();
        }
      });

      el.removeClass('has-success has-error has-feedback');
    },
    findWithClassElementAsc = function (el, klass) {
      var returnEl,
        parent = el;
      for (var i = 0; i <= 10; i += 1) {
        if (parent !== undefined && parent.hasClass(klass)) {
          returnEl = parent;
          break;
        } else if (parent !== undefined) {
          parent = parent.parent();
        }
      }

      return returnEl;
    },

    findWithClassElementDesc = function (el, klass) {
      var child;
      for (var i = 0; i < el.children.length; i += 1) {
        child = el.children[i];
        if (child !== undefined && angular.element(child).hasClass(klass)) {
          break;
        } else if (child.children !== undefined) {
          child = findWithClassElementDesc(child, klass);
          if (child.length > 0) {
            break;
          }
        }
      }

      return angular.element(child);
    },

    findFormGroupElement = function (el) {
      return findWithClassElementAsc(el, 'form-group');
    },

    findInputGroupElement = function (el) {
      return findWithClassElementDesc(el, 'input-group');
    },

    insertAfter = function (referenceNode, newNode) {
      referenceNode[0].parentNode.insertBefore(newNode[0], referenceNode[0].nextSibling);
    },

    /**
     * @ngdoc property
     * @name bootstrap3ElementModifier#addValidationStateIcons
     * @propertyOf bootstrap3ElementModifier
     * @returns {bool} True if an state icon will be added to the element in the valid and invalid control
     * states.  The default is false.
     */
    addValidationStateIcons = false,

    /**
     * @ngdoc function
     * @name bootstrap3ElementModifier#enableValidationStateIcons
     * @methodOf bootstrap3ElementModifier
     *
     * @description
     * Makes an element appear invalid by apply an icon to the input element.
     *
     * @param {bool} enable - True to enable the icon otherwise false.
     */
    enableValidationStateIcons = function (enable) {
      addValidationStateIcons = enable;
    },

    /**
     * @ngdoc function
     * @name bootstrap3ElementModifier#makeValid
     * @methodOf bootstrap3ElementModifier
     *
     * @description
     * Makes an element appear valid by apply bootstrap 3 specific styles and child elements. If the service
     * property 'addValidationStateIcons' is true it will also append validation glyphicon to the element.
     * See: http://getbootstrap.com/css/#forms-control-validation
     *
     * @param {Element} el - The input control element that is the target of the validation.
     */
    makeValid = function (el) {
      var frmGroupEl = findFormGroupElement(el),
        inputGroupEl;

      if (frmGroupEl) {
        reset(frmGroupEl);
        inputGroupEl = findInputGroupElement(frmGroupEl[0]);
        frmGroupEl.addClass('has-success ' + (inputGroupEl.length > 0 || addValidationStateIcons === false ? '' : 'has-feedback'));
        if (addValidationStateIcons) {
          var iconElText = '<span class="glyphicon glyphicon-ok form-control-feedback"></span>';
          if (inputGroupEl.length > 0) {
            iconElText = iconElText.replace('form-', '');
            iconElText = '<span class="input-group-addon control-feedback">' + iconElText + '</span';
          }

          insertAfter(el, angular.element(iconElText));
        }
      } else {
        $log.error('Angular-auto-validate: invalid bs3 form structure elements must be wrapped by a form-group class');
      }
    },

    /**
     * @ngdoc function
     * @name bootstrap3ElementModifier#makeInvalid
     * @methodOf bootstrap3ElementModifier
     *
     * @description
     * Makes an element appear invalid by apply bootstrap 3 specific styles and child elements. If the service
     * property 'addValidationStateIcons' is true it will also append validation glyphicon to the element.
     * See: http://getbootstrap.com/css/#forms-control-validation
     *
     * @param {Element} el - The input control element that is the target of the validation.
     */
    makeInvalid = function (el, errorMsg) {
      var frmGroupEl = findFormGroupElement(el),
        helpTextEl = angular.element('<span class="help-block has-error error-msg">' + errorMsg + '</span>'),
        inputGroupEl;

      if (frmGroupEl) {
        reset(frmGroupEl);
        inputGroupEl = findInputGroupElement(frmGroupEl[0]);
        frmGroupEl.addClass('has-error ' + (inputGroupEl.length > 0 || addValidationStateIcons === false ? '' : 'has-feedback'));
        insertAfter(inputGroupEl.length > 0 ? inputGroupEl : getCorrectElementToPlaceErrorElementAfter(el), helpTextEl);
        if (addValidationStateIcons) {
          var iconElText = '<span class="glyphicon glyphicon-remove form-control-feedback"></span>';
          if (inputGroupEl.length > 0) {
            iconElText = iconElText.replace('form-', '');
            iconElText = '<span class="input-group-addon control-feedback">' + iconElText + '</span>';
          }

          insertAfter(getCorrectElementToPlaceErrorElementAfter(el), angular.element(iconElText));
        }
      } else {
        $log.error('Angular-auto-validate: invalid bs3 form structure elements must be wrapped by a form-group class');
      }
    },

    getCorrectElementToPlaceErrorElementAfter = function (el) {
      var correctEl = el,
        elType = el[0].type ? el[0].type.toLowerCase() : '';

      if ((elType === 'checkbox' || elType === 'radio') && el.parent()[0].nodeName.toLowerCase() === 'label') {
        correctEl = el.parent();
      }

      return correctEl;
    },

    /**
     * @ngdoc function
     * @name bootstrap3ElementModifier#makeDefault
     * @methodOf bootstrap3ElementModifier
     *
     * @description
     * Makes an element appear in its default visual state by apply bootstrap 3 specific styles and child elements.
     *
     * @param {Element} el - The input control element that is the target of the validation.
     */
    makeDefault = function (el) {
      var frmGroupEl = findFormGroupElement(el);
      if (frmGroupEl) {
        reset(frmGroupEl);
      } else {
        $log.error('Angular-auto-validate: invalid bs3 form structure elements must be wrapped by a form-group class');
      }
    },

    waitForAsyncValidators = function (el) {
      var frmGroupEl = findFormGroupElement(el),
        inputGroupEl;

      if (frmGroupEl) {
        reset(frmGroupEl);
        inputGroupEl = findInputGroupElement(frmGroupEl[0]);
        frmGroupEl.addClass('has-feedback ' + (inputGroupEl.length > 0 || addValidationStateIcons === false ? '' : 'has-feedback'));
        if (addValidationStateIcons) {
          var iconElText = '<span class="glyphicon glyphicon-repeat glyphicon-spin-jcs form-control-feedback"></span>';
          if (inputGroupEl.length > 0) {
            iconElText = iconElText.replace('form-', '');
            iconElText = '<span class="input-group-addon control-feedback">' + iconElText + '</span>';
          }

          insertAfter(el, angular.element(iconElText));
        }
      } else {
        $log.error('Angular-auto-validate: invalid bs3 form structure elements must be wrapped by a form-group class');
      }
    };

  return {
    makeValid: makeValid,
    makeInvalid: makeInvalid,
    makeDefault: makeDefault,
    waitForAsyncValidators: waitForAsyncValidators,
    enableValidationStateIcons: enableValidationStateIcons,
    key: 'bs3'
  };
}

Bootstrap3ElementModifierFn.$inject = [
  '$log'
];

angular.module('jcs-autoValidate').factory('bootstrap3ElementModifier', Bootstrap3ElementModifierFn);

/*
 * Taken from https://github.com/angular/angular.js/issues/2690#issue-14462164 (with added tests of course!)
 */
function JCSDebounceFn($timeout) {
  var debounce = function (func, wait, immediate) {
    var timeout;
    return function () {
      var context = this;
      var args = arguments;
      var later = function () {
        timeout = null;
        if (!immediate) {
          func.apply(context, args);
        }
      };

      var callNow = immediate && !timeout;
      $timeout.cancel(timeout);
      timeout = $timeout(later, wait, false);
      if (callNow) {
        func.apply(context, args);
      }
    };
  };

  return {
    debounce: debounce
  };
}

JCSDebounceFn.$inject = [
  '$timeout'
];

angular.module('jcs-autoValidate').factory('jcs-debounce', JCSDebounceFn);

/**
 * Replaces string placeholders with corresponding template string
 */
if (!('format' in String.prototype)) {
  String.prototype.format = function () {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function (match, number) {
      return typeof args[number] !== undefined ? args[number] : match;
    });
  };
}

angular.autoValidate = angular.autoValidate || {
  errorMessages: {}
};

angular.autoValidate.errorMessages['default'] = {
  defaultMsg: 'Please add error message for {0}',
  email: 'Please enter a valid email address',
  minlength: 'Please enter at least {0} characters',
  maxlength: 'You have entered more than the maximum {0} characters',
  min: 'Please enter the minimum number of {0}',
  max: 'Please enter the maximum number of {0}',
  required: 'This field is required',
  date: 'Please enter a valid date',
  pattern: 'Please ensure the entered information adheres to this pattern {0}',
  number: 'Please enter a valid number',
  url: 'Please enter a valid URL in the format of http(s)://www.google.com'
};

function DefaultErrorMessageResolverFn($q, $http) {
  var currentCulture = 'default',

    i18nFileRootPath = 'js/angular-auto-validate/dist/lang',

    cultureRetrievalPromise,

    loadRemoteCulture = function (culture) {
      cultureRetrievalPromise = $http.get('{0}/jcs-auto-validate_{1}.json'.format(i18nFileRootPath, culture.toLowerCase()));
      return cultureRetrievalPromise;
    },

    /**
     * @ngdoc function
     * @name defaultErrorMessageResolver#setI18nFileRootPath
     * @methodOf defaultErrorMessageResolver
     *
     * @description
     * Set the root path to the il8n files on the server
     *
     * @param {String} rootPath - The root path on the server to the il8n file - this defaults
     * to 'js/angular-auto-validate/lang/'
     */
    setI18nFileRootPath = function (rootPath) {
      i18nFileRootPath = rootPath;
    },

    /**
     * @ngdoc function
     * @name defaultErrorMessageResolver#setCulture
     * @methodOf defaultErrorMessageResolver
     *
     * @description
     * Set the culture for the error messages by loading an the correct culture resource file.
     *
     * @param {String} culture - The new culture in the format of 'en-gb' etc.
     * @param {Function} cultureLoadingFn - A optional function to load the culture resolve which should
     * return a promise which is resolved with the culture errorMessage object.  If a function is not specified
     * the culture file is loaded from the **i18nFileRootPath**.
     * @returns {Promise} - A promise which is resolved with the loaded culture error messages object.
     */
    setCulture = function (culture, cultureLoadingFn) {
      var defer = $q.defer();
      cultureLoadingFn = cultureLoadingFn || loadRemoteCulture;
      currentCulture = culture.toLowerCase();
      if (angular.autoValidate.errorMessages[currentCulture] === undefined) {
        cultureRetrievalPromise = cultureLoadingFn(culture);
        cultureRetrievalPromise.then(function (response) {
          cultureRetrievalPromise = undefined;
          angular.autoValidate.errorMessages[currentCulture] = response.data;
          defer.resolve(angular.autoValidate.errorMessages[currentCulture]);
        }, function (err) {
          angular.autoValidate.errorMessages[currentCulture] = {
            defaultMsg: 'Loading culture failed!'
          };
          cultureRetrievalPromise = null;
          defer.reject(err);
        });
      } else {
        defer.resolve(angular.autoValidate.errorMessages[currentCulture]);
      }

      return defer.promise;
    },

    getErrorMessages = function (culture) {
      var defer = $q.defer();
      culture = culture === undefined ? currentCulture : culture.toLowerCase();
      if (cultureRetrievalPromise !== undefined) {
        cultureRetrievalPromise.then(function () {
          defer.resolve(angular.autoValidate.errorMessages[culture]);
        }, function (err) {
          defer.reject(err);
        });
      } else {
        defer.resolve(angular.autoValidate.errorMessages[culture]);
      }
      return defer.promise;
    },

    getMessageTypeOverride = function (errorType, el) {
      var overrideKey;

      if (el) {
        // try and find an attribute which overrides the given error type in the form of errorType-err-type="someMsgKey"
        errorType += '-err-type';


        overrideKey = el.attr('ng-' + errorType);
        if (overrideKey === undefined) {
          overrideKey = el.attr('data-ng-' + errorType) || el.attr(errorType);
        }

        if (overrideKey) {
          overrideKey = overrideKey.replace(/[\W]/g, '');
        }
      }

      return overrideKey;
    },

    /**
     * @ngdoc function
     * @name defaultErrorMessageResolver#resolve
     * @methodOf defaultErrorMessageResolver
     *
     * @description
     * Resolves a validate error type into a user validation error message
     *
     * @param {String} errorType - The type of validation error that has occurred.
     * @param {Element} el - The input element that is the source of the validation error.
     * @returns {Promise} A promise that is resolved when the validation message has been produced.
     */
    resolve = function (errorType, el) {
      var defer = $q.defer(),
        errMsg,
        parameters = [],
        parameter,
        messageTypeOverride;

      if (cultureRetrievalPromise !== undefined) {
        cultureRetrievalPromise.then(function () {
          resolve(errorType, el).then(function (msg) {
            defer.resolve(msg);
          });
        });
      } else {
        errMsg = angular.autoValidate.errorMessages[currentCulture][errorType];
        messageTypeOverride = getMessageTypeOverride(errorType, el);
        if (messageTypeOverride) {
          errMsg = angular.autoValidate.errorMessages[currentCulture][messageTypeOverride];
        }

        if (errMsg === undefined && messageTypeOverride !== undefined) {
          errMsg = angular.autoValidate.errorMessages[currentCulture].defaultMsg.format(messageTypeOverride);
        } else if (errMsg === undefined) {
          errMsg = angular.autoValidate.errorMessages[currentCulture].defaultMsg.format(errorType);
        }

        if (el && el.attr) {
          try {
            parameter = el.attr('ng-' + errorType);
            if (parameter === undefined) {
              parameter = el.attr('data-ng-' + errorType) || el.attr(errorType);
            }

            parameters.push(parameter || '');

            errMsg = errMsg.format(parameters);
          } catch (e) {}
        }

        defer.resolve(errMsg);
      }

      return defer.promise;
    };

  return {
    setI18nFileRootPath: setI18nFileRootPath,
    setCulture: setCulture,
    getErrorMessages: getErrorMessages,
    resolve: resolve
  };
}

DefaultErrorMessageResolverFn.$inject = [
  '$q',
  '$http'
];

angular.module('jcs-autoValidate').factory('defaultErrorMessageResolver', DefaultErrorMessageResolverFn);

function Foundation5ElementModifierFn() {
  var reset = function (el, inputEl) {
      angular.forEach(el.find('small'), function (smallEl) {
        if (angular.element(smallEl).hasClass('error')) {
          angular.element(smallEl).remove();
        }
      });

      inputEl.removeClass('error');
    },
    findParentColumn = function (el) {
      var parent = el;
      for (var i = 0; i <= 3; i += 1) {
        if (parent !== undefined && (parent.hasClass('columns') || parent.hasClass('column'))) {
          break;
        } else if (parent !== undefined) {
          parent = parent.parent();
        }
      }

      return parent;
    },

    /**
     * @ngdoc function
     * @name foundation5ElementModifier#makeValid
     * @methodOf foundation5ElementModifier
     *
     * @description
     * Makes an element appear valid by apply Foundation 5 specific styles and child elements.
     * See: http://foundation.zurb.com/docs/components/forms.html
     *
     * @param {Element} el - The input control element that is the target of the validation.
     */
    makeValid = function (el) {
      var parentColumn = findParentColumn(el);
      reset(parentColumn && parentColumn.length > 0 ? parentColumn : el, el);
    },

    /**
     * @ngdoc function
     * @name foundation5ElementModifier#makeInvalid
     * @methodOf foundation5ElementModifier
     *
     * @description
     * Makes an element appear invalid by apply Foundation 5 specific styles and child elements.
     * See: http://foundation.zurb.com/docs/components/forms.html
     *
     * @param {Element} el - The input control element that is the target of the validation.
     */
    makeInvalid = function (el, errorMsg) {
      var parentColumn = findParentColumn(el),
        helpTextEl;
      reset(parentColumn || el, el);
      el.addClass('error');
      if (parentColumn) {
        helpTextEl = angular.element('<small class="error">' + errorMsg + '</small>');
        parentColumn.append(helpTextEl);
      }
    },

    /**
     * @ngdoc function
     * @name foundation5ElementModifier#makeDefault
     * @methodOf foundation5ElementModifier
     *
     * @description
     * Makes an element appear in its default visual state by apply foundation 5 specific styles and child elements.
     *
     * @param {Element} el - The input control element that is the target of the validation.
     */
    makeDefault = function (el) {
      makeValid(el);
    };

  return {
    makeValid: makeValid,
    makeInvalid: makeInvalid,
    makeDefault: makeDefault,
    key: 'foundation5'
  };
}

angular.module('jcs-autoValidate').factory('foundation5ElementModifier', Foundation5ElementModifierFn);

function ElementUtilsFn() {
  var isElementVisible = function (el) {
    return el[0].offsetWidth > 0 && el[0].offsetHeight > 0;
  };

  return {
    isElementVisible: isElementVisible
  };
}

function ValidationManagerFn(validator, elementUtils, $anchorScroll) {
  var elementTypesToValidate = ['input', 'textarea', 'select', 'form'],

    elementIsVisible = function (el) {
      return elementUtils.isElementVisible(el);
    },

    getFormOptions = function (el) {
      var frmCtrl = angular.element(el).controller('form'),
        options;

      if (frmCtrl !== undefined && frmCtrl !== null) {
        options = frmCtrl.autoValidateFormOptions;
      } else {
        options = validator.defaultFormValidationOptions;
      }

      return options;
    },

    /**
     * Only validate if the element is present, it is visible, if it is not a comment,
     * it is either a valid user input control (input, select, textare, form) or
     * it is a custom control register by the developer.
     * @param el
     * @param formOptions The validation options of the parent form
     * @returns {boolean} true to indicate it should be validated
     */
    shouldValidateElement = function (el, formOptions, formSubmitted) {
      var elementExists = el && el.length > 0,
        isElementAComment = elementExists && el[0].nodeName.toLowerCase() === '#comment',
        correctVisibilityToValidate,
        correctTypeToValidate,
        correctPhaseToValidate;

      if (elementExists && isElementAComment === false) {
        correctVisibilityToValidate = elementIsVisible(el) || formOptions.validateNonVisibleControls;
        correctTypeToValidate = elementTypesToValidate.indexOf(el[0].nodeName.toLowerCase()) > -1 ||
          el[0].hasAttribute('register-custom-form-control');
        correctPhaseToValidate = formOptions.validateOnFormSubmit === false ||
          (formOptions.validateOnFormSubmit === true && formSubmitted === true);
      }

      return elementExists && !isElementAComment && correctVisibilityToValidate && correctTypeToValidate && correctPhaseToValidate;

    },

    /**
     * @ngdoc validateElement
     * @name validation#validateElement
     * @param {object} modelCtrl holds the information about the element e.g. $invalid, $valid
     * @param {options}
     *  - forceValidation if set to true forces the validation even if the element is pristine
     *  - disabled if set to true forces the validation is disabled and will return true
     *  - validateNonVisibleControls if set to true forces the validation of non visible element i.e. display:block
     * @description
     * Validate the form element and make invalid/valid element model status.
     *
     * As of v1.17.22:
     * BREAKING CHANGE to validateElement on the validationManger.  The third parameter is now the parent form's
     * autoValidateFormOptions object on the form controller.  This can be left blank and will be found by the
     * validationManager.
     */
    validateElement = function (modelCtrl, el, options) {
      var isValid = true,
        frmOptions = options || getFormOptions(el),
        needsValidation = modelCtrl.$pristine === false || frmOptions.forceValidation,
        errorType,
        findErrorType = function ($errors) {
          var keepGoing = true,
            errorTypeToReturn;
          angular.forEach($errors, function (status, errortype) {
            if (keepGoing && status) {
              keepGoing = false;
              errorTypeToReturn = errortype;
            }
          });

          return errorTypeToReturn;
        };

      if (frmOptions.disabled === false) {
        if ((frmOptions.forceValidation ||
            (shouldValidateElement(el, frmOptions, frmOptions.getFormController().$submitted) &&
              modelCtrl &&
              needsValidation))) {
          isValid = !modelCtrl.$invalid;

          if (frmOptions.removeExternalValidationErrorsOnSubmit && modelCtrl.removeAllExternalValidation) {
            modelCtrl.removeAllExternalValidation();
          }

          if (modelCtrl.$pending !== undefined && options.waitForAsyncValidators === true) {
            // we have pending async validators
            validator.waitForAsyncValidators(el);
          } else {
            if (isValid) {
              validator.makeValid(el);
            } else {
              errorType = findErrorType(modelCtrl.$errors || modelCtrl.$error);
              if (errorType === undefined) {
                // we have a weird situation some users are encountering where a custom control
                // is valid but the ngModel is report it isn't and thus no valid error type can be found
                isValid = true;
              } else {
                validator.getErrorMessage(errorType, el).then(function (errorMsg) {
                  validator.makeInvalid(el, errorMsg);
                });
              }
            }
          }
        }
      }

      return isValid;
    },

    resetElement = function (element) {
      validator.makeDefault(element);
    },

    resetForm = function (frmElement) {
      angular.forEach((frmElement[0].all || frmElement[0].elements) || frmElement[0], function (element) {
        var controller,
          ctrlElement = angular.element(element);
        controller = ctrlElement.controller('ngModel');

        if (controller !== undefined) {
          if (ctrlElement[0].nodeName.toLowerCase() === 'form') {
            // we probably have a sub form
            resetForm(ctrlElement);
          } else {
            controller.$setPristine();
          }
        }
      });
    },

    validateForm = function (frmElement) {
      var frmValid = true,
        frmCtrl = frmElement ? angular.element(frmElement).controller('form') : undefined,
        processElement = function (ctrlElement, force, formOptions) {
          var controller, isValid, ctrlFormOptions, originalForceValue;

          ctrlElement = angular.element(ctrlElement);
          controller = ctrlElement.controller('ngModel');

          if (controller !== undefined && (force || shouldValidateElement(ctrlElement, formOptions, frmCtrl.$submitted))) {
            if (ctrlElement[0].nodeName.toLowerCase() === 'form') {
              // we probably have a sub form
              validateForm(ctrlElement);
            } else {
              // we need to get the options for the element rather than use the passed in as the
              // element could be an ng-form and have different options to the parent form.
              ctrlFormOptions = getFormOptions(ctrlElement);
              originalForceValue = ctrlFormOptions.forceValidation;
              ctrlFormOptions.forceValidation = force;
              try {
                isValid = validateElement(controller, ctrlElement, ctrlFormOptions);
                if (validator.firstInvalidElementScrollingOnSubmitEnabled() && !isValid && frmValid) {
                  var ctrlElementId = ctrlElement.attr('id');
                  if (ctrlElementId) {
                    $anchorScroll(ctrlElementId);
                  }
                }
                frmValid = frmValid && isValid;
              } finally {
                ctrlFormOptions.forceValidation = originalForceValue;
              }
            }
          }
        },
        clonedOptions;

      if (frmElement === undefined || (frmCtrl !== undefined && frmCtrl.autoValidateFormOptions.disabled)) {
        return frmElement !== undefined;
      }

      //force the validation of controls
      clonedOptions = angular.copy(frmCtrl.autoValidateFormOptions);
      clonedOptions.forceValidation = true;

      // IE8 holds the child controls collection in the all property
      // Firefox in the elements and chrome as a child iterator
      angular.forEach((frmElement[0].elements || frmElement[0].all) || frmElement[0], function (ctrlElement) {
        processElement(ctrlElement, true, clonedOptions);
      });

      // If you have a custom form control that should be validated i.e.
      // <my-custom-element>...</my-custom-element> it will not be part of the forms
      // HTMLFormControlsCollection and thus won't be included in the above element iteration although
      // it will be on the Angular FormController (if it has a name attribute).  So adding the directive
      // register-custom-form-control="" to the control root and autoValidate will include it in this
      // iteration.
      if (frmElement[0].customHTMLFormControlsCollection) {
        angular.forEach(frmElement[0].customHTMLFormControlsCollection, function (ctrlElement) {
          // need to force the validation as the element might not be a known form input type
          // so the normal validation process will ignore it.
          processElement(ctrlElement, true, clonedOptions);
        });
      }

      return frmValid;
    },

    setElementValidationError = function (element, errorMsgKey, errorMsg) {
      if (errorMsgKey) {
        validator.getErrorMessage(errorMsgKey, element).then(function (msg) {
          validator.makeInvalid(element, msg);
        });
      } else {
        validator.makeInvalid(element, errorMsg);
      }
    };

  return {
    setElementValidationError: setElementValidationError,
    validateElement: validateElement,
    validateForm: validateForm,
    resetElement: resetElement,
    resetForm: resetForm
  };
}

ValidationManagerFn.$inject = [
  'validator',
  'jcs-elementUtils',
  '$anchorScroll'
];

angular.module('jcs-autoValidate').factory('jcs-elementUtils', ElementUtilsFn);
angular.module('jcs-autoValidate').factory('validationManager', ValidationManagerFn);

function parseBooleanAttributeValue(val, defaultValue) {
  if ((val === undefined || val === null) && defaultValue !== undefined) {
    return defaultValue;
  } else {
    return val !== 'false';
  }
}

function parseOptions(ctrl, validator, attrs) {
  var opts = ctrl.autoValidateFormOptions = ctrl.autoValidateFormOptions || angular.copy(validator.defaultFormValidationOptions);

  // needed to stop circular ref in json serialisation
  opts.getFormController = function () {
    return ctrl;
  };
  opts.waitForAsyncValidators = parseBooleanAttributeValue(attrs.waitForAsyncValidators, opts.waitForAsyncValidators);
  opts.forceValidation = false;
  opts.disabled = !validator.isEnabled() || parseBooleanAttributeValue(attrs.disableDynamicValidation, opts.disabled);
  opts.validateNonVisibleControls = parseBooleanAttributeValue(attrs.validateNonVisibleControls, opts.validateNonVisibleControls);
  opts.validateOnFormSubmit = parseBooleanAttributeValue(attrs.validateOnFormSubmit, opts.validateOnFormSubmit);
  opts.removeExternalValidationErrorsOnSubmit = attrs.removeExternalValidationErrorsOnSubmit === undefined ?
    opts.removeExternalValidationErrorsOnSubmit :
    parseBooleanAttributeValue(attrs.removeExternalValidationErrorsOnSubmit, opts.removeExternalValidationErrorsOnSubmit);

  // the library might be globally disabled but enabled on a particular form so check the
  // disableDynamicValidation attribute is on the form
  if (validator.isEnabled() === false && attrs.disableDynamicValidation === 'false') {
    opts.disabled = false;
  }
}

angular.module('jcs-autoValidate').directive('form', [
  'validator',
  function (validator) {
    return {
      restrict: 'E',
      require: 'form',
      priority: 9999,
      compile: function () {
        return {
          pre: function (scope, element, attrs, ctrl) {
            parseOptions(ctrl, validator, attrs);
          }
        };
      }
    };
  }
]);

angular.module('jcs-autoValidate').directive('ngForm', [
  'validator',
  function (validator) {
    return {
      restrict: 'EA',
      require: 'form',
      priority: 9999,
      compile: function () {
        return {
          pre: function (scope, element, attrs, ctrl) {
            parseOptions(ctrl, validator, attrs);
          }
        };
      }
    };
  }
]);

function FormResetDirectiveFn(validationManager) {
  return {
    restrict: 'E',
    link: function (scope, el) {
      var formController = el.controller('form');

      function resetFn() {
        validationManager.resetForm(el);
        if (formController.$setPristine) {
          formController.$setPristine();
        }

        if (formController.$setUntouched) {
          formController.$setUntouched();
        }
      }

      if (formController !== undefined &&
        formController.autoValidateFormOptions &&
        formController.autoValidateFormOptions.disabled === false) {
        el.on('reset', resetFn);

        scope.$on('$destroy', function () {
          el.off('reset', resetFn);
        });
      }
    }
  };
}

FormResetDirectiveFn.$inject = [
  'validationManager'
];

angular.module('jcs-autoValidate').directive('form', FormResetDirectiveFn);

function RegisterCustomFormControlFn() {
  var findParentForm = function (el) {
    var parent = el;
    for (var i = 0; i <= 50; i += 1) {
      if (parent !== undefined && parent.nodeName.toLowerCase() === 'form') {
        break;
      } else if (parent !== undefined) {
        parent = angular.element(parent).parent()[0];
      }
    }

    return parent;
  };

  return {
    restrict: 'A',
    link: function (scope, element) {
      var frmEl = findParentForm(element.parent()[0]);
      if (frmEl) {
        frmEl.customHTMLFormControlsCollection = frmEl.customHTMLFormControlsCollection || [];
        frmEl.customHTMLFormControlsCollection.push(element[0]);
      }
    }
  };
}

angular.module('jcs-autoValidate').directive('registerCustomFormControl', RegisterCustomFormControlFn);

function SubmitDecorator($delegate, $parse, validationManager) {
  $delegate[0].compile = function ($element, attrs) {
    var fn = $parse(attrs.ngSubmit),
      force = attrs.ngSubmitForce === 'true';

    return function (scope, element) {
      var formController = element.controller('form'),
        resetListenerOffFn;

      function handlerFn(event) {
        scope.$apply(function () {
          if (formController !== undefined &&
            formController !== null &&
            formController.autoValidateFormOptions &&
            formController.autoValidateFormOptions.disabled === true) {
            fn(scope, {
              $event: event
            });
          } else {
            if (formController.$setSubmitted === undefined) {
              // we probably have angular <= 1.2
              formController.$submitted = true;
            }

            if (validationManager.validateForm(element) || force === true) {
              fn(scope, {
                $event: event
              });
            }
          }
        });
      }

      function resetFormFn() {
        if (element[0].reset) {
          element[0].reset();
        } else {
          validationManager.resetForm(element);
        }
      }

      if (formController && formController.autoValidateFormOptions) {
        // allow the form to be reset programatically or via raising the event
        // form:formName:reset
        formController.autoValidateFormOptions.resetForm = resetFormFn;
        if (formController.$name !== undefined && formController.$name !== '') {
          resetListenerOffFn = scope.$on('form:' + formController.$name + ':reset', resetFormFn);
        }
      }

      element.on('submit', handlerFn);
      scope.$on('$destroy', function () {
        element.off('submit', handlerFn);
        if (resetListenerOffFn) {
          resetListenerOffFn();
        }
      });
    };
  };

  return $delegate;
}

SubmitDecorator.$inject = [
  '$delegate',
  '$parse',
  'validationManager'
];

function ProviderFn($provide) {
  $provide.decorator('ngSubmitDirective', SubmitDecorator);
}

ProviderFn.$inject = [
  '$provide'
];

angular.module('jcs-autoValidate').config(ProviderFn);

angular.module('jcs-autoValidate').config(['$provide',
  function ($provide) {
    $provide.decorator('ngModelDirective', [
      '$timeout',
      '$delegate',
      'validationManager',
      'jcs-debounce',
      function ($timeout, $delegate, validationManager, debounce) {
        var directive = $delegate[0],
          link = directive.link || directive.compile;

        directive.compile = function (el) {
          var supportsNgModelOptions = angular.version.major >= 1 && angular.version.minor >= 3,
            originalLink = link;

          // in the RC of 1.3 there is no directive.link only the directive.compile which
          // needs to be invoked to get at the link functions.
          if (supportsNgModelOptions && angular.isFunction(link)) {
            originalLink = link(el);
          }

          return {
            pre: function (scope, element, attrs, ctrls) {
              var ngModelCtrl = ctrls[0],
                frmCtrl = ctrls[1],
                ngModelOptions = attrs.ngModelOptions === undefined ? undefined : scope.$eval(attrs.ngModelOptions),
                setValidity = ngModelCtrl.$setValidity,
                setPristine = ngModelCtrl.$setPristine,
                setValidationState = debounce.debounce(function () {
                  var validateOptions = frmCtrl !== undefined && frmCtrl !== null ? frmCtrl.autoValidateFormOptions : undefined;
                  validationManager.validateElement(ngModelCtrl, element, validateOptions);
                }, 100);

              if (attrs.formnovalidate === undefined &&
                (frmCtrl !== undefined && frmCtrl !== null && frmCtrl.autoValidateFormOptions &&
                  frmCtrl.autoValidateFormOptions.disabled === false)) {
                // if the version of angular supports ng-model-options let angular handle the element.on bit
                // fixes issue with async validators
                if (supportsNgModelOptions ||
                  (!supportsNgModelOptions || ngModelOptions === undefined || ngModelOptions.updateOn === undefined || ngModelOptions.updateOn === '')) {
                  ngModelCtrl.$setValidity = function (validationErrorKey, isValid) {
                    setValidity.call(ngModelCtrl, validationErrorKey, isValid);
                    setValidationState();
                  };
                } else {
                  element.on(ngModelOptions.updateOn, function () {
                    setValidationState();
                  });

                  scope.$on('$destroy', function () {
                    element.off(ngModelOptions.updateOn);
                  });
                }

                // We override this so we can reset the element state when it is called.
                ngModelCtrl.$setPristine = function () {
                  setPristine.call(ngModelCtrl);
                  validationManager.resetElement(element);
                };

                ngModelCtrl.autoValidated = true;
              }

              ngModelCtrl.setExternalValidation = function (errorMsgKey, errorMessage, addToModelErrors) {
                if (addToModelErrors) {
                  var collection = ngModelCtrl.$error || ngModelCtrl.$errors;
                  collection[errorMsgKey] = false;
                }

                ngModelCtrl.externalErrors = ngModelCtrl.externalErrors || {};
                ngModelCtrl.externalErrors[errorMsgKey] = false;
                validationManager.setElementValidationError(element, errorMsgKey, errorMessage);
              };

              ngModelCtrl.removeExternalValidation = function (errorMsgKey, addToModelErrors) {
                if (addToModelErrors) {
                  var collection = ngModelCtrl.$error || ngModelCtrl.$errors;
                  delete collection[errorMsgKey];
                }

                if (ngModelCtrl.externalErrors) {
                  delete ngModelCtrl.externalErrors[errorMsgKey];
                }

                validationManager.resetElement(element);
              };

              ngModelCtrl.removeAllExternalValidation = function () {
                if (ngModelCtrl.externalErrors) {
                  var errorCollection = ngModelCtrl.$error || ngModelCtrl.$errors;
                  angular.forEach(ngModelCtrl.externalErrors, function (value, key) {
                    delete errorCollection[key];
                  });

                  ngModelCtrl.externalErrors = {};

                  validationManager.resetElement(element);
                }
              };

              if (frmCtrl) {
                frmCtrl.setExternalValidation = function (modelProperty, errorMsgKey, errorMessageOverride, addToModelErrors) {
                  var success = false;
                  if (frmCtrl[modelProperty]) {
                    frmCtrl[modelProperty].setExternalValidation(errorMsgKey, errorMessageOverride, addToModelErrors);
                    success = true;
                  }

                  return success;
                };

                frmCtrl.removeExternalValidation = function (modelProperty, errorMsgKey, errorMessageOverride, addToModelErrors) {
                  var success = false;
                  if (frmCtrl[modelProperty]) {
                    frmCtrl[modelProperty].removeExternalValidation(errorMsgKey, addToModelErrors);
                    success = true;
                  }

                  return success;
                };
              }

              return originalLink.pre ?
                originalLink.pre.apply(this, arguments) :
                this;
            },
            post: function (scope, element, attrs, ctrls) {
              return originalLink.post ?
                originalLink.post.apply(this, arguments) :
                originalLink.apply(this, arguments);
            }
          };
        };

        return $delegate;
      }
    ]);
  }
]);

function AutoValidateRunFn(validator, defaultErrorMessageResolver, bootstrap3ElementModifier, foundation5ElementModifier) {
  validator.setErrorMessageResolver(defaultErrorMessageResolver.resolve);
  validator.registerDomModifier(bootstrap3ElementModifier.key, bootstrap3ElementModifier);
  validator.registerDomModifier(foundation5ElementModifier.key, foundation5ElementModifier);
  validator.setDefaultElementModifier(bootstrap3ElementModifier.key);
}

AutoValidateRunFn.$inject = [
  'validator',
  'defaultErrorMessageResolver',
  'bootstrap3ElementModifier',
  'foundation5ElementModifier'
];

angular.module('jcs-autoValidate').run(AutoValidateRunFn);

}(String, angular));