diff --git a/src/components/compile-html/compile-html.ts b/src/components/compile-html/compile-html.ts index 2798df424..26b3cc480 100644 --- a/src/components/compile-html/compile-html.ts +++ b/src/components/compile-html/compile-html.ts @@ -13,7 +13,8 @@ // limitations under the License. import { - Component, NgModule, Input, OnInit, OnDestroy, ViewContainerRef, Compiler, ViewChild, ComponentRef, Injector, ChangeDetectorRef + Component, NgModule, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, Compiler, ViewChild, ComponentRef, Injector, + SimpleChange, ChangeDetectorRef } from '@angular/core'; import { IonicModule, NavController, Platform, ActionSheetController, AlertController, LoadingController, ModalController, @@ -75,7 +76,7 @@ import { Md5 } from 'ts-md5/dist/md5'; selector: 'core-compile-html', template: '' }) -export class CoreCompileHtmlComponent implements OnInit, OnDestroy { +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, @@ -103,10 +104,10 @@ export class CoreCompileHtmlComponent implements OnInit, OnDestroy { } /** - * Component being initialized. + * Detect changes on input properties. */ - ngOnInit(): void { - if (this.text) { + 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 {}); @@ -123,6 +124,9 @@ export class CoreCompileHtmlComponent implements OnInit, OnDestroy { } } + // Destroy previous components. + this.componentRef && this.componentRef.destroy(); + // Create the component. this.componentRef = this.container.createComponent(componentFactory); }); diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts index 1dc684011..99e1ad423 100644 --- a/src/core/login/pages/reconnect/reconnect.ts +++ b/src/core/login/pages/reconnect/reconnect.ts @@ -88,6 +88,8 @@ export class CoreLoginReconnectPage { return site.getPublicConfig().then((config) => { this.logoUrl = config.logourl || config.compactlogourl; + }).catch(() => { + // Ignore errors. }); } }).catch(() => { diff --git a/src/core/siteaddons/pages/addon-page/addon-page.html b/src/core/siteaddons/pages/addon-page/addon-page.html new file mode 100644 index 000000000..15297abc7 --- /dev/null +++ b/src/core/siteaddons/pages/addon-page/addon-page.html @@ -0,0 +1,17 @@ + + + {{ title }} + + + + + + + + + + + + + + diff --git a/src/core/siteaddons/pages/addon-page/addon-page.module.ts b/src/core/siteaddons/pages/addon-page/addon-page.module.ts new file mode 100644 index 000000000..f7dcf227a --- /dev/null +++ b/src/core/siteaddons/pages/addon-page/addon-page.module.ts @@ -0,0 +1,36 @@ +// (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 { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreSiteAddonsAddonPage } from './addon-page'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreCompileHtmlComponentsModule } from '../../../../components/compile-html/compile-html.module'; + +/** + * Module to lazy load the page. + */ +@NgModule({ + declarations: [ + CoreSiteAddonsAddonPage + ], + imports: [ + CoreComponentsModule, + CoreCompileHtmlComponentsModule, + IonicPageModule.forChild(CoreSiteAddonsAddonPage), + TranslateModule.forChild() + ] +}) +export class CoreSiteAddonsAddonPageModule {} diff --git a/src/core/siteaddons/pages/addon-page/addon-page.ts b/src/core/siteaddons/pages/addon-page/addon-page.ts new file mode 100644 index 000000000..0ae787794 --- /dev/null +++ b/src/core/siteaddons/pages/addon-page/addon-page.ts @@ -0,0 +1,78 @@ +// (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 } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSiteAddonsProvider } from '../../providers/siteaddons'; + +/** + * Page to render a site addon page. + */ +@IonicPage({ segment: 'core-site-addons-addon-page' }) +@Component({ + selector: 'page-core-site-addons-addon', + templateUrl: 'addon-page.html', +}) +export class CoreSiteAddonsAddonPage { + title: string; // Page title. + content: string; // Page content. + dataLoaded: boolean; + + protected component: string; + protected method: string; + protected args: any; + + constructor(params: NavParams, protected domUtils: CoreDomUtilsProvider, protected siteAddonsProvider: CoreSiteAddonsProvider) { + this.title = params.get('title'); + this.component = params.get('component'); + this.method = params.get('method'); + this.args = params.get('args'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchContent().finally(() => { + this.dataLoaded = true; + }); + } + + /** + * Fetches the content of the page. + * + * @return {Promise} Promise resolved when done. + */ + fetchContent(): Promise { + return this.siteAddonsProvider.getContent(this.component, this.method, this.args).then((result) => { + this.content = result.html; + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); + }); + } + + /** + * Refresh the data. + * + * @param {any} refresher Refresher. + */ + refreshData(refresher: any): void { + this.siteAddonsProvider.invalidatePageContent(this.component, this.method, this.args).finally(() => { + this.fetchContent().finally(() => { + refresher.complete(); + }); + }); + } +} diff --git a/src/core/siteaddons/providers/siteaddons.ts b/src/core/siteaddons/providers/siteaddons.ts index e483a992c..d5e030e97 100644 --- a/src/core/siteaddons/providers/siteaddons.ts +++ b/src/core/siteaddons/providers/siteaddons.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { NavController, NavOptions } from 'ionic-angular'; +import { CoreLangProvider } from '../../../providers/lang'; import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreSite } from '../../../classes/site'; import { CoreSitesProvider } from '../../../providers/sites'; @@ -24,6 +25,7 @@ import { } from '../../../core/course/providers/module-delegate'; import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../../core/user/providers/user-delegate'; import { CoreDelegateHandler } from '../../../classes/delegate'; +import { CoreConfigConstants } from '../../../configconstants'; /** * Service to provide functionalities regarding site addons. @@ -36,7 +38,7 @@ export class CoreSiteAddonsProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private mainMenuDelegate: CoreMainMenuDelegate, private moduleDelegate: CoreCourseModuleDelegate, - private userDelegate: CoreUserDelegate) { + private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider) { this.logger = logger.getInstance('CoreUserProvider'); } @@ -55,6 +57,54 @@ export class CoreSiteAddonsProvider { }; } + /** + * Get a certain content for a site addon. + * + * @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 {string} [siteId] Site ID. If not defined, current site. + * @return {Promise<{html: string, javascript: string}>} Promise resolved with the content and the javascript. + */ + getContent(component: string, method: string, args: any, siteId?: string): Promise<{html: string, javascript: string}> { + this.logger.debug(`Get content for component '${component}' and method '${method}'`); + + return this.sitesProvider.getSite(siteId).then((site) => { + // Get current language to be added to params. + return this.langProvider.getCurrentLanguage().then((lang) => { + // Add some params that will always be sent. Clone the object so the original one isn't modified. + const argsToSend = this.utils.clone(args); + argsToSend.userid = args.userid || site.getUserId(); + argsToSend.appid = CoreConfigConstants.app_id; + argsToSend.versionname = CoreConfigConstants.versionname; + argsToSend.lang = lang; + + // Now call the WS. + const data = { + component: component, + method: method, + args: this.utils.objectToArrayOfObjects(argsToSend, 'name', 'value', true) + }, preSets = { + cacheKey: this.getContentCacheKey(component, method, args) + }; + + return this.sitesProvider.getCurrentSite().read('tool_mobile_get_content', data, preSets); + }); + }); + } + + /** + * Get cache key for get content WS calls. + * + * @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. + * @return {string} Cache key. + */ + protected getContentCacheKey(component: string, method: string, args: any): string { + return this.ROOT_CACHE_KEY + 'content:' + component + ':' + method + ':' + JSON.stringify(args); + } + /** * Given a handler's unique name and the key of a string, return the full string key (prefixed). * @@ -81,6 +131,32 @@ export class CoreSiteAddonsProvider { return addon.addon + '_' + handlerName; } + /** + * Invalidate a page content. + * + * @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 {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidatePageContent(component: string, callback: string, args: any, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getContentCacheKey(component, callback, args)); + }); + } + + /** + * Check if the get content WS is available. + * + * @param {CoreSite} site The site to check. If not defined, current site. + */ + isGetContentAvailable(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('tool_mobile_get_content'); + } + /** * Check if a certain addon is a site addon and it's enabled in a certain site. * @@ -181,8 +257,7 @@ export class CoreSiteAddonsProvider { pageParams: { title: prefixedTitle, component: addon.component, - callback: handlerSchema.mainfunction, - contextId: handlerSchema.contextid + method: handlerSchema.method, } }; } @@ -223,10 +298,9 @@ export class CoreSiteAddonsProvider { navCtrl.push('CoreSiteAddonsAddonPage', { title: module.name, component: addon.component, - callback: handlerSchema.mainfunction, - contextId: handlerSchema.contextid, + method: handlerSchema.method, args: { - course: courseId, + courseid: courseId, cmid: module.id } }, options); @@ -282,10 +356,9 @@ export class CoreSiteAddonsProvider { navCtrl.push('CoreSiteAddonsAddonPage', { title: prefixedTitle, component: addon.component, - callback: handlerSchema.mainfunction, - contextId: handlerSchema.contextid, + method: handlerSchema.method, args: { - course: courseId, + courseid: courseId, userid: user.id } }); diff --git a/src/providers/addonmanager.ts b/src/providers/addonmanager.ts index 106e3cc25..0b8b933c1 100644 --- a/src/providers/addonmanager.ts +++ b/src/providers/addonmanager.ts @@ -36,7 +36,7 @@ export class CoreAddonManagerProvider { const siteId = this.sitesProvider.getCurrentSiteId(); this.fetchSiteAddons(siteId).then((addons) => { // Addons fetched, check that site hasn't changed. - if (siteId == this.sitesProvider.getCurrentSiteId()) { + if (siteId == this.sitesProvider.getCurrentSiteId() && addons.length) { // Site is still the same. Load the addons and trigger the event. this.loadSiteAddons(addons); @@ -61,6 +61,11 @@ export class CoreAddonManagerProvider { const addons = []; return this.sitesProvider.getSite(siteId).then((site) => { + if (!this.siteAddonsProvider.isGetContentAvailable(site)) { + // Cannot load site addons, so there's no point to fetch them. + return addons; + } + // Get the list of addons. Try not to use cache. return site.read('tool_mobile_get_plugins_supporting_mobile', {}, { getFromCache: false }).then((data) => { data.plugins.forEach((addon: any) => {