diff --git a/src/styles/toolbar.scss b/src/styles/toolbar.scss
new file mode 100644
index 0000000000..73c99afa23
--- /dev/null
+++ b/src/styles/toolbar.scss
@@ -0,0 +1,18 @@
+.dropdown-kebab-pf {
+ .dropdown-submenu {
+ .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+
+ &:after, &:before {
+ border: none;
+ }
+
+ a:empty {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/src/styles/ui-components.scss b/src/styles/ui-components.scss
index 764eff8abd..2ea1f3122a 100644
--- a/src/styles/ui-components.scss
+++ b/src/styles/ui-components.scss
@@ -2,6 +2,7 @@
@import 'dialog-editor';
@import 'dialog-editor-toolbox';
@import 'dialog-editor-boxes';
+@import 'toolbar';
.miq-sand-paper, .miq-sand-paper > div { /* emulates "cards-pf" background color */
background: #f5f5f5;
@@ -34,7 +35,7 @@
.miq-custom-html {
.form-group.text, .form-group.has-clear {
- padding-right: 20px;
+ padding-right: 20px;
}
}
diff --git a/src/toolbar/components/toolbar-menu/index.ts b/src/toolbar/components/toolbar-menu/index.ts
index 5ddfc5eca7..2f4a834410 100644
--- a/src/toolbar/components/toolbar-menu/index.ts
+++ b/src/toolbar/components/toolbar-menu/index.ts
@@ -2,10 +2,14 @@ import Toolbar from './toolbarComponent';
import ToolbarButton from './toolbarButtonDirective';
import ToolbarList from './toolbarListComponent';
import ToolbarView from './toolbarViewComponent';
+import ToolbarKebab from './toolbarKebabComponent';
+import ToolbarClick from './toolbarClickDirective';
export default (module: ng.IModule) => {
module.component('miqToolbarMenu', new Toolbar);
module.component('miqToolbarList', new ToolbarList);
module.component('miqToolbarView', new ToolbarView);
+ module.component('miqToolbarKebab', new ToolbarKebab);
+ module.directive('miqToolbarClick', ToolbarClick.Factory());
module.directive('miqToolbarButton', ToolbarButton.Factory());
};
diff --git a/src/toolbar/components/toolbar-menu/toolbar-item-click.html b/src/toolbar/components/toolbar-menu/toolbar-item-click.html
new file mode 100644
index 0000000000..bd3c87f99c
--- /dev/null
+++ b/src/toolbar/components/toolbar-menu/toolbar-item-click.html
@@ -0,0 +1,26 @@
+
+
+
+
+ {{item.text}}
+
diff --git a/src/toolbar/components/toolbar-menu/toolbar-kebab.html b/src/toolbar/components/toolbar-menu/toolbar-kebab.html
new file mode 100644
index 0000000000..4445497e19
--- /dev/null
+++ b/src/toolbar/components/toolbar-menu/toolbar-kebab.html
@@ -0,0 +1,29 @@
+
diff --git a/src/toolbar/components/toolbar-menu/toolbar-list.html b/src/toolbar/components/toolbar-menu/toolbar-list.html
index 3fe9071013..d2a818bf38 100644
--- a/src/toolbar/components/toolbar-menu/toolbar-list.html
+++ b/src/toolbar/components/toolbar-menu/toolbar-list.html
@@ -9,32 +9,10 @@
diff --git a/src/toolbar/components/toolbar-menu/toolbar-menu.html b/src/toolbar/components/toolbar-menu/toolbar-menu.html
index 8bceb6ca1f..7f99437f0c 100644
--- a/src/toolbar/components/toolbar-menu/toolbar-menu.html
+++ b/src/toolbar/components/toolbar-menu/toolbar-menu.html
@@ -16,6 +16,11 @@
toolbar-list="item"
on-item-click="vm.onItemClick(item, $event)">
+
+
+
diff --git a/src/toolbar/components/toolbar-menu/toolbarClickDirective.spec.ts b/src/toolbar/components/toolbar-menu/toolbarClickDirective.spec.ts
new file mode 100644
index 0000000000..1fda204dbb
--- /dev/null
+++ b/src/toolbar/components/toolbar-menu/toolbarClickDirective.spec.ts
@@ -0,0 +1,96 @@
+import * as angular from 'angular';
+
+describe('Action click test', () => {
+ const onItemClick = jasmine.createSpy('onItemClick', (item, $event) => undefined);
+ const item = {
+ title: 'title',
+ text: 'someText',
+ hidden: false,
+ explorer: false,
+ confirm: 'confirm-text',
+ data: {
+ 'function': 'someJsFunction',
+ 'function-data': 'dataToPass',
+ target: 'targetData',
+ toggle: 'toggleData',
+ },
+ id: 'someId',
+ url_parms: 'urlParms',
+ 'send_checked': true,
+ prompt: 'promptData',
+ popup: 'popupData',
+ url: 'urlString',
+ icon: 'fa',
+ img_url: 'someUrl'
+ };
+
+ describe('markup', () => {
+ let scope, compile, compiledElement;
+ beforeEach(() => {
+ angular.mock.module('miqStaticAssets.toolbar');
+ angular.mock.inject(($rootScope, $compile: ng.ICompileService) => {
+ scope = $rootScope.$new();
+ compile = $compile;
+ });
+
+ scope.item = item;
+ scope.onItemClick = onItemClick;
+ compiledElement = compile(
+ angular.element(
+ ``
+ ))(scope);
+ scope.$digest();
+ });
+
+ it('should render all data', () => {
+ expect(compiledElement.attr('title')).toBe('title');
+ expect(compiledElement.attr('data-explorer')).toBe('false');
+ expect(compiledElement.attr('data-confirm-tb')).toBe('confirm-text');
+ expect(compiledElement.attr('data-function')).toBe('someJsFunction');
+ expect(compiledElement.attr('data-function-data')).toBe('dataToPass');
+ expect(compiledElement.attr('data-target')).toBe('targetData');
+ expect(compiledElement.attr('data-toggle')).toBe('toggleData');
+ expect(compiledElement.attr('data-click')).toBe('someId');
+ expect(compiledElement.attr('name')).toBe('someId');
+ expect(compiledElement.attr('id')).toBe('someId');
+ expect(compiledElement.attr('data-url_parms')).toBe('urlParms');
+ expect(compiledElement.attr('data-send_checked')).toBe('true');
+ expect(compiledElement.attr('data-prompt')).toBe('promptData');
+ expect(compiledElement.attr('data-popup')).toBe('popupData');
+ expect(compiledElement.attr('data-url')).toBe('urlString');
+ });
+
+ it('should render icon', () => {
+ expect(compiledElement.find('i').length).toBe(1);
+ expect(compiledElement.find('img').length).toBe(0);
+ expect(compiledElement.find('i').attr('style')).toBe('margin-right: 5px;');
+ console.log();
+ });
+
+ it('should render aligned icon', () => {
+ scope.item.text = '';
+ scope.$apply();
+ expect(compiledElement.find('i').attr('style')).toBe(undefined);
+ });
+
+ it('should render image', () => {
+ scope.item.icon = '';
+ scope.$apply();
+ expect(compiledElement.find('img').length).toBe(1);
+ expect(compiledElement.find('i').length).toBe(0);
+ });
+
+ it('should not render image or icon', () => {
+ scope.item.icon = '';
+ scope.item.img_url = '';
+ scope.$apply();
+ expect(compiledElement.find('img').length).toBe(0);
+ expect(compiledElement.find('i').length).toBe(0);
+ });
+
+ it('should call onItemClick with arguments', () => {
+ compiledElement[0].click();
+ expect(onItemClick).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/toolbar/components/toolbar-menu/toolbarClickDirective.ts b/src/toolbar/components/toolbar-menu/toolbarClickDirective.ts
new file mode 100644
index 0000000000..84f3e9b9a0
--- /dev/null
+++ b/src/toolbar/components/toolbar-menu/toolbarClickDirective.ts
@@ -0,0 +1,15 @@
+export default class ToolbarClick implements ng.IDirective {
+ public replace: boolean = true;
+ public template = require('./toolbar-item-click.html');
+ public controllerAs: string = 'vm';
+ public scope: any = {
+ item: '<',
+ onItemClick: '&'
+ };
+
+ public static Factory = () => {
+ let directive: ng.IDirectiveFactory = () => new ToolbarClick();
+ directive.$inject = [];
+ return directive;
+ }
+}
diff --git a/src/toolbar/components/toolbar-menu/toolbarComponent.spec.ts b/src/toolbar/components/toolbar-menu/toolbarComponent.spec.ts
index 9088b780dd..7c46fa386c 100644
--- a/src/toolbar/components/toolbar-menu/toolbarComponent.spec.ts
+++ b/src/toolbar/components/toolbar-menu/toolbarComponent.spec.ts
@@ -76,7 +76,7 @@ describe('Toolbar test', () => {
compile,
compiledElement;
- const toolbarData = require('../../../../demo/data/toolbar.json');
+ let toolbarData = require('../../../../demo/data/toolbar.json');
beforeEach(() => {
angular.mock.module('miqStaticAssets.toolbar');
@@ -85,10 +85,10 @@ describe('Toolbar test', () => {
compile = $compile;
});
- scope.toolbar = toolbarData;
+ scope.toolbarData = toolbarData;
compiledElement = compile(
angular.element(
- ``
+ ``
))(scope);
scope.$digest();
});
diff --git a/src/toolbar/components/toolbar-menu/toolbarComponent.ts b/src/toolbar/components/toolbar-menu/toolbarComponent.ts
index 5af1a1781f..47e3358aa2 100644
--- a/src/toolbar/components/toolbar-menu/toolbarComponent.ts
+++ b/src/toolbar/components/toolbar-menu/toolbarComponent.ts
@@ -1,6 +1,9 @@
import {IToolbarItem} from '../../interfaces/toolbar';
import {ToolbarType} from '../../interfaces/toolbarType';
import * as _ from 'lodash';
+
+const CUSTOM_ID = 'custom_';
+
/**
* @memberof miqStaticAssets
* @ngdoc controller
@@ -105,6 +108,10 @@ export class ToolbarController {
return ToolbarType.BUTTON;
}
+ public getToolbarKebabType(): string {
+ return ToolbarType.KEBAB;
+ }
+
/**
* Helper method for getting string value of {@link ToolbarType.CUSTOM}
* @memberof ToolbarController
@@ -119,6 +126,25 @@ export class ToolbarController {
return ToolbarType.BUTTON_TWO_STATE;
}
+ public collapseButtons() {
+ let buttonsIndex;
+ if (this.toolbarItems) {
+ buttonsIndex = _.findLastIndex(
+ this.toolbarItems,
+ (itemGroup: any) => itemGroup.filter(item => item.id.includes(CUSTOM_ID) !== false).length !== 0
+ );
+ if(buttonsIndex !== -1) {
+ this.toolbarItems[buttonsIndex] = ToolbarController.createKebabFromItems(this.toolbarItems[buttonsIndex]);
+ }
+ }
+ }
+
+ private $onChanges(changesObj) {
+ if (changesObj.toolbarItems) {
+ this.collapseButtons();
+ }
+ }
+
/**
* Private static function for decoding html.
* @memberof ToolbarController
@@ -157,6 +183,7 @@ export class ToolbarController {
(ToolbarController.isButtonSelect(item) && item.items && item.items.length !== 0)
|| ToolbarController.isButton(item)
|| ToolbarController.isButtonTwoState(item)
+ || ToolbarController.isKebabMenu(item)
);
}
@@ -175,6 +202,10 @@ export class ToolbarController {
return item.type === ToolbarType.BUTTON_SELECT;
}
+ private static isKebabMenu(item: IToolbarItem): boolean {
+ return item.type === ToolbarType.KEBAB;
+ }
+
/**
* Private static function for checking if toolbar item type is button.
* @memberof ToolbarController
@@ -185,6 +216,16 @@ export class ToolbarController {
private static isButton(item): boolean {
return item.type === ToolbarType.BUTTON;
}
+
+ private static createKebabFromItems(itemsGroup: any[]) {
+ if (itemsGroup.length > 3) {
+ return itemsGroup.reduce((acc, curr) => {
+ curr.id.includes(CUSTOM_ID) ? acc[0].items.push(curr) : acc.push(curr);
+ return acc;
+ }, [{type: ToolbarType.KEBAB, items: []}]);
+ }
+ return itemsGroup;
+ }
}
/**
diff --git a/src/toolbar/components/toolbar-menu/toolbarKebabComponent.spec.ts b/src/toolbar/components/toolbar-menu/toolbarKebabComponent.spec.ts
new file mode 100644
index 0000000000..be15d0e3c7
--- /dev/null
+++ b/src/toolbar/components/toolbar-menu/toolbarKebabComponent.spec.ts
@@ -0,0 +1,88 @@
+import * as angular from 'angular';
+
+describe('Kebab test', () => {
+ const item = {
+ items: [{
+ type: 'button',
+ text: 'Something'
+ }, {
+ type: 'buttonSelect',
+ text: 'Something select',
+ items: [{
+ type: 'button',
+ text: 'Something 2'
+ }, {
+ type: 'button',
+ text: 'Something 3'
+ }]
+ }],
+ };
+
+ describe('controller', () => {
+ let toolbarKebabController, bindings, onItemClick;
+
+ beforeEach(() => {
+ onItemClick = jasmine.createSpy('onItemClick', (item, $scope) => undefined);
+ bindings = {
+ kebabItem: item,
+ onItemClick: onItemClick
+ };
+ angular.mock.module('miqStaticAssets.toolbar');
+ angular.mock.inject(($componentController) => {
+ toolbarKebabController = $componentController('miqToolbarKebab', null, bindings);
+ });
+ });
+
+ it('should create correctly controller', () => {
+ expect(toolbarKebabController).toBeDefined();
+ });
+
+ it('should set component\'s items', () => {
+ expect(angular.equals(toolbarKebabController.kebabItem, item)).toBeTruthy();
+ expect(angular.equals(toolbarKebabController.onItemClick, onItemClick)).toBeTruthy();
+ });
+ });
+
+ describe('markup', () => {
+ let scope, compile, compiledElement, onItemClick;
+ beforeEach(() => {
+ onItemClick = jasmine.createSpy('onItemClick', (item, $scope) => undefined);
+ angular.mock.module('miqStaticAssets.toolbar');
+ angular.mock.inject(($rootScope, $compile: ng.ICompileService) => {
+ scope = $rootScope.$new();
+ compile = $compile;
+ });
+
+ scope.kebabItem = item;
+ scope.onItemClick = onItemClick;
+ compiledElement = compile(
+ angular.element(
+ ``
+ ))(scope);
+ scope.$digest();
+ });
+
+ describe('kebab menu', () => {
+ it('should render dropdown menu', () => {
+ expect(compiledElement.find('button[uib-dropdown-toggle=""]').length).toBe(1);
+ expect(compiledElement.find('ul')).toBeDefined();
+ });
+
+ it('should render button and button select', () => {
+ expect(compiledElement.find('.dropdown-submenu').length).toBe(1);
+ expect(compiledElement.find('.dropdown-submenu ul').length).toBe(1);
+ expect(compiledElement.find('.dropdown-submenu ul li').length).toBe(2);
+ });
+ });
+
+ it('should call on click', () => {
+ compiledElement.find('li>a')[0].click();
+ expect(onItemClick).toHaveBeenCalled();
+ });
+
+ it('should not call buttonSelect', () => {
+ compiledElement.find('li.dropdown-submenu>a')[0].click();
+ expect(onItemClick).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/toolbar/components/toolbar-menu/toolbarKebabComponent.ts b/src/toolbar/components/toolbar-menu/toolbarKebabComponent.ts
new file mode 100644
index 0000000000..9177bdf7e3
--- /dev/null
+++ b/src/toolbar/components/toolbar-menu/toolbarKebabComponent.ts
@@ -0,0 +1,14 @@
+class ToolbarKebabController {
+ public items;
+ public onItemClick: (args: {item: any, $event: any}) => void;
+}
+
+export default class ToolbarKebab {
+ public template = require('./toolbar-kebab.html');
+ public controller: any = ToolbarKebabController;
+ public controllerAs: string = 'vm';
+ public bindings: any = {
+ kebabItem: '<',
+ onItemClick: '&'
+ };
+}
diff --git a/src/toolbar/interfaces/toolbarType.ts b/src/toolbar/interfaces/toolbarType.ts
index a18ebd15bd..94fa84f31f 100644
--- a/src/toolbar/interfaces/toolbarType.ts
+++ b/src/toolbar/interfaces/toolbarType.ts
@@ -30,5 +30,11 @@ export const ToolbarType = {
* Separator type: `separator`
* @type {string}
*/
- SEPARATOR: 'separator'
+ SEPARATOR: 'separator',
+
+ /**
+ * Kebab type: `kebab`
+ * @type {string}
+ */
+ KEBAB: 'kebab'
};