Add modal button group component

Component is meant to by used by the modals that have buttons
Allows to implement a dynamic way to declare modal buttons.
Each button onClick call back receives the activeModal.
Allowing to close or dismiss the modal on the onClick callback

- Re-use scrm-button-group
- Extend button group and button interfaces
-- Override the onClick handler to receiving the active modal
-- Inject the active modal to the button callbacks
- Allow for a configurable breakpoint using the config:
-- ui.modal_buttons_collapse_breakpoint
- Add karma/jasmine tests
This commit is contained in:
Clemente Raposo 2021-01-11 16:31:30 +00:00 committed by Dillon-Brown
parent b82d7097b7
commit 690162a2ce
7 changed files with 312 additions and 0 deletions

View file

@ -1,3 +1,4 @@
parameters:
ui:
alert_timeout: 3
modal_buttons_collapse_breakpoint: 4

View file

@ -0,0 +1,29 @@
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {ButtonInterface} from '@app-common/components/button/button.model';
import {DropdownButtonInterface} from '@app-common/components/button/dropdown-button.model';
import {DropdownOptions} from '@app-common/components/button/button-group.model';
export declare type ModalButtonCallback = (activeModal: NgbActiveModal) => void;
export type AnyModalButtonInterface = ModalButtonInterface | ModalDropdownButtonInterface;
export interface ModalButtonInterface extends ButtonInterface {
onClick?: ModalButtonCallback;
}
export interface ModalDropdownButtonInterface extends DropdownButtonInterface {
items?: AnyModalButtonInterface[];
}
export interface ModalCloseFeedBack {
type: string;
}
export interface ModalButtonGroupInterface {
wrapperKlass?: string[];
buttonKlass?: string[];
buttons?: AnyModalButtonInterface[];
dropdownLabel?: string;
dropdownOptions?: DropdownOptions;
breakpoint?: number;
}

View file

@ -0,0 +1 @@
<scrm-button-group [config$]="buttonGroup$"></scrm-button-group>

View file

@ -0,0 +1,195 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core';
import {Observable, of} from 'rxjs';
import {shareReplay} from 'rxjs/operators';
import {ModalButtonGroupInterface} from '@app-common/components/modal/modal.model';
import {ModalButtonGroupModule} from '@components/modal/components/modal-button-group/modal-button-group.module';
import {LanguageStore} from '@store/language/language.store';
import {languageStoreMock} from '@store/language/language.store.spec.mock';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {SystemConfigStore} from '@store/system-config/system-config.store';
import {systemConfigStoreMock} from '@store/system-config/system-config.store.spec.mock';
@Component({
selector: 'modal-button-group-test-host-component',
template: '<scrm-modal-button-group [config$]="config" [activeModal]="activeModal"></scrm-modal-button-group>'
})
class ModalButtonGroupTestHostComponent {
closeClicked = 0;
dismissClicked = 0;
activeModal = {
close: (): void => {
this.closeClicked++;
},
dismiss: (): void => {
this.dismissClicked++;
}
} as NgbActiveModal;
clicked = 0;
clickedItem1 = 0;
clickedItem2 = 0;
clickedItem3 = 0;
clickedItem4 = 0;
clickedItem5 = 0;
clickedItem6 = 0;
config: Observable<ModalButtonGroupInterface> = of({
wrapperKlass: ['some-class'],
dropdownLabel: 'Test Dropdown Label',
buttons: [
{
label: 'Item 1',
onClick: (activeModal: NgbActiveModal): void => {
if (activeModal && activeModal.close) {
activeModal.close();
}
this.clickedItem1++;
}
},
{
label: 'Item 2',
onClick: (activeModal: NgbActiveModal): void => {
if (activeModal && activeModal.dismiss) {
activeModal.dismiss();
}
this.clickedItem2++;
}
},
{
label: 'Item 3',
onClick: (): void => {
this.clickedItem3++;
}
},
{
label: 'Item 4',
onClick: (): void => {
this.clickedItem4++;
}
},
{
label: 'Item 5',
onClick: (): void => {
this.clickedItem5++;
}
},
{
label: 'Item 6',
onClick: (): void => {
this.clickedItem6++;
}
},
]
}).pipe(shareReplay(1));
}
describe('ModalButtonGroupComponent', () => {
let testHostComponent: ModalButtonGroupTestHostComponent;
let testHostFixture: ComponentFixture<ModalButtonGroupTestHostComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
ModalButtonGroupTestHostComponent,
],
imports: [
ModalButtonGroupModule
],
providers: [
{provide: LanguageStore, useValue: languageStoreMock},
{provide: SystemConfigStore, useValue: systemConfigStoreMock}
],
}).compileComponents();
testHostFixture = TestBed.createComponent(ModalButtonGroupTestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
}));
it('should create', () => {
expect(testHostComponent).toBeTruthy();
});
it('buttons should be clickable', async(() => {
expect(testHostComponent).toBeTruthy();
const buttons = testHostFixture.nativeElement.getElementsByClassName('button-group-button');
expect(buttons).toBeTruthy();
expect(buttons.length).toEqual(5);
buttons.item(2).click();
buttons.item(3).click();
testHostFixture.detectChanges();
testHostFixture.whenStable().then(() => {
expect(testHostComponent.clickedItem3).toEqual(1);
expect(testHostComponent.clickedItem4).toEqual(1);
});
}));
it('buttons should be clickable and call close modal', async(() => {
expect(testHostComponent).toBeTruthy();
const buttons = testHostFixture.nativeElement.getElementsByClassName('button-group-button');
expect(buttons).toBeTruthy();
expect(buttons.length).toEqual(5);
buttons.item(0).click();
testHostFixture.detectChanges();
testHostFixture.whenStable().then(() => {
expect(testHostComponent.clickedItem1).toEqual(1);
expect(testHostComponent.closeClicked).toEqual(1);
});
}));
it('buttons should be clickable and call dismiss modal', async(() => {
expect(testHostComponent).toBeTruthy();
const buttons = testHostFixture.nativeElement.getElementsByClassName('button-group-button');
expect(buttons).toBeTruthy();
expect(buttons.length).toEqual(5);
buttons.item(1).click();
testHostFixture.detectChanges();
testHostFixture.whenStable().then(() => {
expect(testHostComponent.clickedItem2).toEqual(1);
expect(testHostComponent.dismissClicked).toEqual(1);
});
}));
it('dropdown items should be clickable', async(() => {
expect(testHostComponent).toBeTruthy();
const element = testHostFixture.nativeElement;
const buttonWrapper = element.getElementsByClassName('button-group-dropdown').item(0);
const button = buttonWrapper.getElementsByClassName('dropdown-toggle button-group-button').item(0);
const divElement = element.querySelector('scrm-dropdown-button');
testHostComponent.clicked = 0;
expect(button).toBeTruthy();
button.click();
testHostFixture.detectChanges();
testHostFixture.whenStable().then(() => {
const links = divElement.getElementsByClassName('dropdown-item');
expect(links.length).toEqual(2);
links.item(0).click();
links.item(1).click();
testHostFixture.detectChanges();
testHostFixture.whenStable().then(() => {
expect(testHostComponent.clickedItem5).toEqual(1);
expect(testHostComponent.clickedItem6).toEqual(1);
});
});
}));
});

