Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
* Github: [PR-576](https://github.com/pegasystems/angular-sdk-components/pull/576)
* **Added support for Data Object actions in the case view, and Submit/Cancel controls in the Data Object modal.**
* Github: [PR-577](https://github.com/pegasystems/angular-sdk-components/pull/577)
* **Added support for creating new records for the Autocomplete DataReference and CaseReference components.**
* Github: [PR-585](https://github.com/pegasystems/angular-sdk-components/pull/585)

### **Bug fixes**
* **Fixed DataReference not making an api call on state change.**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export class AngularPConnectService {

// const componentName = inComp.constructor.name;

// The following comment is from the Nebula/Constellation version of this code. Meant as a reminder to check this occasionally
// populate additional props which are component specific and not present in configurations
// This block can be removed once all these props will be added as part of configs
inComp.pConn$.populateAdditionalProps(compProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@
</div>
</mat-option>
</ng-template>
<div *ngIf="showCreateButton" class="psdk-autocomplete-create-new-wrapper">
<mat-divider></mat-divider>
<button
mat-button
type="button"
class="psdk-autocomplete-create-new"
(mousedown)="$event.preventDefault()"
(click)="createNewButtonHandler()"
>
<mat-icon>add</mat-icon>
{{ createNewLabel }}
</button>
</div>
</mat-autocomplete>
<mat-hint *ngIf="helperText" [appFieldWarning]="bFieldMessageVisible$">{{ helperText }}</mat-hint>
<mat-error *ngIf="fieldControl.invalid">{{ getErrorMessage() }}</mat-error>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,34 @@
white-space: normal;
}
}

::ng-deep .psdk-autocomplete-create-new-wrapper {
position: sticky;
bottom: 0;
z-index: 1;
background-color: var(--mat-sys-surface-container, var(--mat-sys-surface, #fff));

.mat-divider {
margin: 0;
}

.psdk-autocomplete-create-new {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 48px;
padding: 0 16px;
text-align: left;
text-transform: none;
color: var(--mat-sys-primary);

.mat-mdc-button-touch-target {
width: 100%;
}

.mat-icon {
margin-right: 8px;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Component, EventEmitter, OnInit, Output, forwardRef, inject } from '@angular/core';
import { Component, EventEmitter, OnInit, Output, ViewChild, forwardRef, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { MatOptionModule } from '@angular/material/core';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDividerModule } from '@angular/material/divider';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

Expand Down Expand Up @@ -41,6 +44,12 @@ interface AutoCompleteProps extends PConnFieldProps {
parameters?: any;
datasource: any;
columns: any[];
allowCreatingRecords?: boolean;
onCreateNew?: () => void;
createNewLabel?: string;
createNewRecord?: () => Promise<unknown>;
contextClass?: string;
referenceType?: string;
}

@Component({
Expand All @@ -54,6 +63,9 @@ interface AutoCompleteProps extends PConnFieldProps {
MatInputModule,
MatAutocompleteModule,
MatOptionModule,
MatButtonModule,
MatIconModule,
MatDividerModule,
FieldWarningDirective,
forwardRef(() => ComponentMapperComponent)
],
Expand All @@ -64,18 +76,29 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {

@Output() onRecordChange: EventEmitter<any> = new EventEmitter();

@ViewChild(MatAutocompleteTrigger) private autocompleteTrigger?: MatAutocompleteTrigger;

configProps$: AutoCompleteProps;

options$: AutoCompleteOption[];
listType: string;
columns: any[] = [];
parameters: {};
datasource: any;
filteredOptions: Observable<AutoCompleteOption[]>;
// Grouped view of filteredOptions, only rendered when hasGroupBy is true
groupedFilteredOptions$: Observable<AutoCompleteGroup[]>;
hasGroupBy = false;
filterValue = '';

// "Create new" footer button state
showCreateButton = false;
createNewLabel = 'Create new';
contextClass?: string;
referenceType?: string;
private onCreateNewFn?: () => void;
private createNewRecordFn?: () => Promise<unknown>;

// Override ngOnInit method
override async ngOnInit(): Promise<void> {
super.ngOnInit();
Expand Down Expand Up @@ -127,13 +150,22 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
this.updateComponentCommonProperties(this.configProps$);

// Set component specific properties
const { value, listType, parameters } = this.configProps$;
const { value, listType, parameters, allowCreatingRecords, onCreateNew, createNewLabel, createNewRecord, contextClass, referenceType } =
this.configProps$;

this.listType = listType;
this.parameters = parameters;

this.showCreateButton = allowCreatingRecords === true;
this.createNewLabel = createNewLabel || 'Create new';
this.contextClass = contextClass;
this.referenceType = referenceType;
this.onCreateNewFn = onCreateNew;
this.createNewRecordFn = createNewRecord;

const context = this.pConn$.getContextName();
const { columns, datasource } = this.generateColumnsAndDataSource();
this.datasource = datasource;

if (columns) {
this.columns = this.preProcessColumns(columns);
Expand Down Expand Up @@ -392,4 +424,127 @@ export class AutoCompleteComponent extends FieldBase implements OnInit {
this.onRecordChange.emit(value);
}
}

// Re-fetches the options list
refreshOptionsList(): void {
if (!this.displayMode$ && this.listType !== 'associated') {
const context = this.pConn$.getContextName();
this.dataPageService
.getDataPageData(this.datasource, this.parameters, context)
.then((results: any) => this.fillOptions(results))
.catch(e => console.error(e));
}
}

// Sets values for all columns that have setProperty defined
setValuesToAdditionalFields(record: Record<string, unknown>): void {
const setPropertyList = this.columns.filter(col => col.setProperty).map(col => ({ source: col.value, target: col.setProperty, key: col.key }));

setPropertyList.forEach(prop => {
let valueToSet: string;
if (prop.key === 'true') {
valueToSet = record[prop.source]?.toString() || (record as any).pyGUID || '';
} else {
valueToSet = record[prop.source]?.toString() || '';
}

if (prop.target === 'Associated property') {
handleEvent(this.actionsApi, 'changeNblur', this.propName, valueToSet);
} else {
const target = typeof prop.target === 'string' ? prop.target : '';
const targetProp = target.startsWith('.') ? target : `.${target}`;
(this.actionsApi as any).updateFieldValue(targetProp, valueToSet, { associatedProperty: this.propName });
(this.actionsApi as any).triggerFieldChange(targetProp, valueToSet);
}
});
}

createNewButtonHandler(): void {
// Close the options panel so it doesn't remain open on top of the create-new modal
this.autocompleteTrigger?.closePanel();

if (this.onCreateNewFn) {
this.onCreateNewFn();
return;
}

if (!this.contextClass) {
return;
}

const context = this.pConn$.getContextName();
const normalizedReferenceType = typeof this.referenceType === 'string' ? this.referenceType.toLowerCase() : '';
const isDataReference = normalizedReferenceType === 'data';
const { CREATE_STAGE_DONE } = PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS;
const DATA_OBJECT_CREATED = (PCore.getConstants().PUB_SUB_EVENTS as any).DATA_EVENTS?.DATA_OBJECT_CREATED;
const eventType = isDataReference && DATA_OBJECT_CREATED ? DATA_OBJECT_CREATED : CREATE_STAGE_DONE;
const contextClass = this.contextClass;

const createNewCallback = isDataReference
? (data: { data?: { responseData?: Record<string, unknown> } }) => {
// Clear contexted cache before re-fetching
PCore.getDataApi().clearContextedCache(context);

const responseData = data?.data?.responseData;
if (responseData) {
this.setValuesToAdditionalFields(responseData);
const displayColumn = this.getDisplayFieldsMetaData(this.columns);
const newKey = responseData[displayColumn.key]?.toString() || (responseData as any).pyGUID;
if (this.onRecordChange && newKey) {
this.onRecordChange.emit({ id: newKey });
}
}

this.refreshOptionsList();
PCore.getPubSubUtils().unsubscribe(eventType, contextClass);
}
: (data: { caseId?: string; caseType?: string; ID?: string }) => {
// Clear contexted cache before re-fetching
PCore.getDataApi().clearContextedCache(context);

const newCaseId = data.caseId?.split(' ').pop();
if (data.caseType === contextClass) {
const selectKey = data.ID || newCaseId;

if (selectKey && this.listType !== 'associated' && this.datasource) {
this.dataPageService
.getDataPageData(this.datasource, this.parameters, context)
.then((results: any) => {
this.fillOptions(results);

const displayColumn = this.getDisplayFieldsMetaData(this.columns);
const newRecord = results?.find((el: any) => el.ID === data.ID || (el[displayColumn.key] || el.pyGUID) === selectKey);
if (newRecord) {
this.setValuesToAdditionalFields(newRecord);
} else {
handleEvent(this.actionsApi, 'changeNblur', this.propName, selectKey);
}
if (this.onRecordChange) {
this.onRecordChange.emit({ id: selectKey });
}
})
.catch(e => console.error(e));
}
PCore.getPubSubUtils().unsubscribe(eventType, contextClass);
}
};

// Build the create action if createNewRecord fn is not provided
const triggerCreate = this.createNewRecordFn
? this.createNewRecordFn()
: isDataReference
? this.pConn$.getActionsApi().showDataObjectCreateView(contextClass)
: this.pConn$.getActionsApi().createWork(contextClass, {
openCaseViewAfterCreate: false,
startingFields: {}
});

Promise.resolve(triggerCreate)
.then(() => {
PCore.getPubSubUtils().subscribe(eventType, createNewCallback, contextClass);
// Re-initialize the list
this.refreshOptionsList();
})
.catch(e => console.error(e));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { ComponentMapperComponent } from '../../../_bridge/component-mapper/comp
export class CancelAlertComponent implements OnChanges {
@Input() pConn$: typeof PConnect;
@Input() bShowAlert$: boolean;
@Input() hideDelete: boolean;
@Input() isDataObject: boolean;
@Input() skipReleaseLockRequest: any;
@Output() onAlertState$: EventEmitter<boolean> = new EventEmitter<boolean>();

itemKey: string;
Expand Down Expand Up @@ -61,7 +64,6 @@ export class CancelAlertComponent implements OnChanges {
}

buttonClick({ action }) {
const actionsAPI = this.pConn$.getActionsApi();
this.localizedVal = PCore.getLocaleUtils().getLocaleValue;

switch (action) {
Expand All @@ -70,23 +72,44 @@ export class CancelAlertComponent implements OnChanges {
break;
case 'discard':
this.psService.sendMessage(true);

// eslint-disable-next-line no-case-declarations
const deletePromise = actionsAPI.deleteCaseInCreateStage(this.itemKey);

deletePromise
.then(() => {
this.psService.sendMessage(false);
this.dismissAlert();
PCore.getPubSubUtils().publish(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL);
})
.catch(() => {
this.psService.sendMessage(false);
this.sendMessage(this.localizedVal('Delete failed.', this.localeCategory));
});
this.handleDiscard();
break;
default:
break;
}
}

// Data objects and local/bulk actions don't have a create-stage case to delete, so each needs its own engine API
handleDiscard() {
const actionsAPI = this.pConn$.getActionsApi();
// @ts-ignore - Property 'options' is private and only accessible within class 'C11nEnv'.
const isBulkAction = (this.pConn$ as any)?.options?.isBulkAction;
const isLocalAction = this.pConn$.getValue(PCore.getConstants().CASE_INFO.IS_LOCAL_ACTION);

if (!this.isDataObject && !isLocalAction && !isBulkAction) {
actionsAPI
.deleteCaseInCreateStage(this.itemKey, this.hideDelete)
.then(() => {
this.psService.sendMessage(false);
this.dismissAlert();
PCore.getPubSubUtils().publish(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL);
})
.catch(() => {
this.psService.sendMessage(false);
this.sendMessage(this.localizedVal('Delete failed.', this.localeCategory));
});
} else if (isLocalAction) {
this.psService.sendMessage(false);
this.dismissAlert();
actionsAPI.cancelAssignment(this.itemKey, false);
} else if (isBulkAction) {
this.psService.sendMessage(false);
this.dismissAlert();
actionsAPI.cancelBulkAction(this.itemKey);
} else {
this.psService.sendMessage(false);
this.dismissAlert();
this.pConn$.getContainerManager().removeContainerItem({ containerItemID: this.itemKey, skipReleaseLockRequest: this.skipReleaseLockRequest });
}
}
}
Loading
Loading