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)">
<ng-template ngSwitchCase="input">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label>
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input
[id]="prop.id"
[formControlName]="prop.name"
@ -12,6 +12,10 @@
/>
</ng-template>
<ng-template ngSwitchCase="hidden">
<input [formControlName]="prop.name" type="hidden" />
</ng-template>
<ng-template ngSwitchCase="checkbox">
<div class="custom-checkbox custom-control" validationTarget>
<input
@ -21,14 +25,15 @@
type="checkbox"
class="custom-control-input"
/>
<label [htmlFor]="prop.id" class="custom-control-label"
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label
>
<ng-template
[ngTemplateOutlet]="label"
[ngTemplateOutletContext]="{ $implicit: 'custom-control-label' }"
></ng-template>
</div>
</ng-template>
<ng-template ngSwitchCase="select">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label>
<ng-template [ngTemplateOutlet]="label"></ng-template>
<select
[id]="prop.id"
[formControlName]="prop.name"
@ -45,7 +50,7 @@
</ng-template>
<ng-template ngSwitchCase="multiselect">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label>
<ng-template [ngTemplateOutlet]="label"></ng-template>
<select
[id]="prop.id"
[formControlName]="prop.name"
@ -62,8 +67,30 @@
</select>
</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">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label>
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input
[id]="prop.id"
[formControlName]="prop.name"
@ -77,17 +104,17 @@
</ng-template>
<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>
</ng-template>
<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>
</ng-template>
<ng-template ngSwitchCase="textarea">
<label [htmlFor]="prop.id">{{ prop.displayName | abpLocalization }} {{ asterisk }}</label>
<ng-template [ngTemplateOutlet]="label"></ng-template>
<textarea
[id]="prop.id"
[formControlName]="prop.name"
@ -97,3 +124,9 @@
></textarea>
</ng-template>
</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,
Component,
Input,
OnChanges,
Optional,
SimpleChanges,
SkipSelf,
OnChanges,
} 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 { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { DateAdapter } from '../../adapters/date.adapter';
import { TimeAdapter } from '../../adapters/time.adapter';
import { ePropType } from '../../enums/props.enum';
@ -38,6 +46,8 @@ export class ExtensibleFormPropComponent implements OnChanges {
@Input() prop: FormProp;
asterisk = '';
options$: Observable<ABP.Option<any>[]> = of([]);
validators: ValidatorFn[] = [];
@ -46,15 +56,55 @@ export class ExtensibleFormPropComponent implements OnChanges {
disabled: boolean;
constructor(public readonly cdRef: ChangeDetectorRef, public readonly track: TrackByService) {}
private readonly form: FormGroup;
typeaheadModel: any;
get asterisk(): string {
return this.validators.some(validator => validator === Validators.required) ? '*' : '';
setTypeaheadValue(selectedOption: ABP.Option<string>) {
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 {
if (prop.options && prop.type !== ePropType.MultiSelect) return 'select';
search = (text$: Observable<string>) =>
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) {
case ePropType.Boolean:
return 'checkbox';
@ -62,14 +112,18 @@ export class ExtensibleFormPropComponent implements OnChanges {
return 'date';
case ePropType.DateTime:
return 'dateTime';
case ePropType.Hidden:
return 'hidden';
case ePropType.MultiSelect:
return 'multiselect';
case ePropType.Text:
return 'textarea';
case ePropType.Time:
return 'time';
case ePropType.MultiSelect:
return 'multiselect';
case ePropType.Typeahead:
return 'typeahead';
default:
return 'input';
return prop.options ? 'select' : 'input';
}
}
@ -92,14 +146,15 @@ export class ExtensibleFormPropComponent implements OnChanges {
}
ngOnChanges({ prop }: SimpleChanges) {
const options = prop.currentValue.options;
const readonly = prop.currentValue.readonly;
const disabled = prop.currentValue.disabled;
const validators = prop.currentValue.validators;
const currentProp = snq<FormProp>(() => prop.currentValue);
const { options, readonly, disabled, validators } = currentProp || {};
if (options) this.options$ = options(this.data);
if (readonly) this.readonly = readonly(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