File

src/app/shared/widgets/data-list/data-list-component/data-list.component.ts

Description

Public interface of the edited Rows

Indexable

[key: string]: DataListEditedRow
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;
}

results matching ""

    No results matching ""