MOBILE-2333 siteaddons: Run bootstrap JS and pass result to content JS

main
Dani Palou 2018-02-21 16:18:03 +01:00
parent 823ea35b69
commit 8e87a1ade9
19 changed files with 488 additions and 279 deletions

View File

@ -67,6 +67,7 @@ import { CoreUserModule } from '@core/user/user.module';
import { CoreGradesModule } from '@core/grades/grades.module';
import { CoreSettingsModule } from '@core/settings/settings.module';
import { CoreSiteAddonsModule } from '@core/siteaddons/siteaddons.module';
import { CoreCompileModule } from '@core/compile/compile.module';
// Addon modules.
import { AddonCalendarModule } from '@addon/calendar/calendar.module';
@ -146,6 +147,7 @@ export const CORE_PROVIDERS: any[] = [
CoreGradesModule,
CoreSettingsModule,
CoreSiteAddonsModule,
CoreCompileModule,
AddonCalendarModule,
AddonUserProfileFieldModule,
AddonFilesModule,

View File

@ -1,200 +0,0 @@
// (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, NgModule, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, Compiler, ViewChild, ComponentRef, Injector,
SimpleChange, ChangeDetectorRef
} from '@angular/core';
import {
IonicModule, NavController, Platform, ActionSheetController, AlertController, LoadingController, ModalController,
PopoverController, ToastController
} from 'ionic-angular';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { CoreLoggerProvider } from '../../providers/logger';
// Import all modules that define components, directives and pipes.
import { CoreComponentsModule } from '../components.module';
import { CoreDirectivesModule } from '../../directives/directives.module';
import { CorePipesModule } from '../../pipes/pipes.module';
import { CoreCourseComponentsModule } from '../../core/course/components/components.module';
import { CoreCourseDirectivesModule } from '../../core/course/directives/directives.module';
import { CoreCoursesComponentsModule } from '../../core/courses/components/components.module';
import { CoreSiteAddonsDirectivesModule } from '../../core/siteaddons/directives/directives.module';
import { CoreSiteHomeComponentsModule } from '../../core/sitehome/components/components.module';
import { CoreUserComponentsModule } from '../../core/user/components/components.module';
// Import core providers.
import { CORE_PROVIDERS } from '../../app/app.module';
import { CORE_CONTENTLINKS_PROVIDERS } from '../../core/contentlinks/contentlinks.module';
import { CORE_COURSE_PROVIDERS } from '../../core/course/course.module';
import { CORE_COURSES_PROVIDERS } from '../../core/courses/courses.module';
import { CORE_FILEUPLOADER_PROVIDERS } from '../../core/fileuploader/fileuploader.module';
import { CORE_GRADES_PROVIDERS } from '../../core/grades/grades.module';
import { CORE_LOGIN_PROVIDERS } from '../../core/login/login.module';
import { CORE_MAINMENU_PROVIDERS } from '../../core/mainmenu/mainmenu.module';
import { CORE_SHAREDFILES_PROVIDERS } from '../../core/sharedfiles/sharedfiles.module';
import { CORE_SITEADDONS_PROVIDERS } from '../../core/siteaddons/siteaddons.module';
import { CORE_SITEHOME_PROVIDERS } from '../../core/sitehome/sitehome.module';
import { CORE_USER_PROVIDERS } from '../../core/user/user.module';
import { IONIC_NATIVE_PROVIDERS } from '../../core/emulator/emulator.module';
// Import other libraries and providers.
import { DomSanitizer } from '@angular/platform-browser';
import { FormBuilder, Validators } from '@angular/forms';
import { Http } from '@angular/http';
import { HttpClient } from '@angular/common/http';
import { CoreConfigConstants } from '../../configconstants';
import { CoreConstants } from '../../core/constants';
import * as moment from 'moment';
import { Md5 } from 'ts-md5/dist/md5';
/**
* 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.
*
* IMPORTANT: Use this component only if it is a must. It will create and compile a new component and module everytime this
* component is used, so it can slow down the app.
*
* This component isn't part of CoreComponentsModule to prevent circular dependencies. If you want to use it,
* you need to import CoreCompileHtmlComponentsModule.
*
* You can provide some Javascript code (as text) to be executed inside the component. The context of the javascript code (this)
* will be the component instance created to compile the template. This means your javascript code can interact with the template.
* The component instance will have most of the providers so you can use them in the javascript code. E.g. if you want to use
* CoreAppProvider, you can do it with "this.CoreAppProvider".
*/
@Component({
selector: 'core-compile-html',
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
];
// Other Ionic/Angular providers that don't depend on where they are injected.
protected OTHER_PROVIDERS = [
TranslateService, Http, HttpClient, Platform, DomSanitizer, ActionSheetController, AlertController, LoadingController,
ModalController, PopoverController, ToastController, FormBuilder
];
@Input() text: string; // The HTML text to display.
@Input() javascript: string; // The javascript to execute in the component.
// Get the container where to put the content.
@ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
protected componentRef: ComponentRef<any>;
protected logger;
constructor(logger: CoreLoggerProvider, protected compiler: Compiler, protected injector: Injector,
protected cdr: ChangeDetectorRef, protected navCtrl: NavController) {
this.logger = logger.getInstance('CoreCompileHtmlComponent');
}
/**
* Detect changes on input properties.
*/
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;
}
}
// Destroy previous components.
this.componentRef && this.componentRef.destroy();
// Create the component.
this.componentRef = this.container.createComponent(componentFactory);
});
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.componentRef && this.componentRef.destroy();
}
/**
* Create a dynamic component to compile the HTML and run the javascript.
*
* @return {any} The component class.
*/
protected createComponent(): any {
// tslint:disable: no-this-assignment
const compileInstance = this,
providers = (<any[]> CORE_PROVIDERS).concat(CORE_CONTENTLINKS_PROVIDERS).concat(CORE_COURSE_PROVIDERS)
.concat(CORE_COURSES_PROVIDERS).concat(CORE_FILEUPLOADER_PROVIDERS).concat(CORE_GRADES_PROVIDERS)
.concat(CORE_LOGIN_PROVIDERS).concat(CORE_MAINMENU_PROVIDERS).concat(CORE_SHAREDFILES_PROVIDERS)
.concat(CORE_SITEHOME_PROVIDERS).concat(CORE_SITEADDONS_PROVIDERS).concat(CORE_USER_PROVIDERS)
.concat(IONIC_NATIVE_PROVIDERS).concat(this.OTHER_PROVIDERS);
// Create the component, using the text as the template.
return Component({
template: this.text
})
(class CoreCompileHtmlFakeComponent implements OnInit {
constructor() {
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
for (const i in providers) {
const providerDef = providers[i];
if (typeof providerDef == 'function' && providerDef.name) {
try {
// Inject the provider to the instance. We use the class name as the property name.
this[providerDef.name] = compileInstance.injector.get(providerDef);
} catch (ex) {
compileInstance.logger.warn('Error injecting provider', providerDef.name, ex);
}
}
}
// Add some final components and providers.
this['ChangeDetectorRef'] = compileInstance.cdr;
this['NavController'] = compileInstance.navCtrl;
this['Validators'] = Validators;
this['CoreConfigConstants'] = CoreConfigConstants;
this['CoreConstants'] = CoreConstants;
this['moment'] = moment;
this['Md5'] = Md5;
this['componentContainer'] = compileInstance.container;
}
ngOnInit(): void {
// If there is some javascript to run, do it now.
if (compileInstance.javascript) {
// tslint:disable: no-eval
eval(compileInstance.javascript);
}
}
});
}
}

