Skip to content

Commit 2bf21ad

Browse files
committed
Merge branch 'dmdimitrov/query-builder-improvements' into mcherkasov/qb-rehydration
2 parents 00a8e2d + 7cffe44 commit 2bf21ad

File tree

5 files changed

+112
-57
lines changed

5 files changed

+112
-57
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: 74 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import { IgxTooltipDirective } from '../directives/tooltip/tooltip.directive';
5454
import { IgxTooltipTargetDirective } from '../directives/tooltip/tooltip-target.directive';
5555
import { IgxQueryBuilderSearchValueTemplateDirective } from './query-builder.directives';
5656
import { IgxQueryBuilderComponent } from './query-builder.component';
57-
import { isTree, recreateTree } from '../data-operations/expressions-tree-util';
5857

5958
const DEFAULT_PIPE_DATE_FORMAT = 'mediumDate';
6059
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
@@ -229,16 +228,13 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
229228
*/
230229
@Input()
231230
public set expressionTree(expressionTree: IExpressionTree) {
232-
if (JSON.stringify(expressionTree) !== JSON.stringify(this._expressionTree)) {
233-
this._expressionTree = expressionTree;
234-
235-
if (!expressionTree) {
236-
this._selectedEntity = null;
237-
this._selectedReturnFields = [];
238-
}
239-
240-
this.init();
231+
this._expressionTree = expressionTree;
232+
if (!expressionTree) {
233+
this._selectedEntity = null;
234+
this._selectedReturnFields = [];
241235
}
236+
237+
this.init();
242238
}
243239

244240
/**
@@ -282,10 +278,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
282278

283279
/**
284280
* Event fired as the expression tree is changed.
285-
*
286-
* ```html
287-
* <igx-query-builder (expressionTreeChange)='onExpressionTreeChange()'></igx-query-builder>
288-
* ```
289281
*/
290282
@Output()
291283
public expressionTreeChange = new EventEmitter<IExpressionTree>();
@@ -392,6 +384,11 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
392384
@ViewChildren(IgxQueryBuilderTreeComponent)
393385
private innerQueries: QueryList<IgxQueryBuilderTreeComponent>;
394386

387+
/**
388+
* @hidden @internal
389+
*/
390+
public innerQueryNewExpressionTree: IExpressionTree;
391+
395392
/**
396393
* @hidden @internal
397394
*/
@@ -470,7 +467,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
470467

471468
private destroy$ = new Subject<any>();
472469
private _parentExpression: ExpressionOperandItem;
473-
private _initialExpressionTree: IExpressionTree;
474470
private _selectedEntity: EntityType;
475471
private _selectedReturnFields: string | string[];
476472
private _selectedField: FieldType;
@@ -543,6 +539,13 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
543539
this.destroy$.complete();
544540
}
545541

542+
/**
543+
* @hidden @internal
544+
*/
545+
public set selectedEntity(value: string) {
546+
this._selectedEntity = this.entities?.find(el => el.name === value);
547+
}
548+
546549
/**
547550
* @hidden @internal
548551
*/
@@ -588,16 +591,13 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
588591
this._selectedEntity.fields = [];
589592
}
590593
this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
591-
592594
this._selectedReturnFields = this._entityNewValue.fields?.map(f => f.field);
593595

594596
if (this._expressionTree) {
595-
this._expressionTree.entity = this._selectedEntity.name;
596-
this._expressionTree.returnFields = [];
597-
this._expressionTree.filteringOperands = [];
598-
599597
this._editedExpression = null;
600-
this.expressionTreeChange.emit(this._expressionTree);
598+
if (!this.parentExpression) {
599+
this.expressionTreeChange.emit(this._expressionTree);
600+
}
601601

602602
this.addAndGroup();
603603
}
@@ -610,21 +610,21 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
610610
this.entitySelect.close();
611611

612612
this._entityNewValue = null;
613+
this.innerQueryNewExpressionTree = null;
613614
}
614615

