Add ability to edit products in UI
This feature will allow vendor admins to edit a vendor's products. Change-Id: I4b6d3e576ef1b20c16845786555d51a86cbc746d
This commit is contained in:
parent
95e79460e7
commit
34829d30b6
@ -8,8 +8,15 @@
|
|||||||
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
||||||
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
||||||
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
||||||
<strong>Vendor Name:</strong> {{ctrl.vendor.name}}<br />
|
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
|
||||||
<strong>Vendor Description:</strong> {{ctrl.vendor.description || '-'}}<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>
|
</div>
|
||||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||||
|
@ -8,8 +8,15 @@
|
|||||||
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
<strong>Product ID:</strong> {{ctrl.id}}<br />
|
||||||
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
<strong>Description:</strong> {{ctrl.product.description}}<br />
|
||||||
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
|
||||||
<strong>Vendor Name:</strong> {{ctrl.vendor.name}}<br />
|
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
|
||||||
<strong>Vendor Description:</strong> {{ctrl.vendor.description || '-'}}<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>
|
</div>
|
||||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<div class="pull-right">
|
<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"
|
<a ng-if="ctrl.product.can_manage"
|
||||||
href="javascript:void(0)"
|
href="javascript:void(0)"
|
||||||
ng-click="ctrl.deleteProduct()"
|
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.addProductVersion = addProductVersion;
|
||||||
ctrl.unassociateTest = unassociateTest;
|
ctrl.unassociateTest = unassociateTest;
|
||||||
ctrl.openVersionModal = openVersionModal;
|
ctrl.openVersionModal = openVersionModal;
|
||||||
|
ctrl.openProductEditModal = openProductEditModal;
|
||||||
|
|
||||||
/** The product id extracted from the URL route. */
|
/** The product id extracted from the URL route. */
|
||||||
ctrl.id = $stateParams.id;
|
ctrl.id = $stateParams.id;
|
||||||
@ -79,7 +80,7 @@
|
|||||||
ctrl.productRequest = $http.get(content_url).success(
|
ctrl.productRequest = $http.get(content_url).success(
|
||||||
function(data) {
|
function(data) {
|
||||||
ctrl.product = data;
|
ctrl.product = data;
|
||||||
ctrl.product_properties =
|
ctrl.productProperties =
|
||||||
angular.fromJson(data.properties);
|
angular.fromJson(data.properties);
|
||||||
}
|
}
|
||||||
).error(function(error) {
|
).error(function(error) {
|
||||||
@ -196,7 +197,7 @@
|
|||||||
$http.put(url, {public: !ctrl.product.public}).success(
|
$http.put(url, {public: !ctrl.product.public}).success(
|
||||||
function (data) {
|
function (data) {
|
||||||
ctrl.product = data;
|
ctrl.product = data;
|
||||||
ctrl.product_properties = angular.fromJson(data.properties);
|
ctrl.productProperties = angular.fromJson(data.properties);
|
||||||
}).error(function (error) {
|
}).error(function (error) {
|
||||||
raiseAlert('danger', 'Error: ', error.detail);
|
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
|
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();
|
ctrl.openVersionModal();
|
||||||
expect(modal.open).toHaveBeenCalled();
|
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() {
|
describe('ProductVersionModalController', function() {
|
||||||
@ -1463,4 +1474,54 @@ describe('Refstack controllers', function () {
|
|||||||
$httpBackend.flush();
|
$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