View File

@ -0,0 +1,27 @@
// (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 { NgModule } from '@angular/core';
import { CoreCompileProvider } from './providers/compile';
@NgModule({
declarations: [
],
imports: [
],
providers: [
CoreCompileProvider
]
})
export class CoreCompileModule { }

View File

@ -27,4 +27,4 @@ import { CoreCompileHtmlComponent } from './compile-html';
CoreCompileHtmlComponent
]
})
export class CoreCompileHtmlComponentsModule {}
export class CoreCompileHtmlComponentModule {}

View File

@ -0,0 +1,150 @@
// (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, NgModule, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, Compiler, ViewChild, ComponentRef,
SimpleChange, ChangeDetectorRef
} from '@angular/core';
import { IonicModule, NavController } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
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.
*
* IMPORTANT: Use this component only if it is a must. It will create and compile a new component and module everytime this
* component is used, so it can slow down the app.
*
* This component has its own module to prevent circular dependencies. If you want to use it,
* you need to import CoreCompileHtmlComponentModule.
*
* You can provide some Javascript code (as text) to be executed inside the component. The context of the javascript code (this)
* will be the component instance created to compile the template. This means your javascript code can interact with the template.
* The component instance will have most of the providers so you can use them in the javascript code. E.g. if you want to use
* CoreAppProvider, you can do it with "this.CoreAppProvider".
*/
@Component({
selector: 'core-compile-html',
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.
// Get the container where to put the content.
@ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
protected componentRef: ComponentRef<any>;
constructor(protected compileProvider: CoreCompileProvider, protected compiler: Compiler,
protected cdr: ChangeDetectorRef, protected navCtrl: NavController) { }
/**
* Detect changes on input properties.
*/
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;
}
}
// Destroy previous components.
this.componentRef && this.componentRef.destroy();
// Create the component.
this.componentRef = this.container.createComponent(componentFactory);
});
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.componentRef && this.componentRef.destroy();
}
/**
* Create a dynamic component to compile the HTML and run the javascript.
*
* @return {any} The component class.
*/
protected createComponent(): 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 {
constructor() {
// If there is some javascript to run, prepare the instance.
if (compileInstance.javascript) {
compileInstance.compileProvider.injectLibraries(this);
// Add some more components and classes.
this['ChangeDetectorRef'] = compileInstance.cdr;
this['NavController'] = compileInstance.navCtrl;
this['componentContainer'] = compileInstance.container;
// Add the data passed to the component.
for (const name in compileInstance.jsData) {
this[name] = compileInstance.jsData[name];
}
}
}
ngOnInit(): void {
// If there is some javascript to run, do it now.
if (compileInstance.javascript) {
compileInstance.compileProvider.executeJavascript(this, compileInstance.javascript);
}
}
});
}
}

