Skip to content
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

fix(buttons): fix radio btns for reactive forms, add radio reactive demo #3384

Merged
merged 5 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions demo/src/app/components/+buttons/buttons-section.list.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DemoButtonsBasicComponent } from './demos/basic/basic';
import { DemoButtonsCheckboxComponent } from './demos/checkbox/checkbox';
import { DemoButtonsRadioComponent } from './demos/radio/radio';
import { DemoButtonsCheckboxReactiveFormsComponent } from './demos/checkbox-reactiveforms/checkbox-reactiveforms';
import { DemoButtonsRadioReactiveFormsComponent } from './demos/radio-reactiveforms/radio-reactiveforms';
import { DemoButtonsDisabledComponent } from './demos/disabled/disabled';

Expand Down Expand Up @@ -41,18 +42,30 @@ export const demoComponentContent: ContentSection[] = [
html: require('!!raw-loader?lang=markup!./demos/checkbox/checkbox.html'),
outlet: DemoButtonsCheckboxComponent
},
{
title: 'Checkbox with Reactive Forms',
anchor: 'checkbox-reactiveforms"',
description: `<p>Checkbox buttons with ReactiveForms</p>`,
component: require('!!raw-loader?lang=typescript!./demos/checkbox-reactiveforms/checkbox-reactiveforms.ts'),
html: require('!!raw-loader?lang=markup!./demos/checkbox-reactiveforms/checkbox-reactiveforms.html'),
outlet: DemoButtonsCheckboxReactiveFormsComponent
},
{
title: 'Radio & Uncheckable Radio',
anchor: 'radio-button',
description: `<p>Radio buttons with checked/unchecked states</p>`,
description: `<p>Radio buttons with checked/unchecked states. Group can be created in two ways: using
<code>btnRadioGroup</code> directive or using the same <code>ngModel</code> binding with several buttons (works only for
template driven forms). Check the demo below for more info.</p>`,
component: require('!!raw-loader?lang=typescript!./demos/radio/radio.ts'),
html: require('!!raw-loader?lang=markup!./demos/radio/radio.html'),
outlet: DemoButtonsRadioComponent
},
{
title: 'Radio with Reactive Forms',
anchor: 'radio-reactiveforms"',
description: `<p>Checkbox buttons with ReactiveForms</p>`,
anchor: 'radio-reactiveforms',
description: `<p>Radio buttons with ReactiveForms. Example below shows how to use radio buttons with reactive
forms. Please be aware that for reactive forms it's required to use <code>btnRadioGroup</code> directive along with
<code>btnRadio</code>'s</p>`,
component: require('!!raw-loader?lang=typescript!./demos/radio-reactiveforms/radio-reactiveforms.ts'),
html: require('!!raw-loader?lang=markup!./demos/radio-reactiveforms/radio-reactiveforms.html'),
outlet: DemoButtonsRadioReactiveFormsComponent
Expand Down Expand Up @@ -80,6 +93,11 @@ export const demoComponentContent: ContentSection[] = [
title: 'ButtonRadioDirective',
anchor: 'button-radio-directive',
outlet: NgApiDocComponent
},
{
title: 'ButtonRadioGroupDirective',
anchor: 'button-radio-group-directive',
outlet: NgApiDocComponent
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<pre class="card card-block card-header">{{myForm.value | json}}</pre>
<form [formGroup]="myForm">
<div class="btn-group">
<label class="btn btn-primary" [class.active]="myForm.value.left"
btnCheckbox formControlName="left">Left</label>

<label class="btn btn-primary" [class.active]="myForm.value.middle"
btnCheckbox formControlName="middle">Middle</label>

<label class="btn btn-primary" [class.active]="myForm.value.right"
btnCheckbox formControlName="right">Right</label>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
selector: 'demo-buttons-checkbox-reactiveforms',
templateUrl: './checkbox-reactiveforms.html'
})
export class DemoButtonsCheckboxReactiveFormsComponent implements OnInit {
myForm: FormGroup;

constructor(private formBuilder: FormBuilder) {}

ngOnInit() {
this.myForm = this.formBuilder.group({
left: false,
middle: true,
right: false
});
}
}
4 changes: 3 additions & 1 deletion demo/src/app/components/+buttons/demos/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { DemoButtonsBasicComponent } from './basic/basic';
import { DemoButtonsCheckboxComponent } from './checkbox/checkbox';
import { DemoButtonsRadioComponent } from './radio/radio';
import { DemoButtonsRadioReactiveFormsComponent } from './radio-reactiveforms/radio-reactiveforms';
import { DemoButtonsCheckboxReactiveFormsComponent } from './checkbox-reactiveforms/checkbox-reactiveforms';
import { DemoButtonsDisabledComponent } from './disabled/disabled';
import { DemoButtonsRadioReactiveFormsComponent } from './radio-reactiveforms/radio-reactiveforms';

