Skip to content

Commit 0056177

Browse files
feat(query-builder): save and restore inner query state on commit/discard (#14916)
* feat(query-builder): save and restore inner query state --------- Co-authored-by: INFRAGISTICS\IPetrov <[email protected]>
1 parent d4314a5 commit 0056177

File tree

3 files changed

+98
-47
lines changed

3 files changed

+98
-47
lines changed

projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ <h6 class="igx-filter-empty__title">
411411
[entities]="entities"
412412
[queryBuilder]="this.queryBuilder"
413413
[parentExpression]="expressionItem"
414-
[expressionTree]="expressionItem.expression.searchTree"
414+
[expressionTree]="expressionItem.inEditMode ? (innerQueryNewExpressionTree ?? getExpressionTreeCopy(expressionItem.expression.searchTree, true)) : expressionItem.expression.searchTree"
415415
(inEditModeChange)="onInEditModeChanged($event)"
416416
[searchValueTemplate]="searchValueTemplate">
417417
</igx-query-builder-tree>

projects/igniteui-angular/src/lib/query-builder/query-builder-tree.component.ts

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,13 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
228228
*/
229229
@Input()
230230
public set expressionTree(expressionTree: IExpressionTree) {
231-
if (JSON.stringify(expressionTree) !== JSON.stringify(this._expressionTree)) {
232-
this._expressionTree = expressionTree;
233-
if (!expressionTree) {
234-
this._selectedEntity = null;
235-
this._selectedReturnFields = [];
236-
}
237-
238-
this.init();
231+
this._expressionTree = expressionTree;
232+
if (!expressionTree) {
233+
this._selectedEntity = null;
234+
this._selectedReturnFields = [];
239235
}
236+
237+
this.init();
240238
}
241239

242240
/**
@@ -280,10 +278,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
280278

281279
/**
282280
* Event fired as the expression tree is changed.
283-
*
284-
* ```html
285-
* <igx-query-builder (expressionTreeChange)='onExpressionTreeChange()'></igx-query-builder>
286-
* ```
287281
*/
288282
@Output()
289283
public expressionTreeChange = new EventEmitter<IExpressionTree>();
@@ -390,6 +384,11 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
390384
@ViewChildren(IgxQueryBuilderTreeComponent)
391385
private innerQueries: QueryList<IgxQueryBuilderTreeComponent>;
392386

387+
/**
388+
* @hidden @internal
389+
*/
390+
public innerQueryNewExpressionTree: IExpressionTree;
391+
393392
/**
394393
* @hidden @internal
395394
*/
@@ -468,7 +467,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
468467

469468
private destroy$ = new Subject<any>();
470469
private _parentExpression: ExpressionOperandItem;
471-
private _initialExpressionTree: IExpressionTree;
472470
private _selectedEntity: EntityType;
473471
private _selectedReturnFields: string | string[];
474472
private _selectedField: FieldType;
@@ -541,6 +539,13 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
541539
this.destroy$.complete();
542540
}
543541

542+
/**
543+
* @hidden @internal
544+
*/
545+
public set selectedEntity(value: string) {
546+
this._selectedEntity = this.entities?.find(el => el.name === value);
547+
}
548+
544549
/**
545550
* @hidden @internal
546551
*/
@@ -586,16 +591,13 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
586591
this._selectedEntity.fields = [];
587592
}
588593
this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
589-
590594
this._selectedReturnFields = this._entityNewValue.fields?.map(f => f.field);
591595

592596
if (this._expressionTree) {
593-
this._expressionTree.entity = this._selectedEntity.name;
594-
this._expressionTree.returnFields = [];
595-
this._expressionTree.filteringOperands = [];
596-
597597
this._editedExpression = null;
598-
this.expressionTreeChange.emit(this._expressionTree);
598+
if (!this.parentExpression) {
599+
this.expressionTreeChange.emit(this._expressionTree);
600+
}
599601

600602
this.addAndGroup();
601603
}
@@ -608,21 +610,21 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
608610
this.entitySelect.close();
609611

610612
this._entityNewValue = null;
613+
this.innerQueryNewExpressionTree = null;
611614
}
612615