View file

@ -0,0 +1,68 @@
import {Component, Input, OnInit} from '@angular/core';
import {Observable} from 'rxjs';
import {ButtonGroupInterface} from '@app-common/components/button/button-group.model';
import {ModalButtonGroupInterface} from '@app-common/components/modal/modal.model';
import {deepClone} from '@app-common/utils/object-utils';
import {map} from 'rxjs/operators';
import {ButtonUtils} from '@components/button/button.utils';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import * as _ from 'lodash';
import {SystemConfigStore} from '@store/system-config/system-config.store';
@Component({
selector: 'scrm-modal-button-group',
templateUrl: './modal-button-group.component.html',
styleUrls: []
})
export class ModalButtonGroupComponent implements OnInit {
@Input() config$: Observable<ModalButtonGroupInterface>;
@Input() activeModal: NgbActiveModal = null;
buttonGroup$: Observable<ButtonGroupInterface>;
protected defaultButtonGroup: ButtonGroupInterface = {
breakpoint: 4,
wrapperKlass: ['modal-buttons'],
buttonKlass: ['modal-button', 'btn', 'btn-sm'],
buttons: []
};
constructor(
protected buttonUtils: ButtonUtils,
protected config: SystemConfigStore,
) {
const ui = this.config.getConfigValue('ui');
if (ui && ui.modal_button_group_breakpoint) {
this.defaultButtonGroup.breakpoint = ui.modal_buttons_collapse_breakpoint;
}
}
ngOnInit(): void {
if (this.config$) {
this.buttonGroup$ = this.config$.pipe(
map((config: ButtonGroupInterface) => this.mapButtonGroup(config))
);
}
}
protected mapButtonGroup(config: ButtonGroupInterface): ButtonGroupInterface {
const group = _.defaults({...config}, deepClone(this.defaultButtonGroup));
this.mapButtons(group);
return group;
}
protected mapButtons(group: ButtonGroupInterface): void {
const buttons = group.buttons || [];
group.buttons = [];
if (buttons.length > 0) {
buttons.forEach(modalButton => {
const button = this.buttonUtils.addOnClickPartial(modalButton, this.activeModal);
group.buttons.push(button);
});
}
}
}

View file

@ -0,0 +1,17 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ModalButtonGroupComponent} from './modal-button-group.component';
import {ButtonGroupModule} from '@components/button-group/button-group.module';
@NgModule({
declarations: [ModalButtonGroupComponent],
exports: [
ModalButtonGroupComponent
],
imports: [
CommonModule,
ButtonGroupModule
]
})
export class ModalButtonGroupModule {
}

View file

@ -110,6 +110,7 @@ export const systemConfigMockData = {
value: null,
items: {
alert_timeout: 3,
modal_buttons_collapse_breakpoint: 4,
}
},
date_format: {