Add support for detail actions
This updates the angular actions directive to support the "detail" list type to render actions as tiles with a title and description. To see it in action: Update some row actions so the template attribute has a title and description property. Then add these actions to an array in the detail page controller and add the actions directive to the content of the detail page, something like this: <actions allowed="ctrl.detailActions" type="detail" item="ctrl.item" ng-if="ctrl.item"></actions> Implements blueprint next-steps Change-Id: I5e85004255e351c1fdd121251030e0697b3b9b9f
This commit is contained in:
parent
caa5e91059
commit
0c5c57542b
@ -0,0 +1,13 @@
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="panel $panel-classes$">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">$title$</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>$description$</p>
|
||||
<action action-classes="'$action-classes$'" item="$item$">
|
||||
$text$
|
||||
</action>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,2 @@
|
||||
<actions allowed="actions" type="detail" item="rowItem">
|
||||
</actions>
|
@ -28,21 +28,20 @@
|
||||
* @name horizon.framework.widgets.action-list.directive:actions
|
||||
* @element
|
||||
* @description
|
||||
* The `actions` directive represents the actions to be
|
||||
* displayed in a Bootstrap button group or button
|
||||
* dropdown.
|
||||
* The `actions` directive represents the actions to be displayed in a Bootstrap button
|
||||
* group, button dropdown, or bootstrap panels.
|
||||
*
|
||||
*
|
||||
* Attributes:
|
||||
*
|
||||
* @param {string} type
|
||||
* Type can be only be 'row' or 'batch'.
|
||||
* 'batch' actions are rendered as a button group, 'row' is rendered as a button dropdown menu.
|
||||
* 'batch' actions are typically used for actions across multiple items while
|
||||
* 'row' actions are used per item.
|
||||
* Type can be 'row', 'batch', or 'detail'. 'batch' actions are rendered as a button group,
|
||||
* 'row' actions are rendered as a button dropdown menu, 'detail' actions are rendered as
|
||||
* bootstrap panels. 'batch' actions are typically used for actions across multiple items while
|
||||
* 'row' and 'detail' actions are used per item.
|
||||
*
|
||||
* @param {string=} item
|
||||
* The item to pass to the 'service' when using 'row' type.
|
||||
* The item to pass to the 'service' when using 'row' or 'detail' type.
|
||||
*
|
||||
* @param {function} result-handler
|
||||
* (Optional) A function that is called with the return value from a clicked actions perform
|
||||
@ -77,8 +76,8 @@
|
||||
* 2. type: '<action_button_type>'
|
||||
* This creates an action button based off a 'known' button type.
|
||||
* Currently supported values are
|
||||
* 1. 'delete' - Delete a single row. Only for 'row' type.
|
||||
* 2. 'danger' - For marking an Action as dangerous. Only for 'row' type.
|
||||
* 1. 'delete' - Delete a single row. Only for 'row' or 'detail' type.
|
||||
* 2. 'danger' - For marking an Action as dangerous. Only for 'row' or 'detail' type.
|
||||
* 3. 'delete-selected' - Delete multiple rows. Only for 'batch' type.
|
||||
* 4. 'create' - Create a new entity. Only for 'batch' type.
|
||||
*
|
||||
@ -90,15 +89,20 @@
|
||||
* For custom styling of the button, `actionClasses` can be optionally included.
|
||||
* The directive will be responsible for binding the correct callback.
|
||||
*
|
||||
* 4. title: 'title', description: 'description'
|
||||
* A title and description must be provided for the 'detail' type. These are used as
|
||||
* the title and description to display in the bootstrap panel.
|
||||
*
|
||||
* service: is the service expected to have two functions
|
||||
* 1. allowed: is expected to return a promise that resolves
|
||||
* if the action is permitted and is rejected if not. If there are multiple promises that
|
||||
* need to be resolved, you can $q.all to combine multiple promises into a single promise.
|
||||
* When using 'row' type, the current 'item' will be passed to the function.
|
||||
* When using 'row' or 'detail' type, the current 'item' will be passed to the function.
|
||||
* When using 'batch' type, no arguments are provided.
|
||||
* 2. perform: is what gets called when the button is clicked. Also expected to return a
|
||||
* promise that resolves when the action completes.
|
||||
* When using 'row' type, the current 'item' is evaluated and passed to the function.
|
||||
* When using 'row' or 'detail' type, the current 'item' is evaluated and passed to the
|
||||
* function.
|
||||
* When using 'batch' type, 'item' is not passed.
|
||||
* When using 'delete-selected' for 'batch' type, all selected rows are passed.
|
||||
*
|
||||
@ -222,6 +226,10 @@
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* detail:
|
||||
*
|
||||
* The 'detail' type actions are identical to the 'row' type actions except that the template
|
||||
* property for each action should have a title and description property.
|
||||
*/
|
||||
function actions(
|
||||
$parse,
|
||||
|
@ -317,6 +317,36 @@
|
||||
expect(callbacks.first).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render detail actions', function () {
|
||||
var actions = [{
|
||||
template: {
|
||||
text: 'Action 1',
|
||||
title: 'Do something cool',
|
||||
description: 'This describes what that cool thing is you can do.'
|
||||
},
|
||||
service: getService(getPermission(true), callback)
|
||||
},{
|
||||
template: {
|
||||
text: 'Action 2',
|
||||
title: 'Do something dangerous',
|
||||
type: 'danger',
|
||||
description: 'This describes what that dangerous thing is you can do.'
|
||||
},
|
||||
service: getService(getPermission(true), callback)
|
||||
}];
|
||||
var element = rowElementFor(actions, true);
|
||||
|
||||
expect(element.find('.panel').length).toBe(2);
|
||||
expect(element.find('.panel-title').first().text().trim()).toBe('Do something cool');
|
||||
expect(element.find('.panel-title').last().text().trim()).toBe('Do something dangerous');
|
||||
expect(element.find('.panel-body button').first().text().trim()).toBe('Action 1');
|
||||
expect(element.find('.panel-body button').last().text().trim()).toBe('Action 2');
|
||||
expect(element.find('.panel').first().hasClass('panel-info')).toBe(true);
|
||||
expect(element.find('.panel').last().hasClass('panel-danger')).toBe(true);
|
||||
expect(element.find('.panel-body button').first().hasClass('btn-primary')).toBe(true);
|
||||
expect(element.find('.panel-body button').last().hasClass('btn-danger')).toBe(true);
|
||||
});
|
||||
|
||||
function permittedActionWithUrl(templateName) {
|
||||
return {
|
||||
template: {
|
||||
@ -392,13 +422,13 @@
|
||||
return element;
|
||||
}
|
||||
|
||||
function rowElementFor(actions) {
|
||||
function rowElementFor(actions, detail) {
|
||||
$scope.rowItem = rowItem;
|
||||
$scope.actions = function() {
|
||||
return actions;
|
||||
};
|
||||
|
||||
var element = angular.element(getTemplate('actions.row'));
|
||||
var element = angular.element(getTemplate(detail ? 'actions.detail' : 'actions.row'));
|
||||
|
||||
$compile(element)($scope);
|
||||
$scope.$apply();
|
||||
|
@ -15,6 +15,8 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var dangerTypes = { 'delete': 1, 'danger': 1, 'delete-selected': 1 };
|
||||
|
||||
angular
|
||||
.module('horizon.framework.widgets.action-list')
|
||||
.factory('horizon.framework.widgets.action-list.actions.service', actionsService);
|
||||
@ -83,7 +85,9 @@
|
||||
if (permittedActions.pass.length > 0) {
|
||||
var templateFetch = $q.all(permittedActions.pass.map(getTemplate));
|
||||
|
||||
if (listType === 'batch' || permittedActions.pass.length === 1) {
|
||||
if (listType === 'detail') {
|
||||
templateFetch.then(addDetailActions);
|
||||
} else if (listType === 'batch' || permittedActions.pass.length === 1) {
|
||||
element.addClass('btn-addon');
|
||||
templateFetch.then(addButtons);
|
||||
} else {
|
||||
@ -92,6 +96,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addDetailActions(templates) {
|
||||
var row = angular.element('<div class="row"></div>');
|
||||
element.append(row);
|
||||
templates.forEach(function renderDetailAction(template) {
|
||||
var templateElement = angular.element(template.template);
|
||||
templateElement.find('action').attr('callback', template.callback);
|
||||
row.append($compile(templateElement)(scope));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the buttons as a list of buttons
|
||||
*/
|
||||
@ -195,6 +209,10 @@
|
||||
'$action-classes$', getActionClasses(action, index, permittedActions.length)
|
||||
)
|
||||
.replace('$text$', action.template.text)
|
||||
.replace('$title$', action.template.title)
|
||||
.replace('$description$', action.template.description)
|
||||
.replace('$panel-classes$',
|
||||
action.template.type in dangerTypes ? 'panel-danger' : 'panel-info')
|
||||
.replace('$item$', item);
|
||||
defered.resolve({
|
||||
template: template,
|
||||
@ -216,22 +234,29 @@
|
||||
*/
|
||||
function getActionClasses(action, index, numPermittedActions) {
|
||||
var actionClassesParam = action.template.actionClasses || "";
|
||||
var actionClasses = 'btn ';
|
||||
if (listType === 'row') {
|
||||
if (numPermittedActions === 1 || index === 0) {
|
||||
var actionClasses = "btn ";
|
||||
if (action.template.type === "delete" || action.template.type === 'danger') {
|
||||
actionClasses += "btn-danger ";
|
||||
if (action.template.type in dangerTypes) {
|
||||
actionClasses += 'btn-danger ';
|
||||
} else {
|
||||
actionClasses += "btn-default ";
|
||||
actionClasses += 'btn-default ';
|
||||
}
|
||||
return actionClasses + actionClassesParam;
|
||||
} else {
|
||||
if (action.template.type === "delete" || action.template.type === 'danger') {
|
||||
if (action.template.type in dangerTypes) {
|
||||
return 'text-danger' + actionClassesParam;
|
||||
} else {
|
||||
return actionClassesParam;
|
||||
}
|
||||
}
|
||||
} else if (listType === 'detail') {
|
||||
if (action.template.type in dangerTypes) {
|
||||
actionClasses += 'btn-danger';
|
||||
} else {
|
||||
actionClasses += 'btn-primary';
|
||||
}
|
||||
return actionClasses;
|
||||
} else {
|
||||
return actionClassesParam;
|
||||
}
|
||||
@ -250,11 +275,11 @@
|
||||
if (angular.isDefined(action.template.url)) {
|
||||
// use the given URL
|
||||
return action.template.url;
|
||||
} else if (angular.isDefined(action.template.type)) {
|
||||
} else if (angular.isDefined(action.template.type) && listType !== 'detail') {
|
||||
// determine the template by the given type
|
||||
return basePath + 'action-list/actions-' + action.template.type + '.template.html';
|
||||
} else {
|
||||
// determine the template by `listType` which can be row or batch
|
||||
// determine the template by `listType` which can be row, batch, or detail
|
||||
return basePath + 'action-list/actions-' + listType + '.template.html';
|
||||
}
|
||||
}
|
||||
|
6
releasenotes/notes/bp-next-steps-4c7064e52d5abcf5.yaml
Normal file
6
releasenotes/notes/bp-next-steps-4c7064e52d5abcf5.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Added ability to render angular row actions with additional details that
|
||||
explain the purpose of the action. These are rendered as tiles and are
|
||||
meant to depict the next steps a user might want to take for a given
|
||||
resource.
|
Loading…
Reference in New Issue
Block a user