File

projects/i-components/src/lib/components/xam-grid/xam-grid.component.ts

Description

Custom filter operands for xam grid.

Extends

IgxStringFilteringOperand

Constructor

Private constructor()

Creates an instance of XamGridCustomFilterOperands.

import { DOCUMENT } from '@angular/common';
import {
  AfterContentInit,
  AfterViewChecked,
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  ComponentFactoryResolver,
  ContentChild,
  ContentChildren,
  DoCheck,
  ElementRef,
  EventEmitter,
  HostListener,
  Inject,
  Injector,
  Input,
  IterableDiffer,
  IterableDiffers,
  OnDestroy,
  OnInit,
  Output,
  QueryList,
  ViewChild,
  ViewChildren,
} from '@angular/core';
import {
  AddNewRow,
  AllowToolTips,
  AngularComponentId,
  BeginEditingCellEventArgs,
  CancellablePageChangingEventArgs,
  CellBase,
  CellClickedEventArgs,
  CellControlAttachedEventArgs,
  CellExitedEditingEventArgs,
  CellSelectionAction,
  CollectionChangeAction,
  CollectionChangeInfo,
  ColumnBaseCollection,
  ColumnWidth,
  ColumnWidthType,
  ComponentId,
  DependencyPropertyChangedEventArgs,
  DoesNotEndWithOperand,
  DoesNotStartWithOperand,
  EditingCellEventArgs,
  EditingRowEventArgs,
  ExitEditingCellEventArgs,
  FixedState,
  GroupColumnsCollection,
  InitializeRowEventArgs,
  IRecordFilter,
  iuAny,
  iuCount,
  iuFirstOrDefault,
  iuWhere,
  ModelProxy,
  MouseEditingAction,
  out,
  PageChangedEventArgs,
  PagingLocation,
  ReflectionHelper,
  RuntimeStyleInfo,
  SelectedRowsCollection,
  SelectionCollectionChangedEventArgs,
  SelectionSettings,
  Setter,
  SimpleDictionary,
  SimpleList,
  smColorToCssColor,
  smTryParseFloat,
  SolidColorBrush,
  SortDirection,
  ThicknessModel,
  UnboundColumnDataContext,
  VirtualCollection,
  XamGridCell,
  XamGridCellControl,
  XamGridCheckboxColumnModel,
  XamGridColumnModel,
  XamGridColumnType,
  XamGridEditingType,
  XamGridGroupColumnModel,
  XamGridModel,
  XamGridRow,
  XamGridTemplateColumnModel,
  XamGridTextColumnModel,
  XamGridUnboundColumnModel,
  XamSelectionMode,
} from '@mobilize/wms-framework';
import {
  CellType,
  ColumnPinningPosition,
  DefaultSortingStrategy,
  FilteringExpressionsTree,
  GridSelectionMode,
  IActiveNodeChangeEventArgs,
  IColumnSelectionEventArgs,
  IFilteringExpression,
  IFilteringExpressionsTree,
  IForOfState,
  IGridCellEventArgs,
  IGridEditDoneEventArgs,
  IGridEditEventArgs,
  IGridKeydownEventArgs,
  IGridScrollEventArgs,
  IgxColumnComponent,
  IgxGridComponent,
  IgxIconService,
  IgxStringFilteringOperand,
  IgxToastComponent,
  IPageEventArgs,
  IPinColumnEventArgs,
  IRowDataEventArgs,
  ISortingExpression,
  RowType,
  SortingDirection,
} from 'igniteui-angular';
import { isEqual } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { NotifyCollectionChangedHandler } from '../../utils';
import { XamDataGridEventManager } from '../../utils/event-manager';
import { Utils } from '../../utils/utilities';
import { ValidationHelper } from '../../utils/validation-helper';
import { BaseComponent } from '../base/base.component';
import { XamGridColumnComponent } from '../xam-grid-column/xam-grid-column.component';
import { AddNewRowSettingsComponent } from '../xam-grid-settings/xam-grid-add-row.component';
import { ColumnMovingSettingsComponent } from '../xam-grid-settings/xam-grid-column-moving.component';
import { ColumnResizingSettingsComponent } from '../xam-grid-settings/xam-grid-column-resizing.component';
import { EditingSettingsComponent } from '../xam-grid-settings/xam-grid-editing.component';
import { FilteringSettingsComponent } from '../xam-grid-settings/xam-grid-filtering.component';
import { PagerSettingsComponent } from '../xam-grid-settings/xam-grid-paging.component';
import { RowSelectorSettingsComponent } from '../xam-grid-settings/xam-grid-row-selector.component';
import { SelectionSettingsComponent } from '../xam-grid-settings/xam-grid-selection.component';
import { SortingSettingsComponent } from '../xam-grid-settings/xam-grid-sorting.component';
import { SummarySettingsComponent } from '../xam-grid-settings/xam-grid-summary.component';
import { XamColumnLayoutComponent } from './XamColumnLayout.component';
import { CloneStrategy } from './clone-strategy';
import {
  iconDoesNotEndWith,
  iconDoesNotStartWith,
} from './operatorsIconsString';
import { StylePlan } from './style-plan';

/**
 * The collection of events the xam grid will handle in order to emulate
 * the disabled state.
 */
const DISABLED_EVENTS = [
  'pointerdown',
  'mousedown',
  'pointerenter',
  'mouseenter',
  'pointerup',
  'mouseup',
  'focusin',
  'click',
  'dblclick',
  'contextmenu',
  'keydown',
];

/**
 * Angular Component for the Xam Grid Control
 *
 * @export
 * @class XamGridComponent
 * @extends {BaseComponent}
 * @implements {OnInit}
 * @implements {AfterContentInit}
 * @implements {OnDestroy}
 */
