Input component: If in select type more than 7 results available make it autocomplete. Advanced search form add includes/not includes

This commit is contained in:
Konstantinos Triantafyllou 2022-05-27 12:22:04 +03:00
parent 9a2b8d3632
commit be3020b164
6 changed files with 123 additions and 66 deletions

View File

@ -17,16 +17,24 @@
<form (ngSubmit)="queryChanged()" class="uk-card uk-card-default uk-box-shadow-medium" [class.dark]="dark">
<button type="submit" class="uk-hidden"></button>
<div class="uk-card-body uk-height-medium uk-overflow-auto">
<table class="uk-table uk-table-middle uk-table-responsive uk-table-small uk-margin-remove">
<table class="uk-table uk-table-middle uk-text-small uk-table-responsive uk-table-small uk-margin-remove">
<tr>
<td class="uk-text-muted uk-text-uppercase">Field to search</td>
<td class="uk-text-muted uk-text-uppercase uk-width-1-2">Term</td>
<td class="uk-text-bold uk-text-uppercase uk-width-2-5">Searching Fields</td>
<td class="uk-text-bold uk-text-uppercase uk-width-1-3">Terms</td>
</tr>
<tr *ngFor="let selectedField of selectedFields; let i = index">
<td><mat-select [(ngModel)]="selectedField.id" name="selectField_{{i}}" [disableOptionCentering]="true" class="matSelection" panelClass="matSelectionPanel"
(ngModelChange)="fieldIdsChanged(i,selectedField.id)"><!--(click)="fieldIdsChanged(i)" -->
<td>
<div class="uk-grid uk-flex-middle uk-child-width-1-2@m uk-child-width-1-1">
<div input [(value)]="selectedField.id" inputClass="border-bottom" [tooltip]="true"
[options]="fieldIdsOptions" (valueChange)="fieldIdsChanged(i,selectedField.id)" type="select"></div>
<div input *ngIf="selectedField.id != 'q' && !onlyIncludesSupported(i)" [(value)]="selectedField.includes" inputClass="border-bottom" [options]="notOperators" type="select"></div>
<div *ngIf="selectedField.id != 'q' && onlyIncludesSupported(i)">includes</div>
<div *ngIf="selectedField.id == 'q'">include</div>
</div>
<!-- <mat-select [(ngModel)]="selectedField.id" name="selectField_{{i}}" [disableOptionCentering]="true" class="matSelection" panelClass="matSelectionPanel"
(ngModelChange)="fieldIdsChanged(i,selectedField.id)">&lt;!&ndash;(click)="fieldIdsChanged(i)" &ndash;&gt;
<mat-option *ngFor="let id of fieldIds" [value]="id">{{fieldIdsMap[id].name}} </mat-option>
</mat-select></td>
</mat-select>--></td>
<td *ngIf="selectedField.type == 'keyword' || selectedField.type == 'identifier'">
<div class="uk-inline uk-width-expand">
<a *ngIf="selectedField.value.length > 0" class="uk-form-icon uk-form-icon-flip"
@ -82,17 +90,15 @@
value="false">No<br>
</span>
</td>
<td style="min-width: 75px;"><mat-select *ngIf="i+1 <selectedFields.length"
[(ngModel)]="selectedFields[i+1].operatorId"
name="selectOp_{{i}}" [disableOptionCentering]="true" class="matSelection uk-input" panelClass="matSelectionPanel">
<mat-option *ngFor="let op of operators" (change)="fieldOperatorChanged(i+1, op.id, op.id)"
(click)="fieldOperatorChanged(i+1, op.id, op.id)" [value]="op.id">{{op.id}}</mat-option>
</mat-select>
<td class="uk-width-small">
<div *ngIf="i+1 <selectedFields.length && !onlyAndSupported(i)" [(value)]="selectedFields[i+1].operatorId" (valueChange)="fieldOperatorChanged(i+1, $event, $event)"
input [options]="operators" inputClass="border-bottom" type="select"></div>
<div *ngIf="i+1 <selectedFields.length && onlyAndSupported(i)">and</div>
</td>
<td [class.uk-invisible]="selectedFields.length === 1">
<button class="uk-icon uk-close"
(click)="removeField(i)">
<icon name="close" [flex]="true" [ratio]="1.5"></icon>
<icon name="close" [flex]="true" [ratio]="1.2"></icon>
</button>
</td>
</tr>

View File

