Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Angular

Condition Editor

PREREQUISITES ............................................................................................................................... 2

ANGULAR VERSION AND FRONT-END FRAMEWORK ....................................................................... 2

DELIVERABLE .................................................................................................................................. 2

REQUIREMENT ................................................................................................................................ 2

USER INTERFACE ................................................................................................................................. 2


COMPONENT INTERFACE ....................................................................................................................... 3
CONDITION DEFINITION ........................................................................................................................ 4
FILTER CRITERIA CLASS ......................................................................................................................... 4
AVAILABLE ATTRIBUTES ........................................................................................................................ 8
VALUES ..................................................................................................................................................... 8
VARIABLES ................................................................................................................................................. 8

PROVIDED CLASSES ......................................................................................................................... 8

FILTERCRITERIA ................................................................................................................................... 8
ATTRIBUTES ..................................................................................................................................... 12
Prerequisites
Ver good knowledge within Angular and Typescript are required. Please read the
requirements carefully.

Angular Version and Front-End Framework


The implementation needs to be performed with Angular 13 and with the front-end
framework PrimeNg (https://www.primefaces.org/primeng/setup).

Deliverable
The described functionality needs to be provided in components covering the requested
functionality. The coding needs to be delivered as ZIP file in the end. The developed
components need to be fully tested and automated test scripts are highly appreciated.

The components need to be documented inline.

Requirement
The Angular components need to be provide an easy-to-use condition editor.

An example for a simple condition would be NAME = ‘JOHN’, but the creation of complex
conditions needs to be available too e.g. ( NAME = ‘JOHN’ OR NAME = ‘MATT’) AND (AGE >
20 OR MARRIED = FALSE).

User Interface
The User Interface needs to be build using the PrimeNg framework and needs to cover the
following functionality:

- Show available attributes and allow “drag-and-drop” to the condition builder


- The condition builder needs to allow the user to create new conditions, update
existing conditions and delete conditions
- The user needs to be able to add or remove brackets
- The user needs to be able to switch the view from the labels to the technical
attribute names using the settings button
- The values can be either entered manually or a predefined value or variable can be
selected
- The build condition needs to be validated and possible errors (e.g. missing values or
brackets) need to be displayed
- No dialog screens should be used for the additional options (like settings or the value
input), instead the OverlayPanel should be used
(https://www.primefaces.org/primeng/overlaypanel).
- The Condition Builder should be accessible in read and write mode
- The user should be able to confirm the created condition via a “Save” button. The
“Save” button only triggers an event (there is no need to actually save something)

Following UI components should be used (besides PrimeNg inputs and buttons):


- Panel
https://www.primefaces.org/primeng/panel

- PanelMenu
https://www.primefaces.org/primeng/panelmenu

- OverlayPanel
https://www.primefaces.org/primeng/overlaypanel

- CascadedSelect
https://www.primefaces.org/primeng/cascadeselect

- Chip
https://www.primefaces.org/primeng/chip

Component Interface
The component interface needs to contain the following attributes:

@Input() filterCriteria: FilterCriteria[];


@Input() filterAttributeGroups: FilterAttributeGroup [];
@Input() readOnly: boolean;
@Output() onSave: EventEmitter< FilterCriteria[]>() = new EventEmitter< FilterCriteria[]>();
è Needs to be triggered when the “Save” button is pressed.
@Output() onClose: EventEmitter<any>() = new EventEmitter<any>();
è Needs to be triggered once the user presses either the “Close” button in readOnly
mode or “Cancel” in write mode.

Condition definition
A condition always consists of an attribute name, an operator and the value which should be
compared.

Following operators are supported:

Operator Name Description


=, EQ equals
<>, !=, NE not equal
CP Contains pattern Is processed with the LIKE command on
database level. The wildcard % is used.
LE, <= Lower equal
LT, < Lower than
GE, >= Greater equal
GT, > Greater than
SW Starts with e.g. the name John starts with J
EW Ends with e.g. the name John ends with n
BT Between This is the only operator which requires to
values e.g. AGE BT 20 - 40

Conditions can be linked to each other with OR and AND. Furthermore brackets are available
to logically define the connection between the conditions.

Filter Criteria Class


The Filter Criteria Class is provided and must be used to store the condition. It is important
to note, that conditions which are connected with AND and OR require brackets once mixed!

Class definition:
export class FilterCriteria {

field: string;
operation: string;
value: any;
valueTo: any;

and: FilterCriteria[];
or: FilterCriteria[];

(The class is provided within the appendix of this document)

The class already has a toString method which is transforming the condition into something
readable. Please note that the FilterCriteria is always processed as an array!

The next couple of examples will demonstrate the usage of the FilterCriteria class:
Example Condition Filter Criteria
#1 name EQ ‘John’

#2 name EQ ‘John’ AND age >


20

Multiple conditions on the highest level are


always combined with AND.
#3 name EQ ‘John’ AND age > This is not allowed. Brackets are required once
20 OR gender = ‘M’ AND and OR are used.

This check needs to be implemented within the


component. See #4 how the condition should
look like.
#4 (name EQ ‘John’ AND age >
20 ) OR (gender = ‘M’)

This syntax makes sure that user is fully aware of


the entered condition and that the user can
easily extend the condition whenever needed.
#5 (name EQ ‘John’ AND age >
20 ) OR (gender = ‘M’ OR
(gender = ‘F’ AND
birthdate < ‘2000-01-01’))

#6 age BT 20 – 30 AND
employed = true

Following data types need to be supported within the conditions:


- String
- Date/ DateTime
- Number (full integer as well as decimals)
- Boolean
Available attributes
The available attributes for a condition are provided by a REST API. In order to simplify the
implementation test data are provided within the class FilterAttributeTestData and the static
method getTestData.

The hierarchy of the attributes is as follows:

Filter Attribute Group (class FilterAttributeGroup)


è Filter Attribute (class FilterAttribute)
o Values (class FilterAttributeValues)
Fixed values e.g. M = male, F = female)
o Variables (class FilterAttributeVariable)
e.g. %LASTYEAR%

