/**
* @name angular-validator
*
* @fileoverview This is an Angular module that provide form validation
* by extending ngModelController. It allows us to define our validation rules
* and added them as angular custom validators. It extends formController to have
* error messages along with the status of validation.
*
* This module gives us the ability to leverage the flexibility of angular's custom synchronus
* and asynchronous validators. It allows to define rules as normal functions which return
* a JSON object with status of validation and the errorMessage, if there is.
*
* @author Vinit Kumar Rai
*/
'use strict';
/*jshint multistr: true */
var validator = angular.module('hiComponents.validator', []);
validator.service('hiValidatorService', [
'$templateCache',
function ($templateCache) {
/**
* This Service should act as intermediary between directive
* and model. Model will have the validations and they register
* their validation rules with the service, validator directive will
* in turn ask service to trigger those validations and give back the
* result.
*/
var _validators = {}, defaultTemplateKey = 'hiValidatorErrorTemplate';
/**
* @description - Function to register validators.
*
* @param validator - Function object - A function object which validates the given value.
* @param validatorName - String - A unique name for the registered validator.
* @param context - Object - Context in which this validator function will be invoked.
* @param overide - Boolean - If there is already an existing validator registered with the given name,
* should we override or throw exception. By default it will throw excception.
*/
var _registerValidator = function (validator, validatorName, context, async, override) {
if (validatorName in _validators && !override) {
throw 'A validator with same name already exists. Either provide a unique name or specify override true';
}
_validators[validatorName] = {
fn: validator,
context: context,
async: async || false
};
};
/**
* Getter function for registered validator.
*
* @param {String} validator Name of registered validator
* @return {Object} Validator Object
*/
var _getValidatorByName = function (validator) {
if (validator in _validators) {
return _validators[validator];
}
return null;
};
/**
* Initializer function, which get executed on service initialization.
* Here we are adding a default error template if there is no such template
* registered in $templateCache.
*/
var _initialize = function () {
if (!$templateCache.get(defaultTemplateKey)) {
$templateCache.put('hiValidatorErrorTemplate', '
' + '' + '
');
}
};
_initialize();
return {
register: _registerValidator,
getValidator: _getValidatorByName
};
}
]);
validator.directive('hiValidate', [
'hiValidatorService',
'$log',
function (hiValidatorService, $log) {
/**
* Angular Directive, that should be used on input element which needs to be validated.
* You have to specify a validator name(that you have registered) using which you want
* to validate view values.
*
* NOTE: You can specify more than one validator name.
* NOTE: You can specify a mix of synchronus and asynchronous validators.
* NOTE: All synchronus validators will be executed before any asynchronous validators.
* NOTE: Validators will be executed in same order as you have specified in html.
* NOTE: If any synchronus validator fails then it will stop the execution of other validators in sequence.
*/
return {
require: 'ngModel',
scope: {},
link: function (scope, element, attrs, ctrl) {
var validators = null, forEach = angular.forEach;
var initialize = function () {
validators = getValidators();
forEach(validators, function (validatorName) {
var validator = hiValidatorService.getValidator(validatorName);
if (validator) {
registerValidator(validatorName, validator);
} else {
$log.error('No validator with name ' + validatorName + ' has been registered');
}
});
};
var registerValidator = function (validatorName, validator) {
var validatorFn = validator.fn, context = validator.context;
var syncValidateFn = function (modalValue, viewValue) {
var result = validatorFn.call(context, modalValue || viewValue);
ctrl.$error.messages[validatorName] = ctrl.$dirty ? result : {};
return result.isValid;
};
var asyncValidateFn = function (modalValue, viewValue) {
var result = validatorFn.call(context, modalValue || viewValue);
result.then(function (resp) {
ctrl.$error.messages[validatorName] = ctrl.$dirty ? resp : {};
}, function (resp) {
ctrl.$error.messages[validatorName] = ctrl.$dirty ? resp : {};
});
return result;
};
var validatorType = validator.async ? '$asyncValidators' : '$validators';
var fn = validator.async ? asyncValidateFn : syncValidateFn;
ctrl[validatorType][validatorName] = fn;
ctrl.$error.messages = {};
};
var getValidators = function () {
return attrs.hiValidate.split(',').map(function (v) {
return v.trim();
});
};
initialize();
}
};
}
]);
validator.directive('hiValidatorMessage', function () {
/**
* Directive to display error message for the model specified on form.
*
* NOTE: You can also specify a `tpl` attribute that will be used to get an
* already registered template from cache.
* This means you can also specify a custom validation template.
*/
return {
replace: true,
restrict: 'E',
require: '^form',
scope: { model: '@model' },
templateUrl: function (elem, attrs) {
return attrs.tpl || 'hiValidatorErrorTemplate';
},
link: function (scope, element, attrs, formCtrl) {
scope.errors = formCtrl[scope.model].$error.messages;
}
};
});