diff --git a/client/src/app/instance/search/components/criteria/search-type/between-date.component.html b/client/src/app/instance/search/components/criteria/search-type/between-date.component.html index 62923f54ea88a6aee642a9c558fa74d58fc7cc0b..25d35d5bfde81d2512ddb5b10a78b1029b6625c0 100644 --- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/between-date.component.html @@ -1,25 +1,27 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col px-sm-3"> - <input type="text" - placeholder="Pick a date range..." - class="form-control" - [formControl]="field" - [bsValue]="field.value" - [bsConfig]="{ rangeInputFormat: 'YYYY-MM-DD' }" - autocomplete="off" - bsDaterangepicker> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col px-sm-3"> + <input type="text" + placeholder="Pick a date range..." + class="form-control" + formControlName="dateRange" + [bsValue]="form.controls.dateRange.value" + [bsConfig]="{ rangeInputFormat: 'YYYY-MM-DD', isAnimated: true }" + autocomplete="off" + bsDaterangepicker> + </div> </div> </div> + <div class="col-2 text-center align-self-end mb-sm-1 pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end mb-sm-1 pb-3"> - <button class="btn btn-outline-success" *ngIf="!field.disabled" [hidden]="!field.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="field.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> diff --git a/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts b/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts index 6f4389c809f1857dc2215d2b84ceb0a7dfe58266..9e0ab4d1e3f246daf7804dc9e01d83091cd60458 100644 --- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/between-date.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, BetweenCriterion } from 'src/app/instance/store/models'; @@ -25,19 +25,26 @@ export class BetweenDateComponent { @Input() id: number; @Input() operator: string; @Input() label: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<BetweenCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - field = new FormControl(''); + public form = new FormGroup({ + dateRange: new FormControl('', [Validators.required]) + }); + + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const c = changes.criterion.currentValue as BetweenCriterion; + this.form.controls.dateRange.setValue([new Date(c.min), new Date(c.max)]); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } /** * Emits event to add criterion to the criteria list. @@ -45,37 +52,12 @@ export class BetweenDateComponent { * @fires EventEmitter<BetweenCriterion> */ emitAdd(): void { - const dateMin = this.getDateString(this.field.value[0]); - const dateMax = this.getDateString(this.field.value[1]); + const dateMin = this.getDateString(this.form.controls.dateRange.value[0]); + const dateMax = this.getDateString(this.form.controls.dateRange.value[1]); const fd: BetweenCriterion = {id: this.id, type: 'between', min: dateMin, max: dateMax}; this.addCriterion.emit(fd); } - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.field.reset(); - this.field.enable(); - } else { - const c = criterion as BetweenCriterion; - this.field.setValue([new Date(c.min), new Date(c.max)]); - this.field.disable(); - } - } - /** * Stringifies the given date. * diff --git a/client/src/app/instance/search/components/criteria/search-type/between.component.html b/client/src/app/instance/search/components/criteria/search-type/between.component.html index 9d499eb368e8112529d089dfc148d3593fe68f71..d2bb51533bb057e65428885255b60e4b0acfcd19 100644 --- a/client/src/app/instance/search/components/criteria/search-type/between.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/between.component.html @@ -1,40 +1,32 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1 mb-lg-0"> - <div class="readonly">min</div> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1 mb-1 mb-sm-0"> - <input - type="text" - class="form-control" - [placeholder]="getPlaceholderMin()" - [formControl]="fieldMin" - autocomplete="off" /> - </div> - <div class="w-100 d-block d-lg-none"></div> - <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1"> - <div class="readonly">max</div> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <input - type="text" - class="form-control" - [placeholder]="getPlaceholderMax()" - [formControl]="fieldMax" - autocomplete="off" /> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1 mb-lg-0"> + <div class="readonly">min</div> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1 mb-1 mb-sm-0"> + <input type="text" class="form-control" [placeholder]="getPlaceholderMin()" formControlName="min" autocomplete="off" /> + </div> + <div class="w-100 d-block d-lg-none"></div> + <div class="col col-sm-3 col-lg-auto pr-sm-1 mb-1"> + <div class="readonly">max</div> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <input type="text" class="form-control" [placeholder]="getPlaceholderMax()" formControlName="max" autocomplete="off" /> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.controls.min.value && !form.controls.max.value" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!fieldMin.disabled" [hidden]="!fieldMin.value && !fieldMax.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="fieldMin.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> diff --git a/client/src/app/instance/search/components/criteria/search-type/between.component.ts b/client/src/app/instance/search/components/criteria/search-type/between.component.ts index d9c5d87232174b2c5166202e9a99736c999c9b56..bc1d7b62d1ef17b2abaf22395c444db3cf9afa14 100644 --- a/client/src/app/instance/search/components/criteria/search-type/between.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/between.component.ts @@ -7,78 +7,54 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, BetweenCriterion } from 'src/app/instance/store/models'; @Component({ selector: 'app-between', templateUrl: 'between.component.html', - styleUrls: ['operator.component.css'], + styleUrls: ['operator.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) /** * @class * @classdesc Between search type component. */ -export class BetweenComponent { +export class BetweenComponent implements OnChanges { @Input() id: number; @Input() label: string; @Input() placeholderMin: string; @Input() placeholderMax: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<BetweenCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - fieldMin = new FormControl(''); - fieldMax = new FormControl(''); + public form = new FormGroup({ + min: new FormControl('', [Validators.required]), + max: new FormControl('', [Validators.required]) + }); - /** - * Emits event to add criterion to the criteria list. - * - * @fires EventEmitter<BetweenCriterion> - */ - emitAdd(): void { - const fd = {id: this.id, type: 'between', min: this.fieldMin.value, max: this.fieldMax.value}; - this.addCriterion.emit(fd); - } + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + this.form.patchValue(this.criterion); + this.form.disable(); + } - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } } /** - * Fills form with the given criterion. + * Emits event to add criterion to the criteria list. * - * @param {Criterion} criterion - The criterion. + * @fires EventEmitter<BetweenCriterion> */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.fieldMin.reset(); - this.fieldMax.reset(); - this.fieldMin.enable(); - this.fieldMax.enable(); - } else { - const c = criterion as BetweenCriterion; - this.fieldMin.setValue(c.min); - this.fieldMax.setValue(c.max); - this.fieldMin.disable(); - this.fieldMax.disable(); - } + emitAdd(): void { + this.addCriterion.emit({id: this.id, type: 'between', ...this.form.value}); } /** diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html index cc269e03cb4a8cc64fd9cb7278c155cb733d83fc..82faebebb9f3368ffe274f7d27045d5c18d749a4 100644 --- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.html @@ -1,25 +1,27 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> - <div class="readonly">in</div> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1" formArrayName="checkboxes"> - <div *ngFor="let _ of checkboxes.controls; index as i" class="custom-control custom-checkbox form-check form-check-inline form-control-lg"> - <input class="custom-control-input" type="checkbox" id="cb_{{options[i].value}}" [formControlName]="i"> - <label class="custom-control-label" for="cb_{{options[i].value}}">{{ options[i].label }}</label> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> + <div class="readonly">in</div> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1" formArrayName="checkboxes"> + <div *ngFor="let _ of getCheckboxes().controls; index as i" class="custom-control custom-checkbox form-check form-check-inline form-control-lg"> + <input class="custom-control-input" type="checkbox" id="cb_{{options[i].value}}" [formControlName]="i"> + <label class="custom-control-label" for="cb_{{options[i].value}}">{{ options[i].label }}</label> + </div> </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button *ngIf="!form.disabled" class="btn btn-outline-success" [hidden]="!isChecked()" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button *ngIf="form.disabled" class="btn btn-outline-danger" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button *ngIf="!checkboxes.disabled" class="btn btn-outline-success" [hidden]="!isChecked()" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button *ngIf="checkboxes.disabled" class="btn btn-outline-danger" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.css b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.scss similarity index 100% rename from client/src/app/instance/search/components/criteria/search-type/checkbox.component.css rename to client/src/app/instance/search/components/criteria/search-type/checkbox.component.scss diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts index 3e76f54857c92bd46ba212563f1d4e726468fca1..67d147fa49e1d37af2f377208bb5f6fdac1903a6 100644 --- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl, FormArray } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormArray, FormControl } from '@angular/forms'; import { Criterion, SelectMultipleCriterion } from 'src/app/instance/store/models'; import { Option } from 'src/app/metamodel/models'; @@ -16,80 +16,63 @@ import { Option } from 'src/app/metamodel/models'; @Component({ selector: 'app-checkbox', templateUrl: 'checkbox.component.html', - styleUrls: ['checkbox.component.css', 'operator.component.css'], + styleUrls: ['checkbox.component.scss', 'operator.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) /** * @class * @classdesc Checkbox search type component. */ -export class CheckboxComponent { +export class CheckboxComponent implements OnInit, OnChanges { @Input() id: number; @Input() label: string; @Input() options: Option[]; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<SelectMultipleCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - checkboxes: FormArray; - - /** - * Emits event to add criterion to the criteria list. - * - * @fires EventEmitter<SelectMultipleCriterion> - */ - emitAdd(): void { - // Filter options to keep only the checked checkboxes and emit the new criterion - const selected = this.checkboxes.value; - const values = [...this.options.filter((option, index) => selected[index])]; - this.addCriterion.emit({id: this.id, type: 'multiple', options: values}); - } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - // Delete the criterion into the state to reactivate the checkboxes - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - const multipleCriterion = criterion as SelectMultipleCriterion; + public form = new FormGroup({ }); + ngOnInit() { // Initialization of checkboxes (1 per option) const formControls: FormControl[] = []; for (let i = 0; i < this.options.length; i++) { formControls.push(new FormControl()); } + this.form.addControl('checkboxes', new FormArray(formControls)); + } - // Store checkboxes in an array - this.checkboxes = new FormArray(formControls); + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const multipleCriterion = this.criterion as SelectMultipleCriterion; - // If the criterion is already used - // Set to true only the checkboxes present in the criterion - if (multipleCriterion) { for (let i = 0; i < this.options.length; i++) { if (multipleCriterion.options.find(o => o.label === this.options[i].label)) { - this.checkboxes.controls[i].setValue(true); + this.getCheckboxes().controls[i].setValue(true); } } - this.checkboxes.disable(); + this.form.disable(); } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + + getCheckboxes() { + return this.form.controls.checkboxes as FormArray; + } + + /** + * Emits event to add criterion to the criteria list. + * + * @fires EventEmitter<SelectMultipleCriterion> + */ + emitAdd(): void { + const selected = this.getCheckboxes().value; + const values = [...this.options.filter((option, index) => selected[index])]; + this.addCriterion.emit({id: this.id, type: 'multiple', options: values}); } /** @@ -99,6 +82,6 @@ export class CheckboxComponent { */ isChecked(): boolean { // If one of the checkboxes is checked returns true else returns false - return this.checkboxes.controls.filter(formControl => formControl.value).length > 0; + return this.getCheckboxes().controls.filter(formControl => formControl.value).length > 0; } } diff --git a/client/src/app/instance/search/components/criteria/search-type/datalist.component.html b/client/src/app/instance/search/components/criteria/search-type/datalist.component.html index 34b72be2525bd64bca9051ce3b0fc0ace4a4b1ba..d7a05139d425bc120d99890714d0a43487e56952 100644 --- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/datalist.component.html @@ -1,42 +1,44 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <span *ngIf="operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip"> - <span class="far fa-question-circle fa-sm"></span> - </span> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> - <app-operator - [operator]="operator" - [searchType]="'field'" - [advancedForm]="advancedForm" - [disabled]="disabledOperator" - (changeOperator)="changeOperator($event)"> - </app-operator> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <input [attr.list]="getDatalistId()" - type="text" - class="form-control" - [placeholder]="getPlaceholder()" - [formControl]="field" - autocomplete="off" /> - <datalist [id]="getDatalistId()"> - <option *ngFor="let option of options" [value]="option.value"> - </datalist> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <span *ngIf="operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip"> + <span class="far fa-question-circle fa-sm"></span> + </span> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> + <app-operator + [operator]="operator" + [searchType]="'field'" + [advancedForm]="advancedForm" + [disabled]="disabledOperator" + (changeOperator)="changeOperator($event)"> + </app-operator> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <input [attr.list]="getDatalistId()" + type="text" + class="form-control" + [placeholder]="getPlaceholder()" + formControlName="value" + autocomplete="off" /> + <datalist [id]="getDatalistId()"> + <option *ngFor="let option of options" [value]="option.value"> + </datalist> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!field.disabled" [hidden]="!field.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="field.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> +</form> <ng-template #helpLike> <app-help-like></app-help-like> diff --git a/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts b/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts index 5ef229699c0ced5565cd7955ca8812f8a011c6f0..cb31dc150f4829870438be222d9e19daa0860f33 100644 --- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/datalist.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, SimpleChanges, OnChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; import { Option } from 'src/app/metamodel/models'; @@ -22,28 +22,35 @@ import { Option } from 'src/app/metamodel/models'; * @class * @classdesc Datalist search type component. */ -export class DatalistComponent { +export class DatalistComponent implements OnChanges { @Input() id: number; @Input() operator: string; @Input() label: string; @Input() placeholder: string; @Input() options: Option[]; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Input() advancedForm: boolean; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - field = new FormControl(''); + public form = new FormGroup({ + value: new FormControl('', [Validators.required]) + }); + disabledOperator: boolean; + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + this.form.patchValue(this.criterion); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Modifies operator with the given one. * @@ -59,35 +66,7 @@ export class DatalistComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const fd = {id: this.id, type: 'field', operator: this.operator, value: this.field.value}; - this.addCriterion.emit(fd); - } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.field.reset(); - this.field.enable(); - this.disabledOperator = false; - } else { - const c = criterion as FieldCriterion; - this.field.setValue(c.value); - this.field.disable(); - this.disabledOperator = true; - } + this.addCriterion.emit({ id: this.id, type: 'field', operator: this.operator, ...this.form.value }); } /** diff --git a/client/src/app/instance/search/components/criteria/search-type/date.component.html b/client/src/app/instance/search/components/criteria/search-type/date.component.html index 674e2f1e8ede0fd4196803b273986775a5230afd..73c451137fd8532d11f2a385f3301c940525aa21 100644 --- a/client/src/app/instance/search/components/criteria/search-type/date.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/date.component.html @@ -1,34 +1,36 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> - <app-operator - [operator]="operator" - [searchType]="'field'" - [advancedForm]="advancedForm" - [disabled]="disabledOperator" - (changeOperator)="changeOperator($event)"> - </app-operator> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <input type="text" - placeholder="Pick a date..." - class="form-control" - [formControl]="field" - [bsValue]="field.value" - [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD' }" - bsDatepicker> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> + <app-operator + [operator]="operator" + [searchType]="'field'" + [advancedForm]="advancedForm" + [disabled]="disabledOperator" + (changeOperator)="changeOperator($event)"> + </app-operator> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <input type="text" + placeholder="Pick a date..." + class="form-control" + formControlName="date" + [bsValue]="form.controls.date.value" + [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', isAnimated: true }" + bsDatepicker> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!field.disabled" [hidden]="!field.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="field.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> \ No newline at end of file diff --git a/client/src/app/instance/search/components/criteria/search-type/date.component.ts b/client/src/app/instance/search/components/criteria/search-type/date.component.ts index 3b86c996ff6fc5f5f0b5fbd7bc02d598ff1dd1b6..d1f4f57c6f59409f35c1da2838caac8a3dcc593d 100644 --- a/client/src/app/instance/search/components/criteria/search-type/date.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/date.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; @@ -21,27 +21,36 @@ import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; * @class * @classdesc Date search type component. */ -export class DateComponent { +export class DateComponent implements OnChanges { @Input() id: number; @Input() operator: string; @Input() label: string; @Input() placeholder: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Input() advancedForm: boolean; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - field = new FormControl(''); + public form = new FormGroup({ + date: new FormControl('', [Validators.required]) + }); + disabledOperator: boolean; + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const criterion = changes.criterion.currentValue as FieldCriterion; + + this.form.controls.date.setValue(new Date(criterion.value)); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Modifies operator with the given one. * @@ -57,37 +66,10 @@ export class DateComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const fd = {id: this.id, type: 'field', operator: this.operator, value: this.getDateString(this.field.value)}; + const fd = {id: this.id, type: 'field', operator: this.operator, value: this.getDateString(this.form.value.date)}; this.addCriterion.emit(fd); } - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete():void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.field.reset(); - this.field.enable(); - this.disabledOperator = false; - } else { - const c = criterion as FieldCriterion; - this.field.setValue(new Date(c.value)); - this.field.disable(); - this.disabledOperator = true; - } - } - /** * Returns placeholder. * diff --git a/client/src/app/instance/search/components/criteria/search-type/datetime.component.html b/client/src/app/instance/search/components/criteria/search-type/datetime.component.html index ae4a3d961d6f9b9e9f238c625634d815d8957cff..1e7edf0fd6add011ffb12cb561172baf43e5c3ab 100644 --- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/datetime.component.html @@ -1,51 +1,52 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> - <app-operator - [operator]="operator" - [searchType]="'field'" - [advancedForm]="advancedForm" - [disabled]="disabledOperator" - (changeOperator)="changeOperator($event)"> - </app-operator> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1 mb-1 mb-lg-0 pr-lg-1"> - <input type="text" - placeholder="Pick a date..." - class="form-control" - [formControl]="date" - [bsValue]="date.value" - [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD' }" - (change)="change()" - bsDatepicker> - </div> - <div class="w-100 d-block d-lg-none"></div> - <div class="col col-lg-auto"> - <div class="row"> - <div class="col-auto pl-lg-1 pr-1"> - <ng-select (change)="change()" [formControl]="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time"> - <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option> - </ng-select> - </div> - <div class="col col-lg-auto p-0 text-center">:</div> - <div class="col-auto pl-1"> - <ng-select (change)="change()" [formControl]="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time"> - <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option> - </ng-select> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> + <app-operator + [operator]="operator" + [searchType]="'field'" + [advancedForm]="advancedForm" + [disabled]="disabledOperator" + (changeOperator)="changeOperator($event)"> + </app-operator> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1 mb-1 mb-lg-0 pr-lg-1"> + <input type="text" + placeholder="Pick a date..." + class="form-control" + formControlName="date" + [bsValue]="form.controls.date.value" + [bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', isAnimated: true }" + bsDatepicker> + </div> + <div class="w-100 d-block d-lg-none"></div> + <div class="col col-lg-auto"> + <div class="row"> + <div class="col-auto pl-lg-1 pr-1"> + <ng-select formControlName="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time"> + <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option> + </ng-select> + </div> + <div class="col col-lg-auto p-0 text-center">:</div> + <div class="col-auto pl-1"> + <ng-select formControlName="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time"> + <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option> + </ng-select> + </div> </div> </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!date.disabled || !hh.disabled || !mm.disabled" [hidden]="!isValidFields" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="date.disabled && hh.disabled && mm.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> diff --git a/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts b/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts index a4d81d9201b97d75605958336e9e784f8bebb5e4..0ad31ef7e8cfbe70196d092f565fd28bcd258235 100644 --- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/datetime.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; @@ -21,33 +21,43 @@ import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; * @class * @classdesc Datetime search type component. */ -export class DatetimeComponent { +export class DatetimeComponent implements OnChanges { @Input() id: number; @Input() operator: string; @Input() label: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Input() advancedForm: boolean; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); hours: string[] = this.initTime(24); minutes: string[] = this.initTime(60); - date = new FormControl(''); - hh = new FormControl(); - mm = new FormControl(); - isValidFields = false; - datetime: Date = new Date(); + + public form = new FormGroup({ + date: new FormControl('', [Validators.required]), + hh: new FormControl('', [Validators.required]), + mm: new FormControl('', [Validators.required]) + }); disabledOperator: boolean; + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const criterion = changes.criterion.currentValue as FieldCriterion; + const [date, time] = criterion.value.split(' '); + + this.form.controls.date.setValue(new Date(date)); + this.form.controls.hh.setValue(time.slice(0, 2)); + this.form.controls.mm.setValue(time.slice(3, 5)); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Modifies operator with the given one. * @@ -63,53 +73,12 @@ export class DatetimeComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const month = ('0' + (this.datetime.getMonth() + 1)).slice(-2); - const day = ('0' + (this.datetime.getDate())).slice(-2); - const date = this.datetime.getFullYear() + '-' + month + '-' + day; - const time = this.hh.value + ':' + this.mm.value; + const date = this.getDateString(this.form.value.date); + const time = this.form.value.hh + ':' + this.form.value.mm; const fd = {id: this.id, type: 'field', operator: this.operator, value: date + ' ' + time}; this.addCriterion.emit(fd); } - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.date.reset(); - this.date.enable(); - this.hh.reset(); - this.hh.enable(); - this.mm.reset(); - this.mm.enable(); - this.isValidFields = false; - this.disabledOperator = false; - } else { - const c = criterion as FieldCriterion; - const [d, t] = c.value.split(' '); - const [h, m] = t.split(':'); - this.date.setValue(new Date(d)); - this.date.disable(); - this.hh.setValue(h); - this.hh.disable(); - this.mm.setValue(m); - this.mm.disable(); - this.isValidFields = true; - this.disabledOperator = true; - } - } - /** * Returns string array to represent the given time. * @@ -127,21 +96,15 @@ export class DatetimeComponent { } /** - * Reconstructs datetime from form inputs. + * Stringifies the given date. + * + * @param {Date} date - The date. + * + * @return string */ - change(): void { - if (!this.date.value || !this.hh.value || !this.mm.value) { - this.isValidFields = false; - } else { - this.isValidFields = true; - this.datetime.setFullYear( - this.date.value.getFullYear(), - this.date.value.getMonth(), - this.date.value.getDate()); - this.datetime.setHours( - this.hh.value, - this.mm.value); - this.datetime.setSeconds(0, 0); - } + getDateString(date: Date): string { + const month = ('0' + (date.getMonth() + 1)).slice(-2); + const day = ('0' + (date.getDate())).slice(-2); + return date.getFullYear() + '-' + month + '-' + day; } } diff --git a/client/src/app/instance/search/components/criteria/search-type/field.component.html b/client/src/app/instance/search/components/criteria/search-type/field.component.html index cbfcb3b2766ec1302889fc857a5b67dda20ee333..3e25d2cb6edb5ae28f21880c45916cfa79019b02 100644 --- a/client/src/app/instance/search/components/criteria/search-type/field.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/field.component.html @@ -1,39 +1,41 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <span *ngIf="operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip"> - <span class="far fa-question-circle fa-sm"></span> - </span> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> - <app-operator - [operator]="operator" - [searchType]="'field'" - [advancedForm]="advancedForm" - [disabled]="disabledOperator" - (changeOperator)="changeOperator($event)"> - </app-operator> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <input - [type]="getType()" - class="form-control" - [placeholder]="getPlaceholder()" - [formControl]="field" - autocomplete="off"> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <span *ngIf="operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip"> + <span class="far fa-question-circle fa-sm"></span> + </span> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> + <app-operator + [operator]="operator" + [searchType]="'field'" + [advancedForm]="advancedForm" + [disabled]="disabledOperator" + (changeOperator)="changeOperator($event)"> + </app-operator> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <input + [type]="getType()" + class="form-control" + [placeholder]="getPlaceholder()" + formControlName="value" + autocomplete="off"> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!field.disabled" [hidden]="!field.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="field.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> +</form> <ng-template #helpLike> <app-help-like></app-help-like> diff --git a/client/src/app/instance/search/components/criteria/search-type/field.component.ts b/client/src/app/instance/search/components/criteria/search-type/field.component.ts index a82dd2af2a90ac2a825e18423d463945221f8492..fb942cd2b69387e2485ab88dd4d109ed6731c8c1 100644 --- a/client/src/app/instance/search/components/criteria/search-type/field.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/field.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { FieldCriterion, Criterion } from 'src/app/instance/store/models'; @@ -27,22 +27,29 @@ export class FieldComponent { @Input() label: string; @Input() placeholder: string; @Input() attributeType: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Input() advancedForm: boolean; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - field = new FormControl(''); + public form = new FormGroup({ + value: new FormControl('', [Validators.required]) + }); + disabledOperator: boolean; + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + this.form.patchValue(this.criterion); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Modifies operator with the given one. * @@ -58,35 +65,7 @@ export class FieldComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const fd = {id: this.id, type: 'field', operator: this.operator, value: this.field.value}; - this.addCriterion.emit(fd); - } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.field.reset(); - this.field.enable(); - this.disabledOperator = false; - } else { - const c = criterion as FieldCriterion; - this.field.setValue(c.value); - this.field.disable(); - this.disabledOperator = true; - } + this.addCriterion.emit({ id: this.id, type: 'field', operator: this.operator, ...this.form.value }); } /** diff --git a/client/src/app/instance/search/components/criteria/search-type/json.component.html b/client/src/app/instance/search/components/criteria/search-type/json.component.html index e2609133d6a34b491009cbaef6fbb886c933e84c..8d69dac7d08c4e7d15d27433e8cda7129e05ccfb 100644 --- a/client/src/app/instance/search/components/criteria/search-type/json.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/json.component.html @@ -1,4 +1,4 @@ -<form [formGroup]="jsonForm"> +<form [formGroup]="form" novalidate> <div class="row"> <div class="col form-group"> <label>{{ label }}</label> @@ -19,17 +19,17 @@ </div> <div class="w-100 d-block d-sm-none"></div> <div class="col"> - <input class="form-control" id="value" name="value" placeholder="Value" autocomplete="off" formControlName="operator"> + <input class="form-control" id="value" name="value" placeholder="Value" autocomplete="off" formControlName="value"> </div> </div> </div> <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!jsonForm.disabled" [hidden]="!jsonForm.value.path || !jsonForm.value.operator || !jsonForm.value.value" (click)="emitAdd()"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> <span class="fas fa-plus fa-fw"></span> </button> - <button class="btn btn-outline-danger" *ngIf="jsonForm.disabled" (click)="emitDelete()"> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> <span class="fa fa-times fa-fw"></span> </button> </div> </div> -</form> \ No newline at end of file +</form> diff --git a/client/src/app/instance/search/components/criteria/search-type/json.component.ts b/client/src/app/instance/search/components/criteria/search-type/json.component.ts index 0c33cb0b4535ad9f1802eadb602a84c302c921a4..32ddefd758ba96bfa9c9d31bb3834bfff1c0e490 100644 --- a/client/src/app/instance/search/components/criteria/search-type/json.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/json.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { JsonCriterion, Criterion } from 'src/app/instance/store/models'; @@ -21,61 +21,38 @@ import { JsonCriterion, Criterion } from 'src/app/instance/store/models'; * @class * @classdesc JSON search type component. */ -export class JsonComponent { +export class JsonComponent implements OnChanges { @Input() id: number; @Input() label: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<JsonCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - jsonForm = new FormGroup({ - path: new FormControl(), - operator: new FormControl(), - value: new FormControl() + public form = new FormGroup({ + path: new FormControl('', [Validators.required]), + operator: new FormControl('', [Validators.required]), + value: new FormControl('', [Validators.required]) }); + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + this.form.patchValue(changes.criterion.currentValue); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Emits event to add criterion to the criteria list. * * @fires EventEmitter<JsonCriterion> */ emitAdd(): void { - const js = {id: this.id, type: 'json', path: this.jsonForm.value.path, operator: this.jsonForm.value.operator, value: this.jsonForm.value.value}; + const js = {id: this.id, type: 'json', ...this.form.value}; this.addCriterion.emit(js); } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.jsonForm.reset(); - this.jsonForm.enable(); - } else { - const c = criterion as JsonCriterion; - this.jsonForm.controls.path.setValue(c.path); - this.jsonForm.controls.operator.setValue(c.operator); - this.jsonForm.controls.value.setValue(c.value); - this.jsonForm.disable(); - } - } } diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.html b/client/src/app/instance/search/components/criteria/search-type/list.component.html index 1910184b1ad26168dc5a1fff6c07e8ccdd89f9b9..f2e467a552ff1ba5d35b8382ec2229f6046fb0f7 100644 --- a/client/src/app/instance/search/components/criteria/search-type/list.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/list.component.html @@ -1,22 +1,24 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> - <div class="readonly">=</div> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <textarea class="form-control" rows="3" [placeholder]="placeholder" [formControl]="list" autocomplete="off"></textarea> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> + <div class="readonly">=</div> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <textarea class="form-control" rows="3" [placeholder]="getPlaceholder()" formControlName="list" autocomplete="off"></textarea> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!list.disabled" [hidden]="!list.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="list.disabled" (click)="deleteCriterion.emit(id)"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> \ No newline at end of file diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.ts b/client/src/app/instance/search/components/criteria/search-type/list.component.ts index fcbfdb42f2509ad5fd28f136a2f36c864a54fbc5..9887535bcc2efce141b4bb5695e7818bc6dd6c16 100644 --- a/client/src/app/instance/search/components/criteria/search-type/list.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/list.component.ts @@ -7,52 +7,44 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, SimpleChanges, OnChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { ListCriterion, Criterion } from 'src/app/instance/store/models'; @Component({ selector: 'app-list', templateUrl: 'list.component.html', - styleUrls: ['operator.component.css'], + styleUrls: ['operator.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) /** * @class * @classdesc List search type component. */ -export class ListComponent { +export class ListComponent implements OnChanges { @Input() id: number; @Input() label: string; @Input() placeholder: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<ListCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - list = new FormControl(''); + public form = new FormGroup({ + list: new FormControl('', [Validators.required]) + }); - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.list.reset(); - this.list.enable(); - } else { - const c = criterion as ListCriterion; - this.list.setValue(c.values.join('\n')); - this.list.disable(); + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const criterion = changes.criterion.currentValue as ListCriterion; + + this.form.controls.list.setValue(criterion.values.join('\n')); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); } } @@ -75,7 +67,7 @@ export class ListComponent { * @fires EventEmitter<ListCriterion> */ emitAdd(): void { - const ls = { id: this.id, type: 'list', values: this.list.value.split('\n') }; + const ls = { id: this.id, type: 'list', values: this.form.value.list.split('\n') }; this.addCriterion.emit(ls); } } diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.css b/client/src/app/instance/search/components/criteria/search-type/operator.component.scss similarity index 100% rename from client/src/app/instance/search/components/criteria/search-type/operator.component.css rename to client/src/app/instance/search/components/criteria/search-type/operator.component.scss diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.ts b/client/src/app/instance/search/components/criteria/search-type/operator.component.ts index b45fb8e3e4ce02f5122ca0409a4355d5598b2ad3..32b44df3a8c96a908f11fc7de304c549c724c417 100644 --- a/client/src/app/instance/search/components/criteria/search-type/operator.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/operator.component.ts @@ -12,7 +12,7 @@ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from @Component({ selector: 'app-operator', templateUrl: 'operator.component.html', - styleUrls: ['operator.component.css'], + styleUrls: ['operator.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) /** diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.html b/client/src/app/instance/search/components/criteria/search-type/radio.component.html index 2e0dcb37af8dca998d420ce75a90ad60114e1754..3be08e8e6919f71158f5e3990c962d708a15046a 100644 --- a/client/src/app/instance/search/components/criteria/search-type/radio.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/radio.component.html @@ -1,23 +1,23 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1"> - <div *ngFor="let option of options" class="form-check form-check-inline"> - <input class="form-check-input" type="radio" id="cb_{{option.value}}" [formControl]="radio" - [value]="option.value"> - <label class="form-check-label" for="cb_{{option.value}}">{{ option.label }}</label> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1"> + <div *ngFor="let option of options" class="form-check form-check-inline"> + <input class="form-check-input" type="radio" id="cb_{{option.value}}" formControlName="radio" [value]="option.value"> + <label class="form-check-label" for="cb_{{option.value}}">{{ option.label }}</label> + </div> </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!radio.disabled" [hidden]="!radio.value" - (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="radio.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.ts b/client/src/app/instance/search/components/criteria/search-type/radio.component.ts index 111afe43987c9c4060701dc1a5e2fb8e63cdc50c..8e43cccee5cade29d9444f838fac3baf1e4ac07c 100644 --- a/client/src/app/instance/search/components/criteria/search-type/radio.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/radio.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; import { Option } from 'src/app/metamodel/models'; @@ -22,24 +22,32 @@ import { Option } from 'src/app/metamodel/models'; * @class * @classdesc Radio search type component. */ -export class RadioComponent { +export class RadioComponent implements OnChanges { @Input() id: number; @Input() operator: string; @Input() label: string; @Input() options: Option[]; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - radio = new FormControl(''); + public form = new FormGroup({ + radio: new FormControl('', [Validators.required]) + }); + + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const criterion = this.criterion as FieldCriterion; + + this.form.controls.radio.setValue(criterion.value); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } /** * Emits event to add criterion to the criteria list. @@ -47,33 +55,8 @@ export class RadioComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const option = this.options.find(o => o.value === this.radio.value); + const option = this.options.find(o => o.value === this.form.value.radio); const cb = {id: this.id, type: 'field', operator: this.operator, value: option.value}; this.addCriterion.emit(cb); } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.radio.reset(); - this.radio.enable(); - } else { - const c = criterion as FieldCriterion; - this.radio.setValue(c.value); - this.radio.disable(); - } - } } diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html index fed583890b2200e09f544199c1ce24a4e9172697..327c45d80226fbff5a0bdf22e67d19adb71659c4 100644 --- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.html @@ -1,24 +1,26 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> - <div class="readonly">in</div> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <ng-select [formControl]="ms" [multiple]="true" [hideSelected]="true" class="ng-select-custom"> - <ng-option *ngFor="let option of options" [value]="option.value">{{option.label}}</ng-option> - </ng-select> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> + <div class="readonly">in</div> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <ng-select formControlName="select" [multiple]="true" [hideSelected]="true" class="ng-select-custom"> + <ng-option *ngFor="let option of options" [value]="option.value">{{option.label}}</ng-option> + </ng-select> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!ms.disabled" [hidden]="!ms.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="ms.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> \ No newline at end of file diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts index d50a510b3ac97e84e7e0160352baedb1a389e15e..650ac066033ccf4ed998fcdf689b7215f777edbf 100644 --- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, SelectMultipleCriterion } from 'src/app/instance/store/models'; import { Option } from 'src/app/metamodel/models'; @@ -16,30 +16,39 @@ import { Option } from 'src/app/metamodel/models'; @Component({ selector: 'app-select-multiple', templateUrl: 'select-multiple.component.html', - styleUrls: ['operator.component.css'], + styleUrls: ['operator.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) /** * @class * @classdesc Select multiple search type component. */ -export class SelectMultipleComponent { +export class SelectMultipleComponent implements OnChanges { @Input() id: number; @Input() label: string; @Input() options: Option[]; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Output() addCriterion: EventEmitter<SelectMultipleCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - ms = new FormControl(); + public form = new FormGroup({ + select: new FormControl('', [Validators.required]) + }); + + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const multipleCriterion = this.criterion as SelectMultipleCriterion; + + const values = multipleCriterion.options.map(option => option.value); + this.form.controls.select.setValue(values); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } /** * Emits event to add criterion to the criteria list. @@ -47,33 +56,10 @@ export class SelectMultipleComponent { * @fires EventEmitter<SelectMultipleCriterion> */ emitAdd(): void { - const options = this.ms.value.map(optionValue => this.options.find(o => o.value === optionValue)); + const values = this.form.value.select as string[]; + + const options = this.options.filter(option => values.includes(option.value)); const ms = {id: this.id, type: 'multiple', options}; this.addCriterion.emit(ms); } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.ms.reset(); - this.ms.enable(); - } else { - const c = criterion as SelectMultipleCriterion; - this.ms.setValue(c.options.map(o => o.value)); - this.ms.disable(); - } - } } diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.html b/client/src/app/instance/search/components/criteria/search-type/select.component.html index 969d8d0b08a055e1ccff7a5fba1a4243861a8c22..8db580ab4a00290c92a111562710d683e379d53c 100644 --- a/client/src/app/instance/search/components/criteria/search-type/select.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/select.component.html @@ -1,36 +1,38 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <span *ngIf="operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip"> - <span class="far fa-question-circle fa-sm"></span> - </span> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> - <app-operator - [operator]="operator" - [searchType]="'field'" - [advancedForm]="advancedForm" - [disabled]="disabledOperator" - (changeOperator)="changeOperator($event)"> - </app-operator> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col pl-sm-1"> - <ng-select [formControl]="se" [multiple]="false" [hideSelected]="true" class="ng-select-custom"> - <ng-option *ngFor="let option of options" [value]="option.value">{{option.label}}</ng-option> - </ng-select> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <span *ngIf="operator === 'lk'" class="pl-1" [tooltip]="helpLike" placement="right" containerClass="custom-tooltip right-tooltip"> + <span class="far fa-question-circle fa-sm"></span> + </span> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-sm-0"> + <app-operator + [operator]="operator" + [searchType]="'field'" + [advancedForm]="advancedForm" + [disabled]="disabledOperator" + (changeOperator)="changeOperator($event)"> + </app-operator> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col pl-sm-1"> + <ng-select formControlName="select" [multiple]="false" [hideSelected]="true" class="ng-select-custom"> + <ng-option *ngFor="let option of options" [value]="option.value">{{option.label}}</ng-option> + </ng-select> + </div> </div> </div> + <div class="col-2 text-center align-self-end pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end pb-3"> - <button class="btn btn-outline-success" *ngIf="!se.disabled" [hidden]="!se.value" (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="se.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> +</form> <ng-template #helpLike> <app-help-like></app-help-like> diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.ts b/client/src/app/instance/search/components/criteria/search-type/select.component.ts index c515a1ecac0b64830470bc997391dd232adefa53..26dda32e7688634cf7a2dd823f9bb8fff8577621 100644 --- a/client/src/app/instance/search/components/criteria/search-type/select.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/select.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; import { Option } from 'src/app/metamodel/models'; @@ -22,27 +22,34 @@ import { Option } from 'src/app/metamodel/models'; * @class * @classdesc Select search type component. */ -export class SelectComponent { +export class SelectComponent implements OnChanges { @Input() id: number; @Input() operator: string; @Input() label: string; @Input() options: Option[]; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Input() advancedForm: boolean; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); - se = new FormControl(); + public form = new FormGroup({ + select: new FormControl('', [Validators.required]) + }); + disabledOperator: boolean; + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + this.form.controls.select.setValue(changes.criterion.currentValue.value); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Modifies operator with the given one. * @@ -58,35 +65,8 @@ export class SelectComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const option = this.options.find(o => o.value === this.se.value); + const option = this.options.find(o => o.value === this.form.value.select); const se = {id: this.id, type: 'field', operator: this.operator, value: option.value}; this.addCriterion.emit(se); } - - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.se.reset(); - this.se.enable(); - this.disabledOperator = false; - } else { - const c = criterion as FieldCriterion; - this.se.setValue(c.value); - this.se.disable(); - this.disabledOperator = true; - } - } } diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.html b/client/src/app/instance/search/components/criteria/search-type/time.component.html index de4864f75f9e15267e17613e920f9f43f31edff0..e2685a83cd10d9a30f998daf02fd6a5813bcaabd 100644 --- a/client/src/app/instance/search/components/criteria/search-type/time.component.html +++ b/client/src/app/instance/search/components/criteria/search-type/time.component.html @@ -1,37 +1,38 @@ -<div class="row"> - <div class="col form-group"> - <label>{{ label }}</label> - <div class="row"> - <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> - <app-operator - [operator]="operator" - [searchType]="'field'" - [advancedForm]="advancedForm" - [disabled]="disabledOperator" - (changeOperator)="changeOperator($event)"> - </app-operator> - </div> - <div class="w-100 d-block d-sm-none"></div> - <div class="col-auto pl-sm-1 pr-1"> - <ng-select [formControl]="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time"> - <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option> - </ng-select> - </div> - <div class="col col-sm-auto p-0 text-center">:</div> - <div class="col-auto pl-1"> - <ng-select [formControl]="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time"> - <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option> - </ng-select> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group"> + <label>{{ label }}</label> + <div class="row"> + <div class="col col-sm-auto pr-sm-1 mb-1 mb-lg-0"> + <app-operator + [operator]="operator" + [searchType]="'field'" + [advancedForm]="advancedForm" + [disabled]="disabledOperator" + (changeOperator)="changeOperator($event)"> + </app-operator> + </div> + <div class="w-100 d-block d-sm-none"></div> + <div class="col-auto pl-sm-1 pr-1"> + <ng-select formControlName="hh" [multiple]="false" placeholder="HH..." class="ng-select-custom ng-select-time"> + <ng-option *ngFor="let hour of hours" [value]="hour">{{ hour }}</ng-option> + </ng-select> + </div> + <div class="col col-sm-auto p-0 text-center">:</div> + <div class="col-auto pl-1"> + <ng-select formControlName="mm" [multiple]="false" placeholder="MM..." class="ng-select-custom ng-select-time"> + <ng-option *ngFor="let min of minutes" [value]="min">{{ min }}</ng-option> + </ng-select> + </div> </div> </div> + <div class="col-2 text-center align-self-end mb-0 mb-sm-1 pb-3"> + <button class="btn btn-outline-success" *ngIf="!form.disabled" [hidden]="!form.valid" (click)="emitAdd()"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="form.disabled" (click)="deleteCriterion.emit(id)"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> </div> - <div class="col-2 text-center align-self-end mb-0 mb-sm-1 pb-3"> - <button class="btn btn-outline-success" *ngIf="!hh.disabled || !mm.disabled" [hidden]="!hh.value || !mm.value" - (click)="emitAdd()"> - <span class="fas fa-plus fa-fw"></span> - </button> - <button class="btn btn-outline-danger" *ngIf="hh.disabled && mm.disabled" (click)="emitDelete()"> - <span class="fa fa-times fa-fw"></span> - </button> - </div> -</div> \ No newline at end of file +</form> \ No newline at end of file diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.ts b/client/src/app/instance/search/components/criteria/search-type/time.component.ts index 0918072c93409700ae92a965e974cc9dd55a4458..cc9d32a083fcbf6b43d2ce90a7a008bc5bdaca5d 100644 --- a/client/src/app/instance/search/components/criteria/search-type/time.component.ts +++ b/client/src/app/instance/search/components/criteria/search-type/time.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; @@ -21,29 +21,40 @@ import { Criterion, FieldCriterion } from 'src/app/instance/store/models'; * @class * @classdesc Time search type component. */ -export class TimeComponent { +export class TimeComponent implements OnChanges { @Input() id: number; @Input() operator: string; @Input() label: string; - /** - * Calls getDefault function for the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - @Input() - set criterion(criterion: Criterion) { - this.getDefault(criterion); - } + @Input() criterion: Criterion; @Input() advancedForm: boolean; @Output() addCriterion: EventEmitter<FieldCriterion> = new EventEmitter(); @Output() deleteCriterion: EventEmitter<number> = new EventEmitter(); hours: string[] = this.initTime(24); minutes: string[] = this.initTime(60); - hh = new FormControl(); - mm = new FormControl(); + disabledOperator: boolean; + public form = new FormGroup({ + hh: new FormControl('', [Validators.required]), + mm: new FormControl('', [Validators.required]) + }); + + ngOnChanges(changes: SimpleChanges) { + if (changes.criterion && changes.criterion.currentValue) { + const criterion = changes.criterion.currentValue as FieldCriterion; + + this.form.controls.hh.setValue(criterion.value.slice(0, 2)); + this.form.controls.mm.setValue(criterion.value.slice(3, 5)); + this.form.disable(); + } + + if (changes.criterion && !changes.criterion.currentValue) { + this.form.enable(); + this.form.reset(); + } + } + /** * Modifies operator with the given one. * @@ -59,41 +70,10 @@ export class TimeComponent { * @fires EventEmitter<FieldCriterion> */ emitAdd(): void { - const time = {id: this.id, type: 'field', operator: this.operator, value: this.hh.value + ':' + this.mm.value}; + const time = {id: this.id, type: 'field', operator: this.operator, value: this.form.value.hh + ':' + this.form.value.mm}; this.addCriterion.emit(time); } - /** - * Emits event to remove criterion ID from the criteria list. - * - * @fires EventEmitter<number> - */ - emitDelete(): void { - this.deleteCriterion.emit(this.id); - } - - /** - * Fills form with the given criterion. - * - * @param {Criterion} criterion - The criterion. - */ - getDefault(criterion: Criterion): void { - if (!criterion) { - this.hh.reset(); - this.hh.enable(); - this.mm.reset(); - this.mm.enable(); - this.disabledOperator = false; - } else { - const c = criterion as FieldCriterion; - this.hh.setValue(c.value.slice(0, 2)); - this.hh.disable(); - this.mm.setValue(c.value.slice(3, 5)); - this.mm.disable(); - this.disabledOperator = true; - } - } - /** * Returns string array to represent the given time. * diff --git a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts index ca589c83866668ad1f6fd8953bb7567d637889df..59de49ada64fd52e4fcd09720480a1bb8d2daf7d 100644 --- a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import { ConeSearch, Resolver } from 'src/app/instance/store/models'; diff --git a/client/src/assets/app.config.json b/client/src/assets/app.config.json index 0270c50c94d4db9f30d2c118f369f75b0c01884f..69f72dd76e9f151588ed89265dd996fff407414e 100644 --- a/client/src/assets/app.config.json +++ b/client/src/assets/app.config.json @@ -2,7 +2,7 @@ "apiUrl": "http://localhost:8080", "servicesUrl": "http://localhost:5000", "baseHref": "/", - "authenticationEnabled": true, + "authenticationEnabled": false, "ssoAuthUrl": "http://localhost:8180/auth", "ssoRealm": "anis", "ssoClientId": "anis-client", diff --git a/client/src/styles.scss b/client/src/styles.scss index 93c9c212bf75cf6478a33cb0053f85a4eeb36a8b..388dc72eda0e8cd56595c80bfcc8c8b5650ec62e 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -31,9 +31,14 @@ @import "~bootstrap/scss/utilities"; /* Import ngx-toastr bootstrap 4 alert styled design */ - @import '~ngx-toastr/toastr-bs4-alert'; +/* Import ng-select styled design */ +@import "~@ng-select/ng-select/themes/default.theme.css"; + +/* Import datepicker ngx bootstrap styled design */ +@import '~ngx-bootstrap/datepicker/bs-datepicker.css'; + /* Global styles */ .theme-color { @@ -71,11 +76,11 @@ main { } /* Angular forms */ -input.ng-valid, select.ng-valid, textarea.ng-valid { +input.ng-valid, select.ng-valid, ng-select.ng-valid div.ng-select-container, textarea.ng-valid { border-left: 5px solid #42A948; /* green */ } -input.ng-invalid, select.ng-invalid, textarea.ng-invalid { +input.ng-invalid, select.ng-invalid, ng-select.ng-invalid div.ng-select-container, textarea.ng-invalid { border-left: 5px solid #a94442; /* red */ }