Gets group and property level validation wired up to the fields, ensures things like duplicate field warnings are created with the correct indexed field names. Fixes a few old directives to correctly watch for attribute interpolation changes which is need for some of this validation to work since it's dynamically populating the field names.

This commit is contained in:
Shannon
2015-10-09 17:28:22 +02:00
parent c477f7a840
commit 6017e9ddf9
10 changed files with 142 additions and 104 deletions

View File

@@ -11,7 +11,7 @@
function LockedFieldDirective($timeout, localizationService) {
function link(scope, el, attr, ngModel) {
function link(scope, el, attr, ngModel) {
var input = el.children('.umb-locked-field__input');
@@ -22,6 +22,16 @@
scope.locked = true;
}
// if regex validation is not defined as an attr set default state
// if this is set to an empty string then regex validation can be ignored.
if (scope.regexValidation === undefined || scope.regexValidation === null) {
scope.regexValidation = "^[a-zA-Z]\\w.*$";
}
if (scope.serverValidationField === undefined || scope.serverValidationField === null) {
scope.serverValidationField = "";
}
// if locked state is not defined as an attr set default state
if (scope.placeholderText === undefined || scope.placeholderText === null) {
scope.placeholderText = "Enter value...";
@@ -70,7 +80,9 @@
scope: {
model: '=ngModel',
locked: "=?",
placeholderText: "=?"
placeholderText: "=?",
regexValidation: "=?",
serverValidationField: "@"
},
link: link
};

View File

@@ -57,7 +57,7 @@ angular.module('umbraco.directives.validation')
}
};
validators[key] = validateFn;
ctrl.$formatters.push(validateFn);
ctrl.$parsers.push(validateFn);
});

View File

@@ -9,9 +9,7 @@ function valHighlight($timeout) {
restrict: "A",
link: function (scope, element, attrs, ctrl) {
scope.$watch(function() {
return scope.$eval(attrs.valHighlight);
}, function(newVal, oldVal) {
attrs.$observe("valHighlight", function (newVal) {
if (newVal === true) {
element.addClass("highlight-error");
$timeout(function () {
@@ -23,7 +21,7 @@ function valHighlight($timeout) {
element.removeClass("highlight-error");
}
});
}
};
}

View File

@@ -29,7 +29,6 @@ function valEmail(valEmailExpression) {
}
};
ctrl.$formatters.push(patternValidator);
ctrl.$parsers.push(patternValidator);
}
};

View File

@@ -59,9 +59,6 @@ function valPropertyValidator(serverValidationManager) {
}
};
// Formatters are invoked when the model is modified in the code.
modelCtrl.$formatters.push(validate);
// Parsers are called as soon as the value in the form input is modified
modelCtrl.$parsers.push(validate);

View File

