diff --git a/src/core/compile/components/compile-html/compile-html.ts b/src/core/compile/components/compile-html/compile-html.ts index a7773a965..3583a76df 100644 --- a/src/core/compile/components/compile-html/compile-html.ts +++ b/src/core/compile/components/compile-html/compile-html.ts @@ -18,6 +18,7 @@ import { } from '@angular/core'; import { NavController } from 'ionic-angular'; import { CoreCompileProvider } from '../../../compile/providers/compile'; +import { BehaviorSubject } from 'rxjs'; /** * This component has a behaviour similar to $compile for AngularJS. Given an HTML code, it will compile it so all its @@ -48,10 +49,12 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy { protected componentRef: ComponentRef; protected element; + componentObservable: BehaviorSubject; // An observable to notify observers when the component is instantiated. constructor(protected compileProvider: CoreCompileProvider, protected cdr: ChangeDetectorRef, element: ElementRef, @Optional() protected navCtrl: NavController) { this.element = element.nativeElement; + this.componentObservable = new BehaviorSubject(null); } /** @@ -67,6 +70,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy { if (factory) { // Create the component. this.componentRef = this.container.createComponent(factory); + this.componentObservable.next(this.componentRef.instance); } }); } @@ -99,11 +103,11 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy { this['ChangeDetectorRef'] = compileInstance.cdr; this['NavController'] = compileInstance.navCtrl; this['componentContainer'] = compileInstance.element; + } - // Add the data passed to the component. - for (const name in compileInstance.jsData) { - this[name] = compileInstance.jsData[name]; - } + // Add the data passed to the component. + for (const name in compileInstance.jsData) { + this[name] = compileInstance.jsData[name]; } } diff --git a/src/core/siteaddons/components/components.module.ts b/src/core/siteaddons/components/components.module.ts index 15a6f3019..ac5daadeb 100644 --- a/src/core/siteaddons/components/components.module.ts +++ b/src/core/siteaddons/components/components.module.ts @@ -22,13 +22,15 @@ import { CoreSiteAddonsAddonContentComponent } from './addon-content/addon-conte import { CoreSiteAddonsModuleIndexComponent } from './module-index/module-index'; import { CoreSiteAddonsCourseOptionComponent } from './course-option/course-option'; import { CoreSiteAddonsCourseFormatComponent } from './course-format/course-format'; +import { CoreSiteAddonsUserProfileFieldComponent } from './user-profile-field/user-profile-field'; @NgModule({ declarations: [ CoreSiteAddonsAddonContentComponent, CoreSiteAddonsModuleIndexComponent, CoreSiteAddonsCourseOptionComponent, - CoreSiteAddonsCourseFormatComponent + CoreSiteAddonsCourseFormatComponent, + CoreSiteAddonsUserProfileFieldComponent ], imports: [ CommonModule, @@ -43,12 +45,14 @@ import { CoreSiteAddonsCourseFormatComponent } from './course-format/course-form CoreSiteAddonsAddonContentComponent, CoreSiteAddonsModuleIndexComponent, CoreSiteAddonsCourseOptionComponent, - CoreSiteAddonsCourseFormatComponent + CoreSiteAddonsCourseFormatComponent, + CoreSiteAddonsUserProfileFieldComponent ], entryComponents: [ CoreSiteAddonsModuleIndexComponent, CoreSiteAddonsCourseOptionComponent, - CoreSiteAddonsCourseFormatComponent + CoreSiteAddonsCourseFormatComponent, + CoreSiteAddonsUserProfileFieldComponent ] }) export class CoreSiteAddonsComponentsModule {} diff --git a/src/core/siteaddons/components/user-profile-field/user-profile-field.html b/src/core/siteaddons/components/user-profile-field/user-profile-field.html new file mode 100644 index 000000000..fec5e4726 --- /dev/null +++ b/src/core/siteaddons/components/user-profile-field/user-profile-field.html @@ -0,0 +1 @@ + diff --git a/src/core/siteaddons/components/user-profile-field/user-profile-field.ts b/src/core/siteaddons/components/user-profile-field/user-profile-field.ts new file mode 100644 index 000000000..4e0714b36 --- /dev/null +++ b/src/core/siteaddons/components/user-profile-field/user-profile-field.ts @@ -0,0 +1,87 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, OnInit, Input, ViewChild, OnDestroy } from '@angular/core'; +import { CoreSiteAddonsProvider } from '../../providers/siteaddons'; +import { CoreCompileHtmlComponent } from '../../../compile/components/compile-html/compile-html'; +import { Subscription } from 'rxjs'; + +/** + * Component that displays a user profile field created using a site addon. + */ +@Component({ + selector: 'core-site-addons-user-profile-field', + templateUrl: 'user-profile-field.html', +}) +export class CoreSiteAddonsUserProfileFieldComponent implements OnInit, OnDestroy { + @Input() field: any; // 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?: any; // Form where to add the form control. Required if edit=true or signup=true. + @Input() registerAuth?: string; // Register auth method. E.g. 'email'. + + @ViewChild(CoreCompileHtmlComponent) compileComponent: CoreCompileHtmlComponent; + + content = ''; // Content. + jsData; + protected componentObserver: Subscription; + + constructor(protected siteAddonsProvider: CoreSiteAddonsProvider) { } + + /** + * Component being initialized. + */ + ngOnInit(): void { + + // Pass the input data to the component. + this.jsData = { + field: this.field, + signup: this.signup, + edit: this.edit, + form: this.form, + registerAuth: this.registerAuth + }; + + if (this.field) { + // Retrieve the handler data. + const handler = this.siteAddonsProvider.getSiteAddonHandler(this.field.type || this.field.datatype), + handlerSchema = handler && handler.handlerSchema; + + if (handlerSchema) { + // Load first template. + if (handlerSchema.methodTemplates && handlerSchema.methodTemplates.length) { + this.content = handler.handlerSchema.methodTemplates[0].html; + } + + // Wait for the instance to be created. + if (this.compileComponent && this.compileComponent.componentObservable && + handlerSchema.methodJSResult && handlerSchema.methodJSResult.componentInit) { + this.componentObserver = this.compileComponent.componentObservable.subscribe((instance) => { + if (instance) { + // Instance created, call component init. + handlerSchema.methodJSResult.componentInit.apply(instance); + } + }); + } + } + } + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + this.componentObserver && this.componentObserver.unsubscribe(); + } +} diff --git a/src/core/siteaddons/providers/helper.ts b/src/core/siteaddons/providers/helper.ts index 1bfa8336a..abf076fdb 100644 --- a/src/core/siteaddons/providers/helper.ts +++ b/src/core/siteaddons/providers/helper.ts @@ -29,10 +29,14 @@ import { } from '../../course/providers/options-delegate'; import { CoreCourseFormatDelegate, CoreCourseFormatHandler } from '../../course/providers/format-delegate'; import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../user/providers/user-delegate'; +import { + CoreUserProfileFieldDelegate, CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData +} from '../../user/providers/user-profile-field-delegate'; import { CoreDelegateHandler } from '../../../classes/delegate'; import { CoreSiteAddonsModuleIndexComponent } from '../components/module-index/module-index'; import { CoreSiteAddonsCourseOptionComponent } from '../components/course-option/course-option'; import { CoreSiteAddonsCourseFormatComponent } from '../components/course-format/course-format'; +import { CoreSiteAddonsUserProfileFieldComponent } from '../components/user-profile-field/user-profile-field'; import { CoreSiteAddonsProvider } from './siteaddons'; import { CoreSiteAddonsModulePrefetchHandler } from '../classes/module-prefetch-handler'; import { CoreCompileProvider } from '../../compile/providers/compile'; @@ -57,7 +61,7 @@ export class CoreSiteAddonsHelperProvider { private siteAddonsProvider: CoreSiteAddonsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, private coursesProvider: CoreCoursesProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, - private courseFormatDelegate: CoreCourseFormatDelegate) { + private courseFormatDelegate: CoreCourseFormatDelegate, private profileFieldDelegate: CoreUserProfileFieldDelegate) { this.logger = logger.getInstance('CoreSiteAddonsHelperProvider'); } @@ -65,20 +69,31 @@ export class CoreSiteAddonsHelperProvider { * Bootstrap a handler if it has some bootstrap method. * * @param {any} addon Data of the addon. - * @param {string} handlerName Name of the handler in the addon. * @param {any} handlerSchema Data about the handler. * @return {Promise} Promise resolved when done. It returns the results of the getContent call and the data returned by * the bootstrap JS (if any). */ - protected bootstrapHandler(addon: any, handlerName: string, handlerSchema: any): Promise { + protected bootstrapHandler(addon: any, handlerSchema: any): Promise { if (!handlerSchema.bootstrap) { return Promise.resolve({}); } + return this.executeMethodAndJS(addon, handlerSchema.bootstrap); + } + + /** + * Execute a get_content method and run its javascript (if any). + * + * @param {any} addon Data of the addon. + * @param {string} method The method to call. + * @return {Promise} Promise resolved when done. It returns the results of the getContent call and the data returned by + * the JS (if any). + */ + protected executeMethodAndJS(addon: any, method: string): Promise { const siteId = this.sitesProvider.getCurrentSiteId(), preSets = {getFromCache: false}; // Try to ignore cache. - return this.siteAddonsProvider.getContent(addon.component, handlerSchema.bootstrap, {}, preSets).then((result) => { + return this.siteAddonsProvider.getContent(addon.component, method, {}, preSets).then((result) => { if (!result.javascript || this.sitesProvider.getCurrentSiteId() != siteId) { // No javascript or site has changed, stop. return result; @@ -269,43 +284,52 @@ export class CoreSiteAddonsHelperProvider { this.loadHandlerLangStrings(addon, handlerName, handlerSchema); // Wait for the bootstrap JS to be executed. - return this.bootstrapHandler(addon, handlerName, handlerSchema).then((result) => { - let uniqueName; + return this.bootstrapHandler(addon, handlerSchema).then((result) => { + let promise; switch (handlerSchema.delegate) { case 'CoreMainMenuDelegate': - uniqueName = this.registerMainMenuHandler(addon, handlerName, handlerSchema, result); + promise = Promise.resolve(this.registerMainMenuHandler(addon, handlerName, handlerSchema, result)); break; case 'CoreCourseModuleDelegate': - uniqueName = this.registerModuleHandler(addon, handlerName, handlerSchema, result); + promise = Promise.resolve(this.registerModuleHandler(addon, handlerName, handlerSchema, result)); break; case 'CoreUserDelegate': - uniqueName = this.registerUserProfileHandler(addon, handlerName, handlerSchema, result); + promise = Promise.resolve(this.registerUserProfileHandler(addon, handlerName, handlerSchema, result)); break; case 'CoreCourseOptionsDelegate': - uniqueName = this.registerCourseOptionHandler(addon, handlerName, handlerSchema, result); + promise = Promise.resolve(this.registerCourseOptionHandler(addon, handlerName, handlerSchema, result)); break; case 'CoreCourseFormatDelegate': - uniqueName = this.registerCourseFormatHandler(addon, handlerName, handlerSchema, result); + promise = Promise.resolve(this.registerCourseFormatHandler(addon, handlerName, handlerSchema, result)); + break; + + case 'CoreUserProfileFieldDelegate': + promise = Promise.resolve(this.registerUserProfileFieldHandler(addon, handlerName, handlerSchema, result)); break; default: // Nothing to do. + promise = Promise.resolve(); } - if (uniqueName) { - // Store the handler data. - this.siteAddonsProvider.setSiteAddonHandler(uniqueName, { - addon: addon, - handlerName: handlerName, - handlerSchema: handlerSchema, - bootstrapResult: result - }); - } + return promise.then((uniqueName) => { + if (uniqueName) { + // Store the handler data. + this.siteAddonsProvider.setSiteAddonHandler(uniqueName, { + addon: addon, + handlerName: handlerName, + handlerSchema: handlerSchema, + bootstrapResult: result + }); + } + }); + }).catch((err) => { + this.logger.error('Error executing bootstrap method', handlerSchema.bootstrap, err); }); } @@ -527,12 +551,18 @@ export class CoreSiteAddonsHelperProvider { const uniqueName = this.siteAddonsProvider.getHandlerUniqueName(addon, handlerName), baseHandler = this.getBaseHandler(uniqueName), prefixedTitle = this.getHandlerPrefixedString(baseHandler.name, handlerSchema.displaydata.title); - let userHandler: CoreUserProfileHandler; + let userHandler: CoreUserProfileHandler, + type = handlerSchema.type; + + // Only support TYPE_COMMUNICATION and TYPE_NEW_PAGE. + if (type != CoreUserDelegate.TYPE_COMMUNICATION) { + type = CoreUserDelegate.TYPE_NEW_PAGE; + } // Extend the base handler, adding the properties required by the delegate. userHandler = Object.assign(baseHandler, { priority: handlerSchema.priority, - type: handlerSchema.type, + type: type, isEnabledForUser: (user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise => { // First check if it's enabled for the user. const enabledForUser = this.isHandlerEnabledForUser(user.id, handlerSchema.restricttocurrentuser, @@ -572,4 +602,62 @@ export class CoreSiteAddonsHelperProvider { return uniqueName; } + + /** + * Given a handler in an addon, register it in the user profile field delegate. + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler in the addon. + * @param {any} handlerSchema Data about the handler. + * @param {any} bootstrapResult Result of the bootstrap WS call. + * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + */ + protected registerUserProfileFieldHandler(addon: any, handlerName: string, handlerSchema: any, bootstrapResult: any) + : string | Promise { + if (!handlerSchema || !handlerSchema.method) { + // Required data not provided, stop. + return; + } + + // Execute the main method and its JS. The template returned will be used in the profile field component. + return this.executeMethodAndJS(addon, handlerSchema.method).then((result) => { + // Create the base handler. + const fieldType = addon.component.replace('profilefield_', ''), + baseHandler = this.getBaseHandler(fieldType); + let fieldHandler: CoreUserProfileFieldHandler; + + // Store in handlerSchema some data required by the component. + handlerSchema.methodTemplates = result.templates; + handlerSchema.methodJSResult = result.jsResult; + + // Extend the base handler, adding the properties required by the delegate. + fieldHandler = Object.assign(baseHandler, { + getData: (field: any, signup: boolean, registerAuth: string, formValues: any): + Promise | CoreUserProfileFieldHandlerData => { + if (result && result.jsResult && result.jsResult.getData) { + // The JS of the main method implements the getData function, use it. + return result.jsResult.getData(); + } + + // No getData function implemented, use a default behaviour. + const name = 'profile_field_' + field.shortname; + + return { + type: field.type || field.datatype, + name: name, + value: formValues[name] + }; + }, + getComponent: (injector: Injector): any | Promise => { + return CoreSiteAddonsUserProfileFieldComponent; + } + }); + + this.profileFieldDelegate.registerHandler(fieldHandler); + + return fieldType; + }).catch((err) => { + this.logger.error('Error executing main method', handlerSchema.method, err); + }); + } }