613616
/**
614617
* @hidden @internal
615618
*/
616619
public set selectedReturnFields(value: string[]) {
617-
const oldValue = this._selectedReturnFields;
618-
619-
if (this._selectedReturnFields !== value && oldValue !== value) {
620+
if (this._selectedReturnFields !== value) {
620621
this._selectedReturnFields = value;
621622

622-
if (this._expressionTree) {
623+
if (this._expressionTree && !this.parentExpression) {
623624
this._expressionTree.returnFields = value;
624625
this.expressionTreeChange.emit(this._expressionTree);
625626
}
627+
626628
}
627629
}
628630

@@ -747,20 +749,12 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
747749

748750
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
749751
if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
750-
if (!this._editedExpression.expression.searchTree && innerQuery.expressionTree) {
751-
this._editedExpression.expression.searchTree = new FilteringExpressionsTree(innerQuery.expressionTree.operator);
752-
}
753-
754-
if (this._editedExpression.expression.searchTree) {
755-
this._editedExpression.expression.searchTree.entity = innerQuery.selectedEntity.name;
756-
this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields;
757-
this._editedExpression.expression.searchTree.filteringOperands = innerQuery.expressionTree?.filteringOperands;
758-
this._editedExpression.expression.searchTree.operator = innerQuery.expressionTree?.operator;
759-
this._editedExpression.expression.searchTree.fieldName = innerQuery.expressionTree?.fieldName;
760-
}
752+
this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
753+
this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields;
761754
} else {
762755
this._editedExpression.expression.searchTree = null;
763756
}
757+
this.innerQueryNewExpressionTree = null;
764758

765759
if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
766760
this._editedExpression.expression.searchVal = null;
@@ -771,7 +765,9 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
771765
}
772766

773767
this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
774-
this.expressionTreeChange.emit(this._expressionTree);
768+
if (!this.parentExpression) {
769+
this.expressionTreeChange.emit(this._expressionTree);
770+
}
775771
}
776772

777773
/**
@@ -791,11 +787,12 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
791787
if (this.innerQueries) {
792788
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
793789
if (innerQuery) {
794-
innerQuery.expressionTree = this._initialExpressionTree;
795-
796790
if (innerQuery._editedExpression) {
797791
innerQuery.cancelOperandEdit();
798792
}
793+
794+
innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
795+
this.innerQueryNewExpressionTree = null;
799796
}
800797
}
801798

@@ -815,6 +812,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
815812
*/
816813
public operandCanBeCommitted(): boolean {
817814
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
815+
818816
return this.selectedField && this.selectedCondition &&
819817
(
820818
(
@@ -928,7 +926,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
928926
this._editedExpression.inEditMode = false;
929927
}
930928

931-
if (this.parentExpression) {
929+
if (this.parentExpression && !this.parentExpression.inEditMode) {
932930
this.inEditModeChange.emit(this.parentExpression);
933931
}
934932

@@ -946,12 +944,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
946944

947945
expressionItem.inEditMode = true;
948946
this._editedExpression = expressionItem;
949-
if (expressionItem.expression.searchTree) {
950-
this._initialExpressionTree = expressionItem.expression.searchTree;
951-
} else {
952-
this._initialExpressionTree = null;
953-
}
954-
955947
this.cdr.detectChanges();
956948

957949
this.entitySelectOverlaySettings.target = this.entitySelect.element;
@@ -1269,6 +1261,28 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
12691261
}
12701262
}
12711263

1264+
public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1265+
if (!expressionTree) {
1266+
return null;
1267+
}
1268+
1269+
const exprTreeCopy =
1270+
{
1271+
filteringOperands: [],
1272+
operator: expressionTree.operator,
1273+
fieldName: expressionTree.fieldName,
1274+
entity: expressionTree.entity,
1275+
returnFields: expressionTree.returnFields
1276+
};
1277+
expressionTree.filteringOperands.forEach(o => o instanceof FilteringExpressionsTree ? exprTreeCopy.filteringOperands.push(this.getExpressionTreeCopy(o)) : exprTreeCopy.filteringOperands.push(o));
1278+
1279+
if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
1280+
this.innerQueryNewExpressionTree = exprTreeCopy;
1281+
}
1282+
1283+
return exprTreeCopy;
1284+
}
1285+
12721286
public onSelectAllClicked(_event) {
12731287
if (
12741288
(this._selectedReturnFields.length > 0 && this._selectedReturnFields.length < this._selectedEntity.fields.length) ||
@@ -1387,7 +1401,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
13871401
condition: filteringExpr.condition,
13881402
conditionName: filteringExpr.condition.name,
13891403
searchVal: filteringExpr.searchVal,
1390-
searchTree: filteringExpr.searchTree, // this.createExpressionGroupItem(filteringExpr.searchTree, groupItem),
1404+
searchTree: filteringExpr.searchTree,
13911405
ignoreCase: filteringExpr.ignoreCase
13921406
};
13931407
const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
@@ -1504,7 +1518,9 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
15041518
this.deleteItem(expressionItem.parent);
15051519
}
15061520

