Skip to content

Commit

Permalink
Merge 9aaf34d into c7dd2ac
Browse files Browse the repository at this point in the history
  • Loading branch information
anjmao committed Jan 23, 2019
2 parents c7dd2ac + 9aaf34d commit d6e58e9
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 121 deletions.
46 changes: 45 additions & 1 deletion demo/app/examples/groups.component.ts
Expand Up @@ -4,7 +4,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
selector: 'select-groups',
changeDetection: ChangeDetectionStrategy.Default,
template: `
<p>
<p>
ng-select supports grouping flat array of objects by providing <b>groupBy</b> input
</p>
Expand Down Expand Up @@ -122,6 +122,30 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
<p>
<small>Selected: {{selectedAccounts6 | json}}</small>
</p>
<hr />
<label>
Group by children array. Note that when grouping by already grouped items ng-optgroup-tmp is
required to display correct headers.
</label>
---html,true
<ng-select [items]="projects"
bindLabel="title"
bindValue="id"
groupBy="subprojects"
[multiple]="true"
[(ngModel)]="selectedProjects">
<ng-template ng-optgroup-tmp let-item="item">
{{item.title}}
</ng-template>
<ng-template ng-option-tmp let-item="item">
{{item.title}}
</ng-template>
</ng-select>
---
<p>
<small>Selected: {{selectedProjects | json}}</small>
</p>
`
})
export class SelectGroupsComponent {
Expand All @@ -143,6 +167,26 @@ export class SelectGroupsComponent {
{ name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }
];

selectedProjects = [];
projects = [
{
id: 'p1',
title: 'Project A',
subprojects: [
{ title: 'Subproject 1 of A', id: 's1p1' },
{ title: 'Subproject 2 of A', id: 's2p1' },
]
},
{
id: 'p2',
title: 'Project B',
subprojects: [
{ title: 'Subproject 1 of B', id: 's1p2' },
{ title: 'Subproject 2 of B', id: 's2p2' },
]
}
]

accounts2 = this.accounts.slice();
selectedAccount2 = ['Natasha'];
groupByFn = (item) => item.child.state;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -81,7 +81,7 @@
"typescript": "2.9.2",
"webpack": "4.16.5",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "3.1.5",
"webpack-dev-server": "^3.1.14",
"zone.js": "^0.8.26"
}
}
43 changes: 33 additions & 10 deletions src/ng-select/items-list.ts
Expand Up @@ -5,10 +5,9 @@ import { isDefined, isFunction, isObject } from './value-utils';
import { newId } from './id';
import { SelectionModel } from './selection-model';

type OptionGroups = Map<string, NgOption[]>;
type OptionGroups = Map<string | NgOption, NgOption[]>;