export const DEMO_COMPONENTS = [
DemoButtonsBasicComponent,
DemoButtonsCheckboxComponent,
DemoButtonsRadioComponent,
DemoButtonsCheckboxReactiveFormsComponent,
DemoButtonsRadioReactiveFormsComponent,
DemoButtonsDisabledComponent
];
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<pre class="card card-block card-header">{{myForm.value | json}}</pre>
<form [formGroup]="myForm">
<div class="btn-group">
<label class="btn btn-primary" [class.active]="myForm.value.left"
btnCheckbox formControlName="left">Left</label>

<label class="btn btn-primary" [class.active]="myForm.value.middle"
btnCheckbox formControlName="middle">Middle</label>

<label class="btn btn-primary" [class.active]="myForm.value.right"
btnCheckbox formControlName="right">Right</label>
<pre class="card card-block card-header">{{ myForm.value | json }}</pre>
<form [formGroup]="myForm" class="form-inline">
<div class="form-group">
<div class="btn-group" btnRadioGroup formControlName="radio">
<label btnRadio="A" class="btn btn-primary">A</label>
<label btnRadio="B" class="btn btn-primary">B</label>
<label btnRadio="C" class="btn btn-primary">C</label>
</div>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
selector: 'demo-buttons-radio-reactiveforms',
Expand All @@ -12,9 +12,7 @@ export class DemoButtonsRadioReactiveFormsComponent implements OnInit {

ngOnInit() {
this.myForm = this.formBuilder.group({
left: false,
middle: true,
right: false
radio: 'C'
});
}
}
13 changes: 9 additions & 4 deletions demo/src/app/components/+buttons/demos/radio/radio.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
<label class="btn btn-primary" [(ngModel)]="radioModel" btnRadio="Middle">Middle</label>
<label class="btn btn-primary" [(ngModel)]="radioModel" btnRadio="Right">Right</label>
</div>
<div class="btn-group">
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="Left" uncheckable>Left</label>
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="Middle" uncheckable>Middle</label>
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="Right" uncheckable>Right</label>
<div class="btn-group" btnRadioGroup [(ngModel)]="radioModel">
<label class="btn btn-success" btnRadio="Left">Left</label>
<label class="btn btn-success" btnRadio="Middle">Middle</label>
<label class="btn btn-success" btnRadio="Right">Right</label>
</div>
<div class="btn-group" btnRadioGroup [(ngModel)]="radioModel">
<label class="btn btn-info" btnRadio="Left" uncheckable>Left</label>
<label class="btn btn-info" btnRadio="Middle" uncheckable>Middle</label>
<label class="btn btn-info" btnRadio="Right" uncheckable>Right</label>
</div>
23 changes: 17 additions & 6 deletions demo/src/ng-api-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ export const ngdoc: any = {
"properties": [],
"methods": []
},
"ButtonRadioGroupDirective": {
"fileName": "src/buttons/button-radio-group.directive.ts",
"className": "ButtonRadioGroupDirective",
"description": "<p>A group of radio buttons.\nA value of a selected button is bound to a variable specified via ngModel.</p>\n",
"selector": "[btnRadioGroup]",
"inputs": [],
"outputs": [],
"properties": [],
"methods": []
},
"ButtonRadioDirective": {
"fileName": "src/buttons/button-radio.directive.ts",
"className": "ButtonRadioDirective",
Expand Down Expand Up @@ -622,12 +632,6 @@ export const ngdoc: any = {
"type": "string",
"description": "<p>CSS class which will be applied to datepicker container,\nusually used to set color theme</p>\n"
},
{
"name": "locale",
"defaultValue": "en",
"type": "string",
"description": "<p>Allows to globally set default locale of datepicker,\nsee documentation on how to enable custom locales</p>\n"
},
{
"name": "maxDate",
"type": "Date",
Expand Down Expand Up @@ -760,6 +764,13 @@ export const ngdoc: any = {
}
]
},
"BsLocaleService": {
"fileName": "src/datepicker/bs-locale.service.ts",
"className": "BsLocaleService",
"description": "",
"methods": [],
"properties": []
},
"DatePickerInnerComponent": {
"fileName": "src/datepicker/datepicker-inner.component.ts",
"className": "DatePickerInnerComponent",
Expand Down
46 changes: 46 additions & 0 deletions src/buttons/button-radio-group.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// tslint:disable:no-use-before-declare
import { ChangeDetectorRef, Directive, ElementRef, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const RADIO_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ButtonRadioGroupDirective),
multi: true
};

