Skip to content

Commit

Permalink
feat(button group): add valueChange output (#2722)
Browse files Browse the repository at this point in the history
  • Loading branch information
unumtresocto committed Jun 15, 2021
1 parent 74096d0 commit 39c610c
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 9 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'ButtonGroupStatusesComponent',
name: 'Button Group Statuses',
},
{
path: 'button-group-value-change.component',
link: '/button-group/button-group-value-change.component',
component: 'ButtonGroupValueChangeComponent',
name: 'Button Group Value Change',
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import {
ChangeDetectorRef,
Component,
ContentChildren,
EventEmitter,
HostBinding,
Input,
OnChanges,
Output,
QueryList,
SimpleChanges,
} from '@angular/core';
import { from, merge, Observable, Subject } from 'rxjs';
import { filter, switchMap, takeUntil } from 'rxjs/operators';
import { debounceTime, filter, startWith, switchMap, takeUntil } from 'rxjs/operators';

import { NbStatusService } from '../../services/status.service';
import { convertToBoolProperty, NbBooleanInput } from '../helpers';
Expand Down Expand Up @@ -55,6 +57,11 @@ import { NbButtonToggleAppearance, NbButtonToggleChange, NbButtonToggleDirective
* toggles pressed, you need to add `multiple` attributes to the `<nb-button-toggle>`.
* @stacked-example(Button Group Multiple, button-group/button-group-multiple.component)
*
* To know which buttons are currently pressed listen to `(valueChange)` on the `nb-button-group`. Event
* contains an array of values of currently pressed button toggles. You can assign a value to the
* `[nbButtonToggle]` via the `value` input.
* @stacked-example(Button Group Value Change, button-group/button-group-value-change.component)
*
* To disable a group of buttons, add a `disabled` attribute to the `<nb-button-group>`.
* @stacked-example(Button Group Disabled, button-group/button-group-disabled.component)
*
Expand Down Expand Up @@ -100,6 +107,8 @@ import { NbButtonToggleAppearance, NbButtonToggleChange, NbButtonToggleDirective
})
export class NbButtonGroupComponent implements OnChanges, AfterContentInit {

protected lastEmittedValue: any[] = [];

protected readonly destroy$: Subject<void> = new Subject<void>();
protected readonly buttonsChange$ = new Subject<NbButton[]>();

Expand Down Expand Up @@ -194,6 +203,12 @@ export class NbButtonGroupComponent implements OnChanges, AfterContentInit {
}
static ngAcceptInputType_ghost: NbBooleanInput;

/**
* Emits when `nbButtonToggle` pressed state change. `$event` contains an array of the currently pressed button
* toggles.
*/
@Output() valueChange = new EventEmitter<any[]>()

@HostBinding('attr.role') role = 'group';

@HostBinding('class')
Expand Down Expand Up @@ -260,6 +275,16 @@ export class NbButtonGroupComponent implements OnChanges, AfterContentInit {
.filter((button: NbButtonToggleDirective) => button !== source)
.forEach((button: NbButtonToggleDirective) => button._updatePressed(false));
});

merge(...buttonsPressedChange$)
.pipe(
// Use startWith to emit if some buttons are initially pressed.
startWith(''),
// Use debounce to emit change once when pressed state change in multiple button toggles.
debounceTime(0),
takeUntil(merge(this.buttonsChange$, this.destroy$)),
)
.subscribe(() => this.emitCurrentValue(toggleButtons));
}

protected syncButtonsProperties(buttons: NbButton[]): void {
Expand All @@ -273,4 +298,18 @@ export class NbButtonGroupComponent implements OnChanges, AfterContentInit {
});
});
}

protected emitCurrentValue(toggleButtons: NbButtonToggleDirective[]): void {
const pressedToggleValues = toggleButtons
.filter((b: NbButtonToggleDirective) => b.pressed && typeof b.value !== 'undefined')
.map((b: NbButtonToggleDirective) => b.value);

// Prevent multiple emissions of empty value.
if (pressedToggleValues.length === 0 && this.lastEmittedValue.length === 0) {
return;
}

this.valueChange.emit(pressedToggleValues);
this.lastEmittedValue = pressedToggleValues;
}
}
40 changes: 32 additions & 8 deletions src/framework/theme/components/button-group/button-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing';
import { ComponentFixture, TestBed, fakeAsync, flush, tick } from '@angular/core/testing';
import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';

