mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACS-5266] Advanced Search - New component for Category facet (#8764)
* [ACS-5266] new component for category facet * [ACS-5266] fixed tests & docs * [ACS-5266] some fixes * [ACS-5266] linting * [ACS-5266] some improvements * [ACS-5266] reduced observable from child component * [ACS-5266] fixed docs * [ACS-5266] rebase & improvements * [ACS-5266] typo
This commit is contained in:
committed by
GitHub
parent
1ebac21251
commit
2a4507d529
@@ -15,9 +15,10 @@ Represents an input with autocomplete options.
|
||||
|
||||
```html
|
||||
<adf-search-chip-autocomplete-input
|
||||
[autocompleteOptions]="allOptions"
|
||||
[autocompleteOptions]="autocompleteOptions"
|
||||
[onReset$]="onResetObservable$"
|
||||
[allowOnlyPredefinedValues]="allowOnlyPredefinedValues"
|
||||
(inputChanged)="onInputChange($event)"
|
||||
(optionsChanged)="onOptionsChange($event)">
|
||||
</adf-search-chip-autocomplete-input>
|
||||
```
|
||||
@@ -25,20 +26,21 @@ Represents an input with autocomplete options.
|
||||
### Properties
|
||||
|
||||
| Name | Type | Default value | Description |
|
||||
|---------------------------|--------------------------|----|-----------------------------------------------------------------------------------------------|
|
||||
| autocompleteOptions | `string[]` | [] | Options for autocomplete |
|
||||
|---------------------------|--------------------------|----|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| autocompleteOptions | `AutocompleteOption[]` | [] | Options for autocomplete |
|
||||
| onReset$ | [`Observable`](https://rxjs.dev/guide/observable)`<void>` | | Observable that will listen to any reset event causing component to clear the chips and input |
|
||||
| allowOnlyPredefinedValues | boolean | true | A flag that indicates whether it is possible to add a value not from the predefined ones |
|
||||
| placeholder | string | 'SEARCH.FILTER.ACTIONS.ADD_OPTION' | Placeholder which should be displayed in input. |
|
||||
| compareOption | (option1: string, option2: string) => boolean | | Function which is used to selected options with all options so it allows to detect which options are already selected. |
|
||||
| compareOption | (option1: AutocompleteOption, option2: AutocompleteOption) => boolean | | Function which is used to selected options with all options so it allows to detect which options are already selected. |
|
||||
| formatChipValue | (option: string) => string | | Function which is used to format custom typed options. |
|
||||
| filter | (options: string[], value: string) => string[] | | Function which is used to filter out possibile options from hint. By default it checks if option includes typed value and is case insensitive. |
|
||||
| filter | (options: AutocompleteOption[], value: string) => AutocompleteOption[] | | Function which is used to filter out possible options from hint. By default it checks if option includes typed value and is case insensitive. |
|
||||
|
||||
### Events
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- |-----------------------------------------------|
|
||||
| optionsChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string[]>` | Emitted when the selected options are changed |
|
||||
| inputChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when the input changes |
|
||||
| optionsChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<AutocompleteOption[]>` | Emitted when the selected options are changed |
|
||||
|
||||
## See also
|
||||
|
||||
|
@@ -28,7 +28,7 @@ Implements a [search widget](../../../lib/content-services/src/lib/search/models
|
||||
"hideDefaultAction": true,
|
||||
"allowOnlyPredefinedValues": false,
|
||||
"field": "SITE",
|
||||
"options": [ "Option 1", "Option 2" ]
|
||||
"autocompleteOptions": [ {"value": "Option 1"}, {"value": "Option 2"} ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ Implements a [search widget](../../../lib/content-services/src/lib/search/models
|
||||
| Name | Type | Description |
|
||||
| ---- |----------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| field | `string` | Field to apply the query to. Required value |
|
||||
| options | `string[]` | Predefined options for autocomplete |
|
||||
| autocompleteOptions | `AutocompleteOption[]` | Predefined options for autocomplete |
|
||||
| allowOnlyPredefinedValues | `boolean` | Specifies whether the input values should only be from predefined |
|
||||
| allowUpdateOnChange | `boolean` | Enable/Disable the update fire event when text has been changed. By default is true |
|
||||
| hideDefaultAction | `boolean` | Show/hide the widget actions. By default is false |
|
||||
|
@@ -14,7 +14,7 @@ Checks if the provided value is contained in the provided array.
|
||||
<!-- {% raw %} -->
|
||||
|
||||
```HTML
|
||||
<mat-option [disabled]="value | adfIsIncluded: arrayOfValues"</mat-option>
|
||||
<mat-option [disabled]="value | adfIsIncluded: arrayOfValues : comparator"></mat-option>
|
||||
```
|
||||
|
||||
<!-- {% endraw %} -->
|
||||
|
@@ -284,6 +284,7 @@
|
||||
"SUMMARY": "{{numResults}} result found for {{searchTerm}}",
|
||||
"NONE": "No results found for {{searchTerm}}",
|
||||
"ERROR": "We hit a problem during the search - try again.",
|
||||
"WILL_CONTAIN": "Results will contain '{{searchTerm}}'",
|
||||
"COLUMNS": {
|
||||
"NAME": "Display name",
|
||||
"MODIFIED_BY": "Modified by",
|
||||
|
@@ -19,6 +19,7 @@ import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatOptionModule, MatRippleModule } from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
@@ -40,6 +41,7 @@ import { MatBadgeModule } from '@angular/material/badge';
|
||||
@NgModule({
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatAutocompleteModule,
|
||||
MatChipsModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
@@ -63,6 +65,7 @@ import { MatBadgeModule } from '@angular/material/badge';
|
||||
],
|
||||
exports: [
|
||||
MatButtonModule,
|
||||
MatAutocompleteModule,
|
||||
MatChipsModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
|
@@ -41,4 +41,11 @@ describe('IsIncludedPipe', () => {
|
||||
it('should return false if the number is not contained in an array', () => {
|
||||
expect(pipe.transform(50, array)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should use provided comparator to check if value contains in the provided array', () => {
|
||||
const arrayOfObjects = [{id: 'id-1', value: 'value-1'}, {id: 'id-2', value: 'value-2'}];
|
||||
const filterFunction = (extension1, extension2) => extension1.value === extension2.value;
|
||||
expect(pipe.transform({id: 'id-1', value: 'value-1'}, arrayOfObjects, filterFunction)).toBeTruthy();
|
||||
expect(pipe.transform({id: 'id-1', value: 'value-3'}, arrayOfObjects, filterFunction)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@@ -4,8 +4,14 @@
|
||||
class="adf-option-chips"
|
||||
*ngFor="let option of selectedOptions"
|
||||
(removed)="remove(option)">
|
||||
<span>{{option}}</span>
|
||||
<button matChipRemove class="adf-option-chips-delete-button" [attr.aria-label]="('SEARCH.FILTER.BUTTONS.REMOVE' | translate) + ' ' + option">
|
||||
<span [matTooltip]="'SEARCH.RESULTS.WILL_CONTAIN' | translate:{searchTerm: option.fullPath}"
|
||||
[matTooltipDisabled]="!option.fullPath" [matTooltipShowDelay]="tooltipShowDelay">
|
||||
{{ option.value }}
|
||||
</span>
|
||||
<button matChipRemove class="adf-option-chips-delete-button" [matTooltipDisabled]="!option.fullPath"
|
||||
[matTooltip]="('SEARCH.FILTER.BUTTONS.REMOVE' | translate) + ' \'' + option.fullPath + '\''"
|
||||
[matTooltipShowDelay]="tooltipShowDelay"
|
||||
[attr.aria-label]="('SEARCH.FILTER.BUTTONS.REMOVE' | translate) + ' ' + option.value">
|
||||
<mat-icon class="adf-option-chips-delete-icon">close</mat-icon>
|
||||
</button>
|
||||
</mat-chip>
|
||||
@@ -24,9 +30,15 @@
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" id="adf-search-chip-autocomplete"
|
||||
(optionActivated)="activeAnyOption = true" (closed)="activeAnyOption = false">
|
||||
<mat-option [disabled]="option | adfIsIncluded: selectedOptions : compareOption" *ngFor="let option of filteredOptions$ | async"
|
||||
<ng-container *ngIf="optionInput.value.length > 0">
|
||||
<mat-option
|
||||
[disabled]="option | adfIsIncluded: selectedOptions : compareOption"
|
||||
*ngFor="let option of filteredOptions" [value]="option" [matTooltipShowDelay]="tooltipShowDelay"
|
||||
[matTooltipDisabled]="!option.fullPath" matTooltipPosition="right"
|
||||
[matTooltip]="'SEARCH.RESULTS.WILL_CONTAIN' | translate:{searchTerm: option.fullPath || option.value}"
|
||||
[ngClass]="(option | adfIsIncluded: selectedOptions : compareOption) && 'adf-autocomplete-added-option'">
|
||||
{{option}}
|
||||
{{ option.fullPath || option.value }}
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
@@ -40,6 +40,10 @@ adf-search-chip-autocomplete-input {
|
||||
}
|
||||
}
|
||||
|
||||
.mat-tooltip-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mat-option.adf-autocomplete-added-option {
|
||||
background: var(--adf-theme-mat-grey-color-a200);
|
||||
color: var(--adf-theme-primary-300);
|
||||
|
@@ -41,8 +41,8 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
||||
fixture = TestBed.createComponent(SearchChipAutocompleteInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.onReset$ = onResetSubject.asObservable();
|
||||
component.autocompleteOptions = [{value: 'option1'}, {value: 'option2'}];
|
||||
fixture.detectChanges();
|
||||
component.autocompleteOptions = ['option1', 'option2'];
|
||||
});
|
||||
|
||||
function getInput(): HTMLInputElement {
|
||||
@@ -110,6 +110,7 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
||||
const optionsChangedSpy = spyOn(component.optionsChanged, 'emit');
|
||||
enterNewInputValue('op');
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
const matOptions = getOptionElements();
|
||||
expect(matOptions.length).toBe(2);
|
||||
@@ -117,8 +118,8 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
||||
const optionToClick = matOptions[0].nativeElement as HTMLElement;
|
||||
optionToClick.click();
|
||||
|
||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith(['option1']);
|
||||
expect(component.selectedOptions).toEqual(['option1']);
|
||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith([{value: 'option1'}]);
|
||||
expect(component.selectedOptions).toEqual([{value: 'option1'}]);
|
||||
expect(getChipList().length).toBe(1);
|
||||
});
|
||||
|
||||
@@ -126,31 +127,35 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
||||
addNewOption('option1');
|
||||
enterNewInputValue('op');
|
||||
|
||||
const addedOptions = getAddedOptionElements();
|
||||
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
const addedOptions = getAddedOptionElements();
|
||||
expect(addedOptions[0]).toBeTruthy();
|
||||
expect(addedOptions.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should apply class to already selected options based on custom compareOption function', async () => {
|
||||
component.allowOnlyPredefinedValues = false;
|
||||
component.autocompleteOptions = ['.test1', 'test3', '.test2', 'test1.'];
|
||||
component.compareOption = (option1, option2) => option1.split('.')[1] === option2;
|
||||
component.autocompleteOptions = [{value: '.test1'}, {value: 'test3'}, {value: '.test2.'}, {value: 'test1'}];
|
||||
component.compareOption = (option1, option2) => option1.value.split('.')[1] === option2.value;
|
||||
|
||||
fixture.detectChanges();
|
||||
addNewOption('test1');
|
||||
enterNewInputValue('t');
|
||||
|
||||
const addedOptions = getAddedOptionElements();
|
||||
await fixture.whenStable();
|
||||
expect(addedOptions.length).toBe(1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getAddedOptionElements().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should limit autocomplete list to 15 values max', () => {
|
||||
component.autocompleteOptions = ['a1','a2','a3','a4','a5','a6','a7','a8','a9','a10','a11','a12','a13','a14','a15','a16'];
|
||||
it('should limit autocomplete list to 15 values max', async () => {
|
||||
component.autocompleteOptions = Array.from({length: 16}, (_, i) => ({value: `a${i}`}));
|
||||
enterNewInputValue('a');
|
||||
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getOptionElements().length).toBe(15);
|
||||
});
|
||||
|
||||
@@ -160,27 +165,33 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
||||
expect(getChipList().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should show autocomplete list if similar predefined values exists', () => {
|
||||
it('should show autocomplete list if similar predefined values exists', async () => {
|
||||
enterNewInputValue('op');
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionElements().length).toBe(2);
|
||||
});
|
||||
|
||||
it('should show autocomplete list based on custom filtering', () => {
|
||||
component.autocompleteOptions = ['.test1', 'test1', 'test1.', '.test2', '.test12'];
|
||||
component.filter = (options, value) => options.filter((option) => option.split('.')[1] === value);
|
||||
it('should show autocomplete list based on custom filtering', async () => {
|
||||
component.autocompleteOptions = [{value: '.test1'}, {value: 'test1'}, {value: 'test1.'}, {value: '.test2'}, {value: '.test12'}];
|
||||
component.filter = (options, value) => options.filter((option) => option.value.split('.')[1] === value);
|
||||
enterNewInputValue('test1');
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionElements().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not show autocomplete list if there are no similar predefined values', () => {
|
||||
it('should not show autocomplete list if there are no similar predefined values', async () => {
|
||||
enterNewInputValue('test');
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionElements().length).toBe(0);
|
||||
});
|
||||
|
||||
it('should emit new value when selected options changed', () => {
|
||||
const optionsChangedSpy = spyOn(component.optionsChanged, 'emit');
|
||||
addNewOption('option1');
|
||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith(['option1']);
|
||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith([{value: 'option1'}]);
|
||||
expect(getChipList().length).toBe(1);
|
||||
expect(getChipValue(0)).toBe('option1');
|
||||
});
|
||||
@@ -221,7 +232,23 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
||||
fixture.debugElement.query(By.directive(MatChipRemove)).nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith(['option2']);
|
||||
expect(optionsChangedSpy).toHaveBeenCalledOnceWith([{value: 'option2'}]);
|
||||
expect(getChipList().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should show full category path when fullPath provided', () => {
|
||||
component.filteredOptions = [{id: 'test-id', value: 'test-value', fullPath: 'test-full-path'}];
|
||||
enterNewInputValue('test-value');
|
||||
const matOption = fixture.debugElement.query(By.css('.mat-option span')).nativeElement;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(matOption.innerHTML).toEqual(' test-full-path ');
|
||||
});
|
||||
|
||||
it('should emit input value when input changed', async () => {
|
||||
const inputChangedSpy = spyOn(component.inputChanged, 'emit');
|
||||
enterNewInputValue('test-value');
|
||||
await fixture.whenStable();
|
||||
expect(inputChangedSpy).toHaveBeenCalledOnceWith('test-value');
|
||||
});
|
||||
});
|
||||
|
@@ -15,13 +15,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, ElementRef, ViewChild, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
SimpleChanges,
|
||||
OnChanges
|
||||
} from '@angular/core';
|
||||
import { ENTER } from '@angular/cdk/keycodes';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatChipInputEvent } from '@angular/material/chips';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, startWith, takeUntil, tap } from 'rxjs/operators';
|
||||
import { EMPTY, Observable, Subject, timer } from 'rxjs';
|
||||
import { debounce, startWith, takeUntil, tap } from 'rxjs/operators';
|
||||
import { AutocompleteOption } from '../../models/autocomplete-option.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-chip-autocomplete-input',
|
||||
@@ -29,12 +42,12 @@ import { map, startWith, takeUntil, tap } from 'rxjs/operators';
|
||||
styleUrls: ['./search-chip-autocomplete-input.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
||||
export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@ViewChild('optionInput')
|
||||
optionInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
@Input()
|
||||
autocompleteOptions: string[] = [];
|
||||
autocompleteOptions: AutocompleteOption[] = [];
|
||||
|
||||
@Input()
|
||||
onReset$: Observable<void>;
|
||||
@@ -46,24 +59,28 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
||||
placeholder = 'SEARCH.FILTER.ACTIONS.ADD_OPTION';
|
||||
|
||||
@Input()
|
||||
compareOption?: (option1: string, option2: string) => boolean;
|
||||
compareOption?: (option1: AutocompleteOption, option2: AutocompleteOption) => boolean;
|
||||
|
||||
@Input()
|
||||
formatChipValue?: (option: string) => string;
|
||||
|
||||
@Input()
|
||||
filter = (options: string[], value: string): string[] => {
|
||||
filter = (options: AutocompleteOption[], value: string): AutocompleteOption[] => {
|
||||
const filterValue = value.toLowerCase();
|
||||
return options.filter(option => option.toLowerCase().includes(filterValue));
|
||||
return options.filter(option => option.value.toLowerCase().includes(filterValue)).slice(0, 15);
|
||||
};
|
||||
|
||||
@Output()
|
||||
optionsChanged: EventEmitter<string[]> = new EventEmitter();
|
||||
optionsChanged = new EventEmitter<AutocompleteOption[]>();
|
||||
|
||||
@Output()
|
||||
inputChanged = new EventEmitter<string>();
|
||||
|
||||
readonly separatorKeysCodes = [ENTER] as const;
|
||||
formCtrl = new FormControl('');
|
||||
filteredOptions$: Observable<string[]>;
|
||||
selectedOptions: string[] = [];
|
||||
filteredOptions: AutocompleteOption[] = [];
|
||||
selectedOptions: AutocompleteOption[] = [];
|
||||
tooltipShowDelay = 800;
|
||||
private onDestroy$ = new Subject<void>();
|
||||
private _activeAnyOption = false;
|
||||
|
||||
@@ -71,16 +88,25 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
||||
this._activeAnyOption = active;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.filteredOptions$ = this.formCtrl.valueChanges.pipe(
|
||||
startWith(null),
|
||||
ngOnInit() {
|
||||
this.formCtrl.valueChanges
|
||||
.pipe(
|
||||
startWith(''),
|
||||
tap(() => this.activeAnyOption = false),
|
||||
map((value: string | null) => (value ? this.filter(this.autocompleteOptions, value).slice(0, 15) : []))
|
||||
);
|
||||
debounce((value: string) => (value ? timer(300) : EMPTY)),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe((value: string) => {
|
||||
this.filteredOptions = value ? this.filter(this.autocompleteOptions, value) : [];
|
||||
this.inputChanged.emit(value);
|
||||
});
|
||||
this.onReset$?.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.reset());
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.onReset$?.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.reset());
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.autocompleteOptions) {
|
||||
this.filteredOptions = changes.autocompleteOptions.currentValue.length > 0 ? this.filter(changes.autocompleteOptions.currentValue, this.formCtrl.value) : [];
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -96,15 +122,20 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (value && this.isExists(value) && !this.isAdded(value)) {
|
||||
this.selectedOptions.push(value);
|
||||
if (this.allowOnlyPredefinedValues) {
|
||||
const index = this.autocompleteOptions.findIndex(option => option.value.toLowerCase() === value.toLowerCase());
|
||||
this.selectedOptions.push(this.autocompleteOptions[index]);
|
||||
} else {
|
||||
this.selectedOptions.push({value});
|
||||
}
|
||||
this.optionsChanged.emit(this.selectedOptions);
|
||||
event.chipInput.clear();
|
||||
this.formCtrl.setValue(null);
|
||||
this.formCtrl.setValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(value: string) {
|
||||
remove(value: AutocompleteOption) {
|
||||
const index = this.selectedOptions.indexOf(value);
|
||||
|
||||
if (index >= 0) {
|
||||
@@ -114,28 +145,28 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
selected(event: MatAutocompleteSelectedEvent) {
|
||||
if (!this.isAdded(event.option.viewValue)) {
|
||||
this.selectedOptions.push(event.option.viewValue);
|
||||
this.selectedOptions.push(event.option.value);
|
||||
this.optionInput.nativeElement.value = '';
|
||||
this.formCtrl.setValue(null);
|
||||
this.formCtrl.setValue('');
|
||||
this.optionsChanged.emit(this.selectedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private isAdded(value: string): boolean {
|
||||
return this.selectedOptions.includes(value);
|
||||
const valueLowerCase = value.toLowerCase();
|
||||
return this.selectedOptions.some(option => option.value.toLowerCase() === valueLowerCase);
|
||||
}
|
||||
|
||||
private isExists(value: string): boolean {
|
||||
const valueLowerCase = value.toLowerCase();
|
||||
return this.allowOnlyPredefinedValues
|
||||
? this.autocompleteOptions.map(option => option.toLowerCase()).includes(value.toLowerCase())
|
||||
? this.autocompleteOptions.some(option => option.value.toLowerCase() === valueLowerCase)
|
||||
: true;
|
||||
}
|
||||
|
||||
private reset() {
|
||||
this.selectedOptions = [];
|
||||
this.optionsChanged.emit(this.selectedOptions);
|
||||
this.formCtrl.setValue(null);
|
||||
this.formCtrl.setValue('');
|
||||
this.optionInput.nativeElement.value = '';
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<adf-search-chip-autocomplete-input
|
||||
[autocompleteOptions]="autocompleteOptions"
|
||||
[autocompleteOptions]="autocompleteOptions$ | async"
|
||||
[onReset$]="reset$"
|
||||
[allowOnlyPredefinedValues]="settings.allowOnlyPredefinedValues"
|
||||
(inputChanged)="onInputChange($event)"
|
||||
[compareOption]="optionComparator"
|
||||
(optionsChanged)="onOptionsChange($event)">
|
||||
</adf-search-chip-autocomplete-input>
|
||||
|
||||
|
@@ -22,6 +22,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchFilterAutocompleteChipsComponent } from './search-filter-autocomplete-chips.component';
|
||||
import { TagService } from '@alfresco/adf-content-services';
|
||||
import { EMPTY, of } from 'rxjs';
|
||||
import { AutocompleteField } from '../../models/autocomplete-option.interface';
|
||||
|
||||
describe('SearchFilterAutocompleteChipsComponent', () => {
|
||||
let component: SearchFilterAutocompleteChipsComponent;
|
||||
@@ -51,7 +52,7 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
||||
} as any;
|
||||
component.settings = {
|
||||
field: 'test', allowUpdateOnChange: true, hideDefaultAction: false, allowOnlyPredefinedValues: false,
|
||||
options: ['option1', 'option2']
|
||||
autocompleteOptions: [{value: 'option1'}, {value: 'option2'}]
|
||||
};
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -63,13 +64,16 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
it('should set autocomplete options on init', () => {
|
||||
component.settings.options = ['test 1', 'test 2'];
|
||||
it('should set autocomplete options on init', (done) => {
|
||||
component.settings.autocompleteOptions = [{value: 'test 1'}, {value: 'test 2'}];
|
||||
component.ngOnInit();
|
||||
expect(component.autocompleteOptions).toEqual(['test 1', 'test 2']);
|
||||
component.autocompleteOptions$.subscribe(result => {
|
||||
expect(result).toEqual([{value: 'test 1'}, {value: 'test 2'}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load tags if field = TAG', () => {
|
||||
it('should load tags if field = TAG', (done) => {
|
||||
const tagPagingMock = {
|
||||
list: {
|
||||
pagination: {},
|
||||
@@ -77,10 +81,13 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
||||
}
|
||||
};
|
||||
|
||||
component.settings.field = 'TAG';
|
||||
component.settings.field = AutocompleteField.TAG;
|
||||
spyOn(tagService, 'getAllTheTags').and.returnValue(of(tagPagingMock));
|
||||
component.ngOnInit();
|
||||
expect(component.autocompleteOptions).toEqual(['tag1', 'tag2']);
|
||||
component.autocompleteOptions$.subscribe(result => {
|
||||
expect(result).toEqual([{value: 'tag1'},{value: 'tag2'}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update display value when options changes', () => {
|
||||
@@ -94,9 +101,9 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
||||
});
|
||||
|
||||
it('should reset value and display value when reset button is clicked', () => {
|
||||
component.setValue(['option1', 'option2']);
|
||||
component.setValue([{value: 'option1'}, {value: 'option2'}]);
|
||||
fixture.detectChanges();
|
||||
expect(component.selectedOptions).toEqual(['option1', 'option2']);
|
||||
expect(component.selectedOptions).toEqual([{value: 'option1'}, {value: 'option2'}]);
|
||||
spyOn(component.context, 'update');
|
||||
spyOn(component.displayValue$, 'next');
|
||||
const clearBtn: HTMLButtonElement = fixture.debugElement.query(By.css('[data-automation-id="adf-search-chip-autocomplete-btn-clear"]')).nativeElement;
|
||||
@@ -110,13 +117,18 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
||||
|
||||
it('should correctly compose the search query', () => {
|
||||
spyOn(component.context, 'update');
|
||||
addNewOption('option2');
|
||||
addNewOption('option1');
|
||||
component.selectedOptions = [{value: 'option2'}, {value: 'option1'}];
|
||||
const applyBtn: HTMLButtonElement = fixture.debugElement.query(By.css('[data-automation-id="adf-search-chip-autocomplete-btn-apply"]')).nativeElement;
|
||||
applyBtn.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.context.update).toHaveBeenCalled();
|
||||
expect(component.context.queryFragments[component.id]).toBe('test: "option2" OR test: "option1"');
|
||||
expect(component.context.queryFragments[component.id]).toBe('test:"option2" OR test:"option1"');
|
||||
|
||||
component.settings.field = AutocompleteField.CATEGORIES;
|
||||
component.selectedOptions = [{id: 'test-id', value: 'test'}];
|
||||
applyBtn.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.context.queryFragments[component.id]).toBe('cm:categories:"workspace://SpacesStore/test-id"');
|
||||
});
|
||||
});
|
||||
|
@@ -16,12 +16,14 @@
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { SearchWidget } from '../../models/search-widget.interface';
|
||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||
import { SearchFilterList } from '../../models/search-filter-list.model';
|
||||
import { TagService } from '../../../tag/services/tag.service';
|
||||
import { CategoryService } from '../../../category/services/category.service';
|
||||
import { AutocompleteField, AutocompleteOption } from '../../models/autocomplete-option.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-filter-autocomplete-chips',
|
||||
@@ -32,18 +34,19 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
||||
id: string;
|
||||
settings?: SearchWidgetSettings;
|
||||
context?: SearchQueryBuilderService;
|
||||
options: SearchFilterList<string[]>;
|
||||
startValue: string[] = null;
|
||||
options: SearchFilterList<AutocompleteOption[]>;
|
||||
startValue: AutocompleteOption[] = [];
|
||||
displayValue$ = new Subject<string>();
|
||||
selectedOptions: AutocompleteOption[] = [];
|
||||
enableChangeUpdate: boolean;
|
||||
|
||||
private resetSubject$ = new Subject<void>();
|
||||
reset$: Observable<void> = this.resetSubject$.asObservable();
|
||||
autocompleteOptions: string[] = [];
|
||||
selectedOptions: string[] = [];
|
||||
enableChangeUpdate: boolean;
|
||||
private autocompleteOptionsSubject$ = new BehaviorSubject<AutocompleteOption[]>([]);
|
||||
autocompleteOptions$: Observable<AutocompleteOption[]> = this.autocompleteOptionsSubject$.asObservable();
|
||||
|
||||
constructor( private tagService: TagService ) {
|
||||
this.options = new SearchFilterList<string[]>();
|
||||
constructor(private tagService: TagService, private categoryService: CategoryService) {
|
||||
this.options = new SearchFilterList<AutocompleteOption[]>();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -70,11 +73,11 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
||||
return !!this.selectedOptions;
|
||||
}
|
||||
|
||||
getCurrentValue(): string[]{
|
||||
getCurrentValue(): AutocompleteOption[] {
|
||||
return this.selectedOptions;
|
||||
}
|
||||
|
||||
onOptionsChange(selectedOptions: string[]) {
|
||||
onOptionsChange(selectedOptions: AutocompleteOption[]) {
|
||||
this.selectedOptions = selectedOptions;
|
||||
if (this.enableChangeUpdate) {
|
||||
this.updateQuery();
|
||||
@@ -82,27 +85,62 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
||||
}
|
||||
}
|
||||
|
||||
setValue(value: string[]) {
|
||||
setValue(value: AutocompleteOption[]) {
|
||||
this.selectedOptions = value;
|
||||
this.displayValue$.next(this.selectedOptions.join(', '));
|
||||
this.submitValues();
|
||||
}
|
||||
|
||||
onInputChange(value: string) {
|
||||
if (this.settings.field === AutocompleteField.CATEGORIES && value) {
|
||||
this.searchForExistingCategories(value);
|
||||
}
|
||||
}
|
||||
|
||||
optionComparator(option1: AutocompleteOption, option2: AutocompleteOption): boolean {
|
||||
return option1.id
|
||||
? option1.id.toUpperCase() === option2.id.toUpperCase()
|
||||
: option1.value.toUpperCase() === option2.value.toUpperCase();
|
||||
}
|
||||
|
||||
private updateQuery() {
|
||||
this.displayValue$.next(this.selectedOptions.join(', '));
|
||||
this.displayValue$.next(this.selectedOptions.map(option => option.value).join(', '));
|
||||
if (this.context && this.settings && this.settings.field) {
|
||||
this.context.queryFragments[this.id] = this.selectedOptions.map(val => `${this.settings.field}: "${val}"`).join(' OR ');
|
||||
let queryFragments;
|
||||
if (this.settings.field === AutocompleteField.CATEGORIES) {
|
||||
queryFragments = this.selectedOptions.map(val => `${this.settings.field}:"workspace://SpacesStore/${val.id}"`);
|
||||
} else {
|
||||
queryFragments = this.selectedOptions.map(val => `${this.settings.field}:"${val.value}"`);
|
||||
}
|
||||
this.context.queryFragments[this.id] = queryFragments.join(' OR ');
|
||||
this.context.update();
|
||||
}
|
||||
}
|
||||
|
||||
private setOptions() {
|
||||
if (this.settings.field === 'TAG') {
|
||||
this.tagService.getAllTheTags().subscribe(res => {
|
||||
this.autocompleteOptions = res.list.entries.map(tag => tag.entry.tag);
|
||||
switch (this.settings.field) {
|
||||
case AutocompleteField.TAG:
|
||||
this.tagService.getAllTheTags().subscribe(tagPaging => {
|
||||
this.autocompleteOptionsSubject$.next(tagPaging.list.entries.map(tag => ({
|
||||
value: tag.entry.tag
|
||||
})));
|
||||
});
|
||||
} else {
|
||||
this.autocompleteOptions = this.settings.options;
|
||||
break;
|
||||
case AutocompleteField.CATEGORIES:
|
||||
this.autocompleteOptionsSubject$.next([]);
|
||||
break;
|
||||
default:
|
||||
this.autocompleteOptionsSubject$.next(this.settings.autocompleteOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private searchForExistingCategories(searchTerm: string) {
|
||||
this.categoryService.searchCategories(searchTerm, 0, 15).subscribe((existingCategoriesResult) => {
|
||||
this.autocompleteOptionsSubject$.next(existingCategoriesResult.list.entries.map((rowEntry) => {
|
||||
const path = rowEntry.entry.path.name.split('/').splice(3).join('/');
|
||||
const fullPath = path ? `${path}/${rowEntry.entry.name}` : rowEntry.entry.name;
|
||||
return {id: rowEntry.entry.id, value: rowEntry.entry.name, fullPath};
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@
|
||||
</mat-form-field>
|
||||
<p class="adf-search-properties-file-type-label">{{ 'SEARCH.SEARCH_PROPERTIES.FILE_TYPE' | translate }}</p>
|
||||
<adf-search-chip-autocomplete-input
|
||||
[autocompleteOptions]="settings?.fileExtensions"
|
||||
[autocompleteOptions]="autocompleteOptions"
|
||||
(optionsChanged)="selectedExtensions = $event"
|
||||
[onReset$]="reset$"
|
||||
[allowOnlyPredefinedValues]="false"
|
||||
|
@@ -140,9 +140,10 @@ describe('SearchPropertiesComponent', () => {
|
||||
field: 'field',
|
||||
fileExtensions: ['pdf', 'doc', 'txt']
|
||||
};
|
||||
component.ngOnInit();
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(searchChipAutocompleteInputComponent.autocompleteOptions).toBe(component.settings.fileExtensions);
|
||||
expect(searchChipAutocompleteInputComponent.autocompleteOptions).toEqual([{value: 'pdf'}, {value: 'doc'}, {value: 'txt'}]);
|
||||
});
|
||||
|
||||
it('should set onReset$ for SearchChipAutocompleteInputComponent to correct value', () => {
|
||||
@@ -154,10 +155,10 @@ describe('SearchPropertiesComponent', () => {
|
||||
});
|
||||
|
||||
it('should compare file extensions case insensitive after calling compareOption on SearchChipAutocompleteInputComponent', () => {
|
||||
const option1 = 'pdf';
|
||||
const option2 = 'PdF';
|
||||
const option1 = {value: 'pdf'};
|
||||
const option2 = {value: 'PdF'};
|
||||
expect(searchChipAutocompleteInputComponent.compareOption(option1, option2)).toBeTrue();
|
||||
expect(searchChipAutocompleteInputComponent.compareOption(option1, `${option2}1`)).toBeFalse();
|
||||
expect(searchChipAutocompleteInputComponent.compareOption(option1, {value: `${option2.value}1`})).toBeFalse();
|
||||
});
|
||||
|
||||
it('should remove preceding dot after calling formatChipValue on SearchChipAutocompleteInputComponent', () => {
|
||||
@@ -167,11 +168,11 @@ describe('SearchPropertiesComponent', () => {
|
||||
});
|
||||
|
||||
it('should filter file extensions case insensitive without dots after calling filter on SearchChipAutocompleteInputComponent', () => {
|
||||
const extensions = ['pdf', 'jpg', 'txt', 'png'];
|
||||
const extensions = [{value: 'pdf'}, {value: 'jpg'}, {value: 'txt'}, {value: 'png'}];
|
||||
const searchValue = 'p';
|
||||
|
||||
expect(searchChipAutocompleteInputComponent.filter(extensions, searchValue)).toEqual(['pdf', 'jpg', 'png']);
|
||||
expect(searchChipAutocompleteInputComponent.filter(extensions, `.${searchValue}`)).toEqual(['pdf', 'png']);
|
||||
expect(searchChipAutocompleteInputComponent.filter(extensions, searchValue)).toEqual([{value:'pdf'}, {value:'jpg'}, {value:'png'}]);
|
||||
expect(searchChipAutocompleteInputComponent.filter(extensions, `.${searchValue}`)).toEqual([{value:'pdf'}, {value:'png'}]);
|
||||
});
|
||||
|
||||
it('should set placeholder for SearchChipAutocompleteInputComponent to correct value', () => {
|
||||
@@ -259,17 +260,17 @@ describe('SearchPropertiesComponent', () => {
|
||||
});
|
||||
|
||||
it('should search by single file type', () => {
|
||||
const extension = 'pdf';
|
||||
const extension = {value: 'pdf'};
|
||||
getSearchChipAutocompleteInputComponent().optionsChanged.emit([extension]);
|
||||
|
||||
component.submitValues();
|
||||
expect(component.displayValue$.next).toHaveBeenCalledWith(extension);
|
||||
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.${extension}")`);
|
||||
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf');
|
||||
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.${extension.value}")`);
|
||||
expect(component.context.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should search by multiple file types', () => {
|
||||
getSearchChipAutocompleteInputComponent().optionsChanged.emit(['pdf', 'txt']);
|
||||
getSearchChipAutocompleteInputComponent().optionsChanged.emit([{value:'pdf'}, {value:'txt'}]);
|
||||
|
||||
component.submitValues();
|
||||
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf, txt');
|
||||
@@ -279,7 +280,7 @@ describe('SearchPropertiesComponent', () => {
|
||||
|
||||
it('should search by file size and type', () => {
|
||||
typeInFileSizeInput();
|
||||
getSearchChipAutocompleteInputComponent().optionsChanged.emit(['pdf', 'txt']);
|
||||
getSearchChipAutocompleteInputComponent().optionsChanged.emit([{value:'pdf'}, {value:'txt'}]);
|
||||
|
||||
component.submitValues();
|
||||
expect(component.displayValue$.next).toHaveBeenCalledWith('SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB, pdf, txt');
|
||||
@@ -315,7 +316,7 @@ describe('SearchPropertiesComponent', () => {
|
||||
clickFileSizeUnitsSelect();
|
||||
getSelectOptions()[1].nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
const extensions = ['pdf', 'txt'];
|
||||
const extensions = [{value: 'pdf'}, {value: 'txt'}];
|
||||
getSearchChipAutocompleteInputComponent().optionsChanged.emit(extensions);
|
||||
|
||||
expect(component.getCurrentValue()).toEqual({
|
||||
@@ -324,7 +325,7 @@ describe('SearchPropertiesComponent', () => {
|
||||
fileSizeUnit: FileSizeUnit.MB,
|
||||
fileSizeOperator: FileSizeOperator.AT_MOST
|
||||
},
|
||||
fileExtensions: extensions
|
||||
fileExtensions: ['pdf', 'txt']
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -342,7 +343,7 @@ describe('SearchPropertiesComponent', () => {
|
||||
getSelectOptions()[1].nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
searchChipAutocompleteInputComponent = getSearchChipAutocompleteInputComponent();
|
||||
searchChipAutocompleteInputComponent.optionsChanged.emit(['pdf', 'txt']);
|
||||
searchChipAutocompleteInputComponent.optionsChanged.emit([{value: 'pdf'}, {value: 'txt'}]);
|
||||
});
|
||||
|
||||
it('should reset form', () => {
|
||||
|
@@ -26,6 +26,7 @@ import { SearchQueryBuilderService } from '../../services/search-query-builder.s
|
||||
import { SearchProperties } from './search-properties';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SearchWidget } from '../../models/search-widget.interface';
|
||||
import { AutocompleteOption } from '../../models/autocomplete-option.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-properties',
|
||||
@@ -39,6 +40,7 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
||||
context?: SearchQueryBuilderService;
|
||||
startValue: SearchProperties;
|
||||
displayValue$ = new Subject<string>();
|
||||
autocompleteOptions: AutocompleteOption[] = [];
|
||||
|
||||
private _form = this.formBuilder.nonNullable.group<FileSizeCondition>({
|
||||
fileSizeOperator: FileSizeOperator.AT_LEAST,
|
||||
@@ -77,8 +79,8 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
||||
return this._reset$;
|
||||
}
|
||||
|
||||
set selectedExtensions(extensions: string[]) {
|
||||
this._selectedExtensions = extensions;
|
||||
set selectedExtensions(extensions: AutocompleteOption[]) {
|
||||
this._selectedExtensions = this.parseFromAutocompleteOptions(extensions);
|
||||
}
|
||||
|
||||
constructor(private formBuilder: FormBuilder, private translateService: TranslateService) {}
|
||||
@@ -88,6 +90,7 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
||||
if (!this.settings.fileExtensions) {
|
||||
this.settings.fileExtensions = [];
|
||||
}
|
||||
this.autocompleteOptions = this.parseToAutocompleteOptions(this.settings.fileExtensions);
|
||||
[this.sizeField, this.nameField] = this.settings.field.split(',');
|
||||
}
|
||||
if (this.startValue) {
|
||||
@@ -127,8 +130,8 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
||||
return event.key !== '-' && event.key !== 'e' && event.key !== '+';
|
||||
}
|
||||
|
||||
compareFileExtensions(extension1: string, extension2: string): boolean {
|
||||
return extension1.toUpperCase() === extension2.toUpperCase();
|
||||
compareFileExtensions(extension1: AutocompleteOption, extension2: AutocompleteOption): boolean {
|
||||
return extension1.value.toUpperCase() === extension2.value.toUpperCase();
|
||||
}
|
||||
|
||||
getExtensionWithoutDot(extension: string): string {
|
||||
@@ -136,11 +139,11 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
||||
return extensionSplitByDot[extensionSplitByDot.length - 1];
|
||||
}
|
||||
|
||||
filterExtensions = (extensions: string[], filterValue: string): string[] => {
|
||||
filterExtensions = (extensions: AutocompleteOption[], filterValue: string): AutocompleteOption[] => {
|
||||
const filterValueLowerCase = this.getExtensionWithoutDot(filterValue).toLowerCase();
|
||||
const extensionWithDot = filterValue.startsWith('.');
|
||||
return extensions.filter((option) => {
|
||||
const optionLowerCase = option.toLowerCase();
|
||||
const optionLowerCase = option.value.toLowerCase();
|
||||
return extensionWithDot && filterValueLowerCase ? optionLowerCase.startsWith(filterValueLowerCase) : optionLowerCase.includes(filterValue);
|
||||
});
|
||||
};
|
||||
@@ -196,10 +199,18 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
||||
|
||||
setValue(searchProperties: SearchProperties) {
|
||||
this.form.patchValue(searchProperties.fileSizeCondition);
|
||||
this.selectedExtensions = searchProperties.fileExtensions;
|
||||
this.selectedExtensions = this.parseToAutocompleteOptions(searchProperties.fileExtensions);
|
||||
this.submitValues();
|
||||
}
|
||||
|
||||
private parseToAutocompleteOptions(array: string[]): AutocompleteOption[] {
|
||||
return array.map(value => ({value}));
|
||||
}
|
||||
|
||||
private parseFromAutocompleteOptions(array: AutocompleteOption[]): string[] {
|
||||
return array.flatMap(option => option.value);
|
||||
}
|
||||
|
||||
private getOperatorNameWidth(operator: string, font: string): number {
|
||||
const context = this.canvas.getContext('2d');
|
||||
context.font = font;
|
||||
|
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface AutocompleteOption {
|
||||
value: string;
|
||||
id?: string;
|
||||
fullPath?: string;
|
||||
}
|
||||
|
||||
export enum AutocompleteField {
|
||||
TAG = 'TAG',
|
||||
CATEGORIES = 'cm:categories'
|
||||
}
|
@@ -15,6 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AutocompleteOption } from './autocomplete-option.interface';
|
||||
|
||||
export interface SearchWidgetSettings {
|
||||
field: string;
|
||||
/* allow the user to update search in every change */
|
||||
@@ -27,6 +29,8 @@ export interface SearchWidgetSettings {
|
||||
format?: string;
|
||||
/* allow the user to search only within predefined options */
|
||||
allowOnlyPredefinedValues?: boolean;
|
||||
/* allow the user to predefine autocomplete options */
|
||||
autocompleteOptions?: AutocompleteOption[];
|
||||
|
||||
[indexer: string]: any;
|
||||
}
|
||||
|
Reference in New Issue
Block a user