igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,201 lines (1,196 loc) • 174 kB
JavaScript
import * as i0 from '@angular/core';
import { Input, HostBinding, Component, Pipe, Injectable, inject, ChangeDetectorRef, ElementRef, LOCALE_ID, EventEmitter, ViewChildren, ViewChild, Output, TemplateRef, Directive, booleanAttribute, ContentChild, NgModule } from '@angular/core';
import { getCurrentResourceStrings, QueryBuilderResourceStringsEN, PlatformUtil, AbsoluteScrollStrategy, VerticalAlignment, HorizontalAlignment, CloseScrollStrategy, ConnectedPositioningStrategy, trackByIdentity, FilteringLogic, DataUtil, AutoPositionStrategy, FilteringExpressionsTree, isTree, GridColumnDataType, IgxStringFilteringOperand, IgxDateTimeFilteringOperand, IgxTimeFilteringOperand, IgxDateFilteringOperand, IgxNumberFilteringOperand, IgxBooleanFilteringOperand, IgxOverlayOutletDirective, IgxPickerClearComponent, IgxPickerToggleComponent, recreateTree } from 'igniteui-angular/core';
import { fromEvent, sampleTime, filter, Subject } from 'rxjs';
import { getLocaleFirstDayOfWeek, NgClass, NgTemplateOutlet, DatePipe } from '@angular/common';
import * as i1 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import { IgxChipComponent } from 'igniteui-angular/chips';
import { IgxDatePickerComponent } from 'igniteui-angular/date-picker';
import { IgxButtonDirective, IgxDateTimeEditorDirective, IgxDragIgnoreDirective, IgxDropDirective, IgxIconButtonDirective, IgxTooltipDirective, IgxTooltipTargetDirective } from 'igniteui-angular/directives';
import { IgxSelectComponent, IgxSelectItemComponent } from 'igniteui-angular/select';
import { IgxTimePickerComponent } from 'igniteui-angular/time-picker';
import { IgxInputDirective, IgxInputGroupComponent, IgxPrefixDirective } from 'igniteui-angular/input-group';
import { IgxIconComponent, IgxIconService } from 'igniteui-angular/icon';
import { IgxComboComponent, IgxComboHeaderDirective } from 'igniteui-angular/combo';
import { IgxCheckboxComponent } from 'igniteui-angular/checkbox';
import { IgxDialogComponent } from 'igniteui-angular/dialog';
import { IgxDropDownComponent, IgxDropDownItemComponent, IgxDropDownItemNavigationDirective } from 'igniteui-angular/drop-down';
import { editor } from '@igniteui/material-icons-extended';
class IgxQueryBuilderHeaderComponent {
constructor() {
this._resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
/**
* Show/hide the legend.
*
* @example
* ```html
* <igx-query-builder-header [showLegend]="false"></igx-query-builder-header>
* ```
* @deprecated in version 19.1.0.
*/
this.showLegend = true;
}
/**
* @hidden @internal
*/
get getClass() {
return 'igx-query-builder__header';
}
/**
* Sets the resource strings.
* By default it uses EN resources.
*
* @deprecated in version 19.1.0.
*/
set resourceStrings(value) {
this._resourceStrings = Object.assign({}, this._resourceStrings, value);
}
/**
* Returns the resource strings.
*/
get resourceStrings() {
return this._resourceStrings;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxQueryBuilderHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.2", type: IgxQueryBuilderHeaderComponent, isStandalone: true, selector: "igx-query-builder-header", inputs: { title: "title", showLegend: "showLegend", resourceStrings: "resourceStrings" }, host: { properties: { "class": "this.getClass" } }, ngImport: i0, template: "<div class=\"igx-query-builder__title\">{{ title }}</div>\n<ng-content></ng-content>\n" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxQueryBuilderHeaderComponent, decorators: [{
type: Component,
args: [{ selector: 'igx-query-builder-header', template: "<div class=\"igx-query-builder__title\">{{ title }}</div>\n<ng-content></ng-content>\n" }]
}], propDecorators: { getClass: [{
type: HostBinding,
args: ['class']
}], title: [{
type: Input
}], showLegend: [{
type: Input
}], resourceStrings: [{
type: Input
}] } });
class IgxFieldFormatterPipe {
transform(value, formatter, rowData, fieldData) {
return formatter(value, rowData, fieldData);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxFieldFormatterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxFieldFormatterPipe, isStandalone: true, name: "fieldFormatter" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxFieldFormatterPipe, decorators: [{
type: Pipe,
args: [{
name: 'fieldFormatter',
standalone: true
}]
}] });
/**
* @hidden @internal
*/
class ExpressionItem {
constructor(parent) {
this.parent = parent;
}
}
/**
* @hidden @internal
*/
class ExpressionGroupItem extends ExpressionItem {
constructor(operator, parent) {
super(parent);
this.operator = operator;
this.children = [];
}
}
/**
* @hidden @internal
*/
class ExpressionOperandItem extends ExpressionItem {
constructor(expression, parent) {
super(parent);
this.expression = expression;
}
}
const IGX_QUERY_BUILDER = 'igx-query-builder';
const IGX_FILTER_TREE = 'igx-filter-tree';
/**
* @hidden @internal
*/
const QueryBuilderSelectors = {
DRAG_INDICATOR: 'igx-drag-indicator',
CHIP_GHOST: 'igx-chip__ghost',
DROP_DOWN_LIST_SCROLL: 'igx-drop-down__list-scroll',
DROP_DOWN_ITEM_DISABLED: 'igx-drop-down__item--disabled',
FILTER_TREE: IGX_FILTER_TREE,
FILTER_TREE_EXPRESSION_CONTEXT_MENU: IGX_FILTER_TREE + '__expression-context-menu',
FILTER_TREE_EXPRESSION_ITEM: IGX_FILTER_TREE + '__expression-item',
FILTER_TREE_EXPRESSION_ITEM_DROP_GHOST: IGX_FILTER_TREE + '__expression-item-drop-ghost',
FILTER_TREE_EXPRESSION_ITEM_KEYBOARD_GHOST: IGX_FILTER_TREE + '__expression-item-keyboard-ghost',
FILTER_TREE_EXPRESSION_ITEM_GHOST: IGX_FILTER_TREE + '__expression-item-ghost',
FILTER_TREE_EXPRESSION_SECTION: IGX_FILTER_TREE + '__expression-section',
FILTER_TREE_LINE_AND: IGX_FILTER_TREE + '__line--and',
FILTER_TREE_LINE_OR: IGX_FILTER_TREE + '__line--or',
FILTER_TREE_SUBQUERY: IGX_FILTER_TREE + '__subquery',
QUERY_BUILDER: IGX_QUERY_BUILDER,
QUERY_BUILDER_BODY: IGX_QUERY_BUILDER + '__main',
QUERY_BUILDER_HEADER: IGX_QUERY_BUILDER + '__header',
QUERY_BUILDER_TREE: IGX_QUERY_BUILDER + '-tree',
};
const DEFAULT_SET_Z_INDEX_DELAY = 10;
const Z_INDEX_TO_SET = 10010; //overlay z-index is 10005
/** @hidden @internal */
class IgxQueryBuilderDragService {
constructor() {
this._keyDragCurrentIndex = 0;
this._keyDragInitialIndex = 0;
this._isKeyDragsFirstMove = true;
}
/** Get the dragged ghost as a HTMLElement*/
get getDragGhostElement() {
return document.querySelector(`.${QueryBuilderSelectors.CHIP_GHOST}[ghostclass="${QueryBuilderSelectors.CHIP_GHOST}"]`);
}
/** Get the drop ghost chip component */
get getDropGhostElement() {
return this._queryBuilderTreeComponent.expressionsChips.find(x => x.data === this.dropGhostExpression);
}
get getMainExpressionTree() {
return this._queryBuilderTreeComponentElRef.nativeElement.querySelector(`.${QueryBuilderSelectors.FILTER_TREE}`);
}
register(tree, el) {
this._queryBuilderTreeComponent = tree;
this._queryBuilderTreeComponentElRef = el;
}
/** When chip is picked up for dragging
*
* @param sourceDragElement The HTML element of the chip that's been dragged
* @param sourceExpressionItem The expressionItem of the chip that's been dragged
* @param isKeyboardDrag If it's a mouse drag or keyboard reorder
*
*/
onMoveStart(sourceDragElement, sourceExpressionItem, isKeyboardDrag) {
this.resetDragAndDrop(true);
this._queryBuilderTreeComponent._expressionTreeCopy = this._queryBuilderTreeComponent._expressionTree;
this.isKeyboardDrag = isKeyboardDrag;
this._sourceExpressionItem = sourceExpressionItem;
this._sourceElement = sourceDragElement;
this.listenToKeyboard();
if (!this.isKeyboardDrag) {
this._sourceElement.style.display = 'none';
this.setDragGhostZIndex();
}
}
/** When dragged chip is let go outside a proper drop zone */
onMoveEnd() {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
if (this.dropGhostExpression) {
//If there is a ghost chip presented to the user, execute drop
this.onChipDropped();
}
else {
this.resetDragAndDrop(true);
}
this._ghostChipMousemoveSubscription$?.unsubscribe();
this._keyboardSubscription$?.unsubscribe();
}
/** When mouse drag enters a chip's area
* @param targetDragElement The HTML element of the drop area chip that's been dragged to
* @param targetExpressionItem The expressionItem of the drop area chip that's been dragged to
*/
onChipEnter(targetDragElement, targetExpressionItem) {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
//If entering the one that's been picked up don't do any thing
if (targetExpressionItem === this.dropGhostExpression) {
return;
}
//Simulate leaving the last entered chip in case of no Leave event triggered due to the artificial drop zone of a north positioned ghost chip
if (this._targetExpressionItem) {
this.resetDragAndDrop(false);
}
this._targetExpressionItem = targetExpressionItem;
//Determine the middle point of the chip.
const appendUnder = this.ghostInLowerPart(targetDragElement);
this.renderDropGhostChip(appendUnder);
}
/** When mouse drag moves in a div's drop area
* @param targetDragElement The HTML element of the drop area chip that's been dragged to
* @param targetExpressionItem The expressionItem of the drop area chip that's been dragged to
*/
onDivOver(targetDragElement, targetExpressionItem) {
if (this._targetExpressionItem === targetExpressionItem) {
this.onChipOver(targetDragElement);
}
else {
this.onChipEnter(targetDragElement, targetExpressionItem);
}
}
/** When mouse drag moves in a chip's drop area
* @param targetDragElement The HTML element of the drop area chip that's been dragged to
*/
onChipOver(targetDragElement) {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
//Determine the middle point of the chip.
const appendUnder = this.ghostInLowerPart(targetDragElement);
this.renderDropGhostChip(appendUnder);
}
/** When mouse drag leaves a chip's drop area */
onChipLeave() {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
//if the drag ghost is on the drop ghost row don't trigger leave
if (this.dragGhostIsOnDropGhostRow()) {
return;
}
if (this._targetExpressionItem) {
this.resetDragAndDrop(false);
}
}
/** When dragged chip is let go in div's drop area
* @param targetExpressionItem The expressionItem of the drop area chip that's been dragged to
*/
onDivDropped(targetExpressionItem) {
if (targetExpressionItem !== this._sourceExpressionItem) {
this.onChipDropped();
}
}
/** When dragged chip is let go in chip's drop area */
onChipDropped() {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
//Determine which chip to be focused after drop completes
const [dropLocationIndex, _] = this.countChipsBeforeDropLocation(this._queryBuilderTreeComponent.rootGroup);
//Delete from old place
this._queryBuilderTreeComponent.deleteItem(this._sourceExpressionItem);
this.dropGhostExpression = null;
this._queryBuilderTreeComponent.focusChipAfterDrag(dropLocationIndex);
this.resetDragAndDrop(true);
this._queryBuilderTreeComponent.exitEditAddMode();
}
/** When mouse drag moves in a AND/OR drop area
* @param targetDragElement The HTML element of the drop area chip that's been dragged to
* @param targetExpressionItem The expressionItem of the drop area chip that's been dragged to
*/
onGroupRootOver(targetDragElement, targetExpressionItem) {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
let newTargetExpressionItem;
if (this.ghostInLowerPart(targetDragElement) || !targetExpressionItem.parent) {
//if ghost is in lower part of the AND/OR (or it's the main group) => drop as first child of that group
//accounting for the fact that the drop ghost might already be there as first child
if (targetExpressionItem.children[0] !== this.dropGhostExpression) {
newTargetExpressionItem = targetExpressionItem.children[0];
}
else {
newTargetExpressionItem = targetExpressionItem.children[1];
}
}
else {
//if ghost is in upper part => drop before the group starts
newTargetExpressionItem = targetExpressionItem;
}
if (this._targetExpressionItem !== newTargetExpressionItem) {
this.resetDragAndDrop(false);
this._targetExpressionItem = newTargetExpressionItem;
this.renderDropGhostChip(false);
}
}
/** When mouse drag moves in 'Add condition' button's drop area
* @param addConditionElement The Add condition button HTML Element
* @param rootGroup The root group of the query tree
*/
onAddConditionEnter(addConditionElement, rootGroup) {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
const lastElement = addConditionElement.parentElement.previousElementSibling.lastElementChild;
//simulate entering in the lower part of the last chip/group
this.onChipEnter(lastElement, rootGroup.children[rootGroup.children.length - 1]);
}
/** When chip's drag indicator is focused
*
* @param sourceDragElement The HTML element of the chip that's been dragged
* @param sourceExpressionItem The expressionItem of the chip that's been dragged
*
*/
onChipDragIndicatorFocus(sourceDragElement, sourceExpressionItem) {
//if drag is not underway, already
if (!this.getDropGhostElement) {
this.onMoveStart(sourceDragElement, sourceExpressionItem, true);
}
}
/** When chip's drag indicator looses focus*/
onChipDragIndicatorFocusOut() {
if (this._sourceElement?.style?.display !== 'none') {
this.resetDragAndDrop(true);
this._keyboardSubscription$?.unsubscribe();
}
}
/** Upon blurring the tree, if Keyboard drag is underway and the next active item is not the drop ghost's drag indicator icon, cancel the drag&drop procedure*/
onDragFocusOut() {
if (this.isKeyboardDrag && this.getDropGhostElement) {
//have to wait a tick because upon blur, the next activeElement is always body, right before the next element gains focus
setTimeout(() => {
if (document.activeElement.className.indexOf(QueryBuilderSelectors.DRAG_INDICATOR) === -1) {
this.resetDragAndDrop(true);
this._keyboardSubscription$?.unsubscribe();
}
}, 0);
}
}
/** Checks if the dragged ghost is horizontally on the same line with the drop ghost*/
dragGhostIsOnDropGhostRow() {
const dragGhostBounds = this.getDragGhostElement.getBoundingClientRect();
const dropGhostBounds = this.getDropGhostElement?.nativeElement?.parentElement.getBoundingClientRect();
if (!dragGhostBounds || !dropGhostBounds) {
return false;
}
const tolerance = dragGhostBounds.bottom - dragGhostBounds.top;
return !(dragGhostBounds.bottom < dropGhostBounds.top - tolerance || dragGhostBounds.top > dropGhostBounds.bottom + tolerance);
}
/** Checks if the dragged ghost is north or south of a target element's center*/
ghostInLowerPart(ofElement) {
const ghostBounds = this.getDragGhostElement.getBoundingClientRect();
const targetBounds = ofElement.getBoundingClientRect();
return ((ghostBounds.top + ghostBounds.bottom) / 2) >= ((targetBounds.top + targetBounds.bottom) / 2);
}
/** Make a copy of the _sourceExpressionItem's chip and paste it in the tree north or south of the _targetExpressionItem's chip */
renderDropGhostChip(appendUnder) {
if (appendUnder !== this._dropUnder || this.isKeyboardDrag) {
this.clearDropGhost();
//Copy dragged chip
const dragCopy = { ...this._sourceExpressionItem };
dragCopy.parent = this._targetExpressionItem.parent;
this.dropGhostExpression = dragCopy;
//Paste chip
this._dropUnder = appendUnder;
const pasteIndex = this._targetExpressionItem.parent.children.indexOf(this._targetExpressionItem);
this._targetExpressionItem.parent.children.splice(pasteIndex + (this._dropUnder ? 1 : 0), 0, dragCopy);
}
//Put focus on the drag icon of the ghost while performing keyboard drag
if (this.isKeyboardDrag) {
setTimeout(() => {
const dropGhostDragIndicator = this.getDropGhostElement?.nativeElement?.querySelector(`.${QueryBuilderSelectors.DRAG_INDICATOR}`);
if (dropGhostDragIndicator) {
dropGhostDragIndicator.focus();
}
}, 0);
}
//Attach a mousemove event listener (if not already in place) to the dragged ghost (if present)
if (!this.isKeyboardDrag && this.getDragGhostElement && (!this._ghostChipMousemoveSubscription$ || this._ghostChipMousemoveSubscription$?.closed === true)) {
const mouseMoves = fromEvent(this.getDragGhostElement, 'mousemove');
//When mouse moves and there is a drop ghost => trigger onChipLeave to check if the drop ghost has to be removed
//effectively solving the case when mouse leaves the QB and a drop ghost is still in place
this._ghostChipMousemoveSubscription$ = mouseMoves.pipe(sampleTime(100)).subscribe(() => {
if (this.getDropGhostElement) {
this.onChipLeave();
}
});
}
this.setDragCursor('grab');
}
/** Set the cursor when dragging a ghost*/
setDragCursor(cursor) {
if (this.getDragGhostElement) {
this.getDragGhostElement.style.cursor = cursor;
}
}
/** Removes the drop ghost expression from the tree and it's chip effectively */
clearDropGhost() {
if (this.dropGhostExpression) {
const children = this.dropGhostExpression.parent.children;
const delIndex = children.indexOf(this.dropGhostExpression);
children.splice(delIndex, 1);
this.dropGhostExpression = null;
}
}
/** Reset Drag&Drop vars. Optionally the drag source vars too*/
resetDragAndDrop(clearDragged) {
this._targetExpressionItem = null;
this._dropUnder = null;
this.clearDropGhost();
this._keyDragInitialIndex = 0;
this._keyDragCurrentIndex = 0;
this._possibleDropLocations = null;
this._isKeyDragsFirstMove = true;
this.setDragCursor('no-drop');
if (this._queryBuilderTreeComponent._expressionTreeCopy) {
this._queryBuilderTreeComponent._expressionTree = this._queryBuilderTreeComponent._expressionTreeCopy;
}
if ((clearDragged || this.isKeyboardDrag) && this._sourceElement) {
this._sourceElement.style.display = '';
}
if (clearDragged) {
this._queryBuilderTreeComponent._expressionTreeCopy = null;
this._sourceExpressionItem = null;
this._sourceElement = null;
}
}
/** Start listening for drag and drop specific keys */
listenToKeyboard() {
this._keyboardSubscription$?.unsubscribe();
this._keyboardSubscription$ = fromEvent(this.getMainExpressionTree, 'keydown')
.pipe(filter(e => ['ArrowUp', 'ArrowDown', 'Enter', 'Space', 'Escape', 'Tab'].includes(e.key)))
// .pipe(tap(e => {
// //Inhibit Tabs if keyboard drag is underway (don't allow to loose focus of the drop ghost's drag indicator)
// if (e.key === 'Tab' && this.getDropGhostElement) {
// e.preventDefault();
// }
// }))
.pipe(filter(event => !event.repeat))
.subscribe(e => {
if (e.key === 'Escape') {
//TODO cancel mouse drag once it's implemented in igx-chip draggable
this.resetDragAndDrop(false);
//Regain focus on the drag icon after keyboard drag cancel
if (this.isKeyboardDrag) {
this._sourceElement.firstElementChild.firstElementChild.firstElementChild.firstElementChild.focus();
}
}
else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
this.arrowDrag(e.key);
}
else if (e.key === 'Enter' || e.key === 'Space') {
//this.platform.isActivationKey(eventArgs) Maybe use this rather that Enter/Space?
this.onChipDropped();
this._keyboardSubscription$.unsubscribe();
}
});
}
/** Perform up/down movement of drop ghost along the expression tree*/
arrowDrag(key) {
if (!this._sourceElement || !this._sourceExpressionItem) {
return;
}
const rootGroup = this._queryBuilderTreeComponent.rootGroup;
if (this._isKeyDragsFirstMove) {
this._possibleDropLocations = this.getPossibleDropLocations(rootGroup, true);
this._keyDragInitialIndex = this._possibleDropLocations.findIndex(e => e[0] === this._sourceExpressionItem && e[1] === true);
this._keyDragCurrentIndex = this._keyDragInitialIndex;
if (this._keyDragInitialIndex === -1) {
console.error("Dragged expression not found");
}
this._sourceElement.style.display = 'none';
}
let newKeyIndexOffset = this._keyDragCurrentIndex;
if (key === 'ArrowUp') {
//decrease index capped at top of tree
newKeyIndexOffset && newKeyIndexOffset--;
}
else if (key === 'ArrowDown') {
//increase index capped at bottom of tree
newKeyIndexOffset < this._possibleDropLocations.length - 1 && newKeyIndexOffset++;
}
else {
console.error('wrong key');
return;
}
//if drop location has no change
if (newKeyIndexOffset !== this._keyDragCurrentIndex || this._isKeyDragsFirstMove) {
this._keyDragCurrentIndex = newKeyIndexOffset;
const newDropTarget = this._possibleDropLocations[this._keyDragCurrentIndex];
this._targetExpressionItem = newDropTarget[0];
this.renderDropGhostChip(newDropTarget[1]);
//Situations when drop ghost hasn't really moved, run one more time
if (this._keyDragCurrentIndex === this._keyDragInitialIndex ||
(this._isKeyDragsFirstMove && this._keyDragCurrentIndex === this._keyDragInitialIndex - 1)) {
this._isKeyDragsFirstMove = false;
this.arrowDrag(key);
}
this._isKeyDragsFirstMove = false;
}
return;
}
/** Produces a flat ordered list of possible drop locations as Tuple <[targetExpression, dropUnder]>, while performing the keyboard drag&drop */
getPossibleDropLocations(group, isRoot) {
const result = new Array();
//Add dropZone under AND/OR (as first child of group)
result.push([group.children[0], false]);
for (let i = 0; i < group.children.length; i++) {
if (group.children[i] instanceof ExpressionGroupItem) {
result.push(...this.getPossibleDropLocations(group.children[i], false));
}
else {
result.push([group.children[i], true]);
}
}
//Add dropZone under the whole group
if (!isRoot) {
result.push([group, true]);
}
return result;
}
/** Counts how many chips will be in the tree (from top to bottom) before the dropped one */
countChipsBeforeDropLocation(group) {
let count = 0, totalCount = 0, targetReached = false;
for (let i = 0; i < group.children.length; i++) {
const child = group.children[i];
if (targetReached) {
break;
}
if (child instanceof ExpressionGroupItem) {
if (child === this._targetExpressionItem) {
if (this._dropUnder) {
[count] = this.countChipsBeforeDropLocation(child);
totalCount += count;
}
targetReached = true;
}
else {
[count, targetReached] = this.countChipsBeforeDropLocation(child);
totalCount += count;
}
}
else {
if (child !== this._sourceExpressionItem && //not the hidden source chip
child !== this.dropGhostExpression && //not the drop ghost
!(child.inEditMode && this._queryBuilderTreeComponent.operandCanBeCommitted() !== true) //not a chip in edit mode that will be discarded
) {
totalCount++;
}
if (child === this._targetExpressionItem) {
targetReached = true;
if (!this._dropUnder &&
!(child.inEditMode && this._queryBuilderTreeComponent.operandCanBeCommitted() !== true)) {
totalCount--;
}
}
}
}
totalCount === -1 && totalCount++;
return [totalCount, targetReached];
}
/** Sets the z-index of the drag ghost with a little delay, since we don't have access to ghostCreated() but we know it's executed right after moveStart() */
setDragGhostZIndex() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
}
this._timeoutId = setTimeout(() => {
if (this.getDragGhostElement?.style) {
this.getDragGhostElement.style.zIndex = `${Z_INDEX_TO_SET}`;
}
}, DEFAULT_SET_Z_INDEX_DELAY);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxQueryBuilderDragService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxQueryBuilderDragService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxQueryBuilderDragService, decorators: [{
type: Injectable
}] });
const DEFAULT_PIPE_DATE_FORMAT = 'mediumDate';
const DEFAULT_PIPE_TIME_FORMAT = 'mediumTime';
const DEFAULT_PIPE_DATE_TIME_FORMAT = 'medium';
const DEFAULT_PIPE_DIGITS_INFO = '1.0-3';
const DEFAULT_CHIP_FOCUS_DELAY = 50;
/** @hidden */
class IgxQueryBuilderTreeComponent {
/**
* @hidden @internal
*/
get getClass() {
return `igx-query-builder-tree--level-${this.level}`;
}
/**
* Returns the parent expression operand.
*/
get parentExpression() {
return this._parentExpression;
}
/**
* Sets the parent expression operand.
*/
set parentExpression(value) {
this._parentExpression = value;
}
/**
* Returns the fields.
*/
get fields() {
if (!this._fields && this.isAdvancedFiltering()) {
this._fields = this.entities[0].fields;
}
return this._fields;
}
/**
* Sets the fields.
*/
set fields(fields) {
this._fields = fields;
this._fields = this._fields?.map(f => ({ ...f, filters: this.getFilters(f), pipeArgs: this.getPipeArgs(f) }));
if (!this._fields && this.isAdvancedFiltering()) {
this._fields = this.entities[0].fields;
}
}
/**
* Returns the expression tree.
*/
get expressionTree() {
return this._expressionTree;
}
/**
* Sets the expression tree.
*/
set expressionTree(expressionTree) {
this._expressionTree = expressionTree;
if (!expressionTree) {
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) {
this.init();
}
}
/**
* Gets the `locale` of the query builder.
* If not set, defaults to application's locale.
*/
get locale() {
return this._locale;
}
/**
* Sets the `locale` of the query builder.
* Expects a valid BCP 47 language tag.
*/
set locale(value) {
this._locale = value;
// if value is invalid, set it back to _localeId
try {
getLocaleFirstDayOfWeek(this._locale);
}
catch {
this._locale = this._localeId;
}
}
/**
* Sets the resource strings.
* By default it uses EN resources.
*/
set resourceStrings(value) {
this._resourceStrings = Object.assign({}, this._resourceStrings, value);
}
/**
* Returns the resource strings.
*/
get resourceStrings() {
return this._resourceStrings;
}
set editingInputsContainer(value) {
if ((value && !this._editingInputsContainer) ||
(value && this._editingInputsContainer && this._editingInputsContainer.nativeElement !== value.nativeElement)) {
requestAnimationFrame(() => {
this.scrollElementIntoView(value.nativeElement);
});
}
this._editingInputsContainer = value;
}
/** @hidden */
get editingInputsContainer() {
return this._editingInputsContainer;
}
set currentGroupButtonsContainer(value) {
if ((value && !this._currentGroupButtonsContainer) ||
(value && this._currentGroupButtonsContainer && this._currentGroupButtonsContainer.nativeElement !== value.nativeElement)) {
requestAnimationFrame(() => {
this.scrollElementIntoView(value.nativeElement);
});
}
this._currentGroupButtonsContainer = value;
}
/** @hidden */
get currentGroupButtonsContainer() {
return this._currentGroupButtonsContainer;
}
/**
* Returns if the select entity dropdown at the root level is disabled after the initial selection.
*/
get disableEntityChange() {
return !this.parentExpression && this.selectedEntity ? this.queryBuilder.disableEntityChange : false;
}
/**
* Returns if the fields combo at the root level is disabled.
*/
get disableReturnFieldsChange() {
return !this.selectedEntity || this.queryBuilder.disableReturnFieldsChange;
}
/**
* Returns the current level.
*/
get level() {
let parent = this.elRef.nativeElement.parentElement;
let _level = 0;
while (parent) {
if (parent.localName === 'igx-query-builder-tree') {
_level++;
}
parent = parent.parentElement;
}
return _level;
}
/** @hidden */
isAdvancedFiltering() {
return (this.entities?.length === 1 && !this.entities[0]?.name) ||
this.entities?.find(e => e.childEntities?.length > 0) !== undefined ||
(this.entities?.length > 0 && this.queryBuilder?.entities?.length > 0 && this.entities !== this.queryBuilder?.entities);
}
/** @hidden */
isHierarchicalNestedQuery() {
return this.queryBuilder.entities !== this.entities;
}
/** @hidden */
isSearchValueInputDisabled() {
return !this.selectedField ||
!this.selectedCondition ||
(this.selectedField &&
(this.selectedField.filters.condition(this.selectedCondition).isUnary ||
this.selectedField.filters.condition(this.selectedCondition).isNestedQuery));
}
constructor() {
this.cdr = inject(ChangeDetectorRef);
this.dragService = inject(IgxQueryBuilderDragService);
this.platform = inject(PlatformUtil);
this.elRef = inject(ElementRef);
this._localeId = inject(LOCALE_ID);
/**
* Sets/gets the search value template.
*/
this.searchValueTemplate = null;
/**
* Gets/sets the expected return field.
*/
this.expectedReturnField = null;
/**
* Event fired as the expression tree is changed.
*/
this.expressionTreeChange = new EventEmitter();
/**
* Event fired if a nested query builder tree is being edited.
*/
this.inEditModeChange = new EventEmitter();
/**
* @hidden @internal
*/
this.selectedExpressions = [];
/**
* @hidden @internal
*/
this.searchValue = { value: null };
/**
* @hidden @internal
*/
this.initialOperator = 0;
/**
* @hidden @internal
*/
this.returnFieldSelectOverlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true
};
/**
* @hidden @internal
*/
this.entitySelectOverlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true
};
/**
* @hidden @internal
*/
this.fieldSelectOverlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true
};
/**
* @hidden @internal
*/
this.conditionSelectOverlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true
};
/**
* @hidden @internal
*/
this.addExpressionDropDownOverlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true
};
/**
* @hidden @internal
*/
this.groupContextMenuDropDownOverlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true
};
this.destroy$ = new Subject();
this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
this._preventInit = false;
this._expandedExpressions = [];
this._resourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN);
this._positionSettings = {
horizontalStartPoint: HorizontalAlignment.Right,
verticalStartPoint: VerticalAlignment.Top
};
this._overlaySettings = {
closeOnOutsideClick: false,
modal: false,
positionStrategy: new ConnectedPositioningStrategy(this._positionSettings),
scrollStrategy: new CloseScrollStrategy()
};
/**
* @hidden @internal
*/
this.deleteItem = (expressionItem, skipEmit = false) => {
if (!expressionItem.parent) {
this.rootGroup = null;
this.currentGroup = null;
//this._expressionTree = null;
return;
}
if (expressionItem === this.currentGroup) {
this.currentGroup = this.currentGroup.parent;
}
const children = expressionItem.parent.children;
const index = children.indexOf(expressionItem);
children.splice(index, 1);
const entity = this.expressionTree ? this.expressionTree.entity : null;
const returnFields = this.expressionTree ? this.expressionTree.returnFields : null;
this._expressionTree = this.createExpressionTreeFromGroupItem(this.rootGroup, entity, returnFields); // TODO: don't recreate if not necessary
if (!children.length) {
this.deleteItem(expressionItem.parent, true);
}
if (!this.parentExpression && !skipEmit) {
this.expressionTreeChange.emit(this._expressionTree);
}
};
/**
* @hidden @internal
*/
this.focusChipAfterDrag = (index) => {
this._lastFocusedChipIndex = index;
this.focusEditedExpressionChip();
};
/** rootGroup is recreated after clicking Apply, which sets new expressionTree and calls init()*/
this.trackExpressionItem = trackByIdentity;
const elRef = this.elRef;
this.locale = this.locale || this._localeId;
this.dragService.register(this, elRef);
}
/**
* @hidden @internal
*/
ngAfterViewInit() {
this._overlaySettings.outlet = this.overlayOutlet;
this.entitySelectOverlaySettings.outlet = this.overlayOutlet;
this.fieldSelectOverlaySettings.outlet = this.overlayOutlet;
this.conditionSelectOverlaySettings.outlet = this.overlayOutlet;
this.returnFieldSelectOverlaySettings.outlet = this.overlayOutlet;
this.addExpressionDropDownOverlaySettings.outlet = this.overlayOutlet;
this.groupContextMenuDropDownOverlaySettings.outlet = this.overlayOutlet;
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
this.cdr.detectChanges();
}
/**
* @hidden @internal
*/
ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.complete();
}
/**
* @hidden @internal
*/
set selectedEntity(value) {
this._selectedEntity = this.entities?.find(el => el.name === value);
}
/**
* @hidden @internal
*/
get selectedEntity() {
return this._selectedEntity;
}
/**
* @hidden @internal
*/
onEntitySelectChanging(event) {
event.cancel = true;
this._entityNewValue = event.newSelection.value;
if (event.oldSelection.value && this.queryBuilder.showEntityChangeDialog) {
this.entityChangeDialog.open();
}
else {
this.onEntityChangeConfirm();
}
}
/**
* @hidden
*/
onShowEntityChangeDialogChange(eventArgs) {
this.queryBuilder.showEntityChangeDialog = !eventArgs.checked;
}
/**
* @hidden
*/
onEntityChangeCancel() {
this.entityChangeDialog.close();
this.entitySelect.close();
this._entityNewValue = null;
}
/**
* @hidden
*/
onEntityChangeConfirm() {
if (this._parentExpression) {
this._expressionTree = this.createExpressionTreeFromGroupItem(this.createExpressionGroupItem(this._expressionTree));
}
this._selectedEntity = this._entityNewValue;
if (!this._selectedEntity.fields) {
this._selectedEntity.fields = [];
}
this.fields = this._entityNewValue ? this._entityNewValue.fields : [];
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);
}
if (this._expressionTree) {
this._expressionTree.entity = this._entityNewValue.name;
const returnFields = Array.isArray(this._selectedReturnFields) ? this._selectedReturnFields : [this._selectedReturnFields];
this._expressionTree.returnFields = this.fields.length === returnFields.length ? ['*'] : returnFields;
this._expressionTree.filteringOperands = [];
this._editedExpression = null;
if (!this.parentExpression) {
this.expressionTreeChange.emit(this._expressionTree);
}
this.rootGroup = null;
this.currentGroup = this.rootGroup;
}
this._selectedField = null;
this.selectedCondition = null;
this.searchValue.value = null;
this.entityChangeDialog.close();
this.entitySelect.close();
this._entityNewValue = null;
this.innerQueryNewExpressionTree = null;
this.initExpressionTree(this._selectedEntity.name, this.selectedReturnFields);
}
/**
* @hidden @internal
*/
set selectedReturnFields(value) {
if (this._selectedReturnFields !== value) {
this._selectedReturnFields = value;
if (this._expressionTree && !this.parentExpression) {
this._expressionTree.returnFields = value.length === this.fields.length ? ['*'] : value;
this.expressionTreeChange.emit(this._expressionTree);
}
}
}
/**
* @hidden @internal
*/
get selectedReturnFields() {
if (typeof this._selectedReturnFields == 'string') {
return [this._selectedReturnFields];
}
return this._selectedReturnFields;
}
/**
* @hidden @internal
*/
set selectedField(value) {
const oldValue = this._selectedField;
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;
this.cdr.detectChanges();
}
}
}
/**
* @hidden @internal
*/
get selectedField() {
return this._selectedField;
}
/**
* @hidden @internal
*
* used by the grid
*/
setPickerOutlet(outlet) {
this.pickerOutlet = outlet;
}
/**
* @hidden @internal
*
* used by the grid
*/
get isContextMenuVisible() {
return !this.groupContextMenuDropDown.collapsed;
}
/**
* @hidden @internal
*/
get hasEditedExpression() {
return this._editedExpression !== undefined && this._editedExpression !== null;
}
/**
* @hidden @internal
*/
addCondition(parent, afterExpression, isUIInteraction) {
this.cancelOperandAdd();
const operandItem = new ExpressionOperandItem({
fieldName: null,
condition: null,
conditionName: null,
ignoreCase: true,
searchVal: null
}, parent);
const groupItem = new ExpressionGroupItem(this.getOperator(null) ?? FilteringLogic.And, parent);
this.contextualGroup = groupItem;
this.initialOperator = null;
this._lastFocusedChipIndex = this._lastFocusedChipIndex === undefined ? -1 : this._lastFocusedChipIndex;
if (parent) {
if (afterExpression) {
const index = parent.children.indexOf(afterExpression);
parent.children.splice(index + 1, 0, operandItem);
}
else {
parent.children.push(operandItem);
}
this._lastFocusedChipIndex++;
}
else {
this.rootGroup = groupItem;
operandItem.parent = groupItem;
this.rootGroup.children.push(operandItem);
this._lastFocusedChipIndex = 0;
}
this._focusDelay = 250;
if (isUIInteraction && !afterExpression) {
this._lastFocusedChipIndex = this.expressionsChips.length;
this._focusDelay = DEFAULT_CHIP_FOCUS_DELAY;
}
this.enterExpressionEdit(operandItem);
}
/**
* @hidden @internal
*/
addReverseGroup(parent, afterExpression) {
parent = parent ?? this.rootGroup;
if (parent.operator === FilteringLogic.And) {
this.addGroup(FilteringLogic.Or, parent, afterExpression);
}
else {
this.addGroup(FilteringLogic.And, parent, afterExpression);
}
}
/**
* @hidden @internal
*/
endGroup(groupItem) {
this.currentGroup = groupItem.parent;
}
/**
* @hidden @internal
*/
commitExpression() {
this.commitOperandEdit();
this.focusEditedExpressionChip();
}
/**
* @hidden @internal
*/
discardExpression(expressionItem) {
this.cancelOperandEdit();
if (expressionItem && expressionItem.expression.fieldName) {
this.focusEditedExpressionChip();
}
}
/**
* @hidden @internal
*/
commitOperandEdit() {
const actualSearchValue = this.searchValue.value;
if (this._editedExpression) {
this._editedExpression.expression.fieldName = this.selectedField.field;
this._editedExpression.expression.condition = this.selectedField.filters.condition(this.selectedCondition);
this._editedExpression.expression.conditionName = this.selectedCondition;
this._editedExpression.expression.searchVal = DataUtil.parseValue(this.selectedField.dataType, actualSearchValue) || actualSearchValue;
this._editedExpression.fieldLabel = this.selectedField.label
? this.selectedField.label
: this.selectedField.header
? this.selectedField.header
: this.selectedField.field;
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
if (innerQuery && this.selectedField?.filters?.condition(this.selectedCondition)?.isNestedQuery) {
innerQuery.exitEditAddMode();
this._editedExpression.expression.searchTree = this.getExpressionTreeCopy(innerQuery.expressionTree);
const returnFields = innerQuery.selectedReturnFields.length > 0 ?
innerQuery.selectedReturnFields :
[innerQuery.fields[0].field];
this._editedExpression.expression.searchTree.returnFields = returnFields;
}
else {
this._editedExpression.expression.searchTree = null;
}
this.innerQueryNewExpressionTree = null;
if (this.selectedField.filters.condition(this.selectedCondition)?.isUnary || this.selectedField.filters.condition(this.selectedCondition)?.isNestedQuery) {
this._editedExpression.expression.searchVal = null;
}
this._editedExpression.inEditMode = false;
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);
}
}
/**
* @hidden @internal
*/
cancelOperandAdd() {
if (this._addModeExpression) {
this._addModeExpression.inAddMode = false;
this._addModeExpression = null;
}
}
/**
* @hidden @internal
*/
cancelOperandEdit() {
if (this.innerQueries) {
const innerQuery = this.innerQueries.filter(q => q.isInEditMode())[0];
if (innerQuery) {
if (innerQuery._editedExpression) {
innerQuery.cancelOperandEdit();
}
innerQuery.expressionTree = this.getExpressionTreeCopy(this._editedExpression.expression.searchTree);
this.innerQueryNewExpressionTree = null;
}
}