Merge "Metadata widget multivalue selection support"

This commit is contained in:
Jenkins
2015-04-04 03:32:51 +00:00
committed by Gerrit Code Review
6 changed files with 191 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
<div class="metadata-tree-item" ng-form="itemForm">
<div class="input-group input-group-sm"
ng-switch on="item.leaf.type"
ng-if="item.leaf.type !== 'array'"
ng-class="{'has-error': itemForm.property.$invalid && itemForm.property.$dirty}">
<span class="input-group-addon"
title="{$ ::item.leaf.name $}"
@@ -26,14 +27,6 @@
ng-options="op for op in item.leaf.enum"
ng-disabled="item.leaf.readonly">
</select>
<select ng-switch-when="array"
name="property"
class="form-control"
required
ng-model="item.leaf.value"
ng-options="op for op in item.leaf.items.enum"
ng-disabled="item.leaf.readonly">
</select>
<input ng-switch-when="integer"
name="property"
type="number"
@@ -68,6 +61,50 @@
</a>
</div>
</div>
<div ng-if="item.leaf.type === 'array'"
class="panel panel-default multiselect">
<div class="panel-heading">
<div ng-click="open()">
<span class="fa fa-fw"
ng-class="item.leaf.readonly ? '' : opened ? 'fa-chevron-down' : 'fa-chevron-right'">
</span>
<span ng-bind="::item.leaf.name"></span>
<div class="text-right">
<a class="btn btn-xs btn-default" ng-click="action()">
<i class="fa fa-minus"></i>
</a>
</div>
</div>
</div>
<div class="panel-body values"
ng-class="{disabled: item.leaf.readonly}">
<span ng-repeat="val in item.leaf.value">
<span class="label label-default"
ng-click="remove(val)">
<span ng-bind="::val"></span>
<span class="fa fa-times" ng-if="!item.leaf.readonly"></span>
</span>
</span>
</div>
<ul class="list-group options" ng-show="opened">
<li class="list-group-item"
ng-repeat="val in values"
ng-click="add(val)">
<span ng-bind="::val"></span>
</li>
</ul>
<div class="panel-footer" ng-show="opened">
<div class="form-inline clearfix">
<div class="form-group pull-right">
<label>Operator</label>
<select class="form-control input-sm"
ng-model="item.leaf.operator"
ng-options="val for val in item.leaf.operators">
</select>
</div>
</div>
</div>
</div>
<div class="label label-info">
<span ng-bind="::item.breadcrumb()"></span>
</div>

View File

@@ -49,6 +49,8 @@
* @property {string} default Property default value
* @property {string} type Property type
* @property {boolean} readonly Property readonly state
* @property {string[]} operators Property available operators when type='array'
* @property {string} operator Property operator when type='array'
*/
function Property(name, json) {
this.name = name;
@@ -58,7 +60,9 @@
this.default = null;
this.type = 'string';
this.readonly = false;
this.operators = ['<in>'];
angular.extend(this, json);
this.operator = this.operators[0];
this.setValue(this.default);
}
@@ -69,14 +73,19 @@
*/
Property.prototype.setValue = function(value) {
if(value === null) {
this.value = null;
this.value = this.type !== 'array' ? null : [];
return;
}
switch (this.type) {
case 'integer': this.value = parseInt(value); break;
case 'number': this.value = parseFloat(value); break;
case 'array': this.value = value.replace(/^<in> /, ''); break;
case 'array':
var data = /^(<.*?>) (.*)$/.exec(value);
if(data) {
this.operator = data[1];
this.value = data[2].split(',');
} break;
case 'boolean': this.value = parseBool(value); break;
default: this.value = value;
}
@@ -89,7 +98,7 @@
*/
Property.prototype.getValue = function() {
switch (this.type) {
case 'array': return '<in> ' + this.value;
case 'array': return this.operator + ' ' + this.value.join(',');
default: return this.value;
}
};

View File

@@ -25,7 +25,7 @@
</div>
</div>
</div>
<ul class="list-group" ng-cloak>
<ul class="list-group metadata-list-group" ng-cloak>
<li class="list-group-item" ng-form="customItemForm">
<div class="input-group input-group-sm"
ng-class="{'has-error': customItemForm.$invalid && customItemForm.$dirty}">
@@ -96,7 +96,7 @@
</div>
</div>
</div>
<ul class="list-group" ng-cloak>
<ul class="list-group metadata-list-group" ng-cloak>
<li ng-repeat="item in existingList = (tree.flatTree | filter:{$:filterText.existing, added:true, leaf:'!null'} | orderBy:'leaf.name')"
ng-class="{'active': tree.selected===item}"
ng-class-odd="'dark-stripe'"

View File

@@ -183,6 +183,46 @@
return $scope.text.required;
}
};
function remove(array, value) {
var index = array.indexOf(value);
if (index > -1) {
array.splice(index, 1);
}
return array;
}
$scope.opened = false;
if($scope.item.leaf.type === 'array') {
$scope.values = $scope.item.leaf.items.enum.filter(function(i) {
return $scope.item.leaf.value.indexOf(i) < 0;
}).sort();
if(!$scope.item.leaf.readonly) {
$scope.add = function (val) {
$scope.item.leaf.value.push(val);
$scope.item.leaf.value.sort();
remove($scope.values, val);
};
$scope.remove = function (val) {
remove($scope.item.leaf.value, val);
$scope.values.push(val);
$scope.values.sort();
if ($scope.item.leaf.value.length === 0) {
$scope.opened = true;
}
};
$scope.open = function () {
$scope.opened = !$scope.opened;
};
$scope.opened = $scope.item.leaf.value.length === 0;
}
}
}
]);

