import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Inject,
Input,
OnDestroy,
OnInit,
Optional,
Output,
QueryList,
TemplateRef,
ViewChild,
ViewChildren,
} from "@angular/core";
import {
ColumnPinningPosition,
DefaultSortingStrategy,
FilteringLogic,
GridSelectionRange,
IGridEditEventArgs,
IGroupingExpression,
IgxColumnComponent,
IgxGridBaseDirective,
IgxGridCell,
IgxGridComponent,
IgxOverlayOutletDirective,
IgxStringFilteringOperand,
IPinningConfig,
ISortingExpression,
ISortingStrategy,
RowPinningPosition,
SortingDirection,
IFilteringOperation,
NoopFilteringStrategy,
NoopSortingStrategy,
IgxHierarchicalGridComponent,
IgxTreeGridComponent,
DisplayDensity,
} from "@infragistics/igniteui-angular";
import { HttpClient } from "@angular/common/http";
import { BehaviorSubject, Observable, ReplaySubject, Subject } from "rxjs";
import { getOrDefault, throwIfUndefined } from "../../widget.configuration";
import { NgUnsubscribe } from "../../../ng-unsubscribe";
import { debounceTime, filter, first, take, takeUntil } from "rxjs/operators";
import {
LocalStorageEntry,
LocalStorageService,
} from "../../../components/local-storage/local-storage.service";
import {
DeletionMode,
Scope,
} from "../../../components/local-storage/local-storage-constants";
import { DataListService } from "../data-list.service";
import { SelectAttributesDialogComponent } from "../../../components/dialog/selectAttributesDialog.component";
import { MatMenuTrigger } from "@angular/material/menu";
import {
angularWidgetBridgeInput,
deepCopy,
flatCopy,
getBodyStyleCssProperty,
} from "../../../components/util/util.service";
import { CurrentLocaleService } from "../../../components/i18n/currentLocale.service";
import { LangChangeEvent, TranslateService } from "@ngx-translate/core";
import { WidgetframeService } from "../../widgetframe/widgetframe.service";
import { AppContext } from "../../../components/app-context/app.context";
import { AppdataStore } from "../../../components/appdata/appdata.store";
import {
Column,
ColumnMap,
DataListConfiguration,
DataListStatus,
DataListTreeConfiguration,
RecommendedAttributes,
SelectionParams,
SortBy,
} from "../../interfaces/list.interfaces";
import { ProgressbarService } from "../../../components/progressbar/progressbar.service";
import { DataListColumnsService } from "../data-list-columns.service";
import { EditAttributeService } from "../../../components/edit-attribute/edit-attribute.service";
import { DataListContextMenuComponent } from "../data-list-context-menu-component/data-list-context-menu.component";
import { DataListStateDirective } from "../data-list-state.directive";
import { ScrollService } from "../../../components/scroll/scroll.service";
import { DataListExportService } from "../data-list-export.service";
import { CustomNotificationService } from "../../../components/notification/customnotification.service";
import { ListCellEditComponent } from "../../../components/list-cell/list-cell-edit.component";
import { ListCellComponent } from "../../../components/list-cell/list-cell.component";
import { densityStorageConfig } from "../../layoutcontainer/layoutcontainer.component";
import { AppService } from "../../configuration/app.service";
import { ConfirmationDialogComponent } from "../../../components/dialog/confirmationDialog.component";
import { WebsocketService } from "../../../components/services/websocket.service";
import { UserService } from "../../../components/user/user.service";
import { DataListApi, DataListApiImpl } from "../data-list.api";
import {
Attribute,
Attributes,
} from "../../../components/edit-attribute/attribute";
import {
CellClassesCallbackRegistry,
ColumnSelectorCallback,
ColumnSelectorCallbackRegistry,
HeaderClassesCallbackRegistry,
} from "../../configuration";
import {
extractAttributeFromCell,
extractAttributeFromRow,
} from "../../../components/list-cell/attribute.util";
import { DataListEndEditService } from "../data-list-end-edit.service";
import { ValidationService } from "../../../components/validation";
import {
DataListActionParameter,
updateRowOrCell,
} from "../default-footer-component/default-data-list-footer.component";
import { DialogService, PageDialogComponent } from "../../../components/dialog";
import { ResizeObserver } from "@juggle/resize-observer";
import {
UnsavedChangesDialogConfiguration,
UnsavedChangesService,
} from "../../../components/util";
import {
NmModuleDescriptor,
NOVOMIND_MODULE_DESCRIPTOR,
} from "../../../../nm.module-descriptor";
import { UtilService } from "../../../components/util/util.service";
const DEFAULT_FRAME_HEIGHT_PX: number = 270;
const DEFAULT_MODIFIERS: string[] = [
"page-title",
"page-frame",
"data-list-frame",
"data-list-header",
"data-list-title",
"data-list-column-header",
];
const TABLE_FOOTER_MODIFIER: string = "data-list-footer";
const DEFAULT_EDIT_BAR_HEIGHT_PX: number = 26;
export const DEFAULT_CLIPBOARD_OPTIONS = {
enabled: true,
copyHeaders: false,
copyFormatters: false,
separator: "\t",
};
const UNSAVED_CHANGES_DIALOG_CONFIG: UnsavedChangesDialogConfiguration = {
allowCancel: false,
allowExitWithoutSaving: true,
allowSaveAndExit: true,
};
@Component({
selector: "nm-data-list-component",
templateUrl: "./data-list.component.html",
styleUrls: ["./data-list.component.scss"],
providers: [EditAttributeService],
})
export class DataListComponent implements OnInit, OnDestroy, AfterViewInit {
@Input()
public configuration: DataListConfiguration;
public tileIdentifierField: string;
@Input()
public widgetId: string;
@Input("uri")
public set uri(value) {
angularWidgetBridgeInput(value, this.inputChannel, this.unsubscribe);
}
@Input("resetEditData")
public set resetEditData(value) {
angularWidgetBridgeInput(
value,
this.resetEditDataChannel,
this.unsubscribe
);
}
@Input("locale")
public set locale(value) {
angularWidgetBridgeInput(value, this.localeChannel, this.unsubscribe);
}
@Input("reload")
public set reload(value) {
angularWidgetBridgeInput(value, this.reloadChannel, this.unsubscribe);
}
@Input("reset")
public set reset(value) {
angularWidgetBridgeInput(value, this.resetChannel, this.unsubscribe);
}
@Input("dataTypeMode")
public set dataTypeMode(value) {
angularWidgetBridgeInput(value, this.dataTypeModeChannel, this.unsubscribe);
}
@Input("data")
public set data(data) {
angularWidgetBridgeInput(data, this.dataChannel, this.unsubscribe);
}
@Input("contextIdentifier")
public set contextIdentifier(data) {
angularWidgetBridgeInput(
data,
this.contextIdentifierChannel,
this.unsubscribe
);
}
@Input("shownAttributesIn")
public set shownAttributes(data) {
angularWidgetBridgeInput(
data,
this.shownAttributesInputChannel,
this.unsubscribe
);
}
@Input("export")
public set export(settings) {
angularWidgetBridgeInput(settings, this.exportChannel, this.unsubscribe);
}
@Input("disableCellEditMode")
public set disableCellEditMode(value) {
angularWidgetBridgeInput(
value,
this.disableCellEditModeChannel,
this.unsubscribe
);
}
@Input("clearCellSelection")
public set clearCellSelection(value) {
angularWidgetBridgeInput(
value,
this.clearCellSelectionChannel,
this.unsubscribe
);
}
@Input("rowSelection")
public set rowSelection(value) {
angularWidgetBridgeInput(value, this.rowSelectionChannel, this.unsubscribe);
}
@Input("clearRowSelection")
public set clearRowSelection(value) {
angularWidgetBridgeInput(
value,
this.clearRowSelectionChannel,
this.unsubscribe
);
}
@Input("endGridEditMode")
public set endGridEditMode(value) {
angularWidgetBridgeInput(value, this.endGridEditChannel, this.unsubscribe);
}
@Input("clearFiltersAndSorting")
public set clearFiltersAndSorting(value) {
angularWidgetBridgeInput(
value,
this.clearFiltersAndSortingChannel,
this.unsubscribe
);
}
@Input("dataTypeInput")
public set dataTypeInput(value) {
angularWidgetBridgeInput(
value,
this.dataTypeInputChannel,
this.unsubscribe
);
}
@Input("eagerLimit")
public set eagerLimit(value) {
angularWidgetBridgeInput(value, this.eagerLimitChannel, this.unsubscribe);
}
@Input("attributeUrl")
public set attributeUrlChannelInput(value) {
angularWidgetBridgeInput(value, this.attributeUrlChannel, this.unsubscribe);
}
@Input("products")
public set products(value) {
angularWidgetBridgeInput(value, this.productChannel, this.unsubscribe);
}
@Input("refreshRows")
public set refreshRowInput(value) {
angularWidgetBridgeInput(value, this.refreshRows, this.unsubscribe);
}
@Input("restoreGlobalFilter")
public set restoreGlobalFilterInput(value) {
angularWidgetBridgeInput(value, this.restoreGlobalFilter, this.unsubscribe);
}
@Input("dynamicColumnsIn")
public set dynamicColumnsIn(value) {
angularWidgetBridgeInput(
value,
this.dynamicColumnsInSubject,
this.unsubscribe
);
}
@Input("additionalParams")
public set additionalParams(value) {
this.attributeUrlParams = value;
}
@Input("filterColumns")
public set filterColumns(value) {
angularWidgetBridgeInput(
value,
this.filterColumnsChannel,
this.unsubscribe
);
}
@Input("preselectAttributeProfile")
public set preselectAttributeProfileIn(value) {
angularWidgetBridgeInput(
value,
this.preselectAttributeProfileChannel,
this.unsubscribe
);
}
@Input("autoFocus") public autoFocus: boolean = false;
@Output("dynamicPrefix")
public dynamicPrefixOutput = new EventEmitter<string>();
@Output("dynamicColumns")
public dynamicColumnsOutput = new EventEmitter<Column[]>();
@Output("selectedItems")
public selectedItems = new EventEmitter<SelectionParams>();
@Output("shownAttributes")
public shownAttributesOutput = new EventEmitter<any[]>();
@Output("editedRows")
public editedRowsOutput = new EventEmitter<DataListEditedRows>();
@Output("editedRow")
public editedRowOutput = new EventEmitter<DataListEditedRow>();
@Output("total")
public totalOutput = new EventEmitter<any>();
@Output("gridOutput")
public gridOutput = new EventEmitter<any>();
@Output("viewMode")
public viewModeOutput = new EventEmitter<any>();
@Output("dataType")
public dataTypeOutput = new EventEmitter<any>();
@Output("isValid")
public isValidOutput = new EventEmitter<any>();
@Output("dataLoaded")
public dataLoadedOutput = new EventEmitter<any>();
@Output("currentData")
public currentData = new EventEmitter<any>();
@Output("editableCell")
public editableCell = new EventEmitter<any>();
@Output("rowEditEnter")
public rowEditEnterEmitter = new EventEmitter<any>();
@Output("rowEditLeave")
public rowEditLeaveEmitter = new EventEmitter<any>();
@Output("api")
public apiEmitter = new EventEmitter<DataListApi>();
@Output("toggleButtonChange")
public toggleButtonChangeEmitter = new EventEmitter<boolean>();
@Output("tileClicked")
public tileClickedEventEmitter = new EventEmitter<any>();
@Output("statusOut")
public statusOutput = new EventEmitter<DataListStatus>();
@Output("reorderedRows")
public reorderedRows = new EventEmitter<any>();
@Output("onDrop")
public onDropEmitter = new EventEmitter<any>();
@Output("onCellSelect")
public onCellSelect = new EventEmitter<any>();
public attributeUrlChannel = new Subject<string>();
public productChannel = new Subject<string[]>();
public refreshRows = new Subject<any[]>();
public dynamicColumnsInSubject = new Subject<any[]>();
private resetEditDataChannel = new Subject<void>();
private inputChannel: ReplaySubject<any> = new ReplaySubject<any>(1);
private reloadChannel: Subject<any> = new Subject<any>();
private localeChannel = new BehaviorSubject<any>(null);
private dataChannel = new ReplaySubject<any>(1);
private shownAttributesInputChannel = new ReplaySubject<any>(1);
private exportChannel = new Subject<any>();
public resetChannel = new Subject<any>();
public currentDataChannel = new ReplaySubject<any>(1);
public filterColumnsChannel = new Subject<any>();
public contextIdentifierChannel = new ReplaySubject<any>(1);
public restoreGlobalFilter = new BehaviorSubject<boolean>(false);
private disableCellEditModeChannel = new Subject<void>();
private clearCellSelectionChannel = new Subject<void>();
private clearRowSelectionChannel = new Subject<void>();
private rowSelectionChannel = new Subject<any>();
private endGridEditChannel = new Subject<void>();
private clearFiltersAndSortingChannel = new Subject<void>();
private preselectAttributeProfileChannel = new Subject<string>();
private preselectAttributeProfile: string;
public tableWidth = null;
public slidevalue: number = 3;
public slideChange = new Subject<number>();
public attributeUrl: string;
public attributeUrlParams: any = {};
public attributesDataType: string;
public cellEditModeActive: boolean = false;
// The column that was right clicked when opening the context menu
public contextMenuColumn: Column;
public dataTypeModeChannel = new BehaviorSubject<string>("choose");
public dataTypeInputChannel = new Subject<any>();
public groupingExpressions: IGroupingExpression[] = [];
public sortingExpressions: ISortingExpression[] = [];
public filterMode: string;
public selectOptionsUriChannel: ReplaySubject<any> = new ReplaySubject<any>(
1
);
public eagerLimitChannel: ReplaySubject<number> = new ReplaySubject<number>(
1
);
@ViewChild("table", { static: true }) public grid: IgxGridComponent;
@ViewChild("groupRowTemplate", { static: true })
public groupRowTemplate: TemplateRef<any>;
@ViewChild(MatMenuTrigger) menuTrigger: MatMenuTrigger;
@ViewChild(DataListContextMenuComponent, { static: true })
childComponentMenu: DataListContextMenuComponent;
@ViewChild(DataListStateDirective)
dataListStateDirective: DataListStateDirective;
@ViewChildren(ListCellEditComponent)
editableListCells: QueryList<ListCellEditComponent>;
@ViewChildren(ListCellComponent) listCells: QueryList<ListCellComponent>;
@ViewChildren("inputBox") inputBoxes: QueryList<ElementRef>;
@ViewChild(IgxOverlayOutletDirective, {
read: IgxOverlayOutletDirective,
static: true,
})
public outlet: IgxOverlayOutletDirective;
public pinningConfig: IPinningConfig = {
columns: ColumnPinningPosition.Start,
rows: RowPinningPosition.Top,
};
public isListMode: boolean = true;
private lastUriLocalStorage: LocalStorageEntry;
private localstorageShownAttributesEntry: LocalStorageEntry;
private localstorageColumnsEntry: LocalStorageEntry;
private viewModeLocalStorage: LocalStorageEntry;
private selectedDataTypeStorage: LocalStorageEntry;
public visibleDataColumns: string[];
public visibleDataColumnsStorage: LocalStorageEntry;
public visibleDataColumnsOptions: Observable<Column[]>;
unsubscribe = NgUnsubscribe.create();
public isEagerLoading: boolean = true;
public total;
public rowSelectable: boolean;
public rowSelectionMode: string;
public contextContent;
public contextValue;
public contextMenuCoordinates: any = {
x: 50,
y: 50,
};
public showFilterRow: boolean = false;
public infoText: string = "list.results";
public wikiLink: string;
public contentVisible: boolean = true;
public dataType;
public identifierCol;
public availableDataTypes: any[] = [];
public initialDataType: string;
public reloadOnDataTypeChange: boolean;
public columns: DataListColumnsService;
public listService: DataListService;
public columnSelector: ColumnSelectorCallback;
public widgetSortingStrategy = new WidgetSortingStrategy();
public viewModeOptions;
public selectedViewMode;
public globalFilterValue: string;
public globalFilterInput: boolean;
public selectedDataType: string;
public lastSelectedCells: GridSelectionRange;
public groupingEnabled: boolean;
public viewPortHeight: number =
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight;
public tableHeight: string;
public frameHeight: number = DEFAULT_FRAME_HEIGHT_PX;
public rowPinningEnabled: boolean = false;
public heightModifier: number = 0;
public tableGridHeight: string;
public tableGridWidth: string;
protected dataInitialized: boolean = false;
protected editedRows: DataListEditedRows = {};
public selectOptions;
private disableContextMenu: boolean;
private cellSelectable: boolean;
private getNextPageOnLazyLoad: boolean;
private pageLimitPerScroll: number;
private sortBy: SortBy[];
private tableHeightRelativeToRows: boolean;
private tableHeightVisibleRows: number;
public density: DisplayDensity = DisplayDensity.compact;
public _primaryKey: string;
public toggleButtonLabel: string;
public clipboardOptions: any;
public tileRedirectLink: string;
public overrideTileClickEvent: boolean;
public selectAllCheckbox: boolean = false;
public rowSelectionType = "multiple";
private clearSortingAndFilteringOnReset: boolean;
public storingEnabled: boolean = false;
public showAllColumns: boolean = true;
public hideFilters: boolean = false;
public showActionsInTableColumn: boolean = false;
public removeColumnHeader: boolean;
public noopFilterStrategy = NoopFilteringStrategy.instance();
public noopSortStrategy = NoopSortingStrategy.instance();
private customFilter = CustomFilteringOperand.instance();
constructor(
protected localStorageService: LocalStorageService,
protected dialog: DialogService,
protected notificationService: CustomNotificationService,
protected translateService: TranslateService,
public currentLocaleService: CurrentLocaleService,
protected widgetFrameService: WidgetframeService,
protected appContext: AppContext,
protected appdataStore: AppdataStore,
protected cdr: ChangeDetectorRef,
protected http: HttpClient,
protected progressbarService: ProgressbarService,
protected editAttributeService: EditAttributeService,
protected scrollService: ScrollService,
protected dataListExportService: DataListExportService,
protected websocketService: WebsocketService,
protected userService: UserService,
protected dataListEndEditService: DataListEndEditService,
protected validationService: ValidationService,
protected unsavedChangesService: UnsavedChangesService,
@Optional() protected appService: AppService,
@Inject(NOVOMIND_MODULE_DESCRIPTOR) private module: NmModuleDescriptor
) {}
protected dataLoaded(data) {
this.editedRowsOutput.next({});
this.editedRows = {};
if (this.grid) {
this.grid.reflow();
this.setStaticHeight();
}
this.currentData.emit(data);
if (this.configuration.visibleDataColumnsSelection) {
let showAll: boolean = false;
if (this.selectedDataType != "attributes") {
showAll = true;
}
this.onDataColumnsChange(this.visibleDataColumns, showAll);
}
if (this.grid) {
this.grid.verticalScroll.cdr.detectChanges();
this.grid.verticalScrollContainer.cdr.detectChanges();
this.grid.cdr.detectChanges();
this.cdr.detectChanges();
if (this.globalFilterValue) {
this.onFilterValueChange();
}
this.currentDataChannel.next(
this.grid.dataView ? this.grid.dataView : this.grid.data
);
this.onRowSelection({ newSelection: this.grid.selectedRows });
}
this.dataLoadedOutput.next();
this.autosizeColumns();
this.dataListStateDirective.restoreGridState();
}
private autosizeColumns() {
if (!this.grid) {
return;
}
const autosizableColumns = this.columns.autosizableColumns();
if (!autosizableColumns || autosizableColumns.length == 0) {
return;
}
this.grid.columnList
.filter((column: IgxColumnComponent) =>
autosizableColumns.includes(column.field)
)
.forEach((column: IgxColumnComponent) => {
if (!column.headerCell) {
return;
}
column.autosize(true);
});
}
public getGridColumnWidth(column: Column) {
if (!this.grid) {
return column.width;
}
const gridColumn = this.grid.getColumnByName(column.field);
return gridColumn?.width ? gridColumn?.width : column.width;
}
public setStaticHeight(newrows: number = 0) {
if (this.configuration.dynamicTableHeight) {
return;
}
if (
this.configuration.tableGridHeight ||
this.configuration.tableGridHeight === null
) {
return;
}
if (newrows === 0) {
newrows = this.tableHeightVisibleRows;
}
setTimeout(() => {
let height =
(this.grid.data.length + newrows <= 10
? this.grid.data.length + newrows
: 10) *
this.grid.rowHeight +
70;
height = height > 100 ? height : 100;
this.grid.height = height.toString() + "px";
this.grid.cdr.detectChanges();
}, 1);
}
public ngOnInit() {
this._primaryKey = this.primaryKey(this.configuration);
this.toggleButtonLabel = getOrDefault(
this.configuration.toggleButtonLabel,
""
);
this.tileRedirectLink = this.configuration.tileRedirectLink;
this.overrideTileClickEvent = getOrDefault(
this.configuration.overrideTileClickEvent,
false
);
this.tileIdentifierField = getOrDefault(
this.configuration.tileIdentifierField,
"identifier"
);
this.attributeUrl = this.configuration.attributeUrl;
this.attributesDataType = this.configuration.attributesDataType;
this.tableWidth = getOrDefault(this.configuration.tableWidth, "100%");
this.frameHeight = getOrDefault(
this.configuration.frameHeight,
DEFAULT_FRAME_HEIGHT_PX
);
this.tableHeightRelativeToRows = getOrDefault(
this.configuration.tableHeightRelativeToRows,
false
);
this.removeColumnHeader = getOrDefault(
this.configuration.removeColumnHeader,
false
);
this.tableHeightVisibleRows = getOrDefault(
this.configuration.tableHeightVisibleRows,
0
);
this.applyDensityFromLocalStorage();
this.availableDataTypes = getOrDefault(
this.configuration.availableDataTypes,
[]
);
this.columns = new DataListColumnsService(
<DataListConfiguration>this.configuration,
this.appService,
this.translateService
);
this.listService = new DataListService(
this.http,
this.progressbarService,
this.notificationService,
this.translateService,
this.columns,
this.websocketService,
this.localstorageShownAttributesEntry,
this.userService
);
if (this.localeChannel.value) {
this.listService.selectedLocale = this.localeChannel.value;
}
this.columns.dynamicColumns
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => this.dynamicColumnsOutput.next(data));
this.listService.totalSubject
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => this.totalOutput.next(data));
this.rowSelectable = getOrDefault(this.configuration.rowSelectable, true);
this.rowSelectionMode = getOrDefault(
this.configuration.rowSelectionMode,
"multiple"
);
this.isEagerLoading =
"eager" == getOrDefault(this.configuration.loadingMode, "eager");
this.globalFilterInput = getOrDefault(
this.configuration.globalFilterInput,
true
);
this.groupingEnabled = getOrDefault(
this.configuration.groupingEnabled,
true
);
this.disableContextMenu = getOrDefault(
this.configuration.disableContextMenu,
false
);
this.cellSelectable = getOrDefault(this.configuration.cellSelectable, true);
this.getNextPageOnLazyLoad = getOrDefault(
this.configuration.getNextPageOnLazyLoad,
false
);
this.rowPinningEnabled = getOrDefault(
this.configuration.rowPinningEnabled,
false
);
this.pageLimitPerScroll = this.configuration.pageLimitPerScroll;
this.initialDataType = this.configuration.initialDataType;
//quickFilter || excelStyleFilter || inlineFilter
this.filterMode = getOrDefault(
this.configuration.filterMode,
"excelStyleFilter"
);
this.reloadOnDataTypeChange = getOrDefault(
this.configuration.reloadOnDataTypeChange,
true
);
this.clearSortingAndFilteringOnReset = getOrDefault(
this.configuration.clearSortingAndFilteringOnReset,
false
);
this.tableGridHeight = getOrDefault(
this.configuration.tableGridHeight,
"100%"
);
this.tableGridWidth = getOrDefault(
this.configuration.tableGridWidth,
"100%"
);
if (this.isEagerLoading && this.configuration.eagerLimit) {
this.listService.setEagerLimit(this.configuration.eagerLimit);
}
if (this.configuration.visibleDataColumnsSelection) {
this.visibleDataColumnsStorage =
this.localStorageService.getLocalStorageEntry(
"visibleDataColumns" + this.configuration.title,
Scope.USER_AND_CLIENT,
DeletionMode.RESET
);
if (this.visibleDataColumnsStorage.exists()) {
this.visibleDataColumns = JSON.parse(
this.visibleDataColumnsStorage.value
);
} else {
this.visibleDataColumns = this.configuration.visibleDataColumnsDefault;
}
}
if (this.availableDataTypes.length > 0) {
this.selectedDataType = this.availableDataTypes[0];
}
if (this.dataTypeModeChannel.value === "choose") {
this.selectedDataTypeStorage =
this.localStorageService.getLocalStorageEntry(
"selectedDataType" + this.configuration.title,
Scope.USER_AND_CLIENT,
DeletionMode.RESET
);
}
if (this.selectedDataTypeStorage.exists()) {
this.selectedDataType = JSON.parse(this.selectedDataTypeStorage.value);
} else if (this.initialDataType) {
this.selectedDataType = this.initialDataType;
} else {
this.selectedDataType = this.availableDataTypes[0];
}
this.listService.initialize(
this.grid,
this.isEagerLoading,
this.configuration,
(data) => this.dataLoaded(data),
this.cdr,
this.selectedDataType
);
this.dataTypeOutput.next(this.selectedDataType);
this.listService.setNextPage(this.getNextPageOnLazyLoad);
this.listService.setPageLimit(this.pageLimitPerScroll);
this.gridOutput.emit(this.grid);
if (this.configuration.columnSelector) {
this.columnSelector = ColumnSelectorCallbackRegistry.get(
this.configuration.columnSelector
);
}
this.viewModeLocalStorage = this.localStorageService.getLocalStorageEntry(
this.configuration["title"] + "-viewMode",
Scope.USER_AND_CLIENT,
DeletionMode.RESET
);
if (this.configuration.viewModeSelection) {
if (this.configuration.viewModeOptions) {
this.viewModeOptions = this.configuration.viewModeOptions;
} else if (this.configuration.viewModeSelection) {
this.viewModeOptions = [];
this.viewModeOptions.push({
identifier: "listView",
description: "button.list-view",
icon: "view_list",
});
this.viewModeOptions.push({
identifier: "tileView",
description: "button.tile-view",
icon: "view_module",
});
}
if (this.viewModeLocalStorage.exists()) {
this.selectedViewMode = this.viewModeOptions.filter(
(mode) =>
mode.identifier === JSON.parse(this.viewModeLocalStorage.value)
)[0];
}
if (!this.selectedViewMode && this.configuration.viewModeSelection) {
this.selectedViewMode = this.viewModeOptions[0];
}
this.viewModeOutput.next(this.selectedViewMode.identifier);
this.listService.setViewmode(this.selectedViewMode.identifier);
}
this.setDefaultGroupingExpression();
this.setDefaultSortingExpressions();
if (this.configuration.pinnedRows) {
this.configuration.pinnedRows.forEach((row) => this.grid.pinRow(row));
}
this.lastUriLocalStorage = this.localStorageService.getLocalStorageEntry(
"last-uri-1" + this.configuration.title,
Scope.USER_AND_CLIENT,
DeletionMode.RESET
);
if (this.configuration.localStorageShownAttributes) {
this.localstorageShownAttributesEntry =
this.localStorageService.getLocalStorageEntry(
this.configuration.localStorageShownAttributes,
Scope.USER_AND_CLIENT,
DeletionMode.NEVER
);
}
if (this.configuration.dynamicColumnLink) {
this.widgetFrameService
.getData(this.configuration.dynamicColumnLink)
.subscribe((attributes) => {
// TODO This wont play nicely with localstorage recovery, since it will just override it :(
this.columns.updateColumns(attributes._embedded.attributes);
});
}
this.clipboardOptions = Object.assign(
{},
DEFAULT_CLIPBOARD_OPTIONS,
this.configuration.clipboardOptions
);
this.tableHeight = this.getTableHeight();
this.localeChannel.pipe(takeUntil(this.unsubscribe)).subscribe((locale) => {
this.updateDataLocale(locale);
});
if (this.configuration.exportPath) {
this.dataListExportService.exportPath = this.configuration.exportPath;
}
this.scrollService
.isToolBoxEditBarVisible()
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
if (this.configuration.tableHeight) {
return;
}
if (data && data.active && !!data.context) {
this.heightModifier = DEFAULT_EDIT_BAR_HEIGHT_PX;
} else {
this.heightModifier = 0;
}
// this.tableHeight = String(this.viewPortHeight - this.frameHeight - this.heightModifier) + 'px';
this.cdr.markForCheck();
});
this.scrollService
.getChangeViewPortSize()
.pipe(takeUntil(this.unsubscribe))
.subscribe((newViewPortHeight) => {
this.viewPortHeight = newViewPortHeight;
// Waiting for sidebar animation to finish to adjust width;
setTimeout(() => {
this.grid.reflow();
}, 200);
});
this.localstorageColumnsEntry =
this.localStorageService.getLocalStorageEntry(
"ColumnsEntry",
Scope.USER_AND_CLIENT,
DeletionMode.NEVER
);
if (
this.localstorageShownAttributesEntry &&
this.localstorageShownAttributesEntry.exists()
) {
this.columns.shownAttributes = JSON.parse(
this.localstorageShownAttributesEntry.value
);
this.columns.updateColumns(this.columns.shownAttributes);
this.shownAttributesOutput.next(this.getShownAttributes());
}
if (this.localstorageColumnsEntry.exists()) {
this.grid.columnList["_results"] = JSON.parse(
this.localstorageColumnsEntry.value
);
}
this.appdataStore.getAppdata().subscribe((appData) => {
this.showActionsInTableColumn =
appData[this.module.identifier].configuration.showActionsInTableColumn;
});
this.subscribeToInputs();
this.sortBy = this.configuration.sortBy;
this.grid.outlet = this.outlet;
//Disable filter and sorting strategy if remote loading
if (!this.isEagerLoading) {
this.grid.sortStrategy = this.noopSortStrategy;
this.grid.filterStrategy = this.noopFilterStrategy;
}
}
protected updateDataLocale(locale) {
if (this.listService) {
this.listService.selectedLocale = locale;
this.listService.processData(true);
}
}
protected updateLocale(locale) {
this.listService.reloadGrid();
this.columns.updateDynamicColumnsHeaders(locale);
}
private setDefaultGroupingExpression() {
if (this.configuration.groupingExpressions) {
this.configuration.groupingExpressions.forEach((expression) => {
const dir =
expression.direction === "asc"
? SortingDirection.Asc
: SortingDirection.Desc;
let strategy = DefaultSortingStrategy.instance();
this.groupingExpressions.push({
dir,
fieldName: expression.field,
ignoreCase: false,
strategy,
groupingComparer: strategy.compareValues,
});
});
}
}
private setDefaultSortingExpressions() {
if (this.configuration.sortingExpressions) {
this.configuration.sortingExpressions.forEach((expression) => {
const dir =
expression.direction === "asc"
? SortingDirection.Asc
: SortingDirection.Desc;
this.sortingExpressions.push({
dir,
fieldName: expression.field,
});
});
}
}
protected clearSelectedRows() {
this.selectedItems.next(null);
if (this.grid.data) {
this.grid.deselectAllRows();
}
}
protected doReset() {
this.clearSelectedRows();
this.listService.reset();
}
public doReload(force: boolean = false) {
this.unsavedChangesService
.leaveContext(UNSAVED_CHANGES_DIALOG_CONFIG)
.subscribe((response) => {
if (!response) {
return;
}
this.listService.reloadGrid(force);
if (this.grid.data) {
this.grid.deselectAllRows();
this.onRowSelection({ newSelection: this.grid.selectedRows });
}
});
}
public doRefresh() {
let selectedRows = this.grid.selectedRows;
this.listService.reloadGrid();
if (selectedRows) {
this.grid.selectRows(selectedRows, true);
this.onRowSelection({ newSelection: this.grid.selectedRows });
}
}
public onDataTypeChange(reload: boolean = true) {
this.unsavedChangesService
.leaveContext(UNSAVED_CHANGES_DIALOG_CONFIG)
.subscribe((response) => {
if (!response) {
return;
}
if (this.grid.data) {
this.grid.deselectAllRows();
}
this.selectedDataTypeStorage.value = JSON.stringify(
this.selectedDataType
);
this.listService.setDataType(
this.selectedDataType,
this.reloadOnDataTypeChange && reload
);
this.dynamicPrefixOutput.next(this.getDynamicPrefix());
this.dataTypeOutput.next(this.selectedDataType);
this.emitDataListStatus("dataType");
});
}
private emitDataListStatus(lastChange: string) {
this.statusOutput.next({
lastChange,
attributes: this.getShownAttributes(),
dataType: this.selectedDataType,
});
}
public ngAfterViewInit() {
this.updateTranslations();
if (this.selectedViewMode) {
this.onViewModeChange();
}
this.grid.groupRowTemplate = this.groupRowTemplate;
setTimeout(() => {
this.grid.reflow();
}, 200);
if (this.sortBy) {
this.doInitialSorting();
}
this.apiEmitter.next(new DataListApiImpl(this, this.configuration));
this.emitDataListStatus(null);
this.dataChannel.pipe(takeUntil(this.unsubscribe)).subscribe((value) => {
if (this.listService) {
this.listService.updateData(value, 0, true);
this.listService.totalSubject.next(value.length);
this.currentData.emit(value);
this.setStaticHeight();
this.grid.cdr.detectChanges();
this.cdr.detectChanges();
this.validateList(null, null);
}
});
this.emitDataListStatus(null);
}
private doInitialSorting() {
let sortingExpressions: ISortingExpression[] = [];
this.sortBy.forEach((element) => {
let columnId = element.field;
let sortDirection = element.direction;
if (sortDirection && columnId) {
let direction = this.getSortDirection(sortDirection.toLowerCase());
sortingExpressions.push({
fieldName: columnId,
dir: direction,
ignoreCase: true,
});
}
});
this.grid.sortingExpressions = sortingExpressions;
}
private getSortDirection(direction) {
if (direction === "asc") {
return SortingDirection.Asc;
} else if (direction === "desc") {
return SortingDirection.Desc;
} else {
return SortingDirection.None;
}
}
protected subscribeToInputs() {
this.shownAttributesInputChannel
.pipe(
takeUntil(this.unsubscribe),
filter((data) => !!data)
)
.subscribe((attributes) => {
this.updateDynamicColumns(attributes);
});
this.reloadChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((force) => {
this.doReload(force);
});
this.dataTypeModeChannel.pipe(takeUntil(this.unsubscribe)).subscribe(() => {
this.dynamicPrefixOutput.next(this.getDynamicPrefix());
});
this.inputChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((link) => {
this.storingEnabled = false;
if (
this.configuration.reloadWithUnsavedChanges === false &&
this.editedRows &&
Object.keys(this.editedRows).length !== 0
) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent);
dialogRef.componentInstance.message = this.translateService.instant(
"message.confirmation.unsavedChanges"
);
dialogRef.componentInstance.title =
this.translateService.instant("hl.confirmation");
dialogRef.componentInstance.buttonAcceptTitle = "button.accept";
dialogRef.componentInstance.buttonCancelTitle = "button.cancel";
dialogRef.afterClosed().subscribe((data) => {
if (data) {
this.handleInput(link);
}
});
return;
}
this.handleInput(link);
});
this.refreshRows.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
const primaryKey = this.primaryKey(this.configuration);
const keys = data.map((entry) => entry[primaryKey]);
this.reRender(keys);
});
this.dynamicColumnsInSubject
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
//Treat these columns as if those are coming from the backend,
// since columns coming from the backend will always be accepted
this.columns.updateColumns(data, true);
});
this.productChannel.pipe(takeUntil(this.unsubscribe)).subscribe((data) => {
this.listService.products = data;
});
this.attributeUrlChannel
.pipe(takeUntil(this.unsubscribe))
.subscribe((url) => (this.attributeUrl = url));
this.resetEditDataChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.editedRows = {};
this.editedRowsOutput.next({});
this.currentData.emit(this.grid.data);
});
this.resetChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.doReset();
if (this.sortBy) {
this.doInitialSorting();
}
if (this.clearSortingAndFilteringOnReset) {
this.clearAllFiltersAndSorting();
}
});
this.exportChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
this.onExport(data);
});
this.currentLocaleService
.getCurrentLocale()
.pipe(takeUntil(this.unsubscribe))
.subscribe((locale) => {
this.updateLocale(locale);
});
this.translateService.onLangChange
.pipe(takeUntil(this.unsubscribe))
.subscribe((transObj) => {
this.updateTranslations(transObj);
});
this.disableCellEditModeChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
const selectedCells = this.grid.selectedCells;
Object.keys(selectedCells).forEach((key) => {
let cell = this.grid.getCellByColumn(
selectedCells[key].cellID.rowIndex,
selectedCells[key].column.field
);
cell.editMode = false;
});
});
this.clearCellSelectionChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
if (this.grid.selectedCells.length > 0) {
this.grid.clearCellSelection();
}
});
this.clearRowSelectionChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.clearSelectedRows();
});
this.rowSelectionChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((rows) => {
if (rows) {
this.grid.deselectAllRows();
this.grid.selectRows(rows);
this.onRowSelection({ newSelection: this.grid.selectedRows });
}
});
this.clearFiltersAndSortingChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.clearAllFiltersAndSorting();
});
this.endGridEditChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.grid.endEdit(true);
});
this.listService.dataLoaded
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
if (!this.dataInitialized) {
this.dataInitialized = true;
} else if (this.isEagerLoading && this.tableHeightRelativeToRows) {
return;
}
});
this.dataTypeInputChannel
.pipe(takeUntil(this.unsubscribe))
.subscribe((dataType) => {
this.selectedDataType = dataType;
this.onDataTypeChange(this.reloadOnDataTypeChange);
});
this.eagerLimitChannel
.pipe(takeUntil(this.unsubscribe))
.subscribe((eagerLimit) => {
this.listService.setEagerLimit(eagerLimit);
});
this.filterColumnsChannel
.pipe(takeUntil(this.unsubscribe))
.subscribe((columns) => {
this.tableWidth = columns.length === 1 ? null : "100%";
this.grid.columns.forEach((column) => {
column.hidden = columns.indexOf(column.field) === -1;
return column;
});
this.grid.reflow();
this.grid.cdr.detectChanges();
});
this.preselectAttributeProfileChannel
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((profile) => (this.preselectAttributeProfile = profile));
}
protected handleInput(link) {
this.grid.navigateTo(0, 0);
this.clearAllFiltersAndSorting(true, false, this.restoreGlobalFilter.value);
this.onInput(link);
if (this.sortBy) {
this.doInitialSorting();
}
}
public reRender(ids: string[]) {
const primaryKey = this.primaryKey(this.configuration);
this.listCells
.filter((cell) => ids.indexOf(cell.row[primaryKey]) !== -1)
.forEach((cell) => {
cell.update();
});
}
protected onInput(link) {
if (this.columns && this.columns.shownAttributes) {
this.columns.updateColumns(this.columns.shownAttributes);
}
this.listService.updateLink(link);
}
public primaryKey(configuration: DataListTreeConfiguration): string {
return configuration.primaryKey || "identifier";
}
public onRowEdit(
event: IGridEditEventArgs,
configuration: DataListConfiguration = this.configuration,
parent: string = null
) {
this.rowEditLeaveEmitter.next();
if (!configuration.rowEditable && parent == null) {
return;
}
const attributes = {};
for (const identifier in event.newValue) {
if (!event.newValue.hasOwnProperty(identifier)) {
continue;
}
if (!event.oldValue.hasOwnProperty(identifier)) {
attributes[identifier] = event.newValue[identifier];
continue;
}
let oldChanges =
event.oldValue[identifier] && event.oldValue[identifier].changes
? event.oldValue[identifier].changes
: 0;
let newChanges =
event.newValue[identifier] && event.newValue[identifier].changes
? event.newValue[identifier].changes
: 0;
if (oldChanges != newChanges) {
attributes[identifier] = event.newValue[identifier];
}
}
this.fireEditedRowsEvent(
event.rowID,
event.newValue,
parent,
attributes,
configuration
);
}
onCellEdit(
event: IGridEditEventArgs,
configuration: DataListConfiguration = this.configuration
) {
if (configuration.rowEditable) {
return;
}
let row = this.grid.getRowByKey(event.rowID);
if (
event.oldValue &&
(event.oldValue === event.newValue ||
(event.oldValue.value && event.oldValue.value === event.newValue.value))
) {
return;
}
// try to find identifier, even if the new value is null/undefined
let value = event.newValue || event.oldValue;
let identifier = null;
if (value) {
identifier = value.identifier ? value.identifier : value;
}
if (!value || !identifier) {
return;
}
const attributes = { [identifier]: event.newValue };
this.fireEditedRowsEvent(
event.rowID,
row.rowData,
null,
attributes,
configuration
);
}
public fireEditedRowsEvent(
rowId: string,
gridRow: any,
parent: string,
attributes,
configuration: DataListConfiguration
) {
this.setEditedRowsData(rowId, gridRow, parent, attributes, configuration);
this.editedRowsOutput.next(this.editedRows);
this.currentData.emit(this.grid.data);
}
protected setEditedRowsData(
rowId: string,
row: any,
parent: string,
attributes,
configuration: DataListConfiguration
) {
const metadata = configuration.metadataKeys
? configuration.metadataKeys.map((key) => row[key])
: [];
if (this.editedRows[rowId]) {
Object.keys(attributes).forEach((key) => {
this.editedRows[rowId].attributes[key] = attributes[key];
this.editedRows[rowId].row = row;
});
} else {
this.editedRows[rowId] = { parent, attributes, row };
//Hide metadata from the public interface since it should no longer be used (you can always directly access the row on the event object
this.editedRows[rowId]["metadata"] = metadata;
}
this.editedRowOutput.next(this.editedRows[rowId]);
}
private updateTranslations(transObj?: LangChangeEvent) {
if (!this.grid) {
return;
}
Object.keys(this.grid.resourceStrings).forEach((key) => {
let value = transObj
? transObj.translations[key]
: this.translateService.instant(key);
if (value) {
this.grid.resourceStrings[key] = value;
}
});
}
public editShownAttributes() {
this.unsavedChangesService
.leaveContext(UNSAVED_CHANGES_DIALOG_CONFIG)
.subscribe((response) => {
if (!response) {
return;
}
this.openDialogForShownAttributes();
});
}
public openDialogForShownAttributes() {
let dialogConf = {
minWidth: "900px",
maxWidth: "900px",
height: this.configuration.profileUrl ? "860px" : "755px",
};
let dialogRef = this.dialog.open(
SelectAttributesDialogComponent,
dialogConf
);
dialogRef.componentInstance.infoText = "infoText.select.attributes";
dialogRef.componentInstance.showRestrictToCategoryButton = getOrDefault(
this.configuration.showRestrictToCategoryButton,
false
);
dialogRef.componentInstance.profileUrl = this.configuration.profileUrl;
dialogRef.componentInstance.preselectAttributeProfile =
this.preselectAttributeProfile;
dialogRef.componentInstance.restrictAttributesToCategoryOnAttributeDialogOpen =
getOrDefault(
this.configuration.restrictAttributesToCategoryOnAttributeDialogOpen,
false
);
dialogRef.componentInstance.preselectedAttributes =
this.getShownAttributes();
dialogRef.componentInstance.enableSorting = true;
dialogRef.componentInstance.currentLocale =
this.currentLocaleService.currentLocale;
dialogRef.componentInstance.additionalProperties = getOrDefault(
this.configuration.selectAttributeAdditionalProperties,
["type", "group", "dataLevels"]
);
dialogRef.componentInstance.selectFilterParams =
this.configuration.selectFilterParams;
dialogRef.componentInstance.maxAttributes = getOrDefault(
this.configuration.selectAttributeMaxAttributes,
100
);
dialogRef.componentInstance.emptySelection = getOrDefault(
this.configuration.selectAttributeEmptySelection,
null
);
if (this.attributesDataType) {
dialogRef.componentInstance.dataType = this.attributesDataType;
}
if (this.listService.products) {
dialogRef.componentInstance.restrictParameters = {
cacheId: null,
products: this.listService.products,
};
} else if (
this.configuration.productNumberField &&
this.listService.dataArray &&
this.listService.dataArray.length !== 0
) {
const products = this.listService.dataArray.map(
(entry) => entry[this.configuration.productNumberField]
);
dialogRef.componentInstance.restrictParameters = {
cacheId: this.listService.cacheId,
products,
};
}
dialogRef.componentInstance.attributeUrl = throwIfUndefined(
this.attributeUrl
);
dialogRef.componentInstance.attributeUrlParams = this.attributeUrlParams;
dialogRef.afterClosed().subscribe((attributes) => {
if (attributes) {
this.storingEnabled = false;
let columns = this.grid.columns
.sort((a, b) => a.visibleIndex - b.visibleIndex)
.map((column) => Attributes.fromField(column.field));
let attributeIdentifier = attributes.map(
(attribute) => attribute.identifier
);
let sorted = columns.sort((a, b) => {
return (
attributeIdentifier.indexOf(a) - attributeIdentifier.indexOf(b)
);
});
this.columns.columnOrder.next(sorted);
this.dataListStateDirective.storeOrder(sorted);
this.updateDynamicColumns(attributes);
this.shownAttributesOutput.next(this.getShownAttributes());
this.emitDataListStatus("attributes");
this.storingEnabled = false;
}
});
}
protected getShownAttributes() {
const sortedColumns = this.grid.columns
.sort((a, b) => a.visibleIndex - b.visibleIndex)
.map((attribute) => Attributes.fromField(attribute.field));
return this.columns.shownAttributes.sort((a, b) => {
return (
sortedColumns.indexOf(a.identifier) -
sortedColumns.indexOf(b.identifier)
);
});
}
/**
* Function that gets called everytime the columns get updated from the SelectAttributesDialogComponent. Will directly call setDynamicColumns, if not overwritten
*/
public updateDynamicColumns(attributes) {
this.setDynamicColumns(attributes);
}
protected setDynamicColumns(attributes) {
this.columns.shownAttributes = deepCopy(attributes);
this.columns.updateColumns(attributes);
if (this.configuration.dynamicColumnsFromData) {
this.doReload();
}
if (this.localstorageShownAttributesEntry) {
this.localstorageShownAttributesEntry.value = JSON.stringify(attributes);
}
}
public onExport(settings) {
let exportData = !this.isEagerLoading ? this.listService.cachedData : null;
this.dataListExportService.export(
this.grid,
settings,
this.configuration.title,
this.configuration.useIgxExport,
exportData
);
}
public ngOnDestroy() {
if (!this.configuration.keepOnPageChangeIdentifier) {
this.columns.destroy();
this.listService.ngOnDestroy();
}
this.unsubscribe.destroy();
}
onCellClick(event) {
this.onCellSelect.next(event.cell);
// use navigateTo to ensure that cell is ready for past and tbody gets focused
if (!event?.cell?.editMode) {
this.grid.navigateTo(
event?.cell?.row?.index,
event?.cell?.column?.visibleIndex,
(args) => {
args?.target?.activate();
this.grid.tbody.nativeElement.focus();
}
);
}
if (this.configuration.rowSelectable !== false) {
return;
}
if (event.cell.row.isSelected) {
this.grid.deselectRows([event.cell.row.rowID]);
} else if (this.cellSelectable) {
this.grid.selectRows([event.cell.row.rowID], true);
}
this.onRowSelection({ newSelection: this.grid.selectedRows });
}
public onRowSelection(event: any) {
const params = this.buildSelectionEvent(event, this.grid, this.listService);
this.selectedItems.next(params);
}
onHeaderSelectorClick(event: any) {
const params = this.buildLazyAllSelectionEvent(
event,
this.grid,
this.listService
);
this.selectedItems.next(params);
}
protected buildLazyAllSelectionEvent(
event: any,
grid: IgxGridBaseDirective,
service: DataListService
): SelectionParams {
let result: SelectionParams = { cacheId: service.cacheId };
this.selectAllCheckbox =
this.selectAllCheckbox === null && event.checked ? false : event.checked;
if (!this.selectAllCheckbox) {
result = {
count: 0,
rows: [],
};
grid.deselectAllRows();
if (service.configuration.emitSelectedItems) {
result.rowData = [];
}
let rowData = service.cachedData.filter((data) => !data.cache);
rowData.forEach((row) => (row.selected = false));
} else {
let rowData = service.cachedData.filter((data) => !data.cache);
rowData.forEach((row) => (row.selected = true));
if (rowData) {
result = this.selectAvailableRows(result, grid, service, rowData);
}
}
return result;
}
protected buildSelectionEvent(
event: any,
grid:
| IgxGridComponent
| IgxHierarchicalGridComponent
| IgxTreeGridComponent,
service: DataListService
): SelectionParams {
let result: SelectionParams = {
count: event.newSelection ? event.newSelection.length : 1,
rows: event.newSelection,
cacheId: service.cacheId,
};
let primaryKey = this.primaryKey(service.configuration);
if (service.configuration.emitSelectedItems && service.isEagerLoading) {
let selection = new Set(event.newSelection);
result.rowData = grid.data.filter((data) =>
selection.has(data[primaryKey])
);
} else if (!service.isEagerLoading) {
let rowData = service.cachedData;
if (event.removed && event.removed.length > 0) {
let removed = new Set(event.removed);
rowData
.filter((data) => removed.has(data[primaryKey]))
.forEach((row) => (row.selected = false));
}
if (event.added && event.added.length > 0) {
let added = new Set(event.added);
rowData
.filter((data) => added.has(data[primaryKey]))
.forEach((row) => (row.selected = true));
}
if (rowData) {
result = this.selectAvailableRows(result, grid, service, rowData);
}
} else if (service.configuration.emitSelectedItems && result) {
result.rowData = result.rows.map((key) => {
return grid.getRowByKey(key).rowData;
});
}
return result;
}
selectAvailableRows(
result: SelectionParams,
grid: IgxGridBaseDirective,
service: DataListService,
rowData: any[]
) {
rowData = rowData.filter((data) => data.selected);
result = {
count: rowData.length,
rows: rowData.map((data) => data[this.primaryKey(service.configuration)]),
};
grid.selectRows(result.rows);
if (result.count == 0) {
this.selectAllCheckbox = false;
grid.deselectAllRows();
} else if (result.count != service.cachedData.length) {
this.selectAllCheckbox = null;
} else {
this.selectAllCheckbox = true;
}
if (service.configuration.emitSelectedItems) {
result.rowData = rowData;
}
return result;
}
onViewModeChange() {
this.listService.setViewmode(
this.selectedViewMode.identifier,
this.configuration.handleViewModeInBackend === true
);
this.viewModeOutput.next(this.selectedViewMode.identifier);
this.isListMode = true;
this.hideFilters = true;
if (this.selectedViewMode.identifier === "listView") {
this.hideFilters = false;
if (
this.columns.shownAttributes &&
this.columns.shownAttributes.length > 0
) {
this.columns.updateColumns(this.columns.shownAttributes);
}
} else if (this.selectedViewMode.identifier === "tileView") {
this.isListMode = false;
this.hideFilters = false;
if (
this.columns.shownAttributes &&
this.columns.shownAttributes.length > 0
) {
this.columns.updateColumns([]);
}
this.groupingExpressions = [];
}
this.viewModeLocalStorage.value = JSON.stringify(
this.selectedViewMode.identifier
);
if (this.globalFilterValue) {
this.onFilterValueChange();
}
this.currentDataChannel.next(
this.grid.dataView ? this.grid.dataView : this.grid.data
);
this.slideChange.next(3);
}
public showFilteringRow(event) {
this.showFilterRow = !this.showFilterRow;
}
public clearGlobalFilter() {
this.globalFilterValue = "";
this.grid.clearFilter();
}
public clearAllFiltersAndSorting(
clearGrouping = true,
loadData: boolean = true,
restoreGlobalFilter = false
) {
if (!restoreGlobalFilter && this.globalFilterInput) {
this.clearGlobalFilter();
}
this.restoreGlobalFilter.next(false);
this.grid.clearFilter();
this.grid.clearSort();
this.grid.clearCellSelection();
this.grid.sortingExpressions = [];
if (clearGrouping) {
this.grid.groupingExpressions = [];
this.setDefaultGroupingExpression();
}
this.setDefaultSortingExpressions();
if (loadData) {
this.listService.processData(true);
}
this.grid.cdr.detectChanges();
}
public trackColumns(index: number, column: Column) {
return (
"" +
column.type +
column.field +
column.hidden +
column.settings.columnOrder
);
}
public getDynamicPrefix() {
if (this.dataTypeModeChannel.value === "choose") {
return this.selectedDataType + "-";
} else {
return "mixed" + "-";
}
}
public getTranslationKey(column: Column) {
if (column.dynamicHeader) {
return this.getDynamicPrefix() + column.header;
}
return column.header;
}
public getColumnHeaderClasses(column: Column) {
let cssClasses = [];
if (column.sortable) {
cssClasses.push("widthSorting");
}
if (column.headerStyle) {
cssClasses.push(column.headerStyle);
}
return cssClasses;
}
public sortStrategy(column: Column): ISortingStrategy {
if (column.type === "widget" && this.isEagerLoading) {
return this.widgetSortingStrategy;
} else {
return DefaultSortingStrategy.instance();
}
}
public groupingComparer(column: Column) {
return null;
}
public onFilterValueChange() {
this.grid.filteringLogic = FilteringLogic.Or;
this.grid.filterGlobal(
this.globalFilterValue,
this.customFilter.condition("object"),
false
);
this.grid.cdr.detectChanges();
this.currentDataChannel.next(this.grid.filteredData);
}
onRangeSelection(event: GridSelectionRange) {
this.lastSelectedCells = event;
let gridData = this.grid.dataView ? this.grid.dataView : this.grid.data;
let range = [];
for (let i = event.rowStart; i < event.rowEnd + 1; i++) {
if (gridData[i].identifier) {
range.push(gridData[i].identifier);
}
}
range = range.filter((id) => !!id);
this.grid.selectRows(range, true);
this.onRowSelection({ newSelection: range });
this.grid.cdr.markForCheck();
}
public onVisibilityChanged(event) {
if (!event.column || !event.column.field) {
return;
}
let newColumns = this.columns.shownAttributes.filter((column) => {
return (
column.identifier !==
this.columns.shownAttributes.filter((column) => {
return (
event.column.field.substr(0, event.column.field.indexOf("#")) ===
column.identifier
);
})[0].identifier
);
});
if (newColumns) {
this.setDynamicColumns(newColumns);
}
}
public stopPropagation(event, field) {
event.stopImmediatePropagation();
event.preventDefault();
this.inputBoxes.forEach((input) => {
if (input.nativeElement.classList.contains(field)) {
input.nativeElement.focus();
}
});
return false;
}
columnInit(column: any) {
if (this.clipboardOptions.copyFormatters) {
column.formatter = (target) => formatCellWidgets(target, "exportValue");
}
}
public onColumnFilterInput(input: any, column: IgxColumnComponent) {
this.grid.filter(
column.field,
input,
this.customFilter.condition("object"),
column.filteringIgnoreCase
);
}
public clearColumnFilter(input: any, column: any) {
this.dataListStateDirective.columnFilterInput[column.field] = null;
this.grid.clearFilter(column.field);
}
public onRowEditEnter() {
this.rowEditEnterEmitter.next();
}
public onRowEditCancel() {
this.rowEditLeaveEmitter.next();
}
public openContextmenu(eventArgs) {
if (!this.disableContextMenu) {
if (eventArgs.cell && eventArgs.cell.column) {
const columnIdentifier = eventArgs.cell.column.field;
this.columns.columns.pipe(first()).subscribe((columns) => {
const column = columns.find(
(entry) => entry.field === columnIdentifier
);
if (column) {
this.contextMenuColumn = column;
}
});
this.contextMenuCoordinates.cell = eventArgs.cell;
}
if (this.lastSelectedCells) {
this.grid.selectRange(this.lastSelectedCells);
}
this.contextMenuCoordinates.x = eventArgs.event.clientX + "px";
this.contextMenuCoordinates.y = eventArgs.event.clientY + "px";
eventArgs.event.preventDefault();
this.menuTrigger.openMenu();
}
}
closeContextMenu() {
if (!this.disableContextMenu) {
this.menuTrigger.closeMenu();
}
}
public validateList(validation: any, configuration: DataListConfiguration) {
if (validation) {
let primaryKey = this.primaryKey(configuration);
this.listCells
.filter(
(cell) =>
cell.row[primaryKey] === validation.primaryKey &&
cell.attribute &&
cell.attribute.identifier === validation.attribute
)
.forEach((cell) => {
cell.cdr.detectChanges();
});
}
let isValid = this.validateAllRows();
this.isValidOutput.emit(isValid);
}
protected validateAllRows(): boolean {
return this.validateGrid(this.grid);
}
protected validateGrid(grid: IgxGridBaseDirective) {
if (this.editedRows == null) {
return true;
}
const rowIds = Object.keys(this.editedRows);
for (let rowId of rowIds) {
const attributes = this.editedRows[rowId]?.attributes;
if (attributes == null) {
continue;
}
const row = grid.data.find(
(record) => record[this._primaryKey] === rowId
);
if (row == null) {
continue;
}
const identifiers = Object.keys(attributes);
for (let identifier of identifiers) {
const value = row[Attributes.toSourceField(identifier)];
if (value?.errors?.length > 0) {
return false;
}
}
}
return true;
}
private checkCellForErrors(
cell: ListCellEditComponent | ListCellComponent
): boolean {
if (!cell.attribute) {
return false;
}
let errors = cell.attribute.errors;
return errors && errors.length > 0;
}
public applyDensityFromLocalStorage() {
if (this.configuration.density) {
this.density = <DisplayDensity>this.configuration.density;
this.grid.displayDensity = this.density;
return;
}
let densityStorage =
this.localStorageService.getLocalStorageEntryByConfig(
densityStorageConfig
);
if (densityStorage.exists()) {
this.density = JSON.parse(densityStorage.value);
}
this.configuration.density = this.density;
this.grid.displayDensity = this.density;
}
onCellEditEnter(event: IGridEditEventArgs) {
this.cellEditModeActive = true;
let field = event.column.field;
let oldValue = event.rowData[Attributes.toSourceField(field)];
if (!oldValue || !oldValue.identifier || oldValue["read-only"]) {
event.cancel = true;
return;
}
this.dataListEndEditService.beginEdit(this.grid);
}
onCellEditExit() {
this.cellEditModeActive = false;
}
updateDataOutput() {
this.grid.cdr.detectChanges();
this.currentDataChannel.next(
this.grid.dataView ? this.grid.dataView : this.grid.data
);
}
getHeaderClass(nmColumn) {
let classes = [];
if (this.configuration.headerClasses) {
for (let cssClass in this.configuration.headerClasses) {
let ref = this.configuration.headerClasses[cssClass];
if (
typeof ref === "string" &&
HeaderClassesCallbackRegistry.get(ref)(nmColumn)
) {
classes.push(cssClass);
}
}
}
// default header style
if (classes.length === 0) {
classes.push(
HeaderClassesCallbackRegistry.getDefaultIsMandatory()(nmColumn)
? "mandatoryCell"
: "default"
);
}
return classes.join(" ");
}
public toggleMode(event) {
this.toggleButtonChangeEmitter.emit(event.checked);
}
public tileClicked(event) {
this.tileClickedEventEmitter.emit(event);
}
public onItemDropped(event, cell: IgxGridCell, column: Column) {
if (column.allowDrop == false) {
return;
}
//Check the type of this event, if this is a data-list-row we know how to handle it, if the target column is of type attribute!
if (event.dragData.type === "data-list-row") {
if (column.type === "attribute") {
//This is an attribute
const value = extractAttributeFromCell(cell, column.field);
const draggingAttribute = extractAttributeFromRow(
event.dragData.source,
event.dragData.column.field
);
if (value && draggingAttribute) {
value.source = deepCopy(draggingAttribute.source);
const attribute = flatCopy(value);
Attributes.updateCell(cell, attribute);
}
}
}
}
public getHighlightReadOnly(): boolean {
return getOrDefault(this.configuration.highlightReadOnly, true);
}
public getCellClass(nmColumn) {
let resolved: any = {};
if (nmColumn.cellClasses) {
for (let cssClass in nmColumn.cellClasses) {
let ref = nmColumn.cellClasses[cssClass];
resolved[cssClass] = ref;
if (typeof ref === "string") {
resolved[cssClass] = CellClassesCallbackRegistry.get(ref) || false;
}
}
} else {
// default cell style
resolved.mandatoryCell =
CellClassesCallbackRegistry.getDefaultIsMandatory();
}
resolved[nmColumn.type ? nmColumn.type : "default"] = true;
return resolved;
}
getTableHeight() {
if (this.configuration.dynamicTableHeight) {
return this.calcTableHeight();
}
return "100%";
}
calcTableHeight() {
let height: string;
let modifiers = DEFAULT_MODIFIERS;
if (this.configuration.dynamicTableHeightAdditionalModifiers) {
modifiers = modifiers.concat(
this.configuration.dynamicTableHeightAdditionalModifiers
);
}
if (this.configuration.dynamicTableHeightContainer) {
const selector = this.configuration.dynamicTableHeightContainer;
let modifier = 0;
if (this.configuration.dynamicTableHeightAdditionalHeight) {
modifier = parseInt(
this.configuration.dynamicTableHeightAdditionalHeight,
10
);
}
let ro = new ResizeObserver((entries, observer) => {
for (let entry of entries) {
const cr = entry.contentRect;
this.tableHeight = `${cr.height - modifier}px`;
return height;
}
});
ro.observe(document.querySelector(selector));
return;
}
let maxHeightContent = getBodyStyleCssProperty("max-height-content");
height = maxHeightContent;
if (modifiers && modifiers.length > 0) {
height = `calc(${maxHeightContent}`;
modifiers.forEach((modifier) => {
height += ` - ${getBodyStyleCssProperty(modifier)}`;
});
if (this.configuration.dynamicTableHeightAdditionalHeight) {
height += ` - ${this.configuration.dynamicTableHeightAdditionalHeight}`;
}
if (
(this.configuration.footer && !this.configuration.footer.hidden) ||
!this.configuration.footer
) {
height += ` - ${getBodyStyleCssProperty(TABLE_FOOTER_MODIFIER)}`;
}
height += `)`;
}
return height;
}
public takeRecommendedAttributes(
recommendedAttributes: RecommendedAttributes
) {
const attributes = new Map<string, Attribute>();
[
...recommendedAttributes.attributes,
...this.columns.shownAttributes,
].forEach((attribute) => attributes.set(attribute.identifier, attribute));
const emptySelection: Attribute[] = getOrDefault(
this.configuration.selectAttributeEmptySelection,
null
);
if (emptySelection) {
emptySelection.forEach((attribute) =>
attributes.delete(attribute.identifier)
);
}
this.updateDynamicColumns(Array.from(attributes.values()));
return false;
}
public dataPasted(processedData: string[][]) {
const uri =
this.configuration.excelCopyParseUrl ||
"/api/core/attributes/parse?include=attribute.source&include=attribute.validation";
const cell = this.grid.selectedCells[0];
if (!cell) {
return;
}
let body = this.prepareExcelPasteRequestData(cell, processedData);
if (
body.needsDateTimeFormatSelection ||
body.needsSeparatorSelection ||
body.needsDateFormatSelection
) {
const dialogConfig = UtilService.getPasteDialogue(body);
let dialogRef = this.dialog.open(PageDialogComponent, dialogConfig);
dialogRef.afterClosed().subscribe((data) => {
if (data) {
body = Object.assign(body, data);
this.widgetFrameService.postData(uri, body).subscribe((response) => {
this.applyExcelPasteData(response, cell);
});
}
});
return;
}
this.widgetFrameService.postData(uri, body).subscribe((response) => {
this.applyExcelPasteData(response, cell);
});
}
public onRowDragStart(event) {
event.dragData.type = "data-list-row";
return event;
}
public onDrop(event) {
if (event.dragData.type !== "data-list-row") {
return;
}
const originalIndex = event.drag.data.index;
const dragRect = event.drag.ghostElement.getBoundingClientRect();
const currRowIndex = this.getCurrentRowIndex(this.grid.rowList.toArray(), {
x: dragRect.x,
y: dragRect.y,
});
if (!currRowIndex) {
return;
}
// remove the row that was dragged and place it onto its new location
this.listService.dataArray.splice(originalIndex, 1);
this.listService.dataArray.splice(currRowIndex, 0, event.dragData.rowData);
this.dataChannel.next(this.listService.dataArray);
this.reorderedRows.emit({
rowData: event.dragData._rowData,
currentRowIndex: currRowIndex,
dataArray: this.listService.dataArray,
});
}
private getCurrentRowIndex(rowList, cursorPosition) {
if (rowList.length === 0) {
return null;
}
const primaryKey = this.primaryKey(this.configuration);
for (const row of rowList) {
const rowRect = row.nativeElement.getBoundingClientRect();
if (cursorPosition.y > rowRect.top && cursorPosition.y < rowRect.bottom) {
// return the index of the targeted row
return this.listService.dataArray.indexOf(
this.listService.dataArray.find(
(r) => r[primaryKey] === row.rowData[primaryKey]
)
);
}
}
}
private ALWAYS_ALLOWED_TYPES = [
"LOCALIZED_STRING",
"PLAIN_STRING",
"INTEGER_NUMBER",
"DECIMAL_NUMBER",
"DATE",
];
private prepareExcelPasteRequestData(
selectedCell,
processedData: string[][]
) {
const pk = this.grid.primaryKey;
const copyEverwhere = this.configuration.excelCopyPasteEverywhere;
const cellData: any[][] = [];
const startRowIndex = selectedCell.row.index;
let needsDateFormatSelection = false;
let needsDateTimeFormatSelection = false;
let needsSeparatorSelection = false;
const cellIndex = selectedCell.column.visibleIndex;
const columns = this.grid.visibleColumns.sort(
(l, r) => l.visibleIndex - r.visibleIndex
);
let rowIteratorIndex = 0;
for (const curentDataRow of processedData) {
const rowData: any[] = [];
cellData.push(rowData);
const dataRec = this.grid.data[startRowIndex + rowIteratorIndex];
if (!dataRec) {
// no rec to update, we are at the end of the list
break;
}
for (
let cellIteratorIndex = 0;
cellIteratorIndex + cellIndex < columns.length &&
cellIteratorIndex < curentDataRow.length;
cellIteratorIndex++
) {
let currentIndex = cellIteratorIndex + cellIndex;
let colKey = Attributes.toSourceField(columns[currentIndex].field);
const data = dataRec[colKey];
if (!data) {
//console.debug(`Cant find field ${colKey} in row`, dataRec)
continue;
}
//We dont have copyeverywhere enabled and this is no attribute type that is always allowed
if (
!copyEverwhere &&
this.ALWAYS_ALLOWED_TYPES.indexOf(data.type) === -1
) {
continue;
}
//We will only write to attribute columns that are not readonly
if (data["read-only"] !== false) {
continue;
}
if (data.type === "DATE") {
needsDateFormatSelection = true;
} else if (data.type === "DATE_TIME") {
needsDateTimeFormatSelection = true;
} else if (
data.type === "MULTI_LOOKUP" ||
data.type === "COMPOSITION_AMOUNT" ||
data.type === "COMPOSITION_PERCENT"
) {
needsSeparatorSelection = true;
}
rowData[cellIteratorIndex] = data;
}
rowIteratorIndex++;
}
const body = {
data: processedData,
dtos: cellData,
needsDateFormatSelection,
needsSeparatorSelection,
needsDateTimeFormatSelection,
};
return body;
}
private applyExcelPasteData(response, selectedCell) {
const rowData = this.grid.data;
let rowIteratorIndex = 0;
const pk = this.grid.primaryKey;
const cellIndex = selectedCell.column.visibleIndex;
const columns = this.grid.visibleColumns;
const startRowIndex = selectedCell.row.index;
const params: DataListActionParameter = {
api: new DataListApiImpl(this, this.configuration),
export: null,
grid: this,
};
response._embedded[response.type].forEach((row) => {
const dataRec = rowData[startRowIndex + rowIteratorIndex];
const rowPkValue = dataRec[pk];
//We need to respect three cases here:
// 1. Everything is fine row and cells are here
// 2. Row wont be found - this happens if we update a row that is outside the viewport (below the current cell) -
// 3. Cell wont be found - this happpens if we update a cell that is outside the viewport (right of the current cells)
const igniteRow = this.grid.getRowByKey(rowPkValue);
let cellIteratorIndex = 0;
row._embedded[row.type].forEach((cell: { identifier: any }) => {
if (cell == null) {
cellIteratorIndex++;
return;
}
let currentIndex = cellIndex + cellIteratorIndex;
let colKey = Attributes.toValueField(columns[currentIndex].field);
// If we cant find the row OR the cell skip the update
if (!igniteRow || !this.grid.getCellByColumn(igniteRow.index, colKey)) {
this.validationService.validateAttribute(cell);
} else {
updateRowOrCell(
cell,
igniteRow,
colKey,
params,
this.validationService
);
cellIteratorIndex++;
}
dataRec[Attributes.toSourceField(cell.identifier)] = cell;
const eventAttributeData = {
[cell.identifier]: cell,
};
params.api.fireEditedRowsEvent(
rowPkValue,
dataRec,
null,
eventAttributeData
);
});
rowIteratorIndex++;
});
this.validateList(null, null);
}
columnOrderChanged(columnIdentifiers: string[]) {
this.columns.columnOrder.next(columnIdentifiers);
}
onDataColumnsChange(
visibleDataColumns: string[],
showAllColumns: boolean = false
) {
this.showAllColumns = showAllColumns;
this.visibleDataColumnsStorage.value = JSON.stringify(visibleDataColumns);
this.visibleDataColumns = visibleDataColumns;
this.cdr.markForCheck();
}
processDataOnChange(event, reset: boolean) {
if (event) {
this.listService.processData(reset);
this.updateDataOutput();
}
}
onFilteringDone() {
this.unsavedChangesService
.leaveContext(UNSAVED_CHANGES_DIALOG_CONFIG)
.subscribe((response) => {
if (!response) {
return;
}
this.listService.processData(true);
this.updateDataOutput();
});
}
}
export function formatCellWidgets(target: any, property: string): string {
let val = "";
if (Array.isArray(target) && target.length > 0) {
val = target
.map((option) => (option[property] ? option[property] : option))
.join(" ");
} else {
val = String(target[property] ? target[property] : target);
}
return val;
}
export class WidgetSortingStrategy implements ISortingStrategy {
public sort(
data: any[],
fieldName: string,
dir: SortingDirection,
ignoreCase: boolean
) {
const cmpFunc = (a, b) => {
return this.compareObjects(a, b, fieldName, ignoreCase, dir);
};
return data.sort(cmpFunc);
}
protected compareObjects(
obj1: any,
obj2: any,
fieldName: string,
ignoreCase: boolean,
dir: SortingDirection
) {
let a = formatCellWidgets(obj1[fieldName], "value");
let b = formatCellWidgets(obj2[fieldName], "value");
if (ignoreCase) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return this.sortByParity(a, b, dir);
}
protected sortByParity(a: any, b: any, dir: SortingDirection) {
if (a > b) return dir === 1 ? +1 : -1;
if (a < b) return dir === 1 ? -1 : +1;
return 0;
}
}
export const PREFIX = "--";
export class CustomFilteringOperand extends IgxStringFilteringOperand {
private constructor() {
super();
this.operations = [customObjectFilteringOperation];
}
}
export const customObjectFilteringOperation: IFilteringOperation = {
iconName: "contains",
isUnary: false,
logic: (target: any, searchVal: string, ignoreCase?: boolean) => {
if (!target && target !== 0) {
return false;
}
let val = "";
if (Array.isArray(target) && target.length > 0) {
val = target
.map((option) => (option.value ? option.value : option))
.join(" ");
} else {
val = String(target.value ? target.value : target);
}
if (ignoreCase) {
return val.toLowerCase().indexOf(searchVal.toLowerCase()) !== -1;
} else {
return val.indexOf(searchVal) !== -1;
}
},
name: "object",
};
/**
* Public interface of the edited Rows
*/
export interface DataListEditedRows {
[key: string]: DataListEditedRow;
}
export interface DataListEditedRow {
parent: string;
//The row that got edited
row: any;
// The edited attributes
attributes: DataListEditedRowsAttributes;
// The tree grid row if we are editing a tree-grid
treeRow?: any;
}
export interface DataListEditedRowsAttributes {
[key: string]: Attribute;
}