MOBILE-2376 siteplugins: Support question behaviours

main
Dani Palou 2018-05-09 10:56:16 +02:00
parent 02fd27d3d4
commit bf658df6b4
23 changed files with 292 additions and 114 deletions

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourAdaptiveHandler implements CoreQuestionBehaviourHand
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourAdaptiveNoPenaltyHandler implements CoreQuestionBeha
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
import { CoreQuestionState } from '@core/question/providers/question';
@ -55,11 +55,12 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
if (this.questionHelper.extractQbehaviourCBM(question)) {
return [AddonQbehaviourDeferredCBMComponent];
}

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { AddonQbehaviourDeferredCBMComponent } from '@addon/qbehaviour/deferredcbm/component/deferredcbm';
@ -35,11 +35,12 @@ export class AddonQbehaviourImmediateCBMHandler implements CoreQuestionBehaviour
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);
if (this.questionHelper.extractQbehaviourCBM(question)) {

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourImmediateFeedbackHandler implements CoreQuestionBeha
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -52,11 +52,12 @@ export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehavi
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
if (this.questionHelper.extractQbehaviourSeenInput(question)) {
return [AddonQbehaviourInformationItemComponent];
}

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourInteractiveHandler implements CoreQuestionBehaviourH
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourInteractiveCountbackHandler implements CoreQuestionB
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -14,11 +14,10 @@
import {
Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ViewChild, ComponentRef, SimpleChange, ChangeDetectorRef,
ElementRef, Optional
ElementRef, Optional, Output, EventEmitter
} from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreCompileProvider } from '../../providers/compile';
import { BehaviorSubject } from 'rxjs';
/**
* This component has a behaviour similar to $compile for AngularJS. Given an HTML code, it will compile it so all its
@ -43,18 +42,17 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
@Input() text: string; // The HTML text to display.
@Input() javascript: string; // The Javascript to execute in the component.
@Input() jsData: any; // Data to pass to the fake component.
@Output() created: EventEmitter<any> = new EventEmitter(); // Will emit an event when the component is instantiated.
// Get the container where to put the content.
@ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
protected componentRef: ComponentRef<any>;
protected element;
componentObservable: BehaviorSubject<any>; // An observable to notify observers when the component is instantiated.
constructor(protected compileProvider: CoreCompileProvider, protected cdr: ChangeDetectorRef, element: ElementRef,
@Optional() protected navCtrl: NavController) {
this.element = element.nativeElement;
this.componentObservable = new BehaviorSubject<any>(null);
}
/**
@ -70,7 +68,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
if (factory) {
// Create the component.
this.componentRef = this.container.createComponent(factory);
this.componentObservable.next(this.componentRef.instance);
this.created.emit(this.componentRef.instance);
}
});
}

View File

@ -75,6 +75,9 @@ import { CoreCourseFormatSingleActivityComponent } from '@core/course/formats/si
import { CoreSitePluginsModuleIndexComponent } from '@core/siteplugins/components/module-index/module-index';
import { CoreSitePluginsCourseOptionComponent } from '@core/siteplugins/components/course-option/course-option';
import { CoreSitePluginsCourseFormatComponent } from '@core/siteplugins/components/course-format/course-format';
import { CoreSitePluginsQuestionComponent } from '@core/siteplugins/components/question/question';
import { CoreSitePluginsQuestionBehaviourComponent } from '@core/siteplugins/components/question-behaviour/question-behaviour';
import { CoreSitePluginsUserProfileFieldComponent } from '@core/siteplugins/components/user-profile-field/user-profile-field';
/**
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
@ -203,6 +206,9 @@ export class CoreCompileProvider {
instance['CoreSitePluginsModuleIndexComponent'] = CoreSitePluginsModuleIndexComponent;
instance['CoreSitePluginsCourseOptionComponent'] = CoreSitePluginsCourseOptionComponent;
instance['CoreSitePluginsCourseFormatComponent'] = CoreSitePluginsCourseFormatComponent;
instance['CoreSitePluginsQuestionComponent'] = CoreSitePluginsQuestionComponent;
instance['CoreSitePluginsQuestionBehaviourComponent'] = CoreSitePluginsQuestionBehaviourComponent;
instance['CoreSitePluginsUserProfileFieldComponent'] = CoreSitePluginsUserProfileFieldComponent;
}
/**

View File

@ -123,11 +123,15 @@ export class CoreQuestionComponent implements OnInit {
promise.then(() => {
// Handle behaviour.
this.behaviourDelegate.handleQuestion(this.question.preferredBehaviour, this.question).then((comps) => {
this.behaviourDelegate.handleQuestion(this.injector, this.question.preferredBehaviour, this.question)
.then((comps) => {
this.behaviourComponents = comps;
});
this.questionHelper.extractQbehaviourRedoButton(this.question);
}).finally(() => {
this.question.html = this.domUtils.removeElementFromHtml(this.question.html, '.im-controls');
this.loaded = true;
});
this.questionHelper.extractQbehaviourRedoButton(this.question);
// Extract the validation error of the question.
this.question.validationError = this.questionHelper.getValidationErrorFromHtml(this.question.html);
@ -138,8 +142,6 @@ export class CoreQuestionComponent implements OnInit {
// Try to extract the feedback and comment for the question.
this.questionHelper.extractQuestionFeedback(this.question);
this.questionHelper.extractQuestionComment(this.question);
this.loaded = true;
});
}
}).catch(() => {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
@ -48,11 +48,12 @@ export interface CoreQuestionBehaviourHandler extends CoreDelegateHandler {
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion?(question: any): any[] | Promise<any[]>;
handleQuestion?(injector: Injector, question: any): any[] | Promise<any[]>;
}
/**
@ -90,14 +91,15 @@ export class CoreQuestionBehaviourDelegate extends CoreDelegate {
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return a directive to render it.
*
* @param {Injector} injector Injector.
* @param {string} behaviour Default behaviour.
* @param {any} question The question.
* @return {Promise<any[]>} Promise resolved with components to render some extra data in the question.
*/
handleQuestion(behaviour: string, question: any): Promise<any[]> {
handleQuestion(injector: Injector, behaviour: string, question: any): Promise<any[]> {
behaviour = this.questionDelegate.getBehaviourForQuestion(question, behaviour);
return Promise.resolve(this.executeFunctionOnEnabled(behaviour, 'handleQuestion', [question]));
return Promise.resolve(this.executeFunctionOnEnabled(behaviour, 'handleQuestion', [injector, question]));
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from './behaviour-delegate';
import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question';
@ -46,11 +46,12 @@ export class CoreQuestionBehaviourDefaultHandler implements CoreQuestionBehaviou
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Nothing to do.
return;
}

View File

@ -0,0 +1,59 @@
// (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 { CoreSitePluginsProvider } from '../providers/siteplugins';
/**
* Base class for components that will display a component using core-compile-html and want to call a
* componentInit function returned by the handler JS.
*/
export class CoreSitePluginsCompileInitComponent {
content = ''; // Content.
jsData: any; // Data to pass to the component.
protected handlerSchema: any; // The handler data.
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
/**
* Function called when the component is created.
*
* @param {any} instance The component instance.
*/
componentCreated(instance: any): void {
// Check if the JS defined an init function.
if (instance && this.handlerSchema && this.handlerSchema.methodJSResult &&
this.handlerSchema.methodJSResult.componentInit) {
this.handlerSchema.methodJSResult.componentInit.apply(instance);
}
}
/**
* Get the handler data.
*
* @param {string} name The name of the handler.
*/
getHandlerData(name: string): void {
// Retrieve the handler data.
const handler = this.sitePluginsProvider.getSitePluginHandler(name);
this.handlerSchema = handler && handler.handlerSchema;
if (this.handlerSchema) {
// Load first template.
if (this.handlerSchema.methodTemplates && this.handlerSchema.methodTemplates.length) {
this.content = handler.handlerSchema.methodTemplates[0].html;
}
}
}
}

View File

@ -0,0 +1,44 @@
// (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 { CoreQuestionBehaviourDefaultHandler } from '@core/question/providers/default-behaviour-handler';
import { CoreSitePluginsQuestionBehaviourComponent } from '../components/question-behaviour/question-behaviour';
import { CoreQuestionProvider } from '@core/question/providers/question';
/**
* Handler to display a question behaviour site plugin.
*/
export class CoreSitePluginsQuestionBehaviourHandler extends CoreQuestionBehaviourDefaultHandler {
constructor(questionProvider: CoreQuestionProvider, public name: string, public type: string, public hasTemplate: boolean) {
super(questionProvider);
}
/**
* Handle a question behaviour.
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
if (this.hasTemplate) {
return [CoreSitePluginsQuestionBehaviourComponent];
}
}
}

View File

@ -24,6 +24,7 @@ import { CoreSitePluginsCourseOptionComponent } from './course-option/course-opt
import { CoreSitePluginsCourseFormatComponent } from './course-format/course-format';
import { CoreSitePluginsUserProfileFieldComponent } from './user-profile-field/user-profile-field';
import { CoreSitePluginsQuestionComponent } from './question/question';
import { CoreSitePluginsQuestionBehaviourComponent } from './question-behaviour/question-behaviour';
@NgModule({
declarations: [
@ -32,7 +33,8 @@ import { CoreSitePluginsQuestionComponent } from './question/question';
CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent,
CoreSitePluginsQuestionComponent
CoreSitePluginsQuestionComponent,
CoreSitePluginsQuestionBehaviourComponent
],
imports: [
CommonModule,
@ -49,14 +51,16 @@ import { CoreSitePluginsQuestionComponent } from './question/question';
CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent,
CoreSitePluginsQuestionComponent
CoreSitePluginsQuestionComponent,
CoreSitePluginsQuestionBehaviourComponent
],
entryComponents: [
CoreSitePluginsModuleIndexComponent,
CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent,
CoreSitePluginsQuestionComponent
CoreSitePluginsQuestionComponent,
CoreSitePluginsQuestionBehaviourComponent
]
})
export class CoreSitePluginsComponentsModule {}

View File

@ -0,0 +1 @@
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -0,0 +1,58 @@
// (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, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays a question behaviour created using a site plugin.
*/
@Component({
selector: 'core-site-plugins-question-behaviour',
templateUrl: 'question-behaviour.html',
})
export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() question: any; // The question where the behaviour will be rendered.
@Input() component: string; // The component the question belongs to.
@Input() componentId: number; // ID of the component the question belongs to.
@Input() attemptId: number; // Attempt ID.
@Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Pass the input and output data to the component.
this.jsData = {
question: this.question,
component: this.component,
componentId: this.componentId,
attemptId: this.attemptId,
offlineEnabled: this.offlineEnabled,
buttonClicked: this.buttonClicked,
onAbort: this.onAbort
};
if (this.question) {
this.getHandlerData('qbehaviour_' + this.question.preferredBehaviour);
}
}
}

