feat: add typeahead to extensible form props

pull/6405/head
Arman Ozak 5 years ago
parent 52dac80570
commit 311fe15031

@ -1,6 +1,6 @@
<div class="form-group" *abpPermission="prop.permission" [ngSwitch]="getComponent(prop)"> <div class="form-group" *abpPermission="prop.permission" [ngSwitch]="getComponent(prop)">
<ng-template ngSwitchCase="input"> <ng-template ngSwitchCase="input">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<input <input
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
@ -12,6 +12,10 @@
/> />
</ng-template> </ng-template>
<ng-template ngSwitchCase="hidden">
<input [formControlName]="prop.name" type="hidden" />
</ng-template>
<ng-template ngSwitchCase="checkbox"> <ng-template ngSwitchCase="checkbox">
<div class="custom-checkbox custom-control" validationTarget> <div class="custom-checkbox custom-control" validationTarget>
<input <input
@ -21,14 +25,15 @@
type="checkbox" type="checkbox"
class="custom-control-input" class="custom-control-input"
/> />
<label [htmlFor]="prop.id" class="custom-control-label" <ng-template
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label [ngTemplateOutlet]="label"
> [ngTemplateOutletContext]="{ $implicit: 'custom-control-label' }"
></ng-template>
</div> </div>
</ng-template> </ng-template>
<ng-template ngSwitchCase="select"> <ng-template ngSwitchCase="select">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<select <select
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
@ -45,7 +50,7 @@
</ng-template> </ng-template>
<ng-template ngSwitchCase="multiselect"> <ng-template ngSwitchCase="multiselect">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<select <select
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
@ -62,8 +67,30 @@
</select> </select>
</ng-template> </ng-template>
<ng-template ngSwitchCase="typeahead">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<div #typeahead class="position-relative" validationStyle validationTarget>
<input
[id]="prop.id"
[autocomplete]="prop.autocomplete"
[abpDisabled]="disabled"
[ngbTypeahead]="search"
[editable]="false"
[inputFormatter]="typeaheadFormatter"
[resultFormatter]="typeaheadFormatter"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="typeaheadModel"
(selectItem)="setTypeaheadValue($event.item)"
(blur)="setTypeaheadValue(typeaheadModel)"
[class.is-invalid]="typeahead.classList.contains('is-invalid')"
class="form-control"
/>
<input [formControlName]="prop.name" type="hidden" />
</div>
</ng-template>
<ng-template ngSwitchCase="date"> <ng-template ngSwitchCase="date">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<input <input
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
@ -77,17 +104,17 @@
</ng-template> </ng-template>
<ng-template ngSwitchCase="time"> <ng-template ngSwitchCase="time">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<ngb-timepicker [formControlName]="prop.name"></ngb-timepicker> <ngb-timepicker [formControlName]="prop.name"></ngb-timepicker>
</ng-template> </ng-template>
<ng-template ngSwitchCase="dateTime"> <ng-template ngSwitchCase="dateTime">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<abp-date-time-picker [prop]="prop"></abp-date-time-picker> <abp-date-time-picker [prop]="prop"></abp-date-time-picker>
</ng-template> </ng-template>
<ng-template ngSwitchCase="textarea"> <ng-template ngSwitchCase="textarea">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label> <ng-template [ngTemplateOutlet]="label"></ng-template>
<textarea <textarea
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
@ -97,3 +124,9 @@
></textarea> ></textarea>
</ng-template> </ng-template>
</div> </div>
<ng-template #label let-classes>
<label [htmlFor]="prop.id" [ngClass]="classes"
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label
>
</ng-template>

