@pega/dx-component-builder-sdk
Version:
Utility for building custom UI components
1,435 lines (1,254 loc) • 44.7 kB
text/typescript
import { Component, OnInit, Input, ViewChild, forwardRef, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRadioModule } from '@angular/material/radio';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { DragDropModule, CdkDragDrop, moveItemInArray, CdkDropList, CdkDrag } from '@angular/cdk/drag-drop';
import { ProgressSpinnerService } from '@pega/angular-sdk-components';
import { Utils } from '@pega/angular-sdk-components';
import { ComponentMapperComponent } from '@pega/angular-sdk-components';
import { getCurrencyOptions } from '@pega/angular-sdk-components';
import { getLocale, getSeconds } from '@pega/angular-sdk-components';
import { formatters } from '@pega/angular-sdk-components';
import { init } from './listViewHelpers';
const SELECTION_MODE = { SINGLE: 'single', MULTI: 'multi' };
interface ListViewProps {
inheritedProps: any;
title: string | undefined;
// If any, enter additional props that only exist on this component
globalSearch?: boolean;
referenceList?: any;
rowClickAction?: any;
selectionMode?: string;
referenceType?: string;
compositeKeys?: any;
showDynamicFields?: boolean;
presets?: any;
reorderFields: string | boolean;
grouping: string | boolean;
value: any;
readonlyContextList: any;
label?: string;
}
export class Group {
level = 0;
parent: Group;
expanded = true;
totalCounts = 0;
get visible(): boolean {
return !this.parent || (this.parent.visible && this.parent.expanded);
}
}
export class ListViewComponent implements OnInit, OnDestroy {
paginator: MatPaginator;
sort: MatSort;
pConn$: typeof PConnect;
bInForm$ = true;
payload;
repeatList$: MatTableDataSource<any>;
fields$: any[];
displayedColumns$ = Array<any>();
groupByColumns$: string[] = [];
configProps$: ListViewProps;
updatedRefList: any;
repeatListData: any[] = [];
searchIcon$: string;
bShowSearch$ = false;
bColumnReorder$ = false;
bGrouping$ = false;
perfFilter: string;
searchFilter: string;
menuSvgIcon$: string;
arrowSvgIcon$ = '';
arrowDownSvgIcon$: string;
arrowUpSvgIcon$: string;
filterSvgIcon$: string;
filterOnSvgIcon$: string;
groupBySvgIcon$: string;
compareType: string;
compareRef: string;
arrowDirection = 'down';
filterByColumns: any[] = [];
bShowFilterPopover$ = false;
bContains$ = true;
bDateTime$ = false;
filterContainsLabel$ = '';
filterContainsType$ = 'contains';
filterContainsValue$: any;
bIsDate$ = false;
bIsDateTime$ = false;
bIsTime$ = false;
currentFilterRefData: any;
currentFilterImageEl: any;
arFilterMainButtons$: any[] = [];
arFilterSecondaryButtons$: any[] = [];
selectionMode?: string;
singleSelectionMode: boolean;
multiSelectionMode: boolean;
rowID: any;
response: any;
compositeKeys: any;
showDynamicFields: any;
filters: any = {};
selectParam: any[] = [];
filterPayload: any;
ref: any = {};
cosmosTableRef: any;
listContext: any = {};
query: any = null;
paging: any;
fieldDefs: any;
xRayApis = PCore.getDebugger().getXRayRuntime();
xRayUid = this.xRayApis.startXRay();
checkBoxValue: string;
label?: string = '';
constructor(
private psService: ProgressSpinnerService,
public utils: Utils
) {}
ngOnInit(): void {
this.configProps$ = this.pConn$.getConfigProps() as ListViewProps;
/** By default, pyGUID is used for Data classes and pyID is for Work classes as row-id/key */
const defRowID = this.configProps$?.referenceType === 'Case' ? 'pyID' : 'pyGUID';
/** If compositeKeys is defined, use dynamic value, else fallback to pyID or pyGUID. */
this.compositeKeys = this.configProps$?.compositeKeys;
this.rowID = this.compositeKeys && this.compositeKeys?.length === 1 ? this.compositeKeys[0] : defRowID;
this.bShowSearch$ = this.utils.getBooleanValue(this.configProps$?.globalSearch ? this.configProps$.globalSearch : this.payload?.globalSearch);
this.bColumnReorder$ = this.utils.getBooleanValue(this.configProps$.reorderFields);
this.bGrouping$ = this.utils.getBooleanValue(this.configProps$.grouping);
this.showDynamicFields = this.configProps$?.showDynamicFields;
this.menuSvgIcon$ = this.utils.getImageSrc('more', this.utils.getSDKStaticContentUrl());
this.arrowDownSvgIcon$ = this.utils.getImageSrc('arrow-down', this.utils.getSDKStaticContentUrl());
this.arrowUpSvgIcon$ = this.utils.getImageSrc('arrow-up', this.utils.getSDKStaticContentUrl());
this.filterSvgIcon$ = this.utils.getImageSrc('filter', this.utils.getSDKStaticContentUrl());
this.filterOnSvgIcon$ = this.utils.getImageSrc('filter-on', this.utils.getSDKStaticContentUrl());
this.groupBySvgIcon$ = this.utils.getImageSrc('row', this.utils.getSDKStaticContentUrl());
this.selectionMode = this.configProps$.selectionMode;
this.checkBoxValue = this.configProps$.value;
this.arFilterMainButtons$.push({ actionID: 'submit', jsAction: 'submit', name: 'Submit' });
this.arFilterSecondaryButtons$.push({ actionID: 'cancel', jsAction: 'cancel', name: 'Cancel' });
let title = this.configProps$?.title || this.configProps$?.label || 'List';
const inheritedProps = this.configProps$?.inheritedProps;
if (title === 'List' && inheritedProps) {
for (const inheritedProp of inheritedProps) {
if (inheritedProp?.prop === 'label') {
title = inheritedProp?.value;
break;
}
}
}
this.label = title;
this.searchIcon$ = this.utils.getImageSrc('search', this.utils.getSDKStaticContentUrl());
setTimeout(() => {
PCore.getPubSubUtils().subscribe(
PCore.getConstants().PUB_SUB_EVENTS.EVENT_DASHBOARD_FILTER_CHANGE,
data => {
this.processFilterChange(data);
},
`dashboard-component-${'id'}`,
false,
this.pConn$.getContextName()
);
PCore.getPubSubUtils().subscribe(
PCore.getConstants().PUB_SUB_EVENTS.EVENT_DASHBOARD_FILTER_CLEAR_ALL,
() => {
this.filters = {};
this.processFilterClear();
},
`dashboard-component-${'id'}`,
false,
this.pConn$.getContextName()
);
}, 0);
if (this.configProps$) {
if (!this.payload) {
this.payload = { referenceList: this.configProps$.referenceList };
}
init({
pConn$: this.pConn$,
bInForm$: this.bInForm$,
...this.payload,
listContext: this.listContext,
ref: this.ref,
showDynamicFields: this.showDynamicFields,
xRayUid: this.xRayUid,
cosmosTableRef: this.cosmosTableRef,
selectionMode: this.selectionMode
}).then(response => {
this.listContext = response;
this.getListData();
});
}
}
getFieldFromFilter(filter, dateRange = false) {
let fieldValue;
if (dateRange) {
fieldValue = filter?.AND[0]?.condition.lhs.field;
} else {
fieldValue = filter?.condition.lhs.field;
}
return fieldValue;
}
// Will be triggered when EVENT_DASHBOARD_FILTER_CHANGE fires
processFilterChange(data) {
const { filterId, filterExpression } = data;
let dashboardFilterPayload: any = {
query: {
filter: {},
select: []
}
};
this.filters[filterId] = filterExpression;
let isDateRange = !!data.filterExpression?.AND;
// Will be AND by default but making it dynamic in case we support dynamic relational ops in future
const relationalOp = 'AND';
let field = this.getFieldFromFilter(filterExpression, isDateRange);
const selectParam: any[] = [];
// Constructing the select parameters list (will be sent in dashboardFilterPayload)
this.displayedColumns$?.forEach(col => {
selectParam.push({
field: col
});
});
// Checking if the triggered filter is applicable for this list
if (data.filterExpression !== null && !(this.displayedColumns$?.length && this.displayedColumns$?.includes(field))) {
return;
}
// This is a flag which will be used to reset dashboardFilterPayload in case we don't find any valid filters
let validFilter = false;
let index = 1;
// Iterating over the current filters list to create filter data which will be POSTed
const filterKeys: any[] = Object.keys(this.filters);
const filterValues: any[] = Object.values(this.filters);
for (let filterIndex = 0; filterIndex < filterKeys.length; filterIndex++) {
const filter = filterValues[filterIndex];
// If the filter is null then we can skip this iteration
if (filter === null) {
// eslint-disable-next-line no-continue
continue;
}
// Checking if the filter is of type- Date Range
isDateRange = !!filter?.AND;
field = this.getFieldFromFilter(filter, isDateRange);
if (!(this.displayedColumns$?.length && this.displayedColumns$?.includes(field))) {
// eslint-disable-next-line no-continue
continue;
}
// If we reach here that implies we've at least one valid filter, hence setting the flag
validFilter = true;
/** Below are the 2 cases for- Text & Date-Range filter types where we'll construct filter data which will be sent in the dashboardFilterPayload
* In Constellation DX Components, through Repeating Structures they might be using several APIs to do it. We're doing it here
*/
if (isDateRange) {
dashboardFilterPayload = this.filterBasedOnDateRange(dashboardFilterPayload, filter, relationalOp, selectParam, index);
} else {
dashboardFilterPayload.query.filter.filterConditions = {
...dashboardFilterPayload.query.filter.filterConditions,
[`T${index++}`]: { ...filter.condition, ignoreCase: true }
};
if (dashboardFilterPayload.query.filter.logic) {
dashboardFilterPayload.query.filter.logic = `${dashboardFilterPayload.query.filter.logic} ${relationalOp} T${index - 1}`;
} else {
dashboardFilterPayload.query.filter.logic = `T${index - 1}`;
}
dashboardFilterPayload.query.select = selectParam;
}
}
// Reset the dashboardFilterPayload if we end up with no valid filters for the list
if (!validFilter) {
dashboardFilterPayload = undefined;
}
this.filterPayload = dashboardFilterPayload;
this.getListData();
}
filterBasedOnDateRange(dashboardFilterPayload, filter, relationalOp, selectParam, index) {
const dateRelationalOp = filter?.AND ? 'AND' : 'OR';
dashboardFilterPayload.query.filter.filterConditions = {
...dashboardFilterPayload.query.filter.filterConditions,
[`T${index++}`]: { ...filter[relationalOp][0].condition },
[`T${index++}`]: { ...filter[relationalOp][1].condition }
};
if (dashboardFilterPayload.query.filter.logic) {
dashboardFilterPayload.query.filter.logic = `${dashboardFilterPayload.query.filter.logic} ${relationalOp} (T${index - 2} ${dateRelationalOp} T${
index - 1
})`;
} else {
dashboardFilterPayload.query.filter.logic = `(T${index - 2} ${relationalOp} T${index - 1})`;
}
dashboardFilterPayload.query.select = selectParam;
return dashboardFilterPayload;
}
processFilterClear() {
this.filterPayload = undefined;
this.getListData();
}
getFieldsMetadata(refList) {
// @ts-ignore - 3rd parameter "associationFilter" should be optional for getDataViewMetadata method
return PCore.getAnalyticsUtils().getDataViewMetadata(refList, this.showDynamicFields);
}
getListData() {
const componentConfig = this.pConn$.getComponentConfig();
if (this.configProps$) {
this.preparePayload();
const refList = this.configProps$.referenceList;
const fieldsMetaDataPromise = this.getFieldsMetadata(refList);
// returns a promise
const payload = this.payload || this.filterPayload || {};
const dataViewParameters = this.payload.parameters;
const workListDataPromise = !this.bInForm$
? PCore.getDataApiUtils().getData(refList, payload)
: PCore.getDataPageUtils().getDataAsync(
refList,
this.pConn$.getContextName(),
payload.dataViewParameters ? payload.dataViewParameters : dataViewParameters,
this.paging,
this.query
);
this.bShowFilterPopover$ = false;
this.psService.sendMessage(true);
Promise.all([fieldsMetaDataPromise, workListDataPromise])
.then((results: any) => {
const fieldsMetaData = results[0];
const workListData = results[1];
this.fields$ = this.configProps$.presets[0].children[0].children;
// this is an unresovled version of this.fields$, need unresolved, so can get the property reference
const columnFields = componentConfig.presets[0].children[0].children;
const tableDataResults = !this.bInForm$ ? workListData.data.data : workListData.data;
const columns = this.getHeaderCells(columnFields, this.fieldDefs);
this.fields$ = this.updateFields(this.fields$, fieldsMetaData.data.fields, columns);
this.displayedColumns$ = columns.map(col => {
return col.id;
});
this.response = tableDataResults;
this.updatedRefList = this.updateData(tableDataResults, this.fields$);
if (this.selectionMode === SELECTION_MODE.SINGLE && this.updatedRefList?.length > 0) {
this.displayedColumns$?.unshift('select');
this.singleSelectionMode = true;
} else if (this.selectionMode === SELECTION_MODE.MULTI && this.updatedRefList?.length > 0) {
this.displayedColumns$?.unshift('select');
this.multiSelectionMode = true;
}
this.repeatList$ = new MatTableDataSource(this.updatedRefList);
this.repeatList$.filterPredicate = this.customFilterPredicate.bind(this);
// keeping an original copy to get back after possible sorts, filters and groupBy
this.repeatListData = this.repeatList$.data.slice();
this.repeatList$.paginator = this.paginator;
this.repeatList$.sort = this.sort;
this.psService.sendMessage(false);
})
.catch(() => {
console.error("Couldn't fetch either the fieldsMetaData or workListData");
this.psService.sendMessage(false);
});
}
}
preparePayload() {
const { fieldDefs, itemKey, patchQueryFields } = this.listContext.meta;
this.fieldDefs = fieldDefs;
let listFields = fieldDefs ? this.buildSelect(fieldDefs, undefined, patchQueryFields, this.payload?.compositeKeys) : [];
listFields = this.addItemKeyInSelect(fieldDefs, itemKey, listFields, this.payload?.compositeKeys);
if (this.payload.query) {
this.query = this.payload.query;
} else if (listFields?.length && this.listContext.meta.isQueryable) {
if (this.filterPayload) {
this.query = {
select: listFields,
filter: this.filterPayload?.query?.filter
};
} else {
this.query = { select: listFields };
}
} else if (this.filterPayload) {
this.query = this.filterPayload?.query;
}
}
// ngOnChanges(changes) {
// if (changes && changes.payload) {
// const currentPayloadVal = changes.payload?.currentValue;
// const previousPayloadVal = changes.payload?.previousValue;
// if (currentPayloadVal !== previousPayloadVal) {
// this.getListData();
// }
// }
// }
ngOnDestroy() {
PCore.getPubSubUtils().unsubscribe(
PCore.getConstants().PUB_SUB_EVENTS.EVENT_DASHBOARD_FILTER_CHANGE,
`dashboard-component-${'id'}`,
this.pConn$.getContextName()
);
PCore.getPubSubUtils().unsubscribe(
PCore.getConstants().PUB_SUB_EVENTS.EVENT_DASHBOARD_FILTER_CLEAR_ALL,
`dashboard-component-${'id'}`,
this.pConn$.getContextName()
);
}
// ngAfterViewInit() {
// // paginator has to exist for this to work,
// // so called after init (paginator drawn)
// // Calls are now in workListData promise
// // this.repeatList$.paginator = this.paginator;
// // this.repeatList$.sort = this.sort;
// }
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.displayedColumns$, event.previousIndex, event.currentIndex);
}
updateFields(arFields: any[], arColumns, fields): any[] {
const arReturn = arFields;
arReturn.forEach((field, i) => {
field.config = { ...field.config, ...fields[i], name: fields[i].id };
});
return arReturn;
}
applySearch(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.searchFilter = filterValue.trim().toLowerCase();
if (this.groupByColumns$.length > 0) {
this.repeatList$.data = this.repeatListData.slice();
this.repeatList$.filter = this.searchFilter;
const repeatData = this.repeatList$.sortData(this.repeatList$.filteredData, this.repeatList$.sort as MatSort);
this.repeatList$.data = this.addGroups(repeatData, this.groupByColumns$);
this.perfFilter = performance.now().toString();
this.repeatList$.filter = this.perfFilter;
} else {
this.repeatList$.filter = this.searchFilter;
}
if (this.repeatList$.paginator) {
this.repeatList$.paginator.firstPage();
}
}
isChecked(rowIn): any {
const initialVal = false;
return this.configProps$?.readonlyContextList?.reduce((acc, currRow) => {
return acc || rowIn[this.rowID] === currRow[this.rowID];
}, initialVal);
}
fieldOnChange(row) {
const value = row[this.rowID];
const reqObj = {};
if (this.compositeKeys?.length > 1) {
const index = this.response.findIndex(element => element[this.rowID] === value);
const selectedRow = this.response[index];
this.compositeKeys.forEach(element => {
reqObj[element] = selectedRow[element];
});
} else {
reqObj[this.rowID] = value;
}
this.checkBoxValue = value;
this.pConn$?.getListActions?.()?.setSelectedRows([reqObj]);
}
onCheckboxClick(row, event) {
const value = row[this.rowID];
const checked = event?.checked;
const reqObj: any = {};
if (this.compositeKeys?.length > 1) {
const index = this.response.findIndex(element => element[this.rowID] === value);
const selectedRow = this.response[index];
this.compositeKeys.forEach(element => {
reqObj[element] = selectedRow[element];
});
reqObj.$selected = checked;
} else {
reqObj[this.rowID] = value;
reqObj.$selected = checked;
}
this.pConn$?.getListActions()?.setSelectedRows([reqObj]);
}
// rowClick(row) {
// switch (this.configProps$.rowClickAction) {
// case 'openAssignment':
// this.psService.sendMessage(true);
// this.openAssignment(row);
// break;
// }
// }
_getIconStyle(level): string {
let sReturn = '';
let nLevel = parseInt(level, 10);
nLevel--;
nLevel *= 15;
sReturn = `padding-left: ${nLevel}px; vertical-align: middle`;
return sReturn;
}
_getGroupName(fieldName) {
for (let fieldIndex = 0; fieldIndex < this.fields$.length; fieldIndex++) {
const field = this.fields$[fieldIndex];
if (field.config.name == fieldName) {
return field.config.label;
}
}
return '';
}
_showButton(name, row) {
let bReturn = false;
const { pxRefObjectClass, pzInsKey, pxRefObjectKey } = row;
switch (name) {
case 'pxTaskLabel':
if (pxRefObjectClass != '' && pzInsKey != '') {
bReturn = true;
}
break;
case 'pxRefObjectInsName':
if (pxRefObjectClass != '' && pxRefObjectKey != '') {
bReturn = true;
}
break;
default:
break;
}
return bReturn;
}
_listViewClick(column, row) {
const name = column.id;
if (column.displayAsLink) {
const { pxObjClass } = row;
let { pzInsKey } = row;
if (column.isAssociation) {
const associationCategory = name.split(':')[0];
pzInsKey = row[`${associationCategory}:pzInsKey`];
}
if (column.isAssignmentLink) {
this.pConn$.getActionsApi().openAssignment(pzInsKey, pxObjClass, {
containerName: 'primary',
channelName: ''
} as any);
} else {
this.pConn$.getActionsApi().openWorkByHandle(pzInsKey, pxObjClass);
}
} else {
switch (name) {
case 'pxTaskLabel':
this.openAssignment(row);
break;
case 'pxRefObjectInsName':
this.openWork(row);
break;
default:
break;
}
}
}
_headerSortClick(event, columnData) {
// images 0 - filter, 1 - arrow, 2 - more
/** Commenting this code for now as it is giving errors not sure if it ever worked */
// let arrowImage = event.srcElement.getElementsByTagName('img')[1];
// let arrowAttr = arrowImage.getAttribute('arrow');
// this.clearOutArrows(event, columnData);
// switch (arrowAttr) {
// case 'up':
// arrowImage.src = this.arrowDownSvgIcon$;
// arrowImage.setAttribute('arrow', 'down');
// break;
// case 'down':
// arrowImage.src = '';
// arrowImage.setAttribute('arrow', 'none');
// break;
// default:
// arrowImage.src = this.arrowUpSvgIcon$;
// arrowImage.setAttribute('arrow', 'up');
// break;
// }
this.compareType = columnData.type;
this.compareRef = columnData.config.name;
// this.arrowDirection = arrowImage.getAttribute('arrow');
this.arrowDirection = this.arrowDirection === 'up' ? 'down' : 'up';
this.filterSortGroupBy();
}
// Commenting below method, since the code which is using it, is already commented
// clearOutArrows(event, columnData) {
// const arImages = event.srcElement.parentElement.getElementsByTagName('img');
// for (const theImage of arImages) {
// // let theImage = arImages[i]
// const arrow = theImage.getAttribute('arrow');
// if (arrow) {
// const arrowId = theImage.getAttribute('arrowid');
// if (arrow != 'none' && arrowId != columnData.config.name) {
// theImage.setAttribute('arrow', 'none');
// theImage.src = '';
// }
// }
// }
// }
sortCompare(a, b): number {
let aValue = a[this.compareRef];
let bValue = b[this.compareRef];
if (this.compareType == 'Date' || this.compareType == 'DateTime') {
aValue = getSeconds(aValue);
bValue = getSeconds(bValue);
}
if (this.compareRef == 'pxRefObjectInsName') {
const result = this.compareByColumnPxRefObjectInsName(aValue, bValue);
if (result !== undefined) {
return result;
}
}
switch (this.arrowDirection) {
case 'up':
if (!aValue || aValue < bValue) {
return -1;
}
if (!bValue || aValue > bValue) {
return 1;
}
break;
case 'down':
if (!bValue || aValue > bValue) {
return -1;
}
if (!aValue || aValue < bValue) {
return 1;
}
break;
default:
break;
}
return 0;
}
compareByColumnPxRefObjectInsName(aValue, bValue) {
const prefixX = aValue.split('-');
const prefixY = bValue.split('-');
switch (this.arrowDirection) {
case 'up':
if (prefixX[0] !== prefixY[0]) {
if (prefixX[0] < prefixY[0]) return -1;
if (prefixX[0] > prefixY[0]) return 1;
return 0;
}
return prefixX[1] - prefixY[1];
case 'down':
if (prefixX[0] !== prefixY[0]) {
if (prefixX[0] > prefixY[0]) return -1;
if (prefixX[0] < prefixY[0]) return 1;
return 0;
}
return prefixY[1] - prefixX[1];
default:
break;
}
return undefined;
}
updateFilterDisplay(type) {
switch (type) {
case 'Date':
this.filterContainsType$ = 'notequal';
this.bContains$ = false;
this.bDateTime$ = true;
this.bIsDate$ = true;
this.bIsDateTime$ = false;
this.bIsTime$ = false;
break;
case 'DateTime':
this.filterContainsType$ = 'notequal';
this.bContains$ = false;
this.bDateTime$ = true;
this.bIsDate$ = false;
this.bIsDateTime$ = true;
this.bIsTime$ = false;
break;
case 'Time':
this.filterContainsType$ = 'notequal';
this.bContains$ = false;
this.bDateTime$ = true;
this.bIsDate$ = false;
this.bIsDateTime$ = false;
this.bIsTime$ = true;
break;
default:
this.filterContainsType$ = 'contains';
this.bContains$ = true;
this.bDateTime$ = false;
this.bIsDate$ = false;
this.bIsDateTime$ = false;
this.bIsTime$ = false;
break;
}
}
_filter(event, columnData) {
// add clickAway listener
window.addEventListener('mouseup', this._clickAway.bind(this));
this.currentFilterRefData = columnData;
this.filterContainsLabel$ = columnData.config.label;
setTimeout(() => {
this.updateFilterDisplay(columnData.type);
this.updateFilterVarsWithCurrent(columnData);
this.bShowFilterPopover$ = true;
});
}
_clickAway(event: any) {
let bInPopUp = false;
// run through list of elements in path, if menu not in th path, then want to
// hide (toggle) the menu
const eventPath = event.path;
for (let pathIndex = 0; pathIndex < eventPath.length; pathIndex++) {
if (
eventPath[pathIndex].className == 'psdk-modal-file-top' ||
eventPath[pathIndex].tagName == 'BUTTON' ||
eventPath[pathIndex].tagName == 'MAT-OPTION' ||
eventPath[pathIndex].tagName == 'MAT-INPUT'
) {
bInPopUp = true;
break;
}
}
if (!bInPopUp) {
// this.bShowFilterPopover$ = false;
window.removeEventListener('mouseup', this._clickAway.bind(this));
}
}
_filterContainsType(event) {
this.filterContainsType$ = event.value;
}
_filterContainsValue(event) {
this.filterContainsValue$ = event.target.value;
}
_filterContainsDateValue(event, value) {
this.filterContainsValue$ = value;
}
_filterContainsDateTimeValue(event) {
this.filterContainsValue$ = event.target.value;
}
_filterContainsTimeValue(event) {
this.filterContainsValue$ = event.target.value;
}
_onFilterActionButtonClick(event: any) {
// modal buttons
switch (event.action) {
case 'cancel':
this.currentFilterRefData = [];
break;
case 'submit':
this.updateFilterWithInfo();
this.filterSortGroupBy();
break;
default:
break;
}
this.bShowFilterPopover$ = false;
window.removeEventListener('mouseup', this._clickAway.bind(this));
}
updateFilterWithInfo() {
let bFound = false;
for (const filterObj of this.filterByColumns) {
if (filterObj.ref === this.currentFilterRefData.config.name) {
filterObj.type = this.currentFilterRefData.type;
filterObj.containsFilter = this.filterContainsType$;
filterObj.containsFilterValue = this.filterContainsValue$;
bFound = true;
break;
}
}
if (!bFound) {
// add in
const filterObj: any = {};
filterObj.ref = this.currentFilterRefData.config.name;
filterObj.type = this.currentFilterRefData.type;
filterObj.containsFilter = this.filterContainsType$;
filterObj.containsFilterValue = this.filterContainsValue$;
this.filterByColumns.push(filterObj);
}
// iterate through filters and update filterOn icon
for (const filterObj of this.filterByColumns) {
const containsFilterValue = filterObj.containsFilterValue;
const containsFilter = filterObj.containsFilter;
const filterRef = filterObj.ref;
const filterIndex = this.displayedColumns$.indexOf(filterRef);
const arFilterImg = document.getElementsByName('filterOnIcon');
const filterImg: any = arFilterImg[filterIndex];
if (containsFilterValue == '' && containsFilter != 'null' && containsFilter != 'notnull') {
// clear icon
filterImg.src = '';
} else {
// show icon
filterImg.src = this.filterOnSvgIcon$;
}
}
}
updateFilterVarsWithCurrent(columnData) {
// find current ref, if exists, move data to variable to display
let bFound = false;
for (const filterObj of this.filterByColumns) {
if (filterObj.ref === this.currentFilterRefData.config.name) {
this.filterContainsType$ = filterObj.containsFilter;
this.filterContainsValue$ = filterObj.containsFilterValue;
bFound = true;
break;
}
}
if (!bFound) {
switch (columnData.type) {
case 'Date':
case 'DateTime':
case 'Time':
this.filterContainsType$ = 'notequal';
break;
default:
this.filterContainsType$ = 'contains';
break;
}
this.filterContainsValue$ = '';
}
}
filterData(item: any) {
let bKeep = true;
for (const filterObj of this.filterByColumns) {
if (filterObj.containsFilterValue != '' || filterObj.containsFilter == 'null' || filterObj.containsFilter == 'notnull') {
let filterValue: any;
switch (filterObj.type) {
case 'Date':
case 'DateTime':
case 'Time':
bKeep = this.filterDataWithDate(item, filterObj, filterValue);
break;
default:
bKeep = this.filterDataWithCommonTypes(item, filterObj, filterValue);
break;
}
}
// if don't keep stop filtering
if (!bKeep) {
break;
}
}
return bKeep;
}
filterDataWithDate(item, filterObj, filterValue) {
let bKeep;
let value = item[filterObj.ref] != null || item[filterObj.ref] != '' ? getSeconds(item[filterObj.ref]) : null;
filterValue = filterObj.containsFilterValue != null && filterObj.containsFilterValue != '' ? getSeconds(filterObj.containsFilterValue) : null;
switch (filterObj.containsFilter) {
case 'notequal':
// becasue filterValue is in minutes, need to have a range of less than 60 secons
if (value != null && filterValue != null) {
// get rid of millisecons
value /= 1000;
filterValue /= 1000;
const diff = value - filterValue;
if (diff >= 0 && diff < 60) {
bKeep = false;
}
}
break;
case 'after':
if (value < filterValue) {
bKeep = false;
}
break;
case 'before':
if (value > filterValue) {
bKeep = false;
}
break;
case 'null':
if (value != null) {
bKeep = false;
}
break;
case 'notnull':
if (value == null) {
bKeep = false;
}
break;
default:
break;
}
return bKeep;
}
filterDataWithCommonTypes(item, filterObj, filterValue) {
let bKeep;
const value = item[filterObj.ref].toLowerCase();
filterValue = filterObj.containsFilterValue.toLowerCase();
switch (filterObj.containsFilter) {
case 'contains':
if (value.indexOf(filterValue) < 0) {
bKeep = false;
}
break;
case 'equals':
if (value != filterValue) {
bKeep = false;
}
break;
case 'startswith':
if (value.indexOf(filterValue) != 0) {
bKeep = false;
}
break;
default:
break;
}
return bKeep;
}
filterSortGroupBy() {
// get original data set
let theData = this.repeatListData.slice();
// last filter config data is global
theData = theData.filter(this.filterData.bind(this));
// last sort config data is global
theData.sort(this.sortCompare.bind(this));
const reGroupData = this.addGroups(theData, this.groupByColumns$);
this.repeatList$.data = [];
this.repeatList$.data.push(...reGroupData);
if (this.searchFilter && this.searchFilter != '') {
this.repeatList$.filter = this.searchFilter;
} else {
this.perfFilter = performance.now().toString();
this.repeatList$.filter = this.perfFilter;
}
this.repeatList$.filter = '';
if (this.repeatList$.paginator) {
this.repeatList$.paginator.firstPage();
}
}
//
// Grouping code inspired by this example
// https://stackblitz.com/edit/angular-material-table-row-grouping
//
_showUnGroupBy(columnData): boolean {
for (const val of this.groupByColumns$) {
if (val == columnData.config.name) {
return true;
}
}
return false;
}
_groupBy(event, columnData) {
this.checkGroupByColumn(columnData.config.name, true);
this.filterSortGroupBy();
}
_unGroupBy(event, columnData) {
// event.stopPropagation();
this.checkGroupByColumn(columnData.config.name, false);
this.filterSortGroupBy();
}
checkGroupByColumn(field, add) {
let found: number | null = null;
for (const column of this.groupByColumns$) {
if (column === field) {
found = this.groupByColumns$.indexOf(column, 0);
}
}
if (found != null && found >= 0) {
if (!add) {
this.groupByColumns$.splice(found, 1);
}
} else if (add) {
this.groupByColumns$.push(field);
}
}
addGroups(data: any[], groupByColumns: string[]): any[] {
const rootGroup = new Group();
rootGroup.expanded = true;
return this.getSublevel(data, 0, groupByColumns, rootGroup);
}
getSublevel(data: any[], level: number, groupByColumns: string[], parent: Group): any[] {
if (level >= groupByColumns.length) {
return data;
}
const groups = this.uniqueBy(
data.map(row => {
const result = new Group();
result.level = level + 1;
result.parent = parent;
for (let i = 0; i <= level; i++) {
result[groupByColumns[i]] = row[groupByColumns[i]];
}
return result;
}),
JSON.stringify
);
const currentColumn = groupByColumns[level];
let subGroups: any[] = [];
groups.forEach(group => {
const rowsInGroup = data.filter(row => group[currentColumn] === row[currentColumn]);
group.totalCounts = rowsInGroup.length;
const subGroup = this.getSublevel(rowsInGroup, level + 1, groupByColumns, group);
subGroup.unshift(group);
subGroups = subGroups.concat(subGroup);
});
return subGroups;
}
uniqueBy(a, key) {
const seen = {};
return a.filter(item => {
const k = key(item);
// eslint-disable-next-line no-return-assign, no-prototype-builtins
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
});
}
isGroup(index, item): boolean {
return item.level;
}
_groupHeaderClick(row) {
row.expanded = !row.expanded;
// this.repeatList$.filter = "";
this.perfFilter = performance.now().toString();
this.repeatList$.filter = this.perfFilter;
}
// below is for grid row grouping
customFilterPredicate(data: any | Group, filter: string): boolean {
return data instanceof Group ? data.visible : this.getDataRowVisibleWithFilter(data, filter);
}
getDataRowVisible(data: any): boolean {
const groupRows = this.repeatList$.data.filter(row => {
if (!(row instanceof Group)) {
return false;
}
let match = true;
this.groupByColumns$.forEach(column => {
if (!row[column] || !data[column] || row[column] !== data[column]) {
match = false;
}
});
return match;
});
if (groupRows.length === 0) {
return true;
}
const parent = groupRows[0] as Group;
return parent.visible && parent.expanded;
}
getDataRowVisibleWithFilter(data, filter) {
// fist check if row is visible with grouping
let bVisible = this.getDataRowVisible(data);
if (bVisible && filter && filter != '' && filter != this.perfFilter) {
// now check if row is filtered.
// assume not there unless we find it
bVisible = false;
for (const col of this.displayedColumns$) {
// filter is lower case
if (data[col] && data[col].toString().toLowerCase().indexOf(filter) >= 0) {
bVisible = true;
break;
}
}
}
return bVisible;
}
updateData(listData: any[], fieldData: any[]): any[] {
const returnList: any[] = new Array<any>();
listData.forEach(row => {
// copy
const rowData = JSON.parse(JSON.stringify(row));
for (let fieldIndex = 0; fieldIndex < fieldData.length; fieldIndex++) {
const config = fieldData[fieldIndex].config;
let fieldName;
let formattedDate;
let myFormat;
let theCurrencyOptions;
switch (fieldData[fieldIndex].type) {
case 'Date':
fieldName = config.name;
myFormat = config.formatter;
if (!myFormat) {
myFormat = 'Date';
}
formattedDate = this.utils.generateDate(rowData[fieldName], myFormat);
rowData[fieldName] = formattedDate;
break;
case 'DateTime':
fieldName = config.name;
myFormat = config.formatter;
if (!myFormat) {
myFormat = 'DateTime-Long';
}
formattedDate = this.utils.generateDateTime(rowData[fieldName], myFormat);
rowData[fieldName] = formattedDate;
break;
case 'Currency':
fieldName = config.name;
theCurrencyOptions = getCurrencyOptions(PCore.getEnvironmentInfo().getLocale() as string);
// eslint-disable-next-line no-case-declarations
const defaultOptions = {
locale: getLocale(),
position: 'before',
decPlaces: 2
};
// eslint-disable-next-line no-case-declarations
const params = { ...defaultOptions, ...theCurrencyOptions };
rowData[fieldName] = formatters.Currency(rowData[fieldName], params);
// val = format(value, column.type, theCurrencyOptions);
break;
default:
break;
}
}
returnList.push(rowData);
});
return returnList;
}
openAssignment(row) {
const { pxRefObjectClass, pzInsKey } = row;
const sTarget = this.pConn$.getContainerName();
const options: any = { containerName: sTarget };
this.pConn$.getActionsApi().openAssignment(pzInsKey, pxRefObjectClass, options);
}
openWork(row) {
const pxRefObjectClass = row.pxRefObjectClass || row.pxObjClass;
const { pxRefObjectKey } = row;
if (pxRefObjectClass !== '' && pxRefObjectKey !== '') {
this.pConn$.getActionsApi().openWorkByHandle(pxRefObjectKey, pxRefObjectClass);
}
}
initializeData(data) {
data.forEach((item, idx) => {
item.__original_index = idx;
item.__level = 1;
});
return data;
}
getType(field) {
const { config = {}, type } = field;
const { formatType } = config;
if (formatType === 'datetime' || formatType === 'date') {
// currently cosmos has only support for date ... it also need to support dateTime
return 'date';
}
return type.toLowerCase();
}
initializeColumns(fields = []) {
return fields.map((field: any, originalColIndex) => ({
...field,
type: this.getType(field),
name: field.config.value.substring(4),
label: field.config.label.substring(3),
id: originalColIndex,
groupingEnabled: true,
grouped: false,
minWidth: 50,
cellRenderer: this.getType(field) === 'text' ? null : field.type,
filter: true
}));
}
getHeaderCells(colFields, fields) {
const AssignDashObjects = ['Assign-Worklist', 'Assign-WorkBasket'];
return colFields.map((field: any, index) => {
let theField = field.config.value.substring(field.config.value.indexOf(' ') + 1);
if (theField.indexOf('.') === 0) {
theField = theField.substring(1);
}
const colIndex = fields.findIndex(ele => ele.name === theField);
const displayAsLink = field.config.displayAsLink;
const headerRow: any = {};
headerRow.id = fields[index].id;
headerRow.type = field.type;
headerRow.displayAsLink = displayAsLink;
headerRow.numeric = field.type === 'Decimal' || field.type === 'Integer' || field.type === 'Percentage' || field.type === 'Currency' || false;
headerRow.disablePadding = false;
headerRow.label = fields[index].label;
if (colIndex > -1) {
headerRow.classID = fields[colIndex].classID;
}
if (displayAsLink) {
headerRow.isAssignmentLink = AssignDashObjects.includes(headerRow.classID);
if (field.config.value?.startsWith('@CA')) {
headerRow.isAssociation = true;
}
}
return headerRow;
});
}
buildSelect(fieldDefs, colId, patchQueryFields = [], compositeKeys = []) {
const listFields: any = [];
if (colId) {
const field = this.getField(fieldDefs, colId);
listFields.push({
field: field.name
});
} else {
// NOTE: If we ever decide to not set up all the `fieldDefs` on select, ensure that the fields
// corresponding to `state.groups` are set up. Needed in Client-mode grouping/pagination.
fieldDefs.forEach(field => {
if (!listFields.find(f => f.field === field.name)) {
listFields.push({
field: field.name
});
}
});
patchQueryFields.forEach(k => {
if (!listFields.find(f => f.field === k)) {
listFields.push({
field: k
});
}
});
}
compositeKeys.forEach(k => {
if (!listFields.find(f => f.field === k)) {
listFields.push({
field: k
});
}
});
return listFields;
}
getResultsText() {
const recordsCount = this.repeatList$?.paginator?.length || 0;
return `${recordsCount || 0} result${recordsCount > 1 ? 's' : ''}`;
}
getField(fieldDefs, columnId) {
const fieldsMap = this.getFieldsMap(fieldDefs);
return fieldsMap.get(columnId);
}
getFieldsMap(fieldDefs) {
const fieldsMap = new Map();
fieldDefs.forEach(element => {
fieldsMap.set(element.id, element);
});
return fieldsMap;
}
addItemKeyInSelect(fieldDefs, itemKey, select, compositeKeys) {
const elementFound = this.getField(fieldDefs, itemKey);
if (
itemKey &&
!elementFound &&
Array.isArray(select) &&
!(compositeKeys !== null && compositeKeys?.length) &&
!select.find(sel => sel.field === itemKey)
) {
return [
...select,
{
field: itemKey
}
];
}
return select;
}
}