Skip to content

Commit a3c31b2

Browse files
committed
feat(combo): add display text overwrite to selection change event, #6342
1 parent 299f101 commit a3c31b2

File tree

3 files changed

+131
-59
lines changed

3 files changed

+131
-59
lines changed

CHANGELOG.md

+17
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,23 @@ All notable changes for each version of this project will be documented in this
8383
- `IgxSelect`:
8484
- adding `IgxSelectHeaderDirective` and `IgxSelectFooterDirective`. These can be used to provide a custom header, respectively footer templates for the `igxSelect` drop-down list. If there are no templates marked with these directives - no default templates will be used so the drop-down list will not have header nor footer.
8585

86+
- `IgxCombo`:
87+
- Added `displayText` property to the combo's `onSelectionChange` event args. The property contains the text that will be populated in the combo's text box **after** selection completes. This text can be overwritten in order to display a custom message, e.g. "3 items selected":
88+
```html
89+
<igx-combo [data]="people" valueKey="id" displayKey="name" placeholder="Invite friends..." (onSelectionChange)="handleSelection($event)">
90+
```
91+
```typescript
92+
export class MyInvitationComponent {
93+
public people: { name: string; id: string }[] = [...];
94+
...
95+
handleSelection(event: IComboSelectionChangeEventArgs) {
96+
const count = event.newSelection.length;
97+
event.displayText = count > 0 ? `${count} friend(s) invited!` : `No friends invited :(`;
98+
}
99+
...
100+
}
101+
```
102+
86103
- `IgxDropDown`:
87104
- `clearSelection` method is added, which can be used to deselect the selected dropdown item
88105

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

+72-41
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,7 @@ describe('igxCombo', () => {
10201020
newSelection: [targetItem.itemID],
10211021
added: [targetItem.itemID],
10221022
removed: [],
1023+
displayText: `${targetItem.value[combo.displayKey]}`,
10231024
event: undefined,
10241025
cancel: false
10251026
});
@@ -1034,6 +1035,7 @@ describe('igxCombo', () => {
10341035
newSelection: [],
10351036
added: [],
10361037
removed: [targetItem.itemID],
1038+
displayText: '',
10371039
event: undefined,
10381040
cancel: false
10391041
});
@@ -1044,58 +1046,66 @@ describe('igxCombo', () => {
10441046
const combo = fix.componentInstance.combo;
10451047
spyOn(combo.onSelectionChange, 'emit');
10461048
let oldSelection = [];
1049+
/**
1050+
* The test component has a defined `valueKey` property.
1051+
* `selectItems` method should be called with the items' corresponding valueKeys
1052+
*/
10471053
let newSelection = [combo.data[1], combo.data[5], combo.data[6]];
1054+
let newSelectionKeys = newSelection.map(entry => entry[combo.valueKey]);
10481055

10491056
combo.toggle();
10501057
tick();
10511058
fix.detectChanges();
1052-
combo.selectItems(newSelection);
1059+
combo.selectItems(newSelectionKeys);
10531060
fix.detectChanges();
10541061
expect(combo.selectedItems().length).toEqual(newSelection.length);
10551062
expect(combo.onSelectionChange.emit).toHaveBeenCalledTimes(1);
10561063
expect(combo.onSelectionChange.emit).toHaveBeenCalledWith({
10571064
oldSelection: oldSelection,
1058-
newSelection: newSelection,
1059-
added: [combo.data[1], combo.data[5], combo.data[6]],
1065+
newSelection: newSelectionKeys,
1066+
added: newSelectionKeys,
10601067
removed: [],
1068+
displayText: newSelection.map(entry => entry[combo.valueKey]).join(', '),
10611069
event: undefined,
10621070
cancel: false
10631071
});
10641072

10651073
let newItem = combo.data[3];
1066-
combo.selectItems([newItem]);
1067-
oldSelection = [...newSelection];
1068-
newSelection.push(newItem);
1074+
combo.selectItems([newItem[combo.valueKey]]);
1075+
oldSelection = [...newSelectionKeys];
1076+
newSelectionKeys.push(newItem[combo.valueKey]);
10691077
fix.detectChanges();
1070-
expect(combo.selectedItems().length).toEqual(newSelection.length);
1078+
expect(combo.selectedItems().length).toEqual(newSelectionKeys.length);
10711079
expect(combo.onSelectionChange.emit).toHaveBeenCalledTimes(2);
10721080
expect(combo.onSelectionChange.emit).toHaveBeenCalledWith({
10731081
oldSelection: oldSelection,
1074-
newSelection: newSelection,
1082+
newSelection: newSelectionKeys,
10751083
removed: [],
1076-
added: [combo.data[3]],
1084+
added: [newItem[combo.valueKey]],
1085+
displayText: newSelectionKeys.join(', '),
10771086
event: undefined,
10781087
cancel: false
10791088
});
10801089

1081-
oldSelection = [...newSelection];
1082-
newSelection = [combo.data[0]];
1083-
combo.selectItems(newSelection, true);
1090+
oldSelection = [...newSelectionKeys];
1091+
newSelectionKeys = [combo.data[0][combo.valueKey]];
1092+
combo.selectItems(newSelectionKeys, true);
10841093
fix.detectChanges();
1085-
expect(combo.selectedItems().length).toEqual(newSelection.length);
1094+
expect(combo.selectedItems().length).toEqual(newSelectionKeys.length);
10861095
expect(combo.onSelectionChange.emit).toHaveBeenCalledTimes(3);
10871096
expect(combo.onSelectionChange.emit).toHaveBeenCalledWith({
10881097
oldSelection: oldSelection,
1089-
newSelection: newSelection,
1098+
newSelection: newSelectionKeys,
10901099
removed: oldSelection,
1091-
added: [combo.data[0]],
1100+
added: [combo.data[0][combo.valueKey]],
1101+
displayText: combo.data[0][combo.valueKey],
10921102
event: undefined,
10931103
cancel: false
10941104
});
10951105

1096-
oldSelection = [...newSelection];
1106+
oldSelection = [...newSelectionKeys];
10971107
newSelection = [];
1098-
newItem = combo.data[0];
1108+
newItem = combo.data[0][combo.valueKey];
10991109
combo.deselectItems([newItem]);
11001110
fix.detectChanges();
11011111
expect(combo.selectedItems().length).toEqual(newSelection.length);
@@ -1104,7 +1114,8 @@ describe('igxCombo', () => {
11041114
expect(combo.onSelectionChange.emit).toHaveBeenCalledWith({
11051115
oldSelection: oldSelection,
11061116
newSelection: newSelection,
1107-
removed: [combo.data[0]],
1117+
removed: [newItem],
1118+
displayText: ``,
11081119
added: [],
11091120
event: undefined,
11101121
cancel: false
@@ -1422,11 +1433,12 @@ describe('igxCombo', () => {
14221433
const dropdown = combo.dropdown;
14231434
let timesFired = 1;
14241435
const mockEvent = new MouseEvent('click');
1425-
const eventParams = {
1436+
const eventParams: IComboSelectionChangeEventArgs = {
14261437
oldSelection: [],
14271438
newSelection: [],
14281439
added: [],
14291440
removed: [],
1441+
displayText: '',
14301442
event: mockEvent,
14311443
cancel: false
14321444
};
@@ -1446,12 +1458,14 @@ describe('igxCombo', () => {
14461458

14471459
eventParams.newSelection = [dropdown.items[3].value];
14481460
eventParams.added = [dropdown.items[3].value];
1461+
eventParams.displayText = dropdown.items[3].value;
14491462
verifyOnSelectionChangeEventIsFired(3);
14501463
timesFired++;
14511464

14521465
eventParams.oldSelection = [dropdown.items[3].value];
14531466
eventParams.newSelection = [dropdown.items[3].value, dropdown.items[7].value];
14541467
eventParams.added = [dropdown.items[7].value];
1468+
eventParams.displayText = `${dropdown.items[3].value}, ${dropdown.items[7].value}`;
14551469
verifyOnSelectionChangeEventIsFired(7);
14561470
timesFired++;
14571471

@@ -1460,6 +1474,7 @@ describe('igxCombo', () => {
14601474
eventParams.newSelection = [dropdown.items[3].value];
14611475
eventParams.added = [];
14621476
eventParams.removed = [dropdown.items[7].value];
1477+
eventParams.displayText = dropdown.items[3].value;
14631478
verifyOnSelectionChangeEventIsFired(7);
14641479
}));
14651480
it('Should be able to select item when in grouped state', fakeAsync(() => {
@@ -1593,56 +1608,67 @@ describe('igxCombo', () => {
15931608
fixture.detectChanges();
15941609
const combo = fixture.componentInstance.combo;
15951610
const selectionSpy = spyOn(fixture.componentInstance, 'onSelectionChange');
1596-
const expectedResults = {
1597-
newSelection: [combo.data[0]],
1611+
const expectedResults: IComboSelectionChangeEventArgs = {
1612+
newSelection: [combo.data[0][combo.valueKey]],
15981613
oldSelection: [],
1599-
added: [combo.data[0]],
1614+
added: [combo.data[0][combo.valueKey]],
16001615
removed: [],
16011616
event: undefined,
1617+
displayText: `${combo.data[0][combo.displayKey]}`,
16021618
cancel: false
16031619
};
1604-
combo.selectItems([combo.data[0]]);
1620+
combo.selectItems([combo.data[0][combo.valueKey]]);
16051621
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
16061622
Object.assign(expectedResults, {
16071623
newSelection: [],
1608-
oldSelection: [combo.data[0]],
1624+
oldSelection: [combo.data[0][combo.valueKey]],
16091625
added: [],
1610-
removed: [combo.data[0]]
1626+
displayText: '',
1627+
removed: [combo.data[0][combo.valueKey]]
16111628
});
1612-
combo.deselectItems([combo.data[0]]);
1629+
combo.deselectItems([combo.data[0][combo.valueKey]]);
16131630
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
16141631
});
16151632

16161633
it('Should properly emit added and removed values in change event - multiple values', () => {
16171634
const fixture = TestBed.createComponent(IgxComboSampleComponent);
16181635
fixture.detectChanges();
16191636
const combo = fixture.componentInstance.combo;
1637+
let oldSelection = [];
1638+
let newSelection = [combo.data[0], combo.data[1], combo.data[2]];
16201639
const selectionSpy = spyOn(fixture.componentInstance, 'onSelectionChange');
1621-
const expectedResults = {
1622-
newSelection: [combo.data[0], combo.data[1], combo.data[2]],
1623-
oldSelection: [],
1624-
added: [combo.data[0], combo.data[1], combo.data[2]],
1640+
const expectedResults: IComboSelectionChangeEventArgs = {
1641+
newSelection: newSelection.map(e => e[combo.valueKey]),
1642+
oldSelection,
1643+
added: newSelection.map(e => e[combo.valueKey]),
16251644
removed: [],
16261645
event: undefined,
1646+
displayText: `${newSelection.map(entry => entry[combo.displayKey]).join(', ')}`,
16271647
cancel: false
16281648
};
1629-
combo.selectItems([combo.data[0], combo.data[1], combo.data[2]]);
1649+
combo.selectItems(newSelection.map(e => e[combo.valueKey]));
16301650
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
1631-
combo.deselectItems([combo.data[0]]);
1651+
oldSelection = [...newSelection].map(e => e[combo.valueKey]);
1652+
newSelection = [combo.data[1], combo.data[2]];
1653+
combo.deselectItems([combo.data[0][combo.valueKey]]);
16321654
Object.assign(expectedResults, {
1633-
newSelection: [combo.data[1], combo.data[2]],
1634-
oldSelection: [combo.data[0], combo.data[1], combo.data[2]],
1655+
newSelection: newSelection.map(e => e[combo.valueKey]),
1656+
oldSelection,
16351657
added: [],
1636-
removed: [combo.data[0]]
1658+
displayText: newSelection.map(e => e[combo.displayKey]).join(', '),
1659+
removed: [combo.data[0][combo.valueKey]]
16371660
});
1661+
oldSelection = [...newSelection].map(e => e[combo.valueKey]);
1662+
newSelection = [combo.data[4], combo.data[5], combo.data[6]];
16381663
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
16391664
Object.assign(expectedResults, {
1640-
newSelection: [combo.data[4], combo.data[5], combo.data[6]],
1641-
oldSelection: [combo.data[1], combo.data[2]],
1642-
added: [combo.data[4], combo.data[5], combo.data[6]],
1643-
removed: [combo.data[1], combo.data[2]]
1665+
newSelection: newSelection.map(e => e[combo.valueKey]),
1666+
oldSelection,
1667+
added: newSelection.map(e => e[combo.valueKey]),
1668+
displayText: newSelection.map(e => e[combo.displayKey]).join(', '),
1669+
removed: oldSelection
16441670
});
1645-
combo.selectItems([combo.data[4], combo.data[5], combo.data[6]], true);
1671+
combo.selectItems(newSelection.map(e => e[combo.valueKey]), true);
16461672
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
16471673
});
16481674
});
@@ -3432,7 +3458,12 @@ class IgxComboScrollTestComponent {
34323458
`
34333459
})
34343460
class IgxComboSampleComponent {
3435-
3461+
/**
3462+
* TODO
3463+
* Test that use this component should properly call `selectItems` method
3464+
* IF a `valueKey` is defined, calls should be w/ the items' valueKey property value
3465+
* IF no `valueKey` is defined, calls should be w/ object references to the items
3466+
*/
34363467
@ViewChild('combo', { read: IgxComboComponent, static: true })
34373468
public combo: IgxComboComponent;
34383469

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

+42-18
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export interface IComboSelectionChangeEventArgs extends CancelableEventArgs, IBa
9191
added: any[];
9292
/** An array containing the values that will be removed from the selection (if any) */
9393
removed: any[];
94+
/** The text that will be displayed in the combo text box */
95+
displayText: string;
9496
/** The user interaction that triggered the selection change */
9597
event?: Event;
9698
}
@@ -1382,39 +1384,61 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
13821384
protected setSelection(newSelection: Set<any>, event?: Event): void {
13831385
const removed = diffInSets(this.selection.get(this.id), newSelection);
13841386
const added = diffInSets(newSelection, this.selection.get(this.id));
1387+
const newSelectionAsArray = Array.from(newSelection);
1388+
const oldSelectionAsArray = Array.from(this.selection.get(this.id) || []);
1389+
const displayText = this.createDisplayText(newSelectionAsArray, oldSelectionAsArray);
13851390
const args: IComboSelectionChangeEventArgs = {
1386-
newSelection: Array.from(newSelection),
1387-
oldSelection: Array.from(this.selection.get(this.id) || []),
1391+
newSelection: newSelectionAsArray,
1392+
oldSelection: oldSelectionAsArray,
13881393
added,
13891394
removed,
13901395
event,
1396+
displayText,
13911397
cancel: false
13921398
};
13931399
this.onSelectionChange.emit(args);
13941400
if (!args.cancel) {
13951401
this.selection.select_items(this.id, args.newSelection, true);
1396-
let value = '';
1397-
if (this.isRemote) {
1398-
if (args.newSelection.length) {
1399-
const removedItems = args.oldSelection.filter(e => args.newSelection.indexOf(e) < 0);
1400-
const addedItems = args.newSelection.filter(e => args.oldSelection.indexOf(e) < 0);
1401-
this.registerRemoteEntries(addedItems);
1402-
this.registerRemoteEntries(removedItems, false);
1403-
value = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e]).join(', ');
1404-
} else {
1405-
// If new selection is empty, clear all items
1406-
this.registerRemoteEntries(args.oldSelection, false);
1407-
}
1402+
if (displayText !== args.displayText) {
1403+
this._value = args.displayText;
14081404
} else {
1409-
value = this.displayKey !== null && this.displayKey !== undefined ?
1410-
this.convertKeysToItems(args.newSelection).map(entry => entry[this.displayKey]).join(', ') :
1411-
args.newSelection.join(', ');
1405+
this._value = this.createDisplayText(args.newSelection, args.oldSelection);
14121406
}
1413-
this._value = value;
14141407
this._onChangeCallback(args.newSelection);
14151408
}
14161409
}
14171410

1411+
/** Returns a string that should be populated in the combo's text box */
1412+
private concatDisplayText(selection: any[]): string {
1413+
const value = this.displayKey !== null && this.displayKey !== undefined ?
1414+
this.convertKeysToItems(selection).map(entry => entry[this.displayKey]).join(', ') :
1415+
selection.join(', ');
1416+
return value;
1417+
}
1418+
1419+
/** Constructs the combo display value
1420+
* If remote, caches the key displayText
1421+
* If not, just combine the object.displayKeys
1422+
*/
1423+
private createDisplayText(newSelection: any[], oldSelection: any[]) {
1424+
let value = '';
1425+
if (this.isRemote) {
1426+
if (newSelection.length) {
1427+
const removedItems = oldSelection.filter(e => newSelection.indexOf(e) < 0);
1428+
const addedItems = newSelection.filter(e => oldSelection.indexOf(e) < 0);
1429+
this.registerRemoteEntries(addedItems);
1430+
this.registerRemoteEntries(removedItems, false);
1431+
value = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e]).join(', ');
1432+
} else {
1433+
// If new selection is empty, clear all items
1434+
this.registerRemoteEntries(oldSelection, false);
1435+
}
1436+
} else {
1437+
value = this.concatDisplayText(newSelection);
1438+
}
1439+
return value;
1440+
}
1441+
14181442
/** if there is a valueKey - map the keys to data items, else - just return the keys */
14191443
private convertKeysToItems(keys: any[]) {
14201444
if (this.comboAPI.valueKey === null) {

0 commit comments

Comments
 (0)