615616
/**
616617
* @hidden @internal
617618
*/
618619
public set selectedReturnFields(value: string[]) {
619-
const oldValue = this._selectedReturnFields;
620-
621-
if (this._selectedReturnFields !== value && oldValue !== value) {
620+
if (this._selectedReturnFields !== value) {
622621
this._selectedReturnFields = value;
623622

624-
if (this._expressionTree) {
623+
if (this._expressionTree && !this.parentExpression) {
625624
this._expressionTree.returnFields = value;
626625
this.expressionTreeChange.emit(this._expressionTree);
627626
}
627+
628628
}
629629
}
630630

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

750750
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0]
751751
if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
752-
if (!this._editedExpression.expression.searchTree && innerQuery.expressionTree) {
753-
this._editedExpression.expression.searchTree = new FilteringExpressionsTree(innerQuery.expressionTree.operator);
754-
}
755-
756-
if (this._editedExpression.expression.searchTree) {
757-
this._editedExpression.expression.searchTree.entity = innerQuery.selectedEntity.name;
758-
this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields;
759-
this._editedExpression.expression.searchTree.filteringOperands = innerQuery.expressionTree?.filteringOperands;
760-
this._editedExpression.expression.searchTree.operator = innerQuery.expressionTree?.operator;
761-
this._editedExpression.expression.searchTree.fieldName = innerQuery.expressionTree?.fieldName;
762-
}
752+
this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
753+
this._editedExpression.expression.searchTree.returnFields = innerQuery.selectedReturnFields;
763754
} else {
764755
this._editedExpression.expression.searchTree = null;
765756
}
757+
this.innerQueryNewExpressionTree = null;
766758

767759
if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary) {
768760
this._editedExpression.expression.searchVal = null;
@@ -773,7 +765,9 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
773765
}
774766

775767
this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, this.selectedEntity?.name, this.selectedReturnFields);
776-
this.expressionTreeChange.emit(this._expressionTree);
768+
if (!this.parentExpression) {
769+
this.expressionTreeChange.emit(this._expressionTree);
770+
}
777771
}
778772

