Skip to content

Commit

Permalink
feat: added dbxActionFormDisabledWhileWorking to dbxActionForm
Browse files Browse the repository at this point in the history
- Disabled state of dbxForm mirrors that of dbxAction now, where multiple string "reasons" can be specified to allow multiple items to manage the disabled state of the form.
  • Loading branch information
dereekb committed Mar 10, 2022
1 parent c20aa02 commit 4d6d67b
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ describe('FormActionDirective', () => {

});

// todo: test dbxActionFormDisabledWhileWorking

});

@Component({
Expand Down
42 changes: 31 additions & 11 deletions packages/dbx-form/src/lib/form/action/form.action.directive.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Directive, Host, OnInit, OnDestroy, Input } from '@angular/core';
import { addSeconds, isPast } from 'date-fns';
import { Observable, of, combineLatest, exhaustMap } from 'rxjs';
import { catchError, filter, first, map, switchMap } from 'rxjs/operators';
import { Observable, of, combineLatest, exhaustMap, catchError, delay, filter, first, map, switchMap, BehaviorSubject, distinctUntilChanged } from 'rxjs';
import { DbxActionContextStoreSourceInstance } from '@dereekb/dbx-core';
import { ReadableError } from '@dereekb/util';
import { SubscriptionObject, LockSet } from '@dereekb/rxjs';
Expand All @@ -15,7 +14,7 @@ export interface DbxActionFormTriggerResult {
export type DbxActionFormValidateFn<T = any> = (value: T) => Observable<boolean>;
export type DbxActionFormModifiedFn<T = any> = (value: T) => Observable<boolean>;

export const APP_ACTION_FORM_DISABLED_KEY = 'actionForm';
export const APP_ACTION_FORM_DISABLED_KEY = 'dbx_action_form';

/**
* Used with an action to bind a form to an action as it's value source.
Expand All @@ -36,16 +35,19 @@ export class DbxActionFormDirective<T = any> implements OnInit, OnDestroy {
* ready to send before the context store is marked enabled.
*/
@Input()
appActionFormValidator?: DbxActionFormValidateFn<T>;
dbxActionFormValidator?: DbxActionFormValidateFn<T>;

/**
* Optional function that checks whether or not the value has been modified.
*/
@Input()
appActionFormModified?: DbxActionFormModifiedFn<T>;
dbxActionFormModified?: DbxActionFormModifiedFn<T>;

private _formDisabledWhileWorking = new BehaviorSubject<boolean>(true);

private _triggeredSub = new SubscriptionObject();
private _isCompleteSub = new SubscriptionObject();
private _isWorkingSub = new SubscriptionObject();

constructor(@Host() public readonly form: DbxMutableForm, public readonly source: DbxActionContextStoreSourceInstance<object, any>) {
if (form.lockSet) {
Expand All @@ -55,6 +57,15 @@ export class DbxActionFormDirective<T = any> implements OnInit, OnDestroy {
this.lockSet.addChildLockSet(source.lockSet, 'source');
}

@Input()
get dbxActionFormDisabledWhileWorking() {
return this._formDisabledWhileWorking.value;
}

set dbxActionFormDisabledWhileWorking(dbxActionFormDisabledWhileWorking: boolean) {
this._formDisabledWhileWorking.next(Boolean(dbxActionFormDisabledWhileWorking ?? true));
}

ngOnInit(): void {

// Pass data from the form to the source when triggered.
Expand Down Expand Up @@ -98,6 +109,7 @@ export class DbxActionFormDirective<T = any> implements OnInit, OnDestroy {

// Update the enabled/disabled state
this._isCompleteSub.subscription = this.form.stream$.pipe(
delay(0),
filter((x) => x.state !== DbxFormState.INITIALIZING),
switchMap((event) => {
return this.form.getValue().pipe(
Expand All @@ -114,7 +126,7 @@ export class DbxActionFormDirective<T = any> implements OnInit, OnDestroy {

const initialIsValidCheck = event.isComplete;
if (initialIsValidCheck) {
validatorObs = (this.appActionFormValidator) ? this.appActionFormValidator(value) : of(true);
validatorObs = (this.dbxActionFormValidator) ? this.dbxActionFormValidator(value) : of(true);
} else {
validatorObs = of(false);
}
Expand All @@ -123,7 +135,7 @@ export class DbxActionFormDirective<T = any> implements OnInit, OnDestroy {

const isConsideredModified = (event.pristine === false && isProbablyTouched);
if (isConsideredModified) {
modifiedObs = (this.appActionFormModified) ? this.appActionFormModified(value) : of(true);
modifiedObs = (this.dbxActionFormModified) ? this.dbxActionFormModified(value) : of(true);
} else {
modifiedObs = of(false);
}
Expand All @@ -148,21 +160,29 @@ export class DbxActionFormDirective<T = any> implements OnInit, OnDestroy {
this.source.enable(APP_ACTION_FORM_DISABLED_KEY, valid);
});

// TODO: Watch the working state and stop allowing input on working..?
// TODO: Watch the disabled state for when another disabled key disables this form.
// Watch the working state and disable form while working
this._isWorkingSub.subscription = combineLatest([this.source.isWorking$, this._formDisabledWhileWorking]).pipe(
map(([isWorking, lockOnWorking]: [boolean, boolean]) => lockOnWorking && isWorking),
distinctUntilChanged()
).subscribe((disable) => {
this.form.setDisabled(APP_ACTION_FORM_DISABLED_KEY, disable);
});
}

ngOnDestroy(): void {
this.source.enable(APP_ACTION_FORM_DISABLED_KEY);
this.lockSet.destroyOnNextUnlock(() => {
this._triggeredSub.destroy();
this._isCompleteSub.destroy();
this._isWorkingSub.destroy();
this._formDisabledWhileWorking.complete();
this.form.setDisabled(APP_ACTION_FORM_DISABLED_KEY, false);
});
}

protected preCheckReadyValue(value: T): Observable<boolean> {
let validatorObs: Observable<boolean> = (this.appActionFormValidator) ? this.appActionFormValidator(value) : of(true);
let modifiedObs: Observable<boolean> = (this.appActionFormModified) ? this.appActionFormModified(value) : of(true);
let validatorObs: Observable<boolean> = (this.dbxActionFormValidator) ? this.dbxActionFormValidator(value) : of(true);
let modifiedObs: Observable<boolean> = (this.dbxActionFormModified) ? this.dbxActionFormModified(value) : of(true);

return combineLatest([
validatorObs,
Expand Down
27 changes: 24 additions & 3 deletions packages/dbx-form/src/lib/form/form.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { forwardRef, Provider, Type } from '@angular/core';
import { Observable } from 'rxjs';
import { LockSet } from '@dereekb/rxjs';
import { Maybe } from '@dereekb/util';
import { BooleanStringKeyArray, Maybe } from '@dereekb/util';

/**
* Current state of a DbxForm
Expand All @@ -12,6 +12,14 @@ export enum DbxFormState {
USED = 1
}


/**
* Unique key for disabling/enabling.
*/
export type DbxFormDisabledKey = string;

export const DEFAULT_FORM_DISABLED_KEY = 'dbx_form_disabled';

/**
* DbxForm stream event
*/
Expand All @@ -22,7 +30,14 @@ export interface DbxFormEvent {
readonly untouched?: boolean;
readonly lastResetAt?: Date;
readonly changesCount?: number;
/**
* Whether or not the form is disabled.
*/
readonly isDisabled?: boolean;
/**
* Current disabled state keys.
*/
readonly disabled?: BooleanStringKeyArray;
}

/**
Expand All @@ -35,13 +50,19 @@ export abstract class DbxForm<T = any> {
* Returns an observable that returns the current state of the form.
*/
abstract getValue(): Observable<T>;

/**
* Returns an observable that returns the current disabled keys.
*/
abstract getDisabled(): Observable<BooleanStringKeyArray>;
}

export abstract class DbxMutableForm<T = any> extends DbxForm<T> {
/**
* LockSet for the form.
*/
abstract readonly lockSet?: LockSet;

/**
* Sets the initial value of the form, and resets the form.
*
Expand All @@ -55,11 +76,11 @@ export abstract class DbxMutableForm<T = any> extends DbxForm<T> {
abstract resetForm(): void;

/**
* Sets the form's disabled state.
* Disables the form
*
* @param disabled
*/
abstract setDisabled(disabled?: boolean): void;
abstract setDisabled(key?: DbxFormDisabledKey, disabled?: boolean): void;

/**
* Force the form to update itself as if it was changed.
Expand Down
33 changes: 23 additions & 10 deletions packages/dbx-form/src/lib/formly/formly.context.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { Provider, Type } from '@angular/core';
import { Provider } from '@angular/core';
import { BehaviorSubject, Observable, of, switchMap, shareReplay, distinctUntilChanged } from 'rxjs';
import { DbxForm, DbxFormEvent, DbxFormState, DbxMutableForm, ProvideDbxMutableForm } from '../form/form';
import { DbxForm, DbxFormDisabledKey, DbxFormEvent, DbxFormState, DbxMutableForm, DEFAULT_FORM_DISABLED_KEY, ProvideDbxMutableForm } from '../form/form';
import { FormlyFieldConfig } from '@ngx-formly/core';
import { LockSet, filterMaybe } from '@dereekb/rxjs';
import { Maybe } from '@dereekb/util';
import { BooleanStringKeyArray, BooleanStringKeyArrayUtilityInstance, Maybe } from '@dereekb/util';

export interface DbxFormlyInitialize<T> {
fields: Observable<FormlyFieldConfig[]>;
initialDisabled: BooleanStringKeyArray;
initialValue: Maybe<Partial<T>>;
initialDisabled: boolean;
}

/**
* DbxFormlyContext delegate.
*
* This is usually the component or element that contains the form itself.
*/
export interface DbxFormlyContextDelegate<T = any> extends Omit<DbxMutableForm<T>, 'lockSet' | 'setDisabled'> {
export interface DbxFormlyContextDelegate<T = any> extends Omit<DbxMutableForm<T>, 'lockSet'> {
readonly stream$: Observable<DbxFormEvent>;
init(initialize: DbxFormlyInitialize<T>): void;
}
Expand All @@ -43,10 +43,11 @@ export class DbxFormlyContext<T> implements DbxForm<T> {

private _fields = new BehaviorSubject<Maybe<FormlyFieldConfig[]>>(undefined);
private _initialValue = new BehaviorSubject<Maybe<Partial<T>>>(undefined);
private _disabled = new BehaviorSubject<boolean>(false);
private _disabled = new BehaviorSubject<BooleanStringKeyArray>(undefined);
private _delegate = new BehaviorSubject<Maybe<DbxFormlyContextDelegate<T>>>(undefined);

readonly fields$ = this._fields.pipe(filterMaybe());
readonly disabled$ = this._disabled.pipe(filterMaybe());
readonly stream$: Observable<DbxFormEvent> = this._delegate.pipe(distinctUntilChanged(), switchMap(x => (x) ? x.stream$ : of(DbxFormlyContext.INITIAL_STATE)), shareReplay(1));

constructor() { }
Expand All @@ -66,8 +67,8 @@ export class DbxFormlyContext<T> implements DbxForm<T> {
if (delegate != null) {
delegate.init({
fields: this.fields$,
initialValue: this._initialValue.value,
initialDisabled: this._disabled.value
initialDisabled: this._disabled.value,
initialValue: this._initialValue.value
});
}

Expand Down Expand Up @@ -103,11 +104,23 @@ export class DbxFormlyContext<T> implements DbxForm<T> {
}

isDisabled(): boolean {
return BooleanStringKeyArrayUtilityInstance.isTrue(this.disabled);
}

get disabled(): BooleanStringKeyArray {
return this._disabled.value;
}

setDisabled(disabled = true): void {
this._disabled.next(disabled);
getDisabled(): Observable<BooleanStringKeyArray> {
return this._disabled.asObservable();
}

setDisabled(key?: DbxFormDisabledKey, disabled = true): void {
this._disabled.next(BooleanStringKeyArrayUtilityInstance.set(this.disabled, key ?? DEFAULT_FORM_DISABLED_KEY, disabled));

if (this._delegate.value) {
this._delegate.value.setDisabled(key, disabled);
}
}

resetForm(): void {
Expand Down
7 changes: 6 additions & 1 deletion packages/dbx-form/src/lib/formly/formly.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FormlyFieldConfig } from '@ngx-formly/core/lib/core';
import { OnInit, OnDestroy, Directive, Input } from '@angular/core';
import { DbxFormlyContext } from './formly.context';
import { Maybe } from '@dereekb/util';
import { DbxFormDisabledKey } from '../form/form';

/**
* Abstract component for wrapping a form.
Expand All @@ -17,7 +18,7 @@ export abstract class AbstractFormlyFormDirective<T> implements OnDestroy {
}

set disabled(disabled: boolean) {
this.context.setDisabled(disabled);
this.context.setDisabled(undefined, disabled);
}

constructor(public readonly context: DbxFormlyContext<T>) { }
Expand All @@ -43,6 +44,10 @@ export abstract class AbstractFormlyFormDirective<T> implements OnDestroy {
this.setValue({});
}

setDisabled(key?: DbxFormDisabledKey, disabled?: boolean): void {
this.context.setDisabled(key, disabled);
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('DbxInputFormControlComponent', () => {
let fixture: ComponentFixture<DbxTestDbxFormComponent>;

beforeEach(async () => {
fixture = TestBed.createComponent(DbxTestDbxFormComponent);
fixture = TestBed.createComponent(DbxTestDbxFormComponent) as ComponentFixture<DbxTestDbxFormComponent>;
testComponent = fixture.componentInstance;
fixture.detectChanges();
});
Expand All @@ -27,4 +27,6 @@ describe('DbxInputFormControlComponent', () => {
expect(testComponent).toBeDefined();
});

// TODO: Test disabled

});
Loading

0 comments on commit 4d6d67b

Please sign in to comment.