Provide 'validatable-with' directive

Make it work together with validation machinery in Barricade and both
interact with standard classes in Angular. If the value is invalid
according to @constraints check, it's not propagated into the YAML
(still not true for the requred fields, has to be fixed on Barricade
side).

Change-Id: I22efce07b75aa2b55b65d3bfaab0d033fa1f0096
This commit is contained in:
Timur Sufiev 2015-05-04 11:38:36 -07:00
parent 4669e42341
commit 2079195f7b
9 changed files with 141 additions and 8 deletions

View File

@ -559,7 +559,12 @@
'index': 0,
'panelIndex': 0,
'row': 0
}
},
'@constraints': [
function(value) {
return value !== 'workbook1' ? true : 'The sample validation failure.';
}
]
})
},
'description': {

View File

@ -112,6 +112,36 @@
}
}
})
.directive('validatableWith', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var model;
if ( attrs.validatableWith ) {
model = $parse(attrs.validatableWith)(scope);
scope.error = '';
model.setValidatable && model.setValidatable(true);
model.on && model.on('validation', function(result) {
var isValid = (result == 'succeeded'),
baseMessage = '';
// (FIXME): hack until Barricade supports validation of empty required entries
if ( !model.get() && model.isRequired() ) {
isValid = false;
baseMessage = 'This field is required.'
}
ctrl.$setValidity('barricade', isValid);
scope.error = model.hasError() ? model.getError() : baseMessage;
});
ctrl.$formatters.push(function(modelValue) {
return modelValue === undefined ?
( ctrl.$isEmpty(ctrl.$viewValue) ? undefined : ctrl.$viewValue ) :
modelValue;
});
}
}
}
})
.directive('typedField', ['$compile', 'merlin.templates',
function($compile, templates) {
function updateAutoCompletionDirective(template) {

View File

@ -32,11 +32,18 @@
});
var modelMixin = Barricade.Blueprint.create(function(type) {
var isValid = true,
isValidatable = false;
this.value = function() {
if ( !arguments.length ) {
return this.get();
if ( isValidatable ) {
return isValid ? this.get() : undefined;
} else {
return this.get();
}
} else {
this.set(arguments[0]);
isValid = !this.hasError();
}
};
this.id = utils.getNewId();
@ -45,6 +52,10 @@
return type;
};
this.setValidatable = function(validatable) {
isValidatable = validatable;
};
this.setType = function(_type) {
type = _type;
};

View File

@ -54,8 +54,13 @@
}
}
editable .fa-pencil {
color: black;
editable {
.fa-pencil {
color: black;
}
button:disabled {
color: gray;
}
}
.section {

View File

@ -5,6 +5,6 @@
</span>
<span ng-show="isEdited">
<input type="text" ng-model="editableValue" show-focus="isEdited">
<button ng-click="accept()"><i class="fa fa-check"></i></button>
<button ng-click="accept()" ng-disabled="!editableValue"><i class="fa fa-check"></i></button>
<button ng-click="reject()"><i class="fa fa-close"></i></button>
</span>

View File

@ -3,5 +3,6 @@
<label for="elem-{$ $id $}">{$ value.title() $}</label>
<input type="number" class="form-control" id="elem-{$ $id $}"
ng-model="value.value" ng-model-options="{ getterSetter: true }"
autocompletable="true">
autocompletable="true" validatable-with="value">
<div ng-show="error" class="alert alert-danger">{$ error $}</div>
</div>

View File

@ -2,5 +2,6 @@
<label for="elem-{$ $id $}">{$ value.title() $}</label>
<input type="text" class="form-control" id="elem-{$ $id $}"
ng-model="value.value" ng-model-options="{ getterSetter: true }"
autocompletable="true">
autocompletable="true" validatable-with="value">
<div ng-show="error" class="alert alert-danger">{$ error $}</div>
</div>

View File

@ -2,5 +2,6 @@
<label for="elem-{$ $id $}">{$ value.title() $}</label>
<textarea class="form-control" id="elem-{$ $id $}"
ng-model="value.value" ng-model-options="{ getterSetter: true }"
autocompletable="true"></textarea>
autocompletable="true" validatable-with="value"></textarea>
<div ng-show="error" class="alert alert-danger">{$ error $}</div>
</div>

View File

@ -347,4 +347,83 @@ describe('merlin directives', function() {
});
});
describe("'validatable'", function() {
var fields;
beforeEach(inject(function($injector) {
fields = $injector.get('merlin.field.models');
}));
describe('working with the @constraints property:', function() {
var model, elt,
goodValue = 'allowedValue',
badValue = 'restrictedValue',
errorMessage = 'Wrong value provided';
beforeEach(function() {
var modelClass = fields.string.extend({}, {
'@constraints': [
function(value) {
return value !== badValue ? true : errorMessage;
}
]
});
$scope.model = modelClass.create();
elt = $compile('<form name="form"><input name="model" type="text" ' +
'ng-model="model.value" ng-model-options="{ getterSetter: true }" ' +
'validatable-with="model"></form>')($scope);
});
describe('any valid value', function() {
beforeEach(function() {
$scope.form.model.$setViewValue(goodValue);
$scope.$digest();
});
it('is allowed to be entered', function() {
expect($scope.form.model.$viewValue).toEqual(goodValue);
});
it('is propagated into the model', function() {
expect($scope.model.value()).toEqual(goodValue);
});
it('does not cause the input to be marked as erroneous', function() {
expect(elt.find('input').hasClass('ng-valid')).toBe(true);
});
it('sets error message on scope to an empty string', function() {
expect($scope.error).toEqual('');
});
});
describe('any invalid value', function() {
beforeEach(function() {
$scope.form.model.$setViewValue(badValue);
$scope.$digest();
});
it('is allowed to be entered', function() {
expect($scope.form.model.$viewValue).toEqual(badValue);
});
it('is not propagated into the model', function() {
expect($scope.model.value()).toBe(undefined);
});
it('causes the input to be marked as erroneous', function() {
expect(elt.find('input').hasClass('ng-invalid')).toBe(true);
});
it('exposes error message in the parent scope', function() {
expect($scope.error).toEqual(errorMessage);
})
});
});
describe('working with the @required property', function() {
// TODO: fill in once validation of @required fields changes in Barricade
});
});
});