779773
/**
@@ -793,11 +787,12 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
793787
if (this.innerQueries) {
794788
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
795789
if (innerQuery) {
796-
innerQuery.expressionTree = this._initialExpressionTree;
797-
798790
if (innerQuery._editedExpression) {
799791
innerQuery.cancelOperandEdit();
800792
}
793+
794+
innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
795+
this.innerQueryNewExpressionTree = null;
801796
}
802797
}
803798

@@ -817,6 +812,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
817812
*/
818813
public operandCanBeCommitted(): boolean {
819814
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
815+
820816
return this.selectedField && this.selectedCondition &&
821817
(
822818
(
@@ -948,12 +944,6 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
948944

949945
expressionItem.inEditMode = true;
950946
this._editedExpression = expressionItem;
951-
if (expressionItem.expression.searchTree) {
952-
this._initialExpressionTree = expressionItem.expression.searchTree;
953-
} else {
954-
this._initialExpressionTree = null;
955-
}
956-
957947
this.cdr.detectChanges();
958948

959949
this.entitySelectOverlaySettings.target = this.entitySelect.element;
@@ -962,12 +952,18 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
962952
this.returnFieldSelectOverlaySettings.target = this.selectedReturnFieldsCombo.getEditElement();
963953
this.returnFieldSelectOverlaySettings.excludeFromOutsideClick = [this.selectedReturnFieldsCombo.getEditElement() as HTMLElement];
964954
this.returnFieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
965-
this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
966-
this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
967-
this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
968-
this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
969-
this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
970-
this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
955+
956+
if (this.fieldSelect) {
957+
this.fieldSelectOverlaySettings.target = this.fieldSelect.element;
958+
this.fieldSelectOverlaySettings.excludeFromOutsideClick = [this.fieldSelect.element as HTMLElement];
959+
this.fieldSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
960+
}
961+
if (this.conditionSelect) {
962+
this.conditionSelectOverlaySettings.target = this.conditionSelect.element;
963+
this.conditionSelectOverlaySettings.excludeFromOutsideClick = [this.conditionSelect.element as HTMLElement];
964+
this.conditionSelectOverlaySettings.positionStrategy = new AutoPositionStrategy();
965+
}
966+
971967

972968
if (!this.selectedField) {
973969
this.fieldSelect.input.nativeElement.focus();
@@ -1271,6 +1267,28 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
12711267
}
12721268
}
12731269

1270+
public getExpressionTreeCopy(expressionTree: IExpressionTree, shouldAssignInnerQueryExprTree?: boolean): IExpressionTree {
1271+
if (!expressionTree) {
1272+
return null;
1273+
}
1274+
1275+
const exprTreeCopy =
1276+
{
1277+
filteringOperands: [],
1278+
operator: expressionTree.operator,
1279+
fieldName: expressionTree.fieldName,
1280+
entity: expressionTree.entity,
1281+
returnFields: expressionTree.returnFields
1282+
};
1283+
expressionTree.filteringOperands.forEach(o => o instanceof FilteringExpressionsTree ? exprTreeCopy.filteringOperands.push(this.getExpressionTreeCopy(o)) : exprTreeCopy.filteringOperands.push(o));
1284+
1285+
if (!this.innerQueryNewExpressionTree && shouldAssignInnerQueryExprTree) {
1286+
this.innerQueryNewExpressionTree = exprTreeCopy;
1287+
}
1288+
1289+
return exprTreeCopy;
1290+
}
1291+
12741292
public onSelectAllClicked(_event) {
12751293
if (
12761294
(this._selectedReturnFields.length > 0 && this._selectedReturnFields.length < this._selectedEntity.fields.length) ||
@@ -1380,7 +1398,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
13801398
}
13811399

13821400
for (const expr of expressionTree.filteringOperands) {
1383-
if (isTree(expr)) {
1401+
if (expr instanceof FilteringExpressionsTree) {
13841402
groupItem.children.push(this.createExpressionGroupItem(expr, groupItem, expressionTree.entity));
13851403
} else {
13861404
const filteringExpr = expr as IFilteringExpression;
@@ -1389,7 +1407,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
13891407
condition: filteringExpr.condition,
13901408
conditionName: filteringExpr.condition?.name || filteringExpr.conditionName,
13911409
searchVal: filteringExpr.searchVal,
1392-
searchTree: filteringExpr.searchTree, // this.createExpressionGroupItem(filteringExpr.searchTree, groupItem),
1410+
searchTree: filteringExpr.searchTree,
13931411
ignoreCase: filteringExpr.ignoreCase
13941412
};
13951413
const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
@@ -1506,7 +1524,9 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy {
15061524
this.deleteItem(expressionItem.parent);
15071525
}
15081526

1509-
this.expressionTreeChange.emit(this._expressionTree);
1527+
if (!this.parentExpression) {
1528+
this.expressionTreeChange.emit(this._expressionTree);
1529+
}
15101530
}
15111531

15121532
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();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export class IgxQueryBuilderComponent implements OnDestroy {
189189
* Returns whether the expression tree can be committed in the current state.
190190
*/
191191
public canCommit(): boolean {
192-
return this.queryTree.canCommitCurrentState() === true;
192+
return this.queryTree?.canCommitCurrentState() === true;
193193
}
194194

195195
/**

src/app/query-builder/query-builder.sample.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class QueryBuilderComponent implements OnInit {
4747

4848
public ngOnInit(): void {
4949
this.fieldsEntityA = [
50-
{ field: 'Id', dataType: 'number', formatter: (value: any, rowData: any) => rowData === 'equals' ? value[0].id : value },
50+
{ field: 'Id', dataType: 'number', formatter: (value: any, rowData: any) => rowData === 'equals' ? `${value.map((v: { id: any; }) => v.id)}` : value },
5151
{ field: 'Name', dataType: 'string' },
5252
{ field: 'Validated', dataType: 'boolean' },
5353
{ field: 'Date created', dataType: 'date' },

0 commit comments

Comments
 (0)