MOBILE-3592 user: Implement profile field delegate and component

main
Dani Palou 2020-11-27 14:53:50 +01:00
parent fa294d7135
commit 3722126b5b
13 changed files with 595 additions and 12 deletions

View File

@ -36,6 +36,7 @@ import { CoreContextMenuComponent } from './context-menu/context-menu';
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item'; import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover'; import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; import { CoreUserAvatarComponent } from './user-avatar/user-avatar';
import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module'; import { CorePipesModule } from '@pipes/pipes.module';
@ -63,6 +64,7 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
CoreContextMenuPopoverComponent, CoreContextMenuPopoverComponent,
CoreNavBarButtonsComponent, CoreNavBarButtonsComponent,
CoreUserAvatarComponent, CoreUserAvatarComponent,
CoreDynamicComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -92,6 +94,7 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
CoreContextMenuPopoverComponent, CoreContextMenuPopoverComponent,
CoreNavBarButtonsComponent, CoreNavBarButtonsComponent,
CoreUserAvatarComponent, CoreUserAvatarComponent,
CoreDynamicComponent,
], ],
}) })
export class CoreComponentsModule {} export class CoreComponentsModule {}

View File

@ -0,0 +1,5 @@
<!-- Content to display if no dynamic component. -->
<ng-content *ngIf="!instance"></ng-content>
<!-- Container of the dynamic component -->
<ng-container #dynamicComponent></ng-container>

View File

@ -0,0 +1,198 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Component,
Input,
ViewChild,
OnChanges,
DoCheck,
ViewContainerRef,
ComponentFactoryResolver,
ComponentRef,
KeyValueDiffers,
SimpleChange,
ChangeDetectorRef,
Optional,
ElementRef,
KeyValueDiffer,
Type,
} from '@angular/core';
import { NavController } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreLogger } from '@singletons/logger';
/**
* Component to create another component dynamically.
*
* You need to pass the class of the component to this component (the class, not the name), along with the input data.
*
* So you should do something like:
*
* import { MyComponent } from './component';
*
* ...
*
* this.component = MyComponent;
*
* And in the template:
*
* <core-dynamic-component [component]="component" [data]="data">
* <p>Cannot render the data.</p>
* </core-dynamic-component>
*
* Please notice that the component that you pass needs to be declared in entryComponents of the module to be created dynamically.
*
* Alternatively, you can also supply a ComponentRef instead of the class of the component. In this case, the component won't
* be instantiated because it already is, it will be attached to the view and the right data will be passed to it.
* Passing ComponentRef is meant for site plugins, so we'll inject a NavController instance to the component.
*
* The contents of this component will be displayed if no component is supplied or it cannot be created. In the example above,
* if no component is supplied then the template will show the message "Cannot render the data.".
*/
/* eslint-disable @angular-eslint/no-conflicting-lifecycle */
@Component({
selector: 'core-dynamic-component',
templateUrl: 'core-dynamic-component.html',
})
export class CoreDynamicComponent implements OnChanges, DoCheck {
@Input() component?: Type<unknown>;
@Input() data?: Record<string | number, unknown>;
// Get the container where to put the dynamic component.
@ViewChild('dynamicComponent', { read: ViewContainerRef }) set dynamicComponent(el: ViewContainerRef) {
this.container = el;
this.createComponent();
}
instance?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
container?: ViewContainerRef;
protected logger: CoreLogger;
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
protected lastComponent?: Type<unknown>;
constructor(
protected factoryResolver: ComponentFactoryResolver,
differs: KeyValueDiffers,
@Optional() protected navCtrl: NavController,
protected cdr: ChangeDetectorRef,
protected element: ElementRef,
) {
this.logger = CoreLogger.getInstance('CoreDynamicComponent');
this.differ = differs.find([]).create();
}
/**
* Detect changes on input properties.
*/
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
if (changes.component && !this.component) {
// Component not set, destroy the instance if any.
this.lastComponent = undefined;
this.instance = undefined;
this.container?.clear();
} else if (changes.component && (!this.instance || this.component != this.lastComponent)) {
this.createComponent();
}
}
/**
* Detect and act upon changes that Angular cant or wont detect on its own (objects and arrays).
*/
ngDoCheck(): void {
if (this.instance) {
// Check if there's any change in the data object.
const changes = this.differ.diff(this.data || {});
if (changes) {
this.setInputData();
if (this.instance.ngOnChanges) {
this.instance.ngOnChanges(CoreDomUtils.instance.createChangesFromKeyValueDiff(changes));
}
}
}
}
/**
* Call a certain function on the component.
*
* @param name Name of the function to call.
* @param params List of params to send to the function.
* @return Result of the call. Undefined if no component instance or the function doesn't exist.
*/
callComponentFunction<T = unknown>(name: string, params?: unknown[]): T | undefined {
if (this.instance && typeof this.instance[name] == 'function') {
return this.instance[name].apply(this.instance, params);
}
}
/**
* Create a component, add it to a container and set the input data.
*
* @return Whether the component was successfully created.
*/
protected createComponent(): boolean {
this.lastComponent = this.component;
if (!this.component || !this.container) {
// No component to instantiate or container doesn't exist right now.
return false;
}
if (this.instance) {
// Component already instantiated.
return true;
}
if (this.component instanceof ComponentRef) {
// A ComponentRef was supplied instead of the component class. Add it to the view.
this.container.insert(this.component.hostView);
this.instance = this.component.instance;
// This feature is usually meant for site plugins. Inject some properties.
this.instance['ChangeDetectorRef'] = this.cdr;
this.instance['NavController'] = this.navCtrl;
this.instance['componentContainer'] = this.element.nativeElement;
} else {
try {
// Create the component and add it to the container.
const factory = this.factoryResolver.resolveComponentFactory(this.component);
const componentRef = this.container.createComponent(factory);
this.instance = componentRef.instance;
} catch (ex) {
this.logger.error('Error creating component', ex);
return false;
}
}
this.setInputData();
return true;
}
/**
* Set the input data for the component.
*/
protected setInputData(): void {
for (const name in this.data) {
this.instance[name] = this.data[name];
}
}
}

