Skip to content

Adding disabledFiltering to simple combo - 19.2.x #16014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: 19.2.x
Choose a base branch
from
Open
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

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

## 19.2.15
### General
- `IgxSimpleCombo`
- Added `disableFiltering` to the `IgxSimpleCombo`, which enables/disables the filtering in the list. The default is `false`. With this introduction, `filteringOptions.filterable` is no longer used.
- `IgxCombo`, `IgxSimpleCombo`
- Removed deprecated `filteringOptions.filterable` option.

## 19.2.0

### General
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@
"version": "19.2.0",
"description": "Updates Ignite UI for Angular from v19.1.0 to v19.2.0",
"factory": "./update-19_2_0"
},
"migration-46": {
"version": "19.2.15",
"description": "Updates Ignite UI for Angular from v19.2.0 to v19.2.15",
"factory": "./update-19_2_15"
}
}
}
108 changes: 108 additions & 0 deletions projects/igniteui-angular/migrations/update-19_2_15/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as path from 'path';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { setupTestTree } from '../common/setup.spec';

describe('Migration 19.2.15 - Replace filteringOptions.filterable', () => {
let appTree: UnitTestTree;
const runner = new SchematicTestRunner(
'ig-migrate',
path.join(__dirname, '../migration-collection.json')
);
const migrationName = 'migration-46';
const makeTemplate = (name: string) => `/testSrc/appPrefix/component/${name}.component.html`;
const makeScript = (name: string) => `/testSrc/appPrefix/component/${name}.component.ts`;
const components = ['igx-simple-combo', 'igx-combo'];



beforeEach(() => {
appTree = setupTestTree();
});

it('should replace simple inline filteringOptions.filterable true with default behavior of the simple combo', async () => {
components.forEach(async component =>{
const input = `<${component} [filteringOptions]="{ filterable: true }"></${component}>`;
appTree.create(makeTemplate(`${component}-inline-true`), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate(`${component}-inline-true`));

expect(output).not.toContain('[disableFiltering]');
expect(output).not.toContain('filterable');
});
});

it('should handle mixed object literal correctly', async () => {
components.forEach(async component =>{
const input = `<${component} [filteringOptions]="{ filterable: false, caseSensitive: true }"></${component}>`;
appTree.create(makeTemplate(`${component}-inline2`), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate(`${component}-inline2`));

expect(output).toContain(`[disableFiltering]="true"`);
expect(output).toContain(`[filteringOptions]="{ caseSensitive: true }"`);
});
});

it('should warn on variable reference', async () => {
components.forEach(async component =>{
const input = `<${component} [filteringOptions]="filterOpts""></${component}>`;
const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

appTree.create(makeTemplate(`${component}-referenceInTsFile`), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate(`${component}-referenceInTsFile`));

expect(output).toContain('[filteringOptions]');
expect(output).toContain(warnMsg);
});
});

it('should skip adding new [disableFiltering] if already present on igx-combo', async () => {
const input = `<igx-combo [disableFiltering]="true" [filteringOptions]="{ filterable: false }"></igx-combo>`;
appTree.create(makeTemplate('combo-has-disableFiltering'), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeTemplate('combo-has-disableFiltering'));

const occurrences = (output.match(/\[disableFiltering\]/g) || []).length;

expect(occurrences).toBe(1);
expect(output).not.toContain('filterable');
});

// TS file tests

it('should insert warning comment before `.filteringOptions.filterable = ...` assignment', async () => {
const input = `this.igxCombo.filteringOptions.filterable = false;`;
const expectedComment = "// Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

appTree.create(makeScript('tsWarnOnDirectAssignment'), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeScript('tsWarnOnDirectAssignment'));

expect(output).toContain(expectedComment);
expect(output).toContain('this.igxCombo.filteringOptions.filterable = false;');
});

it('should insert warning comment before `.filteringOptions = { ... }` assignment', async () => {
const input = `this.igxCombo.filteringOptions = { filterable: false, caseSensitive: true };`;
const expectedComment = "// Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

appTree.create(makeScript('tsWarnOnObjectAssignment'), input);

const tree = await runner.runSchematic(migrationName, {}, appTree);
const output = tree.readContent(makeScript('tsWarnOnObjectAssignment'));

expect(output).toContain(expectedComment);
expect(output).toContain('this.igxCombo.filteringOptions = { filterable: false, caseSensitive: true };');
});


});
127 changes: 127 additions & 0 deletions projects/igniteui-angular/migrations/update-19_2_15/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Element } from '@angular/compiler';
import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { UpdateChanges } from '../common/UpdateChanges';
import {
FileChange,
findElementNodes,
getSourceOffset,
parseFile
} from '../common/util';
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.js';

const version = '19.2.15';