@ -4,14 +4,22 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Input, Input,
OnChanges,
Optional, Optional,
SimpleChanges, SimpleChanges,
SkipSelf, SkipSelf,
OnChanges,
} from '@angular/core'; } from '@angular/core';
import { ControlContainer, Validators, ValidatorFn } from '@angular/forms'; import {
ControlContainer,
FormGroup,
FormGroupDirective,
ValidatorFn,
Validators,
} from '@angular/forms';
import { NgbDateAdapter, NgbTimeAdapter } from '@ng-bootstrap/ng-bootstrap'; import { NgbDateAdapter, NgbTimeAdapter } from '@ng-bootstrap/ng-bootstrap';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { DateAdapter } from '../../adapters/date.adapter'; import { DateAdapter } from '../../adapters/date.adapter';
import { TimeAdapter } from '../../adapters/time.adapter'; import { TimeAdapter } from '../../adapters/time.adapter';
import { ePropType } from '../../enums/props.enum'; import { ePropType } from '../../enums/props.enum';
@ -38,6 +46,8 @@ export class ExtensibleFormPropComponent implements OnChanges {
@Input() prop: FormProp; @Input() prop: FormProp;
asterisk = '';
options$: Observable<ABP.Option<any>[]> = of([]); options$: Observable<ABP.Option<any>[]> = of([]);
validators: ValidatorFn[] = []; validators: ValidatorFn[] = [];
@ -46,15 +56,55 @@ export class ExtensibleFormPropComponent implements OnChanges {
disabled: boolean; disabled: boolean;
constructor(public readonly cdRef: ChangeDetectorRef, public readonly track: TrackByService) {} private readonly form: FormGroup;
typeaheadModel: any;
get asterisk(): string { setTypeaheadValue(selectedOption: ABP.Option<string>) {
return this.validators.some(validator => validator === Validators.required) ? '*' : ''; this.typeaheadModel = selectedOption || { key: null, value: null };
const { key, value } = this.typeaheadModel;
const [keyControl, valueControl] = this.getTypeaheadControls();
keyControl.setValue(key);
valueControl.setValue(value);
valueControl.markAsDirty();
valueControl.markAsTouched();
} }
getComponent(prop: FormProp): string { search = (text$: Observable<string>) =>
if (prop.options && prop.type !== ePropType.MultiSelect) return 'select'; text$
? text$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(text => this.prop.options(this.data, text)),
)
: of([]);
typeaheadFormatter = (option: ABP.Option<any>) => option.key;
get isInvalid() {
const control = this.form.get(this.prop.name);
return control.touched && control.invalid;
}
constructor(
public readonly cdRef: ChangeDetectorRef,
public readonly track: TrackByService,
groupDirective: FormGroupDirective,
) {
this.form = groupDirective.form;
}
private getTypeaheadControls() {
const { name } = this.prop;
const { [name + '_Text']: key, [name]: value } = this.form.controls;
return [key, value];
}
private setAsterisk() {
this.asterisk = this.validators.some(v => v === Validators.required) ? '*' : '';
}
getComponent(prop: FormProp): string {
switch (prop.type) { switch (prop.type) {
case ePropType.Boolean: case ePropType.Boolean:
return 'checkbox'; return 'checkbox';
@ -62,14 +112,18 @@ export class ExtensibleFormPropComponent implements OnChanges {
return 'date'; return 'date';
case ePropType.DateTime: case ePropType.DateTime:
return 'dateTime'; return 'dateTime';
case ePropType.Hidden:
return 'hidden';
case ePropType.MultiSelect:
return 'multiselect';
case ePropType.Text: case ePropType.Text:
return 'textarea'; return 'textarea';
case ePropType.Time: case ePropType.Time:
return 'time'; return 'time';
case ePropType.MultiSelect: case ePropType.Typeahead:
return 'multiselect'; return 'typeahead';
default: default:
return 'input'; return prop.options ? 'select' : 'input';
} }
} }
@ -92,14 +146,15 @@ export class ExtensibleFormPropComponent implements OnChanges {
} }
ngOnChanges({ prop }: SimpleChanges) { ngOnChanges({ prop }: SimpleChanges) {
const options = prop.currentValue.options; const currentProp = snq<FormProp>(() => prop.currentValue);
const readonly = prop.currentValue.readonly; const { options, readonly, disabled, validators } = currentProp || {};
const disabled = prop.currentValue.disabled;
const validators = prop.currentValue.validators;
if (options) this.options$ = options(this.data); if (options) this.options$ = options(this.data);
if (readonly) this.readonly = readonly(this.data); if (readonly) this.readonly = readonly(this.data);
if (disabled) this.disabled = disabled(this.data); if (disabled) this.disabled = disabled(this.data);
if (validators) this.validators = validators(this.data); if (validators) {
this.validators = validators(this.data);
this.setAsterisk();
}
} }
} }

Loading…
Cancel
Save