View File

@ -0,0 +1,142 @@
// (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 { Injectable, Injector } from '@angular/core';
import {
Platform, ActionSheetController, AlertController, LoadingController, ModalController, PopoverController, ToastController
} from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreLoggerProvider } from '../../../providers/logger';
// Import core providers.
import { CORE_PROVIDERS } from '../../../app/app.module';
import { CORE_CONTENTLINKS_PROVIDERS } from '../../contentlinks/contentlinks.module';
import { CORE_COURSE_PROVIDERS } from '../../course/course.module';
import { CORE_COURSES_PROVIDERS } from '../../courses/courses.module';
import { CORE_FILEUPLOADER_PROVIDERS } from '../../fileuploader/fileuploader.module';
import { CORE_GRADES_PROVIDERS } from '../../grades/grades.module';
import { CORE_LOGIN_PROVIDERS } from '../../login/login.module';
import { CORE_MAINMENU_PROVIDERS } from '../../mainmenu/mainmenu.module';
import { CORE_SHAREDFILES_PROVIDERS } from '../../sharedfiles/sharedfiles.module';
import { CORE_SITEHOME_PROVIDERS } from '../../sitehome/sitehome.module';
import { CORE_USER_PROVIDERS } from '../../user/user.module';
import { IONIC_NATIVE_PROVIDERS } from '../../emulator/emulator.module';
// Import only this provider to prevent circular dependencies.
import { CoreSiteAddonsProvider } from '../../siteaddons/providers/siteaddons';
// Import other libraries and providers.
import { DomSanitizer } from '@angular/platform-browser';
import { FormBuilder, Validators } from '@angular/forms';
import { Http } from '@angular/http';
import { HttpClient } from '@angular/common/http';
import { CoreConfigConstants } from '../../../configconstants';
import { CoreConstants } from '../../constants';
import * as moment from 'moment';
import { Md5 } from 'ts-md5/dist/md5';
// Import core classes that can be useful for site addons.
import { CoreSyncBaseProvider } from '../../../classes/base-sync';
import { CoreCache } from '../../../classes/cache';
import { CoreDelegate } from '../../../classes/delegate';
import { CoreContentLinksHandlerBase } from '../../contentlinks/classes/base-handler';
import { CoreContentLinksModuleGradeHandler } from '../../contentlinks/classes/module-grade-handler';
import { CoreContentLinksModuleIndexHandler } from '../../contentlinks/classes/module-index-handler';
import { CoreCourseModulePrefetchHandlerBase } from '../../course/classes/module-prefetch-handler';
/**
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
*/
@Injectable()
export class CoreCompileProvider {
protected logger;
// Other Ionic/Angular providers that don't depend on where they are injected.
protected OTHER_PROVIDERS = [
TranslateService, Http, HttpClient, Platform, DomSanitizer, ActionSheetController, AlertController, LoadingController,
ModalController, PopoverController, ToastController, FormBuilder
];
constructor(protected injector: Injector, logger: CoreLoggerProvider) {
this.logger = logger.getInstance('CoreCompileProvider');
}
/**
* Eval some javascript using the context of the function.
*
* @param {string} javascript The javascript to eval.
* @return {any} Result of the eval.
*/
protected evalInContext(javascript: string): any {
// tslint:disable: no-eval
return eval(javascript);
}
/**
* Execute some javascript code, using a certain instance as the context.
*
* @param {any} instance Instance to use as the context. In the JS code, "this" will be this instance.
* @param {string} javascript The javascript code to eval.
* @return {any} Result of the javascript execution.
*/
executeJavascript(instance: any, javascript: string): any {
try {
return this.evalInContext.call(instance, javascript);
} catch (ex) {
this.logger.error('Error evaluating javascript', ex);
}
}
/**
* Inject all the core libraries in a certain object.
*
* @param {any} instance The instance where to inject the libraries.
*/
injectLibraries(instance: any): void {
const providers = (<any[]> CORE_PROVIDERS).concat(CORE_CONTENTLINKS_PROVIDERS).concat(CORE_COURSE_PROVIDERS)
.concat(CORE_COURSES_PROVIDERS).concat(CORE_FILEUPLOADER_PROVIDERS).concat(CORE_GRADES_PROVIDERS)
.concat(CORE_LOGIN_PROVIDERS).concat(CORE_MAINMENU_PROVIDERS).concat(CORE_SHAREDFILES_PROVIDERS)
.concat(CORE_SITEHOME_PROVIDERS).concat([CoreSiteAddonsProvider]).concat(CORE_USER_PROVIDERS)
.concat(IONIC_NATIVE_PROVIDERS).concat(this.OTHER_PROVIDERS);
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
for (const i in providers) {
const providerDef = providers[i];
if (typeof providerDef == 'function' && providerDef.name) {
try {
// Inject the provider to the instance. We use the class name as the property name.
instance[providerDef.name] = this.injector.get(providerDef);
} catch (ex) {
this.logger.warn('Error injecting provider', providerDef.name, ex);
}
}
}
// Add some final classes.
instance['injector'] = this.injector;
instance['Validators'] = Validators;
instance['CoreConfigConstants'] = CoreConfigConstants;
instance['CoreConstants'] = CoreConstants;
instance['moment'] = moment;
instance['Md5'] = Md5;
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
instance['CoreCache'] = CoreCache;
instance['CoreDelegate'] = CoreDelegate;
instance['CoreContentLinksHandlerBase'] = CoreContentLinksHandlerBase;
instance['CoreContentLinksModuleGradeHandler'] = CoreContentLinksModuleGradeHandler;
instance['CoreContentLinksModuleIndexHandler'] = CoreContentLinksModuleIndexHandler;
instance['CoreCourseModulePrefetchHandlerBase'] = CoreCourseModulePrefetchHandlerBase;
}
}

