feat(forms): add support for disabled controls (#10994)

This commit is contained in:
Kara
2016-08-24 16:58:43 -07:00
committed by Victor Berchet
parent 4f8f8cfc66
commit 2b313e4979
24 changed files with 1335 additions and 343 deletions

View File

@ -13,7 +13,7 @@ import {composeAsyncValidators, composeValidators} from './directives/shared';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {EventEmitter, Observable} from './facade/async';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {isBlank, isPresent, isPromise, normalizeBool} from './facade/lang';
import {isBlank, isPresent, isPromise, isStringMap, normalizeBool} from './facade/lang';
@ -33,6 +33,12 @@ export const INVALID = 'INVALID';
*/
export const PENDING = 'PENDING';
/**
* Indicates that a FormControl is disabled, i.e. that the control is exempt from ancestor
* calculations of validity or value.
*/
export const DISABLED = 'DISABLED';
export function isControl(control: Object): boolean {
return control instanceof AbstractControl;
}
@ -115,6 +121,10 @@ export abstract class AbstractControl {
get pending(): boolean { return this._status == PENDING; }
get disabled(): boolean { return this._status === DISABLED; }
get enabled(): boolean { return this._status !== DISABLED; }
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]): void {
this.asyncValidator = coerceToAsyncValidator(newValidator);
}
@ -175,6 +185,39 @@ export abstract class AbstractControl {
}
}
disable({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
emitEvent = isPresent(emitEvent) ? emitEvent : true;
this._status = DISABLED;
this._forEachChild((control: AbstractControl) => { control.disable({onlySelf: true}); });
this._updateValue();
if (emitEvent) {
this._valueChanges.emit(this._value);
this._statusChanges.emit(this._status);
}
this._updateAncestors(onlySelf);
this._onDisabledChange(true);
}
enable({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._status = VALID;
this._forEachChild((control: AbstractControl) => { control.enable({onlySelf: true}); });
this.updateValueAndValidity({onlySelf: true, emitEvent: emitEvent});
this._updateAncestors(onlySelf);
this._onDisabledChange(false);
}
private _updateAncestors(onlySelf: boolean) {
if (isPresent(this._parent) && !onlySelf) {
this._parent.updateValueAndValidity();
this._parent._updatePristine();
this._parent._updateTouched();
}
}
setParent(parent: FormGroup|FormArray): void { this._parent = parent; }
abstract setValue(value: any, options?: Object): void;
@ -189,14 +232,18 @@ export abstract class AbstractControl {
emitEvent = isPresent(emitEvent) ? emitEvent : true;
this._updateValue();
this._errors = this._runValidator();
const originalStatus = this._status;
this._status = this._calculateStatus();
if (this._status == VALID || this._status == PENDING) {
this._runAsyncValidator(emitEvent);
}
if (this._disabledChanged(originalStatus)) {
this._updateValue();
}
if (emitEvent) {
this._valueChanges.emit(this._value);
this._statusChanges.emit(this._status);
@ -227,6 +274,11 @@ export abstract class AbstractControl {
}
}
private _disabledChanged(originalStatus: string): boolean {
return this._status !== originalStatus &&
(this._status === DISABLED || originalStatus === DISABLED);
}
/**
* Sets errors on a form control.
*
@ -306,6 +358,7 @@ export abstract class AbstractControl {
if (isPresent(this._errors)) return INVALID;
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
if (this._allControlsDisabled()) return DISABLED;
return VALID;
}
@ -318,6 +371,9 @@ export abstract class AbstractControl {
/** @internal */
abstract _anyControls(condition: Function): boolean;
/** @internal */
abstract _allControlsDisabled(): boolean;
/** @internal */
_anyControlsHaveStatus(status: string): boolean {
return this._anyControls((control: AbstractControl) => control.status == status);
@ -350,6 +406,15 @@ export abstract class AbstractControl {
this._parent._updateTouched({onlySelf: onlySelf});
}
}
/** @internal */
_onDisabledChange(isDisabled: boolean): void {}
/** @internal */
_isBoxedValue(formState: any): boolean {
return isStringMap(formState) && Object.keys(formState).length === 2 && 'value' in formState &&
'disabled' in formState;
}
}
/**
@ -375,10 +440,10 @@ export class FormControl extends AbstractControl {
_onChange: Function[] = [];
constructor(
value: any = null, validator: ValidatorFn|ValidatorFn[] = null,
formState: any = null, validator: ValidatorFn|ValidatorFn[] = null,
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null) {
super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator));
this._value = value;
this._applyFormState(formState);
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
this._initObservables();
}
@ -427,10 +492,11 @@ export class FormControl extends AbstractControl {
this.setValue(value, options);
}
reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
this.markAsPristine({onlySelf: onlySelf});
this.markAsUntouched({onlySelf: onlySelf});
this.setValue(value, {onlySelf: onlySelf});
reset(formState: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
this._applyFormState(formState);
this.markAsPristine({onlySelf});
this.markAsUntouched({onlySelf});
this.setValue(this._value, {onlySelf});
}
/**
@ -443,15 +509,35 @@ export class FormControl extends AbstractControl {
*/
_anyControls(condition: Function): boolean { return false; }
/**
* @internal
*/
_allControlsDisabled(): boolean { return this.disabled; }
/**
* Register a listener for change events.
*/
registerOnChange(fn: Function): void { this._onChange.push(fn); }
/**
* Register a listener for disabled events.
*/
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void { this._onDisabledChange = fn; }
/**
* @internal
*/
_forEachChild(cb: Function): void {}
private _applyFormState(formState: any) {
if (this._isBoxedValue(formState)) {
this._value = formState.value;
formState.disabled ? this.disable({onlySelf: true, emitEvent: false}) :
this.enable({onlySelf: true, emitEvent: false});
} else {
this._value = formState;
}
}
}
/**
@ -470,14 +556,10 @@ export class FormControl extends AbstractControl {
* @stable
*/
export class FormGroup extends AbstractControl {
private _optionals: {[key: string]: boolean};
constructor(
public controls: {[key: string]: AbstractControl},
/* @deprecated */ optionals: {[key: string]: boolean} = null, validator: ValidatorFn = null,
public controls: {[key: string]: AbstractControl}, validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null) {
super(validator, asyncValidator);
this._optionals = isPresent(optionals) ? optionals : {};
this._initObservables();
this._setParentForControls();
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
@ -509,30 +591,12 @@ export class FormGroup extends AbstractControl {
this.updateValueAndValidity();
}
/**
* Mark the named control as non-optional.
* @deprecated
*/
include(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, true);
this.updateValueAndValidity();
}
/**
* Mark the named control as optional.
* @deprecated
*/
exclude(controlName: string): void {
StringMapWrapper.set(this._optionals, controlName, false);
this.updateValueAndValidity();
}
/**
* Check whether there is a control with the given name in the group.
*/
contains(controlName: string): boolean {
var c = StringMapWrapper.contains(this.controls, controlName);
return c && this._included(controlName);
const c = StringMapWrapper.contains(this.controls, controlName);
return c && this.get(controlName).enabled;
}
setValue(value: {[key: string]: any}, {onlySelf}: {onlySelf?: boolean} = {}): void {
@ -562,6 +626,14 @@ export class FormGroup extends AbstractControl {
this._updateTouched({onlySelf: onlySelf});
}
getRawValue(): Object {
return this._reduceChildren(
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
acc[name] = control.value;
return acc;
});
}
/** @internal */
_throwIfControlMissing(name: string): void {
if (!Object.keys(this.controls).length) {
@ -601,7 +673,9 @@ export class FormGroup extends AbstractControl {
_reduceValue() {
return this._reduceChildren(
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
acc[name] = control.value;
if (control.enabled || this.disabled) {
acc[name] = control.value;
}
return acc;
});
}
@ -609,18 +683,19 @@ export class FormGroup extends AbstractControl {
/** @internal */
_reduceChildren(initValue: any, fn: Function) {
var res = initValue;
this._forEachChild((control: AbstractControl, name: string) => {
if (this._included(name)) {
res = fn(res, control, name);
}
});
this._forEachChild(
(control: AbstractControl, name: string) => { res = fn(res, control, name); });
return res;
}
/** @internal */
_included(controlName: string): boolean {
var isOptional = StringMapWrapper.contains(this._optionals, controlName);
return !isOptional || StringMapWrapper.get(this._optionals, controlName);
_allControlsDisabled(): boolean {
for (let controlName of Object.keys(this.controls)) {
if (this.controls[controlName].enabled) {
return false;
}
}
return !StringMapWrapper.isEmpty(this.controls);
}
/** @internal */
@ -729,6 +804,8 @@ export class FormArray extends AbstractControl {
this._updateTouched({onlySelf: onlySelf});
}
getRawValue(): any[] { return this.controls.map((control) => control.value); }
/** @internal */
_throwIfControlMissing(index: number): void {
if (!this.controls.length) {
@ -748,11 +825,14 @@ export class FormArray extends AbstractControl {
}
/** @internal */
_updateValue(): void { this._value = this.controls.map((control) => control.value); }
_updateValue(): void {
this._value = this.controls.filter((control) => control.enabled || this.disabled)
.map((control) => control.value);
}
/** @internal */
_anyControls(condition: Function): boolean {
return this.controls.some((control: AbstractControl) => condition(control));
return this.controls.some((control: AbstractControl) => control.enabled && condition(control));
}
/** @internal */
@ -768,4 +848,12 @@ export class FormArray extends AbstractControl {
}
});
}
/** @internal */
_allControlsDisabled(): boolean {
for (let control of this.controls) {
if (control.enabled) return false;
}
return !!this.controls.length;
}
}