1507-
this.expressionTreeChange.emit(this._expressionTree);
1521+
if (!this.parentExpression) {
1522+
this.expressionTreeChange.emit(this._expressionTree);
1523+
}
15081524
}
15091525

15101526
private createGroup(operator: FilteringLogic) {

projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,38 @@ describe('IgxQueryBuilder', () => {
19891989
QueryBuilderFunctions.verifyRootAndSubGroupExpressionsCount(fix, 3, 6);
19901990
}));
19911991

1992+
it('Should not make bug where existing inner query is leaking to a newly created one', fakeAsync(() => {
1993+
queryBuilder.expressionTree = QueryBuilderFunctions.generateExpressionTree();
1994+
fix.detectChanges();
1995+
1996+
let group = QueryBuilderFunctions.getQueryBuilderTreeRootGroup(fix) as HTMLElement;
1997+
1998+
// Add new 'expression'.
1999+
const buttonsContainer = Array.from(group.querySelectorAll('.igx-filter-tree__buttons'))[0];
2000+
const buttons = Array.from(buttonsContainer.querySelectorAll('button'));
2001+
(buttons[0] as HTMLElement).click();
2002+
tick();
2003+
fix.detectChanges();
2004+
2005+
// Add condition with 'in' operator to open inner query
2006+
QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0); // Select 'OrderName' column.
2007+
QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'Contains' operator.
2008+
tick(100);
2009+
fix.detectChanges();
2010+
2011+
//New empty inner query should be displayed
2012+
const queryBuilderElement: HTMLElement = fix.debugElement.queryAll(By.css(`.${QueryBuilderConstants.QUERY_BUILDER_CLASS}`))[0].nativeElement;
2013+
const bodyElement = queryBuilderElement.children[1].children[0];
2014+
const actionArea = bodyElement.children[0].querySelector('.igx-query-builder__root-actions');
2015+
expect(actionArea).toBeDefined('action area is missing');
2016+
expect(actionArea).not.toBeNull('action area is missing');
2017+
expect(actionArea.querySelectorAll(':scope > button').length).toEqual(2);
2018+
expect(bodyElement.children[0].children[1].children[6]).toHaveClass('igx-query-builder-tree');
2019+
expect(bodyElement.children[0].children[1].children[6].children.length).toEqual(3);
2020+
const tree = bodyElement.children[0].children[1].children[6].querySelector('.igx-filter-tree__expression');
2021+
expect(tree).toBeNull();
2022+
}));
2023+
19922024
it('canCommit should return the correct validity state of currently edited condition.', fakeAsync(() => {
19932025
queryBuilder.expressionTree = QueryBuilderFunctions.generateExpressionTree();
19942026
fix.detectChanges();
@@ -2150,6 +2182,9 @@ describe('IgxQueryBuilder', () => {
21502182
QueryBuilderFunctions.clickQueryBuilderTreeExpressionChip(fix, [0], true, 1);
21512183
tick(50);
21522184
fix.detectChanges();
2185+
QueryBuilderFunctions.clickQueryBuilderTreeExpressionChip(fix, [1], true, 1);
2186+
tick(50);
2187+
fix.detectChanges();
21532188
expect(queryBuilder.canCommit()).toBeTrue();
21542189
QueryBuilderFunctions.selectColumnInEditModeExpression(fix, 0, 1);
21552190
expect(queryBuilder.canCommit()).toBeFalse();

0 commit comments

Comments
 (0)