Values
Some attributes may have fixed values. These values should be displayed to the user and
should be selectable for simple access.

Variables
Some attributes may have variables based on their data type. The available variables are
passed as part of the attributes.

Those variables can be used instead of an actual value e.g.

last_login GE ‘%LAST_WEEK%’

Besides the simple variables there are parameter variables as well. The parameter
hasParameter defines that a number parameter needs to be entered by the user. The
parameter part of the value is always based on a number and simply added at the end of the
variable e.g.

last_login GE ‘%MINUS_WEEKS%{2}’

Provided Classes

FilterCriteria
export class FilterCriteria {

public static readonly cOperatorEqual: string = 'EQ';


public static readonly cOperatorNotEqual: string = 'NE';
public static readonly cOperatorContainsPattern: string = 'CP';
public static readonly cOperatorLowerEqual: string = 'LE';
public static readonly cOperatorLowerThan: string = 'LT';
public static readonly cOperatorGreaterEqual: string = 'GE';
public static readonly cOperatorGreaterThan: string = 'GT';
public static readonly cOperatorBetween: string = 'BT';
public static readonly cOperatorStartsWith: string = 'SW';
public static readonly cOperatorEndsWith: string = 'EW';

field: string;
operation: string;
value: any;
valueTo: any;
and: FilterCriteria[];
or: FilterCriteria[];

public static create(


field: string,
operation: string,
value: any,
valueTo: any
) : FilterCriteria {

const criterion: FilterCriteria = new FilterCriteria();

criterion.field = field;
criterion.operation = operation;
criterion.value = value;
criterion.valueTo = valueTo;

return criterion;

/**
* Check if operator is equal.
*/
public isOperatorEqual(): boolean {
return (this.operation == FilterCriteria.cOperatorEqual);
}

/**
* Check whether value is in selection.
* @param value
*/
public checkValueInSelection(value: any) :boolean {

try {
switch (this.operation) {
case FilterCriteria.cOperatorEqual:
if (value == this.value) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorNotEqual:
if (value != this.value) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorContainsPattern:
if (value.includes(this.value)) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorLowerEqual:
if (value <= this.value) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorGreaterEqual:
if (value >= this.value) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorGreaterThan:
if (value > this.value) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorBetween:
if (value <= this.value && value >= this.valueTo) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorStartsWith:
if (value.startsWith(this.value)) {
return true;
} else {
return false;
}
break;
case FilterCriteria.cOperatorEndsWith:
if (value.endsWith(this.value)) {
return true;
} else {
return false;
}
break;
}

return false;

} catch (e) {
return false;
}
}

public static createSingleCondition(


field: string,
operation: string,
value: any,
valueTo: any
): FilterCriteria {

const filterCriteria = new FilterCriteria();


filterCriteria.field = field;
filterCriteria.operation = operation;
filterCriteria.value = value;
filterCriteria.valueTo = valueTo;
return filterCriteria;
}

public addFilterCriteriaToAnd(filterCriteria: FilterCriteria) {


if (!this.and) {
this.and = [];
}
this.and.push(filterCriteria);
}

public addFilterCriteriaToOr(filterCriteria: FilterCriteria) {


if (!this.or) {
this.or = [];
}
this.or.push(filterCriteria);
}

public hasAndOrEntries(): boolean {


if (this.and) {
return true;
}

if (this.or) {
return true;
}

return false;
}

public compare(filterCriteria: FilterCriteria) {


if (this.toString() === filterCriteria.toString()) {
return true;
} else {
return false;
}
}

public toString(): string {

let text: string;


text = '' + this.field + this.operation + this.value;
if (this.valueTo) {
text = text + this.valueTo;
}

if (this.and) {
text = text + ' (';
for (const filterCriterion of this.and) {
text = text + filterCriterion.toString() + ' AND ';
}
}

if (this.or) {
text = text + ' (';
for (const filterCriterion of this.or) {
text = text + filterCriterion.toString() + ' OR ';
}
}
return text;
}

/**
* Copy Criteria.
* @param target
* @param source
*/
public static copyCriteria(target: FilterCriteria[], source:
FilterCriteria[]) {

if (source && source.length > 0) {

for (let filterCriterion of source) {


target.push(filterCriterion);
}

}
}

Attributes
export enum FilterAttributeDateTypeEnum {
string,
date,
dateTime,
boolean,
number_integer,
number_decimal
}

export class FilterAttributeGroup {

name: string;
label: string;

filterAttributes: FilterAttribute[];

/**
* Create filter attribute group.
* @param name
* @param label
* @param filterAttributes
*/
public static create(name: string, label: string, filterAttributes:
FilterAttribute[]) :FilterAttributeGroup {
const filterAttributeGroup = new FilterAttributeGroup();
filterAttributeGroup.name = name;
filterAttributeGroup.label = label;
filterAttributeGroup.filterAttributes = filterAttributes;
return filterAttributeGroup;
}

}
export class FilterAttributeValues {
constructor(public value: any, public label: string) {

}
}

export class FilterAttributeVariable {


constructor(public name: string, public label: string, public
hasParameter: boolean) {

}
}

export class FilterAttribute {

attribute: string;
dataType: FilterAttributeDateTypeEnum;
label: string;

values: FilterAttributeValues[];
variables: FilterAttributeVariable[];

/**
* Create filter attribute without variables.
* @param attribute
* @param label
* @param dataType
*/
public static create(attribute: string, label: string, dataType:
FilterAttributeDateTypeEnum, values: FilterAttributeValues[]) :
FilterAttribute {
const filterAttribute = new FilterAttribute();
filterAttribute.attribute = attribute;
filterAttribute.dataType = dataType;
filterAttribute.label = label;
filterAttribute.values = values;
filterAttribute.variables = [];
return filterAttribute;
}

/**
* Create filter attribute with variables.
* @param attribute
* @param label
* @param dataType
*/
public static createWithVariables(attribute: string, label: string,
dataType: FilterAttributeDateTypeEnum, values: FilterAttributeValues[],
variables: FilterAttributeVariable[]) : FilterAttribute {
const filterAttribute = FilterAttribute.create(attribute, label,
dataType, values);
filterAttribute.variables = variables;
return filterAttribute;
}

export class FilterAttributeTestData {

public static getTestData() : FilterAttributeGroup[] {


return [
FilterAttributeGroup.create("person", "Person", [
FilterAttribute.create("person.name", "Name of person",
FilterAttributeDateTypeEnum.string, []),
FilterAttribute.createWithVariables("birthdate", "Birth
Date of person", FilterAttributeDateTypeEnum.date, [], [
{
name: "%LASTYEAR%", label : "Last Year",
hasParameter: false
},
{
name: "%YEARSBACK%", label : "Number of Years
back", hasParameter: true
},
{
name: "%AGE%", label : "Age with decimals",
hasParameter: true
},
]),
FilterAttribute.create("person.age", "Age of person",
FilterAttributeDateTypeEnum.number_integer, []),
FilterAttribute.create("person.gender", "Gender of person",
FilterAttributeDateTypeEnum.string, [
{
value: "M", label : "Male"
},
{
value: "F", label : "Female"
},
{
value: "O", label : "No Defined"
},
]),
FilterAttribute.create("person.salary", "Salary of person",
FilterAttributeDateTypeEnum.number_decimal, []),
FilterAttribute.create("person.married", "Is married",
FilterAttributeDateTypeEnum.boolean, []),
FilterAttribute.create("person.lastlogin", "Last Login",
FilterAttributeDateTypeEnum.dateTime, []),
]),
FilterAttributeGroup.create("car", "Car", [
FilterAttribute.create("car.hp", "Hourse Power",
FilterAttributeDateTypeEnum.number_integer, []),
FilterAttribute.createWithVariables("car.color", "Gender of
person", FilterAttributeDateTypeEnum.string, [
{
value: "R", label : "Red"
},
{
value: "G", label : "Green"
},
{
value: "B", label : "Black"
},
],
[
{
name: "%DARK%", label : "Dark color",
hasParameter: false
},
{
name: "%BRIGHT%", label : "Bright color",
hasParameter: false
},
]),
])
];

You might also like