Skip to content

Commit b5a7cae

Browse files
Merge pull request #16013 from IgniteUI/ikitanov/fix#15913-20.0.x
Adding disableFiltering to simple-combo - 20.0.x
2 parents ed19b66 + 89808d6 commit b5a7cae

File tree

8 files changed

+281
-18
lines changed

8 files changed

+281
-18
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
All notable changes for each version of this project will be documented in this file.
44

5+
## 20.0.6
6+
### General
7+
- `IgxSimpleCombo`
8+
- Added `disableFiltering` to the `IgxSimpleCombo`, which enables/disables the filtering in the list. The default is `false`.
9+
- `IgxCombo`, `IgxSimpleCombo`
10+
- Removed deprecated `filteringOptions.filterable` option.
11+
512
## 20.0.2
613

714
### New Features
@@ -42,7 +49,6 @@ All notable changes for each version of this project will be documented in this
4249
```
4350

4451
## 19.2.0
45-
4652
### General
4753
- `IgxCarousel`
4854
- Removed deprecated property `keyboardSupport`.

projects/igniteui-angular/migrations/migration-collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@
236236
"version": "20.0.2",
237237
"description": "Updates Ignite UI for Angular from v20.0.0 to v20.0.2",
238238
"factory": "./update-20_0_2"
239+
},
240+
"migration-48": {
241+
"version": "20.0.6",
242+
"description": "Updates Ignite UI for Angular from v20.0.2 to v20.0.6",
243+
"factory": "./update-20_0_6"
239244
}
240245
}
241246
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as path from 'path';
2+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
3+
import { setupTestTree } from '../common/setup.spec';
4+
5+
describe('Migration 20.0.6 - Replace filteringOptions.filterable', () => {
6+
let appTree: UnitTestTree;
7+
const runner = new SchematicTestRunner(
8+
'ig-migrate',
9+
path.join(__dirname, '../migration-collection.json')
10+
);
11+
const migrationName = 'migration-48';
12+
const makeTemplate = (name: string) => `/testSrc/appPrefix/component/${name}.component.html`;
13+
const makeScript = (name: string) => `/testSrc/appPrefix/component/${name}.component.ts`;
14+
const components = ['igx-simple-combo', 'igx-combo'];
15+
16+
17+
18+
beforeEach(() => {
19+
appTree = setupTestTree();
20+
});
21+
22+
it('should replace simple inline filteringOptions.filterable true with default behavior of the simple combo', async () => {
23+
components.forEach(async component =>{
24+
const input = `<${component} [filteringOptions]="{ filterable: true }"></${component}>`;
25+
appTree.create(makeTemplate(`${component}-inline-true`), input);
26+
27+
const tree = await runner.runSchematic(migrationName, {}, appTree);
28+
const output = tree.readContent(makeTemplate(`${component}-inline-true`));
29+
30+
expect(output).not.toContain('[disableFiltering]');
31+
expect(output).not.toContain('filterable');
32+
});
33+
});
34+
35+
it('should handle mixed object literal correctly', async () => {
36+
components.forEach(async component =>{
37+
const input = `<${component} [filteringOptions]="{ filterable: false, caseSensitive: true }"></${component}>`;
38+
appTree.create(makeTemplate(`${component}-inline2`), input);
39+
40+
const tree = await runner.runSchematic(migrationName, {}, appTree);
41+
const output = tree.readContent(makeTemplate(`${component}-inline2`));
42+
43+
expect(output).toContain(`[disableFiltering]="true"`);
44+
expect(output).toContain(`[filteringOptions]="{ caseSensitive: true }"`);
45+
});
46+
});
47+
48+
it('should warn on variable reference', async () => {
49+
components.forEach(async component =>{
50+
const input = `<${component} [filteringOptions]="filterOpts""></${component}>`;
51+
const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
52+
"Since it has been deprecated.'";
53+
54+
appTree.create(makeTemplate(`${component}-referenceInTsFile`), input);
55+
56+
const tree = await runner.runSchematic(migrationName, {}, appTree);
57+
const output = tree.readContent(makeTemplate(`${component}-referenceInTsFile`));
58+
59+
expect(output).toContain('[filteringOptions]');
60+
expect(output).toContain(warnMsg);
61+
});
62+
});
63+
64+
it('should skip adding new [disableFiltering] if already present on igx-combo', async () => {
65+
const input = `<igx-combo [disableFiltering]="true" [filteringOptions]="{ filterable: false }"></igx-combo>`;
66+
appTree.create(makeTemplate('combo-has-disableFiltering'), input);
67+
68+
const tree = await runner.runSchematic(migrationName, {}, appTree);
69+
const output = tree.readContent(makeTemplate('combo-has-disableFiltering'));
70+
71+
const occurrences = (output.match(/\[disableFiltering\]/g) || []).length;
72+
73+
expect(occurrences).toBe(1);
74+
expect(output).not.toContain('filterable');
75+
});
76+
77+
// TS file tests
78+
79+
it('should insert warning comment before `.filteringOptions.filterable = ...` assignment', async () => {
80+
const input = `this.igxCombo.filteringOptions.filterable = false;`;
81+
const expectedComment = "// Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
82+
"Since it has been deprecated.'";
83+
84+
appTree.create(makeScript('tsWarnOnDirectAssignment'), input);
85+
86+
const tree = await runner.runSchematic(migrationName, {}, appTree);
87+
const output = tree.readContent(makeScript('tsWarnOnDirectAssignment'));
88+
89+
expect(output).toContain(expectedComment);
90+
expect(output).toContain('this.igxCombo.filteringOptions.filterable = false;');
91+
});
92+
93+
it('should insert warning comment before `.filteringOptions = { ... }` assignment', async () => {
94+
const input = `this.igxCombo.filteringOptions = { filterable: false, caseSensitive: true };`;
95+
const expectedComment = "// Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
96+
"Since it has been deprecated.'";
97+
98+
appTree.create(makeScript('tsWarnOnObjectAssignment'), input);
99+
100+
const tree = await runner.runSchematic(migrationName, {}, appTree);
101+
const output = tree.readContent(makeScript('tsWarnOnObjectAssignment'));
102+
103+
expect(output).toContain(expectedComment);
104+
expect(output).toContain('this.igxCombo.filteringOptions = { filterable: false, caseSensitive: true };');
105+
});
106+
});
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { Element } from '@angular/compiler';
2+
import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
3+
import { UpdateChanges } from '../common/UpdateChanges';
4+
import {
5+
FileChange,
6+
findElementNodes,
7+
getSourceOffset,
8+
parseFile
9+
} from '../common/util';
10+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.js';
11+
12+
const version = '20.0.6';
13+
14+
export default (): Rule => async (host: Tree, context: SchematicContext) => {
15+
context.logger.info(
16+
`Applying migration for Ignite UI for Angular to version ${version}`
17+
);
18+
19+
const { HtmlParser } = await nativeImport('@angular/compiler') as typeof import('@angular/compiler');
20+
21+
const update = new UpdateChanges(__dirname, host, context);
22+
const changes = new Map<string, FileChange[]>();
23+
const parser = new HtmlParser();
24+
25+
const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
26+
"Since it has been deprecated.'";
27+
28+
const applyChanges = () => {
29+
for (const [path, fileChanges] of changes.entries()) {
30+
let content = host.read(path).toString();
31+
fileChanges.sort((a, b) => b.position - a.position).forEach(c => {
32+
content = c.apply(content);
33+
});
34+
host.overwrite(path, content);
35+
}
36+
};
37+
38+
const addChange = (path: string, change: FileChange) => {
39+
if (!changes.has(path)) {
40+
changes.set(path, []);
41+
}
42+
changes.get(path).push(change);
43+
};
44+
45+
const COMBO_TAGS = ['igx-simple-combo', 'igx-combo'];
46+
47+
for (const path of update.templateFiles) {
48+
const nodes = findElementNodes(parseFile(parser, host, path), COMBO_TAGS);
49+
50+
for (const node of nodes) {
51+
if (!(node instanceof Element)) continue;
52+
53+
const hasDisableFiltering = node.attrs.some(a => a.name.includes('disableFiltering'));
54+
const attr = node.attrs.find(a => a.name === '[filteringOptions]');
55+
if (!attr) continue;
56+
57+
const attrVal = attr.value.trim();
58+
const offset = getSourceOffset(node);
59+
const file = offset.file;
60+
61+
let replacementText = '';
62+
63+
if (attrVal.startsWith('{')) {
64+
// inline object literal
65+
const normalized = attrVal
66+
.replace(/'/g, '"')
67+
.replace(/([{,]\s*)(\w+)\s*:/g, '$1"$2":');
68+
const parsed = JSON.parse(normalized);
69+
const filterable = parsed.filterable;
70+
71+
if (filterable === false && !hasDisableFiltering) {
72+
replacementText += `[disableFiltering]="true"`;
73+
}
74+
75+
const remaining = { ...parsed };
76+
delete remaining.filterable;
77+
const remainingProps = Object.entries(remaining)
78+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
79+
.join(', ');
80+
81+
if (remainingProps.length > 0) {
82+
replacementText += ` [filteringOptions]="{ ${remainingProps} }"`;
83+
}
84+
85+
// Replace whole [filteringOptions] attribute
86+
const match = node.sourceSpan.toString().match(/\[filteringOptions\]="([^"]+)"/);
87+
if (match) {
88+
const attrText = match[0];
89+
const attrPos = file.content.indexOf(attrText, offset.startTag.start);
90+
addChange(file.url, new FileChange(attrPos, replacementText, attrText, 'replace'));
91+
}
92+
} else {
93+
// log for manual TS edit
94+
const comment = `\n<!-- ${warnMsg} -->\n`;
95+
addChange(file.url, new FileChange(offset.startTag.end, comment));
96+
}
97+
}
98+
}
99+
100+
applyChanges();
101+
102+
for (const path of update.tsFiles) {
103+
const content = host.read(path).toString();
104+
const lines = content.split('\n');
105+
const newLines: string[] = [];
106+
107+
let modified = false;
108+
109+
for (const line of lines) {
110+
if (
111+
/\.filteringOptions\.filterable\s*=/.test(line) ||
112+
/\.filteringOptions\s*=/.test(line)
113+
) {
114+
newLines.push('// ' + warnMsg);
115+
modified = true;
116+
}
117+
newLines.push(line);
118+
}
119+
120+
if (modified) {
121+
host.overwrite(path, newLines.join('\n'));
122+
}
123+
}
124+
125+
update.applyChanges();
126+
};

projects/igniteui-angular/src/lib/combo/combo.common.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,6 @@ export const enum DataTypes {
9595
export interface IComboFilteringOptions {
9696
/** Defines filtering case-sensitivity */
9797
caseSensitive?: boolean;
98-
/**
99-
* Defines whether filtering is allowed
100-
* @deprecated in version 18.2.0. Use the `disableFiltering` property instead.
101-
*/
102-
filterable?: boolean;
10398
/** Defines optional key to filter against complex list items. Default to displayKey if provided.*/
10499
filteringKey?: string;
105100
}
@@ -123,6 +118,17 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
123118
@Input({ transform: booleanAttribute })
124119
public showSearchCaseIcon = false;
125120

121+
/**
122+
* Enables/disables filtering in the list. The default is `false`.
123+
*/
124+
@Input({ transform: booleanAttribute })
125+
public get disableFiltering(): boolean {
126+
return this._disableFiltering;
127+
}
128+
public set disableFiltering(value: boolean) {
129+
this._disableFiltering = value;
130+
}
131+
126132
/**
127133
* Set custom overlay settings that control how the combo's list of items is displayed.
128134
* Set:
@@ -945,6 +951,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
945951
protected computedStyles;
946952

947953
private _id: string = `igx-combo-${NEXT_ID++}`;
954+
private _disableFiltering = false;
948955
private _type = null;
949956
private _dataType = '';
950957
private _itemHeight = undefined;

projects/igniteui-angular/src/lib/combo/combo.component.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
128128
@Input({ transform: booleanAttribute })
129129
public autoFocusSearch = true;
130130

131-
/**
132-
* Enables/disables filtering in the list. The default is `false`.
133-
*/
134-
@Input({ transform: booleanAttribute })
135-
public get disableFiltering(): boolean {
136-
return this._disableFiltering || this.filteringOptions.filterable === false;
137-
}
138-
public set disableFiltering(value: boolean) {
139-
this._disableFiltering = value;
140-
}
141131

142132
/**
143133
* Defines the placeholder value for the combo dropdown search field
@@ -184,7 +174,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
184174
protected _prevInputValue = '';
185175

186176
private _displayText: string;
187-
private _disableFiltering = false;
188177

189178
constructor(
190179
elementRef: ElementRef,

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
(focus)="dropdown.onFocus()" (keydown)="handleItemKeyDown($event)">
6969
<igx-combo-item [role]="item?.isHeader? 'group' : 'option'" [singleMode]="true"
7070
[itemHeight]="itemHeight" (click)="handleItemClick()" *igxFor="let item of data
71-
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
71+
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction:disableFiltering
7272
| comboGrouping:groupKey:valueKey:groupSortingDirection:compareCollator;
7373
index as rowIndex; initialChunkSize: 10; containerSize: itemsMaxHeight || containerSize; itemSize: itemHeight || itemSize; scrollOrientation: 'vertical';"
7474
[value]="item" [isHeader]="item?.isHeader" [index]="rowIndex">

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,30 @@ describe('IgxSimpleCombo', () => {
12301230
expect(combo.displayValue).toEqual('Wisconsin');
12311231
});
12321232

1233+
it('should not filter the data when disableFiltering is true', () => {
1234+
combo.disableFiltering = true;
1235+
fixture.detectChanges();
1236+
combo.focusSearchInput();
1237+
1238+
UIInteractions.simulateTyping('con', input);
1239+
expect(combo.comboInput.value).toEqual('con');
1240+
fixture.detectChanges();
1241+
1242+
expect(combo.filteredData.length).toEqual(combo.data.length);
1243+
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
1244+
fixture.detectChanges();
1245+
1246+
combo.disableFiltering = false;
1247+
fixture.detectChanges();
1248+
combo.focusSearchInput();
1249+
combo.comboInput.value = '';
1250+
fixture.detectChanges();
1251+
UIInteractions.simulateTyping('con', input);
1252+
expect(combo.comboInput.value).toEqual('con');
1253+
fixture.detectChanges();
1254+
expect(combo.filteredData.length).toEqual(2);
1255+
});
1256+
12331257
it('should display the AddItem button when allowCustomValues is true and there is a partial match', fakeAsync(() => {
12341258
fixture.componentInstance.allowCustomValues = true;
12351259
fixture.detectChanges();

0 commit comments

Comments
 (0)