Skip to content

Commit

Permalink
feat: allow group by expression (#385)
Browse files Browse the repository at this point in the history
closes #375
  • Loading branch information
anjmao committed Mar 27, 2018
1 parent bc6efe3 commit 0dd11c4
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ map: {
| [clearable] | `boolean` | `true` | no | Allow to clear selected value. Default `true`|
| clearAllText | `string` | `Clear all` | no | Set custom text for clear all icon title |
| dropdownPosition | `bottom`,`top`,`auto` | `bottom` | no | Set the dropdown position on open |
| [groupBy] | `string` | null | no | Allow to group items by key |
| [groupBy] | `string` or `Function` | null | no | Allow to group items by key or function expression |
| [selectableGroup] | `boolean` | false | no | Allow to select group when groupBy is used |
| [items] | `Array<NgOption>` | `[]` | yes | Items array |
| loading | `boolean` | `-` | no | You can set the loading state from the outside (e.g. async items loading) |
Expand Down
48 changes: 33 additions & 15 deletions demo/app/examples/groups.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
ng-select supports grouping flat array of objects by providing <b>groupBy</b> input
</p>
<label>Default</label>
<label>Default group by key</label>
---html,true
<ng-select [items]="accounts"
bindLabel="name"
Expand All @@ -22,38 +22,56 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
<small>Selected: {{selectedAccount | json}}</small>
</p>
<label>Group by function expression</label>
---html,true
<ng-select [items]="accounts2"
bindLabel="name"
bindValue="name"
[groupBy]="groupByFn"
[multiple]="true"
[(ngModel)]="selectedAccount2">
</ng-select>
---
<p>
<small>Selected: {{selectedAccount2 | json}}</small>
</p>
<hr />
<label>With selectable groups</label>
---html,true
<ng-select [items]="accounts2"
<ng-select [items]="accounts3"
bindLabel="name"
groupBy="country"
[selectableGroup]="true"
[(ngModel)]="selectedAccount2">
[(ngModel)]="selectedAccount3">
</ng-select>
---
<p>
<small>Selected: {{selectedAccount2 | json}}</small>
<small>Selected: {{selectedAccount3 | json}}</small>
</p>
`
})
export class SelectGroupsComponent {
selectedAccount = ['Samantha'];
accounts = [
{ name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' },
{ name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States' },
{ name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina' },
{ name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina' },
{ name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador' },
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador' },
{ name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador' },
{ name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia' },
{ name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia' },
{ name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia' }
{ name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } },
{ name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } },
{ name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } },
{ name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } },
{ name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } },
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } },
{ name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } },
{ name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } },
{ name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } },
{ name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }
];

accounts2 = this.accounts.slice();
selectedAccount2 = this.accounts2[1];
selectedAccount2 = ['Natasha'];
groupByFn = (item) => item.child.state;

accounts3 = this.accounts.slice();
selectedAccount3 = this.accounts3[1];

ngOnInit() {

Expand Down
12 changes: 12 additions & 0 deletions integration_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
BASE_DIR=./integration/node_modules
yarn clean
yarn build
mkdir -p $BASE_DIR
mkdir -p ${BASE_DIR}/@ng-select
mkdir -p ${BASE_DIR}/@ng-select/ng-select
cp -R ./dist ${BASE_DIR}/@ng-select/ng-select
cd ./integration
yarn install
yarn build
yarn e2e
11 changes: 4 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@
"npm": ">= 3.0.0"
},
"scripts": {
"clean": "rimraf out-tsc dist/*",
"prebuild": "npm run clean",
"build": "ng-packagr -p ./src/package.json && npm run build:themes",
"clean": "rimraf out-tsc dist/* integration/node_modules/@ng-select",
"prebuild": "yarn clean",
"build": "ng-packagr -p ./src/package.json && yarn build:themes",
"build:demo": "rimraf dist && webpack --config ./scripts/webpack.dev.js --progress --profile --bail",
"build:themes": "node-sass --output-style compressed src/themes/ -o dist/themes",
"prestart": "yarn install",
"start": "webpack-dev-server --config ./scripts/webpack.dev.js --inline --color --progress --port 8080",
"test:watch": "karma start ./scripts/karma.conf.js --no-single-run --auto-watch",
"test": "karma start ./scripts/karma.conf.js --single-run",
"coveralls": "cat ./coverage/lcov/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"preintegration": "npm run clean && npm run build && cd integration && yarn",
"integration": "npm run integration:aot && npm run integration:jit",
"integration:jit": "cd integration && npm run e2e",
"integration:aot": "cd integration && npm run build",
"integration": "./integration_test.sh",
"lint": "tslint --type-check --project ./src/tsconfig.json",
"release": "sh ./release.sh"
},
Expand Down
5 changes: 3 additions & 2 deletions src/ng-select/items-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,10 @@ export class ItemsList {
return this._selected[this._selected.length - 1];
}

private _groupBy(items: NgOption[], prop: string): { [index: string]: NgOption[] } {
private _groupBy(items: NgOption[], prop: string | Function): { [index: string]: NgOption[] } {
const isPropFn = prop instanceof Function;
const groups = items.reduce((grouped, item) => {
const key = item.value[prop];
const key = isPropFn ? (<Function>prop).apply(this, [item.value]) : item.value[<string>prop];
grouped[key] = grouped[key] || [];
grouped[key].push(item);
return grouped;
Expand Down
38 changes: 28 additions & 10 deletions src/ng-select/ng-select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2160,6 +2160,23 @@ describe('NgSelectComponent', function () {
expect(items[11].parent).toBe(items[10]);
}));

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

tickAndDetectChanges(fixture);

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

expect(items.length).toBe(12);
expect(items[0].hasChildren).toBe(true);
expect(items[6].hasChildren).toBe(true);
}));

it('should not mark optgroup item as marked', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectGroupingTestCmp,
Expand Down Expand Up @@ -2307,17 +2324,18 @@ class NgSelectGroupingTestCmp {
@ViewChild(NgSelectComponent) select: NgSelectComponent;
selectedAccountName = 'Adam';
selectedAccount = null;
groupByFn = (item) => item.child.name;
accounts = [
{ name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States' },
{ name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States' },
{ name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina' },
{ name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina' },
{ name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador' },
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador' },
{ name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador' },
{ name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia' },
{ name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia' },
{ name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia' }
{ name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { name: 'c1' } },
{ name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { name: 'c1' } },
{ name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { name: 'c1' } },
{ name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { name: 'c1' } },
{ name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { name: 'c1' } },
{ name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { name: 'c2' } },
{ name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { name: 'c2' } },
{ name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { name: 'c2' } },
{ name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { name: 'c2' } },
{ name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { name: 'c2' } }
];

// TODO: support this case
Expand Down
3 changes: 1 addition & 2 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
"static-before-instance"
],
"no-arg": true,
"no-bitwise": true,
Expand Down

0 comments on commit 0dd11c4

Please sign in to comment.