@Component({
  selector: 'wm-xam-grid',
  templateUrl: './xam-grid.component.html',
  styleUrls: ['./xam-grid.component.scss'],
})
@ComponentId([AngularComponentId.xamGrid])
export class XamGridComponent
  extends BaseComponent
  implements
    OnInit,
    AfterContentInit,
    AfterViewChecked,
    AfterViewInit,
    DoCheck,
    OnDestroy
{
  /**
   * Default XamGrid border color
   *
   * @memberof XamGridComponent
   */
  defaultBorderColor = '#A3AEB9';

  /**
   * Checks if an error state.
   *
   * @memberof XamGridComponent
   */
  validationErrorFlag = false;

  /**
   * Stores the name of the following column with errors
   *
   * @memberof XamGridComponent
   */
  nextColumnWithErrors = null;

  /**
   * reference to the internal grid component when the instance is a inherited grid
   *
   * @type {XamGridComponent}
   * @memberof XamGridComponent
   */
  @ViewChild(XamGridComponent) internalXamGrid: XamGridComponent;

  /**
   * reference to the internal xamGrid used to replicate the columnLayout feature
   *
   * @type {XamGridComponent}
   * @memberof XamGridComponent
   */
  @ViewChild('gridColumnLayout') gridColumnLayout: XamGridComponent;

  /**
   * reference to the internal xamGrid used to replicate the columnLayout feature
   *
   * @type {XamGridComponent}
   * @memberof XamGridComponent
   */
  @ViewChild(IgxToastComponent) toast: IgxToastComponent = null;

  /**
   * Flag used to indicate when a row or cell change their style.
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  styleChanged = false;

  /**
   * Flag which indicates if the rendered have been called.
   *
   * @memberof XamGridComponent
   */
  renderedCalled = false;

  /**
   * The addRowKey property
   *
   * @type {RowType}
   * @memberof XamGridComponent
   */
  addRowKey: RowType;

  /**
   * Registry of XamGridCellControl which have resolved the content
   * This registry is used to reset the herarchy with the cell template control
   * when scrolling or updated to avoid reference from the control the wrong template
   *
   * @type {XamGridCellControl[]}
   * @memberof XamGridComponent
   */
  resolvedCellControlContent: XamGridCellControl[] = [];

  /**
   * New value for items per page.
   *
   * @type {number}
   * @memberof XamGridComponent
   */
  newItemsPerPage: string;

  /**
   * Flags to manage when the button for update the items per page
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  allowChangeItemsPerPage = false;

  /**
   * Keeps a reference to last sorting expression to avoid 'None' state
   *
   * @type {ISortingExpression[]}
   * @memberof XamGridComponent
   */
  lastSortingExpression: ISortingExpression[];

  /**
   * returns the correct context to call the xam grid methods
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get xamGridContext() {
    return this.childXamGrid ?? this;
  }

  /**
   * returns the internal grid component when the inherited grid is different of the columnLayout grid
   *
   * @readonly
   * @type {XamGridComponent}
   * @memberof XamGridComponent
   */
  get childXamGrid(): XamGridComponent {
    return this.internalXamGrid !== this.gridColumnLayout
      ? this.internalXamGrid
      : null;
  }

  /**
   * Flag which indicates if the cell styling is enabled
   *
   * @memberof XamGridComponent
   */
  @Input() cellStyleEnabled = false;

  /**
   * Controls whether to show the column footers if any.
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  @Input() get footerVisibility(): boolean {
    return this.modelProxy.FooterVisibility;
  }

  set footerVisibility(value: boolean) {
    this.modelProxy.FooterVisibility = value;
  }

  /**
   * Custom input of xam grid component to add tooltip message to each filter column.
   * Note: Every FilterRowCellControl is going to have the same tooltip, if an specific
   * tooltip should be set to an specific FilterRowCellControl please add new functionality.
   */
  @Input() filterCellTooltip: string;

  /**
   * Input from the extended component to fill the ColumnLayout in the internal xamGrid
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  @Input() columnLayoutInheritance: any;

  /**
   * Input from the extended component to fill the addNewRow in the internal xamGrid
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  @Input() addNewRowInheritance: any;

  /**
   * Input from the extended component to fill the addNewRow in the internal xamGrid
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  @Input() editingInheritance: EditingSettingsComponent;

  /**
   * Returns the editing instance of the grid from the xam grid or the extended component
   *
   * @readonly
   * @type {EditingSettingsComponent}
   * @memberof XamGridComponent
   */
  get editingComponent(): EditingSettingsComponent {
    return this.editingInheritance ?? this.editing;
  }

  /**
   * Sorting settings from the extended component.
   *
   * @type {SortingSettingsComponent}
   * @memberof XamGridComponent
   */
  @Input() sortingSettingInheritance: SortingSettingsComponent;

  /**
   * Input from the extended component to fill the grid columns in the internal xamGrid
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  @Input() gridColumnsInheritance: any;

  /**
   * Sets whether the columns of all ColumnLayout objects of this XamGrid should be generated.
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  @Input() set autoGenerateColumns(value: boolean) {
    this.modelProxy.AutoGenerateColumns = value;
  }

  /**
   * Gets whether the columns of all ColumnLayout objects of this XamGrid should be generated.
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  get autoGenerateColumns(): boolean {
    return this.model.AutoGenerateColumns;
  }

  /**
   * The collection of items bound to this grid
   *
   * @readonly
   * @type {*}
   * @memberof XamGridComponent
   */
  get collection(): any {
    return this.data?.internalArray || this.model.ItemsSource || this.data;
  }

  /**
   * Sets the name model property for the control when the name is an input
   *
   * @memberof XamGridComponent
   */
  @Input() set name(value: string) {
    this.modelProxy.Name = value;
  }

  /**
   * Object with XamGridComponent properties and events.
   *
   * @type {DataGrid}
   * @memberof XamGridComponent
   */
  @Input()
  public model: XamGridModel;

  /**
   * CellClicked eventEmitter.
   *
   * @type {EventEmitter<IGridCellEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  cellClicked: EventEmitter<any> = new EventEmitter<any>();

  /**
   * cellSelectionChanged eventEmitter.
   *
   * @type {EventEmitter<IGridCellEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  cellSelectionChange: EventEmitter<IGridCellEventArgs> = new EventEmitter<IGridCellEventArgs>();

  /**
   * rowSelectionChange eventEmitter.
   *
   * @type {EventEmitter<IRowSelectionEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  selectedRowsCollectionChanged: EventEmitter<{
    sender: XamGridModel;
    e: SelectionCollectionChangedEventArgs<any>;
  }> = new EventEmitter<{
    sender: XamGridModel;
    e: SelectionCollectionChangedEventArgs<any>;
  }>();

  /**
   * columnSelectionChange event emitter.
   *
   * @type {EventEmitter<IColumnSelectionEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  columnSelectionChanged: EventEmitter<IColumnSelectionEventArgs> = new EventEmitter<IColumnSelectionEventArgs>();

  /**
   * KeyDown eventEmitter.
   *
   * @type {EventEmitter<IRowSelectionEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  keyDown: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Double-click eventEmitter.
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellDoubleClicked: EventEmitter<any> = new EventEmitter<any>();

  /**
   * XamGrid rendered eventEmitter.
   *
   * @type {EventEmitter<IGridCellEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  rendered: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Column resized eventEmitter.
   *
   * @type {EventEmitter<IRowSelectionEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  columnResized: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Column resizing eventEmitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  columnResizing: EventEmitter<any> = new EventEmitter<any>();

  /**
   * column fixed state changed eventEmitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  columnFixedStateChanged: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Event emitted when a column has done applying a sorting
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  columnSorted: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Column sorting eventEmitter.
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  columnSorting: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Column filtering eventEmitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  columnFiltered: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Column fixed eventEmitter.
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  columnFixed: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Cell enter edit mode
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellEnteringEditMode: EventEmitter<any> = new EventEmitter<any>();

  /**
   * CellEnteredEditMode
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellEnteredEditMode: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Cell exiting edit mode eventEmitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellExitingEditMode: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Cell exit edit mode
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellExitedEditMode: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Row exit edit mode
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  rowExitedEditMode: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Row entered edit mode
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  rowEnteredEditMode: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Cell editing event emitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellEditing: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Cell edited event emitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellEdited: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Active cell event emitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  activeCellChanged: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Row added event emitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  rowAdded: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Row deleted event emitter
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  rowDeleted: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Page changing event emitter
   *
   * @type {EventEmitter<CancellablePageChangingEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  pageIndexChanging: EventEmitter<CancellablePageChangingEventArgs> = new EventEmitter<CancellablePageChangingEventArgs>();

  /**
   * Page changed event emitter
   *
   * @type {EventEmitter<PageChangedEventArgs>}
   * @memberof XamGridComponent
   */
  @Output()
  pageIndexChanged: EventEmitter<PageChangedEventArgs> = new EventEmitter<PageChangedEventArgs>();

  /**
   * Query list with XamGridColumnComponent.
   *
   * @type {QueryList<XamGridColumnComponent>}
   * @memberof XamGridComponent
   */
  @ContentChildren(XamGridColumnComponent, {
    read: XamGridColumnComponent,
    descendants: true,
  })
  gridColumns: QueryList<XamGridColumnComponent> = new QueryList<XamGridColumnComponent>();

  /**
   * Query list of customColumn content.
   *
   * @type {QueryList<any>}
   * @memberof XamGridComponent
   */
  @ContentChildren('columnContentTemplate')
  columnContentTemplate: QueryList<any>;
  /**
   * Gets/sets the selection settings for the XamGrid.
   *
   * @type {SelectionSettings}
   * @memberof XamGridComponent
   */
  @Input()
  get selectionSettings(): SelectionSettings {
    return this.modelProxy.SelectionSettings;
  }

  set selectionSettings(value: SelectionSettings) {
    this.modelProxy.SelectionSettings = value;
  }

  /**
   * Gets/set data.
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  @Input()
  get data(): any {
    return this.itemsSource;
  }

  set data(value: any) {
    this.itemsSource = value;
  }

  /**
   * Two-way bindable property for the data source of the Xam grid.
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  @Input()
  get itemsSource(): any {
    return this.modelProxy.ItemsSource;
  }

  set itemsSource(source: any) {
    if (
      !this.checkAndRegisterCompatibilityBinding(
        XamGridModel.ItemsSourceProperty,
        source
      )
    ) {
      this.modelProxy.ItemsSource = source;
      this.registerHandlerForObservablesCollection();
    }
  }

  /**
   * Event emitter for two-way binding of the `itemsSource` property.
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  itemsSourceChange: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Get headerStyle.
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get headerStyle() {
    return this.model.HeaderStyle;
  }

  /**
   * set headerStyle.
   *
   * @memberof XamGridComponent
   */
  @Input()
  set headerStyle(value) {
    /* istanbul ignore else */
    if (
      !this.checkAndRegisterCompatibilityBinding(
        XamGridModel.HeaderStyleProperty,
        value
      )
    ) {
      this.modelProxy.HeaderStyle = value;
    }
  }

  /**
   * Gets/sets the current selected row in the Xam grid.
   *
   * @memberof XamGridComponent
   */
  @Input()
  get selectedRow() {
    return this.modelProxy.SelectedRow;
  }

  set selectedRow(row) {
    /* istanbul ignore else */
    if (
      !this.checkAndRegisterCompatibilityBinding(
        XamGridModel.SelectedRowProperty,
        row
      )
    ) {
      this.modelProxy.SelectedRow = row;
    }
  }

  /**
   * Event emitter for two-way binding of the `selectedRow` property.
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  selectedRowChange: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Event emitted when the grid is done loading in the DOM
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  loaded: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Event emitted when the grid is unloading the component
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  unloaded: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Event emitted when 'new' cells are entering the view
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  cellControlAttached: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Event emitted after the 'new' cells are created
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  afterCellControlAttached: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Event emitted when the row is initialized
   *
   * @type {EventEmitter<any>}
   * @memberof XamGridComponent
   */
  @Output()
  initializeRow: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Sets the grid height to limit how many rows are rendered
   * accepts null value, which will render all rows in the DOM with no scrollbar
   *
   * @type {string|null}
   * @memberof XamGridComponent
   */
  @Input()
  get virtualHeight() {
    return this.model.virtualHeight;
  }

  set virtualHeight(value) {
    this.model.virtualHeight = value;
  }

  /**
   * Gets the border styling of the grid.
   *
   * @memberof XamGridComponent
   */
  @Input()
  get borderThickness() {
    /* istanbul ignore next */
    const top = this.model.BorderThickness?.Top;
    /* istanbul ignore next */
    const bottom = this.model.BorderThickness?.Bottom;
    /* istanbul ignore next */
    const left = this.model.BorderThickness?.Left;
    /* istanbul ignore next */
    const right = this.model.BorderThickness?.Right;
    if (top !== 0 || bottom !== 0 || left !== 0 || right !== 0) {
      return `${top}px ${right}px ${bottom}px ${left}px`;
    } else {
      return '1px';
    }
  }

  /**
   * Sets the border styling of the grid.
   *
   * @memberof XamGridComponent
   */
  set borderThickness(value: string) {
    this.modelProxy.BorderThickness = ThicknessModel.parse(value);
  }

  /**
   * Gets the border color of the control.
   *
   * @memberof XamGridComponent
   */
  @Input()
  get borderBrush() {
    /* istanbul ignore else */
    if (
      this.model.BorderBrush != null &&
      this.model.BorderBrush instanceof SolidColorBrush
    ) {
      return smColorToCssColor(this.model.BorderBrush.Color);
    }
    return this.defaultBorderColor;
  }

  /**
   * Sets the border color of the control.
   *
   * @memberof XamGridComponent
   */
  set borderBrush(value: any) {
    this.modelProxy.BorderBrush = Utils.createSolidBrush(value);
  }

  /**
   * Applies the 'solid' style based on the model BorderThickness
   * value.
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get borderStyle() {
    return this.model.BorderThickness ? 'solid' : null;
  }

  /**
   * Sets the visibility of the Xam grid columns headers.
   *
   * @memberof XamGridComponent
   */
  @Input()
  set headersVisibility(value: any) {
    this.modelProxy.HeaderVisibility = value;
    if (value === 'hidden') {
      if (!this.additionalStyles.sheet.cssRules.length) {
        this.additionalStyles.sheet.insertRule(
          'div.igx-grid__thead { display: none !important; }',
          0
        );
      }
      this.additionalStyles.sheet.disabled = false;
    } else {
      this.additionalStyles.sheet.disabled = true;
    }
  }

  /**
   * Gets the visibility of the Xam grid columns headers.
   *
   * @memberof XamGridComponent
   */
  get headersVisibility(): any {
    return this.modelProxy.HeaderVisibility;
  }

  /**
   * Gets/sets the ColumnWidth type for the columns of the grid.
   * Runs only on initialization
   *
   * @type {ColumnWidth}
   * @memberof XamGridComponent
   */
  @Input()
  get columnWidth(): ColumnWidth {
    return this.modelProxy.ColumnWidth;
  }

  set columnWidth(value: ColumnWidth) {
    /* istanbul ignore else */
    if (value) {
      this.modelProxy.ColumnWidth = ColumnWidth.parse(value);
    }
  }

  /**
   * Gets/sets the isAlternateRowsEnabled, if enabled even rows
   * will display in a different color.
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  @Input()
  get isAlternateRowsEnabled(): boolean {
    return this.modelProxy.IsAlternateRowsEnabled;
  }

  set isAlternateRowsEnabled(value: boolean) {
    this.modelProxy.IsAlternateRowsEnabled = value;
  }

  /**
   * Returns whether the xam grid has a column layout component
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get hasColumnLayout() {
    return !!this.columnLayout || !!this.columnLayoutInheritance;
  }

  /**
   * Returns whether the xam grid has a to add the AddNewRow
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get shouldAddNewRow() {
    return !!this.addNewRow || !!this.addNewRowInheritance;
  }

  /**
   *  Returns whether Add new row button should be disabled
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get disableAddNewRow(): boolean {
    return !!this.addNewRowInheritance?.disableAddNewRow;
  }

  /**
   * Returns the AddNewRow Instance from the current or inherited component
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get addNewRowInstance(): AddNewRowSettingsComponent | any {
    return this.addNewRow ?? this.addNewRowInheritance;
  }

  /**
   * Returns the SortingSettingsComponent instance.
   *
   * @readonly
   * @type {SortingSettingsComponent}
   * @memberof XamGridComponent
   */
  get sortingComponent(): SortingSettingsComponent {
    return this.sorting ?? this.sortingSettingInheritance;
  }

  /**
   * Returns the columnLayout collection
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get columnLayoutItems(): XamColumnLayoutComponent | any {
    return this.columnLayout ?? this.columnLayoutInheritance;
  }

  /**
   * Returns the columns collection
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get gridColumnsItems() {
    return this.gridColumns.length > 0 || !this.gridColumnsInheritance
      ? this.gridColumns
      : this.gridColumnsInheritance;
  }

  /**
   * Returns columns to be rendered.
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get columnsToRender(): any {
    return this.model.Columns;
  }

  /**
   * Returns columns from the model.
   *
   * @readonly
   * @memberof XamGridComponent
   */
  // eslint-disable-next-line
  get Columns(): ColumnBaseCollection {
    return this.modelProxy.getColumns();
  }

  /**
   * Returns the rows from the model.
   *
   * @readonly
   * @memberof XamGridComponent
   */
  // eslint-disable-next-line
  get Rows() {
    return this.model.Rows;
  }

  /**
   * The underlying Angular `igx-grid` instance.
   *
   * @public
   * @type {IgxGridComponent}
   * @memberof XamGridComponent
   */
  @ViewChild('grid', { read: IgxGridComponent })
  public gridRef: IgxGridComponent;

  /**
   * Reference to the component itself in case the control is inherit.
   *
   * @public
   * @type {XamGridComponent}
   * @memberof XamGridComponent
   */
  @ViewChild(XamGridComponent)
  public gridComponent: XamGridComponent;

  /**
   * Returns the IgxGridComponent reference.
   *
   * @type {IgxGridComponent}
   * @memberof XamGridComponent
   */
  get grid(): IgxGridComponent {
    return this.gridRef ?? this.gridComponent?.gridRef;
  }

  /**
   * Returns whether the grid is currently in edit mode.
   *
   * @readonly
   * @memberof XamGridComponent
   */
  get inEditMode() {
    const service = this.grid.crudService as any;
    return service.cellInEditMode || service.rowInEditMode;
  }

  /**
   * Returns the currently visible columns.
   *
   * @readonly
   * @type {IgxColumnComponent[]}
   * @memberof XamGridComponent
   */
  get visibleColumns(): IgxColumnComponent[] {
    if (!this.grid?.columns) {
      return [];
    }
    return this.grid.columns.filter((col) => col.headerCell != null);
  }

  /**
   * Returns if the grid has any filterable or sortable column
   *
   * @readonly
   * @type {boolean}
   * @memberof XamGridComponent
   */
  get isFilteredSortedData(): boolean {
    return this.grid.hasFilterableColumns || this.grid.hasSortableColumns;
  }

  /**
   * The internal grid for columnLayout section.
   *
   * @public
   * @type {XamGridComponent}
   * @memberof XamGridComponent
   */
  @ViewChildren('gridColumnLayout')
  public gridColumnLayouts: QueryList<XamGridComponent>;

  /**
   * Column layout component if present
   *
   * @public
   * @type {XamColumnLayoutComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(XamColumnLayoutComponent)
  public columnLayout: XamColumnLayoutComponent;

  /**
   * Event manager to change the order of certain events
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected eventManager: XamDataGridEventManager;

  /**
   * Selection settings component if present
   *
   * @protected
   * @type {SelectionSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(SelectionSettingsComponent)
  protected selection: SelectionSettingsComponent;

  /**
   * Column moving settings component if present
   *
   * @protected
   * @type {ColumnMovingSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(ColumnMovingSettingsComponent)
  protected columnMoving: ColumnMovingSettingsComponent;

  /**
   * Add new row settings if present
   *
   * @protected
   * @type {AddNewRowSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(AddNewRowSettingsComponent)
  addNewRow: AddNewRowSettingsComponent;

  /**
   * Editing settings component if present
   *
   * @protected
   * @type {EditingSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(EditingSettingsComponent)
  public editing: EditingSettingsComponent;

  /**
   * Filtering settings component if present
   *
   * @protected
   * @type {FilteringSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(FilteringSettingsComponent)
  protected filtering: FilteringSettingsComponent;

  /**
   * Sorting settings component if present
   *
   * @protected
   * @type {SortingSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(SortingSettingsComponent)
  sorting: SortingSettingsComponent;

  /**
   * Summary settings component if present
   *
   * @protected
   * @type {SummarySettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(SummarySettingsComponent)
  protected summary: SummarySettingsComponent;

  /**
   * Pagination settings component if present
   *
   * @protected
   * @type {PagerSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(PagerSettingsComponent)
  protected pagination: PagerSettingsComponent;

  /**
   * Column resizing settings component if present
   *
   * @protected
   * @type {ColumnResizingSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(ColumnResizingSettingsComponent)
  protected resizing: ColumnResizingSettingsComponent;

  /**
   * Row selectors settings component if present
   *
   * @protected
   * @type {RowSelectorSettingsComponent}
   * @memberof XamGridComponent
   */
  @ContentChild(RowSelectorSettingsComponent)
  protected rowSelectors: RowSelectorSettingsComponent;

  /**
   * Handler for the subscriptionEvent of the SortedColumns
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  sortedColumnsHandler: any;

  /**
   * Handler for the subscriptionEvent
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  collectionHandler: any;

  /**
   * Selected rows collection handler
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  selectedRowsCollectionHandler: any;

  /**
   * Semaphore timer to avoid consecutive refresh of Rows structure
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  refreshRowsTimer: any;

  /**
   * Flag to determine if we need to call the processActiveCell method
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  callProcessActiveCellFlag = false;

  /**
   * Flag timer to avoid consecutive vertical scroll events triggered
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  isScrollingVertical: any;

  /**
   * Flag to avoid consecutive horizontal scroll events triggered.
   *
   * @type {any}
   * @memberof XamGridComponent
   */
  isScrollingHorizontal = false;

  /**
   * Flag to indicate when autosize is being calculated for auto columns.
   *
   * @memberof XamGridComponent
   */
  isEvaluatingAutoColumns = false;

  /**
   * Flags that allow navigating throw cells in edit mode event when there are validation errors
   *
   * @memberof XamGridComponent
   */
  isTabKeyNavigating = false;

  /**
   * Flag to indicate when RowCollectionHandler is executing.
   *
   * @memberof XamGridComponent
   */
  isHandlingCollectionChanges = false;

  /**
   * Flag timer to avoid consecutive update of rows filtered
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  refreshRowsFilteredTimer: any;

  /**
   * Additional string filter conditions for the Xam grid.
   *
   * @memberof XamGridComponent
   */
  xamTextFilters = XamGridCustomFilterOperands.instance();

  /**
   * Stores the columns which have been autosized during
   * a horizontal scroll event.
   *
   * @memberof XamGridComponent
   */
  protected autosizedColumns = new Set<IgxColumnComponent>();

  /**
   * Array with data.
   *
   * @type {Array<any>}
   * @memberof XamGridComponent
   */
  protected internalData: Array<any>;

  /**
   * Component's collection wrapper
   * that includes a primary key definition
   * that allows the grid to be edited
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected internalDataWrapper = [];

  /**
   * Columns layout models dictionay
   *
   * @protected
   * @type {SimpleDictionary<any, XamGridModel>}
   * @memberof XamGridComponent
   */
  protected columnLayoutModels: SimpleDictionary<any, XamGridModel>;

  /**
   * The model proxy of XamGridModel
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected modelProxy = ModelProxy.create<XamGridModel>();

  /**
   * Value to limit how many rows are rendered in the DOM when no virtualization is applied
   * This value affects the performance of the grid
   *
   * @protected
   * @type {number}
   * @memberof XamGridComponent
   */
  protected rowLimitNoVirtualization = 2000;

  /**
   * Cells which have a validation message attached.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected cellsWithValidationMsg = new Set<any>();

  /**
   * The iterable differ for the XamGridColumnComponents
   *
   * @private
   * @type {IterableDiffer<XamGridColumnComponent>}
   * @memberof XamGridComponent
   */
  private differ: IterableDiffer<XamGridColumnComponent>;

  /**
   * The html additional styles elements
   *
   * @private
   * @type {HTMLStyleElement}
   * @memberof XamGridComponent
   */
  private additionalStyles: HTMLStyleElement;

  /**
   * Cached latest grid width used to determine if the columns should be calculated again
   *
   * @private
   * @type {number}
   * @memberof XamGridComponent
   */
  private cachedGridWidth = 0;

  /**
   * Tolerance of grid columns width mechanism
   *
   * @private
   * @type {number}
   * @memberof XamGridComponent
   */
  private toleranceGridColumnWidth = 18;

  /**
   * Forces the calculation of the columns width
   *
   * @private
   * @type {boolean}
   * @memberof XamGridComponent
   */
  private forcedColumnsWidthCalc = false;

  /**
   * Forces the calculation of the columns width for special values
   *
   * @private
   * @type {boolean}
   * @memberof XamGridComponent
   */
  private requiredSpecialValuesSizeReCalc = false;
  /**
   * Flag to determine if the active cell changed in the enterEditHandler
   *
   * @private
   * @type {boolean}
   * @memberof XamGridComponent
   */
  private isActiveCellChanged = false;

  /**
   * Plan to apply columns footer style.
   *
   * @private
   * @type {StylePlan}
   * @memberof XamGridComponent
   */
  private footerStylePlan: StylePlan;

  /**
   * Sorting settings for the grid
   *
   * @readonly
   * @memberof XamGridComponent
   */
  sortingExpressions: ISortingExpression[] = [];

  /**
   * Filtering settings for the grid
   *
   * @readonly
   * @memberof XamGridComponent
   */
  filteringExpressions: IFilteringExpression[] | FilteringExpressionsTree = [];

  /**
   * Get the filteringExpressions as IFilteringExpressionsTree
   * @returns IFilteringExpressionsTree
   * @memberof XamGridComponent
   */
  set filteringExpressionsTree(value: IFilteringExpressionsTree) {
    this.filteringExpressions = value as FilteringExpressionsTree;
  }

  /**
   * Get the filteringExpressions as IFilteringExpressionsTree
   * @returns IFilteringExpressionsTree
   * @memberof XamGridComponent
   */
  get filteringExpressionsTree(): IFilteringExpressionsTree {
    return this.filteringExpressions as IFilteringExpressionsTree;
  }

  /**
   * Handler for the subscriptionEvent of the filteredExpressions
   *
   * @type {*}
   * @memberof XamGridComponent
   */
  private filteredExpressionsHandler: any;

  /**
   * Flag to check when the grid has calculated the first column width
   *
   * @private
   * @type {boolean}
   * @memberof XamGridComponent
   */
  private isColumnsWidthSet = false;

  /**
   * Creates an instance of XamGridComponent.
   *
   * @param {Injector} injector
   * @param {ChangeDetectorRef} cdRef
   * @param {IterableDiffers} differsFactory
   * @param {ComponentFactoryResolver} resolver
   * @param {ElementRef<HTMLElement>} ref
   * @param {*} document
   * @param {IgxIconService} iconService
   * @memberof XamGridComponent
   */
  constructor(
    private injector: Injector,
    private cdRef: ChangeDetectorRef,
    private differsFactory: IterableDiffers,
    private resolver: ComponentFactoryResolver,
    private ref: ElementRef<HTMLElement>,
    @Inject(DOCUMENT) private document,
    private iconService: IgxIconService
  ) {
    super();
    if (this.constructor.name === 'XamGridComponent') {
      this.eventManager = new XamDataGridEventManager(this);
    }
    this.differ = this.differsFactory
      .find([])
      .create<XamGridColumnComponent>(null);
    /* istanbul ignore else */
    if (this.document) {
      this.additionalStyles = document.createElement('style');
      document.head.appendChild(this.additionalStyles);
    }
  }

  /**
   * Gets row from model by key
   *
   * @param {*} rowSelector
   * @param {XamGridComponent} component
   * @returns {XamGridRow}
   * @memberof XamGridComponent
   */
  public getRowFromModelByKey(
    rowSelector: any,
    component: XamGridComponent
  ): XamGridRow {
    if (!component.grid) {
      return null;
    }
    const primaryKey = component.grid.primaryKey;
    if (primaryKey !== undefined && primaryKey !== null) {
      const rowIndex = component.internalDataWrapper.findIndex(
        (row) => row[primaryKey] === rowSelector
      );
      if (rowIndex !== -1) {
        return component.Rows.getItem(rowIndex);
      }
      return null;
    } else {
      return component.Rows.toArray().find((row) => isEqual(row, rowSelector));
    }
  }

  /**
   * Get row from filtered data by key
   *
   * @param {*} rowSelector
   * @returns {XamGridRow}
   * @memberof XamGridComponent
   */
  public getRowFromFilteredDataByKey(rowSelector: any): XamGridRow {
    const primaryKey = this.grid.primaryKey;
    if (primaryKey != null) {
      let rowIndex = -1;
      if (rowSelector instanceof XamGridRow) {
        rowIndex = this.Rows.toArray().findIndex(
          (row) => row.Data == rowSelector.Data
        );
      } else {
        rowIndex = this.grid.filteredSortedData.findIndex(
          (row) => row[primaryKey] === rowSelector
        );
      }

      if (rowIndex !== -1) {
        return this.Rows.getItem(rowIndex);
      }
      return null;
    } else {
      return this.Rows.toArray().find((row) => isEqual(row, rowSelector));
    }
  }

  /**
   * Gets the PagerSettingsComponent reference.
   *
   * @memberof XamGridComponent
   */
  public getPaginationComponent() {
    return this.pagination;
  }

  /**
   * Angular lifecycle hook.
   * Generates column models if needed and adds then to the 'silverlight' model.
   *
   * @memberof XamGridComponent
   */
  ngAfterContentInit() {
    super.ngAfterContentInit();
    if (
      !this.gridColumnsItems.length &&
      this.data?.length &&
      this.autoGenerateColumns
    ) {
      this.generateColumnModels();
    }
    if (this.model.IsFirstTimeLoad) {
      if (this.hasColumnLayout) {
        // Remove the columns from the layout part before populating the model
        const layout = this.columnLayoutItems.columns.toArray();
        const collection = this.gridColumnsItems.filter(
          (each) => !layout.includes(each)
        );
        this.gridColumnsItems.reset(collection);
      }
      this.model.Columns.forEach(
        (item) => (item.ColumnLayout.Grid = this.model)
      );
      if (this.constructor.name === 'XamGridComponent') {
        this.gridColumnsItems.forEach(
          (item) =>
            (item.model.ColumnLayout = this.model.RowsManager.ColumnLayout)
        );
        this.gridColumnsItems
          .filter(
            (item) => !item.model.Parent || item.model.Parent === this.model
          )
          .forEach((item) => this.model.addColumn(item.model));
      }
    }
    this.setupGridSettings();
  }

  /**
   * Returns a new model for the details XamGrid.
   *
   * @param {*} dataItem
   * @returns
   * @memberof XamGridComponent
   */
  populateModel(dataItem: any, index: number) {
    let populateModel: XamGridModel;
    if (!this.columnLayoutModels) {
      this.columnLayoutModels = new SimpleDictionary();
    }
    if (!this.columnLayoutModels.hasKey(dataItem)) {
      populateModel = new XamGridModel();
      populateModel.Parent = this.model;
      this.columnLayoutModels.setItem(dataItem, populateModel);
      this.columnLayoutItems.columns.forEach((col) =>
        populateModel.addColumn(col.model)
      );
      populateModel.SelectionSettings = this.model.SelectionSettings;
      populateModel.FilteringSettings = this.model.FilteringSettings;
    } else {
      populateModel = this.columnLayoutModels.getItem(dataItem);
    }
    if (this.columnLayoutModels.internalArray.length > 0) {
      this.setParentRow(populateModel, index);
    }
    return populateModel;
  }

  /**
   * Angular lifecycle hook.
   *
   * @memberof XamGridComponent
   */
  ngAfterViewInit() {
    super.ngAfterViewInit();
    this.registerHandlerForObservablesCollection();
    this.processFilterCellTooltip();
    this.eventManager?.register();
    // Ignores ignite navigation to set a custom one
    if (this.grid?.navigation) {
      this.grid.navigation.handleNavigation = () => {};
    }
    this.afterViewInitCalled = true;
  }

  /**
   * Angular lifecycle hook.
   *
   * @memberof XamGridComponent
   */
  ngAfterViewChecked() {
    /* istanbul ignore else */
    if (!this.isInternalInherit) {
      this.applyColumnFooterStyles();
    }
  }

  /**
   * Apply footer styles to DOM elements.
   *
   * @memberof XamGridComponent
   */
  applyColumnFooterStyles() {
    const newStylePlan = this.buildFooterStylePlan();
    /* istanbul ignore else */
    if (newStylePlan != null && !newStylePlan.equals(this.footerStylePlan)) {
      newStylePlan.execute();
      this.footerStylePlan = newStylePlan;
    }
  }

  /**
   * Build a footer style plan for the current grid.
   *
   * @return {*}  {StylePlan}
   * @memberof XamGridComponent
   */
  buildFooterStylePlan(): StylePlan {
    const summaryCells = this.ref.nativeElement.querySelectorAll(
      'igx-grid-summary-cell'
    );
    /* istanbul ignore else */
    if (summaryCells.length === 0) {
      return null;
    }
    const stylePlan = new StylePlan();
    const notGroupColumns = this.getAllColumns().internalArray.filter(
      (col) => !(col instanceof XamGridGroupColumnModel)
    );
    for (let i = 0; i < notGroupColumns.length; i++) {
      const col = notGroupColumns[i];
      this.processFooterStyle(stylePlan, summaryCells[i], col.FooterStyle);
    }
    return stylePlan.size > 0 ? stylePlan : null;
  }

  /**
   * Process the given footer style into the style plan.
   *
   * @param {StylePlan} stylePlan
   * @param {Element} element
   * @param {RuntimeStyleInfo} footerStyle
   * @memberof XamGridComponent
   */
  processFooterStyle(
    stylePlan: StylePlan,
    element: Element,
    footerStyle: RuntimeStyleInfo
  ) {
    /* istanbul ignore else */
    if (footerStyle == null) {
      return;
    }
    for (const setter of footerStyle.Setters) {
      /* istanbul ignore else */
      if (
        setter.Property === 'Background' &&
        setter.Value instanceof SolidColorBrush
      ) {
        stylePlan.add(setter, element as HTMLElement);
      } else if (
        setter.Property === 'Foreground' &&
        setter.Value instanceof SolidColorBrush
      ) {
        this.processInnerSpans(stylePlan, element, setter);
      }
    }
  }

  /**
   * Add inner `span` elements into style plan, instead of the given element.
   *
   * @param {StylePlan} stylePlan
   * @param {Element} element
   * @param {Setter} setter
   * @memberof XamGridComponent
   */
  processInnerSpans(stylePlan: StylePlan, element: Element, setter: Setter) {
    element?.querySelectorAll('span').forEach((span) => {
      stylePlan.add(setter, span);
    });
  }

  /**
   * Register a handler for current observableCollection
   *
   * @memberof XamGridComponent
   */
  registerHandlerForObservablesCollection() {
    this.removeItemSourceHandler();
    /* istanbul ignore else */
    if (
      this.data &&
      ReflectionHelper.isInterfaceIsSupported(
        this.data,
        'System.Collections.Specialized.INotifyCollectionChanged'
      )
    ) {
      this.invalidateInternalDataWrapper();
      if (!this.isInternalInherit) {
        this.collectionHandler = this.data.CollectionChanged.insertHandler(
          0,
          this.applyDataUpdate.bind(this.xamGridContext)
        );
      }
    }
  }

  /**
   * Verify grid row selection
   *
   * @memberof XamGridComponent
   */
  verifyGridSelections(): void {
    if (!this.grid) return;
    const selectedIndexes = [];
    // Validates witch rows have indexes greeter than 0
    for (const selected of this.selectionSettings?.SelectedRows.toArray()) {
      const idx = this.isFilteredSortedData
        ? this.getRowIndexFromFilteredData(selected)
        : this.getRowIndex(selected);
      if (idx < 0) {
        this.selectionSettings.SelectedRows.remove(selected);
      } else {
        selectedIndexes.push(idx);
      }
    }
    // Finds the rows keys by index and selects it in the igx-grid
    const rowKeys = [];
    if (this.model.SelectionSettings.RowSelection === XamSelectionMode.None) {
      return;
    }
    selectedIndexes.forEach((x) => {
      const row = this.grid.getRowByIndex(x);
      if (row) {
        rowKeys.push(row.key);
      }
    });

    //cell selection happens by user clicking on a cell (singlor o with Ctrl)
    //or by setting a cell as selected (cell.selected = true), events like changing
    //the items source doesn't necessary clear previous selections, so we will
    //make sure to start with a clean slate
    this.grid.clearCellSelection();
    if (rowKeys.length > 0) {
      this.grid.selectRows(rowKeys, true);
    }
  }

  /**
   * Reassign current grid data. This is required to see changes reflected.
   *
   * @memberof XamGridComponent
   */
  applyDataUpdate(isRendering = false): void {
    if (this.constructor.name !== 'XamGridComponent') {
      return;
    }
    if (this.refreshRowsTimer) {
      clearTimeout(this.refreshRowsTimer);
    }
    this.model.NotifyDataUpdate();
    this.InvalidateSelectionAndActivation();
    this.RowCollectionHandler(isRendering);
  }

  /**
   * Handler used to fill grid data when is required
   *
   * @memberof XamGridComponent
   */
  gridDataHandler() {
    if (this.grid) {
      this.totalPagingRecordsHandler();
      this.grid.endEdit?.(false);
      this.addRowKey = null;
      setTimeout(() => {
        this.verifyGridSelections();
      }, 100);
    }
  }

  /**
   * Returns data
   *
   * @return {any[]}
   * @memberof XamGridComponent
   */
  dataHandler(): any[] {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    this.internalDataWrapper = this.collection?.map((record: any) => ({
      __xam_internal_pk__: uuidv4(),
      data: record,
    }));
    return this.internalDataWrapper;
  }

  /**
   * Angular lifecycle hook.
   * Removes all the event listeners for the disabled overlay.
   *
   * @memberof XamGridComponent
   */
  ngOnDestroy() {
    super.ngOnDestroy();
    const params = this.createEventParams();
    this.model?.ScrolledCellIntoView.removeHandler(
      this.scrollCellIntoViewHandler,
      this
    );
    this.model?.Unloaded.fire([params.sender, params.e]);
    this.unloaded.emit(params);
    /* istanbul ignore else */
    if (typeof this.ref.nativeElement.removeEventListener === 'function') {
      DISABLED_EVENTS.forEach((type) => {
        this.ref.nativeElement.removeEventListener(
          type,
          this.disabledEventsHandler,
          { capture: true }
        );
      });
    }
    this.document?.head?.removeChild(this.additionalStyles);
    this.removeItemSourceHandler();
    this.eventManager?.destroy();
    /* istanbul ignore else */
    if (this.model) {
      this.model.gridComponentInstance = null;
    }
  }

  /**
   * Starts the row adding UI for the XamGrid.
   *
   * @param {number} [index=0]
   * @memberof XamGridComponent
   */
  beginAddRow(index?: number) {
    const isAddTop =
      this.addNewRow?.allowAddNewRow === 'Top' ||
      this.addNewRowInheritance?.allowAddNewRow === 'Top';
    index = index ?? (isAddTop ? 0 : this.grid.dataView.length);
    setTimeout(() => {
      this.grid.beginAddRowByIndex(index);
      this.addRowKey = this.grid.getRowByIndex(index);
    }, 100);
  }

  /**
   * Ends row editing and triggers the event pipeline.
   *
   * @param {boolean} [commit=true]
   * @param {Event} [event]
   * @memberof XamGridComponent
   */
  endRowEdit(commit = true, event?: Event) {
    this.grid.crudService.endRowTransaction(commit, event);
  }

  /**
   * Returns if row is defined
   *
   * @return {*}  {boolean}
   * @memberof XamGridComponent
   */
  rowDefined(): boolean {
    return this.grid?.getRowByIndex(0) !== undefined;
  }

  /**
   * Event emitted when the underlying igx-grid is considered 'ready' in the DOM.
   *
   * @memberof XamGridComponent
   */
  renderedHandler() {
    this.grid.dataCloneStrategy = new CloneStrategy();
    const params = this.createEventParams();
    this.setupDisabledOverlay();
    this.setupRowSelectorsSettings();
    this.setupColumnTracking();
    this.setupCellTracker();
    this.setupAvailableRows();
    this.adjustPagerSettings();

    // Footer workarounds
    /* istanbul ignore else */
    if (this.model.FooterVisibility) {
      this.grid.columns
        .filter((col) => !col.columnGroup)
        .forEach((col) => (col.hasSummary = true));
      this.grid.summaryService.recalculateSummaries();
    }

    // There is a timing issue with the generation of the mocked pks for the bound data source.
    // Since inputs are bound right after the constructor the grid tries to resolve the already bound primaryKey
    // and fails. Thus we setup it here.
    this.grid.primaryKey = '__xam_internal_pk__';
    this.applyDataUpdate(true);
    this.verifyGridSelections();

    this.model.Rendered.fire([params.sender, params.e]);
    this.model.Loaded.fire([params.sender, params.e]);
    this.rendered.emit(params);
    this.loaded.emit(params);
    this.renderedCalled = true;
  }

  /**
   * Renders and rebinds the context for the footers of the Xam grid.
   *
   * @memberof XamGridComponent
   */
  renderFooters() {
    this.gridColumnsItems.forEach((column) => column.setupFooters());
    this.grid.summaryService.recalculateSummaries();
    this.grid.notifyChanges(true);
  }

  /**
   * Called whenever sorting action is done
   * Avoids the sorting 'None' state
   *
   * @param {*} e
   * @memberof XamGridComponent
   */
  sortingHandler(e: any): void {
    // TODO: This code should consider if the click event has the ctrl key pressed,
    // If so, it should conserve the current sorting expressions in the array and add the new one,
    // otherwise, it should clear the sorting expressions and save the last one.
    if (e.sortingExpressions.length > 0) {
      this.columnSortingEventParameters(e);
      this.model.ColumnSorting.fire([this.model, e]);
      this.grid.sortingExpressions = [e.sortingExpressions.at(-1)];
    } else {
      e.cancel = true;
      this.lastSortingExpression[0].dir = SortingDirection.Asc;
      this.grid.sort(this.lastSortingExpression);
    }
  }

  /**
   * The function is called when the user clicks on a column header sorting arrow. It gets the column name from the
   * event and then sets the sorting expression to that column.
   * @param {any} e - any - the sorting event object
   */
  private columnSortingEventParameters(e: any): void {
    this.lastSortingExpression = e.sortingExpressions;
    const key: string = e.sortingExpressions
      .at(-1)
      .fieldName.replace('data.', '');
    e.Column = this.Columns.getColumnByKey(key);
    e.Cancel = e.cancel;
  }

  /**
   * Sorting Done handler, it is called when the sorting is done in the igx-grid
   * The code syncs the sorting expressions with the XamGrid.model.SortingSettings.SortedColumns
   * Also call the columnSorted event.
   *
   * @param {(ISortingExpression | ISortingExpression[])} event
   * @memberof XamGridComponent
   */
  sortingDoneHandler(
    event: ISortingExpression | ISortingExpression[],
    singleColumn = true
  ): void {
    // TODO: We need to determinate if the column is a group of columns (using the CTRL key) or just single column.
    // This code assumes that the sorted column is a single column.
    const params = this.createEventParams();
    if (Array.isArray(event)) {
      singleColumn
        ? this.addColumnFromExpression(event.at(-1))
        : event.map((e) => this.addColumnFromExpression(e));
    } else {
      this.addColumnFromExpression(event);
    }

    // Updating the row collection with the sorted result
    this.updateRowsWithFilterResults();

    this.model.ColumnSorted.fire([params.sender, params.e]);
    this.columnSorted.emit(event);
  }

  /**
   * Handler triggered on scroll event
   * Be aware this event is called several times per scroll
   *
   * @param {IGridScrollEventArgs} event
   * @memberof XamGridComponent
   */
  gridScrollHandler(event: IGridScrollEventArgs): void {
    if (event.direction === 'horizontal' && !this.isScrollingHorizontal) {
      this.isScrollingHorizontal = true;
    } else if (event.direction === 'vertical' && !this.isScrollingVertical) {
      this.isScrollingVertical = setTimeout(() => {
        this.processAllowToolTipsByRow();
        this.isScrollingVertical = undefined;
      }, 5);
    }
  }

  /**
   * Handler triggered after the data has changed
   * Emitted after a data operation, rebinding, pagination, etc
   *
   * @param {any} event
   * @memberof XamGridComponent
   */
  dataChangedHandler(event: any): void {
    // Timeout required to let the grid refresh the items
    setTimeout(() => {
      this.processAllowToolTipsByRow();
    });
  }

  /**
   * Filtering handler
   *
   * @param {IFilteringExpressionsTree} event
   * @memberof XamGridComponent
   */
  filteringHandler(event: IFilteringExpressionsTree) {
    const params = this.createEventParams();
    /* istanbul ignore else */
    if (this.model.FilteringSettings && event.fieldName) {
      const rowFiltersArray =
        this.model.FilteringSettings.RowFiltersCollection.internalArray;
      const idx = rowFiltersArray.findIndex(
        (filter: IRecordFilter) => filter.FieldName === event.fieldName
      );
      if (idx < 0) {
        rowFiltersArray.push({
          Conditions: event.filteringOperands,
          FieldName: event.fieldName,
        });
      } else if (event.filteringOperands.length == 0) {
        rowFiltersArray.splice(idx, 1);
      } else {
        rowFiltersArray[idx].Conditions = event.filteringOperands;
      }

      /* eslint-enable */
    }

    // Updating the row collection with the filtered result
    this.updateRowsWithFilterResults();

    this.model.ColumnFiltered.fire([params.sender, params.e]);
    this.columnFiltered.emit(params);
  }

  updateRowsWithFilterResults(): void {
    /* istanbul ignore else */
    if (this.refreshRowsFilteredTimer) {
      clearTimeout(this.refreshRowsFilteredTimer);
    }
    this.refreshRowsFilteredTimer = setTimeout(() => {
      this.Rows.CollectionChanged.fire([
        this.model,
        new CollectionChangeInfo(CollectionChangeAction.Reset),
      ]);
    }, 20);
  }

  /**
   * Column pinning handler
   *
   * @param {IPinColumnEventArgs} event
   * @memberof XamGridComponent
   */
  pinningHandler(event: IPinColumnEventArgs) {
    const params = this.createEventParams();
    const colKey = event.column.field.replace('data.', '');
    const column = this.getColumnModelByKey(colKey);
    const isStart = this.grid?.pinning?.columns === ColumnPinningPosition.Start;
    const array = isStart
      ? this.model.FixedColumnSettings.FixedColumnsLeft
      : this.model.FixedColumnSettings.FixedColumnsRight;

    /* istanbul ignore else */
    if (column) {
      if (event.isPinned) {
        column.IsFixed = FixedState.NotFixed;
        array.remove(column);
      } else {
        column.IsFixed = isStart ? FixedState.Left : FixedState.Right;
        array.add(column);
      }
    }

    this.model.ColumnFixedStateChanged.fire([params.sender, params.e]);
    this.columnFixed.emit(params);
  }

  /**
   * Row added handler
   *
   * @param {IRowDataEventArgs} event
   * @memberof XamGridComponent
   */
  rowAddedHandler(event: IGridEditEventArgs) {
    if (
      ReflectionHelper.isInterfaceIsSupported(
        this.data,
        'System.Collections.Generic.ICollection`1'
      )
    ) {
      this.data.add(event.newValue.data);
    } else {
      this.data.push(event.rowData.data);
    }
    this.invalidateInternalDataWrapper();

    const row =
      event.rowID === this.addRowKey?.key
        ? this.getAddRow()
        : this.getAddedRow(event);
    const params = [this.model, new EditingRowEventArgs(row)];
    this.model.RowAdded.fire(params);
    // Hide the snackbar after row added
    const hideSnackbar = () =>
      ((
        this.grid.nativeElement.querySelector('igx-snackbar') as HTMLElement
      ).style.visibility = 'hidden');
    setTimeout(hideSnackbar);
  }

  /**
   * Returns the row created from the data
   *
   * @private
   * @param {IGridEditEventArgs} event
   * @memberof XamGridComponent
   */
  private getAddedRow(event: IGridEditEventArgs) {
    const eventRowID = JSON.stringify(event.rowID);
    const rowComponent = this.grid.rowList.find(
      (r) => eventRowID === JSON.stringify(r.key)
    );
    this.createRowFromData(event.newValue, rowComponent?.index);
  }

  /**
   * Row deleted handler
   *
   * @param {IRowDataEventArgs} event
   * @memberof XamGridComponent
   */
  rowDeletedHandler(event: IRowDataEventArgs) {
    const params = this.createEventParams();
    if (
      ReflectionHelper.isInterfaceIsSupported(
        this.data,
        'System.Collections.Generic.ICollection`1'
      )
    ) {
      this.data.remove(event.data.data);
    } else {
      const index = this.data.indexOf(event.data.data);
      this.data.splice(index, 1);
    }
    this.invalidateInternalDataWrapper();
    this.model.RowDeleted.fire([params.sender, params.e]);
    this.rowDeleted.emit(params);
  }

  /**
   * Pagination handler for the control
   *
   * @param {IPageEventArgs} event
   * @memberof XamGridComponent
   */
  paginationHandler(event: IPageEventArgs) {
    const { previous, current } = event;
    const changingEvent = new CancellablePageChangingEventArgs();

    changingEvent.NextPageIndex = current;
    this.model.PageIndexChanging.fire([this.model, changingEvent]);
    this.pageIndexChanging.emit(changingEvent);
    /* istanbul ignore next */
    if (changingEvent.Cancel) {
      event.current = previous;
      return;
    }
    /* istanbul ignore next */
    if (this.model.ItemsSource?.['MoveToPage']) {
      this.model.ItemsSource['PageSize'] = this.model.PagerSettings.PageSize;
      this.model.ItemsSource['MoveToPage'](event.current);
    }

    const changedEvent = new PageChangedEventArgs();
    changedEvent.OldPageIndex = previous;
    this.model.PageIndexChanged.fire([this.model, changedEvent]);
    this.pageIndexChanged.emit(changedEvent);
  }

  /**
   * Flag to know when a style change from the model
   *
   * @memberof XamGridComponent
   */
  cellChanged(): void {
    this.styleChanged = true;
  }

  /**
   * Usable for the grid to know when the style has changed and need to call the AlignmentPipe
   *
   * @memberof XamGridComponent
   */
  public ngAfterContentChecked(): void {
    /* istanbul ignore else */
    if (this.styleChanged) {
      this.styleChanged = false;
      this.isCellChanged += 1;
    }
  }

  /**
   * Angular lifecycle hook. Use to apply the style over the cells and rows
   * In charge of triggering the first and any other re-evaluation of the columns width calculation
   *
   * @memberof XamGridComponent
   */
  public ngDoCheck(): void {
    if (
      !this.grid?.['_init'] &&
      this.afterViewInitCalled &&
      this.isGridVisible &&
      this.cachedGridWidth != null &&
      (!this.isColumnsWidthSet || this.shouldReCalcColumnsWidth())
    ) {
      const reCalcSpecialValues =
        this.requiredSpecialValuesSizeReCalc || !this.isColumnsWidthSet;
      this.setColumnsWidth(reCalcSpecialValues);
      this.isColumnsWidthSet = true;
    }
    /* istanbul ignore else */
    if (this.isScrollingHorizontal && !this.isEvaluatingAutoColumns) {
      this.isScrollingHorizontal = false;
      this.evaluateAutoColumns();
    }
    /* istanbul ignore else */
    if (
      this.validationErrorFlag &&
      this.renderedCalled &&
      !this.isGridVisible
    ) {
      this.grid.endEdit(false);
      this.removeAllErrorMessages();
    }
  }

  /**
   * Initialize model object.
   *
   * @memberof XamGridComponent
   */
  ngOnInit(): void {
    this.model = this.model || new XamGridModel();
    this.setupModel(this.model);
    super.ngOnInit();
    this.registerObservers(
      this.selectedRowsCollectionChanged,
      this.model,
      this.model.SelectedRowsCollectionChanged
    );
    this.registerObservers(
      this.activeCellChanged,
      this.model,
      this.model.ActiveCellChanged
    );
    this.registerObservers(
      this.cellDoubleClicked,
      this.model,
      this.model.CellDoubleClicked
    );
    this.registerObservers(
      this.cellClicked,
      this.model,
      this.model.CellClicked
    );
    this.registerObservers(
      this.cellControlAttached,
      this.model,
      this.model.CellControlAttached
    );
    this.registerObservers(
      this.afterCellControlAttached,
      this.model,
      this.model.AfterCellControlAttached
    );
    this.registerObservers(
      this.cellEnteredEditMode,
      this.model,
      this.model.CellEnteredEditMode
    );
    this.registerObservers(
      this.cellEnteringEditMode,
      this.model,
      this.model.CellEnteringEditMode
    );
    this.registerObservers(
      this.cellExitedEditMode,
      this.model,
      this.model.CellExitedEditMode
    );
    this.registerObservers(this.rowAdded, this.model, this.model.RowAdded);
    this.registerObservers(
      this.rowEnteredEditMode,
      this.model,
      this.model.RowEnteredEditMode
    );
    this.registerObservers(
      this.rowExitedEditMode,
      this.model,
      this.model.RowExitedEditMode
    );
    this.registerObservers(
      this.initializeRow,
      this.model,
      this.model.InitializeRow
    );
    this.registerObservers(
      this.columnResizing,
      this.model,
      this.model.ColumnResizing
    );
    this.registerObservers(
      this.columnFixedStateChanged,
      this.model,
      this.model.ColumnFixedStateChanged
    );
    this.registerObservers(
      this.columnSorted,
      this.model,
      this.model.ColumnSorted
    );
    this.registerObservers(
      this.columnSorting,
      this.model,
      this.model.ColumnSorting
    );
    this.registerObservers(
      this.cellExitingEditMode,
      this.model,
      this.model.CellExitingEditMode
    );
    this.model.ScrolledCellIntoView.addHandler(
      this.scrollCellIntoViewHandler,
      this
    );

    this.iconService.addSvgIconFromText(
      'does-not-end-with',
      iconDoesNotEndWith,
      'filter-icons'
    );
    this.iconService.addSvgIconFromText(
      'does-not-start-with',
      iconDoesNotStartWith,
      'filter-icons'
    );
    this.setupSortedColumns();
    this.setupFilteringExpressions();
    if (this.constructor.name === 'XamGridComponent') {
      this.model.gridComponentInstance = this;
    }
  }

  /**
   * Detects if there are changes on the model.
   *
   * @param {string} [name]
   * @param {*} [args]
   * @memberof XamGridComponent
   */
  modelChangeHandler(name?: string, args?: any) {
    this.processItemSource(name);
    this.processItemsSourceChange(name, args);
    this.processHeaderStyle(name);
    this.processGridSelections(name);
    this.processTriggerExitEditMode(name, args);
    super.modelChangeHandler(name);
  }

  /**
   * Handler for the ScrollIntoView method
   *
   * @param {XamGridCell} cell
   * @memberof XamGridComponent
   */
  scrollCellIntoViewHandler(cell: XamGridCell): void {
    if (cell.Row.Index === -1) return;
    setTimeout(() => {
      this.grid?.navigateTo(cell.Row.Index);
    }, 10);
  }

  /**
   * When SelectedItem is changed verify Grid Selections.
   *
   * @param {string} name
   * @return {*}
   * @memberof XamGridComponent
   */
  processGridSelections(name: string): void {
    /* istanbul ignore else */
    if (name === 'SelectedRowsChanged') {
      setTimeout(() => {
        this.verifyGridSelections();
      }, 150); //Mobilize-Note: change must be reviewed in bug 499127
    }
  }

  /**
   * When itemSource is changed igxGrid reassign the data.
   *
   * @param {string} name
   * @return {*}
   * @memberof XamGridComponent
   */
  processItemSource(name: string): void {
    if (name === undefined || name === 'ItemsSource') {
      this.invalidateInternalDataWrapper();
      this.applyDataUpdate.call(this.xamGridContext);
      /* istanbul ignore else */
      if (name === 'ItemsSource') {
        this.resetScrollPosition();
      }
      this.resetRows();
      this.removeAllErrorMessages();
      this.autosizedColumns.clear();
    }
  }

  /**
   * Apply the style changes for each column.
   *
   * @param {string} name
   * @memberof XamGridComponent
   */
  processHeaderStyle(name: string): void {
    if (name === 'HeaderStyle') {
      this.modelProxy.Columns.forEach(
        (column) => (column.HeaderStyle = this.modelProxy.HeaderStyle)
      );
    }
  }

  /**
   * Process undefined headerstyle for new columns
   *
   * @memberof XamGridComponent
   */
  processDefaultHeaderStyle(): void {
    this.modelProxy.Columns.forEach((column) => {
      /* istanbul ignore else */
      if (!column.HeaderStyle) {
        column.HeaderStyle = this.modelProxy.HeaderStyle;
      }
    });
  }

  /**
   * Unregister and clear `collectionHandler` when `ItemsSource` changes.
   *
   * @param {string} name
   * @param {DependencyPropertyChangedEventArgs} args
   * @memberof XamGridComponent
   */
  processItemsSourceChange(
    name: string,
    args: DependencyPropertyChangedEventArgs
  ) {
    /* istanbul ignore else */
    if (
      name === 'ItemsSourceChanged' &&
      args instanceof DependencyPropertyChangedEventArgs
    ) {
      this.InvalidateSelectionAndActivation();
      /* istanbul ignore else */
      if (
        args.OldValue &&
        ReflectionHelper.isInterfaceIsSupported(
          args.OldValue,
          'System.Collections.Specialized.INotifyCollectionChanged'
        )
      ) {
        args.OldValue.CollectionChanged.removeHandler(this.collectionHandler);
        this.collectionHandler = null;
      }
      /* istanbul ignore else */
      if (
        args.NewValue &&
        ReflectionHelper.isInterfaceIsSupported(
          args.NewValue,
          'System.Collections.Specialized.INotifyCollectionChanged'
        )
      ) {
        if (!this.isInternalInherit) {
          this.collectionHandler = args.NewValue.CollectionChanged.addHandler(
            this.applyDataUpdate.bind(this.xamGridContext)
          );
        }
      }
      this.removeAllErrorMessages();
      this.resetScrollPosition();
      this.autosizedColumns.clear();
    }
  }

  /**
   * Triggers the endEdit for the grid to allow changes to be handled
   *
   * @param {string} name
   * @param {*} commit
   * @memberof XamGridComponent
   */
  processTriggerExitEditMode(name: string, commit: any) {
    /* istanbul ignore else */
    if (name === 'triggerExitEditMode' && typeof commit == 'boolean') {
      this.grid.endEdit(commit);
      this.addRowKey = null;
    }
  }

  /**
   * Validates the selectedRows and update the collection if it's necessary
   * @param {boolean} isSelectingCell - boolean - flag to tell if its selecting a cell
   * while is handling collection changes
   * @memberof XamGridComponent
   * @returns {void}
   */
  InvalidateSelectionAndActivation(isSelectingCell = false): void {
    const collection = new SimpleList<XamGridRow>();
    const itemsSource1: any = this.model.ItemsSource;
    const itemsSourceCount = itemsSource1 ? iuCount(itemsSource1) : 0;
    this.selectionSettings?.SelectedRows?.forEach((item) => {
      if (itemsSourceCount <= item.Index || !itemsSource1.contains(item.Data)) {
        collection.add(item);
      }
    });

    if (collection.count > 0) {
      collection.forEach((item) => {
        this.selectionSettings.SelectedRows.remove(item);
      });
    }
    if (
      this.model.ActiveCell &&
      !(this.model.ActiveCell.Row instanceof AddNewRow) &&
      this.shouldInvalidateActiveCell(itemsSource1) &&
      !this.model.PreventClearActiveCell
    ) {
      this.model.ActiveCell = null;
      this.model.SelectedIndex = null;
      this.model.SelectedColumnIndex = null;
      this.cleanActiveFromIgxGrid();
    }

    if (
      this.gridRef != null &&
      this.isHandlingCollectionChanges &&
      isSelectingCell
    ) {
      this.model.SelectionSettings.SelectedRows.internalArray = [];
      this.gridRef.clearCellSelection();
      this.gridRef.deselectAllRows?.();
    }
  }

  /**
   * Reset the rows
   *
   * @memberof XamGridComponent
   */
  resetRows() {
    if (this.grid) {
      setTimeout(() => {
        this.renderFooters();
      });
    }
  }

  /**
   * Reset the scroll position: vertical scroll resets to top
   * and horizontal scroll resets to left.
   *
   * @memberof XamGridComponent
   */
  resetScrollPosition(): void {
    const firstUnpinnedColumn = this.grid?.columns.find((col) => !col.pinned);
    const firstUnpinnedIndex = firstUnpinnedColumn?.visibleIndex ?? 0;
    this.grid?.navigateTo(0, firstUnpinnedIndex);
  }

  /**
   * Returns columns from model.
   *
   * @return {*}  {*}
   * @memberof XamGridComponent
   */
  getColumns(): any {
    return this.model.getColumns();
  }

  /**
   * Returns columns from a column group
   *
   * @param {XamGridColumnModel} col
   * @returns {GroupColumnsCollection}
   * @memberof XamGridComponent
   */
  getColumnGroup(col: XamGridColumnModel): GroupColumnsCollection {
    return (col as XamGridGroupColumnModel).Columns;
  }

  /**
   * Click Handler event.
   *
   * @param {*} $event
   * @memberof XamGridComponent
   */
  cellClickHandler(event: IGridCellEventArgs): void {
    /* istanbul ignore else */
    if (
      event &&
      !this.validationErrorFlag &&
      !this.hasValidationErrors(this.selectedRow)
    ) {
      /* istanbul ignore else */
      if (
        (this.model.EditingSettings.IsMouseActionEditingEnabled ===
          MouseEditingAction.SingleClick ||
          this.model.EditingSettings.IsOnCellActiveEditingEnabled) &&
        (!this.isCellInEditMode() || !event.cell.editMode)
      ) {
        this.cellEnterEditMode(event.cell);
      }
      const params = [
        this.model,
        new CellClickedEventArgs(this.createCell(event.cell)),
      ];
      this.model.CellClicked.fire(params);
    }
  }

  /**
   * Double-click event handler.
   *
   * @param {IGridCellEventArgs} event
   * @memberof XamGridComponent
   */
  doubleClickHandler(event: IGridCellEventArgs): void {
    if (!this.validationErrorFlag) {
      /* istanbul ignore else */
      if (
        this.model.EditingSettings.IsMouseActionEditingEnabled ===
        MouseEditingAction.DoubleClick
      ) {
        this.cellEnterEditMode(event.cell);
      }
      const params = new CellClickedEventArgs(this.createCell(event.cell));
      this.model.CellDoubleClickHandler(params);
    }
  }

  /**
   * Cell selection event handler.
   *
   * @param {IGridCellEventArgs} event
   * @memberof XamGridComponent
   */
  cellSelectionHandler(event: IGridCellEventArgs): void {
    this.nextColumnWithErrors = this.evaluateColumnDataErrors(
      this.model.ActiveCell?.Row?.Data
    );
    if (this.isHandlingCollectionChanges) {
      this.InvalidateSelectionAndActivation(true);
      return;
    }
    /* istanbul ignore else */
    if (!this.nextColumnWithErrors) {
      if (
        this.model.SelectionSettings.CellClickAction !==
        CellSelectionAction.SelectCell
      ) {
        event.cell.selected = false;
        this.model.SelectedIndex = event.cell.row.index;
        this.model.SelectedColumnIndex = event.cell.column.index;
        return;
      }
      this.model.CellSelectionChangeHandler(event);
      this.model.SelectedIndex = event.cell.row.index;
      this.model.SelectedColumnIndex = event.cell.column.index;
      this.cellSelectionChange.emit(event);
    }
  }

  /**
   * Row selection event handler.
   *
   * @param {*} event
   * @memberof XamGridComponent
   */
  rowSelectionHandler(event: any): void {
    this.nextColumnWithErrors = this.evaluateColumnDataErrors(
      this.model.SelectedRow?.Data
    );
    if (this.nextColumnWithErrors) {
      event.cancel = true;
      return;
    }
    let oldRows,
      newRows = [];
    if (this.grid && this.isFilteredSortedData) {
      oldRows = event.oldSelection.map((key) =>
        this.getRowFromFilteredDataByKey(key)
      );
      newRows = event.newSelection.map((key) =>
        this.getRowFromFilteredDataByKey(key)
      );
    } else {
      oldRows = event.oldSelection.map((key) =>
        this.getRowFromModelByKey(key, this)
      );
      newRows = event.newSelection.map((key) =>
        this.getRowFromModelByKey(key, this)
      );
    }
    // avoid to selected something that is not in the grid yet because is adding a new row
    if (newRows == null || (newRows.length === 1 && newRows[0] == null)) {
      return;
    }
    if (this.selectionSettings.RowSelection === XamSelectionMode.Single) {
      if (newRows.length === 1 && newRows[0]) {
        this.model.SelectedRow = newRows[0];
        this.model.SelectedRow.IsSelected = true;
        this.model.SelectionSettings.SelectedRows.internalArray = [
          this.model.SelectedRow,
        ];
      } else {
        this.model.SelectedRow = null;
        this.model.SelectionSettings.SelectedRows.internalArray = [];
      }

      this.processColumnLayoutForRowSelection(oldRows);
    } else {
      this.model.SelectionSettings.SelectedRows.internalArray = newRows.map(
        (row) => {
          row.IsSelected = true;
          return row;
        }
      );
    }
    this.synchronizeRowCell();
    const params = new SelectionCollectionChangedEventArgs();
    params.NewSelectedItems = new SelectedRowsCollection(newRows);
    params.PreviouslySelectedItems = new SelectedRowsCollection(oldRows);
    this.model.SelectedRowsCollectionChanged.fire([this.model, params]);
    this.model.RowSelectionChangeHandler(params);
  }

  /**
   * Synchronize row
   *
   * @memberof XamGridComponent
   */
  synchronizeRowCell(): void {
    /* istanbul ignore else */
    if (this.model.ActiveCell && this.model.SelectionSettings.SelectedRows) {
      this.model.SelectionSettings.SelectedRows.internalArray.forEach((row) => {
        /* istanbul ignore else */
        if (row.Index === this.model.ActiveCell.Row.Index) {
          this.model.ActiveCell.Row = row;
        }
      });
    }
  }

  /**
   * Internal row selection event handler for column layout.
   *
   * @param {IRowSelectionEventArgs} internalEvent
   * @memberof XamGridComponent
   */
  internalColumnLayoutRowSelectionHandler(
    sender: any,
    e: SelectionCollectionChangedEventArgs<SelectedRowsCollection>
  ): void {
    const newEventArgs =
      new SelectionCollectionChangedEventArgs<SelectedRowsCollection>();
    newEventArgs.NewSelectedItems = new SelectedRowsCollection(
      e.NewSelectedItems.toArray()
    );
    newEventArgs.PreviouslySelectedItems = new SelectedRowsCollection(
      e.PreviouslySelectedItems.toArray()
    );
    if (this.selectionSettings.RowSelection === XamSelectionMode.Single) {
      this.deselectGridRows(sender, newEventArgs);
      if (e.NewSelectedItems.count === 1) {
        this.model.SelectedRow = e.NewSelectedItems.getItem(0);
        this.model.SelectedRow.IsSelected = true;
        this.model.SelectionSettings.SelectedRows.internalArray = [
          this.model.SelectedRow,
        ];
      } else {
        this.model.SelectedRow = null;
        this.model.SelectionSettings.SelectedRows.internalArray = [];
      }
    } else {
      this.model.SelectedRow = null;
      this.model.SelectionSettings.SelectedRows.internalArray = [];
    }

    this.model.SelectedRowsCollectionChanged.fire([this.model, newEventArgs]);
    this.model.RowSelectionChangeHandler(e);
  }

  /**
   * Column selection handler.
   *
   * @param {*} event
   * @memberof XamGridComponent
   */
  columnSelectionHandler(event: any): void {
    this.model.SelectionSettings.SelectedColumns.internalArray =
      event.newSelection.map((key) => this.getColumnModelByKey(key));
    const params = this.createEventParams();
    this.model.ColumnSelectionChanged.fire([params.sender, params.e]);
    this.columnSelectionChanged.emit(event);
  }

  /**
   * Keydown event handler.
   *
   * @param {IGridKeydownEventArgs} event
   * @memberof XamGridComponent
   */
  keydownHandler(e: IGridKeydownEventArgs): void {
    const key = (e.event as KeyboardEvent).key;

    if (
      key === 'Enter' &&
      !this.model.EditingSettings.IsEnterKeyEditingEnabled
    ) {
      e.cancel = true;
      this.grid.endEdit(true); // Commits the changes of the edited row
      this.addRowKey = null;
      this.grid.nativeElement.focus(); // Keeps focus on grid
    }
    if (key === 'F2' && !this.model.EditingSettings.IsF2EditingEnabled) {
      e.cancel = true;
    }

    const params = this.createEventParams();
    this.model.KeyDownHandler(params);
    this.keyDown.emit(params);
  }

  /**
   * Handler for arrowRight keydown event
   * Gets the cursor position and move to the next cell
   *
   * @param {KeyboardEvent} event
   * @memberof XamGridComponent
   */
  arrowRightHandler(event: KeyboardEvent): void {
    const position = (<HTMLInputElement>event.target).selectionStart + 1;
    if (
      this.model.ActiveCell !== null &&
      this.model.ActiveCell.Control !== null &&
      this.model.ActiveCell.Control.Cell !== null
    ) {
      //Cell in line value: before context update
      const inLineValue: string =
        (<HTMLInputElement>event.target).value?.trim() === ''
          ? null
          : (<HTMLInputElement>event.target).value;

      //Value after context update
      let value: string = this.model.ActiveCell.Control.Cell.Value;

      // Validate if the value was updated before the context update
      if (!value || value?.length != inLineValue?.length) {
        value = inLineValue;
      }
      if (position > value?.toString().length || !value) {
        this.grid.navigation.dispatchEvent(event);
      }
    }
  }

  /**
   * Handler for arrowLeft keydown event
   * Gets the cursor position and move to the last cell
   *
   * @param {KeyboardEvent} event
   * @memberof XamGridComponent
   */
  arrowLeftHandler(event: KeyboardEvent): void {
    const position = (<HTMLInputElement>event.target).selectionEnd - 1;
    if (position < 0) {
      this.grid.navigation.dispatchEvent(event);
    }
  }

  /**
   * Handler to know which handler has to use
   *
   * @param {KeyboardEvent} event
   * @memberof XamGridComponent
   */
  arrowsHandler(event: KeyboardEvent): boolean {
    if (event.code == 'ArrowRight') {
      this.arrowRightHandler(event);
      return true;
    } else if (event.code == 'ArrowLeft') {
      this.arrowLeftHandler(event);
      return true;
    } else {
      return false;
    }
  }

  /**
   * If AllowEditing is on Cell type and there is a error message on the active cell,
   * prevent clicks on components in edit mode on table data
   *
   * @return {*}  {boolean}
   * @memberof XamGridComponent
   */
  stopClickOutsideCell(): boolean {
    this.forceRevalidation();
    return (
      this.model.EditingSettings.AllowEditing === XamGridEditingType.Cell &&
      this.validationErrorFlag
    );
  }

  /**
   * Calling this function produces the necessary event to execute the validations of the cell being edited.
   * When used, the `cellEditHandler` function will be called
   *
   * @memberof XamGridComponent
   */
  forceRevalidation(): void {
    const args = this.grid.crudService.cell?.createEditEventArgs(true);
    /* istanbul ignore else */
    if (args) {
      this.grid.cellEdit.emit(args);
    }
  }

  /**
   * Listener for tab keydown event
   * Needs to be done in host listener to trigger even in not editable cells
   *
   * @param {KeyboardEvent} event
   * @memberof XamGridComponent
   */
  @HostListener('keydown', ['$event'])
  tabKeyListener(event: KeyboardEvent): void {
    if (this.internalXamGrid) return;
    if (this.shouldAddNewRow) {
      if (!this.arrowsHandler(event)) {
        this.grid.navigation.dispatchEvent(event);
      }
    } else {
      if (event.code === 'Tab') {
        this.processTabCode(event);
      } else if (!this.arrowsHandler(event)) {
        if (event.code != 'Space') {
          this.grid.navigation.dispatchEvent(event);
        }
      }
    }
  }

  /**
   * Processes the Keyboard event when the code is for the Tab key
   *
   * @memberof XamGridComponent
   */
  private processTabCode(event: KeyboardEvent) {
    event.preventDefault();
    const columnkey = 'data.' + this.model.ActiveCell?.Column?.Key;
    /* istanbul ignore next */
    if (
      this.model.HasColumnType(columnkey) &&
      this.grid.crudService.cellInEditMode
    ) {
      this.forceRevalidation();
      if (this.validationErrorFlag) {
        return;
      }
    }
    this.tabKeyNavigation(event.shiftKey);
  }

  /**
   * Column resize event handler.
   *
   * @param {*} event
   * @memberof XamGridComponent
   */
  resizeHandler(event: any): void {
    const params = this.createEventParams();
    this.setsColumnsToNumericValues(); // force value reset for columns to numeric value
    this.processAllowToolTipsByColumn(event.column);
    this.model.ColumnResized.fire([params.sender, params.e]);
    this.columnResized.emit(params);
  }

  /**
   * Active cell handler
   *
   * @param {IActiveNodeChangeEventArgs} event
   * @memberof XamGridComponent
   */
  activeNodeHandler(event: IActiveNodeChangeEventArgs) {
    this.nextColumnWithErrors = this.evaluateColumnDataErrors(
      this.model.ActiveCell?.Row?.Data
    );
    /* istanbul ignore else */
    if (!this.validationErrorFlag && !this.nextColumnWithErrors) {
      // Bug 481171: Workaround for the issue with the active cell selection when is set from the createCell method
      if (event.column === -1) {
        return;
      }
      let rowSource = this.Rows.getItem(event.row);
      const row = this.grid.getRowByIndex(event.row);
      let rowIndex = event.row;
      if (this.isNewRow(row)) {
        const rowToAdd = this.getAddRow();
        rowSource = rowToAdd;
        rowIndex = -1;
      }
      let cell = null;
      if (event.tag === 'dataCell') {
        let columnName = this.grid.getColumnByVisibleIndex(event.column).field;
        columnName = columnName.substring(5, columnName.length);
        cell = rowSource.Cells.getCellByKey(columnName);
      }
      if (this.model.ActiveCell !== cell) {
        this.exitEditOnRowChanged(rowIndex);
        this.model.ActiveCell = cell;
      }
      /* istanbul ignore else */
      if (this.model.ActiveCell) {
        this.model.ActiveCell.Row['IsSelected'] = this.isNewRow(row)
          ? false
          : true;
      }
    }
    this.nextColumnWithErrors = null;
  }

  /**
   * Cell editing handler
   *
   * @param {IGridEditEventArgs} event
   * @memberof XamGridComponent
   */
  cellEditHandler(event: IGridEditEventArgs) {
    const innerCell = this.grid.getCellByKey(event.rowID, event.column.field);
    if (this.model.HasColumnType(event.column?.field)) {
      if (!event.rowData) {
        return;
      }
      const result = { valid: false, value: event.newValue, editor: null };
      this.calculateCellControlValue(innerCell, event, result);
      if (!result.valid) {
        event.cancel = true;
        this.validationErrorFlag = true;
        this.showErrorMessageOnCell('Input is not in a correct format.', event);
        return;
      } else {
        this.validationErrorFlag = false;
        this.removeErrorMessageOnCell(event);
      }

      innerCell.editValue = result.value;
      event.newValue = result.value;
    }
    this.validateDataErrorInfo(
      event.rowData?.data,
      event.column?.header,
      event
    );
    /* istanbul ignore else */
    if (this.validationErrorFlag) {
      event.cancel = true;
      return;
    }
    const cell = this.createCell(innerCell);
    this.model.CellEditing.fire([this.model, new EditingCellEventArgs(cell)]);
    this.cellEditing.emit(cell);
  }

  /**
   * Calculates the value of the cell control
   *
   * @return {string | null}
   * @memberof XamGridComponent
   */
  private calculateCellControlValue(
    cellComponent: CellType,
    event: any,
    result: { valid: boolean; value: any; editor: any }
  ) {
    const cell = this.createCell(cellComponent);
    const cellType = () =>
      this.grid?.getCellByColumn(cell.Row.Index, 'data.' + cell.Column.Key);
    const control = this.getCellControlIfExists(cellType, cell);
    result.editor = control?.Editor;
    /* istanbul ignore next */
    if (control?.Content?.Text) {
      result.value = control?.Content.Text;
    }
    /* istanbul ignore next */
    if (this.model.HasColumnType(event.column?.field)) {
      this.validateLimits(this.model.GetColumnType(event.column.field), result);
    }
  }

  /**
   * Defines whether to use virtualization or not
   *
   * @return {string | null}
   * @memberof XamGridComponent
   */
  calcVirtualHeight(): string | null {
    const isVirtualCollection = this.data instanceof VirtualCollection;
    let height = null;
    if (this.virtualHeight && !isVirtualCollection) {
      height = this.virtualHeight;
    } else if (
      this.data?.count < this.rowLimitNoVirtualization ||
      isVirtualCollection
    ) {
      height = null;
    } else {
      height = '100%';
    }
    return height;
  }

  /**
   * RowEdit handler catches if state error is true and cancel enter edition
   *
   * @param {*} event
   * @memberof XamGridComponent
   */
  rowEditHandler(event: any): void {
    this.nextColumnWithErrors = this.evaluateColumnDataErrors(
      event.rowData?.data
    );
    if (
      !this.isTabKeyNavigating &&
      (this.validationErrorFlag || this.nextColumnWithErrors)
    ) {
      event.cancel = true;
      if (this.nextColumnWithErrors) {
        const nextCell = this.grid.getCellByColumn(
          0,
          'data.' + this.nextColumnWithErrors
        );
        /* istanbul ignore next */
        if (!nextCell?.selected && !this.model.ActiveCell.Row?.Data.HasErrors) {
          nextCell.selected = true;
          nextCell.row.selected = true;
          this.cellEnterEditMode(nextCell);
        }
        this.nextColumnWithErrors = null;
      }
    }
  }

  /**
   * Cell edited handler
   *
   * @param {IGridEditDoneEventArgs} event
   * @memberof XamGridComponent
   */
  cellEditedHandler(event: IGridEditDoneEventArgs) {
    const cellComponent =
      this.grid.getCellByKey(event.rowID, event.column?.field) ??
      this.grid.getCellByColumn(event.cellID?.rowIndex, event.column?.field);
    const cell = this.createCell(cellComponent);
    this.model.CellEdited.fire([
      this.model,
      new ExitEditingCellEventArgs(cell),
    ]);
    this.cellEdited.emit(cell);
  }

  /**
   * Cell entering edit mode handler
   *
   * @memberof XamGridComponent
   */
  /* istanbul ignore next */
  enterEditHandler(event: IGridEditEventArgs) {
    // Clean the pending transactions before edit again.
    // Right now, the cell.row.data.data is the same as the cellModel.Data. This is a temporal fix,
    // to avoid set a old value from the transaction data to the editValue.
    if (event.rowID !== this.addRowKey?.key) {
      this.grid?.transactions?.endPending(true);
    }
    if (
      this.model.ActiveCell?.Row.Index !== event.cellID?.rowIndex &&
      this.isHandlingCollectionChanges
    ) {
      event.cancel = true;
      return;
    }

    this.isActiveCellChanged = false;
    if (
      this.model.EditingSettings.AllowEditing === XamGridEditingType.None ||
      this.model.EditingSettings.IsMouseActionEditingEnabled ===
        MouseEditingAction.None ||
      (this.model.EditingSettings.IsMouseActionEditingEnabled ===
        MouseEditingAction.DoubleClick &&
        event?.event?.type === 'pointerdown' &&
        this.model.ActiveCell?.Row.Index !== event.cellID.rowIndex)
    ) {
      event.cancel = true;
      return;
    }
    const cell = this.createCell(
      this.grid.getCellByKey(event.rowID, event.column.field)
    );
    // this event is called when the user tries to add a new row, but the cell is null in this case
    if (cell == null) {
      return;
    }

    // we need to update the activeCell when the user clicks on the cell
    if (cell !== this.model.ActiveCell) {
      this.model.ActiveCell = cell;
      this.isActiveCellChanged = true;
    }

    const beginEditingCellEventArgs = new BeginEditingCellEventArgs(cell);
    this.model.CellEnteringEditMode.fire([
      this.model,
      beginEditingCellEventArgs,
    ]);
    if (beginEditingCellEventArgs.Cancel) {
      event.cancel = true;
      return;
    }

    this.model.editorTemplateLoading = true;
    // Emulate cell entered edit handler
    setTimeout(() => {
      const cellEvent = new EditingCellEventArgs(cell);
      const cellQuery = `#${this.grid.activeDescendant}`;
      this.selectInputTextIfTextColumnDefault(cell, cellQuery);
      /* istanbul ignore else */
      if (cellQuery && cell?.Column['EditorTemplate']) {
        const cellSelector = this.ref.nativeElement.querySelector(cellQuery);
        const cellType = () =>
          this.grid?.getCellByColumn(cell.Row.Index, 'data.' + cell.Column.Key);
        const control = this.getCellControlIfExists(cellType, cell);
        cellEvent.Editor = control?.Content;
        if (this.isActiveCellChanged) {
          const params = [this.model, new CellClickedEventArgs(cell)];
          this.model.CellClicked.fire(params);
          const cellToFocus = cellSelector.querySelector(
            'input, textarea'
          ) as HTMLElement;
          cellToFocus?.focus();
        }
      }
      this.model.CellEnteredEditMode.fire([this.model, cellEvent]);
      this.model.editorTemplateLoading = false;
    }, 50);

    this.removeErrorMessageOnCell(event);
  }

  /**
   * If the cell is a text column, select the text in the input
   *
   * @param {XamGridCell} cell - XamGridCell - the cell that is being edited
   * @param {string} cellQuery - string - the query selector for the cell
   * @memberof XamGridComponent
   */
  private selectInputTextIfTextColumnDefault(
    cell: XamGridCell,
    cellQuery: string
  ): void {
    if (cell.Column instanceof XamGridTextColumnModel) {
      const input: HTMLInputElement = this.ref.nativeElement.querySelector(
        `${cellQuery} [igxinput]`
      );
      input && input.setSelectionRange(0, input.value.length);
    } else if (cell.Column instanceof XamGridTemplateColumnModel) {
      const textarea: HTMLTextAreaElement =
        this.ref.nativeElement.querySelector(`${cellQuery} textarea`);
      if (textarea) {
        textarea.focus();
        textarea.setSelectionRange(0, textarea.value.length);
      }
    }
  }

  /**
   * Cell exiting edit mode handler
   *
   * @memberof XamGridComponent
   */
  exitEditHandler(event: IGridEditDoneEventArgs): void {
    /*Note: This 'if' statement is a workaround for a bug in the infragistics grid which triggers this event twice*/
    if (
      this.grid.crudService.row &&
      event.rowID !== this.grid.crudService.row.id
    ) {
      this.endRowEdit(false, event.event);
      return;
    }

    const cellComponent =
      this.grid.getCellByKey(event.rowID, event.column?.field) ??
      this.grid.getCellByColumn(event.cellID?.rowIndex, event.column?.field);
    const cell = this.createCell(cellComponent);
    cell.Control.Content?.bindingExpressions.internalArray.forEach(
      (bindingExpression: any) => {
        bindingExpression.Value.UpdateSource();
      }
    );
    const params = [this.model, new CellExitedEditingEventArgs(cell)];

    const cellEvent = new ExitEditingCellEventArgs(cell);
    const cellQuery = `#${this.grid.activeDescendant}`;
    /* istanbul ignore else */ /* istanbul ignore next */
    if (cellQuery && cell?.Column['EditorTemplate']) {
      /* istanbul ignore next */
      const result = { valid: false, value: null, editor: null };
      this.calculateCellControlValue(cellComponent, event, result);
      cellComponent.editValue = result.value;
    }
    this.model.CellExitingEditMode.fire([this.model, cellEvent]);
    // Update the data for the row
    if (cellComponent) {
      cell.Row.Data = cellComponent.row.data.data;
    }
    this.model.CellExitedEditMode.fire(params);
    setTimeout(() => {
      this.cellControlEvents(cell);
    }, 100);
  }

  /**
   * This function is called when a cell is created and it fires two events that can be used to attach
   * a control to the cell.
   * @param {XamGridCell} cell - XamGridCell - The cell that the control is being attached to.
   * @memberof XamGridComponent
   */
  private cellControlEvents(cell: XamGridCell): void {
    const args = new CellControlAttachedEventArgs();
    args.Cell = cell;
    this.model.CellControlAttached.fire([this.model, args]);
    this.model.AfterCellControlAttached.fire([this.model, args]);
  }

  /**
   * Validates if there are columns with errors
   * @param {any} data
   * @memberof XamGridComponent
   */
  evaluateColumnDataErrors(data: any): string {
    let nextColumnWithErrors = null;
    if (
      data &&
      ReflectionHelper.isInterfaceIsSupported(
        data,
        'System.ComponentModel.IDataErrorInfo'
      )
    ) {
      this.Columns.forEach((column) => {
        const key = column.Key;
        /* istanbul ignore else */
        if (data.getItem(key) && nextColumnWithErrors === null) {
          nextColumnWithErrors = key;
        }
      });
    } else if (
      data &&
      ReflectionHelper.isInterfaceIsSupported(
        data,
        'System.ComponentModel.INotifyDataErrorInfo'
      )
    ) {
      this.Columns.forEach((column) => {
        const key = column.Key;
        /* istanbul ignore else */
        if (data.GetErrors(key) && nextColumnWithErrors === null) {
          nextColumnWithErrors = key;
        }
      });
    } else if (
      !nextColumnWithErrors &&
      this.hasValidationErrors(this.selectedRow)
    ) {
      nextColumnWithErrors = this.Columns.getItem(
        this.model.SelectedColumnIndex
      ).Key;
    }
    return nextColumnWithErrors;
  }

  /**
   * Validates if the row currently has any validation errors
   *
   * @param {XamGridRow} row
   * @returns {boolean}
   * @memberof XamGridComponent
   */
  hasValidationErrors(row: XamGridRow): boolean {
    if (!row) return false;
    const rowRef = this.grid.rowList.get(row.Index)?.element.nativeElement;
    return rowRef?.querySelector('.validationCornerError') != null;
  }

  /**
   * Row exiting edit mode handler
   *
   * @param {IGridEditDoneEventArgs} event
   * @memberof XamGridComponent
   */
  rowExitEditHandler(event: IGridEditDoneEventArgs) {
    /* istanbul ignore else */
    if (this.Columns.count > 0) {
      const eventRowID = JSON.stringify(event.rowID);
      const rowComponent = this.grid.rowList.find(
        (r) => eventRowID === JSON.stringify(r.key)
      );
      if (event.rowData && rowComponent?.index > -1) {
        const row = this.createRowFromData(event.rowData, rowComponent.index);
        const params = [this.model, new EditingRowEventArgs(row)];
        //Delay added to avoid possibles infinite loops when calling ExitEditMode from
        //a user handler attached to precisely to RowExitedEditMode event
        setTimeout(() => this.model.RowExitedEditMode.fire(params));
      }
    }
  }

  /**
   * Row exiting edit mode handler
   *
   * @param {IGridEditDoneEventArgs} event
   * @memberof XamGridComponent
   */
  rowEnterEditHandler(event: IGridEditDoneEventArgs) {
    /* istanbul ignore else */
    if (this.Columns.count > 0) {
      const eventRowID = JSON.stringify(event.rowID);
      const rowComponent = this.grid.rowList.find(
        (r) => eventRowID === JSON.stringify(r.key)
      );
      const row =
        event.rowID === this.addRowKey?.key
          ? this.getAddRow()
          : this.createRowFromData(event.rowData, rowComponent?.index);
      const params = [this.model, new EditingRowEventArgs(row)];
      this.model.RowEnteredEditMode.fire(params);
    }
  }

  /**
   * Triggers the cell edit mode if column is editable and not read-only
   *
   * @param {CellType} cellType cell to enter edit mode
   * @param isAsync
   * @memberof XamGridComponent
   */
  cellEnterEditMode(cellType: CellType, isAsync = false): void {
    const colKey = cellType.column.field.replace('data.', '');
    const colModel = this.getColumnModelByKey(colKey);
    /* istanbul ignore else */
    if (this.isEditable(colModel) && !colModel?.IsReadOnly) {
      cellType.column.editable = true;
      cellType.editMode = true;
      this.isTabKeyNavigating = false;
      if (isAsync) {
        setTimeout(() => {
          this.navigateToCell(cellType);
        }, 0);
      } else {
        this.navigateToCell(cellType);
      }
    }
  }

  /**
   * Navigates and focus the content of a cell.
   *
   * @param {CellType} cellType
   * @type {void}
   * @memberof XamGridComponent
   */
  navigateToCell(cellType: CellType): void {
    this.grid.navigateTo(cellType.row.index, cellType.column.index, (args) => {
      args.target.nativeElement.querySelector('input,textarea')?.focus();
    });
  }

  /**
   * Returns current column data context.
   *
   * @param {*} context
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  cellContext(context): any {
    return context;
  }

  /**
   * Paging handler
   *
   * @returns true if handler paging is applied
   */
  pagingHandler(): boolean {
    return this.model.PagerSettings.AllowPaging !== PagingLocation.None;
  }

  /**
   * Validates if data implements IDataErrorInfo and checks for errors.
   *
   * @memberof XamGridComponent
   */
  validateDataErrorInfo(
    data: any,
    columnName: string,
    event: IGridEditDoneEventArgs
  ): void {
    if (
      data &&
      ReflectionHelper.isInterfaceIsSupported(
        data,
        'System.ComponentModel.IDataErrorInfo'
      )
    ) {
      const validationError = data.getItem(columnName);
      /* istanbul ignore else */
      if (validationError) {
        this.validationErrorFlag = true;
        this.showErrorMessageOnCell(validationError, event);
      } else {
        this.validationErrorFlag = false;
        this.removeErrorMessageOnCell(event);
      }
    }
  }

  /**
   * Determinate if the column is a editable column
   *
   * @param {XamGridColumnModel} column
   * @return {*} boolean
   * @memberof XamGridComponent
   */
  isEditable(column: XamGridColumnModel): boolean {
    if (
      this.model.EditingSettings.AllowEditing !== XamGridEditingType.None &&
      !column.IsReadOnly
    ) {
      if (
        (column instanceof XamGridTemplateColumnModel &&
          column.EditorTemplate) ||
        (column instanceof XamGridUnboundColumnModel &&
          column.EditorTemplate &&
          column.EditorTemplate.templateRef)
      ) {
        return true;
      }
      return this.processEditableColumn(column);
    }
    return false;
  }

  /**
   * Determine if the column has sumamry
   *
   * @param {any} column
   * @return {*} boolean
   * @memberof XamGridComponent
   */
  hasSummary(column: any): boolean {
    if (column?.HasSummary || column?.FooterText) {
      return true;
    }
    return false;
  }

  /**
   * Determinate if the column is a editable column when the grid is in edit mode
   *
   * @param {XamGridColumnModel} column
   * @return {*}  {boolean}
   * @memberof XamGridComponent
   */
  processEditableColumn(column: XamGridColumnModel): boolean {
    if (column.Editable) {
      if (
        column instanceof XamGridTextColumnModel ||
        column instanceof XamGridCheckboxColumnModel
      ) {
        return true;
      }
      if (
        (column instanceof XamGridTemplateColumnModel &&
          !column.ItemTemplate?.templateRef) ||
        (column instanceof XamGridUnboundColumnModel &&
          !column.ItemTemplate?.templateRef)
      ) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns number of total items.
   *
   * @return {*}  {number}
   * @memberof XamGridComponent
   */
  totalPagingRecordsHandler(): number {
    const itemsSourcePaged = this.model.ItemsSourceAsPagedCollection;
    if (
      this.grid &&
      this.model.PagerSettings &&
      this.model.PagerSettings.AllowPaging !== 3 &&
      itemsSourcePaged
    ) {
      this.grid.totalRecords = itemsSourcePaged.TotalItemCount;
      return itemsSourcePaged.TotalItemCount;
    } else {
      return (
        this.grid?.totalRecords ??
        (this.model.ItemsSource ? iuCount(this.model.ItemsSource) : 0)
      );
    }
  }

  /**
   * TriggerExitEditMode when a click is called outside a xam grid row and a row and is in edition
   *
   * @param {*} target
   * @memberof XamGridComponent
   */
  /* istanbul ignore next */
  @HostListener('document:mousedown', ['$event.target'])
  clickListener(target: any): void {
    const overlayElement = this.document.querySelector('.igx-overlay');
    const rowsContainer = this.ref?.nativeElement.querySelector(
      '.igx-grid__tbody-content > igx-display-container'
    );
    /* istanbul ignore else */
    if (
      this.isCellOrRowInEditMode() &&
      !rowsContainer.contains(target) &&
      !(
        overlayElement &&
        //should not exit edit mode if a dropdown or datetime picker are opened
        (overlayElement.querySelector('.igx-drop-down__list.igx-toggle') ||
          overlayElement.querySelector(
            '.wm-rad-date-time-picker-overlay__content'
          ))
      ) &&
      !this.model.editorTemplateLoading
    ) {
      this.processTriggerExitEditMode('triggerExitEditMode', true);
    }
  }

  /**
   * Returns true if the cell or row is in EditMode
   *
   * @returns {boolean}
   * @memberof XamGridComponent
   */
  public isCellOrRowInEditMode(): boolean {
    return (
      this.grid?.crudService?.cellInEditMode ||
      this.grid?.crudService?.rowEditing
    );
  }

  /**
   * Returns true if the cell is in EditMode
   *
   * @returns {boolean}
   * @memberof XamGridComponent
   */
  public isCellInEditMode(): boolean {
    return this.grid?.crudService?.cellInEditMode;
  }

  /**
   * Creates the cell component from the infragistics cell component
   *
   * @param {IgxGridCellComponent} cell
   * @param {XamGridRow} [row]
   * @returns
   * @memberof XamGridComponent
   */
  public createCell(cell: CellType, row?: XamGridRow): XamGridCell {
    if (cell == null) {
      return null;
    }
    row =
      cell.row.key === this.addRowKey?.key
        ? this.getAddRow()
        : row ?? this.Rows.getItem(cell.row.index);
    const newCell = row.Cells.getCellByKey(
      cell.column.field.substring(5, cell.column.field.length) ?? ''
    );
    newCell.IsSelected = cell.selected;
    const cellType = () =>
      this.grid?.getCellByColumn(row.Index, 'data.' + newCell.Column.Key);
    newCell.Control = this.getCellControlIfExists(cellType, newCell);
    return newCell;
  }

  /**
   * Validate if a new value is within allowed limits for the given type.
   *
   * @public
   * @param {string} type
   * @param {{valid: boolean, value: any}} result
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  public validateLimits(
    type: XamGridColumnType,
    result: { valid: boolean; value: any }
  ): void {
    switch (type) {
      case 'int':
        this.checkIntLimits(result);
        return;
      case 'decimal':
        this.checkDecimalLimits(result);
        return;
      case 'int?':
      case 'Nullable<int>':
        this.checkNullableIntLimits(result);
        return;
    }

    throw Error(`There is no validation mechanism for ${type} type`);
  }

  /**
   * Get row index
   *
   * @param {XamGridRow} row
   * @returns
   * @memberof XamGridComponent
   */
  protected getRowIndex(row: XamGridRow): number {
    return this.grid.data.findIndex((value) => {
      return value.data == row.Data;
    });
  }

  /**
   * Get row index from filtered data
   *
   * @param {XamGridRow} row
   * @returns
   * @memberof XamGridComponent
   */
  protected getRowIndexFromFilteredData(row: XamGridRow): number {
    return this.grid.filteredSortedData.findIndex((value) => {
      return value.data == row.Data;
    });
  }

  /** Clears the internal data wrapper.
   * On the next rebind of the grid data from the dataHandler method
   * the internal wrapper will be populated.
   */
  protected invalidateInternalDataWrapper() {
    this.internalDataWrapper = [];
  }

  /**
   * Try to infer the 'shape' of the objects in the passed data array and to generate the columns based on that.
   *
   * @protected
   * @param {any[]} data
   * @return {*}  {string[]}
   * @memberof XamGridComponent
   */
  protected generateDataFields(data: any[]): string[] {
    return Object.keys(data && data.length ? data[0] : 0);
  }

  /**
   * Generates XamColumn instances and the corresponding column models for them.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected generateColumnModels(): void {
    const factory = this.resolver.resolveComponentFactory(
      XamGridColumnComponent
    );
    const collection = this.generateDataFields(this.data).map((field) => {
      const ref = factory.create(this.injector);
      ref.instance.key = field;
      ref.changeDetectorRef.detectChanges();
      return ref.instance;
    });

    this.gridColumnsItems.reset(collection);
  }

  /**
   * Setup the handling of dynamic adding/removing of columns in XamGrid and
   * keeping both models in sync.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected setupColumnTracking(): void {
    this.gridColumnsItems.changes?.subscribe(
      (changes: QueryList<XamGridColumnComponent>) => {
        const delta = this.differ.diff(changes);
        /* istanbul ignore else */
        if (delta) {
          // If the new column is added through the 'Silverlight' model, we don't want to duplicate
          // it again there.
          delta.forEachAddedItem((added) => {
            /* istanbul ignore else */
            if (
              !this.model.Columns.contains(added.item.model) &&
              (!added.item.model.Parent ||
                added.item.model.Parent === this.model)
            ) {
              this.model.addColumn(added.item.model);
            }
          });

          delta.forEachRemovedItem((removed) =>
            this.model.Columns.remove(removed.item.model)
          );
        }
      }
    );
    this.processDefaultHeaderStyle();
    this.setColumnProperties(this.getColumns());
  }

  /**
   * Called once after rendering of the XamGrid.
   * Setups the width property for the column models based on the grid `ColumnWidth` property.
   * It is a no-op for Auto, InitialAuto and Star width values.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected setColumnsWidth(isRendering = false): void {
    if (isRendering) {
      this.evaluateColumns();
    }
    this.calculateAndSetNonSpecialColumnsWidth();
    this.processAllowToolTipsByRow();
  }

  /**
   * Evaluate for which auto columns is neccesary to apply autosize.
   *
   * @return {*}
   * @memberof XamGridComponent
   */
  evaluateAutoColumns() {
    const columnsToAutosize = this.visibleColumns
      .filter((col) => !this.autosizedColumns.has(col))
      .map((col) => ({
        col: col,
        model: this.getColumnModelByKey(col.field, true),
      }))
      .filter((pair) => pair.model != null)
      .filter(
        (pair) => pair.model.WidthResolved.WidthType !== ColumnWidthType.Star
      )
      .filter(
        (pair) => pair.model.WidthResolved.WidthType !== ColumnWidthType.Numeric
      )
      .map((pair) => pair.col);
    /* istanbul ignore else */
    if (columnsToAutosize.length === 0) {
      return;
    }
    this.processAutosize(columnsToAutosize);
  }

  /**
   * Asyncronously apply autosize to the given columns.
   *
   * @param {IgxColumnComponent[]} columns
   * @memberof XamGridComponent
   */
  processAutosize(columns: IgxColumnComponent[]) {
    this.isEvaluatingAutoColumns = true;
    setTimeout(() => {
      const col = columns.shift();
      /* istanbul ignore else */
      if (col == null) {
        this.isEvaluatingAutoColumns = false;
        return;
      }
      col.autosize();
      this.autosizedColumns.add(col);
      this.processAutosize(columns);
    }, 1);
  }

  /**
   * Evaluate columns to apply width
   *
   * @memberof XamGridComponent
   */
  evaluateColumns(): void {
    let hasStarValues = false;
    let failedSpecialCases = false;
    /* istanbul ignore else */
    if (this.grid?.columns) {
      this.grid.columns.forEach((col) => {
        const colModel = this.getColumnModelByKey(col.field, true);
        if (colModel) {
          const colWidhtToApply = colModel.WidthResolved;
          if (
            colWidhtToApply.WidthType !== ColumnWidthType.Star &&
            colWidhtToApply.WidthType !== ColumnWidthType.Numeric
          ) {
            failedSpecialCases =
              failedSpecialCases ||
              !this.setColumnWidthSpecialCase(col, colWidhtToApply);
          } else if (colWidhtToApply.WidthType === ColumnWidthType.Star) {
            hasStarValues = true;
          }
        }
      });
      this.setReCalcColumnsValues(hasStarValues, failedSpecialCases);
    }
  }

  /**
   * Sets if the current columns should recalculated.
   *
   * @param {any} async
   * @param {boolean} hasStarValues
   * @param {boolean} failedSpecialCases
   * @type {void}
   * @memberof XamGridComponent
   */
  setReCalcColumnsValues(
    hasStarValues: boolean,
    failedSpecialCases: boolean
  ): void {
    setTimeout(() => {
      this.forcedColumnsWidthCalc = hasStarValues || failedSpecialCases;
      this.requiredSpecialValuesSizeReCalc = failedSpecialCases;
    }, 50);
  }

  /**
   * Calculates and sets the columns width used for numeric and star values.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected calculateAndSetNonSpecialColumnsWidth() {
    this.cachedGridWidth = this.grid.calcWidth;
    this.forcedColumnsWidthCalc = false;
    if (this.grid.columns) {
      const columnModels = this.grid.columns
        .map((col) => this.getColumnModelByKey(col.field, true))
        .filter((model) => model != null);
      this.calculateStarColumnsWidth(columnModels);
      this.calculateNumericColumnsWidth(columnModels);
    }
  }

  /**
   * Calculates column width for numeric columns.
   *
   * @protected
   * @param {XamGridColumnModel[]} columnModels
   * @memberof XamGridComponent
   */
  protected calculateNumericColumnsWidth(columnModels: XamGridColumnModel[]) {
    const numericColumnModels = columnModels.filter(
      (model) => model.WidthResolved.WidthType === ColumnWidthType.Numeric
    );
    for (const model of numericColumnModels) {
      model.CalculatedWidth = model.WidthResolved.Value;
    }
  }

  /**
   * Calculates column width for star columns.
   *
   * @protected
   * @param {XamGridColumnModel[]} columnModels
   * @return {*}
   * @memberof XamGridComponent
   */
  protected calculateStarColumnsWidth(columnModels: XamGridColumnModel[]) {
    const [starTotalSpace, usedPixelSize] = this.getGridRemainSpaceInfo();
    const starColumnModels = columnModels.filter(
      (model) => model.WidthResolved.WidthType === ColumnWidthType.Star
    );
    const starValues = {
      percentPerStar: 0,
      starTotalSpace: starTotalSpace,
      usedPixelSize: usedPixelSize,
      gridTotalSpace: this.grid.calcWidth,
      starColumnModels: starColumnModels,
    };
    this.calculatePercentPerStar(starValues);
    /* istanbul ignore else */
    if (starValues.percentPerStar <= 0) {
      this.setColumnsToMinimumWidth(starColumnModels);
      return;
    }
    while (!this.calculateStarColumnsWidthLoop(starValues)) {
      this.calculatePercentPerStar(starValues);
    }
  }

  /**
   * Calculates width percent that should be given to each star unit.
   *
   * @protected
   * @param {{percentPerStar: number, starTotalSpace: number, usedPixelSize: number, gridTotalSpace:number}} starValues
   * @memberof XamGridComponent
   */
  protected calculatePercentPerStar(starValues: {
    percentPerStar: number;
    starTotalSpace: number;
    usedPixelSize: number;
    gridTotalSpace: number;
  }) {
    const freeSpacePercent =
      (1 - starValues.usedPixelSize / starValues.gridTotalSpace) * 100;
    starValues.percentPerStar = freeSpacePercent / starValues.starTotalSpace;
  }

  /**
   * Calculates column width for star columns. If some star column gets assigned
   * a fixed width, it recalculates `usedPixelSize` and `starTotalSpace` and
   * returns `false`, to indicate that width should be recalculated for all
   * remaining star columns.
   *
   * @protected
   * @param {{percentPerStar: number, starTotalSpace: number, usedPixelSize: number, gridTotalSpace:number, starColumnModels: XamGridColumnModel[]}} starValues
   * @return {*}  {boolean} `true` if width for all star columns was calculated, `false` otherwise.
   * @memberof XamGridComponent
   */
  protected calculateStarColumnsWidthLoop(starValues: {
    percentPerStar: number;
    starTotalSpace: number;
    usedPixelSize: number;
    gridTotalSpace: number;
    starColumnModels: XamGridColumnModel[];
  }): boolean {
    for (const model of starValues.starColumnModels) {
      const colWidth = starValues.percentPerStar * model.WidthResolved.Value;
      if (
        !isNaN(model.MinimumWidth) &&
        model.MinimumWidth > starValues.gridTotalSpace * (colWidth / 100)
      ) {
        // Column minimum width is higher than assigned star space.
        // Assign minimum width, treat this column as fixed size, and recalculate other star columns width again.
        model.CalculatedWidth = model.MinimumWidth;
        starValues.usedPixelSize += parseFloat(model.MinimumWidth);
        starValues.starTotalSpace -= model.WidthResolved.Value;
        this.removeModel(starValues.starColumnModels, model);
        return false;
      } else {
        model.CalculatedWidth = `${colWidth}%`;
      }
    }
    return true;
  }

  /**
   * Removes `model` from the `models` array.
   *
   * @protected
   * @param {XamGridColumnModel[]} models
   * @param {XamGridColumnModel} model
   * @memberof XamGridComponent
   */
  protected removeModel(
    models: XamGridColumnModel[],
    model: XamGridColumnModel
  ) {
    const idx = models.findIndex((x) => x === model);
    if (idx !== -1) {
      models.splice(idx, 1);
    }
  }

  /**
   * Sets widths to `MinimumWidth` (or `'0'` if `MinimumWidth`
   * is not available) for all given columns.
   *
   * @protected
   * @param {XamGridColumnModel[]} starColumnModels
   * @memberof XamGridComponent
   */
  protected setColumnsToMinimumWidth(starColumnModels: XamGridColumnModel[]) {
    for (const model of starColumnModels) {
      const min = Number(model.MinimumWidth);
      model.CalculatedWidth = isNaN(min) ? '0' : min.toString();
    }
  }

  /**
   * Gets the remaining available space
   *
   * @protected
   * @returns
   * @memberof XamGridComponent
   */
  protected getGridRemainSpaceInfo() {
    let starTotalSpace = 0;
    let usedPixelSize = 0;
    /* istanbul ignore else */
    if (this.grid.columns) {
      this.grid.columns.forEach((col) => {
        const colModel = this.getColumnModelByKey(col.field);
        if (colModel?.Visibility) {
          switch (colModel.WidthResolved.WidthType) {
            case ColumnWidthType.Numeric:
              usedPixelSize += colModel.WidthResolved.Value;
              break;
            case ColumnWidthType.Star:
              starTotalSpace += colModel.WidthResolved.Value;
              break;
            default:
              usedPixelSize += this.getColumnCalcPixelWidth(col);
              break;
          }
        }
      });
    }
    return [starTotalSpace, usedPixelSize];
  }

  /**
   * Sets the columns width to numeric values when resizing from UI.
   * All columns should be resized to a numeric value.
   *
   * @protected
   * @returns
   * @memberof XamGridComponent
   */
  protected setsColumnsToNumericValues(): void {
    if (this.grid.columns) {
      this.grid.columns.forEach((col) => {
        const colModel = this.getColumnModelByKey(col.field);
        if (colModel) {
          colModel.Width = new ColumnWidth(
            this.getColumnCalcPixelWidth(col),
            ColumnWidthType.Numeric
          );
        }
      });
    }
  }

  /**
   * Gets the column current used pixel width
   *
   * @protected
   * @param {IgxColumnComponent} col
   * @returns
   * @memberof XamGridComponent
   */
  protected getColumnCalcPixelWidth(col: IgxColumnComponent) {
    return col.calcPixelWidth;
  }

  /**
   * Sets the igx column size for special scenarios using autosize
   *
   * @protected
   * @param {*} col
   * @param {ColumnWidth} columnWidth
   * @memberof XamGridComponent
   */
  protected setColumnWidthSpecialCase(
    col: any,
    columnWidth: ColumnWidth
  ): boolean {
    try {
      switch (columnWidth.WidthType) {
        // Note that for both the following width types only the initially rendered columns
        // will be affected.
        case ColumnWidthType.SizeToCells:
        case ColumnWidthType.Auto:
        case ColumnWidthType.InitialAuto:
          col.autosize();
          break;
        case ColumnWidthType.SizeToHeader:
          col.autosize(true);
          break;
        case ColumnWidthType.Star:
        case ColumnWidthType.Numeric:
          col.autosizeHeader = false;
          break;
        default:
          break;
      }
    } catch {
      return false;
    }
    return true;
  }

  /**
   * Set column properties based on the data grid model setting objects.
   * Called only once, during rendering.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected setColumnProperties(collection: any) {
    collection.forEach((column: XamGridColumnModel) => {
      /* istanbul ignore else */
      if (!this.model.SortingSettings.AllowSorting) {
        column.IsSortable = false;
      }

      /* istanbul ignore else */
      if (this.model.FixedColumnSettings.AllowFixedColumns) {
        column.IsFixable = true;
      }

      /* istanbul ignore else */
      if (this.model.ColumnResizingSettings.AllowColumnResizing) {
        column.IsResizable = true;
      }

      /* istanbul ignore else */
      if (this.model.ColumnMovingSettings.AllowColumnMoving) {
        column.IsMovable = true;
      }

      /* istanbul ignore else */
      if (column instanceof XamGridGroupColumnModel) {
        this.setColumnProperties(column.Columns);
      }
    });
  }

  /**
   * Applies grid selection settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupSelectionSettings(): void {
    if (!this.selection) {
      return;
    }
    this.selectionSettings.CellSelection = this.selection.cellSelection;
    this.selectionSettings.RowSelection = this.selection.rowSelection;
    this.selectionSettings.ColumnSelection = this.selection.columnSelection;
    this.selectionSettings.CellClickAction = this.selection.cellClickAction;
  }

  /**
   * Applies grid editing settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupEditingSettings(): void {
    /* istanbul ignore else */
    if (!this.editingComponent || !this.editingComponent.allowEditing) {
      return;
    }
    /* istanbul ignore else */
    if (XamGridEditingType[this.editingComponent.allowEditing]) {
      this.model.EditingSettings.AllowEditing =
        XamGridEditingType[this.editingComponent.allowEditing];
      /* istanbul ignore else */
      if (this.model.EditingSettings.AllowEditing !== XamGridEditingType.None) {
        this.model.Columns.forEach((column) => {
          /* istanbul ignore else */
          if (!column.IsReadOnly) {
            column.Editable = true;
          }
        });
      }
    }
    /* istanbul ignore else */
    if (MouseEditingAction[this.editingComponent.isMouseActionEditingEnabled]) {
      this.model.EditingSettings.IsMouseActionEditingEnabled =
        MouseEditingAction[this.editingComponent.isMouseActionEditingEnabled];
    }
    this.model.EditingSettings.IsOnCellActiveEditingEnabled =
      this.editingComponent.isOnCellActiveEditingEnabled;
    this.model.EditingSettings.IsEnterKeyEditingEnabled =
      this.editingComponent.isEnterKeyEditingEnabled;
    this.model.EditingSettings.IsF2EditingEnabled =
      this.editingComponent.isF2EditingEnabled;
  }

  /**
   * Applies column resizing settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupColumnResizing(): void {
    if (!this.resizing) {
      return;
    }
    this.model.ColumnResizingSettings.AllowColumnResizing =
      this.resizing.allowColumnResizing;
  }

  /**
   * Applies grid column moving settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupColumnMovingSettings(): void {
    if (!this.columnMoving) {
      return;
    }
    this.model.ColumnMovingSettings.AllowColumnMoving =
      this.columnMoving.allowColumnMoving;
  }

  /**
   * Applies grid filtering settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupFilteringSettings(): void {
    /* istanbul ignore else */
    if (!this.filtering) {
      return;
    }
    this.model.FilteringSettings = this.filtering.model;
  }

  /**
   * Synchronize the grid SortingSettings with the model SortingSettings.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupSortingSettings(): void {
    /* istanbul ignore else */
    if (!this.sortingComponent) {
      return;
    }
    this.model.SortingSettings = this.sortingComponent.model;
  }

  /**
   * Synchronize the grid sortingExpressions with the model SortingSettings.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupSortedColumns(): void {
    this.sortingExpressions.forEach((e) => this.addColumnFromExpression(e));
    this.syncSortedColumns();
    this.sortedColumnsHandler = new NotifyCollectionChangedHandler(
      this.syncSortedColumns.bind(this)
    );
    this.sortedColumnsHandler.trackCollection(
      this.model.SortingSettings.SortedColumns
    );
  }

  /**
   * Sets the grid sortingExpressions based on the model information.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected syncSortedColumns() {
    if (this.model.SortingSettings.SortedColumns) {
      this.sortingExpressions =
        this.model.SortingSettings.SortedColumns.toArray().map((column) => {
          let sortDirection: SortingDirection = SortingDirection.None;
          switch (column.IsSorted) {
            case SortDirection.Ascending:
              sortDirection = SortingDirection.Asc;
              break;
            case SortDirection.Descending:
              sortDirection = SortingDirection.Desc;
              break;
          }
          return {
            fieldName: 'data.' + column.Key,
            dir: sortDirection,
            ignoreCase: true,
            strategy:
              (column.SortComparer as any) ?? DefaultSortingStrategy.instance(),
          };
        });
    }
  }

  /**
   * Synchronize the grid filteringExpressions with the model FilteringSettings.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupFilteringExpressions(): void {
    this.syncFilteringExpressions();
    this.filteredExpressionsHandler = new NotifyCollectionChangedHandler(
      this.syncFilteringExpressions.bind(this)
    );
    this.filteredExpressionsHandler.trackCollection(
      this.model.FilteringSettings.RowFiltersCollection
    );
  }

  /**
   * Sets the grid filteringExpressions based on the info of the model.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected syncFilteringExpressions() {
    if (this.grid && this.model.FilteringSettings.RowFiltersCollection) {
      this.grid.clearFilter();
      this.model.RowsManager.InvalidateRows(true);
      this.model.FilteringSettings.RowFiltersCollection.toArray().forEach(
        (filter: IRecordFilter) => {
          if (filter.Conditions && filter.Conditions.length > 0) {
            (
              this.filteringExpressions as FilteringExpressionsTree
            ).filteringOperands.push({
              fieldName: filter.Conditions[0].fieldName,
              filteringOperands: [
                {
                  fieldName: filter.Conditions[0].fieldName,
                  searchVal: filter.Conditions[0].searchVal,
                  condition: filter.Conditions[0].condition,
                  ignoreCase: filter.Conditions[0].ignoreCase,
                },
              ],
            } as IFilteringExpressionsTree);
          }
        }
      );
    }
  }

  /**
   * Applies grid summary settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupSummarySettings(): void {
    if (!this.summary) {
      return;
    }
  }

  /**
   * Applies grid pagination settings from template directive if defined.
   *
   * @protected
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  protected setupPaginationSettings(): void {
    if (!this.pagination) {
      return;
    }
    this.pagination.setupModelFromParent(this.model.PagerSettings);
    this.model.PagerSettings = this.pagination.model;
    this.xamGridContext.newItemsPerPage =
      this.model.PagerSettings.PageSize.toString();

    this.registerHandler(this.model.PagerSettings.change, (propertyName) => {
      if (propertyName == 'PageSize') {
        if (this.model.ItemsSource['PageSize']) {
          this.model.ItemsSource['PageSize'] =
            this.model.PagerSettings.PageSize;
          this.xamGridContext.newItemsPerPage =
            this.model.PagerSettings.PageSize.toString();
        }
      }
    });
  }

  /**
   * Enables row selectors settings from template directive if defined.
   *
   * @protected
   * @return {*}
   * @memberof XamGridComponent
   */
  protected setupRowSelectorsSettings(): void {
    if (!this.rowSelectors) {
      return;
    }

    this.model.RowSelectorSettings = this.rowSelectors.model;

    /* istanbul ignore else */
    if (this.model.RowSelectorSettings.EnableRowNumbering) {
      // Emulate the the row selector directive as 'being' in the igx-grid.
      this.grid.rowSelectorsTemplates = new QueryList();
      this.grid.rowSelectorsTemplates.reset([
        { templateRef: this.rowSelectors.template },
      ] as any);
    }
  }

  /**
   * Applies Xam grid settings defined in the context of the xam grid as template bindings
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected setupGridSettings(): void {
    this.setupColumnMovingSettings();
    this.setupColumnResizing();
    this.setupSelectionSettings();
    this.setupEditingSettings();
    this.setupFilteringSettings();
    this.setupSortingSettings();
    this.setupSummarySettings();
    this.setupPaginationSettings();
  }

  /**
   * Creates the tracking needed for the even emitter when a new cell has entered
   * the view.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected setupCellTracker(): void {
    const subscribeCallback = (e: IForOfState) => {
      this.clearResolvedCellControl();
      const { startIndex, chunkSize } = e;
      if (
        this.evaluateColumnDataErrors(this.model.ActiveCell?.Row?.Data) &&
        startIndex > this.model.ActiveCell?.Row?.Index
      ) {
        this.removeAllErrorMessages();
      }
      for (let i = startIndex; i < startIndex + chunkSize; i++) {
        const row = this.grid.getRowByIndex(i);
        if (row) {
          this.processRowCells(row);
        }
      }
    };

    this.grid.verticalScrollContainer?.chunkLoad.subscribe(subscribeCallback);
    this.grid.parentVirtDir?.chunkLoad.subscribe(() =>
      subscribeCallback(this.grid.verticalScrollContainer.state)
    );
  }

  /**
   * Method in charge of reset the cell control content herarchy resolutions when required
   * Used in rows scrolling to avoid wrong references in reused controls
   *
   * @memberof XamGridComponent
   */
  clearResolvedCellControl() {
    this.resolvedCellControlContent.forEach((c) => (c.Content = null));
    this.resolvedCellControlContent = [];
  }

  /**
   * For each available row at rendering it will register its cells.
   *
   * @protected
   * @type {void}
   * @memberof XamGridComponent
   */
  protected setupAvailableRows(): void {
    if (this.internalXamGrid) return;
    const availableRows = this.gridRef?.rowList?.toArray();
    if (availableRows && availableRows.length > 0) {
      availableRows.forEach((row) => {
        this.registerCellControl(row);
      });
    }
  }

  /**
   * Register the available cells on the model.
   *
   * @protected
   * @type {void}
   * @memberof XamGridComponent
   */
  protected registerCellControl(row: any): void {
    const rowBase = this.model.Rows.getItem(row.index);
    row.cells.toArray().forEach((cell: any) => {
      const colKey = rowBase.Cells.getKey(cell);
      const col = this.model.Columns.getColumnByKeyDeep(colKey);
      const cellModel = new XamGridCell(rowBase, col.column);
      cellModel.Control = this.getCellControlIfExists(() => cell, cellModel);
      rowBase.Cells.add(cellModel);
    });
  }

  /**
   * Disables default browser behavior for events, and stops propagation through the DOM tree.
   *
   * @protected
   * @param {Event} event
   * @memberof XamGridComponent
   */
  protected disabledEventsHandler = (event: Event) => {
    /* istanbul ignore else */
    if (this.model.IsEnabled) {
      return;
    }
    event.preventDefault();
    event.stopPropagation();
  };

  /**
   * Workaround for the `IsEnabled` property. The listeners here are registered in the capture phase.
   *
   * @protected
   * @memberof XamGridComponent
   */
  protected setupDisabledOverlay() {
    /* istanbul ignore else */
    if (typeof this.ref.nativeElement.addEventListener === 'function') {
      DISABLED_EVENTS.forEach((type) => {
        this.ref.nativeElement.addEventListener(
          type,
          this.disabledEventsHandler,
          { capture: true }
        );
      });
    }
  }

  /**
   * Manipulate the default bottom pager of the grid component to emulate all the values
   * from the PageLocation enumeration.
   *
   * @protected
   * @param {PagingLocation} value
   * @memberof XamGridComponent
   */
  protected adjustPaginator = (value: PagingLocation) => {
    if (
      value === PagingLocation.Top ||
      value === PagingLocation.Hidden ||
      value === PagingLocation.None
    ) {
      const pager = this.grid?.nativeElement?.querySelector(
        '.igx-grid__footer igx-paginator'
      ) as HTMLElement;
      /*istanbul ignore next */
      if (pager) {
        // if pager is displayed then hide it
        pager.style.display = 'none';
      }
    }
  };

  /**
   * Helper method for creating event params.
   *
   * @private
   * @return {*}
   * @memberof XamGridComponent
   */
  private createEventParams(): any {
    return { sender: this.model, e: this };
  }

  /**
   * Returns a column model by key, if found.
   *
   * @private
   * @param {string} key
   * @return {*}  {(XamGridColumnModel | undefined)}
   * @memberof XamGridComponent
   */
  private getColumnModelByKey(
    key: string,
    searchInGroups = false
  ): XamGridColumnModel | undefined {
    const columnKey = this.getColumnKey(key);
    let colModel = this.model.Columns.internalArray.find(
      (column) => column.Key === columnKey
    );
    if (!colModel && searchInGroups) {
      for (const col of this.model.Columns.internalArray) {
        if (col instanceof XamGridGroupColumnModel) {
          colModel = col.Columns.internalArray.find(
            (column) => column.Key === columnKey
          );
        }
        if (colModel) {
          break;
        }
      }
    }
    return colModel;
  }

  /**
   * Gets the column key
   *
   * @private
   * @param {string} key
   * @returns
   * @memberof XamGridComponent
   */
  private getColumnKey(key: string) {
    return key && key.startsWith('data.') ? key.slice(5) : key ?? '';
  }

  /**
   * Helper method to add a column to a column collection.
   * Used to handle filtering/sorting/grouping.
   *
   * @private
   * @param {*} expression
   * @param {ObservableCollection<XamGridColumnModel>} collection
   * @memberof XamGridComponent
   */
  private addColumnFromExpression(expression: any): void {
    const collection = this.model.SortingSettings.SortedColumns;
    collection.clear();
    const colKey = expression.fieldName.replace('data.', '');
    const column = this.getColumnModelByKey(colKey, true);
    let sortDirection: SortDirection = SortDirection.None;
    switch (expression.dir) {
      case SortingDirection.Asc:
        sortDirection = SortDirection.Ascending;
        break;
      case SortingDirection.Desc:
        sortDirection = SortDirection.Descending;
        break;
    }
    column.IsSorted = sortDirection;
    const matchingSortedCol = iuFirstOrDefault(
      iuWhere((col) => col.Key === colKey, collection)
    );
    if (!matchingSortedCol) {
      collection.add(column);
    }
  }

  /**
   * Returns the DOM element of the cell
   *
   * @param {CellType} cell
   * @return {*} {*}
   * @memberof XamGridComponent
   */
  getNativeElement(cell: CellType): any {
    try {
      // In Silverlight you can access the element for all the rows in the xamGrid,
      // but in Angular you can only access the elements for the visible rows.
      if (cell == null || this.grid?.rowList.length <= 0) {
        return null;
      }
      /* istanbul ignore next */
      return this.ref?.nativeElement?.querySelector(
        `[data-rowindex="${cell.row.index}"][data-visibleindex="${cell.column.visibleIndex}"]`
      );
    } catch {
      return null;
    }
  }

  /**
   * Gets filtering cell DOM elements
   *
   * @returns NodeList
   * @memberof XamGridComponent
   */
  getFilteringCells(): any {
    return this.ref?.nativeElement?.querySelectorAll('igx-grid-filtering-cell');
  }

  /**
   * Exits edit mode on row changed
   *
   * @private
   * @param {number} newRowIndex
   * @memberof XamGridComponent
   */
  private exitEditOnRowChanged(newRowIndex: number): void {
    /* istanbul ignore else */
    if (
      this.inEditMode &&
      this.model.SelectedIndex != newRowIndex &&
      newRowIndex != -1 &&
      this.model.EditingSettings.IsMouseActionEditingEnabled ===
        MouseEditingAction.DoubleClick
    ) {
      this.processTriggerExitEditMode('triggerExitEditMode', true);
    }
  }

  /**
   * Gets cell control if it exists.
   *
   * @param {() => CellType} cell
   * @param {any} cellModel
   * @returns {any}
   * @memberof XamGridComponent
   */
  private getCellControlIfExists(cell: () => CellType, cellModel: any): any {
    const control = new XamGridCellControl();
    control.Column = cellModel.Column;
    control.onDemandLoadDataContext = () =>
      this.resolveCellDataContext(control.Cell);
    control.Cell = cellModel;
    /* istanbul ignore else */
    if (cellModel.Column?.ItemTemplate || cellModel.Column?.EditorTemplate) {
      control.LazyLoadContent = () => {
        const element = this.getNativeElement(cell());
        return this.getModelToUse(control, element);
      };
    } else {
      control.LazyLoadContent = null;
    }
    return control;
  }

  /**
   * Returns component model with tooltip handler
   *
   * @param {any} component
   * @param {any} control
   * @returns {any}
   * @memberof XamGridComponent
   */
  processElementComponent(component: any, control: any): any {
    if (component == null) {
      return null;
    }
    if (control) {
      this.registerHandler(control.ToolTipSet, (obj, ctx) => {
        component.syncToolTip(ctx);
      });
      this.resolvedCellControlContent.push(control);
    }
    return component.model;
  }

  /**
   * Returns model for content use
   *
   * @param {any} control
   * @param {any} element
   * @returns {any}
   * @memberof XamGridComponent
   */
  getModelToUse(control: any, element: any): any {
    let childToUse: Element | null = null;
    childToUse = this.findElement(element);
    if (!childToUse) {
      return null;
    } else if (childToUse['__component']?.model) {
      // Set Tooltip
      return this.processElementComponent(childToUse['__component'], control);
    } else if (childToUse.children?.length === 1) {
      // Set Tooltip
      return this.processElementComponent(
        childToUse.children[0]['__component'],
        control
      );
    }
    return null;
  }

  /**
   * If the element has children, return the first child that doesn't have the class
   * 'validationCornerError'.
   *
   * @param {any} element - any - the element that you want to find the child of
   * @returns the child element that is not a validationCornerError.
   * @memberof XamGridComponent
   */
  findElement(element: any): Element | null {
    const hasChildren = element?.children.length > 0;
    let childToUse: Element | null = null;
    if (!hasChildren) {
      return null;
    }
    for (const child of element.children) {
      if (!child.classList.contains('validationCornerError')) {
        childToUse = child;
        break;
      }
    }
    return childToUse;
  }

  /**
   * Returns cell DataContext
   *
   * @param {any} cellModel
   * @returns {any}
   * @memberof XamGridComponent
   */
  resolveCellDataContext(cellModel: any): any {
    return cellModel.Column instanceof XamGridUnboundColumnModel
      ? this.resolveUnboundColumnDataContext(cellModel)
      : cellModel.Row.Data;
  }

  /**
   * Creates a new UnboundColumnDataContext when the resolver is not defined otherwise just change the existing DataContext value.
   *
   * @param {any} cellModel
   * @returns {UnboundColumnDataContext}
   * @memberof XamGridComponent
   */
  private resolveUnboundColumnDataContext(
    cellModel: any
  ): UnboundColumnDataContext {
    if (!cellModel.UnboundDataContextResolver) {
      cellModel.UnboundDataContextResolver = new UnboundColumnDataContext(
        cellModel.Column.Key,
        cellModel.Row.Data,
        cellModel.Value
      );
    } else {
      const unboundContext: UnboundColumnDataContext =
        cellModel.UnboundDataContextResolver;
      unboundContext.RowData = cellModel.Row.Data;
      unboundContext.Value = cellModel.Value;
    }
    return cellModel.UnboundDataContextResolver;
  }

  /**
   * Create dn select a new XamGridRow
   *
   * @param {RowType} row
   * @return {any}
   * @memberof XamGridComponent
   */
  private processRowCells(row: any): void {
    const gridRow = this.createRow(row);
  }

  /**
   * Create or returns a cached row model from a rowType.
   * Could be a data row or a addNew row.
   *
   * @private
   * @param {RowType} row
   * @return {*}
   * @memberof XamGridComponent
   */
  private createRow(row: RowType) {
    const isNewRow = this.isNewRow(row);
    let rowIndex = row.index;
    if (isNewRow) {
      this.addRowKey = row;
    }
    if (this.addRowKey != null) {
      // we need to adjust the index because models.Rows does not contains the "new added row" when we are adding a new row
      rowIndex = row.index - 1;
    }
    const newRow = isNewRow
      ? this.getAddRow()
      : this.model.Rows.getItem(rowIndex);
    if (newRow == null) {
      return;
    }
    if (isNewRow) {
      newRow.IsSelected = false;
    }
    row.cells?.forEach((cell) => {
      const cellModel = newRow.Cells.registerCellFromUI(
        cell,
        newRow,
        this.Columns
      );
      cellModel.IsSelected = cell.selected;
      const cellType = () =>
        this.grid?.getCellByColumn(
          newRow.Index,
          'data.' + cellModel.Column.Key
        );
      cellModel.Control = this.getCellControlIfExists(cellType, cellModel);
    });
    return newRow;
  }

  /**
   * Validate if the row is an add new Row type.
   *
   * @param {RowType} [row] New row.
   * @return {*}  {boolean} `true` if the row is an add new row Type.
   * @memberof XamGridComponent
   */
  isNewRow(row: RowType): boolean {
    const isNew = row?.addRowUI || row?.key === this.addRowKey?.key;
    return isNew ?? false;
  }

  /**
   * Get the addNewRow position in the grid
   *
   * @return {*}  {*}
   * @memberof XamGridComponent
   */
  getAddRow(): any {
    return this.addNewRowInstance?.allowAddNewRow === 'Top'
      ? this.model.RowsManager.AddNewRowTop
      : this.model.RowsManager.AddNewRowBottom;
  }

  /**
   * The all columns collection
   *
   * @type {ColumnBaseCollection}
   * @memberof XamGridComponent
   */
  allColumns: ColumnBaseCollection;

  /**
   * Value which indicates if a change has occurred in a cell
   *
   * @memberof XamGridComponent
   */
  isCellChanged = 0;

  /**
   * Get all columns
   *
   * @param {ColumnBaseCollection | GroupColumnsCollection} columns
   * @return {ColumnBaseCollection}
   * @memberof XamGridComponent
   */
  getAllColumns(
    columns?: ColumnBaseCollection | GroupColumnsCollection
  ): ColumnBaseCollection {
    if (!columns) {
      columns = this.Columns;
      this.allColumns = new ColumnBaseCollection(
        this.model.RowsManager.ColumnLayout
      );
    }
    for (const column of columns) {
      if (column instanceof XamGridGroupColumnModel) {
        this.allColumns.add(<any>column);
        this.allColumns = this.getAllColumns(column.Columns);
      } else {
        this.allColumns.add(<any>column);
      }
    }
    return this.allColumns;
  }

  /**
   * Get a cell throuht it row and column.
   *
   * @return {any}
   * @memberof XamGridComponent
   */
  getCellByRowColumn(rowIndex: number, column: string): any {
    const row = this.model.Rows.getItem(rowIndex);
    const cell = row?.Cells.getCellByKey(column.substring(5));
    return { row, cell };
  }

  /**
   * Process filtering columns to add a filter tooltip
   *
   * @private
   * @memberof XamGridComponent
   */
  private processFilterCellTooltip(): void {
    const filteringColumns = this.getFilteringCells();
    if (
      this.filterCellTooltip &&
      filteringColumns &&
      filteringColumns.length > 0
    ) {
      filteringColumns.forEach((filteringColumn) => {
        filteringColumn.title = this.filterCellTooltip;
      });
    }
  }

  /**
   * Create row from data
   *
   * @private
   * @param {*} data
   * @param {number} index
   * @return {*}
   * @memberof XamGridComponent
   */
  private createRowFromData(data: any, index: number): any {
    const newRow = this.model.Rows.getItem(index);
    newRow.IsSelected = iuAny(
      this.selectionSettings.SelectedRows,
      (row: XamGridRow) => row.Data === data
    );
    return newRow;
  }

  /**
   * Remove  the attached subscription
   *
   * @private
   * @memberof XamGridComponent
   */
  private removeItemSourceHandler() {
    /* istanbul ignore else */
    if (this.collectionHandler) {
      this.data.CollectionChanged.removeHandler(this.collectionHandler);
      this.collectionHandler = null;
    }
  }

  /**
   * Process column layout when a row is selected
   *
   * @private
   * @param {XamGridRow[]} oldRows
   * @memberof XamGridComponent
   */
  private processColumnLayoutForRowSelection(oldRows: XamGridRow[]) {
    /* istanbul ignore else */
    if (this.gridColumnLayouts) {
      const lookInColumnLayout = oldRows.length === 0;
      this.gridColumnLayouts.forEach((colLayout) => {
        if (lookInColumnLayout) {
          colLayout.grid.selectedRows
            .map((key) => this.getRowFromModelByKey(key, colLayout))
            .forEach((r) => {
              if (r != null) {
                oldRows.push(r);
              }
            });
        }
        //deselectAllRows doesn't clear cell selection which happens by user clicking on a cell (singlor o with Ctrl)
        //or by setting a cell as selected (cell.selected = true)
        colLayout.grid.clearCellSelection();
        colLayout.grid.deselectAllRows(false);
      });
    }
  }

  /**
   * Deselects the rows from the main grid and column layouts grids.
   *
   * @param {*} sender
   * @param {SelectionCollectionChangedEventArgs<SelectedRowsCollection>} newEventArgs
   * @returns
   * @memberof XamGridComponent
   */
  private deselectGridRows(
    sender: any,
    newEventArgs: SelectionCollectionChangedEventArgs<SelectedRowsCollection>
  ) {
    /* istanbul ignore else */
    if (this.selectionSettings.RowSelection === XamSelectionMode.Single) {
      /* istanbul ignore else */
      if (newEventArgs.PreviouslySelectedItems.count === 1) {
        return;
      } else {
        this.processPreviousSelectedRows(sender, newEventArgs);
      }
    }
  }

  /**
   * Process previous selected rows when a row is selected in a column layout.
   *
   * @private
   * @param {*} sender
   * @param {SelectionCollectionChangedEventArgs<SelectedRowsCollection>} newEventArgs
   * @memberof XamGridComponent
   */
  private processPreviousSelectedRows(
    sender: any,
    newEventArgs: SelectionCollectionChangedEventArgs<SelectedRowsCollection>
  ) {
    /* istanbul ignore else */
    if (this.gridColumnLayouts) {
      this.gridColumnLayouts.forEach((colLayout) => {
        /* istanbul ignore else */
        if (colLayout.model !== sender) {
          colLayout.grid.selectedRows
            .map((key) => this.getRowFromModelByKey(key, colLayout))
            .forEach((row) => {
              /* istanbul ignore else */
              if (row != null) {
                newEventArgs.PreviouslySelectedItems.add(row);
              }
            });
          //deselectAllRows doesn't clear cell selection which happens by user clicking on a cell (singlor o with Ctrl)
          //or by setting a cell as selected (cell.selected = true)
          colLayout.grid.clearCellSelection();
          colLayout.grid.deselectAllRows(false);
        }
      });
    }
    const oldSelectedRows = this.grid.selectedRows.map((key) =>
      this.getRowFromModelByKey(key, this)
    );
    oldSelectedRows.forEach((r) => newEventArgs.PreviouslySelectedItems.add(r));
    //deselectAllRows doesn't clear cell selection which happens by user clicking on a cell (singlor o with Ctrl)
    //or by setting a cell as selected (cell.selected = true)
    this.grid.clearCellSelection();
    this.grid.deselectAllRows(false);
  }

  /**
   * Defines whether to show or not the cell tooltip by changing the title property
   *
   * @private
   * @param {IgxColumnComponent} column
   * @memberof XamGridComponent
   */
  private processAllowToolTipsByColumn(column: IgxColumnComponent): void {
    const colModel = this.getColumnModelByKey(column.field);
    // Updates only the cells of the current visible rows
    this.grid.dataRowList?.forEach((row) => {
      const columnCells = row.cells.filter((each) => each.column == column);
      // Sets tooltip visibility for each cell
      columnCells.forEach((cell) => {
        this.processAllowToolTips(cell, colModel);
      });
    });
  }

  /**
   * Process AllowToolTip property for all visible rows
   *
   * @private
   * @memberof XamGridComponent
   */
  private processAllowToolTipsByRow(): void {
    this.grid.dataRowList?.forEach((row) => {
      let colModel: XamGridColumnModel;
      // Sets tooltip visibility for each cell
      row.cells.forEach((cell) => {
        colModel = this.getColumnModelByKey(cell.column.field, true);
        this.processAllowToolTips(cell, colModel);
      });
    });
  }

  /**
   * Auxiliary function that defines the cell's tooltip by
   * changing the cell's element title property
   *
   * @param {CellType} cell
   * @param {XamGridColumnModel} model
   * @memberof XamGridComponent
   */
  private processAllowToolTips(
    cell: CellType,
    model: XamGridColumnModel
  ): void {
    const element: HTMLElement = this.getNativeElement(cell);
    if (!element) return;
    switch (model?.AllowToolTips) {
      case AllowToolTips.Always:
        element.title = cell.value;
        break;
      case AllowToolTips.Overflow:
        const isOverflow =
          element.firstElementChild.clientWidth <
          element.firstElementChild.scrollWidth;
        element.title = isOverflow ? cell.value : '';
        break;
      default:
        element.title = '';
        break;
    }
  }

  /**
   * Display an error message on the cell asociated with event.
   *
   * @param {string} msg
   * @param {IGridEditDoneEventArgs} event
   * @memberof XamGridComponent
   */
  showErrorMessageOnCell(msg: string, event: IGridEditDoneEventArgs): void {
    const element = this.getCellElementByEvent(event);
    this.addValidationHelper(element, msg);
    this.cellsWithValidationMsg.add(element);
  }

  /**
   * Removes the error message from the cell asociated with event, if there is any.
   *
   * @param {IGridEditDoneEventArgs} event
   * @memberof XamGridComponent
   */
  removeErrorMessageOnCell(event: IGridEditDoneEventArgs): void {
    const element = this.getCellElementByEvent(event);
    this.removeValidationHelper(element);
    this.cellsWithValidationMsg.delete(element);
  }

  /**
   * Removes error messages from all cells.
   *
   * @memberof XamGridComponent
   */
  removeAllErrorMessages(): void {
    for (const element of this.cellsWithValidationMsg.values()) {
      this.removeValidationHelper(element);
    }
    this.cellsWithValidationMsg.clear();
    this.validationErrorFlag = false;
  }

  /**
   * Removes the validation helper from an element, if it exists.
   *
   * @param {*} element
   * @memberof XamGridComponent
   */
  removeValidationHelper(element: any) {
    /* istanbul ignore else */
    if (element.validationHelper) {
      (
        element.validationHelper as ValidationHelper
      ).unregisterValidationMsgService();
      element.validationHelper = null;
    }
  }

  /**
   * Attach a validation helper to the given element.
   *
   * @param {*} element
   * @param {string} msg
   * @memberof XamGridComponent
   */
  addValidationHelper(element: any, msg: string) {
    /* istanbul ignore else */
    if (!element.validationHelper) {
      element.validationHelper = new ValidationHelper(
        new ElementRef(element),
        this.injector
      );
    }
    (element.validationHelper as ValidationHelper).registerValidationMsgService(
      msg,
      undefined,
      undefined,
      true
    );
  }

  /**
   * Gets the cell associated with the given event.
   *
   * @private
   * @param {IGridEditDoneEventArgs} event
   * @return {*}  {*}
   * @memberof XamGridComponent
   */
  private getCellElementByEvent(event: IGridEditDoneEventArgs): any {
    return this.getNativeElement(
      this.grid.getRowByKey(event.rowID).cells[event.cellID.columnID]
    );
  }

  /**
   * Converts the given value to `null` if it is an empty string,
   * otherwise it converts the value to `number` and check if it
   * is within allowed limits for `int` type.
   *
   * @private
   * @param {{valid: boolean, value: any}} result
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  private checkNullableIntLimits(result: { valid: boolean; value: any }): void {
    /* istanbul ignore else */
    if (result.value === '' || result.value == null) {
      result.value = null;
      result.valid = true;
      return;
    }

    this.checkIntLimits(result);
  }

  /**
   * Converts the given value to `number` and check if
   * it is within allowed limits for `int` type.
   *
   * @private
   * @param {{valid: boolean, value: any}} result
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  private checkIntLimits(result: { valid: boolean; value: any }): void {
    let value = result.value;
    if (typeof value === 'string' && /^\s*(\+|-)?\d+\s*$/.test(value)) {
      value = Number.parseInt(value, 10);
    }

    if (
      typeof value === 'number' &&
      value <= 2147483647 &&
      value >= -2147483648
    ) {
      result.value = value;
      result.valid = true;
      return;
    }

    result.valid = false;
  }

  /**
   * Converts the given value to `number` and check if
   * it is within allowed limits for `decimal` type.
   *
   * NOTE: `number` limits are much lower than `decimal` limits,
   *       so a true check for decimal limits is not possible
   *       with primitive JS `number` type.
   *
   * @private
   * @param {{valid: boolean, value: any}} result
   * @return {*}  {void}
   * @memberof XamGridComponent
   */
  private checkDecimalLimits(result: { valid: boolean; value: any }): void {
    let value = result.value;
    if (
      typeof value === 'string' &&
      /^\s*(?:\+|-)?\d+(?:.\d+)?\s*$/.test(value)
    ) {
      value = Number.parseFloat(value);
    }

    if (
      typeof value === 'number' &&
      value <= Number.MAX_SAFE_INTEGER &&
      value >= Number.MIN_SAFE_INTEGER
    ) {
      result.value = value;
      result.valid = true;
      return;
    }

    result.valid = false;
  }

  /**
   * Indicates if the columns width calculation mechanism should be trigger again
   *
   * @private
   * @returns
   * @memberof XamGridComponent
   */
  private shouldReCalcColumnsWidth() {
    return (
      (this.grid.calcWidth !== this.cachedGridWidth &&
        Math.abs(this.grid.calcWidth - this.cachedGridWidth) >=
          this.toleranceGridColumnWidth) ||
      this.forcedColumnsWidthCalc
    );
  }

  /**
   * Manages the tab key navigation for the grid cells
   *
   * @param {boolean} isShift if the shift key is being pressed
   */
  /* istanbul ignore next */
  private tabKeyNavigation(isShift: boolean): void {
    if (!this.model.ActiveCell) return;
    this.isTabKeyNavigating = true;
    const currentCell = this.model.ActiveCell;
    const igxCol = this.grid.getColumnByName('data.' + currentCell.Column.Key);
    const nextCell = isShift
      ? this.grid.getPreviousCell(currentCell.Row.Index, igxCol.visibleIndex)
      : this.grid.getNextCell(currentCell.Row.Index, igxCol.visibleIndex);
    const colField = this.grid.getColumnByVisibleIndex(
      nextCell.visibleColumnIndex
    ).field;
    let cellModel: CellBase = this.getCellByRowColumn(
      nextCell.rowIndex,
      colField
    ).cell;

    // Clear selection on row changed
    if (
      this.selectionSettings.RowSelection === XamSelectionMode.Multiple &&
      currentCell.Row.Index !== nextCell.rowIndex
    ) {
      this.model.ClearSelectedRows();
      this.grid.deselectAllRows();
    }

    // Changes to the next cell
    if (
      currentCell.Row.Index !== nextCell.rowIndex &&
      this.evaluateColumnDataErrors(this.model.ActiveCell?.Row?.Data)
    ) {
      cellModel = this.getCellByRowColumn(currentCell.Row.Index, colField).cell;
    }
    this.model.ActiveCell = cellModel;
    this.grid.navigation.setActiveNode({
      row: cellModel.Row.Index,
      column: nextCell.visibleColumnIndex,
    });
  }

  /**
   * Triggers a collection changed for each row
   *
   * @private
   * @memberof XamGridComponent
   */
  private RowCollectionHandler(isRendering = false): void {
    this.isHandlingCollectionChanges = true;
    if ((this.renderedCalled || isRendering) && this.grid) {
      this.grid.data = this.dataHandler();
    } else {
      return;
    }
    this.Rows.CollectionChanged.fire([
      this.model,
      new CollectionChangeInfo(CollectionChangeAction.Reset),
    ]);
    this.callProcessActiveCellFlag = true;
    this.refreshRowsTimer = setTimeout(() => {
      this.callProcessActiveCellFlag = false;
      this.executeInitializeRowSequence();
      this.gridDataHandler();
      this.InvalidateSelectionAndActivation();
      if (this.model.Visibility === true && !this.grid?.cdr?.['destroyed']) {
        if (this.isColumnsWidthSet) this.setColumnsWidth(true);
        setTimeout(() => {
          this.grid.markForCheck?.();
        }, 100);
      }
      // Try to set the state of the activeCell in the igx-grid
      this.model.contextActiveCell?.();
      this.detectChangesAction();
      this.isHandlingCollectionChanges = false;
    });
  }

  /**
   * "For each row in the model, fire the InitializeRow event, then for each cell in the row, fire the
   * CellControlAttached event."
   *
   * memberof XamGridComponent
   */
  executeInitializeRowSequence(): void {
    for (const row of this.model.Rows.toArray()) {
      this.model.InitializeRow.fire([
        this.model,
        new InitializeRowEventArgs(row),
      ]);
      row.Cells?.internalArray.forEach((cell) => {
        this.cellControlEvents(cell);
      });
    }
  }

  /**
   * Adjust the pager settings and initialize required fields
   *
   * @private
   * @memberof XamGridComponent
   */
  private adjustPagerSettings() {
    // Workaround for the page location settings
    this.adjustPaginator(this.model.PagerSettings.AllowPaging);
    if (this.pagination) {
      this.pagination.allowPagingChange.subscribe(this.adjustPaginator);
    }
    if (this.pagingHandler()) {
      this.xamGridContext.newItemsPerPage =
        this.model.PagerSettings.PageSize.toString();
    }
  }

  /**
   * Set the Active Cell in editMode if the conditions are met
   *
   * @memberof XamGridComponent
   */
  processActiveCell(
    isOnCellActiveEditingEnabled: boolean,
    isReadOnly: boolean
  ): void {
    const cellType = this.grid?.getCellByColumn(
      this.model.ActiveCell?.Row.Index,
      'data.' + this.model.ActiveCell?.Column.Key
    );
    /* istanbul ignore else */
    if (cellType && isOnCellActiveEditingEnabled && !isReadOnly) {
      this.cellEnterEditMode(cellType, true);
    } else if (cellType && !isReadOnly && cellType.column.index === 0) {
      this.grid.navigateTo(
        cellType?.row.index,
        cellType?.column.index,
        (args) => {
          args.target.activate();
        }
      );
    }
  }

  /**
   * Clean the active cell of the xam-grid component
   *
   * @memberof XamGridComponent
   */
  cleanActiveFromIgxGrid(): void {
    if (this.grid?.navigation?.activeNode) {
      this.grid.navigation._activeNode = {} as any;
    }
  }

  /**
   * Changes the page base on the newItemsPerPage value
   *
   * @memberof XamGridComponent
   */
  changePage(): void {
    this.model.PagerSettings.PageSize = parseInt(this.newItemsPerPage);
    this.allowChangeItemsPerPage = false;
  }

  /**
   * Lost focus handler for page size change control textarea
   *
   * @memberof XamGridComponent
   */
  lostFocusTextPerPage(): void {
    this.allowChangeItemsPerPage = true;
    let value: number;
    if (
      !(
        smTryParseFloat(
          this.newItemsPerPage,
          out((v) => (value = v))
        ) &&
        value % 1 === 0 &&
        value > 0
      )
    ) {
      this.newItemsPerPage = this.model.PagerSettings.PageSize.toString();
    }
  }

  /**
   * Returns true if the FilteringSettings.AllowFiltering is enabled, otherwise false.
   *
   * @type {boolean}
   * @memberof XamGridComponent
   */
  get filteringSettingsEnabled(): boolean {
    return this.model.FilteringSettings.AllowFiltering !== 0;
  }

  /**
   * Validates is the grid is already rendered on screen
   *
   * @return {boolean}
   * @memberof XamGridComponent
   */
  get isGridVisible(): boolean {
    if (!this.grid) return false;
    const rect = this.grid.nativeElement.getBoundingClientRect();
    return (
      this.renderedCalled &&
      !!this.grid.calcWidth &&
      rect.width >= this.grid.calcWidth &&
      rect.height >= this.grid.calcHeight
    );
  }

  /**
   * Explicit mark to be added to the grid to indicate the type of filtering being applied, so custom code can
   * do css rules customizations, possible values are:
   *  none|excelStyleFilter|quickFilter
   *
   * @readonly
   * @type {string}
   * @memberof XamGridComponent
   */
  get wmFilterApplied(): string {
    /* istanbul ignore else */
    if (!this.filteringSettingsEnabled) {
      return 'none';
    }
    return this.model.FilteringSettings.AllowFiltering === 3
      ? 'excelStyleFilter'
      : 'quickFilter';
  }

  /**
   * If the column has a SortComparer, return it. Otherwise, return null.
   * @param {XamGridColumnModel} col - XamGridColumnModel - The column model that is being sorted.
   * @returns The comparer function that will be used to sort the column.
   */
  getStrategy(col: XamGridColumnModel): any {
    if (col.SortComparer) {
      return col.SortComparer;
    }
    return DefaultSortingStrategy.instance();
  }

  /**
   * Returns true if the column IsFilterable and AllowFiltering is true for the grid model. Otherwise, return false.
   * @param {XamGridColumnModel} col - XamGridColumnModel - The column model that is evaluated.
   * @returns boolean.
   */
  getFilterable(col: XamGridColumnModel): any {
    return col.IsFilterable && this.model.FilteringSettings.AllowFiltering;
  }

  /**
   * "If the itemsSource is null or empty, or the active cell's row index is greater than the number of
   * items in the itemsSource, or the active cell's row data is not in the itemsSource, then return
   * true."
   *
   *
   * @param {any} itemsSource1 - The data source for the grid.
   * @returns The return value is a boolean.
   * @memberof XamGridComponent
   */
  private shouldInvalidateActiveCell(itemsSource1: any) {
    return (
      !itemsSource1 ||
      iuCount(itemsSource1) <= this.model.ActiveCell.Row.Index ||
      !itemsSource1.contains(this.model.ActiveCell.Row.Data)
    );
  }

  /**
   *  Sets the parent row property of nested rows
   * @param {XamGridModel} model - Model that contains the nested rows
   * @param {any} rowInternalIndex - The parent row internal index data
   * @memberof XamGridComponent
   */

  private setParentRow(model: XamGridModel, rowInternalIndex: any): void {
    const index = this.grid.getRowByKey(
      rowInternalIndex?.__xam_internal_pk__
    )?.index;
    if (index != -1) {
      const parentRow = this.Rows.getItem(index);
      model.Rows?.toArray().forEach((item) => {
        item.ParentRow = parentRow;
      });
    }
  }

  /**
   * Gets GridSelectionMode depending of the selection value
   * @returns GridSelectionMode
   * @memberof XamGridComponent
   */
  getGridSelectionMode(selection: string): GridSelectionMode {
    switch (selection) {
      case 'None': {
        return GridSelectionMode.none;
      }
      case 'Single': {
        return GridSelectionMode.single;
      }
      case 'Multiple': {
        return GridSelectionMode.multiple;
      }
    }
    return GridSelectionMode.none;
  }

  /**
   * Select true if CellClickAction is SelectRow
   * @returns The return value is a boolean.
   * @memberof XamGridComponent
   */
  get selectRowOnClick(): boolean {
    return (
      this.selectionSettings.CellClickAction === CellSelectionAction.SelectRow
    );
  }
}

/**
 * Custom filter operands for xam grid.
 *
 * @class XamGridCustomFilterOperands
 * @extends {IgxStringFilteringOperand}
 */
class XamGridCustomFilterOperands extends IgxStringFilteringOperand {
  /**
   * Creates an instance of XamGridCustomFilterOperands.
   *
   * @memberof XamGridCustomFilterOperands
   */
  private constructor() {
    super();
    this.operations = [
      ...this.operations.slice(0, 4),
      new DoesNotStartWithOperand().operand,
      new DoesNotEndWithOperand().operand,
      ...this.operations.slice(4),
    ];
  }
}

result-matching ""

    No results matching ""