Replaces the delete button with a disabling version
Replaces the default delete button template for the images batch delete action with a custom one. This template contains a new component, which changes the disabled/enabled state of the underlying action based on the selected images allowed value as specified by the delete-image service used to evaluate the allowed value for individual rows/images and their respective delete actions. Closes-Bug: 1549544 Change-Id: Idd5f59e32268cf4ceefe3639c607fb6c3520e538
This commit is contained in:
parent
f5728eed7a
commit
71cac4a145
@ -38,7 +38,8 @@
|
|||||||
'horizon.app.core.images.actions.delete-image.service',
|
'horizon.app.core.images.actions.delete-image.service',
|
||||||
'horizon.app.core.images.actions.launch-instance.service',
|
'horizon.app.core.images.actions.launch-instance.service',
|
||||||
'horizon.app.core.images.actions.update-metadata.service',
|
'horizon.app.core.images.actions.update-metadata.service',
|
||||||
'horizon.app.core.images.resourceType'
|
'horizon.app.core.images.resourceType',
|
||||||
|
'horizon.app.core.images.basePath'
|
||||||
];
|
];
|
||||||
|
|
||||||
function registerImageActions(
|
function registerImageActions(
|
||||||
@ -49,7 +50,8 @@
|
|||||||
deleteImageService,
|
deleteImageService,
|
||||||
launchInstanceService,
|
launchInstanceService,
|
||||||
updateMetadataService,
|
updateMetadataService,
|
||||||
imageResourceTypeCode
|
imageResourceTypeCode,
|
||||||
|
basePath
|
||||||
) {
|
) {
|
||||||
var imageResourceType = registry.getResourceType(imageResourceTypeCode);
|
var imageResourceType = registry.getResourceType(imageResourceTypeCode);
|
||||||
imageResourceType.itemActions
|
imageResourceType.itemActions
|
||||||
@ -100,15 +102,18 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A custom template is provided instead of the 'standard' definition
|
||||||
|
// to customize when the rendered button is disabled
|
||||||
|
//
|
||||||
|
// The template contains a new angular component which controls the
|
||||||
|
// disabled/enabled state of the rendered button.
|
||||||
imageResourceType.batchActions
|
imageResourceType.batchActions
|
||||||
.append({
|
.append({
|
||||||
id: 'batchDeleteImageAction',
|
id: 'batchDeleteImageAction',
|
||||||
service: deleteImageService,
|
service: deleteImageService,
|
||||||
template: {
|
template: {
|
||||||
type: 'delete-selected',
|
url: basePath + "/actions/delete-image-selected-button.template.html"
|
||||||
text: gettext('Delete Images')
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<delete-image-selected selected="tCtrl.selected"></delete-image-selected>
|
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// The component generally renders the same content as the default batch action
|
||||||
|
// button with the added complexity of changing the buttons enabled/disabled
|
||||||
|
// stated based on the 'allowed' state of the passed selected images.
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('horizon.app.core.images.actions')
|
||||||
|
.component('deleteImageSelected', {
|
||||||
|
controller: controller,
|
||||||
|
templateUrl: templateUrl,
|
||||||
|
bindings: {
|
||||||
|
callback: '=?',
|
||||||
|
selected: '<'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.$inject = [
|
||||||
|
'horizon.app.core.images.actions.delete-image.service',
|
||||||
|
'$q'
|
||||||
|
];
|
||||||
|
|
||||||
|
function controller(deleteImageService, $q) {
|
||||||
|
var ctrl = this;
|
||||||
|
|
||||||
|
ctrl.$onInit = function() {
|
||||||
|
ctrl.text = gettext('Delete Images');
|
||||||
|
ctrl._disable();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl.$onChanges = function() {
|
||||||
|
ctrl._disable();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl._disable = function() {
|
||||||
|
if (ctrl.selected.length === 0) {
|
||||||
|
ctrl.disabled = true;
|
||||||
|
} else {
|
||||||
|
var promises = $.map(ctrl.selected, function(image) {
|
||||||
|
return deleteImageService.allowed(image);
|
||||||
|
});
|
||||||
|
|
||||||
|
$q.all(promises).then(
|
||||||
|
function() {
|
||||||
|
ctrl.disabled = false;
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
ctrl.disabled = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
templateUrl.$inject = ['horizon.app.core.images.basePath'];
|
||||||
|
|
||||||
|
function templateUrl(basePath) {
|
||||||
|
return basePath + 'actions/delete-image-selected.template.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* 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('delete-image-selected component', function() {
|
||||||
|
var $scope, $element, $controller, $q;
|
||||||
|
// Mock image data
|
||||||
|
var mockAllowed = { allowed: true };
|
||||||
|
var mockDisallowed = { allowed: false };
|
||||||
|
|
||||||
|
beforeEach(module('templates'));
|
||||||
|
beforeEach(module('horizon.app.core.images.actions', function($provide) {
|
||||||
|
// Injects a mock 'action' directive for unit testing
|
||||||
|
$provide.decorator('actionDirective', function($delegate) {
|
||||||
|
var component = $delegate[0];
|
||||||
|
|
||||||
|
component.template = '<div>Mock</div>';
|
||||||
|
component.templateUrl = null;
|
||||||
|
|
||||||
|
return $delegate;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock delete-image.service. The disabling mechanism uses the allowed
|
||||||
|
// function from that service using the promises API, which is mocked
|
||||||
|
// here.
|
||||||
|
$provide.service(
|
||||||
|
'horizon.app.core.images.actions.delete-image.service', function() {
|
||||||
|
return {
|
||||||
|
allowed: function(mockImage) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
if (mockImage.allowed) {
|
||||||
|
deferred.resolve();
|
||||||
|
} else {
|
||||||
|
deferred.reject();
|
||||||
|
}
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
beforeEach(inject(function(_$rootScope_, _$compile_, _$q_) {
|
||||||
|
$q = _$q_;
|
||||||
|
$scope = _$rootScope_.$new();
|
||||||
|
var tag = angular.element(
|
||||||
|
'<delete-image-selected selected="selected" callback="callback">' +
|
||||||
|
'</delete-image-selected>'
|
||||||
|
);
|
||||||
|
|
||||||
|
$scope.selected = [];
|
||||||
|
|
||||||
|
$element = _$compile_(tag)($scope);
|
||||||
|
$scope.$apply();
|
||||||
|
|
||||||
|
$controller = $element.controller('deleteImageSelected');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('disables for empty list', function() {
|
||||||
|
expect($controller.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables for all allowed images', function() {
|
||||||
|
// Selections change the object; just pushing in new values wouldn't
|
||||||
|
// trigger disable recalculations
|
||||||
|
$scope.selected = [$.extend({}, mockAllowed)];
|
||||||
|
$scope.$apply();
|
||||||
|
expect($controller.disabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables for all disallowed images', function() {
|
||||||
|
$scope.selected = [$.extend({}, mockDisallowed)];
|
||||||
|
$scope.$apply();
|
||||||
|
expect($controller.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables for mixed images', function() {
|
||||||
|
$scope.selected = [
|
||||||
|
$.extend({}, mockDisallowed),
|
||||||
|
$.extend({}, mockDisallowed)
|
||||||
|
];
|
||||||
|
$scope.$apply();
|
||||||
|
expect($controller.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
@ -0,0 +1,7 @@
|
|||||||
|
<action action-classes="'btn btn-danger'"
|
||||||
|
disabled="$ctrl.disabled"
|
||||||
|
item="$ctrl.selected"
|
||||||
|
callback="$ctrl.callback">
|
||||||
|
<span class="fa fa-trash"></span>
|
||||||
|
{{ $ctrl.text }}
|
||||||
|
</action>
|
Loading…
Reference in New Issue
Block a user