View File

@@ -1,7 +1,7 @@
.metadata-tree {
min-height: 200px;
.panel .list-group {
.metadata-list-group {
height: 400px;
overflow: auto;
}
@@ -61,6 +61,56 @@
text-align: right;
}
.multiselect {
margin: 0;
color: $text-color;
.panel-heading {
padding: 4px;
&>* {
display: table;
width: 100%;
&>* {
display: table-cell;
vertical-align: middle;
}
}
}
.panel-body {
padding: 3px 5px 5px;
}
.panel-footer {
padding: 4px;
}
.values .label {
display: inline-block;
text-transform: uppercase;
}
.name {
vertical-align: text-top;
}
.options {
max-height: 180px;
overflow-y: auto;
&>*:last-child {
border-bottom: none;
}
}
.disabled {
cursor: not-allowed;
background-color: $input-bg-disabled;
}
}
.label-info {
display: none;
position: absolute;

View File

@@ -49,9 +49,10 @@ describe('hz.widget.metadata-tree module', function() {
"items": {
"type": "string",
"enum": [
"val-1", "val-2", "val-3"
"val-1", "val-2", "val-3", "val-4"
]
}
},
"default": "<in> val-2,val-3"
}
}
},
@@ -146,5 +147,43 @@ describe('hz.widget.metadata-tree module', function() {
expect($element.find('ul.list-group:last li[ng-repeat].active').scope().item.leaf.name).toBe('custom');
});
});
describe('hzMetadataTreeItem directive', function() {
var $scope, $element, item;
beforeEach(inject(function ($injector) {
var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
item = new ($injector.get('metadataTreeService').Item)();
$scope.item = item.fromProperty('test', namespaces[0].properties['test:A:6']);
var markup =
'<hz-metadata-tree-item item="item" text="text" action=""></hz-metadata-tree-item>';
$element = angular.element(markup);
$compile($element)($scope);
$scope.$digest();
}));
it('should have additional methods for array ', function () {
expect($element.isolateScope().opened).toBe(false);
expect($element.isolateScope().add).toBeDefined();
expect($element.isolateScope().remove).toBeDefined();
expect($element.isolateScope().open).toBeDefined();
});
it('should add values to array ', function () {
$element.find('.options li:last').trigger('click');
expect(item.leaf.getValue()).toBe('<in> val-2,val-3,val-4');
$element.find('.options li:first').trigger('click');
expect(item.leaf.getValue()).toBe('<in> val-1,val-2,val-3,val-4');
});
it('should remove value from array ', function () {
$element.find('.values .label:first').trigger('click');
expect(item.leaf.getValue()).toBe('<in> val-3');
});
});
});
});