diff options
Diffstat (limited to 'public/src/app/rule-engine/condition')
4 files changed, 415 insertions, 0 deletions
diff --git a/public/src/app/rule-engine/condition/condition.component.html b/public/src/app/rule-engine/condition/condition.component.html new file mode 100644 index 0000000..a441f55 --- /dev/null +++ b/public/src/app/rule-engine/condition/condition.component.html @@ -0,0 +1,89 @@ +<tree-root #tree class="condition-tree" (initialized)="onInitialized(tree)" [nodes]="conditionTree" [options]="customTemplateStringOptions"> + <ng-template #treeNodeTemplate let-node let-index="index"> + + <div> + <div *ngIf="node.data.name === 'operator'" style="background: #F2F2F2;"> + <div style="display: flex; margin-left: 5px; align-items: center; min-height: 35px;"> + <div style="display: flex; align-items: center;" *ngIf="showType"> + <select style="padding: 5px;" [(ngModel)]="node.data.type"> + <option value="ANY">ANY</option> + <option value="ALL">ALL</option> + </select> + + <div style="display: flex; align-items: center; margin-left: 10px;"> + of the following are true: + </div> + </div> + + <div style="display: flex; margin-left: auto;"> + + <div style="display: flex; align-items: center; padding: 0 25px;"> + <button mat-mini-fab color="primary" (click)="addConditional(tree, node)" style="height: 24px; width: 24px; display:flex; box-shadow: none;"> + <mat-icon class="material-icons md-18">add</mat-icon> + </button> + <span class="btn-label">Add Condition + </span> + </div> + + <div style="display: flex; align-items: center; padding: 0 25px;"> + <button mat-mini-fab color="primary" data-tests-id="addConditionGroup" [disabled]="node.data.level === 2" (click)="addConditionalGroup(tree, node)" + style="height: 24px; width: 24px; display:flex; box-shadow: none;"> + <mat-icon class="material-icons md-18">add</mat-icon> + </button> + <span [style.color]="node.data.level === 2 ? '#a7a7a7' : '#009fdb' " [style.cursor]="node.data.level === 2 ? 'default' : 'pointer' " + class="btn-label">Add Condition Group + </span> + </div> + + <div style="display: flex; align-items: center; padding: 0 5px; background: #FFFFFF;"> + <button mat-icon-button (click)="removeConditional(tree, node)" class="button-remove"> + <mat-icon class="md-24">delete</mat-icon> + </button> + </div> + + </div> + </div> + </div> + <div *ngIf="node.data.name === 'condition'"> + <div class="from-conatiner" style="height:35px; "> + <div style="display: flex; width:90%;"> + <div class="label" style="width:100%"> + <span class="label" style="padding: 0 10px; border-left: none;"> + Input + </span> + <input class="input-text" data-tests-id="left" [(ngModel)]="node.data.left" (ngModelChange)="modelChange($event)" ngDefaultControl + type="text"> + </div> + + <div style="margin: 0 1rem;"> + <select style="height: 30px;" data-tests-id="selectOperator" [(ngModel)]="node.data.operator" (ngModelChange)="modelChange($event)" + ngDefaultControl> + <option [ngValue]="null" disabled>Select operator</option> + <option value="contains">Contains</option> + <option value="endsWith">Ends with</option> + <option value="startsWith">Starts with</option> + <option value="equals">Equals</option> + <option value="notEqual">Not equal</option> + </select> + </div> + + <div class="label" style="width:100%"> + <span class="label" style="padding: 0 10px; border-left: none;"> + Value + </span> + <input class="input-text" data-tests-id="right" (ngModelChange)="modelChange($event)" [(ngModel)]="node.data.right" ngDefaultControl + type="text"> + </div> + </div> + <!-- remove button --> + <div class="show-delete"> + <button mat-icon-button (click)="removeConditional(tree, node)" class="button-remove"> + <mat-icon class="md-24">delete</mat-icon> + </button> + </div> + + </div> + </div> + </div> + </ng-template> +</tree-root> diff --git a/public/src/app/rule-engine/condition/condition.component.scss b/public/src/app/rule-engine/condition/condition.component.scss new file mode 100644 index 0000000..8c0e9e0 --- /dev/null +++ b/public/src/app/rule-engine/condition/condition.component.scss @@ -0,0 +1,114 @@ +.condition-tree { + tree-viewport { + overflow-x: hidden; + overflow-y: hidden; + } + .angular-tree-component, + .tree-node-leaf { + margin: 0; + padding: 0; + } + .angular-tree-component { + padding-left: 1em; + overflow-y: hidden; + } + .tree-node-leaf.container { + border-bottom: 0px; + } + .tree-node-leaf.empty { + font-style: italic; + color: #fafafa; + border-color: #fafafa; + } + .tree-node-leaf div { + margin: 0; + top: 0.5em; + } + .node-wrapper { + background: white; + } + .tree-children { + border-left: 2px solid #f2f2f2; + // border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + } + tree-node-expander { + display: none; + } + .node-content-wrapper { + padding-left: 0; + width: 100%; + .show-delete { + opacity: 0; + } + } + .tree-node-content { + width: 100%; + } + .node-content-wrapper-active, + .node-content-wrapper.node-content-wrapper-active:hover, + .node-content-wrapper-active.node-content-wrapper-focused { + background: white; + } + *:focus { + outline: none; + } + + .node-content-wrapper-active, + .node-content-wrapper-focused, + .node-content-wrapper:hover { + box-shadow: none; + .show-delete { + opacity: 1; + display: flex; + align-items: center; + padding: 0 5px; + } + } +} + +.from-conatiner { + display: flex; + align-items: center; + .input-text { + border: none; + flex: 1; + // width: 250px; + padding: 5px 0 5px 5px; + margin: 0; + } + .label { + border: 1px solid #d2d2d2; + height: 30px; + justify-content: center; + align-items: center; + display: flex; + cursor: default; + } +} + +.btn-label { + display: flex; + justify-content: center; + padding-left: 5px; + color: #009fdb; +} + +.button-label { + color: #a7a7a7; + display: flex; + justify-content: center; + padding-left: 5px; + &:hover { + color: #009fdb; + } +} + +.button-remove { + display: flex; + justify-content: center; + color: #a7a7a7; + &:hover { + color: #009fdb; + } +} diff --git a/public/src/app/rule-engine/condition/condition.component.spec.ts b/public/src/app/rule-engine/condition/condition.component.spec.ts new file mode 100644 index 0000000..bb0d38a --- /dev/null +++ b/public/src/app/rule-engine/condition/condition.component.spec.ts @@ -0,0 +1,51 @@ +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { + MatDialogModule, + MatButtonModule, + MatIconModule, + MatDialogRef, + MAT_DIALOG_DATA +} from '@angular/material'; + +import { ConditionComponent } from './condition.component'; + +describe('Condition Component', () => { + let component: ConditionComponent; + let fixture: ComponentFixture<ConditionComponent>; + let de: DebugElement; + let el: HTMLElement; + + beforeEach( + async(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + HttpModule, + MatDialogModule, + MatButtonModule, + MatIconModule + ], + providers: [], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [ConditionComponent] + }).compileComponents(); + }) + ); + + beforeEach(() => { + // create component and test fixture + fixture = TestBed.createComponent(ConditionComponent); + // get test component from the fixture + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/public/src/app/rule-engine/condition/condition.component.ts b/public/src/app/rule-engine/condition/condition.component.ts new file mode 100644 index 0000000..f44fbf4 --- /dev/null +++ b/public/src/app/rule-engine/condition/condition.component.ts @@ -0,0 +1,161 @@ +import { + Component, + ViewEncapsulation, + ViewChild, + Output, + EventEmitter +} from '@angular/core'; +import { TreeModel, TreeComponent, ITreeOptions } from 'angular-tree-component'; +import { some } from 'lodash'; + +@Component({ + selector: 'app-condition', + templateUrl: './condition.component.html', + styleUrls: ['./condition.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ConditionComponent { + conditionTree = []; + showType = false; + @ViewChild(TreeComponent) private tree: TreeComponent; + @Output() onConditionChange = new EventEmitter(); + @Output() removeConditionCheck = new EventEmitter(); + customTemplateStringOptions: ITreeOptions = { + isExpandedField: 'expanded', + animateExpand: true, + animateSpeed: 30, + animateAcceleration: 1.2 + }; + + constructor() { + this.conditionTree.push({ + name: 'operator', + level: 0, + type: 'ALL', + children: [] + }); + this.conditionTree[0].children.push({ + name: 'condition', + left: '', + right: '', + operator: null, + level: 1 + }); + } + + onInitialized(tree) { + tree.treeModel.expandAll(); + } + + updateMode(isSingle, data) { + if (isSingle) { + this.conditionTree[0].children.pop(); + if (typeof data.right !== 'string') { + data.right = data.right.join(','); + } + this.conditionTree[0].children.push({ + name: 'condition', + left: data.left, + right: data.right, + operator: data.operator, + level: 1 + }); + this.showType = false; + } else { + this.conditionTree = data; + setTimeout(() => (this.showType = true), 500); + } + this.tree.treeModel.update(); + } + + addConditional(tree, selectedNode) { + if (this.conditionTree[0].children.length > 0) { + this.showType = true; + } + const tempLevel = + selectedNode.data.name === 'condition' + ? selectedNode.data.level + : selectedNode.data.children[0].level; + + const conditionTemplate = { + name: 'condition', + left: '', + right: '', + operator: null, + level: tempLevel + }; + selectedNode.data.children.push(conditionTemplate); + tree.treeModel.update(); + } + + addConditionalGroup(tree, selectedNode) { + if (selectedNode.level < 3) { + if (this.conditionTree[0].children.length > 0) { + this.showType = true; + } + selectedNode.data.children.push({ + name: 'operator', + level: selectedNode.data.level + 1, + type: 'ALL', + children: [] + }); + + for (let i = 0; i < 2; i++) { + selectedNode.data.children[ + selectedNode.data.children.length - 1 + ].children.push({ + name: 'condition', + left: '', + right: '', + operator: null, + level: selectedNode.data.level + 2 + }); + } + tree.treeModel.update(); + tree.treeModel.expandAll(); + } + } + + removeConditional(tree, selectedNode) { + if ( + (selectedNode.level === 1 && selectedNode.index === 0) || + (selectedNode.parent.data.name === 'operator' && + selectedNode.parent.level === 1 && + selectedNode.parent.data.children.length === 1) + ) { + this.removeConditionCheck.emit(false); + } else if ( + selectedNode.parent.level === 1 && + selectedNode.parent.data.children.length === 2 && + selectedNode.data.name === 'condition' && + some(selectedNode.parent.data.children, { name: 'operator' }) + ) { + return; + } else { + if ( + selectedNode.parent.data.name === 'operator' && + selectedNode.parent.level > 1 + ) { + // Nested Group can delete when more then 2 + if (selectedNode.parent.data.children.length > 2) { + this.deleteNodeAndUpdateTreeView(selectedNode, tree); + } + } else { + this.deleteNodeAndUpdateTreeView(selectedNode, tree); + if (this.conditionTree[0].children.length === 1) { + this.showType = false; + } + } + } + } + + private deleteNodeAndUpdateTreeView(selectedNode: any, tree: any) { + selectedNode.parent.data.children.splice(selectedNode.index, 1); + tree.treeModel.update(); + this.onConditionChange.emit(this.conditionTree); + } + + modelChange(event) { + this.onConditionChange.emit(this.conditionTree); + } +} |