export default (): Rule => async (host: Tree, context: SchematicContext) => {
context.logger.info(
`Applying migration for Ignite UI for Angular to version ${version}`
);

const { HtmlParser } = await nativeImport('@angular/compiler') as typeof import('@angular/compiler');

const update = new UpdateChanges(__dirname, host, context);
const changes = new Map<string, FileChange[]>();
const parser = new HtmlParser();

const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
"Since it has been deprecated.'";

const applyChanges = () => {
for (const [path, fileChanges] of changes.entries()) {
let content = host.read(path).toString();
fileChanges.sort((a, b) => b.position - a.position).forEach(c => {
content = c.apply(content);
});
host.overwrite(path, content);
}
};

const addChange = (path: string, change: FileChange) => {
if (!changes.has(path)) {
changes.set(path, []);
}
changes.get(path).push(change);
};

const COMBO_TAGS = ['igx-simple-combo', 'igx-combo'];

for (const path of update.templateFiles) {
const nodes = findElementNodes(parseFile(parser, host, path), COMBO_TAGS);

for (const node of nodes) {
if (!(node instanceof Element)) continue;

const hasDisableFiltering = node.attrs.some(a => a.name.includes('disableFiltering'));
const attr = node.attrs.find(a => a.name === '[filteringOptions]');
if (!attr) continue;

const attrVal = attr.value.trim();
const offset = getSourceOffset(node);
const file = offset.file;

let replacementText = '';

if (attrVal.startsWith('{')) {
// inline object literal
const normalized = attrVal
.replace(/'/g, '"')
.replace(/([{,]\s*)(\w+)\s*:/g, '$1"$2":');
const parsed = JSON.parse(normalized);
const filterable = parsed.filterable;

if (filterable === false && !hasDisableFiltering) {
replacementText += `[disableFiltering]="true"`;
}

const remaining = { ...parsed };
delete remaining.filterable;
const remainingProps = Object.entries(remaining)
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
.join(', ');

if (remainingProps.length > 0) {
replacementText += ` [filteringOptions]="{ ${remainingProps} }"`;
}

// Replace whole [filteringOptions] attribute
const match = node.sourceSpan.toString().match(/\[filteringOptions\]="([^"]+)"/);
if (match) {
const attrText = match[0];
const attrPos = file.content.indexOf(attrText, offset.startTag.start);
addChange(file.url, new FileChange(attrPos, replacementText, attrText, 'replace'));
}
} else {
// log for manual TS edit
const comment = `\n<!-- ${warnMsg} -->\n`;
addChange(file.url, new FileChange(offset.startTag.end, comment));
}
}
}

applyChanges();

for (const path of update.tsFiles) {
const content = host.read(path).toString();
const lines = content.split('\n');
const newLines: string[] = [];


let modified = false;

for (const line of lines) {
if (
/\.filteringOptions\.filterable\s*=/.test(line) ||
/\.filteringOptions\s*=/.test(line)
) {
newLines.push('// ' + warnMsg);
modified = true;
}
newLines.push(line);
}

if (modified) {
host.overwrite(path, newLines.join('\n'));
}
}

update.applyChanges();
};
18 changes: 13 additions & 5 deletions projects/igniteui-angular/src/lib/combo/combo.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,6 @@ export const enum DataTypes {
export interface IComboFilteringOptions {
/** Defines filtering case-sensitivity */
caseSensitive?: boolean;
/**
* Defines whether filtering is allowed
* @deprecated in version 18.2.0. Use the `disableFiltering` property instead.
*/
filterable?: boolean;
/** Defines optional key to filter against complex list items. Default to displayKey if provided.*/
filteringKey?: string;
}
Expand All @@ -123,6 +118,18 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
@Input({ transform: booleanAttribute })
public showSearchCaseIcon = false;

/**
* Enables/disables filtering in the list. The default is `false`.
*/
@Input({ transform: booleanAttribute })
public get disableFiltering(): boolean {
return this._disableFiltering;
}
public set disableFiltering(value: boolean) {
this._disableFiltering = value;
}


/**
* Set custom overlay settings that control how the combo's list of items is displayed.
* Set:
Expand Down Expand Up @@ -945,6 +952,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
protected computedStyles;

private _id: string = `igx-combo-${NEXT_ID++}`;
private _disableFiltering = false;
private _type = null;
private _dataType = '';
private _itemHeight = undefined;
Expand Down
12 changes: 0 additions & 12 deletions projects/igniteui-angular/src/lib/combo/combo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
@Input({ transform: booleanAttribute })
public autoFocusSearch = true;

/**
* Enables/disables filtering in the list. The default is `false`.
*/
@Input({ transform: booleanAttribute })
public get disableFiltering(): boolean {
return this._disableFiltering || this.filteringOptions.filterable === false;
}
public set disableFiltering(value: boolean) {
this._disableFiltering = value;
}

/**
* Defines the placeholder value for the combo dropdown search field
*
Expand Down Expand Up @@ -184,7 +173,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
protected _prevInputValue = '';

private _displayText: string;
private _disableFiltering = false;

constructor(
elementRef: ElementRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
(focus)="dropdown.onFocus()" (keydown)="handleItemKeyDown($event)">
<igx-combo-item [role]="item?.isHeader? 'group' : 'option'" [singleMode]="true"
[itemHeight]="itemHeight" (click)="handleItemClick()" *igxFor="let item of data
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction:disableFiltering
| comboGrouping:groupKey:valueKey:groupSortingDirection:compareCollator;
index as rowIndex; initialChunkSize: 10; containerSize: itemsMaxHeight || containerSize; itemSize: itemHeight || itemSize; scrollOrientation: 'vertical';"
[value]="item" [isHeader]="item?.isHeader" [index]="rowIndex">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,31 @@ describe('IgxSimpleCombo', () => {
expect(combo.displayValue).toEqual('Wisconsin');
});

it('should not filter the data when disableFiltering is true', () => {
combo.disableFiltering = true;
fixture.detectChanges();
combo.focusSearchInput();

UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();

expect(combo.filteredData.length).toEqual(combo.data.length);
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();

combo.disableFiltering = false;
fixture.detectChanges();
combo.focusSearchInput();

combo.comboInput.value = '';
fixture.detectChanges();
UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();
expect(combo.filteredData.length).toEqual(2);
});

it('should display the AddItem button when allowCustomValues is true and there is a partial match', fakeAsync(() => {
fixture.componentInstance.allowCustomValues = true;
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ export class IgxSimpleComboComponent extends IgxComboBaseDirective implements Co
if (this.collapsed && this.comboInput.focused) {
this.open();
}

if (event !== undefined) {
this.filterValue = this.searchValue = typeof event === 'string' ? event : event.target.value;
}
Expand Down
Loading