Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,21 @@
</cps-autocomplete>
</app-code-example>

<app-code-example
label="Function-based optionLabel (nested property)"
[htmlCode]="examples.functionOptionKey.html"
[tsCode]="examples.functionOptionKey.ts">
<cps-autocomplete
label="Autocomplete with function-based optionLabel (nested property)"
[options]="options"
[optionLabel]="getCode"
optionInfo="name"
placeholder="Enter a city code"
hint="optionLabel uses a function: (option) => option.data.code"
[clearable]="true">
</cps-autocomplete>
</app-code-example>

<app-code-example
label="Async validation"
[htmlCode]="examples.asyncValidation.html"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
];

form!: FormGroup;
syncVal: any = [];

Check warning on line 57 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
componentData = ComponentData;

isSingleLoading = false;
Expand All @@ -62,14 +62,16 @@
externalError = '';

private _singleFilterOptionSubject$ = new Subject<string>();
singleOptionsObservable$?: Observable<any>;

Check warning on line 65 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

private _multiFilterOptionSubject$ = new Subject<string>();
multiOptionsObservable$?: Observable<any>;

Check warning on line 68 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

validating = false;
selectedOption: any = null;

Check warning on line 71 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

getCode = (option: any): string => option.data.code;

Check warning on line 73 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

get availableOptionInfo() {
return this.options.map((option) => option.name).join(', ');
}
Expand Down Expand Up @@ -109,7 +111,7 @@
private _defineOptionsObservable(
subject$: Subject<string>,
single: boolean
): Observable<any> | undefined {

Check warning on line 114 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
return subject$.pipe(
switchMap((value) => {
if (single) this.isSingleLoading = true;
Expand All @@ -126,7 +128,7 @@
);
}

private _getOptionsFromServer(val: string): Observable<any> {

Check warning on line 131 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const filteredRes = this.options.filter((option) => {
return option.name?.toLowerCase()?.includes(val);
});
Expand All @@ -134,7 +136,7 @@
}

// Method to handle selection changes for async validation
onOptionSelected(option: any) {

Check warning on line 139 in projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
this.validating = true;
this.selectedOption = option;
this.externalError = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,23 @@ syncVal: string[] = [];`
ts: citiesOptionsTs
},

functionOptionKey: {
html: `
<cps-autocomplete
label="Autocomplete with function-based optionLabel (nested property)"
[options]="options"
[optionLabel]="getCode"
optionInfo="name"
placeholder="Enter a city code"
hint="optionLabel uses a function: (option) => option.data.code"
[clearable]="true">
</cps-autocomplete>`,
ts: `
${citiesOptionsTs.trim()}

getCode = (option: any): string => option.data.code;`
},

asyncValidation: {
html: `
<cps-autocomplete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@
[ngModelOptions]="{ standalone: true }"></cps-select>
<span class="sync-val">{{ syncVal }}</span>
</div>
<cps-select
label="Select with function-based optionLabel (nested property)"
[options]="options"
[optionLabel]="getCode"
optionInfo="name"
placeholder="Select a city code"
hint="optionLabel uses a function: (option) => option.data.code">
</cps-select>
<cps-select
label="Underlined select"
[options]="options"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class SelectPageComponent implements OnInit {
syncVal: any = [];
componentData = ComponentData;

getCode = (option: any): string => option.data.code;

// eslint-disable-next-line no-useless-constructor
constructor(private _formBuilder: UntypedFormBuilder) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@
<span class="sync-val">{{ syncVal?.label }}</span>
</div>

<cps-tree-autocomplete
label="Tree autocomplete with function-based optionLabel (computed value)"
[options]="options"
[optionLabel]="getLabel"
optionInfo="attrType"
placeholder="Select element"
[clearable]="true"
hint="optionLabel uses a function: (option) => option.label + ' (' + option.attrType + ')'">
</cps-tree-autocomplete>
<cps-tree-autocomplete
label="Underlined tree autocomplete"
[options]="options"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export class TreeAutocompletePageComponent implements OnInit {

componentData = ComponentData;

getLabel = (option: any): string =>
option.label + (option.attrType ? ` (${option.attrType})` : '');

// eslint-disable-next-line no-useless-constructor
constructor(private _formBuilder: UntypedFormBuilder) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
<span class="sync-val">{{ syncVal?.label }}</span>
</div>

<cps-tree-select
label="Tree select with function-based optionLabel (computed value)"
[options]="options"
[optionLabel]="getLabel"
optionInfo="attrType"
placeholder="Select element"
[clearable]="true"
hint="optionLabel uses a function: (option) => option.label + ' (' + option.attrType + ')'">
</cps-tree-select>
<cps-tree-select
label="Underlined tree select"
appearance="underlined"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export class TreeSelectPageComponent implements OnInit {

componentData = ComponentData;

getLabel = (option: any): string =>
option.label + (option.attrType ? ` (${option.attrType})` : '');

// eslint-disable-next-line no-useless-constructor
constructor(private _formBuilder: UntypedFormBuilder) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<div class="single-item-selection">
<span [style.opacity]="activeSingle ? 0 : 1">{{
returnObject
? value[optionLabel]
? getProp(value, optionLabel)
: (value
| labelByValue: options : optionValue : optionLabel)
}}</span>
Expand Down Expand Up @@ -87,7 +87,7 @@
}">
{{
returnObject
? val[optionLabel]
? getProp(val, optionLabel)
: (val
| labelByValue
: options
Expand Down Expand Up @@ -125,7 +125,7 @@
}"
[label]="
returnObject
? val[optionLabel]
? getProp(val, optionLabel)
: (val
| labelByValue: options : optionValue : optionLabel)
">
Expand Down Expand Up @@ -371,14 +371,14 @@
data-testid="cps-autocomplete-options"
class="cps-autocomplete-options-option-label"
[class.virtual-row]="virtualScroll"
>{{ item[optionLabel] }}</span
>{{ getProp(item, optionLabel) }}</span
>
</span>

<span
class="cps-autocomplete-options-option-right"
[class.virtual-row]="virtualScroll"
>{{ item[optionInfo] }}</span
>{{ getProp(item, optionInfo) }}</span
>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,88 @@ describe('CpsAutocompleteComponent', () => {
expect(component.filteredOptions.length).toBe(3);
});

describe('function-based option keys', () => {
const nestedOptions = [
{ meta: { title: 'Alpha', id: 1 } },
{ meta: { title: 'Beta', id: 2 } },
{ meta: { title: 'Gamma', id: 3 } }
];

it('getProp should return property value when key is a string', () => {
expect(component.getProp({ label: 'hello' }, 'label')).toBe('hello');
});

it('getProp should invoke the function and return its result when key is a function', () => {
const fn = (o: any) => o.meta.title;
expect(component.getProp({ meta: { title: 'hello' } }, fn)).toBe('hello');
});

it('should display selected label using function optionLabel', () => {
fixture.componentRef.setInput('options', nestedOptions);
fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title);
component.value = nestedOptions[0];
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
const selectedLabel = fixture.debugElement.query(
By.css('.single-item-selection span')
);
expect(selectedLabel.nativeElement.textContent.trim()).toBe('Alpha');
});

it('should filter options using function optionLabel', fakeAsync(() => {
fixture.componentRef.setInput('options', nestedOptions);
fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title);
fixture.detectChanges();
const inputElement = fixture.debugElement.query(
By.css('.cps-autocomplete-box-input')
);
inputElement.nativeElement.value = 'bet';
inputElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
tick(component.inputChangeDebounceTime);
fixture.detectChanges();
expect(component.filteredOptions.length).toBe(1);
expect(component.filteredOptions[0]).toBe(nestedOptions[1]);
}));

it('should emit the function-derived value when optionValue is a function and returnObject is false', () => {
fixture.componentRef.setInput('options', nestedOptions);
fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title);
fixture.componentRef.setInput('optionValue', (o: any) => o.meta.id);
fixture.componentRef.setInput('returnObject', false);
fixture.detectChanges();
jest.spyOn(component.valueChanged, 'emit');
component.select(nestedOptions[1], false);
expect(component.valueChanged.emit).toHaveBeenCalledWith(2);
});

it('should collect all function-derived values when toggleAll is called with returnObject false', () => {
fixture.componentRef.setInput('options', nestedOptions);
fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title);
fixture.componentRef.setInput('optionValue', (o: any) => o.meta.id);
fixture.componentRef.setInput('returnObject', false);
fixture.componentRef.setInput('multiple', true);
component.value = [];
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
component.toggleAll();
expect(component.value).toEqual([1, 2, 3]);
});

it('should display chips with labels from function optionLabel', () => {
fixture.componentRef.setInput('options', nestedOptions);
fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title);
fixture.componentRef.setInput('multiple', true);
component.value = [nestedOptions[0], nestedOptions[2]];
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
const chipElements = fixture.debugElement.queryAll(By.css('cps-chip'));
expect(chipElements.length).toBe(2);
expect(chipElements[0].nativeElement.textContent.trim()).toBe('Alpha');
expect(chipElements[1].nativeElement.textContent.trim()).toBe('Gamma');
});
});

describe('aria-label', () => {
it('should set aria-label from ariaLabel input', () => {
fixture.componentRef.setInput('ariaLabel', 'Search options');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import { CheckOptionSelectedPipe } from '../../pipes/internal/check-option-selected.pipe';
import { isEqual } from 'lodash-es';
import { CpsTooltipPosition } from '../../directives/cps-tooltip/cps-tooltip.directive';
import { getOptionProp, OptionKey } from '../../utils/internal/option-utils';
import {
CpsMenuComponent,
CpsMenuHideReason
Expand Down Expand Up @@ -194,22 +195,22 @@
@Input() keepInitialOrder = false;

/**
* Name of the label field of an option.
* Name of the label field of an option, or a function that receives the option and returns the label.
* @group Props
*/
@Input() optionLabel = 'label';
@Input() optionLabel: OptionKey = 'label';
Comment on lines +198 to +201

/**
* Name of the value field of an option. Needed only if returnObject prop is false.
* Name of the value field of an option, or a function that receives the option and returns the value. Needed only if returnObject prop is false.
* @group Props
*/
@Input() optionValue = 'value';
@Input() optionValue: OptionKey = 'value';

/**
* Name of the info field of an option, shows the additional information text.
* Name of the info field of an option, or a function that receives the option and returns the info text.
* @group Props
*/
@Input() optionInfo = 'info';
@Input() optionInfo: OptionKey = 'info';

/**
* Hides hint and validation errors.
Expand Down Expand Up @@ -544,6 +545,10 @@
this._destroy$.complete();
}

getProp(option: any, key: OptionKey): any {
return getOptionProp(option, key);
}

select(
option: any,
byValue: boolean,
Expand All @@ -559,15 +564,17 @@
? option
: this.returnObject
? option
: option[this.optionValue];
: getOptionProp(option, this.optionValue);
if (this.multiple) {
let res = [];
if (includes(this.value, val)) {
res = this.value.filter((v: any) => !isEqual(v, val));
} else {
if (this.keepInitialOrder) {
this.options.forEach((o) => {
const ov = this.returnObject ? o : o[this.optionValue];
const ov = this.returnObject
? o

Check warning on line 576 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: getOptionProp(o, this.optionValue);

Check warning on line 577 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 577 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (
this.value.some((v: any) => isEqual(v, ov)) ||
isEqual(val, ov)
Expand All @@ -575,14 +582,17 @@
res.push(ov);
}
});

Check warning on line 584 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {
const opt = this.options.find((o) => {
return isEqual(val, this.returnObject ? o : o[this.optionValue]);
return isEqual(
val,
this.returnObject ? o : getOptionProp(o, this.optionValue)

Check warning on line 589 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 589 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
);

Check warning on line 590 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
});

Check warning on line 591 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
if (opt) {
res = [
...this.value,
this.returnObject ? opt : opt[this.optionValue]
this.returnObject ? opt : getOptionProp(opt, this.optionValue)

Check warning on line 595 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 595 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
];

Check warning on line 596 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 597 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 597 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

Check warning on line 598 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 598 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 598 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
Expand Down Expand Up @@ -613,7 +623,7 @@
res = this.options;
} else {
this.options.forEach((o) => {
res.push(o[this.optionValue]);
res.push(getOptionProp(o, this.optionValue));
});
}
}
Expand Down Expand Up @@ -649,7 +659,9 @@
this.backspaceClickedOnce = false;

let _filteredOptions = this.options.filter((o: any) => {
let res = o[this.optionLabel].toLowerCase().includes(searchVal);
let res = (getOptionProp(o, this.optionLabel) || '')

Check warning on line 662 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
.toLowerCase()
.includes(searchVal);
if (
!res &&
this.withOptionsAliases &&
Expand Down Expand Up @@ -919,9 +931,9 @@
const option = this.options[this.emptyOptionIndex];
return !option
? undefined
: this.returnObject
? option
: option[this.optionValue];
: getOptionProp(option, this.optionValue);

Check warning on line 936 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 936 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

private _toggleOptions(show?: boolean): void {
Expand Down Expand Up @@ -1012,8 +1024,8 @@

private _getValueLabel() {
return !this.isEmptyValue()
? this.returnObject
? this.value[this.optionLabel]
? getOptionProp(this.value, this.optionLabel)

Check warning on line 1028 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: this._labelByValue.transform(
this.value,
this.options,
Expand Down Expand Up @@ -1161,8 +1173,9 @@
return;
}

const found = this.filteredOptions.find(
(o: any) => o[this.optionLabel].toLowerCase() === searchVal
(o: any) =>

Check warning on line 1177 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
(getOptionProp(o, this.optionLabel) || '').toLowerCase() === searchVal

Check warning on line 1178 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 1178 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 1178 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 1178 in projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
);
if (found) {
this.select(found, false, true, needFocusInput);
Expand Down
Loading
Loading