Add validateUnique form input validator

This adds the directive used for validating the uniqueness of a form
input value. It also updates the metadata tree widget to use it.

Closes-Bug: 1556255
Change-Id: Icc6ce0ef5212801fa1531196fb0705760e9c3b6e
This commit is contained in:
Justin Pomeroy 2016-03-11 13:31:43 -06:00
parent 16660b07da
commit 21c019611e
6 changed files with 196 additions and 54 deletions

View File

@ -0,0 +1,90 @@
/*
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
/**
* @ngdoc directive
* @name horizon.framework.util.validators:validateUnique
* @element ng-model
* @description
* The `validateUnique` directive provides validation for form input elements to ensure
* values are unique.
*
* The form input value can be validated against an array of values or a custom validator
* function can be provided. If an array of values is specified, the validator returns true
* if the value is not found in the array. If a function is specified, the validator returns
* the value of the function which should evaluate to true or false.
*
* @restrict A
*
* @example
* ```
* <input type="number" ng-model="value"
* validate-unique="[80,443]">
* ```
*
* @example
* ```
* <input type="string" ng-model="value"
* validate-unique="ctrl.validateUniqueName">
*
* ctrl.validateUniqueName = function(value) {
* return !existingItems.some(function(item) {
* return item.leaf && item.leaf.name === value;
* });
* };
* ```
*/
angular
.module('horizon.framework.util.validators')
.directive('validateUnique', validateUnique);
function validateUnique() {
var directive = {
require: 'ngModel',
restrict: 'A',
link: link
};
return directive;
//////////
function link(scope, element, attrs, ctrl) {
ctrl.$parsers.push(validate);
ctrl.$formatters.push(validate);
attrs.$observe('validateUnique', function () {
validate(ctrl.$modelValue);
});
function validate(value) {
var param = scope.$eval(attrs.validateUnique);
var unique = true;
if (angular.isArray(param) && param.length > 0) {
unique = param.indexOf(value) < 0;
} else if (angular.isFunction(param)) {
unique = param(value);
}
ctrl.$setValidity('unique', unique);
return value;
}
}
}
})();

View File

@ -91,5 +91,99 @@
});
});
describe('validateUnique directive', function () {
var scope, port, name, protocol;
var items = [{ id: '1', protocol: 'HTTP' },
{ id: '2', protocol: 'HTTPS' },
{ id: '3', protocol: 'TCP' }];
var markup =
'<form>' +
'<input type="number" id="port" ng-model="port" validate-unique="ports">' +
'<input id="name" ng-model="name" validate-unique="names">' +
'<input id="protocol" ng-model="protocol" validate-unique="protocolIsUnique">' +
'</form>';
function protocolIsUnique(value) {
return !items.some(function(item) {
return item.protocol === value;
});
}
beforeEach(inject(function ($injector) {
var compile = $injector.get('$compile');
scope = $injector.get('$rootScope').$new();
// generate dom from markup
var element = compile(markup)(scope);
port = element.children('#port');
name = element.children('#name');
protocol = element.children('#protocol');
// set initial data
scope.ports = [80, 443];
scope.names = ['name1', 'name2'];
scope.protocolIsUnique = protocolIsUnique;
scope.$apply();
}));
it('should be initially empty', function () {
expect(port.val()).toEqual('');
expect(name.val()).toEqual('');
expect(protocol.val()).toEqual('');
expect(port.hasClass('ng-valid')).toBe(true);
expect(name.hasClass('ng-valid')).toBe(true);
expect(protocol.hasClass('ng-valid')).toBe(true);
});
it('should be invalid if values are not unique', function () {
scope.port = 80;
scope.name = 'name1';
scope.protocol = 'TCP';
scope.$apply();
expect(port.hasClass('ng-valid')).toBe(false);
expect(name.hasClass('ng-valid')).toBe(false);
expect(protocol.hasClass('ng-valid')).toBe(false);
});
it('should be valid if values are unique', function () {
scope.port = 81;
scope.name = 'name3';
scope.protocol = 'TERMINATED_HTTPS';
scope.$apply();
expect(port.hasClass('ng-valid')).toBe(true);
expect(name.hasClass('ng-valid')).toBe(true);
expect(protocol.hasClass('ng-valid')).toBe(true);
});
it('should be valid if param is undefined', function () {
delete scope.ports;
scope.port = 80;
scope.$apply();
expect(port.hasClass('ng-valid')).toBe(true);
});
it('should be valid if param is a string', function () {
scope.ports = '80';
scope.port = 80;
scope.$apply();
expect(port.hasClass('ng-valid')).toBe(true);
});
it('should be valid if param is a number', function () {
scope.ports = 80;
scope.port = 80;
scope.$apply();
expect(port.hasClass('ng-valid')).toBe(true);
});
it('should be valid if param is an object', function () {
scope.ports = { port: 80 };
scope.port = 80;
scope.$apply();
expect(port.hasClass('ng-valid')).toBe(true);
});
});
});
})();

