From 64c87cf2d766bfde71c2ef230cbdeef8c3b2fd19 Mon Sep 17 00:00:00 2001 From: Ilya Surmay Date: Wed, 3 Jan 2018 18:50:44 +0200 Subject: [PATCH 1/2] fix(buttons): fix radio btns for reactive forms, add radio reactive demo --- .../+buttons/buttons-section.list.ts | 24 ++++++++-- .../checkbox-reactiveforms.html | 13 ++++++ .../checkbox-reactiveforms.ts | 20 ++++++++ .../app/components/+buttons/demos/index.ts | 4 +- .../radio-reactiveforms.html | 19 ++++---- .../radio-reactiveforms.ts | 6 +-- .../+buttons/demos/radio/radio.html | 13 ++++-- demo/src/ng-api-doc.ts | 23 +++++++--- src/buttons/button-radio-group.directive.ts | 46 +++++++++++++++++++ src/buttons/button-radio.directive.ts | 45 +++++++++++------- src/buttons/buttons.module.ts | 5 +- src/buttons/index.ts | 1 + 12 files changed, 172 insertions(+), 47 deletions(-) create mode 100644 demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.html create mode 100644 demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.ts create mode 100644 src/buttons/button-radio-group.directive.ts diff --git a/demo/src/app/components/+buttons/buttons-section.list.ts b/demo/src/app/components/+buttons/buttons-section.list.ts index edb7629382..f397d03cdc 100644 --- a/demo/src/app/components/+buttons/buttons-section.list.ts +++ b/demo/src/app/components/+buttons/buttons-section.list.ts @@ -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'; @@ -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: `

Checkbox buttons with ReactiveForms

`, + 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: `

Radio buttons with checked/unchecked states

`, + description: `

Radio buttons with checked/unchecked states. Group can be created in two ways: using +btnRadioGroup directive or using the same ngModel binding with several buttons (works only for +template driven forms). Check the demo below for more info.

`, 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: `

Checkbox buttons with ReactiveForms

`, + anchor: 'radio-reactiveforms', + description: `

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 btnRadioGroup directive along with + btnRadio's

