Merge "Add a Delete Modal Service for deleting entities"
This commit is contained in:
commit
8b4fc07267
171
horizon/static/framework/widgets/modal/delete-modal.service.js
Normal file
171
horizon/static/framework/widgets/modal/delete-modal.service.js
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use self 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.modal')
|
||||
.factory('horizon.framework.widgets.modal.deleteModalService', deleteModalService);
|
||||
|
||||
deleteModalService.$inject = [
|
||||
'$q',
|
||||
'horizon.framework.widgets.modal.simple-modal.service',
|
||||
'horizon.framework.widgets.toast.service'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngDoc factory
|
||||
* @name horizon.framework.widgets.modal.deleteModalService
|
||||
*
|
||||
* @Description
|
||||
* Brings up the delete confirmation modal dialog.
|
||||
* This provides a reusable modal service for deleting
|
||||
* entities. It can be used for deleting single or multiple
|
||||
* objects.
|
||||
*
|
||||
* It requires that the backing API Service allow for
|
||||
* suppressing of errors so that success and error messages
|
||||
* are shown only once when working with multiple objects.
|
||||
*
|
||||
* On submit, call the given deleteEntity method
|
||||
* and then raise the event.
|
||||
* On cancel, do nothing.
|
||||
*/
|
||||
function deleteModalService($q, simpleModalService, toast) {
|
||||
var service = {
|
||||
open: open
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
//////////////
|
||||
|
||||
/**
|
||||
* @ngDoc method
|
||||
* @name horizon.framework.widgets.modal.deleteModalService.open
|
||||
*
|
||||
* @Description
|
||||
* Brings up the delete confirmation modal dialog for the given
|
||||
* set of entities.
|
||||
*
|
||||
* @param {Object} entities
|
||||
* The Entities that are to be deleted.
|
||||
* Each entity MUST have an ID field. They
|
||||
* could also have a name field which if present is
|
||||
* used in the confirmation display. If a name
|
||||
* is not provided, the ID will be displayed.
|
||||
*
|
||||
* @param {function} context.deleteEntity
|
||||
* The function that should be called to delete each entity.
|
||||
* The first argument is the id of the Entity to delete.
|
||||
* Note: This callback might need to supressErrors on the alert
|
||||
* service.
|
||||
*
|
||||
* @param {string} context.successEvent
|
||||
* The name of the event to emit for the entities that have been deleted successfully.
|
||||
* @param {string} context.failedEvent
|
||||
* The name of the event to emit when the entities that failed to delete successfully.
|
||||
*
|
||||
* On submit, delete given entities.
|
||||
* On cancel, do nothing.
|
||||
*/
|
||||
function open(scope, entities, context) {
|
||||
var options = {
|
||||
title: context.labels.title,
|
||||
body: interpolate(context.labels.message, [entities.map(getName).join("\", \"")]),
|
||||
submit: context.labels.submit
|
||||
};
|
||||
|
||||
simpleModalService.modal(options).result.then(onModalSubmit);
|
||||
|
||||
function onModalSubmit() {
|
||||
resolveAll(entities.map(deleteEntityPromise)).then(notify);
|
||||
}
|
||||
|
||||
function deleteEntityPromise(entity) {
|
||||
return {promise: context.deleteEntity(entity.id), entity: entity};
|
||||
}
|
||||
|
||||
function notify(result) {
|
||||
if (result.pass.length > 0) {
|
||||
scope.$emit(context.successEvent, result.pass.map(getId));
|
||||
toast.add('success', getMessage(context.labels.success, result.pass));
|
||||
}
|
||||
|
||||
if (result.fail.length > 0) {
|
||||
scope.$emit(context.failedEvent, result.fail.map(getId));
|
||||
toast.add('error', getMessage(context.labels.error, result.fail));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the displayed message
|
||||
*/
|
||||
function getMessage(message, entities) {
|
||||
return interpolate(message, [entities.map(getName).join(", ")]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the name of the entity
|
||||
*/
|
||||
function getName(entity) {
|
||||
return entity.name || entity.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the id of the entity
|
||||
*/
|
||||
function getId(entity) {
|
||||
return entity.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all promises.
|
||||
* It asks the backing API Service to suppress errors
|
||||
* and collect all entities to display one
|
||||
* success and one error message.
|
||||
*/
|
||||
function resolveAll(promiseList) {
|
||||
var deferred = $q.defer();
|
||||
var passList = [];
|
||||
var failList = [];
|
||||
var promises = promiseList.map(resolveSingle);
|
||||
|
||||
$q.all(promises).then(onComplete);
|
||||
return deferred.promise;
|
||||
|
||||
function resolveSingle(singlePromise) {
|
||||
var deferredInner = $q.defer();
|
||||
singlePromise.promise.then(success, error);
|
||||
return deferredInner.promise;
|
||||
|
||||
function success() {
|
||||
passList.push(singlePromise.entity);
|
||||
deferredInner.resolve();
|
||||
}
|
||||
|
||||
function error() {
|
||||
failList.push(singlePromise.entity);
|
||||
deferredInner.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function onComplete() {
|
||||
deferred.resolve({pass: passList, fail: failList});
|
||||
}
|
||||
}
|
||||
} // end of batchDeleteService
|
||||
})(); // end of IIFE
|
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use self 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';
|
||||
|
||||
describe('horizon.framework.widgets.modal.deleteModalService', function() {
|
||||
var labels = {
|
||||
title: gettext('Confirm Delete Foobars'),
|
||||
message: gettext('selected "%s"'),
|
||||
submit: gettext('Delete'),
|
||||
success: gettext('Deleted : %s.'),
|
||||
error: gettext('Unable to delete: %s.')
|
||||
};
|
||||
|
||||
var entityAPI = {
|
||||
deleteEntity: function(entityId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (entityId === 'bad') {
|
||||
deferred.reject();
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
var simpleModalService = {
|
||||
modal: function () {
|
||||
return {
|
||||
result: {
|
||||
then: function (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var toastService = {
|
||||
add: function() {}
|
||||
};
|
||||
|
||||
var $scope, $q, service, events;
|
||||
|
||||
function getContext() {
|
||||
return {
|
||||
labels: labels,
|
||||
deleteEntity: entityAPI.deleteEntity,
|
||||
successEvent: 'custom_delete_event_passed',
|
||||
failedEvent: 'custom_delete_event_failed'
|
||||
};
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
beforeEach(module('horizon.framework.util'));
|
||||
beforeEach(module('horizon.framework.widgets'));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('horizon.framework.widgets.modal.simple-modal.service', simpleModalService);
|
||||
$provide.value('horizon.framework.widgets.toast.service', toastService);
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector, _$rootScope_) {
|
||||
$scope = _$rootScope_.$new();
|
||||
$q = $injector.get('$q');
|
||||
service = $injector.get('horizon.framework.widgets.modal.deleteModalService');
|
||||
}));
|
||||
|
||||
it('should open the modal with correct message', function() {
|
||||
var fakeModalService = {
|
||||
result: {
|
||||
then: function (callback) {}
|
||||
}
|
||||
};
|
||||
|
||||
var entities = [
|
||||
{name: 'entity1', id: '1'},
|
||||
{id: '2'}
|
||||
];
|
||||
|
||||
spyOn(simpleModalService, 'modal').and.returnValue(fakeModalService);
|
||||
|
||||
service.open($scope, entities, getContext());
|
||||
|
||||
expect(simpleModalService.modal).toHaveBeenCalled();
|
||||
|
||||
var args = simpleModalService.modal.calls.argsFor(0)[0];
|
||||
expect(args.body).toEqual('selected "entity1", "2"');
|
||||
});
|
||||
|
||||
it('should call entityAPI to delete entities and raise events', function() {
|
||||
spyOn(toastService, 'add').and.callThrough();
|
||||
spyOn($scope, '$emit').and.callThrough();
|
||||
spyOn(entityAPI, 'deleteEntity').and.callThrough();
|
||||
|
||||
var entities = [
|
||||
{name: 'entity1', id: '1'},
|
||||
{name: 'entity2', id: '2'}
|
||||
];
|
||||
|
||||
service.open($scope, entities, getContext());
|
||||
|
||||
$scope.$apply();
|
||||
|
||||
expect(entityAPI.deleteEntity).toHaveBeenCalledWith('1');
|
||||
expect(entityAPI.deleteEntity).toHaveBeenCalledWith('2');
|
||||
expect(toastService.add).toHaveBeenCalledWith('success', 'Deleted : entity1, entity2.');
|
||||
expect($scope.$emit).toHaveBeenCalledWith('custom_delete_event_passed', [ '1', '2' ]);
|
||||
});
|
||||
|
||||
it('should raise failed events if Entity is not deleted', function() {
|
||||
spyOn(toastService, 'add').and.callThrough();
|
||||
spyOn($scope, '$emit').and.callThrough();
|
||||
spyOn(entityAPI, 'deleteEntity').and.callThrough();
|
||||
|
||||
var entities = [{name: 'entity1', id: 'bad'}];
|
||||
|
||||
service.open($scope, entities, getContext());
|
||||
|
||||
$scope.$apply();
|
||||
|
||||
expect(entityAPI.deleteEntity).toHaveBeenCalledWith('bad');
|
||||
expect(toastService.add).toHaveBeenCalledWith('error', 'Unable to delete: entity1.');
|
||||
expect($scope.$emit).toHaveBeenCalledWith('custom_delete_event_failed', ['bad']);
|
||||
});
|
||||
|
||||
it('should raise passed and failed events only for deleted entities', function() {
|
||||
spyOn(toastService, 'add').and.callThrough();
|
||||
spyOn(entityAPI, 'deleteEntity').and.callThrough();
|
||||
spyOn($scope, '$emit').and.callThrough();
|
||||
|
||||
var entities = [
|
||||
{name: 'bad_entity', id: 'bad'},
|
||||
{name: 'entity2', id: '1'}
|
||||
];
|
||||
|
||||
service.open($scope, entities, getContext());
|
||||
|
||||
$scope.$apply();
|
||||
|
||||
expect(entityAPI.deleteEntity).toHaveBeenCalledWith('bad');
|
||||
expect(entityAPI.deleteEntity).toHaveBeenCalledWith('1');
|
||||
expect(toastService.add).toHaveBeenCalledWith('success', 'Deleted : entity2.');
|
||||
expect(toastService.add).toHaveBeenCalledWith('error', 'Unable to delete: bad_entity.');
|
||||
expect($scope.$emit).toHaveBeenCalledWith('custom_delete_event_passed', ['1']);
|
||||
expect($scope.$emit).toHaveBeenCalledWith('custom_delete_event_failed', ['bad']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
Loading…
Reference in New Issue
Block a user