mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-06-30 18:15:11 +00:00
[ADF-5213] Metadata - select list filter (#5961)
* select filter input component * include theme * add filter input * show input conditionally * convert value to string for d:int type values * filter list options pipe * add components to module * convert int value to string * i18n * update tests * tests * update config * remove unneeded decorator * fix lint * update schema * remove filter pipe * provide a filtered list
This commit is contained in:
parent
7b7c996fab
commit
9b0db0a82e
@ -1012,7 +1012,8 @@
|
|||||||
},
|
},
|
||||||
"multi-value-pipe-separator": ", ",
|
"multi-value-pipe-separator": ", ",
|
||||||
"multi-value-chips": true,
|
"multi-value-chips": true,
|
||||||
"copy-to-clipboard-action": true
|
"copy-to-clipboard-action": true,
|
||||||
|
"selectFilterLimit": 5
|
||||||
},
|
},
|
||||||
"sideNav": {
|
"sideNav": {
|
||||||
"expandedSidenav": true,
|
"expandedSidenav": true,
|
||||||
|
@ -1036,6 +1036,14 @@
|
|||||||
"multi-value-chips": {
|
"multi-value-chips": {
|
||||||
"description": "Use chips for multi value properties",
|
"description": "Use chips for multi value properties",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"copy-to-clipboard-action": {
|
||||||
|
"description": "Copy property to the clipboard on double click",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"selectFilterLimit": {
|
||||||
|
"description": "Shows a filter if list options exceed a specified number. Default value 5",
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
@import './components/card-view-textitem/card-view-textitem.component';
|
@import './components/card-view-textitem/card-view-textitem.component';
|
||||||
@import './components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
|
@import './components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
|
||||||
@import './components/card-view/card-view.component';
|
@import './components/card-view/card-view.component';
|
||||||
|
@import './components/card-view-selectitem/select-filter-input/select-filter-input.component';
|
||||||
@import '~@mat-datetimepicker/core/datetimepicker/datetimepicker-theme.scss';
|
@import '~@mat-datetimepicker/core/datetimepicker/datetimepicker-theme.scss';
|
||||||
|
|
||||||
@mixin adf-card-view-module-theme($theme) {
|
@mixin adf-card-view-module-theme($theme) {
|
||||||
@ -12,4 +13,5 @@
|
|||||||
@include adf-card-view-theme($theme);
|
@include adf-card-view-theme($theme);
|
||||||
@include mat-datetimepicker-theme($theme);
|
@include mat-datetimepicker-theme($theme);
|
||||||
@include adf-card-view-array-item-theme($theme);
|
@include adf-card-view-array-item-theme($theme);
|
||||||
|
@include adf-select-filter-input-theme($theme);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ import { CardViewTextItemComponent } from './components/card-view-textitem/card-
|
|||||||
import { CardViewKeyValuePairsItemComponent } from './components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
|
import { CardViewKeyValuePairsItemComponent } from './components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
|
||||||
import { CardViewSelectItemComponent } from './components/card-view-selectitem/card-view-selectitem.component';
|
import { CardViewSelectItemComponent } from './components/card-view-selectitem/card-view-selectitem.component';
|
||||||
import { CardViewArrayItemComponent } from './components/card-view-arrayitem/card-view-arrayitem.component';
|
import { CardViewArrayItemComponent } from './components/card-view-arrayitem/card-view-arrayitem.component';
|
||||||
|
import { SelectFilterInputComponent } from './components/card-view-selectitem/select-filter-input/select-filter-input.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -78,7 +79,8 @@ import { CardViewArrayItemComponent } from './components/card-view-arrayitem/car
|
|||||||
CardViewSelectItemComponent,
|
CardViewSelectItemComponent,
|
||||||
CardViewItemDispatcherComponent,
|
CardViewItemDispatcherComponent,
|
||||||
CardViewContentProxyDirective,
|
CardViewContentProxyDirective,
|
||||||
CardViewArrayItemComponent
|
CardViewArrayItemComponent,
|
||||||
|
SelectFilterInputComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CardViewComponent,
|
CardViewComponent,
|
||||||
@ -88,7 +90,8 @@ import { CardViewArrayItemComponent } from './components/card-view-arrayitem/car
|
|||||||
CardViewTextItemComponent,
|
CardViewTextItemComponent,
|
||||||
CardViewSelectItemComponent,
|
CardViewSelectItemComponent,
|
||||||
CardViewKeyValuePairsItemComponent,
|
CardViewKeyValuePairsItemComponent,
|
||||||
CardViewArrayItemComponent
|
CardViewArrayItemComponent,
|
||||||
|
SelectFilterInputComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CardViewModule {}
|
export class CardViewModule {}
|
||||||
|
@ -8,10 +8,14 @@
|
|||||||
<div *ngIf="isEditable()">
|
<div *ngIf="isEditable()">
|
||||||
<mat-form-field class="adf-select-item-padding-editable adf-property-value">
|
<mat-form-field class="adf-select-item-padding-editable adf-property-value">
|
||||||
<mat-select [(value)]="value"
|
<mat-select [(value)]="value"
|
||||||
|
panelClass="adf-select-filter"
|
||||||
(selectionChange)="onChange($event)"
|
(selectionChange)="onChange($event)"
|
||||||
data-automation-class="select-box">
|
data-automation-class="select-box">
|
||||||
|
|
||||||
|
<adf-select-filter-input *ngIf="showInputFilter" (change)="onFilterInputChange($event)"></adf-select-filter-input>
|
||||||
|
|
||||||
<mat-option *ngIf="showNoneOption()">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
<mat-option *ngIf="showNoneOption()">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
||||||
<mat-option *ngFor="let option of getOptions() | async"
|
<mat-option *ngFor="let option of getList() | async"
|
||||||
[value]="option.key">
|
[value]="option.key">
|
||||||
{{ option.label | translate }}
|
{{ option.label | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
@ -24,12 +24,13 @@ import { setupTestBed } from '../../../testing/setup-test-bed';
|
|||||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||||
|
|
||||||
describe('CardViewSelectItemComponent', () => {
|
describe('CardViewSelectItemComponent', () => {
|
||||||
|
|
||||||
let fixture: ComponentFixture<CardViewSelectItemComponent>;
|
let fixture: ComponentFixture<CardViewSelectItemComponent>;
|
||||||
let component: CardViewSelectItemComponent;
|
let component: CardViewSelectItemComponent;
|
||||||
let overlayContainer: OverlayContainer;
|
let overlayContainer: OverlayContainer;
|
||||||
|
let appConfig: AppConfigService;
|
||||||
const mockData = [{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }, { key: 'three', label: 'Three' }];
|
const mockData = [{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }, { key: 'three', label: 'Three' }];
|
||||||
const mockDefaultProps = {
|
const mockDefaultProps = {
|
||||||
label: 'Select box label',
|
label: 'Select box label',
|
||||||
@ -50,6 +51,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
fixture = TestBed.createComponent(CardViewSelectItemComponent);
|
fixture = TestBed.createComponent(CardViewSelectItemComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
overlayContainer = TestBed.inject(OverlayContainer);
|
overlayContainer = TestBed.inject(OverlayContainer);
|
||||||
|
appConfig = TestBed.inject(AppConfigService);
|
||||||
component.property = new CardViewSelectItemModel(mockDefaultProps);
|
component.property = new CardViewSelectItemModel(mockDefaultProps);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,5 +145,80 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
const label = fixture.debugElement.query(By.css('[data-automation-class="select-box"] .mat-form-field-label'));
|
const label = fixture.debugElement.query(By.css('[data-automation-class="select-box"] .mat-form-field-label'));
|
||||||
expect(label).toBeNull();
|
expect(label).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Filter', () => {
|
||||||
|
it('should render a list of filtered options', () => {
|
||||||
|
appConfig.config['content-metadata'] = {
|
||||||
|
selectFilterLimit: 0
|
||||||
|
};
|
||||||
|
let optionsElement: any[];
|
||||||
|
component.property = new CardViewSelectItemModel({
|
||||||
|
...mockDefaultProps,
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
component.editable = true;
|
||||||
|
component.displayNoneOption = false;
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const selectBox = fixture.debugElement.query(By.css('.mat-select-trigger'));
|
||||||
|
selectBox.triggerEventHandler('click', {});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
optionsElement = Array.from(overlayContainer.getContainerElement().querySelectorAll('mat-option'));
|
||||||
|
expect(optionsElement.length).toBe(3);
|
||||||
|
|
||||||
|
const filterInput = fixture.debugElement.query(By.css('.adf-select-filter-input input'));
|
||||||
|
filterInput.nativeElement.value = mockData[0].label;
|
||||||
|
filterInput.nativeElement.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
optionsElement = Array.from(overlayContainer.getContainerElement().querySelectorAll('mat-option'));
|
||||||
|
expect(optionsElement.length).toBe(1);
|
||||||
|
expect(optionsElement[0].innerText).toEqual(mockData[0].label);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide filter if options are less then limit', () => {
|
||||||
|
appConfig.config['content-metadata'] = {
|
||||||
|
selectFilterLimit: mockData.length + 1
|
||||||
|
};
|
||||||
|
component.property = new CardViewSelectItemModel({
|
||||||
|
...mockDefaultProps,
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
component.editable = true;
|
||||||
|
component.displayNoneOption = false;
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const selectBox = fixture.debugElement.query(By.css('.mat-select-trigger'));
|
||||||
|
selectBox.triggerEventHandler('click', {});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const filterInput = fixture.debugElement.query(By.css('.adf-select-filter-input'));
|
||||||
|
expect(filterInput).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show filter if options are greater then limit', () => {
|
||||||
|
appConfig.config['content-metadata'] = {
|
||||||
|
selectFilterLimit: mockData.length - 1
|
||||||
|
};
|
||||||
|
component.property = new CardViewSelectItemModel({
|
||||||
|
...mockDefaultProps,
|
||||||
|
editable: true
|
||||||
|
});
|
||||||
|
component.editable = true;
|
||||||
|
component.displayNoneOption = false;
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const selectBox = fixture.debugElement.query(By.css('.mat-select-trigger'));
|
||||||
|
selectBox.triggerEventHandler('click', {});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const filterInput = fixture.debugElement.query(By.css('.adf-select-filter-input'));
|
||||||
|
expect(filterInput).not.toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,20 +15,23 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { Component, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||||
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
|
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
|
||||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces';
|
import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces';
|
||||||
import { MatSelectChange } from '@angular/material/select';
|
import { MatSelectChange } from '@angular/material/select';
|
||||||
import { BaseCardView } from '../base-card-view';
|
import { BaseCardView } from '../base-card-view';
|
||||||
|
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||||
|
import { takeUntil, map } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-card-view-selectitem',
|
selector: 'adf-card-view-selectitem',
|
||||||
templateUrl: './card-view-selectitem.component.html',
|
templateUrl: './card-view-selectitem.component.html',
|
||||||
styleUrls: ['./card-view-selectitem.component.scss']
|
styleUrls: ['./card-view-selectitem.component.scss']
|
||||||
})
|
})
|
||||||
export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItemModel<string>> implements OnChanges {
|
export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItemModel<string>> implements OnChanges, OnDestroy {
|
||||||
|
static HIDE_FILTER_LIMIT = 5;
|
||||||
|
|
||||||
@Input() editable: boolean = false;
|
@Input() editable: boolean = false;
|
||||||
|
|
||||||
@ -41,13 +44,29 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
displayEmpty: boolean = true;
|
displayEmpty: boolean = true;
|
||||||
|
|
||||||
value: string;
|
value: string;
|
||||||
|
filter: string = '';
|
||||||
|
showInputFilter: boolean = false;
|
||||||
|
|
||||||
constructor(cardViewUpdateService: CardViewUpdateService) {
|
private onDestroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(cardViewUpdateService: CardViewUpdateService, private appConfig: AppConfigService) {
|
||||||
super(cardViewUpdateService);
|
super(cardViewUpdateService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.value = this.property.value;
|
this.value = this.property.value?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getOptions()
|
||||||
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
|
.subscribe((options: CardViewSelectItemOption<string>[]) => {
|
||||||
|
this.showInputFilter = options.length > this.optionsLimit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilterInputChange(value: string) {
|
||||||
|
this.filter = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEditable(): boolean {
|
isEditable(): boolean {
|
||||||
@ -58,6 +77,16 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
return this.options$ || this.property.options$;
|
return this.options$ || this.property.options$;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getList(): Observable<CardViewSelectItemOption<string>[]> {
|
||||||
|
return this.getOptions()
|
||||||
|
.pipe(
|
||||||
|
map((items: CardViewSelectItemOption<string>[]) => items.filter(
|
||||||
|
(item: CardViewSelectItemOption<string>) =>
|
||||||
|
item.label.toLowerCase().includes(this.filter.toLowerCase()))),
|
||||||
|
takeUntil(this.onDestroy$)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onChange(event: MatSelectChange): void {
|
onChange(event: MatSelectChange): void {
|
||||||
const selectedOption = event.value !== undefined ? event.value : null;
|
const selectedOption = event.value !== undefined ? event.value : null;
|
||||||
this.cardViewUpdateService.update(<CardViewSelectItemModel<string>> { ...this.property }, selectedOption);
|
this.cardViewUpdateService.update(<CardViewSelectItemModel<string>> { ...this.property }, selectedOption);
|
||||||
@ -71,4 +100,13 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
get showProperty(): boolean {
|
get showProperty(): boolean {
|
||||||
return this.displayEmpty || !this.property.isEmpty();
|
return this.displayEmpty || !this.property.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.onDestroy$.next();
|
||||||
|
this.onDestroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get optionsLimit(): number {
|
||||||
|
return this.appConfig.get<number>('content-metadata.selectFilterLimit', CardViewSelectItemComponent.HIDE_FILTER_LIMIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
<div class="adf-select-filter-input-container">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput
|
||||||
|
autocomplete="off"
|
||||||
|
(keydown)="handleKeydown($event)"
|
||||||
|
[placeholder]="'SELECT_FILTER.INPUT.PLACEHOLDER' | translate"
|
||||||
|
#selectFilterInput
|
||||||
|
[ngModel]="term"
|
||||||
|
(ngModelChange)="onModelChange($event)"
|
||||||
|
[attr.aria-label]="'SELECT_FILTER.INPUT.ARIA_LABEL' | translate"
|
||||||
|
(change)="$event.stopPropagation()"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button mat-button
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
[attr.aria-label]="'SELECT_FILTER.BUTTON.ARIA_LABEL' | translate"
|
||||||
|
*ngIf="term"
|
||||||
|
(keydown.enter)="reset($event)"
|
||||||
|
(click)="reset()">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
@ -0,0 +1,35 @@
|
|||||||
|
@mixin adf-select-filter-input-theme($theme) {
|
||||||
|
$mat-select-panel-max-height: 256px !default;
|
||||||
|
$select-filter-height: 4em !default;
|
||||||
|
$background: map-get($theme, background);
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
|
||||||
|
.adf-select-filter-input {
|
||||||
|
height: $select-filter-height;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.adf-select-filter-input-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
z-index: 100;
|
||||||
|
font-size: 14px;
|
||||||
|
color: mat-color($foreground, text, 0.87);
|
||||||
|
line-height: 3em;
|
||||||
|
height: $select-filter-height;
|
||||||
|
padding: 5px 16px 0;
|
||||||
|
background: mat-color($background, card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-select-panel.adf-select-filter {
|
||||||
|
transform: none !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
max-height: calc(#{$mat-select-panel-max-height} + #{$select-filter-height});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||||
|
import { setupTestBed } from '../../../../testing/setup-test-bed';
|
||||||
|
import { CoreTestingModule } from '../../../../testing/core.testing.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { SelectFilterInputComponent } from './select-filter-input.component';
|
||||||
|
import { MatSelect } from '@angular/material/select';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ESCAPE } from '@angular/cdk/keycodes';
|
||||||
|
|
||||||
|
describe('SelectFilterInputComponent', () => {
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<SelectFilterInputComponent>;
|
||||||
|
let component: SelectFilterInputComponent;
|
||||||
|
let matSelect: MatSelect;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CoreTestingModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
],
|
||||||
|
providers: [ MatSelect ]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SelectFilterInputComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
matSelect = TestBed.inject(MatSelect);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should focus input on initialization', async(() => {
|
||||||
|
spyOn(component.selectFilterInput.nativeElement, 'focus');
|
||||||
|
matSelect.openedChange.next(true);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.selectFilterInput.nativeElement.focus).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should clear search term on close', async(() => {
|
||||||
|
component.onModelChange('some-search-term');
|
||||||
|
expect(component.term).toBe('some-search-term');
|
||||||
|
|
||||||
|
matSelect.openedChange.next(false);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.term).toBe('');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit event when value changes', async(() => {
|
||||||
|
spyOn(component.change, 'next');
|
||||||
|
component.onModelChange('some-search-term');
|
||||||
|
expect(component.change.next).toHaveBeenCalledWith('some-search-term');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should reset value on reset() event', () => {
|
||||||
|
component.onModelChange('some-search-term');
|
||||||
|
expect(component.term).toBe('some-search-term');
|
||||||
|
|
||||||
|
component.reset();
|
||||||
|
expect(component.term).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset value on Escape event', () => {
|
||||||
|
component.onModelChange('some-search-term');
|
||||||
|
expect(component.term).toBe('some-search-term');
|
||||||
|
|
||||||
|
component.selectFilterInput.nativeElement.dispatchEvent(new KeyboardEvent('keydown', {'keyCode': ESCAPE} as any));
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.term).toBe('');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,86 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, ViewEncapsulation, ViewChild, ElementRef, OnDestroy, Inject, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { ESCAPE, TAB } from '@angular/cdk/keycodes';
|
||||||
|
import { MatSelect } from '@angular/material/select';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-select-filter-input',
|
||||||
|
templateUrl: './select-filter-input.component.html',
|
||||||
|
styleUrls: ['./select-filter-input.component.scss'],
|
||||||
|
host: { 'class': 'adf-select-filter-input' },
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class SelectFilterInputComponent implements OnDestroy {
|
||||||
|
@ViewChild('selectFilterInput', { read: ElementRef, static: false }) selectFilterInput: ElementRef;
|
||||||
|
@Output() change = new EventEmitter<string>();
|
||||||
|
|
||||||
|
term = '';
|
||||||
|
private onDestroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(@Inject(MatSelect) private matSelect: MatSelect) {}
|
||||||
|
|
||||||
|
onModelChange(value: string) {
|
||||||
|
this.change.next(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.change
|
||||||
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
|
.subscribe((val: string) => this.term = val );
|
||||||
|
|
||||||
|
this.matSelect.openedChange
|
||||||
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
|
.subscribe((isOpened: boolean) => {
|
||||||
|
if (isOpened) {
|
||||||
|
this.selectFilterInput.nativeElement.focus();
|
||||||
|
} else {
|
||||||
|
this.change.next('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(event?: KeyboardEvent) {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.change.next('');
|
||||||
|
this.selectFilterInput.nativeElement.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeydown($event: KeyboardEvent) {
|
||||||
|
if (this.term) {
|
||||||
|
if ($event.keyCode === ESCAPE) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.change.next('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($event.target as HTMLInputElement).tagName === 'INPUT' && $event.keyCode === TAB) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.onDestroy$.next();
|
||||||
|
this.onDestroy$.complete();
|
||||||
|
}
|
||||||
|
}
|
@ -23,5 +23,6 @@ export * from './card-view-item-dispatcher/card-view-item-dispatcher.component';
|
|||||||
export * from './card-view-mapitem/card-view-mapitem.component';
|
export * from './card-view-mapitem/card-view-mapitem.component';
|
||||||
export * from './card-view-textitem/card-view-textitem.component';
|
export * from './card-view-textitem/card-view-textitem.component';
|
||||||
export * from './card-view-selectitem/card-view-selectitem.component';
|
export * from './card-view-selectitem/card-view-selectitem.component';
|
||||||
|
export * from './card-view-selectitem/select-filter-input/select-filter-input.component';
|
||||||
export * from './card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
|
export * from './card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
|
||||||
export * from './card-view-arrayitem/card-view-arrayitem.component';
|
export * from './card-view-arrayitem/card-view-arrayitem.component';
|
||||||
|
@ -35,7 +35,7 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
|||||||
get displayValue() {
|
get displayValue() {
|
||||||
return this.options$.pipe(
|
return this.options$.pipe(
|
||||||
switchMap((options) => {
|
switchMap((options) => {
|
||||||
const option = options.find((o) => o.key === this.value);
|
const option = options.find((o) => o.key === this.value?.toString());
|
||||||
return of(option ? option.label : '');
|
return of(option ? option.label : '');
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -23,7 +23,8 @@ export {
|
|||||||
CardViewTextItemComponent,
|
CardViewTextItemComponent,
|
||||||
CardViewSelectItemComponent,
|
CardViewSelectItemComponent,
|
||||||
CardViewKeyValuePairsItemComponent,
|
CardViewKeyValuePairsItemComponent,
|
||||||
CardViewArrayItemComponent
|
CardViewArrayItemComponent,
|
||||||
|
SelectFilterInputComponent
|
||||||
} from './components/card-view.components';
|
} from './components/card-view.components';
|
||||||
|
|
||||||
export * from './interfaces/card-view.interfaces';
|
export * from './interfaces/card-view.interfaces';
|
||||||
|
@ -509,5 +509,14 @@
|
|||||||
"CLIPBOARD": {
|
"CLIPBOARD": {
|
||||||
"CLICK_TO_COPY": "Click to copy",
|
"CLICK_TO_COPY": "Click to copy",
|
||||||
"SUCCESS_COPY": "Text copied to clipboard"
|
"SUCCESS_COPY": "Text copied to clipboard"
|
||||||
|
},
|
||||||
|
"SELECT_FILTER": {
|
||||||
|
"INPUT": {
|
||||||
|
"PLACEHOLDER": "Search",
|
||||||
|
"ARIA_LABEL": "Search options"
|
||||||
|
},
|
||||||
|
"BUTTON": {
|
||||||
|
"ARIA_LABEL": "Clear"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user