View File

@ -1 +1 @@
<core-compile-html [text]="content" [jsData]="jsData"></core-compile-html>
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, OnDestroy } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreCompileHtmlComponent } from '@core/compile/components/compile-html/compile-html';
import { Subscription } from 'rxjs';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays a question created using a site plugin.
@ -24,7 +23,7 @@ import { Subscription } from 'rxjs';
selector: 'core-site-plugins-question',
templateUrl: 'question.html',
})
export class CoreSitePluginsQuestionComponent implements OnInit, OnDestroy {
export class CoreSitePluginsQuestionComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() question: any; // The question to render.
@Input() component: string; // The component the question belongs to.
@Input() componentId: number; // ID of the component the question belongs to.
@ -33,13 +32,9 @@ export class CoreSitePluginsQuestionComponent implements OnInit, OnDestroy {
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
@ViewChild(CoreCompileHtmlComponent) compileComponent: CoreCompileHtmlComponent;
content = ''; // Content.
jsData;
protected componentObserver: Subscription;
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
@ -57,34 +52,7 @@ export class CoreSitePluginsQuestionComponent implements OnInit, OnDestroy {
};
if (this.question) {
// Retrieve the handler data.
const handler = this.sitePluginsProvider.getSitePluginHandler('qtype_' + this.question.type),
handlerSchema = handler && handler.handlerSchema;
if (handlerSchema) {
// Load first template.
if (handlerSchema.methodTemplates && handlerSchema.methodTemplates.length) {
this.content = handler.handlerSchema.methodTemplates[0].html;
this.getHandlerData('qtype_' + this.question.type);
}
// Wait for the instance to be created.
if (this.compileComponent && this.compileComponent.componentObservable &&
handlerSchema.methodJSResult && handlerSchema.methodJSResult.componentInit) {
this.componentObserver = this.compileComponent.componentObservable.subscribe((instance) => {
if (instance) {
// Instance created, call component init.
handlerSchema.methodJSResult.componentInit.apply(instance);
}
});
}
}
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.componentObserver && this.componentObserver.unsubscribe();
}
}

View File

@ -1 +1 @@
<core-compile-html [text]="content" [jsData]="jsData"></core-compile-html>
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, Input, ViewChild, OnDestroy } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreCompileHtmlComponent } from '@core/compile/components/compile-html/compile-html';
import { Subscription } from 'rxjs';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays a user profile field created using a site plugin.
@ -24,20 +23,16 @@ import { Subscription } from 'rxjs';
selector: 'core-site-plugins-user-profile-field',
templateUrl: 'user-profile-field.html',
})
export class CoreSitePluginsUserProfileFieldComponent implements OnInit, OnDestroy {
export class CoreSitePluginsUserProfileFieldComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() field: any; // The profile field to be rendered.
@Input() signup = false; // True if editing the field in signup. Defaults to false.
@Input() edit = false; // True if editing the field. Defaults to false.
@Input() form?: any; // Form where to add the form control. Required if edit=true or signup=true.
@Input() registerAuth?: string; // Register auth method. E.g. 'email'.
@ViewChild(CoreCompileHtmlComponent) compileComponent: CoreCompileHtmlComponent;
content = ''; // Content.
jsData;
protected componentObserver: Subscription;
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
@ -54,34 +49,7 @@ export class CoreSitePluginsUserProfileFieldComponent implements OnInit, OnDestr
};
if (this.field) {
// Retrieve the handler data.
const handler = this.sitePluginsProvider.getSitePluginHandler(this.field.type || this.field.datatype),
handlerSchema = handler && handler.handlerSchema;
if (handlerSchema) {
// Load first template.
if (handlerSchema.methodTemplates && handlerSchema.methodTemplates.length) {
this.content = handler.handlerSchema.methodTemplates[0].html;
this.getHandlerData(this.field.type || this.field.datatype);
}
// Wait for the instance to be created.
if (this.compileComponent && this.compileComponent.componentObservable &&
handlerSchema.methodJSResult && handlerSchema.methodJSResult.componentInit) {
this.componentObserver = this.compileComponent.componentObservable.subscribe((instance) => {
if (instance) {
// Instance created, call component init.
handlerSchema.methodJSResult.componentInit.apply(instance);
}
});
}
}
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.componentObserver && this.componentObserver.unsubscribe();
}
}

