Skip to content

Instantly share code, notes, and snippets.

@PawelGerr
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PawelGerr/16e6965e866dc8a0b7fe to your computer and use it in GitHub Desktop.
Save PawelGerr/16e6965e866dc8a0b7fe to your computer and use it in GitHub Desktop.
(function () {
'use strict';
/**
* @param $scope
* @param $parse
*/
function TtValidationController($scope, $parse) {
var context = null;
var controls = [];
/**
* @param {TtValidateController} control
*/
this.addValidationControl = function (control) {
if (!control)
throw new Error('Validation control must not be null.');
controls.push(control);
if (context)
initControl(control, context);
};
/**
* @param {TtValidateController} control
*/
this.removeValidationControl = function (control) {
removeFromArray(controls, control);
};
/**
* @param controlElement
*/
this.getNgRepeats = function (controlElement, ngModelExpression) {
var ngRepeats = [];
var previousVariable = getFirstVariableName(ngModelExpression);
while (controlElement && (controlElement.length > 0) && !$scope.isValidationElement(controlElement)) {
var ngRepeatExp = getNgRepeatExpression(controlElement);
if (ngRepeatExp) {
var parsedExpression = parseNgRepeat(ngRepeatExp);
if (parsedExpression.valueExp === previousVariable) {
previousVariable = getFirstVariableName(parsedExpression.collectionExp);
ngRepeats.push(parsedExpression);
}
}
controlElement = controlElement.parent();
}
return ngRepeats;
};
$scope.init = function (validationContext) {
if (context) {
throw new Error('Validation is initialized already.');
}
context = validationContext;
for (var i = 0; i < controls.length; i++) {
var control = controls[i];
initControl(control);
}
};
/**
* @param {TtValidateController} control
*/
function initControl(control) {
if (!control) {
throw new Error('Validation control must not be null.');
}
if (!context) {
throw new Error('Validation context must not be null.');
}
for (var i = 0; i < context.modelValidations.length; i++) {
var modelConfig = context.modelValidations[i];
var validationDefinition = findValidationDefinition(control, modelConfig.validationDefinition);
if (validationDefinition) {
control.applyValidationDefinition(validationDefinition, modelConfig.modelGetter);
return;
}
}
};
/**
* @param element
* @returns {string}
*/
function getNgRepeatExpression(element) {
var angularElem = angular.element(element);
return angularElem.attr('ng-repeat') || angularElem.attr('data-ng-repeat');
}
/**
* @param expression
* @returns {TtNgRepeat}
*/
function parseNgRepeat(expression) {
// the whole parsing code is taken from angular.js
// expression: item in items | (key, value) in items
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
if (!match) {
throw new Error('NgRepeat expression \'' + expression + '\' could not be parsed');
}
var lhs = match[1]; // item | (key, value)
var rhs = match[2]; // items
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
if (!match) {
throw new Error('The left part \'' + lhs + '\' of the NgRepeat expression \'' + expression + '\' could not be parsed.');
}
var valueIdentifier = match[3] || match[1]; // item | value
var keyIdentifier = match[2]; // key
return new TtNgRepeat(rhs, valueIdentifier);
}
/**
* @param {TtValidateController} control
*/
function findValidationDefinition(control, validationDefinition) {
if (!validationDefinition)
return null;
if (control.ngRepeats.length > 0) {
for (var i = control.ngRepeats.length - 1; i >= 0; i--) {
/** @type {TtNgRepeat} */
var parsedNgRepeat = control.ngRepeats[i];
validationDefinition = $parse(parsedNgRepeat.collectionExp)(validationDefinition);
if (!validationDefinition)
return null;
var wrapper = {};
$parse(parsedNgRepeat.valueExp).assign(wrapper, validationDefinition);
validationDefinition = wrapper;
}
}
var propertyValidation = $parse(control.ngModelExpression)(validationDefinition);
if (propertyValidation && propertyValidation.$validation) {
return propertyValidation.$validation;
}
return null;
}
function getFirstVariableName(expression) {
return expression.split('.')[0].trim();
}
};
/**
* @param $parse
* @param {TtValidationDefinition} ttValidationDefinition
* @returns {*}
* @constructor
*/
function Directive($parse, ttValidationDefinition) {
return {
restrict: 'A',
scope: true,
controller: TtValidationController,
compile: function (tElement, tAttrs, transclude) {
return {
pre: function (scope, element, attrs) {
scope.isValidationElement = function (angularElem) {
if (angularElem.length > 0)
angularElem = angularElem[0];
return angularElem === element[0];
}
},
post: function (scope, element, attrs) {
var options = scope.$eval(attrs.ttValidation);
var ctx = {
modelValidations: []
};
var configs = [];
for (var i = 0; i < options.validations.length; i++) {
var validation = options.validations[i];
var valDef;
if (angular.isDefined(validation.definition)) {
if (!angular.isObject(validation.definition))
throw new Error('The validation definition property must be an object.');
valDef = validation.definition;
} else {
valDef = ttValidationDefinition.get(validation.key);
}
var modelCtx = createValidationContext(scope, valDef, validation.model);
ctx.modelValidations.push(modelCtx);
}
scope.init(ctx);
}
};
function createValidationContext(scope, rules, modelExp) {
var modelGetter;
if (modelExp) {
var modelExp = (modelExp),
ngModelGet = $parse(modelExp),
ngModelSet = ngModelGet.assign;
if (!ngModelSet)
throw new Error('Validation model \'' + modelExp + '\' is non-assignable.')
var wrappedRules = {};
ngModelSet(wrappedRules, rules);
rules = wrappedRules;
modelGetter = function () {
return ngModelGet(scope);
}
} else {
modelGetter = function () {
return scope;
};
}
return new TtValidationContext(rules, modelGetter)
}
}
};
}
app.module.directive("ttValidation", Directive);
function TtValidationContext(rules, modelGetter) {
this.validationDefinition = rules;
this.modelGetter = modelGetter;
}
function TtNgRepeat(collectionExp, valueExp) {
this.collectionExp = collectionExp;
this.valueExp = valueExp;
}
function removeFromArray(array, item) {
var index = array.indexOf(item);
if (item >= 0) {
array.splice(index, 1);
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment