Skip to content

Commit 006fe07

Browse files
authored
Dynamic Tabs support added (#101)
* Dynamic tabs support
1 parent 0850429 commit 006fe07

File tree

12 files changed

+263
-63
lines changed

12 files changed

+263
-63
lines changed

angular.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@
131131
"configurations": {
132132
"production": {
133133
"browserTarget": "angular-sdk-components:build:production"
134+
},
135+
"development": {
136+
"browserTarget": "angular-sdk-components:build:development"
134137
}
135138
}
136139
},

packages/angular-sdk-components/tests/e2e/DigV2/ViewTemplates/InlineDashboard.spec.js renamed to packages/angular-sdk-components/tests/e2e/DigV2/LandingPages/InlineDashboard.spec.js

File renamed without changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const { test, expect } = require('@playwright/test');
2+
3+
const config = require('../../../config');
4+
const common = require('../../../common');
5+
6+
test.beforeEach(async ({ page }) => {
7+
await page.setViewportSize({ width: 1720, height: 1080 });
8+
await page.goto(config.config.baseUrl, { waitUntil: 'networkidle' });
9+
});
10+
11+
test.describe('E2E test', () => {
12+
test('should login, create case and run different test cases for Dynamic Tabs', async ({ page }) => {
13+
await common.login(config.config.apps.digv2.user.username, config.config.apps.digv2.user.password, page);
14+
15+
/** Testing announcement banner presence */
16+
const announcementBanner = await page.locator('h2:has-text("Announcements")');
17+
await expect(announcementBanner).toBeVisible();
18+
19+
/** Testing worklist presence */
20+
const worklist = page.locator('div[id="worklist"]:has-text("My Worklist")');
21+
await expect(worklist).toBeVisible();
22+
23+
/** Click on the Create Case button */
24+
const createCase = page.locator('mat-list-item[id="create-case-button"]');
25+
await createCase.click();
26+
27+
/** Creating a View Templates case-type */
28+
const complexFieldsCase = page.locator('mat-list-item[id="case-list-item"] > span:has-text("View Templates")');
29+
await complexFieldsCase.click();
30+
31+
/** Selecting Dynamic Tabs from the Category dropdown */
32+
const selectedCategory = page.locator('mat-select[data-test-id="76729937a5eb6b0fd88c42581161facd"]');
33+
await selectedCategory.click();
34+
await page.locator('mat-option > span:has-text("Dynamic Tabs")').click();
35+
36+
await page.locator('button:has-text("submit")').click();
37+
38+
const title = await page.locator('div.template-title-container span:has-text("Dynamic Tabs Template")');
39+
await expect(title).toBeVisible();
40+
41+
const tablist = await page.locator('mat-tab-group[id="dynamic-tabs"] div[role="tablist"]');
42+
const tabpanel = await page.locator('mat-tab-group[id="dynamic-tabs"] mat-tab-body[role="tabpanel"]');
43+
44+
await expect(tabpanel.nth(0).getByText('Make')).toBeVisible();
45+
await expect(tabpanel.nth(0).getByText('BMW')).toBeVisible();
46+
47+
await tablist.locator('div[role="tab"]').nth(1).click();
48+
expect(await tabpanel.nth(1).getByText('Make')).toBeVisible();
49+
expect(await tabpanel.nth(1).getByText('Audi')).toBeVisible();
50+
51+
await tablist.locator('div[role="tab"]').nth(2).click();
52+
expect(await tabpanel.nth(2).getByText('Make')).toBeVisible();
53+
expect(await tabpanel.nth(2).getByText('FIAT')).toBeVisible();
54+
55+
await tablist.locator('div[role="tab"]').nth(3).click();
56+
expect(await tabpanel.nth(3).getByText('Make')).toBeVisible();
57+
expect(await tabpanel.nth(3).getByText('Chevrolet')).toBeVisible();
58+
59+
/** Submitting the case */
60+
await page.locator('button:has-text("submit")').click();
61+
}, 10000);
62+
});
63+
64+
test.afterEach(async ({ page }) => {
65+
await page.close();
66+
});