@ -1,4 +1,14 @@
import {ChangeDetectorRef, Component, EventEmitter, Input, Output} from '@angular/core';
import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {AdvancedField, Filter} from './searchHelperClasses.class';
@ -9,15 +19,17 @@ import {SearchCustomFilter} from "./searchUtils.class";
import {Subscriber} from "rxjs";
import {properties} from "../../../../environments/environment";
import {Open} from "../../utils/modal/open.component";
import {Option} from "../../sharedComponents/input/input.component";
@Component({
selector: 'advanced-search-form',
templateUrl: 'advancedSearchForm.component.html'
})
export class AdvancedSearchFormComponent {
export class AdvancedSearchFormComponent implements OnInit, OnDestroy, OnChanges {
@Input() entityType;
@Input() fieldIds: string[];
@Input() fieldIdsMap;
public fieldIdsOptions: Option[] = [];
@Input() selectedFields: AdvancedField[];
@Input() isDisabled: boolean = false;
@Input() simpleSearchLink;
@ -39,7 +51,9 @@ export class AdvancedSearchFormComponent {
public searchFields: SearchFields = new SearchFields();
properties: EnvProperties;
public openaireEntities = OpenaireEntities;
public operators: { name: string, id: string }[] = this.searchFields.ADVANCED_SEARCH_OPERATORS;
public operators: string[] = this.searchFields.ADVANCED_SEARCH_OPERATORS;
public notOperators: Option[] = [];
public logicalOperators: Option[] = [];
selectedEntity;
selectedEntitySimpleUrl;
selectedEntityAdvancedUrl;
@ -49,12 +63,6 @@ export class AdvancedSearchFormComponent {
constructor(private route: ActivatedRoute, private router: Router, private cdr: ChangeDetectorRef) {}
ngOnDestroy() {
if (this.sub instanceof Subscriber) {
this.sub.unsubscribe();
}
}
ngOnInit() {
this.selectedEntity = this.entityType;
@ -65,6 +73,32 @@ export class AdvancedSearchFormComponent {
}
this.newFieldId = this.fieldIds[0];
this.newFieldName = this.fieldIdsMap[this.newFieldId].name;
this.buildOperatorsOptions();
this.buildFieldIdsOptions();
}
ngOnDestroy() {
if (this.sub instanceof Subscriber) {
this.sub.unsubscribe();
}
}
ngOnChanges(changes: SimpleChanges) {
if(changes.fieldIds || changes.fieldIdsMap) {
this.buildFieldIdsOptions();
}
}
buildOperatorsOptions() {
this.notOperators = [{label: 'includes', value: true}, {label: 'not includes', value: false}];
this.logicalOperators = [];
}
buildFieldIdsOptions() {
this.fieldIdsOptions = [];
this.fieldIds.forEach(id => {
this.fieldIdsOptions.push({label: this.fieldIdsMap[id].name, value: id})
});
}
simpleEntityChanged($event) {
@ -145,20 +179,11 @@ export class AdvancedSearchFormComponent {
}
fieldIdsChanged(index: number, fieldId: string) {
//console.log("Field index::"+index + " " + this.selectedFields[index].id + " function id:" +fieldId);
var id = this.fieldIds[0];
this.selectedFields[index].name = this.fieldIdsMap[id].name;
this.selectedFields[index].type = this.fieldIdsMap[id].type;
this.selectedFields[index].name = this.fieldIdsMap[fieldId].name;
this.selectedFields[index].type = this.fieldIdsMap[fieldId].type;
this.selectedFields[index].value = "";
this.selectedFields[index].param = this.fieldIdsMap[id].param;
var id = fieldId;//this.selectedFields[index].id;
this.selectedFields[index].name = this.fieldIdsMap[id].name;
this.selectedFields[index].type = this.fieldIdsMap[id].type;
this.selectedFields[index].value = "";
this.selectedFields[index].param = this.fieldIdsMap[id].param;
if (this.fieldIdsMap[id].type == "boolean") {
this.selectedFields[index].param = this.fieldIdsMap[fieldId].param;
if (this.fieldIdsMap[fieldId].type == "boolean") {
this.selectedFields[index].value = "true";
}
}
@ -175,4 +200,11 @@ export class AdvancedSearchFormComponent {
this.fieldList[fieldId] = $event.value;
}
onlyAndSupported(index: number) {
return (this.selectedFields[index] && !this.selectedFields[index].includes) || (this.selectedFields[index+1] && !this.selectedFields[index+1].includes)
}
onlyIncludesSupported(index: number) {
return (this.selectedFields[index] && this.selectedFields[index].operatorId === 'or') || (this.selectedFields[index+1] && this.selectedFields[index+1].operatorId === 'or')
}
}

View File

@ -756,6 +756,7 @@ export class NewSearchPageComponent {
if (params["f" + i] && params["fv" + i]) {
let fieldId = params["f" + i].split(",")[0];
let operator = (params["f" + i].split(",").length > 1) ? params["f" + i].split(",")[1] : "and";
let not: boolean = params["fo" + i] === "not" && fieldId != 'q';
let fieldparam = (this.fieldIdsMap[fieldId]) ? this.fieldIdsMap[fieldId].param : "";
if (!this.fieldIdsMap[fieldId]) {
@ -765,7 +766,7 @@ export class NewSearchPageComponent {
let value: string = StringUtils.URIDecode(params["fv" + i]);
if (this.fieldIdsMap[fieldId].type == "date") {
let validDates: boolean = true;
let dateField: AdvancedField = new AdvancedField(fieldId, fieldparam, this.fieldIdsMap[fieldId].name, this.fieldIdsMap[fieldId].type, value, operator);
let dateField: AdvancedField = new AdvancedField(fieldId, fieldparam, this.fieldIdsMap[fieldId].name, this.fieldIdsMap[fieldId].type, value, operator, !not);
if (value.indexOf("range") != -1) {
dateField.dateValue.type = "range";
if (value.length < 26) {
@ -787,7 +788,7 @@ export class NewSearchPageComponent {
}
} else {
this.selectedFields.push(new AdvancedField(fieldId, fieldparam, this.fieldIdsMap[fieldId].name, this.fieldIdsMap[fieldId].type, value, operator));
this.selectedFields.push(new AdvancedField(fieldId, fieldparam, this.fieldIdsMap[fieldId].name, this.fieldIdsMap[fieldId].type, value, operator, !not));
}
}
@ -917,8 +918,8 @@ export class NewSearchPageComponent {
} else if (this.selectedFields[i].type == "identifier") {
params += NewSearchPageComponent.createKeywordQuery(this.entityType, this.selectedFields[i].value, this.selectedFields[i].id, this.selectedFields[i].operatorId, countParams);
} else if (countParams == 0 && this.selectedFields[i].operatorId == "not" && this.fieldIdsMap[this.selectedFields[i].id].equalityOperator != "=") {
params += " " + this.selectedFields[i].id + " <> " + '"' + StringUtils.URIEncode(this.selectedFields[i].value) + '"' + " ";
} else if ((!this.selectedFields[i].includes || (countParams == 0 && this.selectedFields[i].operatorId == "not" && this.fieldIdsMap[this.selectedFields[i].id].equalityOperator != "="))) {
params += (this.selectedFields[i].operatorId && countParams>0?this.selectedFields[i].operatorId:"") + " (" + this.selectedFields[i].id + " <> " + '"' + StringUtils.URIEncode(this.selectedFields[i].value) + '"' + ") ";
} else if (this.fieldIdsMap[this.selectedFields[i].id].equalityOperator == "=") {
params += NewSearchPageComponent.createQuotedKeywordQuery(this.selectedFields[i].value, this.selectedFields[i].id, this.selectedFields[i].operatorId, countParams, false);
} else {
@ -1334,6 +1335,10 @@ export class NewSearchPageComponent {
if ((this.selectedFields[i].value && this.selectedFields[i].value.length > 0) || this.selectedFields[i].type == "date") {
this.parameterNames.push("f" + i);
this.parameterValues.push(this.selectedFields[i].id + (this.selectedFields[i].operatorId != 'and' ? ("," + this.selectedFields[i].operatorId) : ''));
if(!this.selectedFields[i].includes && this.selectedFields[i].id !== "q") {
this.parameterNames.push("fo" + i);
this.parameterValues.push("not");
}
this.parameterNames.push("fv" + i);
}
if (this.selectedFields[i].type == "date") {

View File

@ -26,18 +26,20 @@ export class AdvancedField{
public type: string = "keyword"; //keyword, static or dynamic
public value: string = '';
public valueLabel: string = '';
public includes: boolean = true;
public operatorId: string;
public operatorName: string ="";
public valid: boolean = true;
public dateValue:DateValue = new DateValue("any");
constructor(id:string,param:string,name:string, type:string, value:string,operator:string){
constructor(id:string,param:string,name:string, type:string, value:string,operator:string, includes: boolean = true){
this.id = id;
this.param = param;
this.name = name;
this.type = type;
this.value = value;
this.operatorId = operator;
this.includes = includes;
// this.operatorName = "AND";
}

View File

@ -50,13 +50,14 @@ declare var UIkit;
<div class="input-wrapper" [class.disabled]="formControl.disabled" [class.opened]="opened"
[class.focused]="focused" [ngClass]="inputClass" [class.hint]="hint"
[class.active]="(formAsControl?.value || type === 'select' || formAsArray?.length > 0 || getLabel(formAsControl?.value)) && !focused"
[class.danger]="(formControl.invalid && (formControl.touched || searchControl?.touched)) || (searchControl?.invalid && searchControl?.touched)">
<div #inputBox class="input-box" [class.select]="type === 'select'" click-outside-or-esc
[class.danger]="(formControl.invalid && (formControl.touched || !!searchControl?.touched)) || (!!searchControl?.invalid && !!searchControl?.touched)">
<div #inputBox class="input-box" [class.select]="selectable" click-outside-or-esc
[class.static]="placeholderInfo?.static" (clickOutside)="click($event)">
<div *ngIf="!placeholderInfo?.static && placeholderInfo.label" class="placeholder">
<label>{{placeholderInfo.label}} <sup *ngIf="required">*</sup></label>
</div>
<div class="uk-flex" [class.uk-flex-middle]="type !== 'textarea'">
<div class="uk-flex" [class.uk-flex-middle]="type !== 'textarea'"
[attr.uk-tooltip]="(tooltip && !focused && type !== 'chips')?('title: ' + getLabel(formControl.value) + '; delay: 500'):null">
<ng-template [ngIf]="type === 'text' || type === 'URL' || type === 'logoURL'">
<input #input class="input" [attr.placeholder]="placeholderInfo?.static?placeholderInfo.label:hint"
[formControl]="formAsControl" [class.uk-text-truncate]="!focused">
@ -67,18 +68,21 @@ declare var UIkit;
</ng-template>
<ng-template [ngIf]="type === 'select'">
<ng-container *ngIf="placeholderInfo?.static">
<div *ngIf="!getLabel(formControl.value)" class="placeholder uk-text-truncate">{{placeholderInfo.label}}</div>
<div *ngIf="getLabel(formControl.value)" class="input uk-text-truncate">{{getLabel(formControl.value)}}</div>
<div *ngIf="!getLabel(formControl.value)"
class="placeholder uk-text-truncate">{{placeholderInfo.label}}</div>
<div *ngIf="getLabel(formControl.value)"
class="input uk-text-truncate">{{getLabel(formControl.value)}}</div>
</ng-container>
<ng-container *ngIf="!placeholderInfo?.static">
<div *ngIf="!getLabel(formControl.value)" class="input uk-text-truncate">No value selected</div>
<div *ngIf="getLabel(formControl.value)" class="input uk-text-truncate">{{getLabel(formControl.value)}}</div>
<div *ngIf="getLabel(formControl.value)"
class="input uk-text-truncate">{{getLabel(formControl.value)}}</div>
</ng-container>
</ng-template>
<ng-template [ngIf]="type === 'autocomplete'">
<input *ngIf="focused" [attr.placeholder]="placeholderInfo?.static?placeholderInfo.label:hint"
#searchInput class="input" [formControl]="searchControl" [class.uk-text-truncate]="!focused">
<div *ngIf="!focused" class="input">{{getLabel(formAsControl.value)}}</div>
<div *ngIf="!focused" class="input uk-text-truncate">{{getLabel(formAsControl.value)}}</div>
</ng-template>
<ng-template [ngIf]="type === 'autocomplete_soft'">
<input #input class="input" [attr.placeholder]="placeholderInfo?.static?placeholderInfo.label:hint"
@ -88,7 +92,8 @@ declare var UIkit;
<div class="uk-grid uk-grid-small uk-grid-row-collapse uk-width-expand" uk-grid>
<div *ngFor="let chip of formAsArray.controls; let i=index" [class.uk-hidden]="!focused && i > 0"
class="chip">
<div class="uk-label uk-label-small uk-flex uk-flex-middle">
<div class="uk-label uk-label-small uk-flex uk-flex-middle"
[attr.uk-tooltip]="(tooltip)?('title: ' + getLabel(chip.value) + '; delay: 500'):null">
<span class="uk-text-truncate uk-width-expand">{{getLabel(chip.value)}}</span>
<icon (click)="remove(i, $event)" class="uk-link-text uk-margin-small-left clickable" [flex]="true"
name="close" ratio="0.7"></icon>
@ -111,9 +116,9 @@ declare var UIkit;
class="uk-margin-left icon">
<icon *ngIf="formControl.disabled && disabledIcon" [name]="disabledIcon" [flex]="true"></icon>
<ng-template [ngIf]="formControl.enabled">
<icon *ngIf="!formControl.value && icon" [name]="icon" [flex]="true"></icon>
<icon *ngIf="!icon && type === 'select' && selectArrow" name="arrow_drop_down" [flex]="true"></icon>
<button *ngIf="searchControl?.value && type === 'autocomplete'" class="uk-close uk-icon"
<icon *ngIf="!searchControl?.value && icon" [name]="icon" [flex]="true"></icon>
<icon *ngIf="!icon && type === 'select' && selectArrow" [name]="selectArrow" [flex]="true"></icon>
<button *ngIf="!!searchControl?.value && type === 'autocomplete'" class="uk-close uk-icon"
(click)="resetSearch($event)">
<icon [flex]="true" name="close"></icon>
</button>
@ -130,7 +135,7 @@ declare var UIkit;
<ul class="uk-nav uk-dropdown-nav">
<li *ngFor="let option of filteredOptions; let i=index"
[class.uk-active]="(formControl.value === option.value) || selectedIndex === i"
[attr.uk-tooltip]="(tooltip)?('title: ' + option.label + ';'):null">
[attr.uk-tooltip]="(tooltip)?('title: ' + option.label + '; delay: 500'):null">
<a (click)="selectOption(option, $event)"
[class]="option.disabled ? 'uk-disabled uk-text-muted' : ''">{{option.label}}</a>
</li>
@ -198,6 +203,7 @@ export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChang
@Input() tooltip: boolean = false;
@Input() selectArrow: string = 'arrow_drop_down';
@Input() selectedIndex: number = 0;
@Input() selectable: boolean = false;
/** Chips && Autocomplete*/
public filteredOptions: Option[] = [];
public searchControl: FormControl;
@ -250,6 +256,14 @@ export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChang
return option;
}
});
if(this.type === "select") {
if (this.optionsArray.length > 7) {
this.type = 'autocomplete';
this.showOptionsOnEmpty = true;
this.icon = this.selectArrow;
}
this.selectable = true;
}
}
constructor(private elementRef: ElementRef, private cdr: ChangeDetectorRef) {
@ -481,9 +495,9 @@ export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChang
} else if (this.searchInput) {
this.searchInput.nativeElement.focus();
}
if (this.type === 'select') {
if (this.selectArrow) {
this.open(!this.opened);
} else if (this.type !== 'autocomplete' || !this.formControl.value) {
} else if (this.type !== 'autocomplete' || this.showOptionsOnEmpty || !this.formControl.value) {
this.open(true);
}
} else {
@ -505,16 +519,17 @@ export class InputComponent implements OnInit, OnDestroy, AfterViewInit, OnChang
open(value: boolean) {
this.opened = value && this.formControl.enabled;
this.cdr.detectChanges();
if (this.optionBox) {
if (this.opened) {
this.selectedIndex = this.filteredOptions.findIndex(option => option.value === this.formControl.value);
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
}
UIkit.dropdown(this.optionBox.nativeElement).show();
} else {
if (this.optionBox && this.opened) {
this.selectedIndex = this.filteredOptions.findIndex(option => option.value === this.formControl.value);
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
}
UIkit.dropdown(this.optionBox.nativeElement).show();
} else {
if (this.optionBox) {
UIkit.dropdown(this.optionBox.nativeElement).hide();
}
this.focused = false;
}
}

View File

@ -636,10 +636,7 @@ export class SearchFields {
};
public ADVANCED_SEARCH_OPERATORS: { name: string, id: string }[] = [{name: "AND", id: "and"}, {
name: "OR",
id: "or"
}, {name: "NOT", id: "not"}];
public ADVANCED_SEARCH_OPERATORS: string[] = ["and", "or"];
public COMMUNITIES_SEARCH_FIELDS: string[] = ["type", "access", "role"];