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…
x
Reference in New Issue
Block a user