angular-auto-validate
Version:
An automatic validation module for AngularJS which gets rid of excess html in favour of dynamic element modification to notify the user of validation errors
231 lines (199 loc) • 9.08 kB
JavaScript
function ElementUtilsFn() {
var isElementVisible = function (el) {
return el[0].offsetWidth > 0 && el[0].offsetHeight > 0;
};
return {
isElementVisible: isElementVisible
};
}
function ValidationManagerFn(validator, elementUtils) {
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 (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);
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'
];
angular.module('jcs-autoValidate').factory('jcs-elementUtils', ElementUtilsFn);
angular.module('jcs-autoValidate').factory('validationManager', ValidationManagerFn);