projects/i-components/src/lib/components/xam-grid/xam-grid.component.ts
Custom filter operands for xam grid.
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),
];
}
}