View File

@ -1,3 +1,3 @@
<core-loading [hideUntil]="dataLoaded">
<core-compile-html [text]="content" [javascript]="javascript"></core-compile-html>
<core-compile-html [text]="content" [javascript]="javascript" [jsData]="bootstrapResult"></core-compile-html>
</core-loading>

View File

@ -28,6 +28,7 @@ export class CoreSiteAddonsAddonContentComponent implements OnInit {
@Input() component: string;
@Input() method: string;
@Input() args: any;
@Input() bootstrapResult: any; // Result of the bootstrap JS of the handler.
@Output() onContentLoaded?: EventEmitter<boolean>; // Emits an event when the content is loaded.
@Output() onLoadingContent?: EventEmitter<boolean>; // Emits an event when starts to load the content.

View File

@ -17,7 +17,7 @@ import { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '../../../components/components.module';
import { CoreCompileHtmlComponentsModule } from '../../../components/compile-html/compile-html.module';
import { CoreCompileHtmlComponentModule } from '../../compile/components/compile-html/compile-html.module';
import { CoreSiteAddonsAddonContentComponent } from './addon-content/addon-content';
import { CoreSiteAddonsModuleIndexComponent } from './module-index/module-index';
@ -30,7 +30,7 @@ import { CoreSiteAddonsModuleIndexComponent } from './module-index/module-index'
CommonModule,
IonicModule,
CoreComponentsModule,
CoreCompileHtmlComponentsModule,
CoreCompileHtmlComponentModule,
TranslateModule.forChild()
],
providers: [

View File

@ -9,4 +9,4 @@
</core-context-menu>
</core-navbar-buttons>
<core-site-addons-addon-content *ngIf="component && method" [component]="component" [method]="method" [args]="args" (onContentLoaded)="contentLoaded($event)" (onLoadingContent)="contentLoading($event)"></core-site-addons-addon-content>
<core-site-addons-addon-content *ngIf="component && method" [component]="component" [method]="method" [args]="args" [bootstrapResult]="bootstrapResult" (onContentLoaded)="contentLoaded($event)" (onLoadingContent)="contentLoading($event)"></core-site-addons-addon-content>

View File

@ -37,6 +37,7 @@ export class CoreSiteAddonsModuleIndexComponent implements OnInit, OnDestroy, Co
component: string;
method: string;
args: any;
bootstrapResult: any;
// Data for context menu.
externalUrl: string;
@ -60,7 +61,7 @@ export class CoreSiteAddonsModuleIndexComponent implements OnInit, OnDestroy, Co
this.refreshIcon = 'spinner';
if (this.module) {
const handler = this.siteAddonsProvider.getModuleSiteAddonHandler(this.module.modname);
const handler = this.siteAddonsProvider.getSiteAddonHandler(this.module.modname);
if (handler) {
this.component = handler.addon.component;
this.method = handler.handlerSchema.method;
@ -68,6 +69,7 @@ export class CoreSiteAddonsModuleIndexComponent implements OnInit, OnDestroy, Co
courseid: this.courseId,
cmid: this.module.id
};
this.bootstrapResult = handler.bootstrapResult;
}
// Get the data for the context menu.

View File

@ -91,7 +91,8 @@ export class CoreSiteAddonsCallWSNewContentDirective extends CoreSiteAddonsCallW
title: this.title,
component: this.component,
method: this.method,
args: args
args: args,
bootstrapResult: this.parentContent && this.parentContent.bootstrapResult
});
}
}