export class ItemsList {

private _groups: OptionGroups;

constructor(
Expand Down Expand Up @@ -305,11 +304,29 @@ export class ItemsList {
}

private _groupBy(items: NgOption[], prop: string | Function): OptionGroups {
const isFn = isFunction(this._ngSelect.groupBy);
const groups = new Map<string, NgOption[]>();
const groups = new Map<string | NgOption, NgOption[]>();
if (items.length === 0) {
return groups;
}

// Check if items are already grouped by given key.
if (Array.isArray(items[0].value[<string>prop])) {
for (const item of items) {
const children = (item.value[<string>prop] || []).map((x, index) => this.mapItem(x, index));
groups.set(item, children);
}
return groups;
}

const isFnKey = isFunction(this._ngSelect.groupBy);
const keyFn = (item: NgOption) => {
let key = isFnKey ? (<Function>prop)(item.value) : item.value[<string>prop];
return isDefined(key) ? key : undefined;
}

// Group items by key.
for (const item of items) {
let key = isFn ? (<Function>prop)(item.value) : item.value[<string>prop];
key = isDefined(key) ? key : undefined;
let key = keyFn(item);
const group = groups.get(key);
if (group) {
group.push(item);
Expand All @@ -321,7 +338,7 @@ export class ItemsList {
}

private _flatten(groups: OptionGroups) {
const isFn = isFunction(this._ngSelect.groupBy);
const isGroupByFn = isFunction(this._ngSelect.groupBy);
const items = [];
const withoutGroup = groups.get(undefined) || [];
items.push(...withoutGroup);
Expand All @@ -330,16 +347,22 @@ export class ItemsList {
if (!isDefined(key)) {
continue;
}
const isObjectKey = isObject(key);
const parent: NgOption = {
label: key,
label: isObjectKey ? '' : <string>key,
children: undefined,
parent: null,
index: i++,
disabled: !this._ngSelect.selectableGroup,
htmlId: newId(),
};
const groupKey = isFn ? this._ngSelect.bindLabel : <string>this._ngSelect.groupBy;
const groupValue = this._ngSelect.groupValue || (() => ({[groupKey]: key}));
const groupKey = isGroupByFn ? this._ngSelect.bindLabel : <string>this._ngSelect.groupBy;
const groupValue = this._ngSelect.groupValue || (() => {
if (isObjectKey) {
return (<NgOption>key).value;
}
return { [groupKey]: key };
});
const children = groups.get(key).map(x => {
x.parent = parent;
x.children = undefined;
Expand Down
41 changes: 39 additions & 2 deletions src/ng-select/ng-select.component.spec.ts
Expand Up @@ -2976,7 +2976,7 @@ describe('NgSelectComponent', function () {
});

describe('Grouping', () => {
it('should group by group key', fakeAsync(() => {
it('should group flat items list by group key', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectGroupingTestCmp,
`<ng-select [items]="accounts"
Expand Down Expand Up @@ -3008,6 +3008,36 @@ describe('NgSelectComponent', function () {
expect(items[11].parent).toBe(items[10]);
}));

it('should group items with children array by group key', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectGroupingTestCmp,
`<ng-select [items]="groupedAccounts"
groupBy="accounts"
[(ngModel)]="selectedAccount">
</ng-select>`);

tickAndDetectChanges(fixture);

const items = fixture.componentInstance.select.itemsList.items;

expect(items.length).toBe(14);
expect(items[0].children).toBeDefined();
expect(items[0].index).toBe(0);
expect(items[0].disabled).toBeTruthy();
expect(items[0].value).toEqual(jasmine.objectContaining({ country: 'United States' }));

expect(items[1].children).toBeUndefined();
expect(items[1].parent).toBe(items[0]);

expect(items[2].children).toBeUndefined();
expect(items[2].parent).toBe(items[0]);

expect(items[3].value).toEqual(jasmine.objectContaining({ country: 'Argentina' }));

expect(items[10].value).toEqual(jasmine.objectContaining({ country: 'Colombia' }));
expect(items[11].parent).toBe(items[10]);
}));

it('should not group items without key', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectGroupingTestCmp,
Expand Down Expand Up @@ -3316,7 +3346,6 @@ class NgSelectGroupingTestCmp {
{ name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { name: 'c2' } }
];

// TODO: support this case
groupedAccounts = [
{
country: 'United States',
Expand All @@ -3339,6 +3368,14 @@ class NgSelectGroupingTestCmp {
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30 },
{ name: 'Natasha', email: 'natasha@email.com', age: 54 },
]
},
{
country: 'Colombia',
accounts: [
{ name: 'Nicole', email: 'nicole@email.com', age: 43 },
{ name: 'Michael', email: 'michael@email.com', age: 15 },
{ name: 'Nicolás', email: 'nicole@email.com', age: 43 }
]
}
]
}
3 changes: 2 additions & 1 deletion src/ng-select/ng-select.component.ts
Expand Up @@ -56,6 +56,7 @@ export type AutoCorrect = 'off' | 'on';
export type AutoCapitalize = 'off' | 'on';
export type AddTagFn = ((term: string) => any | Promise<any>);
export type CompareWithFn = (a: any, b: any) => boolean;
export type GroupValueFn = (key: string | object, children: any[]) => string | object;

@Component({
selector: 'ng-select',
Expand Down Expand Up @@ -97,7 +98,7 @@ export class NgSelectComponent implements OnDestroy, OnChanges, AfterViewInit, C
@Input() openOnEnter: boolean;
@Input() maxSelectedItems: number;
@Input() groupBy: string | Function;
@Input() groupValue: Function;
@Input() groupValue: GroupValueFn;
@Input() bufferAmount = 4;
@Input() virtualScroll: boolean;
@Input() selectableGroup = false;
Expand Down

0 comments on commit d6e58e9

Please sign in to comment.