View File

@ -172,8 +172,8 @@
<ion-item-divider class="ion-text-wrap"> <ion-item-divider class="ion-text-wrap">
<ion-label>{{ category.name }}</ion-label> <ion-label>{{ category.name }}</ion-label>
</ion-item-divider> </ion-item-divider>
<!-- @todo <core-user-profile-field *ngFor="let field of category.fields" [field]="field" edit="true" signup="true" <core-user-profile-field *ngFor="let field of category.fields" [field]="field" [edit]="true" [signup]="true"
registerAuth="email" [form]="signupForm"></core-user-profile-field> --> registerAuth="email" [form]="signupForm"></core-user-profile-field>
</ng-container> </ng-container>
<!-- ReCAPTCHA --> <!-- ReCAPTCHA -->

View File

@ -21,6 +21,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module'; import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreUserComponentsModule } from '@features/user/components/components.module';
import { CoreLoginEmailSignupPage } from './email-signup'; import { CoreLoginEmailSignupPage } from './email-signup';
@ -41,6 +42,7 @@ const routes: Routes = [
ReactiveFormsModule, ReactiveFormsModule,
CoreComponentsModule, CoreComponentsModule,
CoreDirectivesModule, CoreDirectivesModule,
CoreUserComponentsModule,
], ],
declarations: [ declarations: [
CoreLoginEmailSignupPage, CoreLoginEmailSignupPage,

View File

@ -25,6 +25,7 @@ import { CoreWS, CoreWSExternalWarning } from '@services/ws';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreSitePublicConfigResponse } from '@classes/site'; import { CoreSitePublicConfigResponse } from '@classes/site';
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
import { import {
AuthEmailSignupProfileFieldsCategory, AuthEmailSignupProfileFieldsCategory,
@ -82,6 +83,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
protected navCtrl: NavController, protected navCtrl: NavController,
protected fb: FormBuilder, protected fb: FormBuilder,
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected userProfileFieldDelegate: CoreUserProfileFieldDelegate,
) { ) {
// Create the ageVerificationForm. // Create the ageVerificationForm.
this.ageVerificationForm = this.fb.group({ this.ageVerificationForm = this.fb.group({
@ -156,7 +158,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
if (typeof this.ageDigitalConsentVerification == 'undefined') { if (typeof this.ageDigitalConsentVerification == 'undefined') {
const result = await CoreUtils.instance.ignoreErrors( const result = await CoreUtils.instance.ignoreErrors(
CoreWS.instance.callAjax<IsAgeVerificationEnabledResponse>( CoreWS.instance.callAjax<IsAgeVerificationEnabledWSResponse>(
'core_auth_is_age_digital_consent_verification_enabled', 'core_auth_is_age_digital_consent_verification_enabled',
{}, {},
{ siteUrl: this.siteUrl }, { siteUrl: this.siteUrl },
@ -189,7 +191,11 @@ export class CoreLoginEmailSignupPage implements OnInit {
{ siteUrl: this.siteUrl }, { siteUrl: this.siteUrl },
); );
// @todo userProfileFieldDelegate if (this.userProfileFieldDelegate.hasRequiredUnsupportedField(this.settings.profilefields)) {
this.allRequiredSupported = false;
throw new Error(Translate.instance.instant('core.login.signuprequiredfieldnotsupported'));
}
this.categories = CoreLoginHelper.instance.formatProfileFieldsForSignup(this.settings.profilefields); this.categories = CoreLoginHelper.instance.formatProfileFieldsForSignup(this.settings.profilefields);
@ -274,7 +280,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
const params: Record<string, unknown> = { const params: SignupUserWSParams = {
username: this.signupForm.value.username.trim().toLowerCase(), username: this.signupForm.value.username.trim().toLowerCase(),
password: this.signupForm.value.password, password: this.signupForm.value.password,
firstname: CoreTextUtils.instance.cleanTags(this.signupForm.value.firstname), firstname: CoreTextUtils.instance.cleanTags(this.signupForm.value.firstname),
@ -295,8 +301,15 @@ export class CoreLoginEmailSignupPage implements OnInit {
} }
try { try {
// @todo Get the data for the custom profile fields. // Get the data for the custom profile fields.
const result = await CoreWS.instance.callAjax<SignupUserResult>( params.customprofilefields = await this.userProfileFieldDelegate.getDataForFields(
this.settings?.profilefields,
true,
'email',
this.signupForm.value,
);
const result = await CoreWS.instance.callAjax<SignupUserWSResult>(
'auth_email_signup_user', 'auth_email_signup_user',
params, params,
{ siteUrl: this.siteUrl }, { siteUrl: this.siteUrl },
@ -376,7 +389,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
params.age = parseInt(params.age, 10); // Use just the integer part. params.age = parseInt(params.age, 10); // Use just the integer part.
try { try {
const result = await CoreWS.instance.callAjax<IsMinorResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl }); const result = await CoreWS.instance.callAjax<IsMinorWSResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl });
CoreDomUtils.instance.triggerFormSubmittedEvent(this.ageFormElement, true); CoreDomUtils.instance.triggerFormSubmittedEvent(this.ageFormElement, true);
@ -404,14 +417,35 @@ export class CoreLoginEmailSignupPage implements OnInit {
/** /**
* Result of WS core_auth_is_age_digital_consent_verification_enabled. * Result of WS core_auth_is_age_digital_consent_verification_enabled.
*/ */
export type IsAgeVerificationEnabledResponse = { type IsAgeVerificationEnabledWSResponse = {
status: boolean; // True if digital consent verification is enabled, false otherwise. status: boolean; // True if digital consent verification is enabled, false otherwise.
}; };
/**
* Params for WS auth_email_signup_user.
*/
type SignupUserWSParams = {
username: string; // Username.
password: string; // Plain text password.
firstname: string; // The first name(s) of the user.
lastname: string; // The family name of the user.
email: string; // A valid and unique email address.
city?: string; // Home city of the user.
country?: string; // Home country code.
recaptchachallengehash?: string; // Recaptcha challenge hash.
recaptcharesponse?: string; // Recaptcha response.
customprofilefields?: { // User custom fields (also known as user profile fields).
type: string; // The type of the custom field.
name: string; // The name of the custom field.
value: unknown; // Custom field value, can be an encoded json if required.
}[];
redirect?: string; // Redirect the user to this site url after confirmation.
};
/** /**
* Result of WS auth_email_signup_user. * Result of WS auth_email_signup_user.
*/ */
export type SignupUserResult = { type SignupUserWSResult = {
success: boolean; // True if the user was created false otherwise. success: boolean; // True if the user was created false otherwise.
warnings?: CoreWSExternalWarning[]; warnings?: CoreWSExternalWarning[];
}; };
@ -419,6 +453,6 @@ export type SignupUserResult = {
/** /**
* Result of WS core_auth_is_minor. * Result of WS core_auth_is_minor.
*/ */
export type IsMinorResult = { type IsMinorWSResult = {
status: boolean; // True if the user is considered to be a digital minor, false if not. status: boolean; // True if the user is considered to be a digital minor, false if not.
}; };

View File

@ -0,0 +1,43 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { CoreUserProfileFieldComponent } from './user-profile-field/user-profile-field';
@NgModule({
declarations: [
CoreUserProfileFieldComponent,
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
],
providers: [
],
exports: [
CoreUserProfileFieldComponent,
],
})
export class CoreUserComponentsModule {}

View File

@ -0,0 +1 @@
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>

View File

@ -0,0 +1,83 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnInit, Injector, Type } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
import { CoreUtils } from '@services/utils/utils';
/**
* Directive to render user profile field.
*/
@Component({
selector: 'core-user-profile-field',
templateUrl: 'core-user-profile-field.html',
})
export class CoreUserProfileFieldComponent implements OnInit {
@Input() field?: AuthEmailSignupProfileField; // The profile field to be rendered.
@Input() signup = false; // True if editing the field in signup. Defaults to false.
@Input() edit = false; // True if editing the field. Defaults to false.
@Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true.
@Input() registerAuth?: string; // Register auth method. E.g. 'email'.
@Input() contextLevel?: string; // The context level.
@Input() contextInstanceId?: number; // The instance ID related to the context.
@Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters.
componentClass?: Type<unknown>; // The class of the component to render.
data: CoreUserProfileFieldComponentData = {}; // Data to pass to the component.
constructor(
protected userProfileFieldsDelegate: CoreUserProfileFieldDelegate,
protected injector: Injector,
) { }
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
if (!this.field) {
return;
}
this.componentClass = await this.userProfileFieldsDelegate.getComponent(this.injector, this.field, this.signup);
this.data.field = this.field;
this.data.edit = CoreUtils.instance.isTrueOrOne(this.edit);
if (this.edit) {
this.data.signup = CoreUtils.instance.isTrueOrOne(this.signup);
this.data.disabled = CoreUtils.instance.isTrueOrOne(this.field.locked);
this.data.form = this.form;
this.data.registerAuth = this.registerAuth;
this.data.contextLevel = this.contextLevel;
this.data.contextInstanceId = this.contextInstanceId;
this.data.courseId = this.courseId;
}
}
}
export type CoreUserProfileFieldComponentData = {
field?: AuthEmailSignupProfileField;
edit?: boolean;
signup?: boolean;
disabled?: boolean;
form?: FormGroup;
registerAuth?: string;
contextLevel?: string;
contextInstanceId?: number;
courseId?: number;
};

View File

@ -41,7 +41,7 @@
<ion-item class="ion-text-wrap" *ngIf="user.address"> <ion-item class="ion-text-wrap" *ngIf="user.address">
<ion-label> <ion-label>
<h2>{{ 'core.user.address' | translate}}</h2> <h2>{{ 'core.user.address' | translate}}</h2>
<p><a class="core-anchor" [href]="user.encodedAddress" core-link auto-login="no"> <p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no">
{{ user.address }} {{ user.address }}
</a></p> </a></p>
</ion-label> </ion-label>

View File

@ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module'; import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreUserComponentsModule } from '@features/user/components/components.module';
import { CoreUserAboutPage } from './about.page'; import { CoreUserAboutPage } from './about.page';
@ -38,6 +39,7 @@ const routes: Routes = [
TranslateModule.forChild(), TranslateModule.forChild(),
CoreComponentsModule, CoreComponentsModule,
CoreDirectivesModule, CoreDirectivesModule,
CoreUserComponentsModule,
], ],
declarations: [ declarations: [
CoreUserAboutPage, CoreUserAboutPage,

View File

@ -0,0 +1,210 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable, Injector, Type } from '@angular/core';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreError } from '@classes/errors/error';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreUserProfileField } from './user';
/**
* Interface that all user profile field handlers must implement.
*/
export interface CoreUserProfileFieldHandler extends CoreDelegateHandler {
/**
* Type of the field the handler supports. E.g. 'checkbox'.
*/
type: string;
/**
* Return the Component to use to display the user profile field.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param injector Injector.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector): Type<unknown> | Promise<Type<unknown>>;
/**
* Get the data to send for the field based on the input data.
*
* @param field User field to get the data for.
* @param signup True if user is in signup page.
* @param registerAuth Register auth method. E.g. 'email'.
* @param formValues Form Values.
* @return Data to send for the field.
*/
getData?(
field: AuthEmailSignupProfileField | CoreUserProfileField,
signup: boolean,
registerAuth: string,
formValues: Record<string, unknown>,
): Promise<CoreUserProfileFieldHandlerData>;
}
export interface CoreUserProfileFieldHandlerData {
/**
* Name of the custom field.
*/
name: string;
/**
* The type of the custom field
*/
type: string;
/**
* Value of the custom field.
*/
value: unknown;
}
/**
* Service to interact with user profile fields.
*/
@Injectable({
providedIn: 'root',
})
export class CoreUserProfileFieldDelegate extends CoreDelegate<CoreUserProfileFieldHandler> {
protected handlerNameProperty = 'type';
constructor() {
super('CoreUserProfileFieldDelegate', true);
}
/**
* Get the type of a field.
*
* @param field The field to get its type.
* @return The field type.
*/
protected getType(field: AuthEmailSignupProfileField | CoreUserProfileField): string {
return ('type' in field ? field.type : field.datatype) || '';
}
/**
* Get the component to use to display an user field.
*
* @param injector Injector.
* @param field User field to get the directive for.
* @param signup True if user is in signup page.
* @return Promise resolved with component to use, undefined if not found.
*/
async getComponent(
injector: Injector,
field: AuthEmailSignupProfileField | CoreUserProfileField,
signup: boolean,
): Promise<Type<unknown> | undefined> {
const type = this.getType(field);
try {
if (signup) {
return await this.executeFunction(type, 'getComponent', [injector]);
} else {
return await this.executeFunctionOnEnabled(type, 'getComponent', [injector]);
}
} catch (error) {
this.logger.error('Error getting component for field', type, error);
}
}
/**
* Get the data to send for a certain field based on the input data.
*
* @param field User field to get the data for.
* @param signup True if user is in signup page.
* @param registerAuth Register auth method. E.g. 'email'.
* @param formValues Form values.
* @return Data to send for the field.
*/
async getDataForField(
field: AuthEmailSignupProfileField | CoreUserProfileField,
signup: boolean,
registerAuth: string,
formValues: Record<string, unknown>,
): Promise<CoreUserProfileFieldHandlerData> {
const type = this.getType(field);
const handler = this.getHandler(type, !signup);
if (handler) {
const name = 'profile_field_' + field.shortname;
if (handler.getData) {
return await handler.getData(field, signup, registerAuth, formValues);
} else if (field.shortname && typeof formValues[name] != 'undefined') {
// Handler doesn't implement the function, but the form has data for the field.
return {
type: type,
name: name,
value: formValues[name],
};
}
}
throw new CoreError('User profile field handler not found.');
}
/**
* Get the data to send for a list of fields based on the input data.
*
* @param fields User fields to get the data for.
* @param signup True if user is in signup page.
* @param registerAuth Register auth method. E.g. 'email'.
* @param formValues Form values.
* @return Data to send.
*/
async getDataForFields(
fields: (AuthEmailSignupProfileField | CoreUserProfileField)[] | undefined,
signup: boolean = false,
registerAuth: string = '',
formValues: Record<string, unknown>,
): Promise<CoreUserProfileFieldHandlerData[]> {
if (!fields) {
return [];
}
const result: CoreUserProfileFieldHandlerData[] = [];
await Promise.all(fields.map(async (field) => {
try {
const data = await this.getDataForField(field, signup, registerAuth, formValues);
if (data) {
result.push(data);
}
} catch (error) {
// Ignore errors.
}
}));
return result;
}
/**
* Check if any of the profile fields is not supported in the app.
*
* @param fields List of fields.
* @return Whether any of the profile fields is not supported in the app.
*/
hasRequiredUnsupportedField(fields?: AuthEmailSignupProfileField[]): boolean {
if (!fields || !fields.length) {
return false;
}
return fields.some((field) => field.required && !this.hasHandler(this.getType(field)));
}
}

View File

@ -18,6 +18,7 @@ import { Routes } from '@angular/router';
import { CoreMainMenuMoreRoutingModule } from '@features/mainmenu/pages/more/more-routing.module'; import { CoreMainMenuMoreRoutingModule } from '@features/mainmenu/pages/more/more-routing.module';
import { CORE_SITE_SCHEMAS } from '@services/sites'; import { CORE_SITE_SCHEMAS } from '@services/sites';
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/db/user'; import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/db/user';
import { CoreUserComponentsModule } from './components/components.module';
const routes: Routes = [ const routes: Routes = [
{ {
@ -29,6 +30,7 @@ const routes: Routes = [
@NgModule({ @NgModule({
imports: [ imports: [
CoreMainMenuMoreRoutingModule.forChild({ siblings: routes }), CoreMainMenuMoreRoutingModule.forChild({ siblings: routes }),
CoreUserComponentsModule,
], ],
providers: [ providers: [
{ {