@@ -13,50 +13,51 @@ function valRegex() {
link: function (scope, elm, attrs, ctrl) {
var flags = "";
if (attrs.valRegexFlags) {
try {
flags = scope.$eval(attrs.valRegexFlags);
if (!flags) {
flags = attrs.valRegexFlags;
var regex;
attrs.$observe("valRegexFlags", function (newVal) {
if (newVal) {
flags = newVal;
}
});
attrs.$observe("valRegex", function (newVal) {
if (newVal) {
try {
var resolved = newVal;
if (resolved) {
regex = new RegExp(resolved, flags);
}
else {
regex = new RegExp(attrs.valRegex, flags);
}
}
catch (e) {
regex = new RegExp(attrs.valRegex, flags);
}
}
catch (e) {
flags = attrs.valRegexFlags;
}
}
var regex;
try {
var resolved = scope.$eval(attrs.valRegex);
if (resolved) {
regex = new RegExp(resolved, flags);
}
else {
regex = new RegExp(attrs.valRegex, flags);
}
}
catch(e) {
regex = new RegExp(attrs.valRegex, flags);
}
});
var patternValidator = function (viewValue) {
//NOTE: we don't validate on empty values, use required validator for that
if (!viewValue || regex.test(viewValue)) {
// it is valid
ctrl.$setValidity('valRegex', true);
//assign a message to the validator
ctrl.errorMsg = "";
return viewValue;
}
else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('valRegex', false);
//assign a message to the validator
ctrl.errorMsg = "Value is invalid, it does not match the correct pattern";
return undefined;
if (regex) {
//NOTE: we don't validate on empty values, use required validator for that
if (!viewValue || regex.test(viewValue)) {
// it is valid
ctrl.$setValidity('valRegex', true);
//assign a message to the validator
ctrl.errorMsg = "";
return viewValue;
}
else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity('valRegex', false);
//assign a message to the validator
ctrl.errorMsg = "Value is invalid, it does not match the correct pattern";
return undefined;
}
}
};
ctrl.$formatters.push(patternValidator);
ctrl.$parsers.push(patternValidator);
}
};

View File

@@ -11,47 +11,46 @@ function valServerField(serverValidationManager) {
restrict: "A",
link: function (scope, element, attr, ctrl) {
if (!attr.valServerField) {
throw "valServerField must have a field name for referencing server errors";
}
var fieldName = null;
var fieldName = attr.valServerField;
var evalfieldName = scope.$eval(attr.valServerField);
if (evalfieldName) {
fieldName = evalfieldName;
}
attr.$observe("valServerField", function (newVal) {
if (newVal && fieldName === null) {
fieldName = newVal;
//subscribe to the changed event of the view model. This is required because when we
// have a server error we actually invalidate the form which means it cannot be
// resubmitted. So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
ctrl.$viewChangeListeners.push(function () {
if (ctrl.$invalid) {
ctrl.$setValidity('valServerField', true);
}
});
//subscribe to the server validation changes
serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
if (!isValid) {
ctrl.$setValidity('valServerField', false);
//assign an error msg property to the current validator
ctrl.errorMsg = fieldErrors[0].errorMsg;
}
else {
ctrl.$setValidity('valServerField', true);
//reset the error message
ctrl.errorMsg = "";
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
serverValidationManager.unsubscribe(null, fieldName);
});
//subscribe to the changed event of the view model. This is required because when we
// have a server error we actually invalidate the form which means it cannot be
// resubmitted. So once a field is changed that has a server error assigned to it
// we need to re-validate it for the server side validator so the user can resubmit
// the form. Of course normal client-side validators will continue to execute.
ctrl.$viewChangeListeners.push(function () {
if (ctrl.$invalid) {
ctrl.$setValidity('valServerField', true);
}
});
//subscribe to the server validation changes
serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
if (!isValid) {
ctrl.$setValidity('valServerField', false);
//assign an error msg property to the current validator
ctrl.errorMsg = fieldErrors[0].errorMsg;
}
else {
ctrl.$setValidity('valServerField', true);
//reset the error message
ctrl.errorMsg = "";
}
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
// but they are a different callback instance than the above.
element.bind('$destroy', function () {
serverValidationManager.unsubscribe(null, fieldName);
});
}
};
}

View File

@@ -42,6 +42,8 @@
umb-auto-resize
required
val-server-field="{{'Groups[' + $index + '].Name'}}" />
<span class="help-inline" val-msg-for="groupName" val-toggle-msg="valServerField"></span>
</div>
</ng-form>
@@ -93,13 +95,21 @@
<ng-form name="propertyTypeForm">
<div class="control-group -no-margin" ng-if="!sortingMode">
<div class="umb-group-builder__property-meta-alias">{{ property.alias }}</div>
<umb-locked-field locked="locked"
ng-model="property.alias"
placeholder-text="'Alias...'"
server-validation-field="{{'Groups[' + $parent.$index + '].Properties[' + $index + '].Alias'}}">
</umb-locked-field>
<div class="umb-group-builder__property-meta-label">
<textarea placeholder="Label..." ng-model="property.label" ng-disabled="property.inherited"
name="groupName"
umb-auto-resize
required
val-server-field="{{'Groups[' + $parent.$index + '].Properties[' + $index + '].Label'}}"></textarea>
<span class="help-inline" val-msg-for="groupName" val-toggle-msg="valServerField"></span>
</div>
<div class="umb-group-builder__property-meta-description">

View File

@@ -8,20 +8,21 @@
<i class="umb-locked-field__lock-icon icon-unlocked -unlocked"></i>
</a>
<input
type="text"
class="umb-locked-field__input"
name="lockedField"
ng-model="model"
ng-disabled="locked"
ng-class="{'-unlocked': !locked}"
placeholder="{{placeholderText}}"
umb-auto-resize
required
val-regex="^[a-zA-Z]\w.*$"
/>
<input type="text"
class="umb-locked-field__input"
name="lockedField"
ng-model="model"
ng-disabled="locked"
ng-class="{'-unlocked': !locked}"
placeholder="{{placeholderText}}"
val-regex="{{regexValidation}}"
umb-auto-resize
required
val-server-field="{{serverValidationField}}"
/>
<span class="validation-label" val-msg-for="lockedField" val-toggle-msg="required"><localize key="required" /></span>
<span class="validation-label" val-msg-for="lockedField" val-toggle-msg="valRegex">Invalid alias</span>
<span ng-if="regexValidation.length > 0" class="validation-label" val-msg-for="lockedField" val-toggle-msg="valRegex">Invalid alias</span>
<span ng-if="serverValidationField.length > 0" class="validation-label" val-msg-for="lockedField" val-toggle-msg="valServerField"></span>
</div>