View File

@ -1,52 +0,0 @@
/*
* Copyright 2015, Intel Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
angular
.module('horizon.framework.widgets.metadata.tree')
.directive('metadataTreeUnique', metadataTreeUnique);
/**
* @ngdoc directive
* @name metadataTreeUnique
* @restrict A
*
* @description
* The `metadataTreeUnique` helper directive provides validation
* for field which value should be unique new Item
*/
function metadataTreeUnique() {
var directive = {
restrict: 'A',
require: ['^metadataTree', 'ngModel'],
link: link
};
return directive;
function link(scope, elm, attrs, controllers) {
var metadataTree = controllers[0];
var ngModel = controllers[1];
ngModel.$validators.unique = function (modelValue, viewValue) {
return !metadataTree.tree.flatTree.some(function (item) {
return item.leaf && item.leaf.name === viewValue;
});
};
}
}
})();

View File

@ -36,6 +36,7 @@
ctrl.availableFilter = availableFilter;
ctrl.quickFilter = quickFilter;
ctrl.checkNameUnique = checkNameUnique;
ctrl.text = angular.extend({}, defaults.text, ctrl.text);
if (!ctrl.tree) {
ctrl.tree = new metadataTreeService.Tree(ctrl.available, ctrl.existing);
@ -71,6 +72,16 @@
}
return item.label.indexOf(text) > -1 || item.leaf.name.indexOf(text) > -1;
}
/**
* Function used by the validateUnique directive to validate that a custom key name
* is unique.
*/
function checkNameUnique(name) {
return !ctrl.tree.flatTree.some(function (item) {
return item.leaf && item.leaf.name === name;
});
}
}
})();

View File

@ -32,7 +32,7 @@
<span class="input-group-addon" ng-bind="::ctrl.text.custom"></span>
<input class="form-control" type="text" name="customItem"
ng-model="ctrl.customItem"
metadata-tree-unique/>
validate-unique="ctrl.checkNameUnique"/>
<span class="input-group-btn">
<button type='button' class="btn btn-primary"
ng-click="ctrl.tree.addCustom(ctrl.customItem); ctrl.customItem=''"

View File

@ -31,7 +31,6 @@
* |--------------------------------------------------------------------------------------------|
* | {@link horizon.framework.widgets.metadata.tree.directive:metadataTree `metadataTree`} |
* | {@link horizon.framework.widgets.metadata.tree.directive:metadataTreeItem `metadataTreeItem`} |
* | {@link horizon.framework.widgets.metadata.tree.directive:metadataTreeUnique `metadataTreeUnique`} |
* | {@link horizon.framework.widgets.metadata.tree.controller:MetadataTreeController `MetadataTreeController`} |
* | {@link horizon.framework.widgets.metadata.tree.controller:MetadataTreeItemController `MetadataTreeItemController`} |
*