/**
* A group of radio buttons.
* A value of a selected button is bound to a variable specified via ngModel.
*/
@Directive({
selector: '[btnRadioGroup]',
providers: [RADIO_CONTROL_VALUE_ACCESSOR]
})
export class ButtonRadioGroupDirective implements ControlValueAccessor {

This comment was marked as off-topic.

onChange: any = Function.prototype;
onTouched: any = Function.prototype;

get value(): any {
return this._value;
}
set value(value: any) {
this._value = value;
}

private _value: any;

constructor(private el: ElementRef, private cdr: ChangeDetectorRef) {}

writeValue(value: any): void {
this._value = value;
this.cdr.markForCheck();
}

registerOnChange(fn: any): void {
this.onChange = fn;
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
45 changes: 29 additions & 16 deletions src/buttons/button-radio.directive.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// tslint:disable:no-use-before-declare
import {
ChangeDetectorRef, Directive, ElementRef, forwardRef, HostBinding,
HostListener, Input, OnInit
ChangeDetectorRef, Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, OnInit,
Optional
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ButtonRadioGroupDirective } from './button-radio-group.directive';

export const RADIO_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
Expand All @@ -22,41 +23,53 @@ export const RADIO_CONTROL_VALUE_ACCESSOR: any = {
export class ButtonRadioDirective implements ControlValueAccessor, OnInit {
onChange: any = Function.prototype;
onTouched: any = Function.prototype;
private _value: any;

/** Radio button value, will be set to `ngModel` */
@Input() btnRadio: any;
/** If `true` — radio button can be unchecked */
@Input() uncheckable: boolean;
/** Current value of radio component or group */
@Input() value: any;
@Input() get value(): any {
return this.group ? this.group.value : this._value;

This comment was marked as off-topic.

This comment was marked as off-topic.

}

set value(value: any) {
if (this.group) {
this.group.value = value;

return;
}
this._value = value;
}

@HostBinding('class.active')
get isActive(): boolean {
return this.btnRadio === this.value;
}

constructor(private el: ElementRef, private cdr: ChangeDetectorRef) {
}
constructor(
private el: ElementRef,
private cdr: ChangeDetectorRef,
@Optional() private group: ButtonRadioGroupDirective
) {}

@HostListener('click')
onClick(): void {
if (this.el.nativeElement.attributes.disabled) {
if (this.el.nativeElement.attributes.disabled || !this.uncheckable && this.btnRadio === this.value) {
return;
}

if (this.uncheckable && this.btnRadio === this.value) {
this.value = undefined;
this.onTouched();
this.onChange(this.value);
this.value = this.uncheckable && this.btnRadio === this.value ? undefined : this.btnRadio;

return;
}
if (this.group) {
this.group.onTouched();

This comment was marked as off-topic.

this.group.onChange(this.value);

if (this.btnRadio !== this.value) {
this.value = this.btnRadio;
this.onTouched();
this.onChange(this.value);
return;
}
this.onTouched();
this.onChange(this.value);
}

ngOnInit(): void {
Expand Down
5 changes: 3 additions & 2 deletions src/buttons/buttons.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { NgModule, ModuleWithProviders } from '@angular/core';

import { ButtonCheckboxDirective } from './button-checkbox.directive';
import { ButtonRadioDirective } from './button-radio.directive';
import { ButtonRadioGroupDirective } from './button-radio-group.directive';

@NgModule({
declarations: [ButtonCheckboxDirective, ButtonRadioDirective],
exports: [ButtonCheckboxDirective, ButtonRadioDirective]
declarations: [ButtonCheckboxDirective, ButtonRadioDirective, ButtonRadioGroupDirective],
exports: [ButtonCheckboxDirective, ButtonRadioDirective, ButtonRadioGroupDirective]
})
export class ButtonsModule {
static forRoot(): ModuleWithProviders {
Expand Down
1 change: 1 addition & 0 deletions src/buttons/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ButtonCheckboxDirective } from './button-checkbox.directive';
export { ButtonRadioGroupDirective } from './button-radio-group.directive';
export { ButtonRadioDirective } from './button-radio.directive';
export { ButtonsModule } from './buttons.module';