Add property-collection-editor directive

Refactor the current code to create a reusable component for editing the
contents of property collections. The new component has replaced code
in the existing port create/edit views, and will do the same for the
node views.

Change-Id: Iea81609450acc6a72ab3cbe6be070f9b845aaa9b
This commit is contained in:
Peter Piela 2017-07-06 12:51:36 -04:00
parent 9c9a298aa8
commit 4ead23c007
8 changed files with 315 additions and 78 deletions

View File

@ -29,9 +29,9 @@
'horizon.dashboard.admin.ironic.validDatapathIdPattern',
'horizon.dashboard.admin.ironic.form-field.service',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.property-collection.service',
'ctrl',
'node'
];
'node'];
/**
* @description Utility class used to manage local-link-connection
@ -141,13 +141,9 @@
validDatapathIdPattern,
formFieldService,
ironic,
propertyCollectionService,
ctrl,
node) {
ctrl.port = {
extra: {},
node_uuid: node.uuid
};
ctrl.address = new formFieldService.FormField({
id: "macAddress",
title: gettext("MAC address"),
@ -186,7 +182,8 @@
var field = ctrl.portgroup_uuid;
if (portgroups.length > 0) {
field.portgroups.push({uuid: null, name: gettext("Select a portgroup")});
field.portgroups.push({uuid: null,
name: gettext("Select a portgroup")});
}
field.portgroups = field.portgroups.concat(portgroups);
@ -195,6 +192,13 @@
}
});
ctrl.extra = new propertyCollectionService.PropertyCollection({
id: 'extra',
title: gettext('Extras'),
addPropertyLabel: gettext('Add Extra'),
placeholder: gettext('Property Name')
});
/**
* Cancel the modal
*
@ -203,26 +207,5 @@
ctrl.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
/**
* Delete a port metadata property
*
* @param {string} propertyName - Name of the property
* @return {void}
*/
ctrl.deleteExtra = function(propertyName) {
delete ctrl.port.extra[propertyName];
};
/**
* Check whether the specified port metadata property already exists
*
* @param {string} propertyName - Name of the metadata property
* @return {boolean} True if the property already exists,
* otherwise false
*/
ctrl.checkExtraUnique = function(propertyName) {
return !(propertyName in ctrl.port.extra);
};
}
})();

View File

@ -26,49 +26,7 @@
</div>
</form>
<form id="AddExtraForm" name="AddExtraForm" style="margin-bottom:10px;">
<label for="extras" class="control-label" translate>Extras</label>
<div class="input-group input-group-sm">
<span class="input-group-addon"
style="width:25%;text-align:right">
Add Extra:</span>
<input class="form-control"
type="text"
ng-model="extraName"
validate-unique="ctrl.checkExtraUnique"
placeholder="{$ ::'Property Name' | translate $}"/>
<span class="input-group-btn">
<button class="btn btn-primary"
type="button"
ng-disabled="!extraName || AddExtraForm.$invalid"
ng-click="ctrl.port.extra[extraName] = null;
extraName = null">
<span class="fa fa-plus"> </span>
</button>
</span>
</div>
</form>
<form class="form-horizontal" id="ExtraForm" name="ExtraForm">
<div class="input-group input-group-sm"
ng-repeat="(propertyName, propertyValue) in ctrl.port.extra">
<span class="input-group-addon"
style="width:25%;text-align:right">
{$ propertyName $}
</span>
<input class="form-control"
type="text"
name="{$ propertyName $}"
ng-model="ctrl.port.extra[propertyName]"
ng-required="true"/>
<div class="input-group-btn">
<a class="btn btn-default"
ng-click="ctrl.deleteExtra(propertyName)">
<span class="fa fa-minus"> </span>
</a>
</div>
</div>
</form>
<property-collection-editor collection="ctrl.extra"></property-collection-editor>
</div>
<!--modal footer-->
<div class="modal-footer ng-scope">
@ -79,7 +37,7 @@
<button type="submit"
ng-disabled="CreatePortForm.$invalid ||
LocalLinkConnectionForm.$invalid ||
ExtraForm.$invalid"
!ctrl.extra.complete()"
ng-click="ctrl.submit()"
class="btn btn-primary">
{$ ::ctrl.submitButtonTitle $}

