From 7f93fd261e69a0998e896418856e976e784f203b Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Mon, 15 Aug 2022 17:34:53 +0300 Subject: [PATCH] Add Custom Component Feature to ExtensibleForm. --- .../account/src/lib/account-routing.module.ts | 4 +- .../account/src/lib/account.module.ts | 13 +++- ...xtensible-personal-settings.component.html | 5 ++ ...xtensible-personal-settings.component.scss | 0 ...nsible-personal-settings.component.spec.ts | 24 +++++++ .../extensible-personal-settings.component.ts | 42 +++++++++++ .../lib/components/hello/hello.component.ts | 15 ++++ .../default-personal-settings-form-props.ts | 15 ++++ .../src/lib/guards/extensions.guard.ts | 42 +++++++++++ .../packages/account/src/lib/guards/index.ts | 1 + .../account/src/lib/models/config-options.ts | 11 +++ .../src/lib/tokens/extensions.token.ts | 17 +++++ .../packages/account/src/lib/tokens/index.ts | 1 + .../extensible-form-prop.component.html | 6 ++ .../extensible-form-prop.component.ts | 71 ++++++++++++------- .../extensible-table.component.ts | 9 +-- .../extensions/src/lib/models/form-props.ts | 2 + .../extensions/src/lib/models/props.ts | 1 + .../src/lib/tokens/extensions.token.ts | 3 + 19 files changed, 250 insertions(+), 32 deletions(-) create mode 100644 npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.html create mode 100644 npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.scss create mode 100644 npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.spec.ts create mode 100644 npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.ts create mode 100644 npm/ng-packs/packages/account/src/lib/components/hello/hello.component.ts create mode 100644 npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts create mode 100644 npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts create mode 100644 npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts diff --git a/npm/ng-packs/packages/account/src/lib/account-routing.module.ts b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts index c09dede3cb..e4b18a717b 100644 --- a/npm/ng-packs/packages/account/src/lib/account-routing.module.ts +++ b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts @@ -13,6 +13,8 @@ import { RegisterComponent } from './components/register/register.component'; import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; import { eAccountComponents } from './enums/components'; import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; +import { AccountExtensionsGuard } from './guards'; + const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'login' }, @@ -68,7 +70,7 @@ const routes: Routes = [ { path: 'manage', component: ReplaceableRouteContainerComponent, - canActivate: [AuthGuard], + canActivate: [AuthGuard, AccountExtensionsGuard], data: { replaceableComponent: { key: eAccountComponents.ManageProfile, diff --git a/npm/ng-packs/packages/account/src/lib/account.module.ts b/npm/ng-packs/packages/account/src/lib/account.module.ts index bfa37d4bfa..26f71ef150 100644 --- a/npm/ng-packs/packages/account/src/lib/account.module.ts +++ b/npm/ng-packs/packages/account/src/lib/account.module.ts @@ -15,6 +15,11 @@ import { accountConfigOptionsFactory } from './utils/factory-utils'; import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component'; import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; +import { ExtensiblePersonalSettingsComponent } from './components/extensible-personal-settings/extensible-personal-settings.component'; +import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions'; +import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS } from './tokens/extensions.token'; +import { AccountExtensionsGuard } from './guards/extensions.guard'; +import { HelloComponent } from './components/hello/hello.component'; const declarations = [ LoginComponent, @@ -27,13 +32,14 @@ const declarations = [ ]; @NgModule({ - declarations: [...declarations], + declarations: [...declarations, ExtensiblePersonalSettingsComponent, HelloComponent], imports: [ CoreModule, AccountRoutingModule, ThemeSharedModule, NgbDropdownModule, NgxValidateCoreModule, + UiExtensionsModule, ], exports: [...declarations], }) @@ -49,6 +55,11 @@ export class AccountModule { useFactory: accountConfigOptionsFactory, deps: [ACCOUNT_CONFIG_OPTIONS], }, + { + provide: ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, + useValue: options.editFormPropContributors, + }, + AccountExtensionsGuard, ], }; } diff --git a/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.html b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.html new file mode 100644 index 0000000000..5b17770996 --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.html @@ -0,0 +1,5 @@ +

extensible-personal-settings works!

+ +
+ +
diff --git a/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.scss b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.spec.ts b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.spec.ts new file mode 100644 index 0000000000..e2a2102e5a --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExtensiblePersonalSettingsComponent } from './extensible-personal-settings.component'; + +describe('ExtensiblePersonalSettingsComponent', () => { + let component: ExtensiblePersonalSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExtensiblePersonalSettingsComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExtensiblePersonalSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.ts b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.ts new file mode 100644 index 0000000000..0b54672cab --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/components/extensible-personal-settings/extensible-personal-settings.component.ts @@ -0,0 +1,42 @@ +import {Component, Injector, OnInit} from '@angular/core'; +import {EXTENSIONS_IDENTIFIER, FormPropData, generateFormFromProps} from "@abp/ng.theme.shared/extensions"; +import { eAccountComponents } from '../../enums/components'; +import {FormBuilder, FormGroup} from "@angular/forms"; +import {ProfileService} from "@abp/ng.account.core/proxy"; + +@Component({ + selector: 'abp-extensible-personal-settings', + templateUrl: './extensible-personal-settings.component.html', + styleUrls: ['./extensible-personal-settings.component.scss'], + providers:[ + { + provide: EXTENSIONS_IDENTIFIER, + useValue: eAccountComponents.PersonalSettings, + }, + ] +}) +export class ExtensiblePersonalSettingsComponent implements OnInit { + selected = {a: 1};// hacky for triggering 'edit' mode of extensible-form + + form: FormGroup; + + constructor( + private profileService: ProfileService, + protected fb: FormBuilder, + protected injector: Injector, + ) {} + + + buildForm() { + const data = new FormPropData(this.injector, this.selected); + this.form = generateFormFromProps(data); + } + + ngOnInit(): void { + this.buildForm() + } + + save() { + + } +} diff --git a/npm/ng-packs/packages/account/src/lib/components/hello/hello.component.ts b/npm/ng-packs/packages/account/src/lib/components/hello/hello.component.ts new file mode 100644 index 0000000000..2fc59314aa --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/components/hello/hello.component.ts @@ -0,0 +1,15 @@ +import {Component, Inject } from '@angular/core'; +import {FORM_PROP_DATA_STREAM, FormProp} from "@abp/ng.theme.shared/extensions"; + +@Component({ + selector: 'abp-hello', + template: `

hello works! {{name | abpLocalization}}

`, + styles: [], +}) +export class HelloComponent { + name:string; + constructor(@Inject(FORM_PROP_DATA_STREAM) private propData:FormProp) { + this.name = propData.displayName + } + +} diff --git a/npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts b/npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts new file mode 100644 index 0000000000..0624e074d3 --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts @@ -0,0 +1,15 @@ +import {ePropType, FormProp} from "@abp/ng.theme.shared/extensions"; +import {UpdateProfileDto} from "@abp/ng.account.core/proxy"; +import {Validators} from "@angular/forms"; +import {HelloComponent} from "../components/hello/hello.component"; + +export const DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS = FormProp.createMany([ + { + type: ePropType.String, + name: 'userName', + displayName: 'Account::UserName', + id: 'user-name', + validators: () => [Validators.required, Validators.maxLength(256)], + template: HelloComponent + }, +]) diff --git a/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts b/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts new file mode 100644 index 0000000000..20c4269977 --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts @@ -0,0 +1,42 @@ +import {Injectable, Injector} from "@angular/core"; +import {CanActivate} from "@angular/router"; +import {Observable} from "rxjs"; +import { + ExtensionsService, + getObjectExtensionEntitiesFromStore, + mapEntitiesToContributors, mergeWithDefaultProps +} from "@abp/ng.theme.shared/extensions"; + import {ConfigStateService} from "@abp/ng.core"; +import {tap, map, mapTo} from "rxjs/operators"; +import {ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, DEFAULT_ACCOUNT_FORM_PROPS} from '../tokens/extensions.token'; +import { AccountEditFormPropContributors } from '../models/config-options'; +import { eAccountComponents } from '../enums/components'; + +@Injectable() +export class AccountExtensionsGuard implements CanActivate { + constructor(private injector: Injector) {} + + canActivate(): Observable { + const extensions: ExtensionsService = this.injector.get(ExtensionsService); + + const editFormContributors: AccountEditFormPropContributors = + this.injector.get(ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, null) || {}; + + const configState = this.injector.get(ConfigStateService); + return getObjectExtensionEntitiesFromStore(configState, 'Account').pipe( + map(entities => ({ + [eAccountComponents.PersonalSettings]: entities.PersonalSettings, + })), + mapEntitiesToContributors(configState, 'AbpAccount'), + tap(objectExtensionContributors => { + mergeWithDefaultProps( + extensions.editFormProps, + DEFAULT_ACCOUNT_FORM_PROPS, + objectExtensionContributors.editForm, + editFormContributors, + ); + }), + mapTo(true), + ); + } +} diff --git a/npm/ng-packs/packages/account/src/lib/guards/index.ts b/npm/ng-packs/packages/account/src/lib/guards/index.ts index 382170b2b1..275cd2f8e8 100644 --- a/npm/ng-packs/packages/account/src/lib/guards/index.ts +++ b/npm/ng-packs/packages/account/src/lib/guards/index.ts @@ -1 +1,2 @@ export * from './authentication-flow.guard'; +export * from './extensions.guard'; diff --git a/npm/ng-packs/packages/account/src/lib/models/config-options.ts b/npm/ng-packs/packages/account/src/lib/models/config-options.ts index 898021b04e..f2e603ab32 100644 --- a/npm/ng-packs/packages/account/src/lib/models/config-options.ts +++ b/npm/ng-packs/packages/account/src/lib/models/config-options.ts @@ -1,3 +1,14 @@ +import {eAccountComponents} from "../enums"; +import { EditFormPropContributorCallback } from '@abp/ng.theme.shared/extensions'; +import {UpdateProfileDto} from "@abp/ng.account.core/proxy"; + export interface AccountConfigOptions { redirectUrl?: string; } +export type AccountEditFormPropContributors = Partial<{ + [eAccountComponents.PersonalSettings]: EditFormPropContributorCallback[]; +}>; + +export interface AccountConfigOptions { + editFormPropContributors?: AccountEditFormPropContributors; +} diff --git a/npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts b/npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts new file mode 100644 index 0000000000..6d95db8b6d --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts @@ -0,0 +1,17 @@ +import {eAccountComponents} from "../enums"; +import {DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS} from "../defaults/default-personal-settings-form-props"; +import {InjectionToken} from "@angular/core"; +import {EditFormPropContributorCallback} from "@abp/ng.theme.shared/extensions"; +import {UpdateProfileDto} from "@abp/ng.account.core/proxy"; + +export const DEFAULT_ACCOUNT_FORM_PROPS = { + [eAccountComponents.PersonalSettings]: DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS, +}; + +export const ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS = new InjectionToken( + 'ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS', +); + +type EditFormPropContributors = Partial<{ + [eAccountComponents.PersonalSettings]: EditFormPropContributorCallback[]; +}>; diff --git a/npm/ng-packs/packages/account/src/lib/tokens/index.ts b/npm/ng-packs/packages/account/src/lib/tokens/index.ts index 0a88318130..4381d1bc77 100644 --- a/npm/ng-packs/packages/account/src/lib/tokens/index.ts +++ b/npm/ng-packs/packages/account/src/lib/tokens/index.ts @@ -1 +1,2 @@ export * from './config-options.token'; +export * from './extensions.token'; diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html index 4367bd098a..67b0022be8 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html @@ -3,6 +3,12 @@ *abpPermission="prop.permission; runChangeDetection: false" [ngSwitch]="getComponent(prop)" > + + + + + + ; + public injectorForCustomComponent: Injector + asterisk = ''; options$: Observable[]> = of([]); @@ -62,15 +65,16 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { readonly!: boolean; - disabledFn = (data:PropData) => false; - - get disabled() { - return this.disabledFn(this.data) - } + typeaheadModel: any; private readonly form: FormGroup; - typeaheadModel: any; + disabledFn = (data: PropData) => false; + + get disabled() { + return this.disabledFn(this.data) + } + setTypeaheadValue(selectedOption: ABP.Option) { this.typeaheadModel = selectedOption || { key: null, value: null }; @@ -108,12 +112,13 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { public readonly track: TrackByService, protected configState: ConfigStateService, groupDirective: FormGroupDirective, + private injector: Injector ) { this.form = groupDirective.form; } private getTypeaheadControls() { - const { name } = this.prop; + const {name} = this.prop; const extraPropName = `${EXTRA_PROPERTIES_KEY}.${name}`; const keyControl = this.form.get(addTypeaheadTextSuffix(extraPropName)) || @@ -133,6 +138,9 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } getComponent(prop: FormProp): string { + if (prop.template) { + return 'template' + } switch (prop.type) { case ePropType.Boolean: return 'checkbox'; @@ -173,13 +181,24 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } } - ngOnChanges({ prop }: SimpleChanges) { - const currentProp = prop?.currentValue; - const { options, readonly, disabled, validators } = currentProp || {}; + ngOnChanges({prop}: SimpleChanges) { + const currentProp = prop?.currentValue as FormProp; + const {options, readonly, disabled, validators, template} = currentProp || {}; + if (template) { + this.injectorForCustomComponent = Injector.create({ + providers: [ + { + provide: FORM_PROP_DATA_STREAM, + useValue: currentProp + } + ], + parent: this.injector, + }); + } if (options) this.options$ = options(this.data); if (readonly) this.readonly = readonly(this.data); - + if (disabled) { this.disabledFn = disabled; } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts index 9682188638..d3c58f0c93 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts @@ -158,15 +158,16 @@ export class ExtensibleTableComponent implements OnChanges { value, }; if (prop.value.component) { - const injector = Injector.create( - [ + + const injector = Injector.create({ + providers: [ { provide: PROP_DATA_STREAM, useValue: value, }, ], - this.injector, - ); + parent: this.injector, + }); record[propKey].injector = injector; record[propKey].component = prop.value.component; } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/form-props.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/form-props.ts index 44b309d0c2..b607e4cb6e 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/form-props.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/form-props.ts @@ -38,6 +38,7 @@ export class FormProp extends Prop { readonly defaultValue: boolean | number | string | Date; readonly options: PropCallback[]>> | undefined; readonly id: string | undefined; + readonly template? : Type constructor(options: FormPropOptions) { super( @@ -47,6 +48,7 @@ export class FormProp extends Prop { options.permission, options.visible, options.isExtra, + options.template ); this.asyncValidators = options.asyncValidators || (_ => []); diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/props.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/props.ts index 66fc4f2c67..0b26512dbc 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/props.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/models/props.ts @@ -33,6 +33,7 @@ export abstract class Prop { public readonly permission: string, public readonly visible: PropPredicate = _ => true, public readonly isExtra = false, + public readonly template? : Type ) { this.displayName = this.displayName || this.name; } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensions.token.ts b/npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensions.token.ts index d2cbbe87bd..28f26317d8 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensions.token.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensions.token.ts @@ -3,6 +3,7 @@ import { ActionCallback, ReadonlyActionData as ActionData } from '../models/acti import { ExtensionsService } from '../services/extensions.service'; import { Observable } from 'rxjs'; import { ePropType } from '../enums/props.enum'; + import {FormProp} from "../models/form-props"; export const EXTENSIONS_IDENTIFIER = new InjectionToken('EXTENSIONS_IDENTIFIER'); export type ActionKeys = Extract<'entityActions' | 'toolbarActions', keyof ExtensionsService>; @@ -24,3 +25,5 @@ export const ENTITY_PROP_TYPE_CLASSES = new InjectionToken( factory: () => ({} as EntityPropTypeClass), }, ); + +export const FORM_PROP_DATA_STREAM = new InjectionToken('FORM_PROP_DATA_STREAM');