`, 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 @@ -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 } ] } diff --git a/demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.html b/demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.html new file mode 100644 index 0000000000..fe9f44f43d --- /dev/null +++ b/demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.html @@ -0,0 +1,13 @@ +
{{myForm.value | json}}
+
+
+ + + + + +
+
diff --git a/demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.ts b/demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.ts new file mode 100644 index 0000000000..62285d24c5 --- /dev/null +++ b/demo/src/app/components/+buttons/demos/checkbox-reactiveforms/checkbox-reactiveforms.ts @@ -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 + }); + } +} diff --git a/demo/src/app/components/+buttons/demos/index.ts b/demo/src/app/components/+buttons/demos/index.ts index 0140d6908a..f1c79a2c9d 100644 --- a/demo/src/app/components/+buttons/demos/index.ts +++ b/demo/src/app/components/+buttons/demos/index.ts @@ -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 ]; diff --git a/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.html b/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.html index fe9f44f43d..386afbc50c 100644 --- a/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.html +++ b/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.html @@ -1,13 +1,10 @@ -
{{myForm.value | json}}
-
-
- - - - - +
{{ myForm.value | json }}
+ +
+
+ + + +
diff --git a/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.ts b/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.ts index 941958b57b..570470ee4b 100644 --- a/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.ts +++ b/demo/src/app/components/+buttons/demos/radio-reactiveforms/radio-reactiveforms.ts @@ -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', @@ -12,9 +12,7 @@ export class DemoButtonsRadioReactiveFormsComponent implements OnInit { ngOnInit() { this.myForm = this.formBuilder.group({ - left: false, - middle: true, - right: false + radio: 'C' }); } } diff --git a/demo/src/app/components/+buttons/demos/radio/radio.html b/demo/src/app/components/+buttons/demos/radio/radio.html index 8e0af3013c..7c57c29fe7 100644 --- a/demo/src/app/components/+buttons/demos/radio/radio.html +++ b/demo/src/app/components/+buttons/demos/radio/radio.html @@ -4,8 +4,13 @@
-
- - - +
+ + + +
+
+ + +
diff --git a/demo/src/ng-api-doc.ts b/demo/src/ng-api-doc.ts index d10014f7f9..a328c41683 100644 --- a/demo/src/ng-api-doc.ts +++ b/demo/src/ng-api-doc.ts @@ -186,6 +186,16 @@ export const ngdoc: any = { "properties": [], "methods": [] }, + "ButtonRadioGroupDirective": { + "fileName": "src/buttons/button-radio-group.directive.ts", + "className": "ButtonRadioGroupDirective", + "description": "

A group of radio buttons.\nA value of a selected button is bound to a variable specified via ngModel.

\n", + "selector": "[btnRadioGroup]", + "inputs": [], + "outputs": [], + "properties": [], + "methods": [] + }, "ButtonRadioDirective": { "fileName": "src/buttons/button-radio.directive.ts", "className": "ButtonRadioDirective", @@ -622,12 +632,6 @@ export const ngdoc: any = { "type": "string", "description": "

CSS class which will be applied to datepicker container,\nusually used to set color theme

\n" }, - { - "name": "locale", - "defaultValue": "en", - "type": "string", - "description": "

Allows to globally set default locale of datepicker,\nsee documentation on how to enable custom locales

\n" - }, { "name": "maxDate", "type": "Date", @@ -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", diff --git a/src/buttons/button-radio-group.directive.ts b/src/buttons/button-radio-group.directive.ts new file mode 100644 index 0000000000..e980e8b52a --- /dev/null +++ b/src/buttons/button-radio-group.directive.ts @@ -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 { + 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; + } +} diff --git a/src/buttons/button-radio.directive.ts b/src/buttons/button-radio.directive.ts index 45a86c7415..fca8b336b1 100644 --- a/src/buttons/button-radio.directive.ts +++ b/src/buttons/button-radio.directive.ts @@ -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, @@ -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; + } + + 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.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 { diff --git a/src/buttons/buttons.module.ts b/src/buttons/buttons.module.ts index a99b14e4c2..9b2a17d0d3 100644 --- a/src/buttons/buttons.module.ts +++ b/src/buttons/buttons.module.ts @@ -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 { diff --git a/src/buttons/index.ts b/src/buttons/index.ts index 44cabb64e6..3a59cf2eb7 100644 --- a/src/buttons/index.ts +++ b/src/buttons/index.ts @@ -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'; From 1ba14cb965651f17ceb2fa22b09b4a6ef4ab8eaf Mon Sep 17 00:00:00 2001 From: Ilya Surmay Date: Fri, 12 Jan 2018 15:18:46 +0200 Subject: [PATCH 2/2] feat(buttons): add setDisabledState --- demo/src/ng-api-doc.ts | 5 +++ src/buttons/button-radio.directive.ts | 45 ++++++++++++++++++++------- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/demo/src/ng-api-doc.ts b/demo/src/ng-api-doc.ts index a328c41683..a3e9fb19e4 100644 --- a/demo/src/ng-api-doc.ts +++ b/demo/src/ng-api-doc.ts @@ -207,6 +207,11 @@ export const ngdoc: any = { "type": "any", "description": "

Radio button value, will be set to ngModel

\n" }, + { + "name": "disabled", + "type": "boolean", + "description": "

If true — radio button is disabled

\n" + }, { "name": "uncheckable", "type": "boolean", diff --git a/src/buttons/button-radio.directive.ts b/src/buttons/button-radio.directive.ts index fca8b336b1..e3370ff848 100644 --- a/src/buttons/button-radio.directive.ts +++ b/src/buttons/button-radio.directive.ts @@ -1,7 +1,7 @@ // tslint:disable:no-use-before-declare import { ChangeDetectorRef, Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, OnInit, - Optional + Optional, Renderer2 } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ButtonRadioGroupDirective } from './button-radio-group.directive'; @@ -24,6 +24,7 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit { onChange: any = Function.prototype; onTouched: any = Function.prototype; private _value: any; + private _disabled: boolean; /** Radio button value, will be set to `ngModel` */ @Input() btnRadio: any; @@ -42,6 +43,15 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit { } this._value = value; } + /** If `true` — radio button is disabled */ + @Input() get disabled(): boolean { + return this._disabled; + } + + set disabled(disabled: boolean) { + this._disabled = disabled; + this.setDisabledState(disabled); + } @HostBinding('class.active') get isActive(): boolean { @@ -51,7 +61,8 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit { constructor( private el: ElementRef, private cdr: ChangeDetectorRef, - @Optional() private group: ButtonRadioGroupDirective + @Optional() private group: ButtonRadioGroupDirective, + private renderer: Renderer2 ) {} @HostListener('click') @@ -61,15 +72,7 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit { } this.value = this.uncheckable && this.btnRadio === this.value ? undefined : this.btnRadio; - - if (this.group) { - this.group.onTouched(); - this.group.onChange(this.value); - - return; - } - this.onTouched(); - this.onChange(this.value); + this._onChange(this.value); } ngOnInit(): void { @@ -80,6 +83,17 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit { this.onTouched(); } + _onChange(value: any): void { + if (this.group) { + this.group.onTouched(); + this.group.onChange(value); + + return; + } + this.onTouched(); + this.onChange(value); + } + // ControlValueAccessor // model -> view writeValue(value: any): void { @@ -94,4 +108,13 @@ export class ButtonRadioDirective implements ControlValueAccessor, OnInit { registerOnTouched(fn: any): void { this.onTouched = fn; } + + setDisabledState(disabled: boolean): void { + if (disabled) { + this.renderer.setAttribute(this.el.nativeElement, 'disabled', 'disabled'); + + return; + } + this.renderer.removeAttribute(this.el.nativeElement, 'disabled'); + } }