View File

@ -54,9 +54,11 @@
* @return {void}
*/
ctrl.createPort = function() {
var port = angular.copy(ctrl.port);
port.address = ctrl.address.value;
var port = {
extra: ctrl.extra.properties,
node_uuid: node.id,
address: ctrl.address.value
};
var attr = ctrl.localLinkConnection.toPortAttr();
if (attr) {

View File

@ -89,7 +89,7 @@
UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG);
}
ctrl.port.extra = angular.copy(port.extra);
ctrl.extra.properties = angular.copy(port.extra);
/**
* Apply updates to the port being edited
@ -108,7 +108,7 @@
patcher.buildPatch(port.local_link_connection,
ctrl.localLinkConnection.toPortAttr(),
"/local_link_connection");
patcher.buildPatch(port.extra, ctrl.port.extra, "/extra");
patcher.buildPatch(port.extra, ctrl.extra.properties, "/extra");
patcher.buildPatch(port.portgroup_uuid,
ctrl.portgroup_uuid.value,
"/portgroup_uuid");

View File

@ -0,0 +1,36 @@
/*
* Copyright 2017 Cray Inc.
*
* 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.dashboard.admin.ironic')
.directive('propertyCollectionEditor', PropertyCollectionEditor);
PropertyCollectionEditor.$inject = [
'horizon.dashboard.admin.ironic.basePath'
];
function PropertyCollectionEditor(basePath) {
return {
restrict: 'E',
scope: {
collection: '='
},
templateUrl: basePath + '/property-collection-editor.html'
};
}
})();

View File

@ -0,0 +1,53 @@
<div>
<!-- Add property to collection -->
<form id="add_{$ collection.id $}_form"
name="add_{$ collection.id $}_form"
style="margin-bottom:10px;">
<label for="add_{$ collection.id $}_input"
class="control-label">{$ collection.title $}</label>
<div class="input-group input-group-sm">
<span class="input-group-addon"
style="width:25%;text-align:right">
{$ collection.addPropertyLabel $}:</span>
<input id="add_{$ collection.id $}_input"
class="form-control"
type="text"
ng-model="newPropertyName"
validate-unique="collection.checkPropertyUnique"
placeholder="{$ collection.placeholder $}"/>
<span class="input-group-btn">
<button class="btn btn-primary"
type="button"
ng-disabled="!newPropertyName ||
add_{$ collection.id $}_form.$invalid"
ng-click="collection.addProperty(newPropertyName);
newPropertyName = null">
<span class="fa fa-plus"> </span>
</button>
</span>
</div>
</form>
<!-- Property list -->
<form class="form-horizontal"
id="{$ collection.id $}_form"
name="{$ collection.id $}_form">
<div class="input-group input-group-sm"
ng-repeat="(propertyName, propertyValue) in collection.properties">
<span class="input-group-addon"
style="width:25%;text-align:right">
{$ propertyName $}
</span>
<input class="form-control"
type="text"
name="{$ propertyName $}"
ng-model="collection.properties[propertyName]"
ng-required="true"/>
<div class="input-group-btn">
<a class="btn btn-default"
ng-click="collection.deleteProperty(propertyName)">
<span class="fa fa-minus"> </span>
</a>
</div>
</div>
</form>
</div>

View File

@ -0,0 +1,105 @@
/*
* Copyright 2017 Cray Inc.
*
* 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.dashboard.admin.ironic')
.factory('horizon.dashboard.admin.ironic.property-collection.service',
propertyCollectionService);
function propertyCollectionService() {
var service = {
PropertyCollection: PropertyCollection
};
/**
* @description Utility class for managing property collections.
* Used is association with the property-collection-editor directive.
*
* @param {object} args - Base properties are:
* id [string] - Unique id used to create DOM element ids, and
* internal variable names
* title [string] - Label used to identify the collection to the user
* addPropertyLabel [string] - Label used to prompt the user to add a new
* property
* placeholder [string] - Placeholder for text input field
* properties [object] - Dictionary of property values indexed by
* property name
*
* @return {void}
*/
function PropertyCollection(args) {
var collection = this;
collection.id = undefined;
collection.title = undefined;
collection.addPropertyLabel = undefined;
collection.placeholder = undefined;
collection.properties = {};
angular.forEach(args, function(value, arg) {
collection[arg] = value;
});
/**
* @description Test whether this collection contains a property.
*
* @param {string} propertyName - Property name.
* @return {boolean} True if the property already exists, false otherwise.
*/
this.checkPropertyUnique = function(propertyName) {
return !(propertyName in collection.properties);
};
/**
* @description Add a property to the collection.
*
* @param {string} propertyName - Property name.
* @return {void}
*/
this.addProperty = function(propertyName) {
this.properties[propertyName] = null;
};
/**
* @description Delete a specified property.
*
* @param {string} propertyName - Property name.
* @return {void}
*/
this.deleteProperty = function(propertyName) {
delete collection.properties[propertyName];
};
/**
* @description Test whether this collection is in a complete state.
* Complete is defined as all properties having a non-null value.
*
* @return {boolean} True if the collection is complete, false otherwise.
*/
this.complete = function() {
for (var propertyName in this.properties) {
if (this.properties.hasOwnProperty(propertyName) &&
this.properties[propertyName] === null) {
return false;
}
}
return true;
};
}
return service;
}
})();