projects/angular-sdk-library/src/lib/_bridge/helpers/sdk-pega-component-map.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import { MaterialUtilityComponent } from '../../_components/designSystemExtensio
102102
import { RichTextComponent } from '../../_components/field/rich-text/rich-text.component';
103103
import { RichTextEditorComponent } from '../../_components/designSystemExtension/rich-text-editor/rich-text-editor.component';
104104
import { ListViewActionButtonsComponent } from '../../_components/field/list-view-action-buttons/list-view-action-buttons.component';
105+
import { DynamicTabsComponent } from '../../_components/template/dynamic-tabs/dynamic-tabs.component';
105106

106107
// pegaSdkComponentMap is the JSON object where we'll store the components that are
107108
// the default implementations provided by the SDK. These will be used if there isn't
@@ -149,6 +150,7 @@ const pegaSdkComponentMap = {
149150
DetailsThreeColumn: DetailsThreeColumnComponent,
150151
DetailsTwoColumn: DetailsTwoColumnComponent,
151152
Dropdown: DropdownComponent,
153+
DynamicTabs: DynamicTabsComponent,
152154
Email: EmailComponent,
153155
ErrorBoundary: ErrorBoundaryComponent,
154156
FeedContainer: FeedContainerComponent,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "DynamicTabs",
3+
"label": "Dynamic tabs (list)",
4+
"description": "Dynamic tabs (list)",
5+
"type": "Template",
6+
"subtype": "DETAILS",
7+
"icon": "DynamicTabs.svg",
8+
"hideTemplateEdit": true,
9+
"hideFromTemplatePicker": true,
10+
"viewHeadingLabel": "Default heading",
11+
"properties": [
12+
{
13+
"name": "referenceList",
14+
"label": "Data page",
15+
"format": "CONTENTPICKERSOURCE"
16+
},
17+
{
18+
"name": "parameters",
19+
"label": "Parameters",
20+
"format": "PARAMETERS",
21+
"showOverride": false
22+
},
23+
{
24+
"name": "tablabel",
25+
"label": "Tab label",
26+
"format": "PROPERTY",
27+
"ofContextClass": true
28+
},
29+
{
30+
"format": "VIEWPICKER",
31+
"name": "Views",
32+
"label": "Tab content",
33+
"ofContextClass": true
34+
}
35+
]
36+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<mat-tab-group id="dynamic-tabs" mat-stretch-tabs="false" animationDuration="0">
2+
<mat-tab *ngFor="let tab of tabsItems" [label]="tab.name">
3+
<component-mapper name="View" [props]="{ pConn$: tab.content.getPConnect() }"></component-mapper>
4+
</mat-tab>
5+
</mat-tab-group>

projects/angular-sdk-library/src/lib/_components/template/dynamic-tabs/dynamic-tabs.component.scss

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { DynamicTabsComponent } from './dynamic-tabs.component';
4+
5+
describe('DynamicTabsComponent', () => {
6+
let component: DynamicTabsComponent;
7+
let fixture: ComponentFixture<DynamicTabsComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [DynamicTabsComponent]
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(DynamicTabsComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Component, Input, forwardRef } from '@angular/core';
2+
import { FormGroup } from '@angular/forms';
3+
import { MatTabsModule } from '@angular/material/tabs';
4+
import { CommonModule } from '@angular/common';
5+
import { buildView } from '../../../_helpers/field-group-utils';
6+
import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect';
7+
import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component';
8+
9+
interface DynamicTabsProps {
10+
referenceList: string;
11+
template: string;
12+
}
13+
14+
@Component({
15+
selector: 'app-dynamic-tabs',
16+
templateUrl: './dynamic-tabs.component.html',
17+
styleUrls: ['./dynamic-tabs.component.scss'],
18+
standalone: true,
19+
imports: [CommonModule, MatTabsModule, forwardRef(() => ComponentMapperComponent)]
20+
})
21+
export class DynamicTabsComponent {
22+
@Input() pConn$: typeof PConnect;
23+
@Input() formGroup$: FormGroup;
24+
25+
angularPConnectData: AngularPConnectData = {};
26+
tabsItems: Array<any>;
27+
28+
constructor(private angularPConnect: AngularPConnectService) {}
29+
30+
ngOnInit(): void {
31+
// First thing in initialization is registering and subscribing to the AngularPConnect service
32+
this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange);
33+
this.checkAndUpdate();
34+
}
35+
36+
ngOnDestroy() {
37+
if (this.angularPConnectData.unsubscribeFn) {
38+
this.angularPConnectData.unsubscribeFn();
39+
}
40+
}
41+
42+
onStateChange() {
43+
this.checkAndUpdate();
44+
}
45+
46+
checkAndUpdate() {
47+
// Should always check the bridge to see if the component should update itself (re-render)
48+
const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this);
49+
50+
// ONLY call updateSelf when the component should update
51+
if (bUpdateSelf) {
52+
this.updateSelf();
53+
}
54+
}
55+
56+
updateSelf() {
57+
const { referenceList } = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as DynamicTabsProps;
58+
59+
// @ts-ignore - Property 'getComponentConfig' is private and only accessible within class 'C11nEnv'
60+
const { tablabel } = this.pConn$.getComponentConfig();
61+
const tablabelProp = PCore.getAnnotationUtils().getPropertyName(tablabel);
62+
63+
this.pConn$.setInheritedProp('displayMode', 'LABELS_LEFT');
64+
this.pConn$.setInheritedProp('readOnly', true);
65+
66+
const referenceListData: any = this.pConn$.getValue(`${referenceList}.pxResults`, ''); // 2nd arg empty string until typedefs properly allow optional
67+
68+
this.tabsItems =
69+
referenceListData?.map((item, i) => {
70+
const currentTabLabel = item[tablabelProp] || PCore.getLocaleUtils().getLocaleValue('No label specified in config', 'Generic');
71+
return {
72+
id: i,
73+
name: currentTabLabel,
74+
content: buildView(this.pConn$, i, '')
75+
};
76+
}) || [];
77+
}
78+
}

projects/angular-sdk-library/src/lib/_components/template/field-group-template/field-group-template.component.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
33
import { FormGroup } from '@angular/forms';
44
import { MatButtonModule } from '@angular/material/button';
55
import { AngularPConnectData, AngularPConnectService } from '../../../_bridge/angular-pconnect';
6-
import { FieldGroupUtils } from '../../../_helpers/field-group-utils';
6+
import { buildView, getReferenceList } from '../../../_helpers/field-group-utils';
77
import { Utils } from '../../../_helpers/utils';
88
import { ComponentMapperComponent } from '../../../_bridge/component-mapper/component-mapper.component';
99

@@ -50,8 +50,7 @@ export class FieldGroupTemplateComponent implements OnInit {
5050

5151
constructor(
5252
private angularPConnect: AngularPConnectService,
53-
private utils: Utils,
54-
private fieldGroupUtils: FieldGroupUtils
53+
private utils: Utils
5554
) {}
5655

5756
ngOnInit(): void {
@@ -111,7 +110,7 @@ export class FieldGroupTemplateComponent implements OnInit {
111110
const lookForChildInConfig = this.configProps$.lookForChildInConfig;
112111
this.heading = this.configProps$.heading ?? 'Row';
113112
this.fieldHeader = this.configProps$.fieldHeader;
114-
const resolvedList = this.fieldGroupUtils.getReferenceList(this.pConn$);
113+
const resolvedList = getReferenceList(this.pConn$);
115114
this.pageReference = `${this.pConn$.getPageReference()}${resolvedList}`;
116115
this.pConn$.setReferenceList(resolvedList);
117116
if (this.readonlyMode) {
@@ -130,7 +129,7 @@ export class FieldGroupTemplateComponent implements OnInit {
130129
children.push({
131130
id: index,
132131
name: this.fieldHeader === 'propertyRef' ? this.getDynamicHeader(item, index) : this.getStaticHeader(this.heading, index),
133-
children: this.fieldGroupUtils.buildView(this.pConn$, index, lookForChildInConfig)
132+
children: buildView(this.pConn$, index, lookForChildInConfig)
134133
});
135134
});
136135
this.children = children;

0 commit comments

Comments
 (0)