From 2e8424d3a2f1171e9ddc14cc4e5f180f59324b96 Mon Sep 17 00:00:00 2001 From: dpalou Date: Tue, 16 Oct 2018 17:53:09 +0200 Subject: [PATCH] MOBILE-1874 block: Create a delegate and component for blocks --- .../block/myoverview/component/myoverview.ts | 4 +- .../timeline/components/timeline/timeline.ts | 4 +- src/app/app.module.ts | 2 + src/core/block/block.module.ts | 34 +++++ .../block/classes/base-block-component.ts} | 4 +- src/core/block/classes/base-block-handler.ts | 56 ++++++++ src/core/block/components/block/block.ts | 72 +++++++++++ .../block/components/block/core-block.html | 6 + .../block/components/components.module.ts | 38 ++++++ .../block/providers/default-block-handler.ts | 29 +++++ src/core/block/providers/delegate.ts | 122 ++++++++++++++++++ src/core/compile/providers/compile.ts | 6 +- 12 files changed, 369 insertions(+), 8 deletions(-) create mode 100644 src/core/block/block.module.ts rename src/{addon/block/classes/block-component.ts => core/block/classes/base-block-component.ts} (97%) create mode 100644 src/core/block/classes/base-block-handler.ts create mode 100644 src/core/block/components/block/block.ts create mode 100644 src/core/block/components/block/core-block.html create mode 100644 src/core/block/components/components.module.ts create mode 100644 src/core/block/providers/default-block-handler.ts create mode 100644 src/core/block/providers/delegate.ts diff --git a/src/addon/block/myoverview/component/myoverview.ts b/src/addon/block/myoverview/component/myoverview.ts index 554b31107..244a72117 100644 --- a/src/addon/block/myoverview/component/myoverview.ts +++ b/src/addon/block/myoverview/component/myoverview.ts @@ -23,7 +23,7 @@ import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion'; -import { AddonBlockComponent } from '../../classes/block-component'; +import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; /** * Component to render a my overview block. @@ -32,7 +32,7 @@ import { AddonBlockComponent } from '../../classes/block-component'; selector: 'addon-block-myoverview', templateUrl: 'addon-block-myoverview.html' }) -export class AddonBlockMyOverviewComponent extends AddonBlockComponent implements OnInit, OnDestroy { +export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy { @ViewChild('searchbar') searchbar: Searchbar; courses = { diff --git a/src/addon/block/timeline/components/timeline/timeline.ts b/src/addon/block/timeline/components/timeline/timeline.ts index 4aadf42cd..f58a5aa9d 100644 --- a/src/addon/block/timeline/components/timeline/timeline.ts +++ b/src/addon/block/timeline/components/timeline/timeline.ts @@ -19,7 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; -import { AddonBlockComponent } from '../../../classes/block-component'; +import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; import { AddonBlockTimelineProvider } from '../../providers/timeline'; /** @@ -29,7 +29,7 @@ import { AddonBlockTimelineProvider } from '../../providers/timeline'; selector: 'addon-block-timeline', templateUrl: 'addon-block-timeline.html' }) -export class AddonBlockTimelineComponent extends AddonBlockComponent implements OnInit { +export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implements OnInit { sort = 'sortbydates'; filter = 'next30days'; currentSite: any; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6fe4d4970..a76d66cba 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -75,6 +75,7 @@ import { CoreSitePluginsModule } from '@core/siteplugins/siteplugins.module'; import { CoreCompileModule } from '@core/compile/compile.module'; import { CoreQuestionModule } from '@core/question/question.module'; import { CoreCommentsModule } from '@core/comments/comments.module'; +import { CoreBlockModule } from '@core/block/block.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -188,6 +189,7 @@ export const CORE_PROVIDERS: any[] = [ CoreCompileModule, CoreQuestionModule, CoreCommentsModule, + CoreBlockModule, AddonBadgesModule, AddonCalendarModule, AddonCompetencyModule, diff --git a/src/core/block/block.module.ts b/src/core/block/block.module.ts new file mode 100644 index 000000000..2448c6e1d --- /dev/null +++ b/src/core/block/block.module.ts @@ -0,0 +1,34 @@ +// (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 { CoreBlockDelegate } from './providers/delegate'; +import { CoreBlockDefaultHandler } from './providers/default-block-handler'; + +// List of providers (without handlers). +export const CORE_BLOCK_PROVIDERS: any[] = [ + CoreBlockDelegate +]; + +@NgModule({ + declarations: [], + imports: [ + ], + providers: [ + CoreBlockDelegate, + CoreBlockDefaultHandler + ], + exports: [] +}) +export class CoreBlockModule {} diff --git a/src/addon/block/classes/block-component.ts b/src/core/block/classes/base-block-component.ts similarity index 97% rename from src/addon/block/classes/block-component.ts rename to src/core/block/classes/base-block-component.ts index 3a2282d1c..1fe1dc326 100644 --- a/src/addon/block/classes/block-component.ts +++ b/src/core/block/classes/base-block-component.ts @@ -17,9 +17,9 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** - * Template class to easily create AddonBlockComponent of blocks. + * Template class to easily create components for blocks. */ -export class AddonBlockComponent implements OnInit { +export class CoreBlockBaseComponent implements OnInit { loaded: boolean; // If the component has been loaded. protected fetchContentDefaultError: string; // Default error to show when loading contents. diff --git a/src/core/block/classes/base-block-handler.ts b/src/core/block/classes/base-block-handler.ts new file mode 100644 index 000000000..85fe49995 --- /dev/null +++ b/src/core/block/classes/base-block-handler.ts @@ -0,0 +1,56 @@ +// (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 { Injector } from '@angular/core'; +import { CoreBlockHandler, CoreBlockHandlerData } from '../providers/delegate'; + +/** + * Base handler for blocks. + * + * This class is needed because parent classes cannot have @Injectable in Angular v6, so the default handler cannot be a + * parent class. + */ +export class CoreBlockBaseHandler implements CoreBlockHandler { + name = 'CoreBlockBase'; + blockName = 'base'; + + constructor() { + // Nothing to do. + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Returns the data needed to render the block. + * + * @param {Injector} injector Injector. + * @param {any} block The block to render. + * @param {string} contextLevel The context where the block will be used. + * @param {number} instanceId The instance ID associated with the context level. + * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + */ + getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number) + : CoreBlockHandlerData | Promise { + + // To be overridden. + return; + } +} diff --git a/src/core/block/components/block/block.ts b/src/core/block/components/block/block.ts new file mode 100644 index 000000000..c27ddf181 --- /dev/null +++ b/src/core/block/components/block/block.ts @@ -0,0 +1,72 @@ +// (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, Input, OnInit, Injector } from '@angular/core'; +import { CoreBlockDelegate } from '../../providers/delegate'; + +/** + * Component to render a block. + */ +@Component({ + selector: 'core-block', + templateUrl: 'core-block.html' +}) +export class CoreBlockComponent implements OnInit { + @Input() block: any; // The block to render. + @Input() contextLevel: string; // The context where the block will be used. + @Input() instanceId: number; // The instance ID associated with the context level. + @Input() extraData: any; // Any extra data to be passed to the block. + + title: string; // The title of the block. + componentClass: any; // The class of the component to render. + data: any = {}; // Data to pass to the component. + class: string; // CSS class to apply to the block. + loaded = false; + + constructor(protected injector: Injector, protected blockDelegate: CoreBlockDelegate) { } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (!this.block) { + this.loaded = true; + + return; + } + + // Get the data to render the block. + this.blockDelegate.getBlockDisplayData(this.injector, this.block, this.contextLevel, this.instanceId).then((data) => { + if (!data) { + // Block not supported, don't render it. + return; + } + + this.title = data.title; + this.class = data.class; + this.componentClass = data.component; + + // Set up the data needed by the block component. + this.data = Object.assign({ + block: this.block, + contextLevel: this.contextLevel, + instanceId: this.instanceId, + }, this.extraData || {}, data.componentData || {}); + }).catch(() => { + // Ignore errors. + }).finally(() => { + this.loaded = true; + }); + } +} diff --git a/src/core/block/components/block/core-block.html b/src/core/block/components/block/core-block.html new file mode 100644 index 000000000..80ef357de --- /dev/null +++ b/src/core/block/components/block/core-block.html @@ -0,0 +1,6 @@ + +
+ {{ title | translate }} + + +
diff --git a/src/core/block/components/components.module.ts b/src/core/block/components/components.module.ts new file mode 100644 index 000000000..512729b8e --- /dev/null +++ b/src/core/block/components/components.module.ts @@ -0,0 +1,38 @@ +// (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 { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreBlockComponent } from './block/block'; +import { CoreComponentsModule } from '@components/components.module'; + +@NgModule({ + declarations: [ + CoreBlockComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule + ], + providers: [ + ], + exports: [ + CoreBlockComponent + ] +}) +export class CoreBlockComponentsModule {} diff --git a/src/core/block/providers/default-block-handler.ts b/src/core/block/providers/default-block-handler.ts new file mode 100644 index 000000000..c4d04891f --- /dev/null +++ b/src/core/block/providers/default-block-handler.ts @@ -0,0 +1,29 @@ +// (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 } from '@angular/core'; +import { CoreBlockBaseHandler } from '../classes/base-block-handler'; + +/** + * Default handler used when a block type doesn't have a specific implementation. + */ +@Injectable() +export class CoreBlockDefaultHandler extends CoreBlockBaseHandler { + name = 'CoreBlockDefault'; + type = 'default'; + + constructor() { + super(); + } +} diff --git a/src/core/block/providers/delegate.ts b/src/core/block/providers/delegate.ts new file mode 100644 index 000000000..b6d605bfa --- /dev/null +++ b/src/core/block/providers/delegate.ts @@ -0,0 +1,122 @@ +// (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 { CoreLoggerProvider } from '@providers/logger'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { CoreBlockDefaultHandler } from './default-block-handler'; + +/** + * Interface that all blocks must implement. + */ +export interface CoreBlockHandler extends CoreDelegateHandler { + /** + * Name of the block the handler supports. E.g. 'activity_modules'. + * @type {string} + */ + blockName: string; + + /** + * Returns the data needed to render the block. + * + * @param {Injector} injector Injector. + * @param {any} block The block to render. + * @param {string} contextLevel The context where the block will be used. + * @param {number} instanceId The instance ID associated with the context level. + * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + */ + getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number) + : CoreBlockHandlerData | Promise; +} + +/** + * Data needed to render a block. It's returned by the handler. + */ +export interface CoreBlockHandlerData { + /** + * Title to display for the block. + * @type {string} + */ + title: string; + + /** + * Class to add to the displayed block. + * @type {string} + */ + class?: string; + + /** + * The component to render the contents of the block. + * It's recommended to return the class of the component, but you can also return an instance of the component. + * @type {any} + */ + component: any; + + /** + * Data to pass to the component. All the properties in this object will be passed to the component as inputs. + * @type {any} + */ + componentData?: any; +} + +/** + * Delegate to register block handlers. + */ +@Injectable() +export class CoreBlockDelegate extends CoreDelegate { + + protected handlerNameProperty = 'blockName'; + + constructor(logger: CoreLoggerProvider, sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, + protected defaultHandler: CoreBlockDefaultHandler) { + super('CoreBlockDelegate', logger, sitesProvider, eventsProvider); + } + + /** + * Get the display data for a certain block. + * + * @param {Injector} injector Injector. + * @param {any} block The block to render. + * @param {string} contextLevel The context where the block will be used. + * @param {number} instanceId The instance ID associated with the context level. + * @return {Promise} Promise resolved with the display data. + */ + getBlockDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number): Promise { + return Promise.resolve(this.executeFunctionOnEnabled(block.name, 'getDisplayData', [injector, block])); + } + + /** + * Check if any of the blocks in a list is supported. + * + * @param {any[]} blocks The list of blocks. + * @return {boolean} Whether any of the blocks is supported. + */ + hasSupportedBlock(blocks: any[]): boolean { + blocks = blocks || []; + + return !!blocks.find((block) => { return this.isBlockSupported(block.name); }); + } + + /** + * Check if a block is supported. + * + * @param {string} name Block "name". E.g. 'activity_modules'. + * @return {boolean} Whether it's supported. + */ + isBlockSupported(name: string): boolean { + return this.hasHandler(name, true); + } +} diff --git a/src/core/compile/providers/compile.ts b/src/core/compile/providers/compile.ts index d49667352..869b5a421 100644 --- a/src/core/compile/providers/compile.ts +++ b/src/core/compile/providers/compile.ts @@ -23,6 +23,7 @@ import { CoreLoggerProvider } from '@providers/logger'; // Import core providers. import { CORE_PROVIDERS } from '@app/app.module'; +import { CORE_BLOCK_PROVIDERS } from '@core/block/block.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'; @@ -70,6 +71,7 @@ import { CoreSitePluginsDirectivesModule } from '@core/siteplugins/directives/di import { CoreSiteHomeComponentsModule } from '@core/sitehome/components/components.module'; import { CoreUserComponentsModule } from '@core/user/components/components.module'; import { CoreQuestionComponentsModule } from '@core/question/components/components.module'; +import { CoreBlockComponentsModule } from '@core/block/components/components.module'; // Import some components listed in entryComponents so they can be injected dynamically. import { CoreCourseUnsupportedModuleComponent } from '@core/course/components/unsupported-module/unsupported-module'; @@ -139,7 +141,7 @@ export class CoreCompileProvider { IonicModule, TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, CorePipesModule, CoreCourseComponentsModule, CoreCoursesComponentsModule, CoreSiteHomeComponentsModule, CoreUserComponentsModule, CoreCourseDirectivesModule, CoreSitePluginsDirectivesModule, CoreQuestionComponentsModule, AddonModAssignComponentsModule, - AddonModWorkshopComponentsModule + AddonModWorkshopComponentsModule, CoreBlockComponentsModule ]; constructor(protected injector: Injector, logger: CoreLoggerProvider, compilerFactory: JitCompilerFactory) { @@ -227,7 +229,7 @@ export class CoreCompileProvider { .concat(ADDON_MOD_QUIZ_PROVIDERS).concat(ADDON_MOD_RESOURCE_PROVIDERS).concat(ADDON_MOD_SCORM_PROVIDERS) .concat(ADDON_MOD_SURVEY_PROVIDERS).concat(ADDON_MOD_URL_PROVIDERS).concat(ADDON_MOD_WIKI_PROVIDERS) .concat(ADDON_MOD_WORKSHOP_PROVIDERS).concat(ADDON_NOTES_PROVIDERS).concat(ADDON_NOTIFICATIONS_PROVIDERS) - .concat(ADDON_PUSHNOTIFICATIONS_PROVIDERS).concat(ADDON_REMOTETHEMES_PROVIDERS); + .concat(ADDON_PUSHNOTIFICATIONS_PROVIDERS).concat(ADDON_REMOTETHEMES_PROVIDERS).concat(CORE_BLOCK_PROVIDERS); // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance. for (const i in providers) {