View File

@ -0,0 +1,100 @@
/**
* Copyright 2017 Cray Inc
*
* 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";
/**
* @description Unit tests for the form-field service
*/
describe(
'horizon.dashboard.admin.ironic.property-collection.service',
function() {
var propertyCollectionService;
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(inject(function($injector) {
propertyCollectionService =
$injector.get('horizon.dashboard.admin.ironic.property-collection.service');
}));
it('defines the form-field service', function() {
expect(propertyCollectionService).toBeDefined();
});
it('PropertyCollection - default construction', function() {
var collection = new propertyCollectionService.PropertyCollection({});
expect(collection.id).toBeUndefined();
expect(collection.title).toBeUndefined();
expect(collection.addPropertyLabel).toBeUndefined();
expect(collection.placeholder).toBeUndefined();
expect(collection.properties).toEqual({});
expect(collection.checkPropertyUnique).toBeDefined();
expect(collection.addProperty).toBeDefined();
expect(collection.deleteProperty).toBeDefined();
expect(collection.complete).toBeDefined();
});
it('PropertyCollection - local parameters', function() {
var args = {id: 'id',
title: 'title',
placeholder: 'placeholder',
properties: {'prop-1': 'prop1-val',
'prop-2': 'prop2-val'}
};
var collection = new propertyCollectionService.PropertyCollection(args);
for (var arg in args) {
if (args.hasOwnProperty(arg)) {
expect(collection[arg]).toBeDefined();
expect(collection[arg]).toEqual(args[arg]);
}
}
});
it('checkPropertyUnique', function() {
var collection = new propertyCollectionService.PropertyCollection({});
expect(collection.checkPropertyUnique('foo')).toBe(true);
collection.addProperty('foo');
expect(collection.checkPropertyUnique('foo')).toBe(false);
});
it('addProperty', function() {
var collection = new propertyCollectionService.PropertyCollection({});
collection.addProperty('foo');
expect(collection.properties.foo).toBeDefined();
expect(collection.properties.foo).toBe(null);
});
it('deleteProperty', function() {
var collection = new propertyCollectionService.PropertyCollection({});
var original = angular.copy(collection);
collection.addProperty('foo');
collection.deleteProperty('foo');
expect(collection).toEqual(original);
});
it('complete', function() {
var collection = new propertyCollectionService.PropertyCollection({});
expect(collection.complete()).toBe(true);
collection.addProperty('foo');
expect(collection.complete()).toBe(false);
});
});
})();