View File

@ -88,7 +88,8 @@ export class CoreSiteAddonsNewContentDirective implements OnInit {
title: this.title,
component: this.component,
method: this.method,
args: args
args: args,
bootstrapResult: this.parentContent && this.parentContent.bootstrapResult
});
}
});

View File

@ -11,5 +11,5 @@
<ion-refresher [enabled]="content && content.dataLoaded" (ionRefresh)="refreshData($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-site-addons-addon-content [component]="component" [method]="method" [args]="args"></core-site-addons-addon-content>
<core-site-addons-addon-content [component]="component" [method]="method" [args]="args" [bootstrapResult]="bootstrapResult"></core-site-addons-addon-content>
</ion-content>

View File

@ -32,12 +32,14 @@ export class CoreSiteAddonsAddonPage {
component: string;
method: string;
args: any;
bootstrapResult: any;
constructor(params: NavParams) {
this.title = params.get('title');
this.component = params.get('component');
this.method = params.get('method');
this.args = params.get('args');
this.bootstrapResult = params.get('bootstrapResult');
}
/**

View File

@ -18,16 +18,18 @@ import { CoreLangProvider } from '../../../providers/lang';
import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSite } from '../../../classes/site';
import { CoreSitesProvider } from '../../../providers/sites';
import { CoreMainMenuDelegate, CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate';
import { CoreUtilsProvider } from '../../../providers/utils/utils';
import { CoreMainMenuDelegate, CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../mainmenu/providers/delegate';
import {
CoreCourseModuleDelegate, CoreCourseModuleHandler, CoreCourseModuleHandlerData
} from '../../../core/course/providers/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '../../../core/course/providers/module-prefetch-delegate';
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../../core/user/providers/user-delegate';
} from '../../course/providers/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '../../course/providers/module-prefetch-delegate';
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../user/providers/user-delegate';
import { CoreDelegateHandler } from '../../../classes/delegate';
import { CoreSiteAddonsModuleIndexComponent } from '../components/module-index/module-index';
import { CoreSiteAddonsProvider } from './siteaddons';
import { CoreSiteAddonsModulePrefetchHandler } from '../classes/module-prefetch-handler';
import { CoreCompileProvider } from '../../compile/providers/compile';
/**
* Helper service to provide functionalities regarding site addons. It basically has the features to load and register site
@ -42,10 +44,42 @@ export class CoreSiteAddonsHelperProvider {
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private injector: Injector,
private mainMenuDelegate: CoreMainMenuDelegate, private moduleDelegate: CoreCourseModuleDelegate,
private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider,
private siteAddonsProvider: CoreSiteAddonsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate) {
private siteAddonsProvider: CoreSiteAddonsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate,
private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider) {
this.logger = logger.getInstance('CoreSiteAddonsHelperProvider');
}
/**
* Bootstrap a handler if it has some bootstrap JS.
*
* @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<any>} Promise resolved when done. The resolve param is the result of the javascript execution (if any).
*/
protected bootstrapHandler(addon: any, handlerName: string, handlerSchema: any): Promise<any> {
if (!handlerSchema.bootstrap) {
return Promise.resolve();
}
const siteId = this.sitesProvider.getCurrentSiteId(),
preSets = {getFromCache: false}; // Try to ignore cache.
return this.siteAddonsProvider.getContent(addon.component, handlerSchema.bootstrap, {}, preSets).then((result) => {
if (!result.javascript || this.sitesProvider.getCurrentSiteId() != siteId) {
// No javascript or site has changed, stop.
return;
}
// Create a "fake" instance to hold all the libraries.
const instance = {};
this.compileProvider.injectLibraries(instance);
// Now execute the javascript using this instance.
return this.compileProvider.executeJavascript(instance, result.javascript);
});
}
/**
* Create a base handler for a site addon.
*
@ -86,17 +120,6 @@ export class CoreSiteAddonsHelperProvider {
return this.getHandlerPrefixForStrings(handlerName) + key;
}
/**
* Get the unique name of a handler (addon + handler).
*
* @param {any} addon Data of the addon.
* @param {string} handlerName Name of the handler inside the addon.
* @return {string} Unique name.
*/
protected getHandlerUniqueName(addon: any, handlerName: string): string {
return addon.addon + '_' + handlerName;
}
/**
* Check if a certain addon is a site addon and it's enabled in a certain site.
*
@ -134,7 +157,7 @@ export class CoreSiteAddonsHelperProvider {
}
for (const lang in handlerSchema.lang) {
const prefix = this.getHandlerPrefixForStrings(this.getHandlerUniqueName(addon, handlerName));
const prefix = this.getHandlerPrefixForStrings(this.siteAddonsProvider.getHandlerUniqueName(addon, handlerName));
this.langProvider.addSiteAddonsStrings(lang, handlerSchema.lang[lang], prefix);
}
@ -144,8 +167,11 @@ export class CoreSiteAddonsHelperProvider {
* Load a site addon.
*
* @param {any} addon Data of the addon.
* @return {Promise<any>} Promise resolved when loaded.
*/
loadSiteAddon(addon: any): void {
loadSiteAddon(addon: any): Promise<any> {
const promises = [];
try {
if (!addon.parsedHandlers) {
addon.parsedHandlers = JSON.parse(addon.handlers);
@ -153,11 +179,13 @@ export class CoreSiteAddonsHelperProvider {
// Register all the handlers.
for (const name in addon.parsedHandlers) {
this.registerHandler(addon, name, addon.parsedHandlers[name]);
promises.push(this.registerHandler(addon, name, addon.parsedHandlers[name]));
}
} catch (ex) {
this.logger.warn('Error parsing site addon', ex);
}
return this.utils.allPromises(promises);
}
/**
@ -166,26 +194,42 @@ export class CoreSiteAddonsHelperProvider {
* @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<any>} Promise resolved when done.
*/
registerHandler(addon: any, handlerName: string, handlerSchema: any): void {
registerHandler(addon: any, handlerName: string, handlerSchema: any): Promise<any> {
this.loadHandlerLangStrings(addon, handlerName, handlerSchema);
switch (handlerSchema.delegate) {
case 'CoreMainMenuDelegate':
this.registerMainMenuHandler(addon, handlerName, handlerSchema);
break;
// Wait for the bootstrap JS to be executed.
return this.bootstrapHandler(addon, handlerName, handlerSchema).then((result) => {
let uniqueName;
case 'CoreCourseModuleDelegate':
this.registerModuleHandler(addon, handlerName, handlerSchema);
break;
switch (handlerSchema.delegate) {
case 'CoreMainMenuDelegate':
uniqueName = this.registerMainMenuHandler(addon, handlerName, handlerSchema, result);
break;
case 'CoreUserDelegate':
this.registerUserProfileHandler(addon, handlerName, handlerSchema);
break;
case 'CoreCourseModuleDelegate':
uniqueName = this.registerModuleHandler(addon, handlerName, handlerSchema, result);
break;
default:
// Nothing to do.
}
case 'CoreUserDelegate':
uniqueName = this.registerUserProfileHandler(addon, handlerName, handlerSchema, result);
break;
default:
// Nothing to do.
}
if (uniqueName) {
// Store the handler data.
this.siteAddonsProvider.setSiteAddonHandler(uniqueName, {
addon: addon,
handlerName: handlerName,
handlerSchema: handlerSchema,
bootstrapResult: result
});
}
});
}
/**
@ -194,15 +238,18 @@ export class CoreSiteAddonsHelperProvider {
* @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 executing the bootstrap JS.
* @return {string} A string to identify the handler.
*/
protected registerMainMenuHandler(addon: any, handlerName: string, handlerSchema: any): void {
protected registerMainMenuHandler(addon: any, handlerName: string, handlerSchema: any, bootstrapResult?: any): string {
if (!handlerSchema || !handlerSchema.displaydata) {
// Required data not provided, stop.
return;
}
// Create the base handler.
const baseHandler = this.getBaseHandler(this.getHandlerUniqueName(addon, handlerName)),
const uniqueName = this.siteAddonsProvider.getHandlerUniqueName(addon, handlerName),
baseHandler = this.getBaseHandler(uniqueName),
prefixedTitle = this.getHandlerPrefixedString(baseHandler.name, handlerSchema.displaydata.title);
let mainMenuHandler: CoreMainMenuHandler;
@ -219,12 +266,15 @@ export class CoreSiteAddonsHelperProvider {
title: prefixedTitle,
component: addon.component,
method: handlerSchema.method,
bootstrapResult: bootstrapResult
}
};
}
});
this.mainMenuDelegate.registerHandler(mainMenuHandler);
return uniqueName;
}
/**
@ -233,8 +283,10 @@ export class CoreSiteAddonsHelperProvider {
* @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 executing the bootstrap JS.
* @return {string} A string to identify the handler.
*/
protected registerModuleHandler(addon: any, handlerName: string, handlerSchema: any): void {
protected registerModuleHandler(addon: any, handlerName: string, handlerSchema: any, bootstrapResult?: any): string {
if (!handlerSchema || !handlerSchema.displaydata) {
// Required data not provided, stop.
return;
@ -243,16 +295,10 @@ export class CoreSiteAddonsHelperProvider {
// Create the base handler.
const modName = addon.component.replace('mod_', ''),
baseHandler = this.getBaseHandler(modName),
hasOfflineFunctions = !!(handlerSchema.offlinefunctions && Object.keys(handlerSchema.offlinefunctions).length);
hasOfflineFunctions = !!(handlerSchema.offlinefunctions && Object.keys(handlerSchema.offlinefunctions).length),
showDowloadButton = handlerSchema.downloadbutton;
let moduleHandler: CoreCourseModuleHandler;
// Store the handler data.
this.siteAddonsProvider.setModuleSiteAddonHandler(modName, {
addon: addon,
handlerName: handlerName,
handlerSchema: handlerSchema
});
// Extend the base handler, adding the properties required by the delegate.
moduleHandler = Object.assign(baseHandler, {
getData: (module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData => {
@ -260,7 +306,7 @@ export class CoreSiteAddonsHelperProvider {
title: module.name,
icon: handlerSchema.displaydata.icon,
class: handlerSchema.displaydata.class,
showDownloadButton: hasOfflineFunctions,
showDownloadButton: typeof showDowloadButton != 'undefined' ? showDowloadButton : hasOfflineFunctions,
action: (event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void => {
event.preventDefault();
event.stopPropagation();
@ -285,6 +331,8 @@ export class CoreSiteAddonsHelperProvider {
}
this.moduleDelegate.registerHandler(moduleHandler);
return modName;
}
/**
@ -293,15 +341,18 @@ export class CoreSiteAddonsHelperProvider {
* @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 executing the bootstrap JS.
* @return {string} A string to identify the handler.
*/
protected registerUserProfileHandler(addon: any, handlerName: string, handlerSchema: any): void {
protected registerUserProfileHandler(addon: any, handlerName: string, handlerSchema: any, bootstrapResult?: any): string {
if (!handlerSchema || !handlerSchema.displaydata) {
// Required data not provided, stop.
return;
}
// Create the base handler.
const baseHandler = this.getBaseHandler(this.getHandlerUniqueName(addon, handlerName)),
const uniqueName = this.siteAddonsProvider.getHandlerUniqueName(addon, handlerName),
baseHandler = this.getBaseHandler(uniqueName),
prefixedTitle = this.getHandlerPrefixedString(baseHandler.name, handlerSchema.displaydata.title);
let userHandler: CoreUserProfileHandler;
@ -332,7 +383,8 @@ export class CoreSiteAddonsHelperProvider {
args: {
courseid: courseId,
userid: user.id
}
},
bootstrapResult: bootstrapResult
});
}
};
@ -340,5 +392,7 @@ export class CoreSiteAddonsHelperProvider {
});
this.userDelegate.registerHandler(userHandler);
return uniqueName;
}
}

View File

@ -23,9 +23,9 @@ import { CoreUtilsProvider } from '../../../providers/utils/utils';
import { CoreConfigConstants } from '../../../configconstants';
/**
* Handler of a site addon representing a module.
* Handler of a site addon.
*/
export interface CoreSiteAddonsModuleHandler {
export interface CoreSiteAddonsHandler {
/**
* The site addon data.
* @type {any}
@ -43,6 +43,12 @@ export interface CoreSiteAddonsModuleHandler {
* @type {any}
*/
handlerSchema: any;
/**
* Result of executing the bootstrap JS.
* @type {any}
*/
bootstrapResult?: any;
}
export interface CoreSiteAddonsGetContentResult {
@ -79,7 +85,7 @@ export class CoreSiteAddonsProvider {
protected ROOT_CACHE_KEY = 'CoreSiteAddons:';
protected logger;
protected moduleSiteAddons: {[modName: string]: CoreSiteAddonsModuleHandler} = {};
protected siteAddons: {[name: string]: CoreSiteAddonsHandler} = {}; // Site addons registered.
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
private langProvider: CoreLangProvider, private appProvider: CoreAppProvider, private platform: Platform) {
@ -179,10 +185,12 @@ export class CoreSiteAddonsProvider {
* @param {string} component Component where the class is. E.g. mod_assign.
* @param {string} method Method to execute in the class.
* @param {any} args The params for the method.
* @param {CoreSiteWSPreSets} [preSets] Extra options.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<CoreSiteAddonsGetContentResult>} Promise resolved with the result.
*/
getContent(component: string, method: string, args: any, siteId?: string): Promise<CoreSiteAddonsGetContentResult> {
getContent(component: string, method: string, args: any, preSets?: CoreSiteWSPreSets, siteId?: string)
: Promise<CoreSiteAddonsGetContentResult> {
this.logger.debug(`Get content for component '${component}' and method '${method}'`);
return this.sitesProvider.getSite(siteId).then((site) => {
@ -194,10 +202,11 @@ export class CoreSiteAddonsProvider {
component: component,
method: method,
args: this.utils.objectToArrayOfObjects(argsToSend, 'name', 'value', true)
}, preSets = {
cacheKey: this.getContentCacheKey(component, method, args)
};
preSets = preSets || {};
preSets.cacheKey = this.getContentCacheKey(component, method, args);
return this.sitesProvider.getCurrentSite().read('tool_mobile_get_content', data, preSets);
}).then((result) => {
if (result.otherdata) {
@ -226,13 +235,24 @@ export class CoreSiteAddonsProvider {
}
/**
* Get the site addon handler for a certain module.
* Get the unique name of a handler (addon + handler).
*
* @param {string} modName Name of the module.
* @return {CoreSiteAddonsModuleHandler} Handler.
* @param {any} addon Data of the addon.
* @param {string} handlerName Name of the handler inside the addon.
* @return {string} Unique name.
*/
getModuleSiteAddonHandler(modName: string): CoreSiteAddonsModuleHandler {
return this.moduleSiteAddons[modName];
getHandlerUniqueName(addon: any, handlerName: string): string {
return addon.addon + '_' + handlerName;
}
/**
* Get a site addon handler.
*
* @param {string} name Unique name of the handler.
* @return {CoreSiteAddonsHandler} Handler.
*/
getSiteAddonHandler(name: string): CoreSiteAddonsHandler {
return this.siteAddons[name];
}
/**
@ -328,12 +348,12 @@ export class CoreSiteAddonsProvider {
}
/**
* Set the site addon handler for a certain module.
* Store a site addon handler.
*
* @param {string} modName Name of the module.
* @param {CoreSiteAddonsModuleHandler} handler Handler to set.
* @param {string} name A unique name to identify the handler.
* @param {CoreSiteAddonsHandler} handler Handler to set.
*/
setModuleSiteAddonHandler(modName: string, handler: CoreSiteAddonsModuleHandler): void {
this.moduleSiteAddons[modName] = handler;
setSiteAddonHandler(name: string, handler: CoreSiteAddonsHandler): void {
this.siteAddons[name] = handler;
}
}

View File

@ -13,7 +13,6 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { Platform } from 'ionic-angular';
import { CoreSiteAddonsProvider } from './providers/siteaddons';
import { CoreSiteAddonsHelperProvider } from './providers/helper';
import { CoreSiteAddonsComponentsModule } from './components/components.module';

View File

@ -17,6 +17,7 @@ import { CoreEventsProvider } from './events';
import { CoreLoggerProvider } from './logger';
import { CoreSitesProvider } from './sites';
import { CoreSiteWSPreSets } from '../classes/site';
import { CoreUtilsProvider } from './utils/utils';
import { CoreSiteAddonsProvider } from '../core/siteaddons/providers/siteaddons';
import { CoreSiteAddonsHelperProvider } from '../core/siteaddons/providers/helper';
@ -29,7 +30,8 @@ export class CoreAddonManagerProvider {
protected logger;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
private siteAddonsProvider: CoreSiteAddonsProvider, private siteAddonsHelperProvider: CoreSiteAddonsHelperProvider) {
private siteAddonsProvider: CoreSiteAddonsProvider, private siteAddonsHelperProvider: CoreSiteAddonsHelperProvider,
private utils: CoreUtilsProvider) {
logger = logger.getInstance('CoreAddonManagerProvider');
// Fetch the addons on login.
@ -39,9 +41,10 @@ export class CoreAddonManagerProvider {
// Addons fetched, check that site hasn't changed.
if (siteId == this.sitesProvider.getCurrentSiteId() && addons.length) {
// Site is still the same. Load the addons and trigger the event.
this.loadSiteAddons(addons);
this.loadSiteAddons(addons).then(() => {
eventsProvider.trigger(CoreEventsProvider.SITE_ADDONS_LOADED, {}, siteId);
});
eventsProvider.trigger(CoreEventsProvider.SITE_ADDONS_LOADED, {}, siteId);
}
});
});
@ -85,10 +88,15 @@ export class CoreAddonManagerProvider {
* Load site addons.
*
* @param {any[]} addons The addons to load.
* @return {Promise<any>} Promise resolved when loaded.
*/
loadSiteAddons(addons: any[]): void {
loadSiteAddons(addons: any[]): Promise<any> {
const promises = [];
addons.forEach((addon) => {
this.siteAddonsHelperProvider.loadSiteAddon(addon);
promises.push(this.siteAddonsHelperProvider.loadSiteAddon(addon));
});
return this.utils.allPromises(promises);
}
}