@pega/dx-component-builder-sdk
Version:
Utility for building custom UI components
1,021 lines (891 loc) • 33.8 kB
text/typescript
import { Component, OnInit, Input, ViewChild, forwardRef, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormGroup } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import isEqual from 'fast-deep-equal';
import { ComponentMapperComponent } from '@pega/angular-sdk-components';
import { AngularPConnectData, AngularPConnectService } from '@pega/angular-sdk-components';
import { DatapageService } from '@pega/angular-sdk-components';
import { getReferenceList } from '@pega/angular-sdk-components';
import { buildFieldsForTable, filterDataByCommonFields, filterDataByDate, getContext } from './helpers';
import { evaluateAllowRowAction } from '@pega/angular-sdk-components';
import { Utils } from '@pega/angular-sdk-components';
import { getSeconds } from '@pega/angular-sdk-components';
interface SimpleTableManualProps {
// If any, enter additional props that only exist on this component
visibility?: boolean;
grouping?: any;
referenceList?: any[];
children?: any[];
renderMode?: string;
presets?: any[];
label?: string;
showLabel?: boolean;
dataPageName?: string;
contextClass?: string;
propertyLabel?: string;
fieldMetadata?: any;
allowActions?: any;
allowTableEdit?: boolean;
allowRowDelete?: any;
editMode?: string;
addAndEditRowsWithin?: any;
viewForAddAndEditModal?: any;
editModeConfig?: any;
displayMode?: string;
useSeparateViewForEdit: any;
viewForEditModal: any;
targetClassLabel: string;
}
class Group {
level = 0;
parent: Group;
expanded = true;
totalCounts = 0;
get visible(): boolean {
return !this.parent || (this.parent.visible && this.parent.expanded);
}
}
export class SimpleTableManualComponent implements OnInit, OnDestroy {
sort: MatSort;
pConn$: typeof PConnect;
formGroup$: FormGroup;
// Used with AngularPConnect
angularPConnectData: AngularPConnectData = {};
configProps$: SimpleTableManualProps;
fields$: any[];
bVisible$ = true;
displayedColumns: string[] = [];
rowData: MatTableDataSource<any>;
originalData: any[] = [];
processedFields: any[] = [];
fieldDefs: any[] = [];
requestedReadOnlyMode = false;
readOnlyMode = false;
editableMode: boolean;
menuIconOverride$: string;
pageReference: string;
referenceList: any;
contextClass: any;
showAddRowButton: boolean;
prevReferenceList: any[] = [];
elementsData: MatTableDataSource<any>;
originalElementsData: MatTableDataSource<any>;
rawFields: any;
label?: string = '';
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;
groupByColumns$: string[] = [];
compareType: string;
compareRef: string;
arrowDirection = 'down';
filterByColumns: any[] = [];
currentFilterRefData: any;
filterContainsLabel$ = '';
filterContainsType$ = 'contains';
filterContainsValue$: any;
bShowFilterPopover$ = false;
bContains$ = true;
bDateTime$ = false;
bIsDate$ = false;
bIsDateTime$ = false;
bIsTime$ = false;
currentFilterImageEl: any;
arFilterMainButtons$: any[] = [];
arFilterSecondaryButtons$: any[] = [];
selectionMode: string;
singleSelectionMode: boolean;
multiSelectionMode: boolean;
rowID: any;
response: any;
compositeKeys: any;
parameters: any;
allowEditingInModal = false;
defaultView: any;
referenceListStr: any;
bUseSeparateViewForEdit: any;
editView: any;
settingsSvgIcon$: string;
isInitialized = false;
targetClassLabel: string;
localizedVal = PCore.getLocaleUtils().getLocaleValue;
localeCategory = 'SimpleTable';
constructor(
private angularPConnect: AngularPConnectService,
public utils: Utils,
private dataPageService: DatapageService
) {}
ngOnInit(): void {
this.elementsData = new MatTableDataSource<any>([]);
this.isInitialized = true;
// First thing in initialization is registering and subscribing to the AngularPConnect service
this.angularPConnectData = this.angularPConnect.registerAndSubscribeComponent(this, this.onStateChange);
this.configProps$ = this.pConn$.getConfigProps() as SimpleTableManualProps;
// Then, continue on with other initialization
this.menuIconOverride$ = this.utils.getImageSrc('trash', this.utils.getSDKStaticContentUrl());
// call checkAndUpdate when initializing
this.checkAndUpdate();
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.bGrouping$ = this.utils.getBooleanValue(this.configProps$.grouping);
this.menuSvgIcon$ = this.utils.getImageSrc('more', this.utils.getSDKStaticContentUrl());
this.arFilterMainButtons$.push({ actionID: 'submit', jsAction: 'submit', name: 'Submit' });
this.arFilterSecondaryButtons$.push({ actionID: 'cancel', jsAction: 'cancel', name: 'Cancel' });
this.searchIcon$ = this.utils.getImageSrc('search', this.utils.getSDKStaticContentUrl());
this.settingsSvgIcon$ = this.utils.getImageSrc('more', this.utils.getSDKStaticContentUrl());
}
ngOnDestroy(): void {
if (this.angularPConnectData.unsubscribeFn) {
this.angularPConnectData.unsubscribeFn();
}
}
checkAndUpdate() {
// Should always check the bridge to see if the component should
// update itself (re-render)
const bUpdateSelf = this.angularPConnect.shouldComponentUpdate(this);
// ONLY call updateSelf when the component should update
if (bUpdateSelf) {
this.updateSelf();
}
}
// updateSelf
updateSelf(): void {
// moved this from ngOnInit() and call this from there instead...
this.configProps$ = this.pConn$.resolveConfigProps(this.pConn$.getConfigProps()) as SimpleTableManualProps;
if (this.configProps$.visibility != null) {
// eslint-disable-next-line no-multi-assign
this.bVisible$ = this.bVisible$ = this.utils.getBooleanValue(this.configProps$.visibility);
}
// NOTE: getConfigProps() has each child.config with datasource and value undefined
// but getRawMetadata() has each child.config with datasource and value showing their unresolved values (ex: "@P thePropName")
// We need to use the prop name as the "glue" to tie the Angular Material table dataSource, displayColumns and data together.
// So, in the code below, we'll use the unresolved config.value (but replacing the space with an underscore to keep things happy)
const rawMetadata = this.pConn$.getRawMetadata();
// Adapted from Nebula
const {
referenceList = [], // if referenceList not in configProps$, default to empy list
renderMode,
children, // destructure children into an array var: "resolvedFields"
presets,
allowActions,
allowTableEdit,
allowRowDelete,
label: labelProp,
propertyLabel,
fieldMetadata,
editMode,
addAndEditRowsWithin,
viewForAddAndEditModal,
editModeConfig,
displayMode,
useSeparateViewForEdit,
viewForEditModal,
targetClassLabel
} = this.configProps$;
const simpleTableManualProps: any = {};
if (this.checkIfAllowActionsOrRowEditingExist(allowActions) && editMode) {
simpleTableManualProps.hideAddRow = allowActions?.allowAdd === false;
simpleTableManualProps.hideDeleteRow = allowActions?.allowDelete === false;
simpleTableManualProps.hideEditRow = allowActions?.allowEdit === false;
simpleTableManualProps.disableDragDrop = allowActions?.allowDragDrop === false;
} else if (allowTableEdit === false) {
simpleTableManualProps.hideAddRow = true;
simpleTableManualProps.hideDeleteRow = true;
simpleTableManualProps.disableDragDrop = true;
}
this.referenceListStr = getContext(this.pConn$).referenceListStr;
this.label = labelProp || propertyLabel;
this.parameters = fieldMetadata?.datasource?.parameters;
this.targetClassLabel = targetClassLabel;
let { contextClass } = this.configProps$;
this.referenceList = referenceList;
if (!contextClass) {
let listName = this.pConn$.getComponentConfig().referenceList;
listName = PCore.getAnnotationUtils().getPropertyName(listName);
contextClass = this.pConn$.getFieldMetadata(listName)?.pageClass;
}
this.contextClass = contextClass;
const resolvedFields = children?.[0]?.children || presets?.[0].children?.[0].children;
// get raw config as @P and other annotations are processed and don't appear in the resolved config.
// Destructure "raw" children into array var: "rawFields"
// NOTE: when config.listType == "associated", the property can be found in either
// config.value (ex: "@P .DeclarantChoice") or
// config.datasource (ex: "@ASSOCIATED .DeclarantChoice")
// Neither of these appear in the resolved (this.configProps$)
const rawConfig: any = rawMetadata?.config;
const rawFields = rawConfig?.children?.[0]?.children || rawConfig?.presets?.[0].children?.[0]?.children;
this.rawFields = rawFields;
// At this point, fields has resolvedFields and rawFields we can use
// start of from Nebula
// get context name and referenceList which will be used to prepare config of PConnect
// const { contextName, referenceListStr, pageReferenceForRows } = getContext(this.pConn$);
const resolvedList = getReferenceList(this.pConn$);
this.pageReference = `${this.pConn$.getPageReference()}${resolvedList}`;
this.pConn$.setReferenceList(resolvedList);
// This gives up the "properties" we need to map to row/column values later
// const processedData = populateRowKey(referenceList);
this.requestedReadOnlyMode = renderMode === 'ReadOnly';
this.readOnlyMode = renderMode === 'ReadOnly';
this.editableMode = renderMode === 'Editable';
const isDisplayModeEnabled = displayMode === 'DISPLAY_ONLY';
this.showAddRowButton = !this.readOnlyMode && !simpleTableManualProps.hideAddRow;
this.allowEditingInModal =
(editMode ? editMode === 'modal' : addAndEditRowsWithin === 'modal') && !(renderMode === 'ReadOnly' || isDisplayModeEnabled);
const showDeleteButton = this.editableMode && !simpleTableManualProps.hideDeleteRow && evaluateAllowRowAction(allowRowDelete, this.rowData);
this.defaultView = editModeConfig ? editModeConfig.defaultView : viewForAddAndEditModal;
this.bUseSeparateViewForEdit = editModeConfig ? editModeConfig.useSeparateViewForEdit : useSeparateViewForEdit;
this.editView = editModeConfig ? editModeConfig.editView : viewForEditModal;
// const showDeleteButton = !this.readOnlyMode && !hideDeleteRow;
// Nebula has other handling for isReadOnlyMode but has Cosmos-specific code
// so ignoring that for now...
// fieldDefs will be an array where each entry will have a "name" which will be the
// "resolved" property name (that we can use as the colId) though it's not really
// resolved. The buildFieldsForTable helper just removes the "@P " (which is what
// Nebula does). It will also have the "label", and "meta" contains the original,
// unchanged config info. For now, much of the info here is carried over from
// Nebula and we may not end up using it all.
this.fieldDefs = buildFieldsForTable(rawFields, resolvedFields, showDeleteButton);
this.initializeDefaultPageInstructions();
// end of from Nebula
// Here, we use the "name" field in fieldDefs since that has the assoicated property
// (if one exists for the field). If no "name", use "cellRenderer" (typically get DELETE_ICON)
// for our columns.
this.displayedColumns = this.fieldDefs?.map(field => {
return field.name ? field.name : field.cellRenderer;
});
// And now we can process the resolvedFields to add in the "name"
// from from the fieldDefs. This "name" is the value that
// we'll share to connect things together in the table.
this.processedFields = [];
this.processedFields = resolvedFields.map((field, i) => {
field.config.name = this.displayedColumns[i]; // .config["value"].replace(/ ./g,"_"); // replace space dot with underscore
return field;
});
// for adding rows to table when editable and not modal view
if (this.prevReferenceList.length !== this.referenceList.length) {
this.buildElementsForTable();
}
// for edit and adding rows in modal view and to generate readonly list
if (!isEqual(this.prevReferenceList, this.referenceList) && (this.readOnlyMode || this.allowEditingInModal)) {
this.generateRowsData();
}
this.prevReferenceList = this.referenceList;
// These are the data structures referred to in the html file.
// These are the relationships that make the table work
// displayedColumns: key/value pairs where key is order of column and
// value is the property shown in that column. Ex: 1: "FirstName"
// processedFields: key/value pairs where each key is order of column
// and each value is an object of more detailed information about that
// column.
// rowData: array of each row's key/value pairs. Inside each row,
// each key is an entry in displayedColumns: ex: "FirstName": "Charles"
// Ex: { 1: {config: {label: "First Name", readOnly: true: name: "FirstName"}}, type: "TextInput" }
// The "type" indicates the type of component that should be used for editing (when editing is enabled)
//
// Note that the "property" shown in the column ("FirstName" in the above examples) is what
// ties the 3 data structures together.
}
checkIfAllowActionsOrRowEditingExist(newflagobject) {
return (newflagobject && Object.keys(newflagobject).length > 0) || this.pConn$.getComponentConfig().allowRowEdit;
}
initializeDefaultPageInstructions() {
if (this.isInitialized) {
this.isInitialized = false;
if (this.allowEditingInModal) {
this.pConn$.getListActions().initDefaultPageInstructions(
this.pConn$.getReferenceList(),
this.fieldDefs.filter(item => item.name).map(item => item.name)
);
} else {
// @ts-ignore - An argument for 'propertyNames' was not provided.
this.pConn$.getListActions().initDefaultPageInstructions(this.pConn$.getReferenceList());
}
}
}
getResultsText() {
const recordsCount = this.readOnlyMode ? this.rowData?.data.length : this.referenceList?.length;
return `${recordsCount || 0} result${recordsCount > 1 ? 's' : ''}`;
}
sortCompare(a, b): number {
let aValue = a[0][this.compareRef];
let bValue = b[0][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;
if (eventPath) {
for (let eventIndex = 0; eventIndex < eventPath.length; eventIndex++) {
if (
eventPath[eventIndex].className == 'psdk-modal-file-top' ||
eventPath[eventIndex].tagName == 'BUTTON' ||
eventPath[eventIndex].tagName == 'MAT-OPTION' ||
eventPath[eventIndex].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(element: any) {
const item = element[0];
let bKeep = true;
for (const filterObj of this.filterByColumns) {
if (filterObj.containsFilterValue != '' || filterObj.containsFilter == 'null' || filterObj.containsFilter == 'notnull') {
switch (filterObj.type) {
case 'Date':
case 'DateTime':
case 'Time':
bKeep = filterDataByDate(item, filterObj);
break;
default:
bKeep = filterDataByCommonFields(item, filterObj);
break;
}
} else if (filterObj.containsFilterValue === '') {
bKeep = true;
}
// if don't keep stop filtering
if (!bKeep) {
break;
}
}
return bKeep;
}
filterSortGroupBy() {
let theData = this.originalData.slice().map((item, index) => {
return [item, index];
});
// 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));
this.rowData.data = theData.map(item => item[0]);
const newElements: any = new Array(this.rowData.data.length);
theData.forEach((item, index) => {
newElements[index] = this.originalElementsData[item[1]];
});
this.elementsData = newElements;
}
_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();
}
_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);
}
}
_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 '';
}
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.rowData.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.rowData.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;
}
getDisplayColumns(fields = []) {
return fields.map((field: any) => {
let theField = field.config.value.substring(field.config.value.indexOf(' ') + 1);
if (theField.indexOf('.') == 0) {
theField = theField.substring(1);
}
return theField;
});
}
_getIconStyle(level): string {
let sReturn = '';
let nLevel = parseInt(level, 10);
nLevel--;
nLevel *= 15;
sReturn = `padding-left: ${nLevel}px; vertical-align: middle`;
return sReturn;
}
// Callback passed when subscribing to store change
onStateChange() {
this.checkAndUpdate();
}
// return the value that should be shown as the contents for the given row data
// of the given row field
getRowValue(inRowData: Object, inColKey: string): any {
// See what data (if any) we have to display
const refKeys: string[] = inColKey.split('.');
let valBuilder = inRowData;
for (const key of refKeys) {
valBuilder = valBuilder[key];
}
return valBuilder;
}
generateRowsData() {
const { dataPageName, referenceList } = this.configProps$;
const context = this.pConn$.getContextName();
// if dataPageName property value exists then make a datapage fetch call and get the list of data.
if (dataPageName) {
this.dataPageService.getDataPageData(dataPageName, this.parameters, context).then(listData => {
const data = this.formatRowsData(listData);
this.originalData = data;
this.rowData = new MatTableDataSource(data);
});
} else {
// The referenceList prop has the JSON data for each row to be displayed
// in the table. So, iterate over referenceList to create the dataRows that
// we're using as the table's dataSource
const data = this.formatRowsData(referenceList);
this.originalData = data;
this.rowData = new MatTableDataSource(data);
}
}
formatRowsData(data) {
return data?.map(item => {
return this.displayedColumns.reduce((dataForRow, colKey) => {
dataForRow[colKey] = this.getRowValue(item, colKey);
return dataForRow;
}, {});
});
}
addRecord() {
if (this.allowEditingInModal && this.defaultView) {
this.pConn$
.getActionsApi()
.openEmbeddedDataModal(
this.defaultView,
this.pConn$ as any,
this.referenceListStr,
this.referenceList.length,
PCore.getConstants().RESOURCE_STATUS.CREATE,
this.targetClassLabel
);
} else {
this.pConn$.getListActions().insert({ classID: this.contextClass }, this.referenceList.length);
}
this.pConn$.clearErrorMessages({
property: this.pConn$.getStateProps()?.referenceList?.substring(1)
});
}
editRecord(data, index) {
if (data) {
this.pConn$
.getActionsApi()
.openEmbeddedDataModal(
this.bUseSeparateViewForEdit ? this.editView : this.defaultView,
this.pConn$ as any,
this.referenceListStr,
index,
PCore.getConstants().RESOURCE_STATUS.UPDATE,
this.targetClassLabel
);
}
}
deleteRecord(index) {
this.pConn$.getListActions().deleteEntry(index);
}
buildElementsForTable() {
const context = this.pConn$.getContextName();
const eleData: any = [];
this.referenceList.forEach((element, index) => {
const data: any = [];
this.rawFields?.forEach(item => {
item = {
...item,
config: { ...item.config, label: '', displayMode: this.readOnlyMode || this.allowEditingInModal ? 'DISPLAY_ONLY' : undefined }
};
const referenceListData = getReferenceList(this.pConn$);
const isDatapage = referenceListData.startsWith('D_');
const pageReferenceValue = isDatapage ? `${referenceListData}[${index}]` : `${this.pConn$.getPageReference()}${referenceListData}[${index}]`;
const config = {
meta: item,
options: {
context,
pageReference: pageReferenceValue,
referenceList: referenceListData,
hasForm: true
}
};
const view = PCore.createPConnect(config);
data.push(view);
});
eleData.push(data);
});
this.originalElementsData = eleData;
this.elementsData = eleData;
}
}