import {
Expand All @@ -26,13 +26,15 @@ import {
[status]="status"
[shape]="shape"
[appearance]="appearance"
[disabled]="groupDisabled">
<button nbButtonToggle>A</button>
<button nbButtonToggle>B</button>
<button nbButtonToggle>C</button>
<button nbButtonToggle>D</button>
<button nbButtonToggle>E</button>
<button nbButtonToggle *ngIf="showLastButton">F</button>
[disabled]="groupDisabled"
[multiple]="multiple"
(valueChange)="onValueChange($event)">
<button nbButtonToggle value="A">A</button>
<button nbButtonToggle value="B">B</button>
<button nbButtonToggle value="C">C</button>
<button nbButtonToggle value="D">D</button>
<button nbButtonToggle value="E">E</button>
<button nbButtonToggle value="F" *ngIf="showLastButton">F</button>
</nb-button-group>
`,
})
Expand All @@ -42,11 +44,14 @@ export class NbButtonGroupTestComponent {
status: NbComponentStatus = 'danger';
appearance: NbButtonToggleAppearance = 'outline';
groupDisabled: boolean = false;
multiple: boolean = false;

showLastButton = false;

@ViewChild(NbButtonGroupComponent) buttonGroup: NbButtonGroupComponent;
@ViewChildren(NbButtonToggleDirective) toggleButtons: QueryList<NbButtonToggleDirective>;

onValueChange() {}
}

describe('Component: NbButtonGroup', () => {
Expand Down Expand Up @@ -134,4 +139,23 @@ describe('Component: NbButtonGroup', () => {
fixture.detectChanges();
expect(toggleButtons.last.disabled).toEqual(true);
}));

it('should correctly emit active buttons', fakeAsync(() => {
const clickButton = (el: HTMLElement, index = 0) => {
el.querySelectorAll<HTMLButtonElement>('[nbButtonToggle]')[index].click();
}
const nativeElement = fixture.nativeElement;
spyOn(testComponent, 'onValueChange').and.callThrough();
testComponent.showLastButton = true;
testComponent.multiple = true;
fixture.detectChanges();

clickButton(nativeElement, 0);
tick();
expect(testComponent.onValueChange).toHaveBeenCalledWith(['A']);

clickButton(nativeElement, 5);
tick();
expect(testComponent.onValueChange).toHaveBeenCalledWith(['A', 'F']);
}))
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export class NbButtonToggleDirective extends NbButton {

@Input() appearance: NbButtonToggleAppearance = 'filled';

/**
* A value associated with the button.
*/
@Input() value: any;

/**
* Controls button pressed state
**/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ButtonAndButtonToggleGroupsComponent } from './button-and-button-toggle
import { ButtonGroupInteractiveComponent } from './button-group-interactive.component';
import { ButtonGroupDisabledComponent } from './button-group-disabled.component';
import { ButtonGroupStatusesComponent } from './button-group-statuses.component';
import { ButtonGroupValueChangeComponent } from './button-group-value-change.component';

const routes: Route[] = [
{
Expand Down Expand Up @@ -53,6 +54,10 @@ const routes: Route[] = [
path: 'button-group-statuses.component',
component: ButtonGroupStatusesComponent,
},
{
path: 'button-group-value-change.component',
component: ButtonGroupValueChangeComponent,
},
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<nb-card>
<nb-card-body>
<p>
Group with single select value: {{ singleSelectGroupValue | json }}
</p>

<nb-button-group (valueChange)="updateSingleSelectGroupValue($event)">
<button nbButtonToggle value="One">One</button>
<button nbButtonToggle value="Two">Two</button>
<button nbButtonToggle value="Three">Three</button>
</nb-button-group>
</nb-card-body>
</nb-card>

<nb-card>
<nb-card-body>
<p>
Group with single select value: {{ multiSelectGroupValue | json }}
</p>

<nb-button-group multiple (valueChange)="updateMultiSelectGroupValue($event)">
<button nbButtonToggle value="One">One</button>
<button nbButtonToggle value="Two">Two</button>
<button nbButtonToggle value="Three">Three</button>
</nb-button-group>
</nb-card-body>
</nb-card>

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';

@Component({
templateUrl: './button-group-value-change.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ButtonGroupValueChangeComponent {
singleSelectGroupValue = [];
multiSelectGroupValue = [];

constructor(private cd: ChangeDetectorRef) {
}

updateSingleSelectGroupValue(value): void {
this.singleSelectGroupValue = value;
this.cd.markForCheck();
}

updateMultiSelectGroupValue(value): void {
this.multiSelectGroupValue = value;
this.cd.markForCheck();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ButtonAndButtonToggleGroupsComponent } from './button-and-button-toggle
import { ButtonGroupInteractiveComponent } from './button-group-interactive.component';
import { ButtonGroupDisabledComponent } from './button-group-disabled.component';
import { ButtonGroupStatusesComponent } from './button-group-statuses.component';
import { ButtonGroupValueChangeComponent } from './button-group-value-change.component';

@NgModule({
declarations: [
Expand All @@ -37,6 +38,7 @@ import { ButtonGroupStatusesComponent } from './button-group-statuses.component'
ButtonGroupInteractiveComponent,
ButtonGroupDisabledComponent,
ButtonGroupStatusesComponent,
ButtonGroupValueChangeComponent,
],
imports: [
CommonModule,
Expand Down

0 comments on commit 39c610c

Please sign in to comment.