MOBILE-2333 siteaddons: Support creating new components

Now site addons can return a ComponentRef instead of the component class in the getComponent functions of the delegates. This means they can create a new component on the fly using CoreCompileProvider.instantiateDynamicComponent
main
Dani Palou 2018-03-02 15:25:00 +01:00
parent ffa5a53fc6
commit d98088f6a6
39 changed files with 353 additions and 200 deletions

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, Optional } from '@angular/core';
import { NavParams, NavController, Content, PopoverController } from 'ionic-angular';
import { Content, PopoverController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app';
import { CoreDomUtilsProvider } from '@providers/utils/dom';

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { NavController, NavOptions } from 'ionic-angular';
import { AddonModBookProvider } from './book';
import { AddonModBookIndexComponent } from '../components/index/index';
@ -60,12 +60,14 @@ export class AddonModBookModuleHandler implements CoreCourseModuleHandler {
/**
* Get the component to render the module. This is needed to support singleactivity course format.
* The component returned must implement CoreCourseModuleMainComponent.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {any} module The module object.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getMainComponent(course: any, module: any): any {
getMainComponent(injector: Injector, course: any, module: any): any | Promise<any> {
return AddonModBookIndexComponent;
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
/**
@ -58,12 +58,14 @@ export class AddonModLabelModuleHandler implements CoreCourseModuleHandler {
/**
* Get the component to render the module. This is needed to support singleactivity course format.
* The component returned must implement CoreCourseModuleMainComponent.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {any} module The module object.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getMainComponent(course: any, module: any): any {
getMainComponent(injector: Injector, course: any, module: any): any | Promise<any> {
// There's no need to implement this because label cannot be used in singleactivity course format.
}
}

View File

@ -12,7 +12,7 @@
// 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 } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
'../../../../core/user/providers/user-profile-field-delegate';
import { AddonUserProfileFieldCheckboxComponent } from '../component/checkbox';
@ -60,10 +60,12 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel
/**
* 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.
*
* @return {any} The component to use, undefined if not found.
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): any {
getComponent(injector: Injector): any | Promise<any> {
return AddonUserProfileFieldCheckboxComponent;
}
}

View File

@ -12,7 +12,7 @@
// 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 } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
'../../../../core/user/providers/user-profile-field-delegate';
import { AddonUserProfileFieldDatetimeComponent } from '../component/datetime';
@ -62,10 +62,12 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel
/**
* 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.
*
* @return {any} The component to use, undefined if not found.
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): any {
getComponent(injector: Injector): any | Promise<any> {
return AddonUserProfileFieldDatetimeComponent;
}
}

View File

@ -12,7 +12,7 @@
// 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 } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
'../../../../core/user/providers/user-profile-field-delegate';
import { AddonUserProfileFieldMenuComponent } from '../component/menu';
@ -60,10 +60,12 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan
/**
* 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.
*
* @return {any} The component to use, undefined if not found.
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): any {
getComponent(injector: Injector): any | Promise<any> {
return AddonUserProfileFieldMenuComponent;
}
}

View File

@ -12,7 +12,7 @@
// 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 } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
'../../../../core/user/providers/user-profile-field-delegate';
import { AddonUserProfileFieldTextComponent } from '../component/text';
@ -57,10 +57,12 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan
/**
* 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.
*
* @return {any} The component to use, undefined if not found.
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): any {
getComponent(injector: Injector): any | Promise<any> {
return AddonUserProfileFieldTextComponent;
}
}

View File

@ -12,7 +12,7 @@
// 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 } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
'../../../../core/user/providers/user-profile-field-delegate';
import { AddonUserProfileFieldTextareaComponent } from '../component/textarea';
@ -66,10 +66,12 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel
/**
* 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.
*
* @return {any} The component to use, undefined if not found.
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): any {
getComponent(injector: Injector): any | Promise<any> {
return AddonUserProfileFieldTextareaComponent;
}
}

View File

@ -13,9 +13,10 @@
// limitations under the License.
import {
Component, Input, ViewChild, OnInit, OnChanges, DoCheck, ViewContainerRef, ComponentFactoryResolver,
KeyValueDiffers, SimpleChange
Component, Input, ViewChild, OnInit, OnChanges, DoCheck, ViewContainerRef, ComponentFactoryResolver, ComponentRef,
KeyValueDiffers, SimpleChange, ChangeDetectorRef, Optional, ElementRef
} from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreLoggerProvider } from '@providers/logger';
/**
@ -39,6 +40,10 @@ import { CoreLoggerProvider } from '@providers/logger';
*
* 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 addons, 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.".
*/
@ -62,7 +67,8 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck {
protected logger: any;
protected differ: any; // To detect changes in the data input.
constructor(logger: CoreLoggerProvider, private factoryResolver: ComponentFactoryResolver, differs: KeyValueDiffers) {
constructor(logger: CoreLoggerProvider, protected factoryResolver: ComponentFactoryResolver, differs: KeyValueDiffers,
@Optional() protected navCtrl: NavController, protected cdr: ChangeDetectorRef, protected element: ElementRef) {
this.logger = logger.getInstance('CoreDynamicComponent');
this.differ = differs.find([]).create();
}
@ -128,21 +134,32 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck {
return true;
}
try {
// Create the component and add it to the container.
const factory = this.factoryResolver.resolveComponentFactory(this.component),
componentRef = this.container.createComponent(factory);
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.instance = componentRef.instance;
// This feature is usually meant for site addons. 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),
componentRef = this.container.createComponent(factory);
this.setInputData();
this.instance = componentRef.instance;
} catch (ex) {
this.logger.error('Error creating component', ex);
return true;
} catch (ex) {
this.logger.error('Error creating component', ex);
return false;
return false;
}
}
this.setInputData();
return true;
}
/**

View File

@ -14,7 +14,7 @@
// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo
import { Component, ViewChild, Input, ElementRef, OnInit } from '@angular/core';
import { Component, ViewChild, Input, ElementRef, OnInit, Optional } from '@angular/core';
import { NavController, Nav } from 'ionic-angular';
/**
@ -54,7 +54,7 @@ export class CoreSplitViewComponent implements OnInit {
// Empty placeholder for the 'detail' page.
detailPage: any = null;
constructor(private masterNav: NavController, element: ElementRef) {
constructor(@Optional() private masterNav: NavController, element: ElementRef) {
this.element = element.nativeElement;
}

View File

@ -13,24 +13,12 @@
// limitations under the License.
import {
Component, NgModule, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, Compiler, ViewChild, ComponentRef,
SimpleChange, ChangeDetectorRef
Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ViewChild, ComponentRef, SimpleChange, ChangeDetectorRef,
ElementRef, Optional
} from '@angular/core';
import { IonicModule, NavController } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { NavController } from 'ionic-angular';
import { CoreCompileProvider } from '../../../compile/providers/compile';
// Import all modules that define components, directives and pipes.
import { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
import { CorePipesModule } from '../../../../pipes/pipes.module';
import { CoreCourseComponentsModule } from '../../../course/components/components.module';
import { CoreCourseDirectivesModule } from '../../../course/directives/directives.module';
import { CoreCoursesComponentsModule } from '../../../courses/components/components.module';
import { CoreSiteAddonsDirectivesModule } from '../../../siteaddons/directives/directives.module';
import { CoreSiteHomeComponentsModule } from '../../../sitehome/components/components.module';
import { CoreUserComponentsModule } from '../../../user/components/components.module';
/**
* This component has a behaviour similar to $compile for AngularJS. Given an HTML code, it will compile it so all its
* components and directives are instantiated.
@ -51,13 +39,6 @@ import { CoreUserComponentsModule } from '../../../user/components/components.mo
template: '<ng-container #dynamicComponent></ng-container>'
})
export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
// List of imports for dynamic module. Since the template can have any component we need to import all core components modules.
protected IMPORTS = [
IonicModule, TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, CorePipesModule,
CoreCourseComponentsModule, CoreCoursesComponentsModule, CoreSiteHomeComponentsModule, CoreUserComponentsModule,
CoreCourseDirectivesModule, CoreSiteAddonsDirectivesModule
];
@Input() text: string; // The HTML text to display.
@Input() javascript: string; // The Javascript to execute in the component.
@Input() jsData; // Data to pass to the fake component.
@ -66,9 +47,12 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
@ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
protected componentRef: ComponentRef<any>;
protected element;
constructor(protected compileProvider: CoreCompileProvider, protected compiler: Compiler,
protected cdr: ChangeDetectorRef, protected navCtrl: NavController) { }
constructor(protected compileProvider: CoreCompileProvider, protected cdr: ChangeDetectorRef, element: ElementRef,
@Optional() protected navCtrl: NavController) {
this.element = element.nativeElement;
}
/**
* Detect changes on input properties.
@ -76,26 +60,14 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
if ((changes.text || changes.javascript) && this.text) {
// Create a new component and a new module.
const component = this.createComponent(),
module = NgModule({imports: this.IMPORTS, declarations: [component]})(class {});
// Compile the module and the component.
this.compiler.compileModuleAndAllComponentsAsync(module).then((factories) => {
// Search the factory of the component we just created.
let componentFactory;
for (const i in factories.componentFactories) {
const factory = factories.componentFactories[i];
if (factory.componentType == component) {
componentFactory = factory;
break;
}
}
this.compileProvider.createAndCompileComponent(this.text, this.getComponentClass()).then((factory) => {
// Destroy previous components.
this.componentRef && this.componentRef.destroy();
// Create the component.
this.componentRef = this.container.createComponent(componentFactory);
if (factory) {
// Create the component.
this.componentRef = this.container.createComponent(factory);
}
});
}
}
@ -108,20 +80,16 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
}
/**
* Create a dynamic component to compile the HTML and run the javascript.
* Get a class that defines the dynamic component.
*
* @return {any} The component class.
*/
protected createComponent(): any {
protected getComponentClass(): any {
// tslint:disable: no-this-assignment
const compileInstance = this;
// Create the component, using the text as the template.
return Component({
template: this.text
})
(class CoreCompileHtmlFakeComponent implements OnInit {
return class CoreCompileHtmlFakeComponent implements OnInit {
constructor() {
// If there is some javascript to run, prepare the instance.
if (compileInstance.javascript) {
@ -130,7 +98,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
// Add some more components and classes.
this['ChangeDetectorRef'] = compileInstance.cdr;
this['NavController'] = compileInstance.navCtrl;
this['componentContainer'] = compileInstance.container;
this['componentContainer'] = compileInstance.element;
// Add the data passed to the component.
for (const name in compileInstance.jsData) {
@ -145,6 +113,6 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
compileInstance.compileProvider.executeJavascript(this, compileInstance.javascript);
}
}
});
};
}
}

View File

@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, Component, NgModule, Compiler, ComponentFactory, ComponentRef, NgModuleRef } from '@angular/core';
import {
Platform, ActionSheetController, AlertController, LoadingController, ModalController, PopoverController, ToastController
Platform, ActionSheetController, AlertController, LoadingController, ModalController, PopoverController, ToastController,
IonicModule
} from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { TranslateService, TranslateModule } from '@ngx-translate/core';
import { CoreLoggerProvider } from '../../../providers/logger';
// Import core providers.
@ -55,6 +56,24 @@ import { CoreContentLinksModuleGradeHandler } from '../../contentlinks/classes/m
import { CoreContentLinksModuleIndexHandler } from '../../contentlinks/classes/module-index-handler';
import { CoreCourseModulePrefetchHandlerBase } from '../../course/classes/module-prefetch-handler';
// Import all modules that define components, directives and pipes.
import { CoreComponentsModule } from '../../../components/components.module';
import { CoreDirectivesModule } from '../../../directives/directives.module';
import { CorePipesModule } from '../../../pipes/pipes.module';
import { CoreCourseComponentsModule } from '../../course/components/components.module';
import { CoreCourseDirectivesModule } from '../../course/directives/directives.module';
import { CoreCoursesComponentsModule } from '../../courses/components/components.module';
import { CoreSiteAddonsDirectivesModule } from '../../siteaddons/directives/directives.module';
import { CoreSiteHomeComponentsModule } from '../../sitehome/components/components.module';
import { CoreUserComponentsModule } from '../../user/components/components.module';
// Import some components listed in entryComponents so they can be injected dynamically.
import { CoreCourseUnsupportedModuleComponent } from '../../course/components/unsupported-module/unsupported-module';
import { CoreCourseFormatSingleActivityComponent } from '../../course/formats/singleactivity/components/singleactivity';
import { CoreSiteAddonsModuleIndexComponent } from '../../siteaddons/components/module-index/module-index';
import { CoreSiteAddonsCourseOptionComponent } from '../../siteaddons/components/course-option/course-option';
import { CoreSiteAddonsCourseFormatComponent } from '../../siteaddons/components/course-format/course-format';
/**
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
*/
@ -69,10 +88,46 @@ export class CoreCompileProvider {
ModalController, PopoverController, ToastController, FormBuilder
];
constructor(protected injector: Injector, logger: CoreLoggerProvider) {
// List of imports for dynamic module. Since the template can have any component we need to import all core components modules.
protected IMPORTS = [
IonicModule, TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, CorePipesModule,
CoreCourseComponentsModule, CoreCoursesComponentsModule, CoreSiteHomeComponentsModule, CoreUserComponentsModule,
CoreCourseDirectivesModule, CoreSiteAddonsDirectivesModule
];
constructor(protected injector: Injector, logger: CoreLoggerProvider, protected compiler: Compiler) {
this.logger = logger.getInstance('CoreCompileProvider');
}
/**
* Create and compile a dynamic component.
*
* @param {string} template The template of the component.
* @param {any} componentClass The JS class of the component.
* @return {Promise<ComponentFactory<any>>} Promise resolved with the factory to instantiate the component.
*/
createAndCompileComponent(template: string, componentClass: any): Promise<ComponentFactory<any>> {
// Create the component using the template and the class.
const component = Component({
template: template
})
(componentClass);
// Now create the module containing the component.
const module = NgModule({imports: this.IMPORTS, declarations: [component]})(class {});
// Compile the module and the component.
return this.compiler.compileModuleAndAllComponentsAsync(module).then((factories) => {
// Search and return the factory of the component we just created.
for (const i in factories.componentFactories) {
const factory = factories.componentFactories[i];
if (factory.componentType == component) {
return factory;
}
}
});
}
/**
* Eval some javascript using the context of the function.
*
@ -124,6 +179,9 @@ export class CoreCompileProvider {
}
}
// Inject current service.
instance['CoreCompileProvider'] = this;
// Add some final classes.
instance['injector'] = this.injector;
instance['Validators'] = Validators;
@ -138,5 +196,29 @@ export class CoreCompileProvider {
instance['CoreContentLinksModuleGradeHandler'] = CoreContentLinksModuleGradeHandler;
instance['CoreContentLinksModuleIndexHandler'] = CoreContentLinksModuleIndexHandler;
instance['CoreCourseModulePrefetchHandlerBase'] = CoreCourseModulePrefetchHandlerBase;
instance['CoreCourseUnsupportedModuleComponent'] = CoreCourseUnsupportedModuleComponent;
instance['CoreCourseFormatSingleActivityComponent'] = CoreCourseFormatSingleActivityComponent;
instance['CoreSiteAddonsModuleIndexComponent'] = CoreSiteAddonsModuleIndexComponent;
instance['CoreSiteAddonsCourseOptionComponent'] = CoreSiteAddonsCourseOptionComponent;
instance['CoreSiteAddonsCourseFormatComponent'] = CoreSiteAddonsCourseFormatComponent;
}
/**
* Instantiate a dynamic component.
*
* @param {string} template The template of the component.
* @param {any} componentClass The JS class of the component.
* @param {Injector} [injector] The injector to use. It's recommended to pass it so NavController and similar can be injected.
* @return {Promise<ComponentRef<any>>} Promise resolved with the component instance.
*/
instantiateDynamicComponent(template: string, componentClass: any, injector?: Injector): Promise<ComponentRef<any>> {
injector = injector || this.injector;
return this.createAndCompileComponent(template, componentClass).then((factory) => {
if (factory) {
// Create and return the component.
return factory.create(injector, undefined, undefined, injector.get(NgModuleRef));
}
});
}
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
import {
Component, Input, OnInit, OnChanges, OnDestroy, SimpleChange, Output, EventEmitter, ViewChildren, QueryList
Component, Input, OnInit, OnChanges, OnDestroy, SimpleChange, Output, EventEmitter, ViewChildren, QueryList, Injector
} from '@angular/core';
import { Content } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
@ -69,7 +69,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
protected sectionStatusObserver;
constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService,
constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService, private injector: Injector,
private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider,
eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private content: Content,
prefetchDelegate: CoreCourseModulePrefetchDelegate) {
@ -194,19 +194,29 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
protected getComponents(): void {
if (this.course) {
if (!this.courseFormatComponent) {
this.courseFormatComponent = this.cfDelegate.getCourseFormatComponent(this.course);
this.cfDelegate.getCourseFormatComponent(this.injector, this.course).then((component) => {
this.courseFormatComponent = component;
});
}
if (!this.courseSummaryComponent) {
this.courseSummaryComponent = this.cfDelegate.getCourseSummaryComponent(this.course);
this.cfDelegate.getCourseSummaryComponent(this.injector, this.course).then((component) => {
this.courseSummaryComponent = component;
});
}
if (!this.sectionSelectorComponent) {
this.sectionSelectorComponent = this.cfDelegate.getSectionSelectorComponent(this.course);
this.cfDelegate.getSectionSelectorComponent(this.injector, this.course).then((component) => {
this.sectionSelectorComponent = component;
});
}
if (!this.singleSectionComponent) {
this.singleSectionComponent = this.cfDelegate.getSingleSectionComponent(this.course);
this.cfDelegate.getSingleSectionComponent(this.injector, this.course).then((component) => {
this.singleSectionComponent = component;
});
}
if (!this.allSectionsComponent) {
this.allSectionsComponent = this.cfDelegate.getAllSectionsComponent(this.course);
this.cfDelegate.getAllSectionsComponent(this.injector, this.course).then((component) => {
this.allSectionsComponent = component;
});
}
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
@ -67,7 +67,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
protected prefetchHandler: CoreCourseModulePrefetchHandler;
protected statusObserver;
constructor(protected navCtrl: NavController, protected prefetchDelegate: CoreCourseModulePrefetchDelegate,
constructor(@Optional() protected navCtrl: NavController, protected prefetchDelegate: CoreCourseModulePrefetchDelegate,
protected domUtils: CoreDomUtilsProvider, protected courseHelper: CoreCourseHelperProvider,
protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider) {
this.completionChanged = new EventEmitter();

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnChanges, SimpleChange, ViewChild } from '@angular/core';
import { Component, Input, OnChanges, SimpleChange, ViewChild, Injector } from '@angular/core';
import { CoreCourseModuleDelegate } from '../../../providers/module-delegate';
import { CoreCourseUnsupportedModuleComponent } from '../../../components/unsupported-module/unsupported-module';
import { CoreDynamicComponent } from '../../../../../components/dynamic-component/dynamic-component';
@ -36,7 +36,7 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
componentClass: any; // The class of the component to render.
data: any = {}; // Data to pass to the component.
constructor(private moduleDelegate: CoreCourseModuleDelegate) { }
constructor(private moduleDelegate: CoreCourseModuleDelegate, private injector: Injector) { }
/**
* Detect changes on input properties.
@ -47,8 +47,9 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
const module = this.sections[0] && this.sections[0].modules && this.sections[0].modules[0];
if (module && !this.componentClass) {
// We haven't obtained the class yet. Get it now.
this.componentClass = this.moduleDelegate.getMainComponent(this.course, module) ||
CoreCourseUnsupportedModuleComponent;
this.moduleDelegate.getMainComponent(this.injector, this.course, module).then((component) => {
this.componentClass = component || CoreCourseUnsupportedModuleComponent;
});
}
this.data.courseId = this.course.id;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreCourseFormatHandler } from '../../../providers/format-delegate';
import { CoreCourseFormatSingleActivityComponent } from '../components/singleactivity';
@ -86,11 +86,13 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa
* Return the Component to use to display the course format instead of using the default one.
* Use it if you want to display a format completely different from the default one.
* If you want to customize the default format there are several methods to customize parts of it.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getCourseFormatComponent(course: any): any {
getCourseFormatComponent(injector: Injector, course: any): any | Promise<any> {
return CoreCourseFormatSingleActivityComponent;
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, OnDestroy } from '@angular/core';
import { Component, ViewChild, OnDestroy, Injector } from '@angular/core';
import { IonicPage, NavParams, Content, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
@ -63,7 +63,7 @@ export class CoreCourseSectionPage implements OnDestroy {
private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate,
private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider,
private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider,
sitesProvider: CoreSitesProvider, private navCtrl: NavController,
sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector,
private prefetchDelegate: CoreCourseModulePrefetchDelegate) {
this.course = navParams.get('course');
this.sectionId = navParams.get('sectionId');
@ -191,7 +191,8 @@ export class CoreCourseSectionPage implements OnDestroy {
}));
// Load the course handlers.
promises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.course, refresh, false).then((handlers) => {
promises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, this.course, refresh, false)
.then((handlers) => {
// Add the courseId to the handler component data.
handlers.forEach((handler) => {
handler.data.componentData = handler.data.componentData || {};

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger';
@ -85,44 +85,54 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
* Return the Component to use to display the course format instead of using the default one.
* Use it if you want to display a format completely different from the default one.
* If you want to customize the default format there are several methods to customize parts of it.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getCourseFormatComponent?(course: any): any;
getCourseFormatComponent?(injector: Injector, course: any): any | Promise<any>;
/**
* Return the Component to use to display the course summary inside the default course format.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getCourseSummaryComponent?(course: any): any;
getCourseSummaryComponent?(injector: Injector, course: any): any | Promise<any>;
/**
* Return the Component to use to display the section selector inside the default course format.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getSectionSelectorComponent?(course: any): any;
getSectionSelectorComponent?(injector: Injector, course: any): any | Promise<any>;
/**
* Return the Component to use to display a single section. This component will only be used if the user is viewing a
* single section. If all the sections are displayed at once then it won't be used.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getSingleSectionComponent?(course: any): any;
getSingleSectionComponent?(injector: Injector, course: any): any | Promise<any>;
/**
* Return the Component to use to display all sections in a course.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getAllSectionsComponent?(course: any): any;
getAllSectionsComponent?(injector: Injector, course: any): any | Promise<any>;
/**
* Invalidate the data required to load the course format.
@ -199,31 +209,40 @@ export class CoreCourseFormatDelegate extends CoreDelegate {
/**
* Get the component to use to display all sections in a course.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getAllSectionsComponent(course: any): any {
return this.executeFunction(course.format, 'getAllSectionsComponent', [course]);
getAllSectionsComponent(injector: Injector, course: any): Promise<any> {
return Promise.resolve(this.executeFunction(course.format, 'getAllSectionsComponent', [injector, course])).catch((e) => {
this.logger.error('Error getting all sections component', e);
});
}
/**
* Get the component to use to display a course format.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getCourseFormatComponent(course: any): any {
return this.executeFunction(course.format, 'getCourseFormatComponent', [course]);
getCourseFormatComponent(injector: Injector, course: any): Promise<any> {
return Promise.resolve(this.executeFunction(course.format, 'getCourseFormatComponent', [injector, course])).catch((e) => {
this.logger.error('Error getting course format component', e);
});
}
/**
* Get the component to use to display the course summary in the default course format.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getCourseSummaryComponent(course: any): any {
return this.executeFunction(course.format, 'getCourseSummaryComponent', [course]);
getCourseSummaryComponent(injector: Injector, course: any): Promise<any> {
return Promise.resolve(this.executeFunction(course.format, 'getCourseSummaryComponent', [injector, course])).catch((e) => {
this.logger.error('Error getting course summary component', e);
});
}
/**
@ -259,22 +278,29 @@ export class CoreCourseFormatDelegate extends CoreDelegate {
/**
* Get the component to use to display the section selector inside the default course format.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getSectionSelectorComponent(course: any): any {
return this.executeFunction(course.format, 'getSectionSelectorComponent', [course]);
getSectionSelectorComponent(injector: Injector, course: any): Promise<any> {
return Promise.resolve(this.executeFunction(course.format, 'getSectionSelectorComponent', [injector, course]))
.catch((e) => {
this.logger.error('Error getting section selector component', e);
});
}
/**
* Get the component to use to display a single section. This component will only be used if the user is viewing
* a single section. If all the sections are displayed at once then it won't be used.
*
* @param {Injector} injector Injector.
* @param {any} course The course to render.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getSingleSectionComponent(course: any): any {
return this.executeFunction(course.format, 'getSingleSectionComponent', [course]);
getSingleSectionComponent(injector: Injector, course: any): Promise<any> {
return Promise.resolve(this.executeFunction(course.format, 'getSingleSectionComponent', [injector, course])).catch((e) => {
this.logger.error('Error getting single section component', e);
});
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app';
@ -120,7 +120,7 @@ export class CoreCourseHelperProvider {
private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider,
private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider,
private eventsProvider: CoreEventsProvider, private fileHelper: CoreFileHelperProvider,
private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider) { }
private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private injector: Injector) { }
/**
* This function treats every module on the sections provided to load the handler data, treat completion
@ -275,7 +275,7 @@ export class CoreCourseHelperProvider {
if (courseHandlers) {
promise = Promise.resolve(courseHandlers);
} else {
promise = this.courseOptionsDelegate.getHandlersToDisplay(course);
promise = this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course);
}
return promise.then((handlers: CoreCourseOptionsHandlerToDisplay[]) => {
@ -323,7 +323,7 @@ export class CoreCourseHelperProvider {
subPromises.push(this.courseProvider.getSections(course.id, false, true).then((courseSections) => {
sections = courseSections;
}));
subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(course).then((cHandlers) => {
subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course).then((cHandlers) => {
handlers = cHandlers;
}));

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { NavController, NavOptions } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger';
@ -38,12 +38,14 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
/**
* Get the component to render the module. This is needed to support singleactivity course format.
* The component returned must implement CoreCourseModuleMainComponent.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {any} module The module object.
* @return {any} The component to use, undefined if not found.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getMainComponent(course: any, module: any): any;
getMainComponent(injector: Injector, course: any, module: any): any | Promise<any>;
}
/**
@ -176,17 +178,17 @@ export class CoreCourseModuleDelegate extends CoreDelegate {
/**
* Get the component to render the module.
*
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {any} module The module object.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getMainComponent?(course: any, module: any): any {
getMainComponent(injector: Injector, course: any, module: any): Promise<any> {
const handler = this.enabledHandlers[module.modname];
if (handler && handler.getMainComponent) {
const component = handler.getMainComponent(course, module);
if (component) {
return component;
}
return Promise.resolve(handler.getMainComponent(injector, course, module)).catch((err) => {
this.logger.error('Error getting main component', err);
});
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger';
@ -45,10 +45,11 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
/**
* Returns the data needed to render the handler.
*
* @param {Injector} injector Injector.
* @param {number} courseId The course ID.
* @return {CoreCourseOptionsHandlerData} Data.
* @return {CoreCourseOptionsHandlerData|Promise<CoreCourseOptionsHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(courseId: number): CoreCourseOptionsHandlerData;
getDisplayData?(injector: Injector, courseId: number): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData>;
/**
* Should invalidate the data to determine if the handler is enabled for a certain course.
@ -243,6 +244,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
* Get the list of handlers that should be displayed for a course.
* This function should be called only when the handlers need to be displayed, since it can call several WebServices.
*
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {boolean} [refresh] True if it should refresh the list.
* @param {boolean} [isGuest] Whether it's guest.
@ -250,7 +252,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<CoreCourseOptionsHandlerToDisplay[]>} Promise resolved with array of handlers.
*/
getHandlersToDisplay(course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any):
getHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any):
Promise<CoreCourseOptionsHandlerToDisplay[]> {
course.id = parseInt(course.id, 10);
@ -269,14 +271,19 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
// Call getHandlersForAccess to make sure the handlers have been loaded.
return this.getHandlersForAccess(course.id, refresh, accessData, course.navOptions, course.admOptions);
}).then(() => {
const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] = [];
const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] = [],
promises = [];
this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => {
handlersToDisplay.push({
data: handler.getDisplayData(course),
priority: handler.priority,
prefetch: handler.prefetch
});
promises.push(Promise.resolve(handler.getDisplayData(injector, course)).then((data) => {
handlersToDisplay.push({
data: data,
priority: handler.priority,
prefetch: handler.prefetch
});
}).catch((err) => {
this.logger.error('Error getting data for handler', handler.name, err);
}));
});
// Sort them by priority.

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreCoursesProvider } from '../../providers/courses';
@ -31,7 +31,8 @@ import { CoreCoursesProvider } from '../../providers/courses';
export class CoreCoursesCourseListItemComponent implements OnInit {
@Input() course: any; // The course to render.
constructor(private navCtrl: NavController, private translate: TranslateService, private coursesProvider: CoreCoursesProvider) {
constructor(@Optional() private navCtrl: NavController, private translate: TranslateService,
private coursesProvider: CoreCoursesProvider) {
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { Component, Input, OnInit, OnDestroy, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
@ -44,7 +44,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
protected isDestroyed = false;
protected courseStatusObserver;
constructor(private navCtrl: NavController, private courseHelper: CoreCourseHelperProvider,
constructor(@Optional() private navCtrl: NavController, private courseHelper: CoreCourseHelperProvider,
private courseFormatDelegate: CoreCourseFormatDelegate, private domUtils: CoreDomUtilsProvider,
private courseProvider: CoreCourseProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) {
// Listen for status change in course.

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, Output, OnChanges, EventEmitter, SimpleChange } from '@angular/core';
import { Component, Input, Output, OnChanges, EventEmitter, SimpleChange, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
@ -43,9 +43,10 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
next30Days: any[] = [];
future: any[] = [];
constructor(private navCtrl: NavController, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider,
private domUtils: CoreDomUtilsProvider, private sitesProvider: CoreSitesProvider,
private courseProvider: CoreCourseProvider, private contentLinksHelper: CoreContentLinksHelperProvider) {
constructor(@Optional() private navCtrl: NavController, private utils: CoreUtilsProvider,
private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider,
private contentLinksHelper: CoreContentLinksHelperProvider) {
this.loadMore = new EventEmitter();
}

View File

@ -39,8 +39,9 @@ export class CoreGradesCourseComponent {
gradesTable: any;
constructor(private gradesProvider: CoreGradesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
private gradesHelper: CoreGradesHelperProvider, private sitesProvider: CoreSitesProvider, private navCtrl: NavController,
private appProvider: CoreAppProvider, @Optional() private svComponent: CoreSplitViewComponent) {
private gradesHelper: CoreGradesHelperProvider, private sitesProvider: CoreSitesProvider,
@Optional() private navCtrl: NavController, private appProvider: CoreAppProvider,
@Optional() private svComponent: CoreSplitViewComponent) {
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '../../course/providers/options-delegate';
import { CoreCourseProvider } from '../../course/providers/course';
@ -80,9 +80,11 @@ export class CoreGradesCourseOptionHandler implements CoreCourseOptionsHandler {
/**
* Returns the data needed to render the handler.
*
* @return {CoreCourseOptionsHandlerData} Data needed to render the handler.
* @param {Injector} injector Injector.
* @param {number} courseId The course ID.
* @return {CoreCourseOptionsHandlerData|Promise<CoreCourseOptionsHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(): CoreCourseOptionsHandlerData {
getDisplayData(injector: Injector, courseId: number): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
return {
title: 'core.grades.grades',
class: 'core-grades-course-handler',

View File

@ -62,7 +62,7 @@ export class CoreSiteAddonsCallWSNewContentDirective extends CoreSiteAddonsCallW
constructor(element: ElementRef, translate: TranslateService, domUtils: CoreDomUtilsProvider,
siteAddonsProvider: CoreSiteAddonsProvider, @Optional() parentContent: CoreSiteAddonsAddonContentComponent,
protected utils: CoreUtilsProvider, protected navCtrl: NavController) {
protected utils: CoreUtilsProvider, @Optional() protected navCtrl: NavController) {
super(element, translate, domUtils, siteAddonsProvider, parentContent);
}

View File

@ -13,7 +13,6 @@
// limitations under the License.
import { Directive, Input, OnInit, ElementRef, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '../../../providers/utils/dom';
import { CoreUtilsProvider } from '../../../providers/utils/utils';

View File

@ -54,7 +54,7 @@ export class CoreSiteAddonsNewContentDirective implements OnInit {
protected element: HTMLElement;
constructor(element: ElementRef, protected utils: CoreUtilsProvider, protected navCtrl: NavController,
constructor(element: ElementRef, protected utils: CoreUtilsProvider, @Optional() protected navCtrl: NavController,
@Optional() protected parentContent: CoreSiteAddonsAddonContentComponent, protected domUtils: CoreDomUtilsProvider,
protected siteAddonsProvider: CoreSiteAddonsProvider) {
this.element = element.nativeElement || element;

View File

@ -43,6 +43,9 @@ import { CoreCoursesProvider } from '../../courses/providers/courses';
* addons.
*
* This code is split from CoreSiteAddonsProvider to prevent circular dependencies.
*
* @todo: Support ViewChild and similar in site addons. Possible solution: make components and directives inject the instance
* inside the host DOM element?
*/
@Injectable()
export class CoreSiteAddonsHelperProvider {
@ -337,7 +340,7 @@ export class CoreSiteAddonsHelperProvider {
displaySectionSelector: (course: any): boolean => {
return typeof handlerSchema.displaysectionselector != 'undefined' ? handlerSchema.displaysectionselector : true;
},
getCourseFormatComponent: (course: any): any => {
getCourseFormatComponent: (injector: Injector, course: any): any | Promise<any> => {
if (handlerSchema.method) {
return CoreSiteAddonsCourseFormatComponent;
}
@ -377,7 +380,8 @@ export class CoreSiteAddonsHelperProvider {
: boolean | Promise<boolean> => {
return this.isHandlerEnabledForCourse(courseId, handlerSchema.restricttoenrolledcourses, bootstrapResult.restrict);
},
getDisplayData: (courseId: number): CoreCourseOptionsHandlerData => {
getDisplayData: (injector: Injector, courseId: number):
CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> => {
return {
title: prefixedTitle,
class: handlerSchema.displaydata.class,
@ -488,7 +492,7 @@ export class CoreSiteAddonsHelperProvider {
}
};
},
getMainComponent: (course: any, module: any): any => {
getMainComponent: (injector: Injector, course: any, module: any): any | Promise<any> => {
return CoreSiteAddonsModuleIndexComponent;
}
});

View File

@ -144,7 +144,10 @@ export class CoreSiteAddonsProvider {
*/
createDataForJS(bootstrapResult: any, contentResult?: any): any {
// First of all, add the data returned by the bootstrap JS (if any).
const data = this.utils.clone(bootstrapResult.jsResult || {});
let data = this.utils.clone(bootstrapResult.jsResult || {});
if (typeof data == 'boolean') {
data = {};
}
// Now add some data returned by the bootstrap WS call.
data.BOOTSTRAP_TEMPLATES = this.utils.objectToKeyValueMap(bootstrapResult.templates, 'id', 'html');

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, Injector } from '@angular/core';
import { CoreUserProfileFieldDelegate } from '../../providers/user-profile-field-delegate';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -33,13 +33,16 @@ export class CoreUserProfileFieldComponent implements OnInit {
componentClass: any; // The class of the component to render.
data: any = {}; // Data to pass to the component.
constructor(private ufDelegate: CoreUserProfileFieldDelegate, private utilsProvider: CoreUtilsProvider) { }
constructor(private ufDelegate: CoreUserProfileFieldDelegate, private utilsProvider: CoreUtilsProvider,
private injector: Injector) { }
/**
* Component being initialized.
*/
ngOnInit(): void {
this.componentClass = this.ufDelegate.getComponent(this.field, this.signup);
this.ufDelegate.getComponent(this.injector, this.field, this.signup).then((component) => {
this.componentClass = component;
});
this.data.field = this.field;
this.data.edit = this.utilsProvider.isTrueOrOne(this.edit);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '../../course/providers/options-delegate';
import { CoreCourseProvider } from '../../course/providers/course';
@ -79,9 +79,11 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption
/**
* Returns the data needed to render the handler.
*
* @return {CoreCourseOptionsHandlerData} Data needed to render the handler.
* @param {Injector} injector Injector.
* @param {number} courseId The course ID.
* @return {CoreCourseOptionsHandlerData|Promise<CoreCourseOptionsHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(): CoreCourseOptionsHandlerData {
getDisplayData(injector: Injector, courseId: number): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
return {
title: 'core.user.participants',
class: 'core-user-participants-handler',

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
@ -22,10 +22,12 @@ export interface CoreUserProfileFieldHandler extends CoreDelegateHandler {
/**
* 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.
*
* @return {any} The component to use, undefined if not found.
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): any;
getComponent(injector: Injector): any | Promise<any>;
/**
* Get the data to send for the field based on the input data.
@ -75,17 +77,23 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate {
/**
* Get the component to use to display an user field.
*
* @param {Injector} injector Injector.
* @param {any} field User field to get the directive for.
* @param {boolean} signup True if user is in signup page.
* @return {any} The component to use, undefined if not found.
* @return {Promise<any>} Promise resolved with component to use, undefined if not found.
*/
getComponent(field: any, signup: boolean): any {
getComponent(injector: Injector, field: any, signup: boolean): Promise<any> {
const type = field.type || field.datatype;
let result;
if (signup) {
return this.executeFunction(type, 'getComponent');
result = this.executeFunction(type, 'getComponent', [injector]);
} else {
return this.executeFunctionOnEnabled(type, 'getComponent');
result = this.executeFunctionOnEnabled(type, 'getComponent', [injector]);
}
return Promise.resolve(result).catch((err) => {
this.logger.error('Error getting component for field', type, err);
});
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import { Directive, Input, OnInit, ElementRef, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -31,7 +31,7 @@ export class CoreAutoFocusDirective implements OnInit {
protected element: HTMLElement;
constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider,
private navCtrl: NavController) {
@Optional() private navCtrl: NavController) {
this.element = element.nativeElement || element;
}

View File

@ -13,7 +13,6 @@
// limitations under the License.
import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreFileHelperProvider } from '../providers/file-helper';
import { CoreDomUtilsProvider } from '../providers/utils/dom';
import { CoreUtilsProvider } from '../providers/utils/utils';

View File

@ -62,7 +62,7 @@ export class CoreFormatTextDirective implements OnChanges {
private textUtils: CoreTextUtilsProvider, private translate: TranslateService, private platform: Platform,
private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private loggerProvider: CoreLoggerProvider,
private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider,
private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController,
private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController,
@Optional() private content: Content) {
this.element = element.nativeElement;
this.element.classList.add('opacity-hide'); // Hide contents until they're treated.

View File

@ -39,7 +39,7 @@ export class CoreLinkDirective implements OnInit {
constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider,
private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider,
private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController,
private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController,
@Optional() private content: Content) {
// This directive can be added dynamically. In that case, the first param is the anchor HTMLElement.
this.element = element.nativeElement || element;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import { Directive, Input, OnInit, ElementRef, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
/**
@ -27,7 +27,7 @@ export class CoreUserLinkDirective implements OnInit {
protected element: HTMLElement;
constructor(element: ElementRef, private navCtrl: NavController) {
constructor(element: ElementRef, @Optional() private navCtrl: NavController) {
// This directive can be added dynamically. In that case, the first param is the anchor HTMLElement.
this.element = element.nativeElement || element;
}