Merge "Add ability to edit products in UI"
This commit is contained in:
commit
4b5ceaf0ed
@ -8,8 +8,15 @@
|
||||
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
||||
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
||||
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
||||
<strong>Vendor Name:</strong> {{ctrl.vendor.name}}<br />
|
||||
<strong>Vendor Description:</strong> {{ctrl.vendor.description || '-'}}<br />
|
||||
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
|
||||
<div ng-if="ctrl.productProperties">
|
||||
<strong>Properties:</strong>
|
||||
<ul>
|
||||
<li ng-repeat="(key, value) in ctrl.productProperties">
|
||||
<em>{{key}}</em>: {{value}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||
|
@ -8,8 +8,15 @@
|
||||
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
||||
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
||||
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
||||
<strong>Vendor Name:</strong> {{ctrl.vendor.name}}<br />
|
||||
<strong>Vendor Description:</strong> {{ctrl.vendor.description || '-'}}<br />
|
||||
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
|
||||
<div ng-if="ctrl.productProperties">
|
||||
<strong>Properties:</strong>
|
||||
<ul>
|
||||
<li ng-repeat="(key, value) in ctrl.productProperties">
|
||||
<em>{{key}}</em>: {{value}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||
|
@ -1,4 +1,9 @@
|
||||
<div class="pull-right">
|
||||
<a ng-ig="ctrl.product.can_manage"
|
||||
href="javascript:void(0)"
|
||||
ng-click="ctrl.openProductEditModal()">
|
||||
Edit
|
||||
</a><br />
|
||||
<a ng-if="ctrl.product.can_manage"
|
||||
href="javascript:void(0)"
|
||||
ng-click="ctrl.deleteProduct()"
|
||||
|
@ -0,0 +1,61 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">×</button>
|
||||
<h4>Edit Product</h4>
|
||||
<p>Make changes to your product.</p>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="name"
|
||||
ng-model="modal.product.name">
|
||||
<br />
|
||||
<label for="description">Description</label>
|
||||
<textarea type="text"
|
||||
class="form-control"
|
||||
id="description"
|
||||
ng-model="modal.product.description"
|
||||
rows="4"
|
||||
wrap="off">
|
||||
</textarea>
|
||||
<br />
|
||||
<label for="properties">Properties</label>
|
||||
<small><span class="text-muted glyphicon glyphicon-info-sign" title="Add arbitrary custom properties to your product."></span></small>
|
||||
<div class="row" ng-repeat="(index, prop) in modal.productProperties">
|
||||
<div class="col-md-2">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="prop.key">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="prop.value">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<a class="text-danger glyphicon glyphicon-remove"
|
||||
title="Delete this property?"
|
||||
ng-click="modal.removeProperty(index)"
|
||||
style='top:8px'></a>
|
||||
</div>
|
||||
</div>
|
||||
<div><small><a ng-click="modal.addField()"><span class="glyphicon glyphicon-plus"></span> Add new property</a></small></div>
|
||||
</div>
|
||||
<div ng-show="modal.showError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{modal.error}}
|
||||
</div>
|
||||
<div ng-show="modal.showSuccess" class="alert alert-success" role="success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<span class="sr-only">Success:</span>
|
||||
Changes saved successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.saveChanges()">Save Changes</button>
|
||||
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
|
||||
</div>
|
||||
</div>
|
@ -44,6 +44,7 @@
|
||||
ctrl.addProductVersion = addProductVersion;
|
||||
ctrl.unassociateTest = unassociateTest;
|
||||
ctrl.openVersionModal = openVersionModal;
|
||||
ctrl.openProductEditModal = openProductEditModal;
|
||||
|
||||
/** The product id extracted from the URL route. */
|
||||
ctrl.id = $stateParams.id;
|
||||
@ -79,7 +80,7 @@
|
||||
ctrl.productRequest = $http.get(content_url).success(
|
||||
function(data) {
|
||||
ctrl.product = data;
|
||||
ctrl.product_properties =
|
||||
ctrl.productProperties =
|
||||
angular.fromJson(data.properties);
|
||||
}
|
||||
).error(function(error) {
|
||||
@ -196,7 +197,7 @@
|
||||
$http.put(url, {public: !ctrl.product.public}).success(
|
||||
function (data) {
|
||||
ctrl.product = data;
|
||||
ctrl.product_properties = angular.fromJson(data.properties);
|
||||
ctrl.productProperties = angular.fromJson(data.properties);
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', 'Error: ', error.detail);
|
||||
});
|
||||
@ -292,6 +293,28 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will allow product details
|
||||
* to be edited.
|
||||
*/
|
||||
function openProductEditModal() {
|
||||
$uibModal.open({
|
||||
templateUrl: '/components/products/partials' +
|
||||
'/productEditModal.html',
|
||||
backdrop: true,
|
||||
windowClass: 'modal',
|
||||
animation: true,
|
||||
controller: 'ProductEditModalController as modal',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
product: function () {
|
||||
return ctrl.product;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
@ -354,4 +377,107 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular
|
||||
.module('refstackApp')
|
||||
.controller('ProductEditModalController', ProductEditModalController);
|
||||
|
||||
ProductEditModalController.$inject = [
|
||||
'$uibModalInstance', '$http', '$state', 'product', 'refstackApiUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Product Edit Modal Controller
|
||||
* This controls the modal that allows editing a product.
|
||||
*/
|
||||
function ProductEditModalController($uibModalInstance, $http,
|
||||
$state, product, refstackApiUrl) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.close = close;
|
||||
ctrl.addField = addField;
|
||||
ctrl.saveChanges = saveChanges;
|
||||
ctrl.removeProperty = removeProperty;
|
||||
|
||||
ctrl.product = product;
|
||||
ctrl.productName = product.name;
|
||||
ctrl.productProperties = [];
|
||||
|
||||
parseProductProperties();
|
||||
|
||||
/**
|
||||
* Close the product edit modal.
|
||||
*/
|
||||
function close() {
|
||||
$uibModalInstance.dismiss('exit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a blank property key-value pair into the productProperties
|
||||
* array. This will spawn new input boxes.
|
||||
*/
|
||||
function addField() {
|
||||
ctrl.productProperties.push({'key': '', 'value': ''});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the server with the changes.
|
||||
*/
|
||||
function saveChanges() {
|
||||
ctrl.showError = false;
|
||||
ctrl.showSuccess = false;
|
||||
var url = [refstackApiUrl, '/products/', ctrl.product.id].join('');
|
||||
var properties = propertiesToJson();
|
||||
var content = {'description': ctrl.product.description,
|
||||
'properties': properties};
|
||||
if (ctrl.productName != ctrl.product.name) {
|
||||
content.name = ctrl.product.name;
|
||||
}
|
||||
$http.put(url, content).success(function() {
|
||||
ctrl.showSuccess = true;
|
||||
$state.reload();
|
||||
}).error(function(error) {
|
||||
ctrl.showError = true;
|
||||
ctrl.error = error.detail;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property from the productProperties array at the given
|
||||
* index.
|
||||
*/
|
||||
function removeProperty(index) {
|
||||
ctrl.productProperties.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the product properties and put them in a format more suitable
|
||||
* for forms.
|
||||
*/
|
||||
function parseProductProperties() {
|
||||
var props = angular.fromJson(ctrl.product.properties);
|
||||
angular.forEach(props, function(value, key) {
|
||||
ctrl.productProperties.push({'key': key, 'value': value});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list of property objects to a dict containing the
|
||||
* each key-value pair.
|
||||
*/
|
||||
function propertiesToJson() {
|
||||
if (!ctrl.productProperties.length) {
|
||||
return null;
|
||||
}
|
||||
var properties = {};
|
||||
for (var i = 0, len = ctrl.productProperties.length; i < len; i++) {
|
||||
var prop = ctrl.productProperties[i];
|
||||
if (prop.key && prop.value) {
|
||||
properties[prop.key] = prop.value;
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -1423,6 +1423,17 @@ describe('Refstack controllers', function () {
|
||||
ctrl.openVersionModal();
|
||||
expect(modal.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have a method to open a modal for product editing',
|
||||
function () {
|
||||
var modal;
|
||||
inject(function ($uibModal) {
|
||||
modal = $uibModal;
|
||||
});
|
||||
spyOn(modal, 'open');
|
||||
ctrl.openProductEditModal();
|
||||
expect(modal.open).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProductVersionModalController', function() {
|
||||
@ -1463,4 +1474,54 @@ describe('Refstack controllers', function () {
|
||||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProductEditModalController', function() {
|
||||
var ctrl, modalInstance, state;
|
||||
var fakeProduct = {'name': 'Foo', 'description': 'Bar', 'id': '1234',
|
||||
'properties': {'key1': 'value1'}};
|
||||
|
||||
beforeEach(inject(function ($controller) {
|
||||
modalInstance = {
|
||||
dismiss: jasmine.createSpy('modalInstance.dismiss')
|
||||
};
|
||||
state = {
|
||||
reload: jasmine.createSpy('state.reload')
|
||||
};
|
||||
ctrl = $controller('ProductEditModalController',
|
||||
{$uibModalInstance: modalInstance, $state: state,
|
||||
product: fakeProduct}
|
||||
);
|
||||
}));
|
||||
|
||||
it('should be able to add/remove properties',
|
||||
function () {
|
||||
var expected = [{'key': 'key1', 'value': 'value1'}];
|
||||
expect(ctrl.productProperties).toEqual(expected);
|
||||
ctrl.removeProperty(0);
|
||||
expect(ctrl.productProperties).toEqual([]);
|
||||
ctrl.addField();
|
||||
expected = [{'key': '', 'value': ''}];
|
||||
expect(ctrl.productProperties).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should have a function to save changes',
|
||||
function () {
|
||||
var expectedContent = {
|
||||
'name': 'Foo1', 'description': 'Bar',
|
||||
'properties': {'key1': 'value1'}
|
||||
};
|
||||
$httpBackend.expectPUT(
|
||||
fakeApiUrl + '/products/1234', expectedContent)
|
||||
.respond(200, '');
|
||||
ctrl.product.name = 'Foo1';
|
||||
ctrl.saveChanges();
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should have a function to exit the modal',
|
||||
function () {
|
||||
ctrl.close();
|
||||
expect(modalInstance.dismiss).toHaveBeenCalledWith('exit');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user