From 3374ac5ac388ff9653f4d40f97c160f2f370fd60 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Fri, 21 Mar 2025 14:28:06 +0200 Subject: [PATCH 01/49] feat(h-grid): advanced filtering POC with sample --- .../lib/data-operations/filtering-strategy.ts | 29 +- .../src/lib/grids/common/grid.interface.ts | 1 + .../advanced-filtering-dialog.component.ts | 61 +- .../query-builder-tree.component.html | 2 +- .../query-builder-tree.component.ts | 3 +- src/app/app.component.ts | 4 + src/app/app.routes.ts | 4 + .../data.ts | 1925 +++++++++++++++++ ...chical-grid-advanced-filtering.sample.html | 15 + ...chical-grid-advanced-filtering.sample.scss | 13 + ...archical-grid-advanced-filtering.sample.ts | 47 + 11 files changed, 2092 insertions(+), 12 deletions(-) create mode 100644 src/app/hierarchical-grid-advanced-filtering/data.ts create mode 100644 src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.html create mode 100644 src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.scss create mode 100644 src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index ffecead98e2..ae893b2c664 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -39,13 +39,25 @@ export interface IgxFilterItem { export abstract class BaseFilteringStrategy implements IFilteringStrategy { // protected public findMatchByExpression(rec: any, expr: IFilteringExpression, isDate?: boolean, isTime?: boolean, grid?: GridType): boolean { + if (expr.searchTree) { + const records = rec[expr.searchTree.entity]; + for (let index = 0; index < records.length; index++) { + const record = records[index]; + if (this.matchRecord(record, expr.searchTree, grid, expr.searchTree.entity)) { + return true; + } + } + + return false; + } + const cond = expr.condition; const val = this.getFieldValue(rec, expr.fieldName, isDate, isTime, grid); return cond.logic(val, expr.searchVal, expr.ignoreCase); } // protected - public matchRecord(rec: any, expressions: IFilteringExpressionsTree | IFilteringExpression, grid?: GridType): boolean { + public matchRecord(rec: any, expressions: IFilteringExpressionsTree | IFilteringExpression, grid?: GridType, entity?: string): boolean { if (expressions) { if (isTree(expressions)) { const expressionsTree = expressions; @@ -54,7 +66,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { if (expressionsTree.filteringOperands && expressionsTree.filteringOperands.length) { for (const operand of expressionsTree.filteringOperands) { - matchOperand = this.matchRecord(rec, operand, grid); + matchOperand = this.matchRecord(rec, operand, grid, entity); // Return false if at least one operand does not match and the filtering logic is And if (!matchOperand && operator === FilteringLogic.And) { @@ -73,9 +85,16 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { return true; } else { const expression = expressions; - const column = grid && grid.getColumnByName(expression.fieldName); - const isDate = column ? column.dataType === DateType || column.dataType === DateTimeType : false; - const isTime = column ? column.dataType === TimeType : false; + let isDate = false; + let isTime = false; + if (!entity) { + const column = grid && grid.getColumnByName(expression.fieldName); + isDate = column ? column.dataType === DateType || column.dataType === DateTimeType : false; + isTime = column ? column.dataType === TimeType : false; + } else { + // TODO: check for date and time + } + return this.findMatchByExpression(rec, expression, isDate, isTime, grid); } } diff --git a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts index f0ea80c2def..3c8d8aa4f38 100644 --- a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts +++ b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts @@ -1497,4 +1497,5 @@ export interface IClipboardOptions { export interface EntityType { name: string; fields: FieldType[]; + childEntities?: EntityType[]; } diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index 3a733ddd475..89641f9c745 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -6,7 +6,7 @@ import { IDragStartEventArgs, IgxDragDirective, IgxDragHandleDirective } from '. import { Subject } from 'rxjs'; import { IActiveNode } from '../../grid-navigation.service'; import { PlatformUtil } from '../../../core/utils'; -import { FieldType, GridType } from '../../common/grid.interface'; +import { EntityType, FieldType, GridType } from '../../common/grid.interface'; import { IgxQueryBuilderComponent } from '../../../query-builder/query-builder.component'; import { GridResourceStringsEN } from '../../../core/i18n/grid-resources'; import { IFilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree'; @@ -15,6 +15,8 @@ import { IgxQueryBuilderHeaderComponent } from '../../../query-builder/query-bui import { NgClass } from '@angular/common'; import { getCurrentResourceStrings } from '../../../core/i18n/resources'; import { QueryBuilderResourceStringsEN } from '../../../core/i18n/query-builder-resources'; +import { IgxHierarchicalGridComponent } from '../../hierarchical-grid/hierarchical-grid.component'; +import { IgxRowIslandComponent } from '../../hierarchical-grid/row-island.component'; /** * A component used for presenting advanced filtering UI for a Grid. @@ -191,20 +193,69 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes this.closeDialog(); } - + /** * @hidden @internal */ public generateEntity() { - const entities = [ + if (this.queryBuilder?.entities) { + return this.queryBuilder?.entities; + } + + const isHierarchicalGrid = this.grid instanceof IgxHierarchicalGridComponent; + const entities: EntityType[] = [ { - name: null, - fields: this.filterableFields + name: null, + fields: this.filterableFields.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[] } ]; + + if (isHierarchicalGrid) { + const hierarchicalGrid = this.grid as IgxHierarchicalGridComponent; + entities[0].childEntities = hierarchicalGrid.childLayoutList.reduce((acc, rowIsland) => { + return acc.concat(this.generateChildEntity(rowIsland, hierarchicalGrid.data[0][rowIsland.key][0])); + } + , []); + } + return entities; } + private generateChildEntity(rowIsland: IgxRowIslandComponent, firstRowData: any[]): EntityType { + const entityName = rowIsland.key; + let fields = []; + let childEntities; + if (!rowIsland.autoGenerate) { + fields = rowIsland.columnList.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[]; + } else { + const rowIslandFields = Object.keys(firstRowData).map(key => { + if (firstRowData[key] instanceof Array) { + return null; + } + + return { + field: key, + dataType: (this.grid as IgxHierarchicalGridComponent).resolveDataTypes(firstRowData[key]) + } + }); + fields = rowIslandFields.filter(f => f !== null) as FieldType[]; + } + + const rowIslandChildEntities = rowIsland.childLayoutList.reduce((acc, childRowIsland) => { + return acc.concat(this.generateChildEntity(childRowIsland, firstRowData[childRowIsland.key][0])); + }, []); + + if (rowIslandChildEntities.length > 0) { + childEntities = rowIslandChildEntities; + } + + return { + name: entityName, + fields: fields, + childEntities: childEntities + } + } + private assignResourceStrings() { // If grid has custom resource strings set for the advanced filtering, // they are passed to the query builder resource strings. diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index 89f198e2180..5a2b4e3f7cb 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -453,7 +453,7 @@ +

Sample One

+ + + + + + + + + + + + + diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.scss b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.scss new file mode 100644 index 00000000000..493f8cca993 --- /dev/null +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.scss @@ -0,0 +1,13 @@ +:host::ng-deep { + .activeRow { + background-color: rgb(201, 241, 201); + } +} + +[igxButton="contained"] { + margin: 0.5rem; +} + +h4 { + padding-top: 2rem; +} diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts new file mode 100644 index 00000000000..5b4d4fd6013 --- /dev/null +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts @@ -0,0 +1,47 @@ +import { Component, ViewChild, ChangeDetectorRef, AfterViewInit } from '@angular/core'; +import { + IgxHierarchicalGridComponent, + IGX_HIERARCHICAL_GRID_DIRECTIVES, + FilteringExpressionsTree, + FilteringLogic, + IgxNumberFilteringOperand, + IgxStringFilteringOperand +} from 'igniteui-angular'; +import { SINGERS } from './data'; + + +@Component({ + selector: 'app-hierarchical-grid-advanced-filtering-sample', + styleUrls: ['hierarchical-grid-advanced-filtering.sample.scss'], + templateUrl: 'hierarchical-grid-advanced-filtering.sample.html', + imports: [IGX_HIERARCHICAL_GRID_DIRECTIVES] +}) +export class HierarchicalGridAdvancedFilteringSampleComponent implements AfterViewInit { + @ViewChild('hierarchicalGrid', { static: true }) + private hierarchicalGrid: IgxHierarchicalGridComponent; + + public localData = []; + + constructor(private cdr: ChangeDetectorRef) { + this.localData = SINGERS; + } + + public ngAfterViewInit() { + const innerTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Albums', ['Artist']); + innerTree.filteringOperands.push({ + fieldName: 'USBillboard200', + condition: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo'), + conditionName: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo').name, + searchVal: 5 + }); + const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Artists', ['*']); + tree.filteringOperands.push({ + fieldName: 'Artist', + condition: IgxStringFilteringOperand.instance().condition('inQuery'), + conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, + searchTree: innerTree + }); + this.hierarchicalGrid.advancedFilteringExpressionsTree = tree; + this.cdr.detectChanges(); + } +} From 878981edebd34cb143f775e5725dc0eefdebbd54 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Thu, 27 Mar 2025 15:09:15 +0200 Subject: [PATCH 02/49] feat(h-grid): hide return fields select/combo in advanced filtering --- .../src/lib/query-builder/query-builder-tree.component.html | 6 ++++-- .../src/lib/query-builder/query-builder-tree.component.ts | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index cbcca7392dd..0a1dca77b4f 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -29,7 +29,9 @@
- Select + @if (!this.isHierarchicalGridNestedQuery()) { + Select + } @if (!parentExpression) { } - @else { + @else if (!this.isAdvancedFiltering() && !this.isHierarchicalGridNestedQuery()) { Date: Fri, 28 Mar 2025 11:00:51 +0200 Subject: [PATCH 03/49] test(AF): refactor AF rehydration describe --- .../grid/grid-filtering-advanced.spec.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 9fb2b661cfe..42d99e19be3 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1292,7 +1292,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 1); // Select 'ProductName' column. QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0); // Select 'Contains' operator. - let input = QueryBuilderFunctions.getQueryBuilderValueInput(fix).querySelector('input'); + const input = QueryBuilderFunctions.getQueryBuilderValueInput(fix).querySelector('input'); UIInteractions.clickAndSendInputElementValue(input, 'angular', fix); // Type filter value. // Commit the populated expression. QueryBuilderFunctions.clickQueryBuilderExpressionCommitButton(fix); @@ -1455,11 +1455,16 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { }); describe('Expression tree rehydration - ', () => { + let fix: ComponentFixture; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(() => { + fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); + grid = fix.componentInstance.grid; + fix.detectChanges(); + })); it('should correctly filter with a deserialized expression tree.', fakeAsync(() => { const errorSpy = spyOn(console, 'error'); - let fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); - fix.detectChanges(); - let grid = fix.componentInstance.grid; expect(errorSpy).not.toHaveBeenCalled(); @@ -1470,12 +1475,8 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { it('should correctly filter with a declared IFilteringExpressionsTree object.', fakeAsync(() => { const errorSpy = spyOn(console, 'error'); - let fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); - fix.detectChanges(); fix.componentInstance.grid.advancedFilteringExpressionsTree = fix.componentInstance.filterTreeObject; fix.detectChanges(); - let grid = fix.componentInstance.grid; - expect(errorSpy).not.toHaveBeenCalled(); // Verify filtered data @@ -1485,11 +1486,8 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { it('should correctly filter when binding to a declared IFilteringExpressionsTree object.', fakeAsync(() => { const errorSpy = spyOn(console, 'error'); - let fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); - fix.detectChanges(); fix.componentInstance.filterTree = fix.componentInstance.filterTreeObject; fix.detectChanges(); - let grid = fix.componentInstance.grid; expect(errorSpy).not.toHaveBeenCalled(); From 6824a862b987d8747cb44d2c570c3f305287d344 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Fri, 28 Mar 2025 13:05:31 +0200 Subject: [PATCH 04/49] fix(hgrid): Cannot access 'IgxGridBaseDirective' before initialization --- .../advanced-filtering/advanced-filtering-dialog.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index 89641f9c745..74a65ca3707 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -202,7 +202,7 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes return this.queryBuilder?.entities; } - const isHierarchicalGrid = this.grid instanceof IgxHierarchicalGridComponent; + const isHierarchicalGrid = this.grid.type === 'hierarchical'; const entities: EntityType[] = [ { name: null, @@ -230,7 +230,7 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes } else { const rowIslandFields = Object.keys(firstRowData).map(key => { if (firstRowData[key] instanceof Array) { - return null; + return null; } return { From a9f8e330ad7a7930f42e5d61982e70e162d38e85 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 31 Mar 2025 13:35:30 +0300 Subject: [PATCH 05/49] test(AF): add tests for QB in hgrid AF --- .../advanced-filtering-dialog.component.ts | 2 +- .../grid/grid-filtering-advanced.spec.ts | 126 +++++++++++++++++- .../query-builder-functions.spec.ts | 6 +- 3 files changed, 125 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index 74a65ca3707..c549b05788a 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -230,7 +230,7 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes } else { const rowIslandFields = Object.keys(firstRowData).map(key => { if (firstRowData[key] instanceof Array) { - return null; + return null; } return { diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 42d99e19be3..aed412d537e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -20,7 +20,7 @@ import { IgxGridAdvancedFilteringWithToolbarComponent } from '../../test-utils/grid-samples.spec'; import { FormattedValuesFilteringStrategy } from '../../data-operations/filtering-strategy'; -import { IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec'; +import { IgxHierarchicalGridTestBaseComponent, IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec'; import { IgxHierarchicalGridComponent } from '../hierarchical-grid/public_api'; import { IFilteringEventArgs, IgxGridToolbarAdvancedFilteringComponent } from '../public_api'; import { SampleTestData } from '../../test-utils/sample-test-data.spec'; @@ -40,7 +40,8 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { IgxGridAdvancedFilteringBindingComponent, IgxHierGridExternalAdvancedFilteringComponent, IgxGridAdvancedFilteringDynamicColumnsComponent, - IgxGridAdvancedFilteringWithToolbarComponent + IgxGridAdvancedFilteringWithToolbarComponent, + IgxHierarchicalGridTestBaseComponent ] }); })); @@ -637,9 +638,9 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fix.detectChanges(); const dropdownItems = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement); expect(dropdownItems.length).toBe(4); - expect((dropdownItems[0] as HTMLElement).innerText).toBe('HeaderID'); + expect((dropdownItems[0] as HTMLElement).innerText).toBe('ID'); expect((dropdownItems[1] as HTMLElement).innerText).toBe('ProductName'); - expect((dropdownItems[2] as HTMLElement).innerText).toBe('Another Field'); + expect((dropdownItems[2] as HTMLElement).innerText).toBe('AnotherField'); expect((dropdownItems[3] as HTMLElement).innerText).toBe('ReleaseTime'); })); @@ -1328,7 +1329,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { QueryBuilderFunctions.clickQueryBuilderColumnSelect(fix); fix.detectChanges(); const dropdownValues = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); - const expectedValues = ['ID', 'ProductName', 'Downloads', 'Released', 'ReleaseDate', 'Another Field', 'DateTimeCreated']; + const expectedValues = ['ID', 'ProductName', 'Downloads', 'Released', 'ReleaseDate', 'AnotherField', 'DateTimeCreated']; expect(expectedValues).toEqual(dropdownValues); })); }); @@ -1463,6 +1464,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { grid = fix.componentInstance.grid; fix.detectChanges(); })); + it('should correctly filter with a deserialized expression tree.', fakeAsync(() => { const errorSpy = spyOn(console, 'error'); @@ -1496,6 +1498,120 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { expect(grid.rowList.length).toBe(2); })); }); + + describe('Hierarchical grid advanced filtering - ', () => { + let fix: ComponentFixture; + let hgrid: IgxHierarchicalGridComponent; + + beforeEach(fakeAsync(() => { + fix = TestBed.createComponent(IgxHierarchicalGridTestBaseComponent); + hgrid = fix.componentInstance.hgrid; + hgrid.allowAdvancedFiltering = true; + fix.detectChanges(); + + // Open Advanced Filtering dialog. + hgrid.openAdvancedFilteringDialog(); + fix.detectChanges(); + + // Click the initial 'Add Condition' button of the query builder. + QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, 0); + tick(100); + fix.detectChanges(); + })); + + it(`Should have 'In'/'Not-In' operators for fields with chilld entities.`, fakeAsync(() => { + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + + // Open the operator dropdown and verify they are 'string' specific + 'In'/'Not In'. + QueryBuilderFunctions.clickQueryBuilderOperatorSelect(fix); + fix.detectChanges(); + const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[0].nativeElement; + const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); + const expectedValues = ['Contains', 'Does Not Contain', 'Starts With', 'Ends With', 'Equals', + 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null', 'In', 'Not In'];; + expect(dropdownValues).toEqual(expectedValues); + + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(false); + tick(200); + fix.detectChanges(); + })); + + it(`Should NOT have 'In'/'Not-In' operators for fields without chilld entities.`, fakeAsync(() => { + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. + + // Select entity in nested level + QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); + // Populate edit inputs on level 1. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 11, 1); // Select 'Not In' operator. + + // Select entity in nested level + QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 2); + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 2); // Select 'ID' column. + // Open the operator dropdown and verify they are 'string' specific + 'In'/'Not In'. + QueryBuilderFunctions.clickQueryBuilderOperatorSelect(fix, 2); + fix.detectChanges(); + const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[2].nativeElement; + const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); + const expectedValues = ['Contains', 'Does Not Contain', 'Starts With', 'Ends With', 'Equals', + 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null'];; + expect(dropdownValues).toEqual(expectedValues); + + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(false); + tick(200); + fix.detectChanges(); + })); + + it('Should have correct entities depending on the hierarchy level.', fakeAsync(() => { + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. + + QueryBuilderFunctions.clickQueryBuilderEntitySelect(fix, 1); + fix.detectChanges(); + const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[1].nativeElement; + const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); + const expectedValues = ['childData']; + expect(dropdownValues).toEqual(expectedValues); + + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(false); + tick(200); + fix.detectChanges(); + })); + + it(`Should apply 'In'/'Not-In' operators for each level properly.`, fakeAsync(() => { + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. + + // Select entity in nested level + QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); + // Populate edit inputs on level 1. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0, 1); // Select 'Contains' operator. + + const input = QueryBuilderFunctions.getQueryBuilderValueInput(fix, false, 1).querySelector('input'); + // Type Value + UIInteractions.clickAndSendInputElementValue(input, '39'); + tick(100); + fix.detectChanges(); + + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(true); + tick(200); + fix.detectChanges(); + + // Veify grid data + expect(hgrid.filteredData.length).toEqual(2); + expect(hgrid.rowList.length).toBe(2); + })); + }); }); diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts index e334816edc3..46d97a3361a 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts @@ -841,13 +841,13 @@ export class QueryBuilderFunctions { fix.detectChanges(); } - public static selectEntityAndClickInitialAddCondition(fix: ComponentFixture, entityIndex: number, groupIndex = 0) { - QueryBuilderFunctions.selectEntityInEditModeExpression(fix, entityIndex); + public static selectEntityAndClickInitialAddCondition(fix: ComponentFixture, entityIndex: number, level = 0) { + QueryBuilderFunctions.selectEntityInEditModeExpression(fix, entityIndex, level); tick(100); fix.detectChanges(); // Click the initial 'Add Condition' button. - QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, groupIndex); + QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, level); tick(100); fix.detectChanges(); } From edcb401fcb0f0333df1f016a846f2b64a30589b8 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 31 Mar 2025 13:57:33 +0300 Subject: [PATCH 06/49] fix(query-builder): commit expressions properly in hgrid --- .../src/lib/grids/grid/grid-filtering-advanced.spec.ts | 4 ++-- .../src/lib/query-builder/query-builder-tree.component.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index aed412d537e..30bc6dc2058 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1608,8 +1608,8 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fix.detectChanges(); // Veify grid data - expect(hgrid.filteredData.length).toEqual(2); - expect(hgrid.rowList.length).toBe(2); + expect(hgrid.filteredData.length).toEqual(5); + expect(hgrid.rowList.length).toBe(5); })); }); }); diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index ead97d7123f..190fab3e3e0 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -938,7 +938,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) ) || ( - this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0 + this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && + (innerQuery.selectedReturnFields?.length > 0 || (innerQuery.selectedReturnFields?.length === 0 && this.isAdvancedFiltering())) ) || this.selectedField.filters.condition(this.selectedCondition)?.isUnary ); From ae5062e6c6e3e5aa5ebb75042be7d1d995be5a16 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 31 Mar 2025 15:38:36 +0300 Subject: [PATCH 07/49] fix(query-builder): get proper condition list for AF & QB --- .../src/lib/query-builder/query-builder-tree.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 190fab3e3e0..0fef5419541 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -1364,8 +1364,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { public getConditionList(): string[] { if (!this.selectedField) return []; - if ((this.entities?.length === 1 && !this.entities[0].name && !this.entities[0].childEntities) || - (this.selectedEntity && this.selectedEntity.name && !this.selectedEntity.childEntities)) { + if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) || + (this.isHierarchicalGridNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) { return this.selectedField.filters.conditionList(); } From 04d0496c68bbc2f91801e625c355876bd2ac8c1c Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Mon, 31 Mar 2025 17:14:51 +0300 Subject: [PATCH 08/49] chore(*): fix selected field filters --- .../src/lib/query-builder/query-builder-tree.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index d8fb6a099fe..4756ca65d4a 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -697,6 +697,10 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public get selectedField(): FieldType { + if (this._selectedField && !this._selectedField.filters) { + this._selectedField.filters = this.getFilters(this._selectedField); + } + return this._selectedField; } From 69fe521fc841279913e81ecf2d3181ce0691da63 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 1 Apr 2025 14:18:42 +0300 Subject: [PATCH 09/49] chore(*): fix adv filtering check and set selected field pipeArgs --- .../src/lib/query-builder/query-builder-tree.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 4756ca65d4a..a122ee78514 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -516,7 +516,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { /** @hidden */ protected isAdvancedFiltering(): boolean { - return this.entities?.length === 1 && !this.entities[0]?.name; + return (this.entities?.length === 1 && !this.entities[0]?.name) || + this.entities.find(e => e.childEntities?.length > 0) !== undefined; } /** @hidden */ @@ -701,6 +702,10 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { this._selectedField.filters = this.getFilters(this._selectedField); } + if (this._selectedField && this._selectedField.pipeArgs) { + this._selectedField.pipeArgs = this.getPipeArgs(this._selectedField); + } + return this._selectedField; } From 2f41c93f110ca9a5ce0813006f6ab9704f17e632 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 1 Apr 2025 14:39:42 +0300 Subject: [PATCH 10/49] chore(*): fix selected field pipeArgs --- .../src/lib/query-builder/query-builder-tree.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index a122ee78514..4200355ff53 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -698,11 +698,12 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public get selectedField(): FieldType { + console.log('get selectedField'); if (this._selectedField && !this._selectedField.filters) { this._selectedField.filters = this.getFilters(this._selectedField); } - if (this._selectedField && this._selectedField.pipeArgs) { + if (this._selectedField && !this._selectedField.pipeArgs) { this._selectedField.pipeArgs = this.getPipeArgs(this._selectedField); } From b846588166ea97d34ec8ff5b67cab52894fd072f Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 1 Apr 2025 15:18:50 +0300 Subject: [PATCH 11/49] feat(h-grid): support notInQuery in adv filtering --- .../src/lib/data-operations/filtering-strategy.ts | 4 +++- .../src/lib/query-builder/query-builder-tree.component.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index 94003bd78a3..dca96efe02b 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -41,9 +41,11 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { public findMatchByExpression(rec: any, expr: IFilteringExpression, isDate?: boolean, isTime?: boolean, grid?: GridType): boolean { if (expr.searchTree) { const records = rec[expr.searchTree.entity]; + const shouldMatchRecords = expr.conditionName === 'inQuery'; for (let index = 0; index < records.length; index++) { const record = records[index]; - if (this.matchRecord(record, expr.searchTree, grid, expr.searchTree.entity)) { + if ((shouldMatchRecords && this.matchRecord(record, expr.searchTree, grid, expr.searchTree.entity)) || + (!shouldMatchRecords && !this.matchRecord(record, expr.searchTree, grid, expr.searchTree.entity))) { return true; } } diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 4200355ff53..51eea117505 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -698,7 +698,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public get selectedField(): FieldType { - console.log('get selectedField'); if (this._selectedField && !this._selectedField.filters) { this._selectedField.filters = this.getFilters(this._selectedField); } From a7bac6f170436372607022fdfd6220f236b182b5 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Wed, 2 Apr 2025 15:52:28 +0300 Subject: [PATCH 12/49] feat(h-grid): adv filtering improvements for load on demand --- .../lib/data-operations/filtering-strategy.ts | 5 + .../advanced-filtering-dialog.component.ts | 7 +- .../hierarchical-grid-remote.sample.html | 45 +++----- .../hierarchical-grid-remote.sample.ts | 103 ++++++++++-------- 4 files changed, 81 insertions(+), 79 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index dca96efe02b..9024f73e3d3 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -42,6 +42,11 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { if (expr.searchTree) { const records = rec[expr.searchTree.entity]; const shouldMatchRecords = expr.conditionName === 'inQuery'; + // if records is empty, it means that the child grid is not created yet + if (!records || !records.length) { + return true; + } + for (let index = 0; index < records.length; index++) { const record = records[index]; if ((shouldMatchRecords && this.matchRecord(record, expr.searchTree, grid, expr.searchTree.entity)) || diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index c549b05788a..f0325c2d792 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -227,7 +227,7 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes let childEntities; if (!rowIsland.autoGenerate) { fields = rowIsland.columnList.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[]; - } else { + } else if (firstRowData) { const rowIslandFields = Object.keys(firstRowData).map(key => { if (firstRowData[key] instanceof Array) { return null; @@ -242,10 +242,13 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes } const rowIslandChildEntities = rowIsland.childLayoutList.reduce((acc, childRowIsland) => { + if (!firstRowData) { + return null; + } return acc.concat(this.generateChildEntity(childRowIsland, firstRowData[childRowIsland.key][0])); }, []); - if (rowIslandChildEntities.length > 0) { + if (rowIslandChildEntities?.length > 0) { childEntities = rowIslandChildEntities; } diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html index 2c2d2059def..43140dfc25b 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html @@ -1,42 +1,29 @@ - - + - Parent Toolbar - - - + Customers - + + - Child Toolbar - Level 2 - - - + Orders - - - - - - + + + + - Child Toolbar - Level 3 - - - - + Order Details - - - - + + + + + diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index e3f705c25a4..8e73cfc27d7 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -1,85 +1,92 @@ -import { Component, ViewChild, AfterViewInit } from '@angular/core'; +import { Component, ViewChild, OnInit, ChangeDetectorRef } from '@angular/core'; import { IgxRowIslandComponent, IgxHierarchicalGridComponent, IGridCreatedEventArgs, - GridSelectionMode, - IGX_HIERARCHICAL_GRID_DIRECTIVES + IGX_HIERARCHICAL_GRID_DIRECTIVES, + FilteringExpressionsTree, + IgxStringFilteringOperand } from 'igniteui-angular'; -import { RemoteService } from '../shared/remote.service'; +import { HttpClient } from '@angular/common/http'; + +const API_ENDPOINT = 'https://data-northwind.indigo.design'; @Component({ selector: 'app-hierarchical-grid-remote-sample', templateUrl: 'hierarchical-grid-remote.sample.html', styleUrls: ['hierarchical-grid-remote.sample.scss'], - providers: [RemoteService], imports: [IGX_HIERARCHICAL_GRID_DIRECTIVES] }) -export class HierarchicalGridRemoteSampleComponent implements AfterViewInit { - @ViewChild('rowIsland1', { static: true }) - private rowIsland1: IgxRowIslandComponent; - +export class HierarchicalGridRemoteSampleComponent implements OnInit { @ViewChild('hGrid', { static: true }) private hGrid: IgxHierarchicalGridComponent; public selectionMode; public remoteData = []; public primaryKeys = [ - { name: 'CustomerID', type: 'string', level: 0 }, - { name: 'OrderID', type: 'number', level: 1 }, - { name: 'EmployeeID', type: 'number', level: 2 }, - { name: 'ProductID', type: 'number', level: 2 } + { name: 'Customers', type: 'string', level: 0 }, + { name: 'Orders', type: 'number', level: 1 }, + { name: 'Details', type: 'number', level: 2 } ]; - constructor(private remoteService: RemoteService) { - remoteService.url = 'https://services.odata.org/V4/Northwind/Northwind.svc/'; - - this.remoteService.urlBuilder = (dataState) => this.buildUrl(dataState); - this.selectionMode = GridSelectionMode.none; - } - - public buildUrl(dataState) { - let qS = ''; - if (dataState) { - qS += `${dataState.key}?`; + constructor(private http: HttpClient, private cdr: ChangeDetectorRef) {} - const level = dataState.level; - if (level > 0) { - const parentKey = this.primaryKeys.find((key) => key.level === level - 1); - const parentID = typeof dataState.parentID !== 'object' ? dataState.parentID : dataState.parentID[parentKey.name]; + public ngOnInit() { + const ordersTree = new FilteringExpressionsTree(0, undefined, 'Orders', ['shipVia']); + ordersTree.filteringOperands.push({ + fieldName: 'shipVia', + ignoreCase: false, + conditionName: IgxStringFilteringOperand.instance().condition('equals').name, + searchVal: 'AirCargo' + }); - if (parentKey.type === 'string') { - qS += `$filter=${parentKey.name} eq '${parentID}'`; - } else { - qS += `$filter=${parentKey.name} eq ${parentID}`; - } - } - } - return `${this.remoteService.url}${qS}`; + const customersTree = new FilteringExpressionsTree(0, undefined, 'Customers', ['customerId', 'companyName', 'contactName', 'contactTitle']); + // customersTree.filteringOperands.push({ + // fieldName: 'customerId', + // conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, + // ignoreCase: false, + // searchTree: ordersTree + // }); + customersTree.filteringOperands.push({ + fieldName: 'customerId', + ignoreCase: false, + conditionName: IgxStringFilteringOperand.instance().condition('startsWith').name, + searchVal: 'A' + }); + this.hGrid.advancedFilteringExpressionsTree = customersTree; } public ngAfterViewInit() { - this.remoteService.getData({ parentID: null, level: 0, key: 'Customers' }, (data) => { - this.remoteData = data['value']; + this.hGrid.isLoading = true; + this.http.post(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, this.hGrid.advancedFilteringExpressionsTree).subscribe(data =>{ + console.log('data', data); + this.remoteData = Object.values(data)[0]; this.hGrid.isLoading = false; + this.cdr.detectChanges(); + this.calculateColsInView(); }); } - public setterChange() { - this.rowIsland1.rowSelection = this.rowIsland1.rowSelection === GridSelectionMode.multiple - ? GridSelectionMode.none : GridSelectionMode.multiple; - } - - public setterBindingChange() { - this.selectionMode = this.selectionMode === GridSelectionMode.none ? GridSelectionMode.multiple : GridSelectionMode.none; + private calculateColsInView() { + this.hGrid.columns.forEach(column => + column.hidden = !this.hGrid.advancedFilteringExpressionsTree.returnFields.includes(column.field)); } public gridCreated(event: IGridCreatedEventArgs, rowIsland: IgxRowIslandComponent) { event.grid.isLoading = true; - this.remoteService.getData({ parentID: event.parentID, level: rowIsland.level, key: rowIsland.key }, (data) => { - event.grid.data = data['value']; + const url = this.buildUrl(event, rowIsland); + this.http.get(url).subscribe(data => { + console.log('data', data); + event.grid.data = Object.values(data); event.grid.isLoading = false; - event.grid.cdr.detectChanges(); + this.cdr.detectChanges(); }); } + + private buildUrl(event: IGridCreatedEventArgs, rowIsland: IgxRowIslandComponent) { + const rowIslandKey = this.primaryKeys.find(key => key.level === rowIsland.level).name; + const parentKey = (event.grid.parent as any).key ?? event.grid.parent.advancedFilteringExpressionsTree.entity; + const url = `${API_ENDPOINT}/${parentKey}/${event.parentID}/${rowIslandKey}`; + return url; + } } From 3205fd87ba4575c571e6d7b527159f373de62c57 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Mon, 7 Apr 2025 15:49:29 +0300 Subject: [PATCH 13/49] fix(h-grid): set more fields props in entities --- .../advanced-filtering-dialog.component.ts | 12 +++++++++++- .../query-builder/query-builder-tree.component.ts | 8 -------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index f0325c2d792..bf54196c512 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -206,7 +206,17 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes const entities: EntityType[] = [ { name: null, - fields: this.filterableFields.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[] + fields: this.filterableFields.map(f => ({ + field: f.field, + dataType: f.dataType, + // label: f.label, + // header: f.header, + editorOptions: f.editorOptions, + filters: f.filters, + pipeArgs: f.pipeArgs, + defaultTimeFormat: f.defaultTimeFormat, + defaultDateTimeFormat: f.defaultDateTimeFormat + })) as FieldType[] } ]; diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 51eea117505..9e1a445b24c 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -698,14 +698,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public get selectedField(): FieldType { - if (this._selectedField && !this._selectedField.filters) { - this._selectedField.filters = this.getFilters(this._selectedField); - } - - if (this._selectedField && !this._selectedField.pipeArgs) { - this._selectedField.pipeArgs = this.getPipeArgs(this._selectedField); - } - return this._selectedField; } From 874359df3dcb7b689eca47139894d71ba7ad8979 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Wed, 9 Apr 2025 16:11:18 +0300 Subject: [PATCH 14/49] feat(h-grid): set remote entities for advanced filtering --- .../data-operations/expressions-tree-util.ts | 3 +- .../advanced-filtering-dialog.component.ts | 6 +- .../src/lib/grids/grid-base.directive.ts | 26 +++++--- .../hierarchical-grid.component.ts | 22 ++++++- .../query-builder-tree.component.html | 2 +- .../query-builder-tree.component.ts | 7 +++ .../hierarchical-grid-remote.sample.html | 11 +++- .../hierarchical-grid-remote.sample.ts | 63 ++++++++++++++++--- 8 files changed, 117 insertions(+), 23 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts b/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts index c9543362999..2c4c5a4771e 100644 --- a/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts @@ -180,6 +180,7 @@ export function isTree(entry: IExpressionTree | IFilteringExpression): entry is */ export function recreateTree(tree: IExpressionTree, entities: EntityType[]): IExpressionTree { const entity = entities.find(e => e.name === tree.entity); + if (!entity) return tree; for (let i = 0; i < tree.filteringOperands.length; i++) { const operand = tree.filteringOperands[i]; @@ -187,7 +188,7 @@ export function recreateTree(tree: IExpressionTree, entities: EntityType[]): IEx tree.filteringOperands[i] = recreateTree(operand, entities); } else { if (operand.searchTree) { - operand.searchTree = recreateTree(operand.searchTree, entities); + operand.searchTree = recreateTree(operand.searchTree, entities[0].childEntities ?? entities); } tree.filteringOperands[i] = recreateExpression(operand, entity?.fields); } diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index bf54196c512..189200837bd 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -219,9 +219,13 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes })) as FieldType[] } ]; - + if (isHierarchicalGrid) { const hierarchicalGrid = this.grid as IgxHierarchicalGridComponent; + if (hierarchicalGrid.remoteEntities) { + return hierarchicalGrid.remoteEntities; + } + entities[0].childEntities = hierarchicalGrid.childLayoutList.reduce((acc, rowIsland) => { return acc.concat(this.generateChildEntity(rowIsland, hierarchicalGrid.data[0][rowIsland.key][0])); } diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 4d02cd45be2..be51a2d73f2 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -147,7 +147,8 @@ import { ISizeInfo, RowType, IPinningConfig, - IClipboardOptions + IClipboardOptions, + EntityType } from './common/grid.interface'; import { DropPosition } from './moving/moving.service'; import { IgxHeadSelectorDirective, IgxRowSelectorDirective } from './selection/row-selectors'; @@ -179,7 +180,7 @@ import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../data-operations import { IgxGridCellComponent } from './cell.component'; import { IgxGridValidationService } from './grid/grid-validation.service'; import { getCurrentResourceStrings } from '../core/i18n/resources'; -import { isTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util'; +import { isTree, recreateTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util'; import { getUUID } from './common/random'; interface IMatchInfoCache { @@ -1859,7 +1860,7 @@ export abstract class IgxGridBaseDirective implements GridType, value.type = FilteringExpressionsTreeType.Regular; if (value && this._columns?.length > 0) { - this._filteringExpressionsTree = recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; + this._filteringExpressionsTree = this.getRecreatedTree(value); } else { this._filteringExpressionsTree = value; } @@ -1909,7 +1910,7 @@ export abstract class IgxGridBaseDirective implements GridType, if (value && isTree(value)) { value.type = FilteringExpressionsTreeType.Advanced; if (this._columns && this._columns.length > 0) { - this._advancedFilteringExpressionsTree = recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; + this._advancedFilteringExpressionsTree = this.getRecreatedTree(value); } else { this._advancedFilteringExpressionsTree = value; } @@ -3138,6 +3139,7 @@ export abstract class IgxGridBaseDirective implements GridType, matchCount: 0, content: '' }; + protected _hGridRemoteEntities: EntityType[]; protected gridComputedStyles; /** @hidden @internal */ @@ -6623,10 +6625,10 @@ export abstract class IgxGridBaseDirective implements GridType, this._unpinnedColumns = newColumns.filter((c) => !c.pinned); this._columns = newColumns; if (this._columns && this._columns.length && this._filteringExpressionsTree) { - this._filteringExpressionsTree = recreateTreeFromFields(this._filteringExpressionsTree, this.columns) as IFilteringExpressionsTree; + this._filteringExpressionsTree = this.getRecreatedTree(this._filteringExpressionsTree); } if (this._columns && this._columns.length && this._advancedFilteringExpressionsTree) { - this._advancedFilteringExpressionsTree = recreateTreeFromFields(this._advancedFilteringExpressionsTree, this.columns) as IFilteringExpressionsTree; + this._advancedFilteringExpressionsTree = this.getRecreatedTree(this._advancedFilteringExpressionsTree); } this.resetCaches(); } @@ -6691,10 +6693,10 @@ export abstract class IgxGridBaseDirective implements GridType, this._columns = this.getColumnList(); } if (this._columns && this._columns.length && this._filteringExpressionsTree) { - this._filteringExpressionsTree = recreateTreeFromFields(this._filteringExpressionsTree, this._columns) as IFilteringExpressionsTree; + this._filteringExpressionsTree = this.getRecreatedTree(this._filteringExpressionsTree); } if (this._columns && this._columns.length && this._advancedFilteringExpressionsTree) { - this._advancedFilteringExpressionsTree = recreateTreeFromFields(this._advancedFilteringExpressionsTree, this._columns) as IFilteringExpressionsTree; + this._advancedFilteringExpressionsTree = this.getRecreatedTree(this._advancedFilteringExpressionsTree); } this.initColumns(this._columns, (col: IgxColumnComponent) => this.columnInit.emit(col)); @@ -7907,4 +7909,12 @@ export abstract class IgxGridBaseDirective implements GridType, this.navigation.activeNode = {} as IActiveNode; this.notifyChanges(); } + + private getRecreatedTree(value: IFilteringExpressionsTree): IFilteringExpressionsTree { + if (this._hGridRemoteEntities) { + return recreateTree(value, this._hGridRemoteEntities) as IFilteringExpressionsTree; + } else { + return recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; + } + } } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index 4b06865bfdf..e8d1ce80478 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -35,7 +35,7 @@ import { takeUntil } from 'rxjs/operators'; import { IgxTemplateOutletDirective } from '../../directives/template-outlet/template_outlet.directive'; import { IgxGridSelectionService } from '../selection/selection.service'; import { IgxForOfSyncService, IgxForOfScrollSyncService } from '../../directives/for-of/for_of.sync.service'; -import { CellType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface'; +import { CellType, EntityType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface'; import { IgxRowIslandAPIService } from './row-island-api.service'; import { IgxGridCRUDService } from '../common/crud.service'; import { IgxHierarchicalGridRow } from '../grid-public-row'; @@ -561,6 +561,26 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti return this._defaultExpandState; } + /** + * Gets/Sets the entities used for advanced filtering. + * + * @remarks + * This property is required in remote data scenarios. + * @example + * ```typescript + * const remoteEntities = this.grid.remoteEntities; + * this.grid.remoteEntities = []; + * ``` + */ + @Input() + public set remoteEntities(entities: EntityType[]) { + this._hGridRemoteEntities = entities; + } + + public get remoteEntities() { + return this._hGridRemoteEntities; + } + /** * Gets the unique identifier of the parent row. It may be a `string` or `number` if `primaryKey` of the * parent grid is set or an object reference of the parent record otherwise. diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index 0a1dca77b4f..207d4ec5146 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -7,7 +7,7 @@ -
+
From + Customers diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index 8e73cfc27d7..a77e421b1f4 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -5,7 +5,8 @@ import { IGridCreatedEventArgs, IGX_HIERARCHICAL_GRID_DIRECTIVES, FilteringExpressionsTree, - IgxStringFilteringOperand + IgxStringFilteringOperand, + EntityType } from 'igniteui-angular'; import { HttpClient } from '@angular/common/http'; @@ -28,35 +29,77 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { { name: 'Orders', type: 'number', level: 1 }, { name: 'Details', type: 'number', level: 2 } ]; + public remoteEntities: EntityType[] = [ + { + name: 'Customers', + fields: [ + { field: 'customerId', dataType: 'string' }, + { field: 'companyName', dataType: 'string' }, + { field: 'contactName', dataType: 'string' }, + { field: 'contactTitle', dataType: 'string' } + ], + childEntities: [ + { + name: 'Orders', + fields: [ + { field: 'orderId', dataType: 'number' }, + { field: 'customerId', dataType: 'string' }, + { field: 'employeeId', dataType: 'number' }, + { field: 'shipVia', dataType: 'string' } + ], + childEntities: [ + { + name: 'Details', + fields: [ + { field: 'orderId', dataType: 'number' }, + { field: 'productId', dataType: 'number' }, + { field: 'unitPrice', dataType: 'number' }, + { field: 'quantity', dataType: 'number' }, + { field: 'discount', dataType: 'number' } + ] + } + ] + } + ] + } + ]; constructor(private http: HttpClient, private cdr: ChangeDetectorRef) {} public ngOnInit() { - const ordersTree = new FilteringExpressionsTree(0, undefined, 'Orders', ['shipVia']); + const ordersTree = new FilteringExpressionsTree(0, undefined, 'Orders', ['customerId']); ordersTree.filteringOperands.push({ fieldName: 'shipVia', ignoreCase: false, + condition: IgxStringFilteringOperand.instance().condition('equals'), conditionName: IgxStringFilteringOperand.instance().condition('equals').name, searchVal: 'AirCargo' }); const customersTree = new FilteringExpressionsTree(0, undefined, 'Customers', ['customerId', 'companyName', 'contactName', 'contactTitle']); - // customersTree.filteringOperands.push({ - // fieldName: 'customerId', - // conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, - // ignoreCase: false, - // searchTree: ordersTree - // }); customersTree.filteringOperands.push({ fieldName: 'customerId', + condition: IgxStringFilteringOperand.instance().condition('notInQuery'), + conditionName: IgxStringFilteringOperand.instance().condition('notInQuery').name, ignoreCase: false, - conditionName: IgxStringFilteringOperand.instance().condition('startsWith').name, - searchVal: 'A' + searchTree: ordersTree }); + // customersTree.filteringOperands.push({ + // fieldName: 'customerId', + // ignoreCase: false, + // conditionName: IgxStringFilteringOperand.instance().condition('startsWith').name, + // searchVal: 'A' + // }); this.hGrid.advancedFilteringExpressionsTree = customersTree; } public ngAfterViewInit() { + this.advancedFilteringExprTreeChange(); + } + + public advancedFilteringExprTreeChange() { + if (!this.hGrid.advancedFilteringExpressionsTree) return; + this.hGrid.isLoading = true; this.http.post(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, this.hGrid.advancedFilteringExpressionsTree).subscribe(data =>{ console.log('data', data); From becee5a2f9fc557beb7f1e80e008edfc1707080b Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Wed, 9 Apr 2025 16:16:47 +0300 Subject: [PATCH 15/49] chore(*): fix sample when filtering is cleared --- .../hierarchical-grid-remote.sample.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index a77e421b1f4..551b0964fc0 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -98,10 +98,13 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { } public advancedFilteringExprTreeChange() { - if (!this.hGrid.advancedFilteringExpressionsTree) return; + let tree = this.hGrid.advancedFilteringExpressionsTree; + if (!tree) { + tree = new FilteringExpressionsTree(0, undefined, this.remoteEntities[0].name, this.remoteEntities[0].fields.map(f => f.field)); + } this.hGrid.isLoading = true; - this.http.post(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, this.hGrid.advancedFilteringExpressionsTree).subscribe(data =>{ + this.http.post(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, tree).subscribe(data =>{ console.log('data', data); this.remoteData = Object.values(data)[0]; this.hGrid.isLoading = false; From 87fc940b02db54eb04946d986f1a811882bb1d39 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Thu, 10 Apr 2025 10:50:15 +0300 Subject: [PATCH 16/49] feat(h-grid): set filtering entities and fix rehydration --- .../data-operations/expressions-tree-util.ts | 4 +- .../advanced-filtering-dialog.component.ts | 89 ++++--------------- .../src/lib/grids/grid-base.directive.ts | 6 +- .../hierarchical-grid.component.ts | 80 +++++++++++++++-- ...archical-grid-advanced-filtering.sample.ts | 2 - .../hierarchical-grid-remote.sample.html | 2 +- .../hierarchical-grid-remote.sample.ts | 6 +- 7 files changed, 104 insertions(+), 85 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts b/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts index 2c4c5a4771e..365f349d603 100644 --- a/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts @@ -178,8 +178,8 @@ export function isTree(entry: IExpressionTree | IFilteringExpression): entry is * @param entities An array of entities to use for recreating the tree. * @returns The recreated expression tree. */ -export function recreateTree(tree: IExpressionTree, entities: EntityType[]): IExpressionTree { - const entity = entities.find(e => e.name === tree.entity); +export function recreateTree(tree: IExpressionTree, entities: EntityType[], isRoot: boolean = false): IExpressionTree { + const entity = isRoot ? entities[0] : entities.find(e => e.name === tree.entity); if (!entity) return tree; for (let i = 0; i < tree.filteringOperands.length; i++) { diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index 189200837bd..7c75fe87e53 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -193,83 +193,32 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes this.closeDialog(); } - /** * @hidden @internal */ public generateEntity() { if (this.queryBuilder?.entities) { return this.queryBuilder?.entities; - } - - const isHierarchicalGrid = this.grid.type === 'hierarchical'; - const entities: EntityType[] = [ - { - name: null, - fields: this.filterableFields.map(f => ({ - field: f.field, - dataType: f.dataType, - // label: f.label, - // header: f.header, - editorOptions: f.editorOptions, - filters: f.filters, - pipeArgs: f.pipeArgs, - defaultTimeFormat: f.defaultTimeFormat, - defaultDateTimeFormat: f.defaultDateTimeFormat - })) as FieldType[] - } - ]; - - if (isHierarchicalGrid) { - const hierarchicalGrid = this.grid as IgxHierarchicalGridComponent; - if (hierarchicalGrid.remoteEntities) { - return hierarchicalGrid.remoteEntities; - } - - entities[0].childEntities = hierarchicalGrid.childLayoutList.reduce((acc, rowIsland) => { - return acc.concat(this.generateChildEntity(rowIsland, hierarchicalGrid.data[0][rowIsland.key][0])); - } - , []); - } - - return entities; - } - - private generateChildEntity(rowIsland: IgxRowIslandComponent, firstRowData: any[]): EntityType { - const entityName = rowIsland.key; - let fields = []; - let childEntities; - if (!rowIsland.autoGenerate) { - fields = rowIsland.columnList.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[]; - } else if (firstRowData) { - const rowIslandFields = Object.keys(firstRowData).map(key => { - if (firstRowData[key] instanceof Array) { - return null; - } - - return { - field: key, - dataType: (this.grid as IgxHierarchicalGridComponent).resolveDataTypes(firstRowData[key]) + } else if (this.grid.type === 'hierarchical') { + return (this.grid as IgxHierarchicalGridComponent).filteringEntities; + } else { + const entities: EntityType[] = [ + { + name: null, + fields: this.filterableFields.map(f => ({ + field: f.field, + dataType: f.dataType, + // label: f.label, + // header: f.header, + editorOptions: f.editorOptions, + filters: f.filters, + pipeArgs: f.pipeArgs, + defaultTimeFormat: f.defaultTimeFormat, + defaultDateTimeFormat: f.defaultDateTimeFormat + })) as FieldType[] } - }); - fields = rowIslandFields.filter(f => f !== null) as FieldType[]; - } - - const rowIslandChildEntities = rowIsland.childLayoutList.reduce((acc, childRowIsland) => { - if (!firstRowData) { - return null; - } - return acc.concat(this.generateChildEntity(childRowIsland, firstRowData[childRowIsland.key][0])); - }, []); - - if (rowIslandChildEntities?.length > 0) { - childEntities = rowIslandChildEntities; - } - - return { - name: entityName, - fields: fields, - childEntities: childEntities + ]; + return entities; } } diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index be51a2d73f2..125bb101428 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3139,7 +3139,7 @@ export abstract class IgxGridBaseDirective implements GridType, matchCount: 0, content: '' }; - protected _hGridRemoteEntities: EntityType[]; + protected _hGridFilteringEntities: EntityType[]; protected gridComputedStyles; /** @hidden @internal */ @@ -7911,8 +7911,8 @@ export abstract class IgxGridBaseDirective implements GridType, } private getRecreatedTree(value: IFilteringExpressionsTree): IFilteringExpressionsTree { - if (this._hGridRemoteEntities) { - return recreateTree(value, this._hGridRemoteEntities) as IFilteringExpressionsTree; + if (this._hGridFilteringEntities) { + return recreateTree(value, this._hGridFilteringEntities, true) as IFilteringExpressionsTree; } else { return recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index e8d1ce80478..caddec2ac71 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -35,7 +35,7 @@ import { takeUntil } from 'rxjs/operators'; import { IgxTemplateOutletDirective } from '../../directives/template-outlet/template_outlet.directive'; import { IgxGridSelectionService } from '../selection/selection.service'; import { IgxForOfSyncService, IgxForOfScrollSyncService } from '../../directives/for-of/for_of.sync.service'; -import { CellType, EntityType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface'; +import { CellType, EntityType, FieldType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface'; import { IgxRowIslandAPIService } from './row-island-api.service'; import { IgxGridCRUDService } from '../common/crud.service'; import { IgxHierarchicalGridRow } from '../grid-public-row'; @@ -573,12 +573,12 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti * ``` */ @Input() - public set remoteEntities(entities: EntityType[]) { - this._hGridRemoteEntities = entities; + public set filteringEntities(entities: EntityType[]) { + this._hGridFilteringEntities = entities; } - public get remoteEntities() { - return this._hGridRemoteEntities; + public get filteringEntities() { + return this._hGridFilteringEntities; } /** @@ -708,6 +708,11 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti this.rootGrid.hasChildrenKey; this.showExpandAll = this.parentIsland ? this.parentIsland.showExpandAll : this.rootGrid.showExpandAll; + + if (!this._hGridFilteringEntities) { + this._hGridFilteringEntities = this.generateFilteringEntities(); + } + } /** @@ -1211,4 +1216,69 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti grid.cdr.markForCheck(); }); } + + private generateFilteringEntities() { + const filterableFields = this.columns.filter((column) => !column.columnGroup && column.filterable); + const entities: EntityType[] = [ + { + name: null, + fields: filterableFields.map(f => ({ + field: f.field, + dataType: f.dataType, + // label: f.label, + // header: f.header, + editorOptions: f.editorOptions, + filters: f.filters, + pipeArgs: f.pipeArgs, + defaultTimeFormat: f.defaultTimeFormat, + defaultDateTimeFormat: f.defaultDateTimeFormat + })) as FieldType[] + } + ]; + + entities[0].childEntities = this.childLayoutList.reduce((acc, rowIsland) => { + return acc.concat(this.generateChildEntity(rowIsland, this.data[0][rowIsland.key][0])); + } + , []); + + return entities; + } + + private generateChildEntity(rowIsland: IgxRowIslandComponent, firstRowData: any[]): EntityType { + const entityName = rowIsland.key; + let fields = []; + let childEntities; + if (!rowIsland.autoGenerate) { + fields = rowIsland.columnList.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[]; + } else if (firstRowData) { + const rowIslandFields = Object.keys(firstRowData).map(key => { + if (firstRowData[key] instanceof Array) { + return null; + } + + return { + field: key, + dataType: this.resolveDataTypes(firstRowData[key]) + } + }); + fields = rowIslandFields.filter(f => f !== null) as FieldType[]; + } + + const rowIslandChildEntities = rowIsland.childLayoutList.reduce((acc, childRowIsland) => { + if (!firstRowData) { + return null; + } + return acc.concat(this.generateChildEntity(childRowIsland, firstRowData[childRowIsland.key][0])); + }, []); + + if (rowIslandChildEntities?.length > 0) { + childEntities = rowIslandChildEntities; + } + + return { + name: entityName, + fields: fields, + childEntities: childEntities + } + } } diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts index 5b4d4fd6013..e61c42ea0a9 100644 --- a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts @@ -30,14 +30,12 @@ export class HierarchicalGridAdvancedFilteringSampleComponent implements AfterVi const innerTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Albums', ['Artist']); innerTree.filteringOperands.push({ fieldName: 'USBillboard200', - condition: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo'), conditionName: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo').name, searchVal: 5 }); const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Artists', ['*']); tree.filteringOperands.push({ fieldName: 'Artist', - condition: IgxStringFilteringOperand.instance().condition('inQuery'), conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, searchTree: innerTree }); diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html index 2f83716754f..1488c510a66 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html @@ -6,7 +6,7 @@ [height]="'800px'" [width]="'100%'" [allowAdvancedFiltering]="true" - [remoteEntities]="remoteEntities" + [filteringEntities]="remoteEntities" (advancedFilteringExpressionsTreeChange)="advancedFilteringExprTreeChange()"> Customers diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index 551b0964fc0..18c947b8ce0 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -114,8 +114,10 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { } private calculateColsInView() { - this.hGrid.columns.forEach(column => - column.hidden = !this.hGrid.advancedFilteringExpressionsTree.returnFields.includes(column.field)); + if (this.hGrid.advancedFilteringExpressionsTree) { + this.hGrid.columns.forEach(column => + column.hidden = !this.hGrid.advancedFilteringExpressionsTree.returnFields.includes(column.field)); + } } public gridCreated(event: IGridCreatedEventArgs, rowIsland: IgxRowIslandComponent) { From 1266803cea0c5a41b3cbf2b7c53cb17a32dfc85c Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Thu, 10 Apr 2025 16:35:10 +0300 Subject: [PATCH 17/49] feat(h-grid): prop rename and return first field if returnFields is empty --- .../advanced-filtering-dialog.component.ts | 2 +- .../src/lib/grids/grid-base.directive.ts | 6 +++--- .../hierarchical-grid.component.ts | 18 +++++++++--------- .../query-builder-tree.component.ts | 5 ++++- ...rchical-grid-advanced-filtering.sample.html | 6 +++--- .../hierarchical-grid-remote.sample.html | 12 +++++------- .../hierarchical-grid-remote.sample.ts | 12 ++++++------ 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts index 7c75fe87e53..e98253b688d 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/advanced-filtering/advanced-filtering-dialog.component.ts @@ -200,7 +200,7 @@ export class IgxAdvancedFilteringDialogComponent implements AfterViewInit, OnDes if (this.queryBuilder?.entities) { return this.queryBuilder?.entities; } else if (this.grid.type === 'hierarchical') { - return (this.grid as IgxHierarchicalGridComponent).filteringEntities; + return (this.grid as IgxHierarchicalGridComponent).schema; } else { const entities: EntityType[] = [ { diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 5fc595d452a..42abc796055 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3139,7 +3139,7 @@ export abstract class IgxGridBaseDirective implements GridType, matchCount: 0, content: '' }; - protected _hGridFilteringEntities: EntityType[]; + protected _hGridSchema: EntityType[]; protected gridComputedStyles; /** @hidden @internal */ @@ -7926,8 +7926,8 @@ export abstract class IgxGridBaseDirective implements GridType, } private getRecreatedTree(value: IFilteringExpressionsTree): IFilteringExpressionsTree { - if (this._hGridFilteringEntities) { - return recreateTree(value, this._hGridFilteringEntities, true) as IFilteringExpressionsTree; + if (this._hGridSchema) { + return recreateTree(value, this._hGridSchema, true) as IFilteringExpressionsTree; } else { return recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index caddec2ac71..ed1690efdba 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -568,17 +568,17 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti * This property is required in remote data scenarios. * @example * ```typescript - * const remoteEntities = this.grid.remoteEntities; - * this.grid.remoteEntities = []; + * const schema = this.grid.schema; + * this.grid.schema = []; * ``` */ @Input() - public set filteringEntities(entities: EntityType[]) { - this._hGridFilteringEntities = entities; + public set schema(entities: EntityType[]) { + this._hGridSchema = entities; } - public get filteringEntities() { - return this._hGridFilteringEntities; + public get schema() { + return this._hGridSchema; } /** @@ -709,8 +709,8 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti this.showExpandAll = this.parentIsland ? this.parentIsland.showExpandAll : this.rootGrid.showExpandAll; - if (!this._hGridFilteringEntities) { - this._hGridFilteringEntities = this.generateFilteringEntities(); + if (!this._hGridSchema) { + this._hGridSchema = this.generateSchema(); } } @@ -1217,7 +1217,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti }); } - private generateFilteringEntities() { + private generateSchema() { const filterableFields = this.columns.filter((column) => !column.columnGroup && column.filterable); const entities: EntityType[] = [ { diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 6dcd11ee807..53ac89af2e9 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -834,7 +834,10 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) { innerQuery.exitEditAddMode(); this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree); - this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields; + const returnFields = innerQuery.selectedReturnFields.length > 0 ? + innerQuery.selectedReturnFields : + [innerQuery.fields[0].field]; + this._editedExpression.expression.searchTree.returnFields = returnFields; } else { this._editedExpression.expression.searchTree = null; } diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.html b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.html index e3a39417b44..b7fe0a8600a 100644 --- a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.html +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.html @@ -3,10 +3,10 @@

Sample One

- - + + diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html index 1488c510a66..5a35a2e9c60 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html @@ -6,17 +6,15 @@ [height]="'800px'" [width]="'100%'" [allowAdvancedFiltering]="true" - [filteringEntities]="remoteEntities" + [schema]="remoteEntities" (advancedFilteringExpressionsTreeChange)="advancedFilteringExprTreeChange()"> Customers - + Orders diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index 18c947b8ce0..fc4d1aab917 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -25,9 +25,9 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { public selectionMode; public remoteData = []; public primaryKeys = [ - { name: 'Customers', type: 'string', level: 0 }, - { name: 'Orders', type: 'number', level: 1 }, - { name: 'Details', type: 'number', level: 2 } + { name: 'Customers', key: 'customerId' }, + { name: 'Orders', key: 'orderId' }, + { name: 'Details', key: 'orderId' } ]; public remoteEntities: EntityType[] = [ { @@ -42,8 +42,8 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { { name: 'Orders', fields: [ + { field: 'customerId', dataType: 'string' }, // first field will be treated as foreign key { field: 'orderId', dataType: 'number' }, - { field: 'customerId', dataType: 'string' }, { field: 'employeeId', dataType: 'number' }, { field: 'shipVia', dataType: 'string' } ], @@ -51,7 +51,7 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { { name: 'Details', fields: [ - { field: 'orderId', dataType: 'number' }, + { field: 'orderId', dataType: 'number' }, // first field will be treated as foreign key { field: 'productId', dataType: 'number' }, { field: 'unitPrice', dataType: 'number' }, { field: 'quantity', dataType: 'number' }, @@ -132,7 +132,7 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { } private buildUrl(event: IGridCreatedEventArgs, rowIsland: IgxRowIslandComponent) { - const rowIslandKey = this.primaryKeys.find(key => key.level === rowIsland.level).name; + const rowIslandKey = this.primaryKeys.find(key => key.name === rowIsland.key).name; const parentKey = (event.grid.parent as any).key ?? event.grid.parent.advancedFilteringExpressionsTree.entity; const url = `${API_ENDPOINT}/${parentKey}/${event.parentID}/${rowIslandKey}`; return url; From 76129481805d50ab362c294b7d4daf7b313f1663 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Thu, 10 Apr 2025 19:46:05 +0300 Subject: [PATCH 18/49] feat(h-grid): add fields for child entities and not show select entity if nested --- .../data-operations/filtering-condition.ts | 7 ++++ .../query-builder-tree.component.html | 6 +-- .../query-builder-tree.component.ts | 40 ++++++++++++++++--- .../query-builder/query-builder.component.ts | 18 +++++++++ ...archical-grid-advanced-filtering.sample.ts | 4 +- .../hierarchical-grid-remote.sample.ts | 2 +- 6 files changed, 66 insertions(+), 11 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts index edf766bffcc..46f412eedf9 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts @@ -50,6 +50,13 @@ export class IgxFilteringOperand { public conditionList(): string[] { return this.operations.filter(f => !f.hidden && !f.isNestedQuery).map((element) => element.name); } + + /** + * Returns "In" and "Not In" conditions + */ + public nestedConditionList(): string[] { + return this.operations.filter(f => !f.hidden && f.isNestedQuery).map((element) => element.name); + } /** * Returns an array of names of the conditions which are visible in the UI, including "In" and "Not In", allowing the creation of sub-queries. diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index 207d4ec5146..e1057a114e4 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -7,7 +7,7 @@ -
+
From } - @else if (!this.isAdvancedFiltering() && !this.isHierarchicalGridNestedQuery()) { + @else { e.childEntities?.length > 0) !== undefined; + this.entities.find(e => e.childEntities?.length > 0) !== undefined || + this.entities !== this.queryBuilder.entities; } /** @hidden */ @@ -555,6 +556,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { this.addExpressionDropDownOverlaySettings.outlet = this.overlayOutlet; this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet; + console.log('ngAfterViewInit', this._expressionTree); if (this.isAdvancedFiltering() && this.entities?.length === 1) { this._selectedEntity = this.entities[0]; } @@ -575,6 +577,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public set selectedEntity(value: string) { + console.log('selectedEntity', value); this._selectedEntity = this.entities?.find(el => el.name === value); } @@ -691,6 +694,10 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { if (this._selectedField !== value) { this._selectedField = value; + if (this._selectedField && !this._selectedField.dataType) { + this._selectedField.filters = this.getFilters(this._selectedField); + } + this.selectDefaultCondition(); if (oldValue && this._selectedField && this._selectedField.dataType !== oldValue.dataType) { this.searchValue.value = null; @@ -1368,9 +1375,16 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { public getConditionList(): string[] { if (!this.selectedField) return []; - if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) || - (this.isHierarchicalGridNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) { - return this.selectedField.filters.conditionList(); + if (!this.selectedField.filters) { + this.selectedField.filters = this.getFilters(this.selectedField); + } + + if (this.isAdvancedFiltering()) { + if (!this.selectedField.dataType) { // field was generated for child entity + return this.selectedField.filters.nestedConditionList(); + } else { + return this.selectedField.filters.conditionList(); + } } return this.selectedField.filters.extendedConditionList(); @@ -1436,6 +1450,16 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } } + public determineEntities(): EntityType[] { + // TODO: FIX, not working correctly for every scenario + + if (this.entities.length === 1 && this.entities[0].childEntities && this.selectedField) { + return this.entities[0].childEntities.filter(e => e.name === this.selectedField.field); + } + + return (this.selectedEntity ? this.selectedEntity.childEntities : this.entities[0].childEntities) ?? this.entities; + } + public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree { if (!expressionTree) { return null; @@ -1512,8 +1536,12 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } private selectDefaultCondition() { - if (this.selectedField && this.selectedField.filters) { - this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0]; + if (this.selectedField) { + if (!this.selectedField.dataType) { + this.selectedCondition = 'inQuery'; + } else { + this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0]; + } } } diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts index 2f101085378..50d4032613b 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts @@ -64,6 +64,10 @@ export class IgxQueryBuilderComponent implements OnDestroy { */ @Input() public set entities(entities: EntityType[]) { + if (entities) { + this.updateEntities(entities) // add field for every child entity recursively + } + if (entities !== this._entities) { if (entities && this.expressionTree) { this._expressionTree = recreateTree(this._expressionTree, entities); @@ -270,6 +274,20 @@ export class IgxQueryBuilderComponent implements OnDestroy { } } + private updateEntities(entities: EntityType[]) { + entities.forEach((entity) => { + if (entity.childEntities) { + entity.childEntities.forEach((childEntity) => { + if (entity.fields.find((f) => f.field === childEntity.name)) { + return; + } + entity.fields.push({ field: childEntity.name, dataType: null }); + }); + this.updateEntities(entity.childEntities); + } + }); + } + private registerSVGIcons(): void { const editorIcons = editor as any[]; diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts index e61c42ea0a9..99910728ca3 100644 --- a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts @@ -30,12 +30,14 @@ export class HierarchicalGridAdvancedFilteringSampleComponent implements AfterVi const innerTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Albums', ['Artist']); innerTree.filteringOperands.push({ fieldName: 'USBillboard200', + condition: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo'), conditionName: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo').name, searchVal: 5 }); const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Artists', ['*']); tree.filteringOperands.push({ - fieldName: 'Artist', + fieldName: 'Albums', + condition: IgxStringFilteringOperand.instance().condition('inQuery'), conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, searchTree: innerTree }); diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index fc4d1aab917..1fa5faa2521 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -78,7 +78,7 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { const customersTree = new FilteringExpressionsTree(0, undefined, 'Customers', ['customerId', 'companyName', 'contactName', 'contactTitle']); customersTree.filteringOperands.push({ - fieldName: 'customerId', + fieldName: 'Orders', condition: IgxStringFilteringOperand.instance().condition('notInQuery'), conditionName: IgxStringFilteringOperand.instance().condition('notInQuery').name, ignoreCase: false, From a4b4c74a7309f67f6017bc8a7d22a814a1b03fde Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Thu, 10 Apr 2025 19:50:07 +0300 Subject: [PATCH 19/49] chore(*): revert filed name change --- .../hierarchical-grid-remote/hierarchical-grid-remote.sample.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index 1fa5faa2521..fc4d1aab917 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -78,7 +78,7 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { const customersTree = new FilteringExpressionsTree(0, undefined, 'Customers', ['customerId', 'companyName', 'contactName', 'contactTitle']); customersTree.filteringOperands.push({ - fieldName: 'Orders', + fieldName: 'customerId', condition: IgxStringFilteringOperand.instance().condition('notInQuery'), conditionName: IgxStringFilteringOperand.instance().condition('notInQuery').name, ignoreCase: false, From 4beb821a6c5b6a41432caf2c6dbaf703b2d0b59b Mon Sep 17 00:00:00 2001 From: teodosiah Date: Fri, 11 Apr 2025 09:48:43 +0300 Subject: [PATCH 20/49] test(h-grid): fix childData and undefined errors in tests --- .../grid/grid-filtering-advanced.spec.ts | 44 +++++++++++++++++++ .../hierarchical-grid.component.ts | 43 ++++++++++-------- .../query-builder-tree.component.ts | 2 +- .../hierarchical-grid-remote.sample.ts | 6 +-- 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 30bc6dc2058..fd835f6935d 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -28,6 +28,7 @@ import { QueryBuilderFunctions } from '../../query-builder/query-builder-functio import { By } from '@angular/platform-browser'; import { IgxDateTimeEditorDirective } from '../../directives/date-time-editor/date-time-editor.directive'; import { QueryBuilderSelectors } from '../../query-builder/query-builder.common'; +import { IgxHGridRemoteOnDemandComponent } from '../hierarchical-grid/hierarchical-grid.spec'; describe('IgxGrid - Advanced Filtering #grid - ', () => { configureTestSuite((() => { @@ -1602,6 +1603,10 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { tick(100); fix.detectChanges(); + QueryBuilderFunctions.clickQueryBuilderExpressionCommitButton(fix, 1); + fix.detectChanges(); + QueryBuilderFunctions.clickQueryBuilderExpressionCommitButton(fix, 0); + fix.detectChanges(); // Close Advanced Filtering dialog. hgrid.closeAdvancedFilteringDialog(true); tick(200); @@ -1611,6 +1616,45 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { expect(hgrid.filteredData.length).toEqual(5); expect(hgrid.rowList.length).toBe(5); })); + + it('Should correctly apply filtering expressions tree to the hgrid component through API.', fakeAsync(() => { + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(false); + tick(200); + fix.detectChanges(); + // Spy for error messages in the console + const consoleSpy = spyOn(console, 'error'); + // Apply advanced filter through API. + const innerTree = new FilteringExpressionsTree(0, undefined, 'childData', ['ID']); + innerTree.filteringOperands.push({ + fieldName: 'ID', + ignoreCase: false, + conditionName: IgxStringFilteringOperand.instance().condition('contains').name, + searchVal: '1' + }); + + const tree = new FilteringExpressionsTree(0, undefined, 'rootData', ['ID']); + tree.filteringOperands.push({ + fieldName: 'ID', + conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, + ignoreCase: false, + searchTree: innerTree + }); + tree.filteringOperands.push(innerTree); + hgrid.advancedFilteringExpressionsTree = tree; + fix.detectChanges(); + + // Check for error messages in the console + expect(consoleSpy).not.toHaveBeenCalled(); + })); + + // fit('Should be able to filter hierarchicaly with load on demand through API.', fakeAsync(() => { + // const fixture = TestBed.createComponent(IgxHGridRemoteOnDemandComponent); + // const hierarchicalGrid = fixture.componentInstance.hgrid; + // hierarchicalGrid.allowAdvancedFiltering = true; + // fixture.detectChanges(); + + // })); }); }); diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index ed1690efdba..263e3f59b9c 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -1219,27 +1219,32 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti private generateSchema() { const filterableFields = this.columns.filter((column) => !column.columnGroup && column.filterable); - const entities: EntityType[] = [ - { - name: null, - fields: filterableFields.map(f => ({ - field: f.field, - dataType: f.dataType, - // label: f.label, - // header: f.header, - editorOptions: f.editorOptions, - filters: f.filters, - pipeArgs: f.pipeArgs, - defaultTimeFormat: f.defaultTimeFormat, - defaultDateTimeFormat: f.defaultDateTimeFormat - })) as FieldType[] - } - ]; + let entities: EntityType[]; + + if(filterableFields.length !== 0) { + entities = [ + { + name: null, + fields: filterableFields.map(f => ({ + field: f.field, + dataType: f.dataType, + // label: f.label, + // header: f.header, + editorOptions: f.editorOptions, + filters: f.filters, + pipeArgs: f.pipeArgs, + defaultTimeFormat: f.defaultTimeFormat, + defaultDateTimeFormat: f.defaultDateTimeFormat + })) as FieldType[] + } + ]; - entities[0].childEntities = this.childLayoutList.reduce((acc, rowIsland) => { - return acc.concat(this.generateChildEntity(rowIsland, this.data[0][rowIsland.key][0])); + entities[0].childEntities = this.childLayoutList.reduce((acc, rowIsland) => { + return acc.concat(this.generateChildEntity(rowIsland, this.data[0][rowIsland.key][0])); + } + , []); + } - , []); return entities; } diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 12f4a11e1a2..3c12c86e8e1 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -517,7 +517,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { /** @hidden */ protected isAdvancedFiltering(): boolean { return (this.entities?.length === 1 && !this.entities[0]?.name) || - this.entities.find(e => e.childEntities?.length > 0) !== undefined || + this.entities?.find(e => e.childEntities?.length > 0) !== undefined || this.entities !== this.queryBuilder.entities; } diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index fc4d1aab917..ea6298a3b3f 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Component, ViewChild, OnInit, ChangeDetectorRef, AfterViewInit } from '@angular/core'; import { IgxRowIslandComponent, IgxHierarchicalGridComponent, @@ -18,7 +18,7 @@ const API_ENDPOINT = 'https://data-northwind.indigo.design'; styleUrls: ['hierarchical-grid-remote.sample.scss'], imports: [IGX_HIERARCHICAL_GRID_DIRECTIVES] }) -export class HierarchicalGridRemoteSampleComponent implements OnInit { +export class HierarchicalGridRemoteSampleComponent implements OnInit, AfterViewInit { @ViewChild('hGrid', { static: true }) private hGrid: IgxHierarchicalGridComponent; @@ -71,7 +71,6 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { ordersTree.filteringOperands.push({ fieldName: 'shipVia', ignoreCase: false, - condition: IgxStringFilteringOperand.instance().condition('equals'), conditionName: IgxStringFilteringOperand.instance().condition('equals').name, searchVal: 'AirCargo' }); @@ -79,7 +78,6 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit { const customersTree = new FilteringExpressionsTree(0, undefined, 'Customers', ['customerId', 'companyName', 'contactName', 'contactTitle']); customersTree.filteringOperands.push({ fieldName: 'customerId', - condition: IgxStringFilteringOperand.instance().condition('notInQuery'), conditionName: IgxStringFilteringOperand.instance().condition('notInQuery').name, ignoreCase: false, searchTree: ordersTree From f96f3499f7adcaf238bf138412a040d87d83079b Mon Sep 17 00:00:00 2001 From: teodosiah Date: Fri, 11 Apr 2025 10:33:22 +0300 Subject: [PATCH 21/49] test(h-grid): fix test failing due to UI changes --- .../grid/grid-filtering-advanced.spec.ts | 55 +++++-------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index fd835f6935d..18ff5138ff4 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1520,7 +1520,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fix.detectChanges(); })); - it(`Should have 'In'/'Not-In' operators for fields with chilld entities.`, fakeAsync(() => { + it(`Should NOT have 'In'/'Not-In' operators for not chilld entities fields.`, fakeAsync(() => { // Populate edit inputs. QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. @@ -1530,7 +1530,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[0].nativeElement; const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); const expectedValues = ['Contains', 'Does Not Contain', 'Starts With', 'Ends With', 'Equals', - 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null', 'In', 'Not In'];; + 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null'];; expect(dropdownValues).toEqual(expectedValues); // Close Advanced Filtering dialog. @@ -1539,60 +1539,33 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fix.detectChanges(); })); - it(`Should NOT have 'In'/'Not-In' operators for fields without chilld entities.`, fakeAsync(() => { + it(`Should have 'In'/'Not-In' operators for chilld entities fields.`, fakeAsync(() => { // Populate edit inputs. - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. - QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. - - // Select entity in nested level - QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); - // Populate edit inputs on level 1. - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. - QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 11, 1); // Select 'Not In' operator. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 3); // Select 'Child Data'. - // Select entity in nested level - QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 2); - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 2); // Select 'ID' column. - // Open the operator dropdown and verify they are 'string' specific + 'In'/'Not In'. - QueryBuilderFunctions.clickQueryBuilderOperatorSelect(fix, 2); + // Open the operator dropdown and verify they are only 'In'/'Not In'. + QueryBuilderFunctions.clickQueryBuilderOperatorSelect(fix); fix.detectChanges(); - const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[2].nativeElement; + const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[0].nativeElement; const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); - const expectedValues = ['Contains', 'Does Not Contain', 'Starts With', 'Ends With', 'Equals', - 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null'];; + const expectedValues = ['In', 'Not In'];; expect(dropdownValues).toEqual(expectedValues); // Close Advanced Filtering dialog. hgrid.closeAdvancedFilteringDialog(false); tick(200); - fix.detectChanges(); - })); - - it('Should have correct entities depending on the hierarchy level.', fakeAsync(() => { - // Populate edit inputs. - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. - QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. - - QueryBuilderFunctions.clickQueryBuilderEntitySelect(fix, 1); fix.detectChanges(); - const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[1].nativeElement; - const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); - const expectedValues = ['childData']; - expect(dropdownValues).toEqual(expectedValues); - - // Close Advanced Filtering dialog. - hgrid.closeAdvancedFilteringDialog(false); - tick(200); - fix.detectChanges(); })); it(`Should apply 'In'/'Not-In' operators for each level properly.`, fakeAsync(() => { // Populate edit inputs. - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. - QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 3); // Select 'childData' entity. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0); // Select 'In' operator. - // Select entity in nested level - QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); + // Click the initial 'Add Condition' button. + QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, 1); + tick(100); + fix.detectChanges(); // Populate edit inputs on level 1. QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0, 1); // Select 'Contains' operator. From 9e6e4218db226ed439322b63ff562337b385d3c2 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Fri, 11 Apr 2025 11:13:35 +0300 Subject: [PATCH 22/49] test(h-grid): fix failing QB tests --- .../src/lib/query-builder/query-builder-tree.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 3c12c86e8e1..076478e512b 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -518,7 +518,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { protected isAdvancedFiltering(): boolean { return (this.entities?.length === 1 && !this.entities[0]?.name) || this.entities?.find(e => e.childEntities?.length > 0) !== undefined || - this.entities !== this.queryBuilder.entities; + this.entities !== this.queryBuilder?.entities; } /** @hidden */ From 04e5711654eab35e7265199ac64ca0f0ac859c87 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Fri, 11 Apr 2025 14:54:09 +0300 Subject: [PATCH 23/49] feat(h-grid): revert creating fields for each child entity --- .../data-operations/filtering-condition.ts | 7 ----- .../query-builder-tree.component.html | 4 +-- .../query-builder-tree.component.ts | 29 ++++--------------- .../query-builder/query-builder.component.ts | 18 ------------ ...archical-grid-advanced-filtering.sample.ts | 2 +- .../hierarchical-grid-remote.sample.html | 1 + .../hierarchical-grid-remote.sample.ts | 18 ++++++++---- 7 files changed, 21 insertions(+), 58 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts index 46f412eedf9..57f3a8d06fb 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts @@ -51,13 +51,6 @@ export class IgxFilteringOperand { return this.operations.filter(f => !f.hidden && !f.isNestedQuery).map((element) => element.name); } - /** - * Returns "In" and "Not In" conditions - */ - public nestedConditionList(): string[] { - return this.operations.filter(f => !f.hidden && f.isNestedQuery).map((element) => element.name); - } - /** * Returns an array of names of the conditions which are visible in the UI, including "In" and "Not In", allowing the creation of sub-queries. * @hidden @internal diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index e1057a114e4..460e1ae4e6c 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -7,7 +7,7 @@ -
+
From el.name === value); } @@ -1379,12 +1377,9 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { this.selectedField.filters = this.getFilters(this.selectedField); } - if (this.isAdvancedFiltering()) { - if (!this.selectedField.dataType) { // field was generated for child entity - return this.selectedField.filters.nestedConditionList(); - } else { - return this.selectedField.filters.conditionList(); - } + if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) || + (this.isHierarchicalGridNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) { + return this.selectedField.filters.conditionList(); } return this.selectedField.filters.extendedConditionList(); @@ -1450,16 +1445,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } } - public determineEntities(): EntityType[] { - // TODO: FIX, not working correctly for every scenario - - if (this.entities.length === 1 && this.entities[0].childEntities && this.selectedField) { - return this.entities[0].childEntities.filter(e => e.name === this.selectedField.field); - } - - return (this.selectedEntity ? this.selectedEntity.childEntities : this.entities[0].childEntities) ?? this.entities; - } - public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree { if (!expressionTree) { return null; @@ -1536,12 +1521,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } private selectDefaultCondition() { - if (this.selectedField) { - if (!this.selectedField.dataType) { - this.selectedCondition = 'inQuery'; - } else { - this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0]; - } + if (this.selectedField && this.selectedField.filters) { + this.selectedCondition = this.selectedField.filters.conditionList().indexOf('equals') >= 0 ? 'equals' : this.selectedField.filters.conditionList()[0]; } } diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts index 50d4032613b..2f101085378 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts @@ -64,10 +64,6 @@ export class IgxQueryBuilderComponent implements OnDestroy { */ @Input() public set entities(entities: EntityType[]) { - if (entities) { - this.updateEntities(entities) // add field for every child entity recursively - } - if (entities !== this._entities) { if (entities && this.expressionTree) { this._expressionTree = recreateTree(this._expressionTree, entities); @@ -274,20 +270,6 @@ export class IgxQueryBuilderComponent implements OnDestroy { } } - private updateEntities(entities: EntityType[]) { - entities.forEach((entity) => { - if (entity.childEntities) { - entity.childEntities.forEach((childEntity) => { - if (entity.fields.find((f) => f.field === childEntity.name)) { - return; - } - entity.fields.push({ field: childEntity.name, dataType: null }); - }); - this.updateEntities(entity.childEntities); - } - }); - } - private registerSVGIcons(): void { const editorIcons = editor as any[]; diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts index 99910728ca3..5b4d4fd6013 100644 --- a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts @@ -36,7 +36,7 @@ export class HierarchicalGridAdvancedFilteringSampleComponent implements AfterVi }); const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Artists', ['*']); tree.filteringOperands.push({ - fieldName: 'Albums', + fieldName: 'Artist', condition: IgxStringFilteringOperand.instance().condition('inQuery'), conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, searchTree: innerTree diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html index 5a35a2e9c60..eca7380d243 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.html @@ -22,6 +22,7 @@ + Order Details diff --git a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts index ea6298a3b3f..0c9d4b1c08a 100644 --- a/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts +++ b/src/app/hierarchical-grid-remote/hierarchical-grid-remote.sample.ts @@ -6,7 +6,8 @@ import { IGX_HIERARCHICAL_GRID_DIRECTIVES, FilteringExpressionsTree, IgxStringFilteringOperand, - EntityType + EntityType, + IgxNumberFilteringOperand } from 'igniteui-angular'; import { HttpClient } from '@angular/common/http'; @@ -45,7 +46,8 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit, AfterViewI { field: 'customerId', dataType: 'string' }, // first field will be treated as foreign key { field: 'orderId', dataType: 'number' }, { field: 'employeeId', dataType: 'number' }, - { field: 'shipVia', dataType: 'string' } + { field: 'shipVia', dataType: 'string' }, + { field: 'freight', dataType: 'number' } ], childEntities: [ { @@ -69,16 +71,18 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit, AfterViewI public ngOnInit() { const ordersTree = new FilteringExpressionsTree(0, undefined, 'Orders', ['customerId']); ordersTree.filteringOperands.push({ - fieldName: 'shipVia', + fieldName: 'freight', ignoreCase: false, - conditionName: IgxStringFilteringOperand.instance().condition('equals').name, - searchVal: 'AirCargo' + condition: IgxNumberFilteringOperand.instance().condition('greaterThanOrEqualTo'), + conditionName: IgxNumberFilteringOperand.instance().condition('greaterThanOrEqualTo').name, + searchVal: '500' }); const customersTree = new FilteringExpressionsTree(0, undefined, 'Customers', ['customerId', 'companyName', 'contactName', 'contactTitle']); customersTree.filteringOperands.push({ fieldName: 'customerId', - conditionName: IgxStringFilteringOperand.instance().condition('notInQuery').name, + condition: IgxStringFilteringOperand.instance().condition('inQuery'), + conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, ignoreCase: false, searchTree: ordersTree }); @@ -101,6 +105,8 @@ export class HierarchicalGridRemoteSampleComponent implements OnInit, AfterViewI tree = new FilteringExpressionsTree(0, undefined, this.remoteEntities[0].name, this.remoteEntities[0].fields.map(f => f.field)); } + console.log(tree); + this.hGrid.isLoading = true; this.http.post(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, tree).subscribe(data =>{ console.log('data', data); From 595c3cbb337cb9e1a442149414594b7738fc3a64 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Fri, 11 Apr 2025 16:15:17 +0300 Subject: [PATCH 24/49] feat(h-grid): set expected return field in nested query and select if match --- .../query-builder/query-builder-tree.component.html | 1 + .../query-builder/query-builder-tree.component.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index 460e1ae4e6c..d172e58b86e 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -469,6 +469,7 @@ [entities]="(this.selectedEntity ? this.selectedEntity.childEntities : entities[0].childEntities) ?? entities" [queryBuilder]="this.queryBuilder" [parentExpression]="expressionItem" + [expectedReturnField]="this.selectedField?.field" [expressionTree]="expressionItem.inEditMode ? (innerQueryNewExpressionTree ?? getExpressionTreeCopy(expressionItem.expression.searchTree, true)) : expressionItem.expression.searchTree" (inEditModeChange)="onInEditModeChanged($event)" [searchValueTemplate]="searchValueTemplate"> diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index fab2f30409d..6b40020685b 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -242,6 +242,11 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { return this._resourceStrings; } + /** + * Gets/sets the expected return field. + */ + @Input() public expectedReturnField: string = null; + /** * Event fired as the expression tree is changed. */ @@ -557,7 +562,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet; if (this.isAdvancedFiltering() && this.entities?.length === 1) { - this._selectedEntity = this.entities[0]; + this.selectedEntity = this.entities[0].name; } // Trigger additional change detection cycle @@ -629,7 +634,11 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } this.fields = this._entityNewValue ? this._entityNewValue.fields : []; - this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field); + if (this.entities[0].fields.find(f => f.field === this.expectedReturnField)) { + this._selectedReturnFields = [this.expectedReturnField]; + } else { + this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field); + } if (this._expressionTree) { this._expressionTree.entity = this._entityNewValue.name; From 631619958f0203bd95142e774bfbb3d2c3434451 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Fri, 11 Apr 2025 16:36:52 +0300 Subject: [PATCH 25/49] chore(*): fix filtering when child grid is empty --- .../lib/data-operations/filtering-strategy.ts | 5 +++-- ...archical-grid-advanced-filtering.sample.ts | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index 9024f73e3d3..824ddbba5e9 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -42,9 +42,10 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { if (expr.searchTree) { const records = rec[expr.searchTree.entity]; const shouldMatchRecords = expr.conditionName === 'inQuery'; - // if records is empty, it means that the child grid is not created yet - if (!records || !records.length) { + if (!records) { // child grid is not yet created return true; + } else if (records.length === 0) { // child grid is empty + return false; } for (let index = 0; index < records.length; index++) { diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts index 5b4d4fd6013..793203f88ff 100644 --- a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts @@ -27,21 +27,28 @@ export class HierarchicalGridAdvancedFilteringSampleComponent implements AfterVi } public ngAfterViewInit() { - const innerTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Albums', ['Artist']); - innerTree.filteringOperands.push({ + const albumsTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Albums', ['Artist']); + albumsTree.filteringOperands.push({ fieldName: 'USBillboard200', condition: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo'), conditionName: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo').name, searchVal: 5 }); - const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Artists', ['*']); - tree.filteringOperands.push({ + // const toursTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Tours', ['TouredBy']); + // toursTree.filteringOperands.push({ + // fieldName: 'Headliner', + // condition: IgxStringFilteringOperand.instance().condition('equals'), + // conditionName: IgxStringFilteringOperand.instance().condition('equals').name, + // searchVal: 'YES' + // }); + const artistsTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Artists', ['*']); + artistsTree.filteringOperands.push({ fieldName: 'Artist', condition: IgxStringFilteringOperand.instance().condition('inQuery'), conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, - searchTree: innerTree + searchTree: albumsTree }); - this.hierarchicalGrid.advancedFilteringExpressionsTree = tree; + this.hierarchicalGrid.advancedFilteringExpressionsTree = artistsTree; this.cdr.detectChanges(); } } From fb358ff097703cf905c6b439807b230574dec4ef Mon Sep 17 00:00:00 2001 From: teodosiah Date: Fri, 11 Apr 2025 16:40:24 +0300 Subject: [PATCH 26/49] test(h-grid): fix duplicated fields issue --- .../grids/hierarchical-grid/hierarchical-grid.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index 263e3f59b9c..7f9df32b8bd 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -66,6 +66,7 @@ import { IgxGridDragSelectDirective } from '../selection/drag-select.directive'; import { IgxGridBodyDirective } from '../grid.common'; import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component'; import { IgxActionStripToken } from '../../action-strip/token'; +import { flatten } from '../../core/utils'; let NEXT_ID = 0; @@ -1254,7 +1255,8 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti let fields = []; let childEntities; if (!rowIsland.autoGenerate) { - fields = rowIsland.columnList.map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[]; + fields = flatten(rowIsland.childColumns.toArray()).filter(col => col.field) + .map(f => ({ field: f.field, dataType: f.dataType })) as FieldType[]; } else if (firstRowData) { const rowIslandFields = Object.keys(firstRowData).map(key => { if (firstRowData[key] instanceof Array) { From 1bb5055c1c237b43ded24ac3400c5ffc078ec47d Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Fri, 11 Apr 2025 17:06:46 +0300 Subject: [PATCH 27/49] chore(*): fix selection of expected return field --- .../src/lib/query-builder/query-builder-tree.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 6b40020685b..ed716666d6b 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -634,7 +634,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } this.fields = this._entityNewValue ? this._entityNewValue.fields : []; - if (this.entities[0].fields.find(f => f.field === this.expectedReturnField)) { + if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) { this._selectedReturnFields = [this.expectedReturnField]; } else { this._selectedReturnFields = this.parentExpression ? [] : this._entityNewValue.fields?.map(f => f.field); From 53c4f44d984f65eb7be63a20c4b4807061d52751 Mon Sep 17 00:00:00 2001 From: Galina Edinakova Date: Fri, 11 Apr 2025 17:20:08 +0300 Subject: [PATCH 28/49] fix(QB): Removed unneeded check in operandCanBeCommitted(). --- .../src/lib/query-builder/query-builder-tree.component.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index ed716666d6b..156f907dd2f 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -948,7 +948,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { */ public operandCanBeCommitted(): boolean { const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]; - return this.selectedField && this.selectedCondition && ( ( @@ -956,13 +955,12 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { !(this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) ) || ( - this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && - (innerQuery.selectedReturnFields?.length > 0 || (innerQuery.selectedReturnFields?.length === 0 && this.isAdvancedFiltering())) + this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery && innerQuery && !!innerQuery.expressionTree && innerQuery.selectedReturnFields?.length > 0 ) || this.selectedField.filters.condition(this.selectedCondition)?.isUnary ); } - + /** * @hidden @internal */ From 1d384c60437be4d974c2f074e21707f7dc174193 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Fri, 11 Apr 2025 18:02:13 +0300 Subject: [PATCH 29/49] chore(*): fix timing of schema generation --- .../hierarchical-grid/hierarchical-grid.component.ts | 9 ++++----- .../lib/test-utils/hierarchical-grid-components.spec.ts | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index 7f9df32b8bd..a6ad1970bf4 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -579,6 +579,10 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti } public get schema() { + if (!this._hGridSchema) { + this._hGridSchema = this.generateSchema(); + } + return this._hGridSchema; } @@ -709,11 +713,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti this.rootGrid.hasChildrenKey; this.showExpandAll = this.parentIsland ? this.parentIsland.showExpandAll : this.rootGrid.showExpandAll; - - if (!this._hGridSchema) { - this._hGridSchema = this.generateSchema(); - } - } /** diff --git a/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts index 6e611b87043..3fd5a9c5baf 100644 --- a/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts @@ -444,7 +444,7 @@ export class IgxHierarchicalGridActionStripComponent extends IgxHierarchicalGrid @Component({ template: ` - + @@ -459,13 +459,13 @@ export class IgxHierarchicalGridActionStripComponent extends IgxHierarchicalGrid - + `, imports: [IgxHierarchicalGridComponent, IgxColumnComponent, IgxRowIslandComponent, IgxAdvancedFilteringDialogComponent] }) export class IgxHierGridExternalAdvancedFilteringComponent extends IgxHierarchicalGridTestBaseComponent { - // @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) - // public hgrid: IgxHierarchicalGridComponent; + @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) + public override hgrid: IgxHierarchicalGridComponent; public override data = SampleTestData.generateHGridData(5, 3); } From 15619b57476e91a93200d9efe0756bf2df692b86 Mon Sep 17 00:00:00 2001 From: Galina Edinakova Date: Fri, 11 Apr 2025 18:11:55 +0300 Subject: [PATCH 30/49] fix(*): QB test failure caused by preselection of matching field --- .../src/lib/query-builder/query-builder.component.spec.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts index 98be7fbf5a4..0800ba40c53 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts @@ -1887,10 +1887,7 @@ describe('IgxQueryBuilder', () => { tick(100); fix.detectChanges(); - commitBtn = QueryBuilderFunctions.getQueryBuilderExpressionCommitButton(fix); - ControlsFunction.verifyButtonIsDisabled(commitBtn as HTMLElement, true); - - // Select return field + // Change return field from preselected 'OrderId' to 'Id' QueryBuilderFunctions.selectFieldsInEditModeExpression(fix, [0], 1); tick(100); fix.detectChanges(); @@ -1901,7 +1898,7 @@ describe('IgxQueryBuilder', () => { QueryBuilderFunctions.verifyEditModeExpressionInputStates(fix, true, true, false, true); // Parent commit button should be enabled QueryBuilderFunctions.clickQueryBuilderExpressionCommitButton(fix); fix.detectChanges(); - + //Verify that expressionTree is correct const exprTree = JSON.stringify(fix.componentInstance.queryBuilder.expressionTree, null, 2); expect(exprTree).toBe(`{ From bf41866d7cc586204b1dde40e5060868f14f89ca Mon Sep 17 00:00:00 2001 From: teodosiah Date: Fri, 11 Apr 2025 19:38:36 +0300 Subject: [PATCH 31/49] test(h-grid): fix failing tests due to UI changes --- .../grid/grid-filtering-advanced.spec.ts | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 18ff5138ff4..647d7dd5972 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1520,7 +1520,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fix.detectChanges(); })); - it(`Should NOT have 'In'/'Not-In' operators for not chilld entities fields.`, fakeAsync(() => { + it(`Should have 'In'/'Not-In' operators for fields with chilld entities.`, fakeAsync(() => { // Populate edit inputs. QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. @@ -1530,7 +1530,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[0].nativeElement; const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); const expectedValues = ['Contains', 'Does Not Contain', 'Starts With', 'Ends With', 'Equals', - 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null'];; + 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null', 'In', 'Not In'];; expect(dropdownValues).toEqual(expectedValues); // Close Advanced Filtering dialog. @@ -1539,30 +1539,66 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fix.detectChanges(); })); - it(`Should have 'In'/'Not-In' operators for chilld entities fields.`, fakeAsync(() => { + it(`Should NOT have 'In'/'Not-In' operators for fields without chilld entities.`, fakeAsync(() => { // Populate edit inputs. - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 3); // Select 'Child Data'. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. - // Open the operator dropdown and verify they are only 'In'/'Not In'. - QueryBuilderFunctions.clickQueryBuilderOperatorSelect(fix); + // Select entity in nested level + QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); + // Populate edit inputs on level 1. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 11, 1); // Select 'Not In' operator. + + // Select entity in nested level + QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 2); + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 2); // Select 'ID' column. + // Open the operator dropdown and verify they are 'string' specific + 'In'/'Not In'. + QueryBuilderFunctions.clickQueryBuilderOperatorSelect(fix, 2); fix.detectChanges(); - const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[0].nativeElement; + const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[2].nativeElement; const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); - const expectedValues = ['In', 'Not In'];; + const expectedValues = ['Contains', 'Does Not Contain', 'Starts With', 'Ends With', 'Equals', + 'Does Not Equal', 'Empty', 'Not Empty', 'Null', 'Not Null'];; expect(dropdownValues).toEqual(expectedValues); // Close Advanced Filtering dialog. hgrid.closeAdvancedFilteringDialog(false); tick(200); + fix.detectChanges(); + })); + + it('Should have correct entities depending on the hierarchy level.', fakeAsync(() => { + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. + + QueryBuilderFunctions.clickQueryBuilderEntitySelect(fix, 1); fix.detectChanges(); + const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[1].nativeElement; + const dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); + const expectedValues = ['childData']; + expect(dropdownValues).toEqual(expectedValues); + + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(false); + tick(200); + fix.detectChanges(); })); it(`Should apply 'In'/'Not-In' operators for each level properly.`, fakeAsync(() => { // Populate edit inputs. - QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 3); // Select 'childData' entity. - QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0); // Select 'In' operator. + QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. - // Click the initial 'Add Condition' button. + // Select entity in nested level + QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); + // Select return field + QueryBuilderFunctions.selectFieldsInEditModeExpression(fix, [0], 1); + tick(100); + fix.detectChanges(); + + // Click the initial 'Add Condition' button of the query builder. QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, 1); tick(100); fix.detectChanges(); From 2cd36b463f820d9e3cd7030ad48a1006293ebc3e Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Mon, 14 Apr 2025 15:09:05 +0300 Subject: [PATCH 32/49] feat(h-grid): update changelog and documentation comment --- CHANGELOG.md | 7 +++++++ .../src/lib/data-operations/filtering-condition.ts | 2 +- .../grids/hierarchical-grid/hierarchical-grid.component.ts | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1cd562f37e..7b6b0da8526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ All notable changes for each version of this project will be documented in this - A column's `minWidth` and `maxWidth` constrain the user-specified `width` so that it cannot go outside their bounds. - In SSR mode grid with height 100% or with no height will render on the server with % size and with no data. The grid will show either the empty grid template or the loading indicator (if isLoading is true). - In SSR mode grid with width 100% or with no width will render on the server with % size and with all columns. +- `IgxHierarchicalGrid` + - New advanced filtering functionality is implemented. + - Added a new `schema` input property that can be used to pass collection of `EntityType` objects. This property is required for remote data scenarios. +- `IgxQueryBuilderComponent`, `IgxAdvancedFilteringDialogComponent` + - Added support for entities with hierarchical structure. +- `EntityType` + - A new optional property called `childEntities` has been introduced that can be used to create nested entities. ## 19.1.1 ### New Features diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts index 57f3a8d06fb..edf766bffcc 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts @@ -50,7 +50,7 @@ export class IgxFilteringOperand { public conditionList(): string[] { return this.operations.filter(f => !f.hidden && !f.isNestedQuery).map((element) => element.name); } - + /** * Returns an array of names of the conditions which are visible in the UI, including "In" and "Not In", allowing the creation of sub-queries. * @hidden @internal diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index a6ad1970bf4..f1b4db4eee1 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -563,14 +563,14 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti } /** - * Gets/Sets the entities used for advanced filtering. + * Gets/Sets the collection of `EntityType` objects represented in hierarchical structure, which will be used in the advanced filtering dialog. * * @remarks * This property is required in remote data scenarios. * @example * ```typescript * const schema = this.grid.schema; - * this.grid.schema = []; + * this.grid.schema = [{ name: 'Products', fields: [...], childEntities: [...] }]; * ``` */ @Input() From c80b4861abc186285b42cef77ae78bd92eb3e4d5 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 14 Apr 2025 15:22:18 +0300 Subject: [PATCH 33/49] test(h-grid): check default value of inner query return fields --- .../grid/grid-filtering-advanced.spec.ts | 43 ++++++++++++++----- .../hierarchical-grid-components.spec.ts | 1 + 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 647d7dd5972..79d48e88247 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -20,7 +20,7 @@ import { IgxGridAdvancedFilteringWithToolbarComponent } from '../../test-utils/grid-samples.spec'; import { FormattedValuesFilteringStrategy } from '../../data-operations/filtering-strategy'; -import { IgxHierarchicalGridTestBaseComponent, IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec'; +import { IgxHierarchicalGridExportComponent, IgxHierarchicalGridTestBaseComponent, IgxHierarchicalGridTestCustomToolbarComponent, IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec'; import { IgxHierarchicalGridComponent } from '../hierarchical-grid/public_api'; import { IFilteringEventArgs, IgxGridToolbarAdvancedFilteringComponent } from '../public_api'; import { SampleTestData } from '../../test-utils/sample-test-data.spec'; @@ -1590,18 +1590,14 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { // Populate edit inputs. QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'ID' column. QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator. - - // Select entity in nested level - QueryBuilderFunctions.selectEntityAndClickInitialAddCondition(fix, 0, 1); - // Select return field - QueryBuilderFunctions.selectFieldsInEditModeExpression(fix, [0], 1); tick(100); fix.detectChanges(); - // Click the initial 'Add Condition' button of the query builder. - QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, 1); - tick(100); - fix.detectChanges(); + const entityInputGroup = QueryBuilderFunctions.getQueryBuilderEntitySelect(fix, 1).querySelector('input'); + expect(entityInputGroup.value).toBe('childData'); + + const fieldInputGroup = QueryBuilderFunctions.getQueryBuilderFieldsCombo(fix, 1).querySelector('input'); + expect(fieldInputGroup.value).toBe('ID'); // Populate edit inputs on level 1. QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0, 1); // Select 'Contains' operator. @@ -1626,6 +1622,33 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { expect(hgrid.rowList.length).toBe(5); })); + it(`Should have correct return fields in the child query when there are multiple child entities.`, fakeAsync(() => { + const fixture = TestBed.createComponent(IgxHierarchicalGridExportComponent); + const hierarchicalGrid = fixture.componentInstance.hGrid; + hierarchicalGrid.allowAdvancedFiltering = true; + fixture.detectChanges(); + + hierarchicalGrid.openAdvancedFilteringDialog(); + fixture.detectChanges(); + + // Click the initial 'Add Condition' button. + QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fixture, 0); + tick(100); + fixture.detectChanges(); + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fixture, 0); // Select 'Artist' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fixture, 10); // Select 'In' operator. + tick(100); + fixture.detectChanges(); + + QueryBuilderFunctions.selectEntityInEditModeExpression(fixture, 0, 1); + tick(100); + fixture.detectChanges(); + + const fieldInputGroup = QueryBuilderFunctions.getQueryBuilderFieldsCombo(fixture, 1).querySelector('input'); + expect(fieldInputGroup.value).toBe('Artist'); + })); + it('Should correctly apply filtering expressions tree to the hgrid component through API.', fakeAsync(() => { // Close Advanced Filtering dialog. hgrid.closeAdvancedFilteringDialog(false); diff --git a/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts index 3fd5a9c5baf..a3974d23955 100644 --- a/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts @@ -484,6 +484,7 @@ export class IgxHierGridExternalAdvancedFilteringComponent extends IgxHierarchic + From 89167471f487888e786f232e683381bfdc28538a Mon Sep 17 00:00:00 2001 From: igdmdimitrov <49060557+igdmdimitrov@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:49:26 +0300 Subject: [PATCH 34/49] Update CHANGELOG.md Co-authored-by: Galina Edinakova --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6b0da8526..a75d2e8fca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ All notable changes for each version of this project will be documented in this - In SSR mode grid with height 100% or with no height will render on the server with % size and with no data. The grid will show either the empty grid template or the loading indicator (if isLoading is true). - In SSR mode grid with width 100% or with no width will render on the server with % size and with all columns. - `IgxHierarchicalGrid` - - New advanced filtering functionality is implemented. + - Introduced a new advanced filtering capability that enables top-level records to be dynamically refined based on the attributes or data of their associated child records. - Added a new `schema` input property that can be used to pass collection of `EntityType` objects. This property is required for remote data scenarios. - `IgxQueryBuilderComponent`, `IgxAdvancedFilteringDialogComponent` - Added support for entities with hierarchical structure. From f90fa210cc8a118f426b2a619733dec74ce5d7d9 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 14 Apr 2025 15:57:24 +0300 Subject: [PATCH 35/49] fix(h-grid): select proper return field when there is single child entity --- .../src/lib/grids/grid/grid-filtering-advanced.spec.ts | 9 ++++++++- .../lib/query-builder/query-builder-tree.component.ts | 3 +++ .../lib/test-utils/hierarchical-grid-components.spec.ts | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 79d48e88247..c42186b0d49 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1593,11 +1593,17 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { tick(100); fix.detectChanges(); + // When there is one entity, it should be selected by default const entityInputGroup = QueryBuilderFunctions.getQueryBuilderEntitySelect(fix, 1).querySelector('input'); expect(entityInputGroup.value).toBe('childData'); const fieldInputGroup = QueryBuilderFunctions.getQueryBuilderFieldsCombo(fix, 1).querySelector('input'); expect(fieldInputGroup.value).toBe('ID'); + + // Click the initial 'Add Condition' button. + QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fix, 0); + tick(100); + fix.detectChanges(); // Populate edit inputs on level 1. QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1); // Select 'ID' column. QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 0, 1); // Select 'Contains' operator. @@ -1625,13 +1631,14 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { it(`Should have correct return fields in the child query when there are multiple child entities.`, fakeAsync(() => { const fixture = TestBed.createComponent(IgxHierarchicalGridExportComponent); const hierarchicalGrid = fixture.componentInstance.hGrid; + fixture.componentInstance.shouldDisplayArtist = true; hierarchicalGrid.allowAdvancedFiltering = true; fixture.detectChanges(); hierarchicalGrid.openAdvancedFilteringDialog(); fixture.detectChanges(); - // Click the initial 'Add Condition' button. + // Click the initial 'Add Condition' button. QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fixture, 0); tick(100); fixture.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 156f907dd2f..87b06dfebb9 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -563,6 +563,9 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { if (this.isAdvancedFiltering() && this.entities?.length === 1) { this.selectedEntity = this.entities[0].name; + if (this._selectedEntity.fields.find(f => f.field === this.expectedReturnField)) { + this._selectedReturnFields = [this.expectedReturnField]; + } } // Trigger additional change detection cycle diff --git a/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts index a3974d23955..4e5afe2ca3f 100644 --- a/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/hierarchical-grid-components.spec.ts @@ -484,7 +484,9 @@ export class IgxHierGridExternalAdvancedFilteringComponent extends IgxHierarchic - + @if(shouldDisplayArtist) { + + } @@ -511,6 +513,7 @@ export class IgxHierGridExternalAdvancedFilteringComponent extends IgxHierarchic export class IgxHierarchicalGridExportComponent { @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hGrid: IgxHierarchicalGridComponent; public data = SampleTestData.hierarchicalGridExportData(); + public shouldDisplayArtist = false; } From c9a65cda44ec3ad5e5974f40040626dff0fcbc08 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Mon, 14 Apr 2025 16:19:23 +0300 Subject: [PATCH 36/49] chore(*): trigger schema generation --- .../hierarchical-grid.component.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index f1b4db4eee1..77c28fd7dac 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -67,6 +67,7 @@ import { IgxGridBodyDirective } from '../grid.common'; import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component'; import { IgxActionStripToken } from '../../action-strip/token'; import { flatten } from '../../core/utils'; +import { IFilteringExpressionsTree } from 'igniteui-angular'; let NEXT_ID = 0; @@ -449,6 +450,17 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti return this.parentIsland ? this.parentIsland.actionStrip : super.actionStrip; } + public override get advancedFilteringExpressionsTree(): IFilteringExpressionsTree { + return super.advancedFilteringExpressionsTree; + } + + public override set advancedFilteringExpressionsTree(value: IFilteringExpressionsTree) { + if (!this._hGridSchema) { + this._hGridSchema = this.generateSchema(); + } + super.advancedFilteringExpressionsTree = value; + } + private _data; private h_id = `igx-hierarchical-grid-${NEXT_ID++}`; private childGridTemplates: Map = new Map(); @@ -1243,7 +1255,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti return acc.concat(this.generateChildEntity(rowIsland, this.data[0][rowIsland.key][0])); } , []); - } return entities; From c6fddccbe6a73d977b534dba4d846537f2d424fd Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Mon, 14 Apr 2025 16:23:07 +0300 Subject: [PATCH 37/49] chore(*): fix circular dependency --- .../lib/grids/hierarchical-grid/hierarchical-grid.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index 77c28fd7dac..25cd6fec2e1 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -67,7 +67,7 @@ import { IgxGridBodyDirective } from '../grid.common'; import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component'; import { IgxActionStripToken } from '../../action-strip/token'; import { flatten } from '../../core/utils'; -import { IFilteringExpressionsTree } from 'igniteui-angular'; +import { IFilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree'; let NEXT_ID = 0; From 972e400fcc354f6eb4dd608f20aa2dc31c2424bb Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 14 Apr 2025 17:29:01 +0300 Subject: [PATCH 38/49] test(h-grid): add LOD schema prop test --- .../grid/grid-filtering-advanced.spec.ts | 77 +++++++++++++++++-- .../query-builder-functions.spec.ts | 8 +- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index c42186b0d49..4a44272762e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1685,15 +1685,80 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { // Check for error messages in the console expect(consoleSpy).not.toHaveBeenCalled(); + expect(hgrid.filteredData.length).toBe(13); })); - // fit('Should be able to filter hierarchicaly with load on demand through API.', fakeAsync(() => { - // const fixture = TestBed.createComponent(IgxHGridRemoteOnDemandComponent); - // const hierarchicalGrid = fixture.componentInstance.hgrid; - // hierarchicalGrid.allowAdvancedFiltering = true; - // fixture.detectChanges(); + it('Should have proper fields in UI when schema is defined with load on demand.', fakeAsync(() => { + const fixture = TestBed.createComponent(IgxHGridRemoteOnDemandComponent); + const hierarchicalGrid = fixture.componentInstance.instance; + hierarchicalGrid.allowAdvancedFiltering = true; + hierarchicalGrid.schema = [ + { + name: 'rootLevel', + fields: [ + { field: 'ID', dataType: 'string' }, + { field: 'ChildLevels', dataType: 'number' }, + { field: 'ProductName', dataType: 'string' }, + { field: 'Col1', dataType: 'number' }, + { field: 'Col2', dataType: 'number' }, + { field: 'Col3', dataType: 'number' } + ], + childEntities: [ + { + name: 'childData', + fields: [ + { field: 'ID', dataType: 'string' }, + { field: 'ProductName', dataType: 'string' } + ], + childEntities: [ + { + name: 'childData2', + fields: [ + { field: 'ID', dataType: 'string' }, + { field: 'ProductName', dataType: 'string' } + ] + } + ] + } + ] + } + ] + fixture.detectChanges(); + + hierarchicalGrid.openAdvancedFilteringDialog(); + fixture.detectChanges(); - // })); + // Click the initial 'Add Condition' button. + QueryBuilderFunctions.clickQueryBuilderInitialAddConditionBtn(fixture, 0); + tick(100); + fixture.detectChanges(); + // Populate edit inputs. + QueryBuilderFunctions.selectColumnInEditModeExpression(fixture, 0); // Select 'ID' column. + QueryBuilderFunctions.selectOperatorInEditModeExpression(fixture, 10); // Select 'In' operator. + tick(100); + fixture.detectChanges(); + + const entityInputGroup = QueryBuilderFunctions.getQueryBuilderEntitySelect(fixture, 1).querySelector('input'); + expect(entityInputGroup.value).toBe('childData'); + + const fieldInputGroup = QueryBuilderFunctions.getQueryBuilderFieldsCombo(fixture, 1).querySelector('input'); + expect(fieldInputGroup.value).toBe('ID'); + + // Verify entities + QueryBuilderFunctions.clickQueryBuilderEntitySelect(fixture, 1); + fixture.detectChanges(); + const queryBuilderElement: HTMLElement = fixture.debugElement.queryAll(By.css(`.${QueryBuilderSelectors.QUERY_BUILDER_TREE}`))[1].nativeElement; + let dropdownValues: string[] = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement).map((x: any) => x.innerText); + let expectedValues = ['childData']; + expect(dropdownValues).toEqual(expectedValues); + + // Verify return fileds + QueryBuilderFunctions.clickQueryBuilderFieldsCombo(fixture, 1); + fixture.detectChanges(); + dropdownValues = QueryBuilderFunctions.getQueryBuilderSelectDropdownItems(queryBuilderElement, 1).map((x: any) => x.innerText); + expectedValues = ['ID', 'ProductName']; + expect(dropdownValues).toEqual(expectedValues); + })); }); }); diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts index 46d97a3361a..917a630ffaf 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-functions.spec.ts @@ -311,14 +311,14 @@ export class QueryBuilderFunctions { return outlet; } - public static getQueryBuilderSelectDropdown(queryBuilderElement: HTMLElement) { + public static getQueryBuilderSelectDropdown(queryBuilderElement: HTMLElement, index = 0) { const outlet = QueryBuilderFunctions.getQueryBuilderOutlet(queryBuilderElement); - const selectDropdown = outlet.querySelector(`.${QueryBuilderSelectors.DROP_DOWN_LIST_SCROLL}`); + const selectDropdown = outlet.querySelectorAll(`.${QueryBuilderSelectors.DROP_DOWN_LIST_SCROLL}`).item(index); return selectDropdown; } - public static getQueryBuilderSelectDropdownItems(queryBuilderElement: HTMLElement) { - const selectDropdown = QueryBuilderFunctions.getQueryBuilderSelectDropdown(queryBuilderElement); + public static getQueryBuilderSelectDropdownItems(queryBuilderElement: HTMLElement, index = 0) { + const selectDropdown = QueryBuilderFunctions.getQueryBuilderSelectDropdown(queryBuilderElement, index); const items = Array.from(selectDropdown.querySelectorAll('.igx-drop-down__item')); return items; } From d7cdc511184e2efe495e4219614c91d337330f60 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Mon, 14 Apr 2025 17:31:07 +0300 Subject: [PATCH 39/49] test(h-grid): update describe imports --- .../src/lib/grids/grid/grid-filtering-advanced.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 4a44272762e..810fde35fcd 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -20,7 +20,7 @@ import { IgxGridAdvancedFilteringWithToolbarComponent } from '../../test-utils/grid-samples.spec'; import { FormattedValuesFilteringStrategy } from '../../data-operations/filtering-strategy'; -import { IgxHierarchicalGridExportComponent, IgxHierarchicalGridTestBaseComponent, IgxHierarchicalGridTestCustomToolbarComponent, IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec'; +import { IgxHierarchicalGridExportComponent, IgxHierarchicalGridTestBaseComponent, IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec'; import { IgxHierarchicalGridComponent } from '../hierarchical-grid/public_api'; import { IFilteringEventArgs, IgxGridToolbarAdvancedFilteringComponent } from '../public_api'; import { SampleTestData } from '../../test-utils/sample-test-data.spec'; @@ -42,7 +42,9 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { IgxHierGridExternalAdvancedFilteringComponent, IgxGridAdvancedFilteringDynamicColumnsComponent, IgxGridAdvancedFilteringWithToolbarComponent, - IgxHierarchicalGridTestBaseComponent + IgxHierarchicalGridTestBaseComponent, + IgxHierarchicalGridExportComponent, + IgxHGridRemoteOnDemandComponent ] }); })); From ce2b943636a6285f5e5ac6b3982c47226331b0f7 Mon Sep 17 00:00:00 2001 From: teodosiah Date: Tue, 15 Apr 2025 12:24:43 +0300 Subject: [PATCH 40/49] test(h-grid): add check for JSON parsed expression tree --- .../grid/grid-filtering-advanced.spec.ts | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index 810fde35fcd..8e591807f24 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -1671,7 +1671,7 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { fieldName: 'ID', ignoreCase: false, conditionName: IgxStringFilteringOperand.instance().condition('contains').name, - searchVal: '1' + searchVal: '39' }); const tree = new FilteringExpressionsTree(0, undefined, 'rootData', ['ID']); @@ -1681,13 +1681,45 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { ignoreCase: false, searchTree: innerTree }); - tree.filteringOperands.push(innerTree); + hgrid.advancedFilteringExpressionsTree = tree; fix.detectChanges(); // Check for error messages in the console expect(consoleSpy).not.toHaveBeenCalled(); - expect(hgrid.filteredData.length).toBe(13); + expect(hgrid.filteredData.length).toBe(5); + })); + + it('Should correctly apply JSON filtering expressions tree to the hgrid correctly.', fakeAsync(() => { + // Close Advanced Filtering dialog. + hgrid.closeAdvancedFilteringDialog(false); + tick(200); + fix.detectChanges(); + // Spy for error messages in the console + const consoleSpy = spyOn(console, 'error'); + + const innerTree = new FilteringExpressionsTree(0, undefined, 'childData', ['ID']); + innerTree.filteringOperands.push({ + fieldName: 'ID', + ignoreCase: false, + conditionName: IgxStringFilteringOperand.instance().condition('contains').name, + searchVal: '39' + }); + + const tree = new FilteringExpressionsTree(0, undefined, 'rootData', ['ID']); + tree.filteringOperands.push({ + fieldName: 'ID', + conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, + ignoreCase: false, + searchTree: innerTree + }); + + hgrid.advancedFilteringExpressionsTree = JSON.parse(JSON.stringify(tree)); + fix.detectChanges(); + + // Check for error messages in the console + expect(consoleSpy).not.toHaveBeenCalled(); + expect(hgrid.filteredData.length).toBe(5); })); it('Should have proper fields in UI when schema is defined with load on demand.', fakeAsync(() => { From 9f0d8c6d7a614c08da817dad0d153e92e770dc6a Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 15:15:59 +0300 Subject: [PATCH 41/49] chore(*): fix clear filter not showing add condition option --- .../lib/query-builder/query-builder-tree.component.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 87b06dfebb9..a50c64292a6 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -194,8 +194,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { public set expressionTree(expressionTree: IExpressionTree) { this._expressionTree = expressionTree; if (!expressionTree) { - this._selectedEntity = null; - this._selectedReturnFields = []; + this._selectedEntity = this.isAdvancedFiltering() ? this.entities[0] : null; + this._selectedReturnFields = this._selectedEntity?.fields?.map(f => f.field); } if (!this._preventInit) { @@ -835,6 +835,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public commitOperandEdit() { + console.log('commitOperandEdit'); const actualSearchValue = this.searchValue.value; if (this._editedExpression) { this._editedExpression.expression.fieldName = this.selectedField.field; @@ -868,6 +869,10 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { this._editedExpression = null; } + if (this.selectedReturnFields.length === 0) { + this.selectedReturnFields = this.fields.map(f => f.field); + } + this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields); if (!this.parentExpression) { this.expressionTreeChange.emit(this._expressionTree); From d1d7accbe94dfc67fc1dcdd6fb917716c310a141 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 15:45:59 +0300 Subject: [PATCH 42/49] chore(*): address comments and other fixes --- .../hierarchical-grid/hierarchical-grid.component.ts | 8 +++++--- .../src/lib/query-builder/query-builder-tree.component.ts | 5 ++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index 25cd6fec2e1..4812af26798 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -575,10 +575,12 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti } /** - * Gets/Sets the collection of `EntityType` objects represented in hierarchical structure, which will be used in the advanced filtering dialog. - * + * Gets/Sets the schema for the hierarchical grid. + * This schema defines the structure and properties of the data displayed in the grid. + * @Input() + * @param {EntityType[]} entities - An array of EntityType objects representing the grid's schema. * @remarks - * This property is required in remote data scenarios. + * This property is required in remote data filtering scenarios. * @example * ```typescript * const schema = this.grid.schema; diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index a50c64292a6..541704be757 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -194,8 +194,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { public set expressionTree(expressionTree: IExpressionTree) { this._expressionTree = expressionTree; if (!expressionTree) { - this._selectedEntity = this.isAdvancedFiltering() ? this.entities[0] : null; - this._selectedReturnFields = this._selectedEntity?.fields?.map(f => f.field); + this._selectedEntity = this.isAdvancedFiltering() && this.entities.length === 1 ? this.entities[0] : null; + this._selectedReturnFields = this._selectedEntity ? this._selectedEntity.fields?.map(f => f.field) : []; } if (!this._preventInit) { @@ -835,7 +835,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { * @hidden @internal */ public commitOperandEdit() { - console.log('commitOperandEdit'); const actualSearchValue = this.searchValue.value; if (this._editedExpression) { this._editedExpression.expression.fieldName = this.selectedField.field; From 270ac2b9d0f4ee08187f6eefbd8fe7f153762f21 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 17:28:10 +0300 Subject: [PATCH 43/49] feat(h-grid): fixed adv filtering for date and time columns on nested levels --- .../lib/data-operations/filtering-strategy.ts | 34 +++++++++++++++---- ...archical-grid-advanced-filtering.sample.ts | 12 +++---- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index 824ddbba5e9..0ccf4d34e90 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -1,12 +1,13 @@ import { FilteringLogic, IFilteringExpression } from './filtering-expression.interface'; import { FilteringExpressionsTree, IFilteringExpressionsTree } from './filtering-expressions-tree'; import { resolveNestedPath, parseDate, formatDate, formatCurrency } from '../core/utils'; -import { ColumnType, GridType } from '../grids/common/grid.interface'; +import { ColumnType, EntityType, GridType } from '../grids/common/grid.interface'; import { GridColumnDataType } from './data-util'; import { SortingDirection } from './sorting-strategy'; import { formatNumber, formatPercent, getLocaleCurrencyCode } from '@angular/common'; import { IFilteringState } from './filtering-state.interface'; import { isTree } from './expressions-tree-util'; +import { IgxHierarchicalGridComponent } from '../grids/hierarchical-grid/hierarchical-grid.component'; const DateType = 'date'; const DateTimeType = 'dateTime'; @@ -94,16 +95,19 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { return true; } else { const expression = expressions; - let isDate = false; - let isTime = false; + let dataType = null; if (!entity) { const column = grid && grid.getColumnByName(expression.fieldName); - isDate = column ? column.dataType === DateType || column.dataType === DateTimeType : false; - isTime = column ? column.dataType === TimeType : false; - } else { - // TODO: check for date and time + dataType = column.dataType; + } else if (grid.type === 'hierarchical') { + const schema = (grid as IgxHierarchicalGridComponent).schema; + const entityMatch = this.findEntityByName(schema, 'entity'); + dataType = entityMatch?.fields.find(f => f.field === expression.fieldName)?.dataType; } + const isDate = dataType ? dataType === DateType || dataType === DateTimeType : false; + const isTime = dataType ? dataType === TimeType : false; + return this.findMatchByExpression(rec, expression, isDate, isTime, grid); } } @@ -111,6 +115,22 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { return true; } + private findEntityByName(schema: EntityType[], name: string): EntityType | null { + for (const entity of schema) { + if (entity.name === name) { + return entity; + } + + if (entity.childEntities && entity.childEntities.length > 0) { + const found = this.findEntityByName(entity.childEntities, name); + if (found) { + return found; + } + } + } + return null; + } + public getFilterItems(column: ColumnType, tree: IFilteringExpressionsTree): Promise { let data = column.grid.gridAPI.filterDataByExpressions(tree); diff --git a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts index 793203f88ff..5e3eff812c3 100644 --- a/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts +++ b/src/app/hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample.ts @@ -4,8 +4,8 @@ import { IGX_HIERARCHICAL_GRID_DIRECTIVES, FilteringExpressionsTree, FilteringLogic, - IgxNumberFilteringOperand, - IgxStringFilteringOperand + IgxStringFilteringOperand, + IgxDateFilteringOperand } from 'igniteui-angular'; import { SINGERS } from './data'; @@ -29,10 +29,10 @@ export class HierarchicalGridAdvancedFilteringSampleComponent implements AfterVi public ngAfterViewInit() { const albumsTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Albums', ['Artist']); albumsTree.filteringOperands.push({ - fieldName: 'USBillboard200', - condition: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo'), - conditionName: IgxNumberFilteringOperand.instance().condition('lessThanOrEqualTo').name, - searchVal: 5 + fieldName: 'LaunchDate', + condition: IgxDateFilteringOperand.instance().condition('after'), + conditionName: IgxDateFilteringOperand.instance().condition('after').name, + searchVal: new Date(2018, 1, 1) }); // const toursTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Tours', ['TouredBy']); // toursTree.filteringOperands.push({ From aa7c34752517e204a40b48b33c8f310032514fa8 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 17:36:54 +0300 Subject: [PATCH 44/49] chore(*): added null check for dataType --- .../src/lib/data-operations/filtering-strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index 0ccf4d34e90..0347e202d54 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -98,7 +98,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { let dataType = null; if (!entity) { const column = grid && grid.getColumnByName(expression.fieldName); - dataType = column.dataType; + dataType = column?.dataType; } else if (grid.type === 'hierarchical') { const schema = (grid as IgxHierarchicalGridComponent).schema; const entityMatch = this.findEntityByName(schema, 'entity'); From b71d5a22d62a93fccbcef34662afb824436260d7 Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 17:54:10 +0300 Subject: [PATCH 45/49] chore(*): renamed method to isHierarchicalNestedQuery --- .../src/lib/query-builder/query-builder-tree.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html index d172e58b86e..9ebaf1a9191 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html @@ -7,7 +7,7 @@ -
+
From
- @if (!this.isHierarchicalGridNestedQuery()) { + @if (!this.isHierarchicalNestedQuery()) { Select } @if (!parentExpression) { From 6a5c88d34b3c6f48834ca97c0c8954c8fcc7d2fc Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 17:54:55 +0300 Subject: [PATCH 46/49] chore(*): renamed method to isHierarchicalNestedQuery --- .../src/lib/query-builder/query-builder-tree.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts index 541704be757..7a59165e80c 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts @@ -527,7 +527,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } /** @hidden */ - protected isHierarchicalGridNestedQuery(): boolean { + protected isHierarchicalNestedQuery(): boolean { return this.queryBuilder.entities !== this.entities } @@ -1392,7 +1392,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { } if ((this.isAdvancedFiltering() && !this.entities[0].childEntities) || - (this.isHierarchicalGridNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) { + (this.isHierarchicalNestedQuery() && this.selectedEntity.name && !this.selectedEntity.childEntities)) { return this.selectedField.filters.conditionList(); } From d6cb157472f20bbd345a6ac202fbf61168ef2d3a Mon Sep 17 00:00:00 2001 From: igdmdimitrov Date: Tue, 15 Apr 2025 18:08:58 +0300 Subject: [PATCH 47/49] chore(*): regenerated elements config --- .../src/analyzer/elements.config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/projects/igniteui-angular-elements/src/analyzer/elements.config.ts b/projects/igniteui-angular-elements/src/analyzer/elements.config.ts index 6056d49ce84..0eb64660797 100644 --- a/projects/igniteui-angular-elements/src/analyzer/elements.config.ts +++ b/projects/igniteui-angular-elements/src/analyzer/elements.config.ts @@ -7,20 +7,20 @@ import { } from "../../../igniteui-angular/src/public_api"; import { IgxPaginatorComponent } from "../../../igniteui-angular/src/lib/paginator/paginator.component"; import { IgxPaginatorToken } from "../../../igniteui-angular/src/lib/paginator/token"; -import { IgxActionStripComponent } from "../../../igniteui-angular/src/lib/action-strip/action-strip.component"; -import { IgxActionStripToken } from "../../../igniteui-angular/src/lib/action-strip/token"; -import { IgxGridEditingActionsComponent } from "../../../igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component"; -import { IgxGridActionsBaseDirective } from "../../../igniteui-angular/src/lib/action-strip/grid-actions/grid-actions-base.directive"; -import { IgxGridPinningActionsComponent } from "../../../igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component"; -import { IgxColumnComponent } from "../../../igniteui-angular/src/lib/grids/columns/column.component"; -import { IgxColumnGroupComponent } from "../../../igniteui-angular/src/lib/grids/columns/column-group.component"; -import { IgxColumnLayoutComponent } from "../../../igniteui-angular/src/lib/grids/columns/column-layout.component"; import { IgxGridToolbarTitleComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/common"; import { IgxGridToolbarActionsComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/common"; import { IgxGridToolbarAdvancedFilteringComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/grid-toolbar-advanced-filtering.component"; import { IgxGridToolbarComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/grid-toolbar.component"; import { IgxToolbarToken } from "../../../igniteui-angular/src/lib/grids/toolbar/token"; +import { IgxColumnComponent } from "../../../igniteui-angular/src/lib/grids/columns/column.component"; +import { IgxColumnGroupComponent } from "../../../igniteui-angular/src/lib/grids/columns/column-group.component"; import { IgxRowIslandComponent } from "../../../igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component"; +import { IgxActionStripComponent } from "../../../igniteui-angular/src/lib/action-strip/action-strip.component"; +import { IgxActionStripToken } from "../../../igniteui-angular/src/lib/action-strip/token"; +import { IgxGridEditingActionsComponent } from "../../../igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component"; +import { IgxGridActionsBaseDirective } from "../../../igniteui-angular/src/lib/action-strip/grid-actions/grid-actions-base.directive"; +import { IgxGridPinningActionsComponent } from "../../../igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component"; +import { IgxColumnLayoutComponent } from "../../../igniteui-angular/src/lib/grids/columns/column-layout.component"; import { IgxGridToolbarExporterComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/grid-toolbar-exporter.component"; import { IgxGridToolbarHidingComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/grid-toolbar-hiding.component"; import { IgxGridToolbarPinningComponent } from "../../../igniteui-angular/src/lib/grids/toolbar/grid-toolbar-pinning.component"; From a4642c2a1ae56e2f9c61d00d50fb678b69e166a9 Mon Sep 17 00:00:00 2001 From: igdmdimitrov <49060557+igdmdimitrov@users.noreply.github.com> Date: Tue, 15 Apr 2025 18:18:56 +0300 Subject: [PATCH 48/49] Update projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts Co-authored-by: Galina Edinakova --- .../src/lib/data-operations/filtering-strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts index 0347e202d54..bc8812b713b 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts @@ -101,7 +101,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy { dataType = column?.dataType; } else if (grid.type === 'hierarchical') { const schema = (grid as IgxHierarchicalGridComponent).schema; - const entityMatch = this.findEntityByName(schema, 'entity'); + const entityMatch = this.findEntityByName(schema, entity); dataType = entityMatch?.fields.find(f => f.field === expression.fieldName)?.dataType; } From 4b619edde2f1a2fddf8139505495dab7ddff5141 Mon Sep 17 00:00:00 2001 From: Galina Edinakova Date: Tue, 15 Apr 2025 18:40:17 +0300 Subject: [PATCH 49/49] chore(*): Fixed a typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a75d2e8fca5..b85ee771af2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ All notable changes for each version of this project will be documented in this - Introduced a new `expanded` input property, enabling dynamic control over the banner's state. The banner can now be programmatically set to expanded (visible) or collapsed (hidden) both initially and at runtime. Animations will trigger during runtime updates — the **open animation** plays when `expanded` is set to `true`, and the **close animation** plays when set to `false`. However, no animations will trigger when the property is set initially. - The banner's event lifecycle (`opening`, `opened`, `closing`, `closed`) only triggers through **user interactions** (e.g., clicking to open/close). Programmatic updates using the `expanded` property will not fire any events. - If the `expanded` property changes during an ongoing animation, the current animation will **stop** and the opposite animation will begin from the **point where the previous animation left off**. For instance, if the open animation (10 seconds) is interrupted at 6 seconds and `expanded` is set to `false`, the close animation (5 seconds) will start from its 3rd second. -- `IgxQueryBuilder` has new design that comes with updated appearance and new functionality +- `IgxQueryBuilder` has a new design that comes with an updated appearance and new functionality - `IgxQueryBuilderComponent` - Introduced the ability to create nested queries by specifying IN/NOT IN operators. - Introduced the ability to reposition condition chips by dragging or using `Arrow Up/Down`.