MOBILE-1874 block: Create a delegate and component for blocks

main
dpalou 2018-10-16 17:53:09 +02:00
parent aa600f2d92
commit 2e8424d3a2
12 changed files with 369 additions and 8 deletions

View File

@ -23,7 +23,7 @@ import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCourseHelperProvider } from '@core/course/providers/helper';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion'; 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. * Component to render a my overview block.
@ -32,7 +32,7 @@ import { AddonBlockComponent } from '../../classes/block-component';
selector: 'addon-block-myoverview', selector: 'addon-block-myoverview',
templateUrl: 'addon-block-myoverview.html' 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; @ViewChild('searchbar') searchbar: Searchbar;
courses = { courses = {

View File

@ -19,7 +19,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; 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'; import { AddonBlockTimelineProvider } from '../../providers/timeline';
/** /**
@ -29,7 +29,7 @@ import { AddonBlockTimelineProvider } from '../../providers/timeline';
selector: 'addon-block-timeline', selector: 'addon-block-timeline',
templateUrl: 'addon-block-timeline.html' templateUrl: 'addon-block-timeline.html'
}) })
export class AddonBlockTimelineComponent extends AddonBlockComponent implements OnInit { export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implements OnInit {
sort = 'sortbydates'; sort = 'sortbydates';
filter = 'next30days'; filter = 'next30days';
currentSite: any; currentSite: any;

View File

@ -75,6 +75,7 @@ import { CoreSitePluginsModule } from '@core/siteplugins/siteplugins.module';
import { CoreCompileModule } from '@core/compile/compile.module'; import { CoreCompileModule } from '@core/compile/compile.module';
import { CoreQuestionModule } from '@core/question/question.module'; import { CoreQuestionModule } from '@core/question/question.module';
import { CoreCommentsModule } from '@core/comments/comments.module'; import { CoreCommentsModule } from '@core/comments/comments.module';
import { CoreBlockModule } from '@core/block/block.module';
// Addon modules. // Addon modules.
import { AddonBadgesModule } from '@addon/badges/badges.module'; import { AddonBadgesModule } from '@addon/badges/badges.module';
@ -188,6 +189,7 @@ export const CORE_PROVIDERS: any[] = [
CoreCompileModule, CoreCompileModule,
CoreQuestionModule, CoreQuestionModule,
CoreCommentsModule, CoreCommentsModule,
CoreBlockModule,
AddonBadgesModule, AddonBadgesModule,
AddonCalendarModule, AddonCalendarModule,
AddonCompetencyModule, AddonCompetencyModule,

View File

@ -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 {}

View File

@ -17,9 +17,9 @@ import { CoreLoggerProvider } from '@providers/logger';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; 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. loaded: boolean; // If the component has been loaded.
protected fetchContentDefaultError: string; // Default error to show when loading contents. protected fetchContentDefaultError: string; // Default error to show when loading contents.

View File

@ -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<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean | Promise<boolean> {
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<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
// To be overridden.
return;
}
}

View File

@ -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;
});
}
}

View File

@ -0,0 +1,6 @@
<!-- Only render the block if it's supported. -->
<div *ngIf="loaded && componentClass" class="{{class}}">
<ion-item-divider color="light" *ngIf="title">{{ title | translate }}</ion-item-divider>
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
</div>

View File

@ -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 {}

View File

@ -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();
}
}

View File

@ -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<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData>;
}
/**
* 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<CoreBlockHandlerData>} Promise resolved with the display data.
*/
getBlockDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number): Promise<CoreBlockHandlerData> {
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);
}
}

View File

@ -23,6 +23,7 @@ import { CoreLoggerProvider } from '@providers/logger';
// Import core providers. // Import core providers.
import { CORE_PROVIDERS } from '@app/app.module'; 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_CONTENTLINKS_PROVIDERS } from '@core/contentlinks/contentlinks.module';
import { CORE_COURSE_PROVIDERS } from '@core/course/course.module'; import { CORE_COURSE_PROVIDERS } from '@core/course/course.module';
import { CORE_COURSES_PROVIDERS } from '@core/courses/courses.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 { CoreSiteHomeComponentsModule } from '@core/sitehome/components/components.module';
import { CoreUserComponentsModule } from '@core/user/components/components.module'; import { CoreUserComponentsModule } from '@core/user/components/components.module';
import { CoreQuestionComponentsModule } from '@core/question/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 some components listed in entryComponents so they can be injected dynamically.
import { CoreCourseUnsupportedModuleComponent } from '@core/course/components/unsupported-module/unsupported-module'; import { CoreCourseUnsupportedModuleComponent } from '@core/course/components/unsupported-module/unsupported-module';
@ -139,7 +141,7 @@ export class CoreCompileProvider {
IonicModule, TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, CorePipesModule, IonicModule, TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, CorePipesModule,
CoreCourseComponentsModule, CoreCoursesComponentsModule, CoreSiteHomeComponentsModule, CoreUserComponentsModule, CoreCourseComponentsModule, CoreCoursesComponentsModule, CoreSiteHomeComponentsModule, CoreUserComponentsModule,
CoreCourseDirectivesModule, CoreSitePluginsDirectivesModule, CoreQuestionComponentsModule, AddonModAssignComponentsModule, CoreCourseDirectivesModule, CoreSitePluginsDirectivesModule, CoreQuestionComponentsModule, AddonModAssignComponentsModule,
AddonModWorkshopComponentsModule AddonModWorkshopComponentsModule, CoreBlockComponentsModule
]; ];
constructor(protected injector: Injector, logger: CoreLoggerProvider, compilerFactory: JitCompilerFactory) { 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_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_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_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. // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
for (const i in providers) { for (const i in providers) {