View File

@ -25,6 +25,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitePluginsProvider } from './siteplugins';
import { CoreCompileProvider } from '@core/compile/providers/compile';
import { CoreQuestionProvider } from '@core/question/providers/question';
// Delegates
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
@ -36,6 +37,7 @@ import { CoreUserDelegate } from '@core/user/providers/user-delegate';
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
import { CoreSettingsDelegate } from '@core/settings/providers/delegate';
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate';
import { AddonMessageOutputDelegate } from '@addon/messageoutput/providers/delegate';
// Handler classes.
@ -48,6 +50,7 @@ import { CoreSitePluginsUserProfileHandler } from '../classes/user-handler';
import { CoreSitePluginsUserProfileFieldHandler } from '../classes/user-profile-field-handler';
import { CoreSitePluginsSettingsHandler } from '../classes/settings-handler';
import { CoreSitePluginsQuestionHandler } from '../classes/question-handler';
import { CoreSitePluginsQuestionBehaviourHandler } from '../classes/question-behaviour-handler';
import { CoreSitePluginsMessageOutputHandler } from '../classes/message-output-handler';
/**
@ -72,6 +75,7 @@ export class CoreSitePluginsHelperProvider {
private courseFormatDelegate: CoreCourseFormatDelegate, private profileFieldDelegate: CoreUserProfileFieldDelegate,
private textUtils: CoreTextUtilsProvider, private filepoolProvider: CoreFilepoolProvider,
private settingsDelegate: CoreSettingsDelegate, private questionDelegate: CoreQuestionDelegate,
private questionBehaviourDelegate: CoreQuestionBehaviourDelegate, private questionProvider: CoreQuestionProvider,
private messageOutputDelegate: AddonMessageOutputDelegate) {
this.logger = logger.getInstance('CoreSitePluginsHelperProvider');
@ -450,6 +454,10 @@ export class CoreSitePluginsHelperProvider {
promise = Promise.resolve(this.registerQuestionHandler(plugin, handlerName, handlerSchema, result));
break;
case 'CoreQuestionBehaviourDelegate':
promise = Promise.resolve(this.registerQuestionBehaviourHandler(plugin, handlerName, handlerSchema, result));
break;
case 'AddonMessageOutputDelegate':
promise = Promise.resolve(this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, result));
break;
@ -666,6 +674,57 @@ export class CoreSitePluginsHelperProvider {
});
}
/**
* Given a handler in an plugin, register it in the question behaviour delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @param {any} initResult Result of the init WS call.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerQuestionBehaviourHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any)
: string | Promise<string> {
if (!handlerSchema.method) {
// Required data not provided, stop.
this.logger.warn('Ignore site plugin because it doesn\'t provide method', plugin, handlerSchema);
return;
}
this.logger.debug('Register site plugin in question behaviour delegate:', plugin, handlerSchema, initResult);
// Execute the main method and its JS. The template returned will be used in the question component.
return this.executeMethodAndJS(plugin, handlerSchema.method).then((result) => {
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
type = plugin.component.replace('qbehaviour_', ''),
behaviourHandler = new CoreSitePluginsQuestionBehaviourHandler(this.questionProvider, uniqueName, type,
result.templates.length);
// Store in handlerSchema some data required by the component.
handlerSchema.methodTemplates = result.templates;
handlerSchema.methodJSResult = result.jsResult;
if (result && result.jsResult) {
// Override default handler functions with the result of the method JS.
for (const property in behaviourHandler) {
if (property != 'constructor' && typeof behaviourHandler[property] == 'function' &&
typeof result.jsResult[property] == 'function') {
behaviourHandler[property] = result.jsResult[property].bind(behaviourHandler);
}
}
}
this.questionBehaviourDelegate.registerHandler(behaviourHandler);
return plugin.component;
}).catch((err) => {
this.logger.error('Error executing main method for question', handlerSchema.method, err);
});
}
/**
* Given a handler in an plugin, register it in the settings delegate.
*