diff --git a/src/addons/addons.module.ts b/src/addons/addons.module.ts index 7b2688efe..ad92c6f09 100644 --- a/src/addons/addons.module.ts +++ b/src/addons/addons.module.ts @@ -24,6 +24,7 @@ import { AddonNotificationsModule } from './notifications/notifications.module'; import { AddonMessageOutputModule } from './messageoutput/messageoutput.module'; import { AddonMessagesModule } from './messages/messages.module'; import { AddonModModule } from './mod/mod.module'; +import { AddonQbehaviourModule } from './qbehaviour/qbehaviour.module'; @NgModule({ imports: [ @@ -37,6 +38,7 @@ import { AddonModModule } from './mod/mod.module'; AddonNotificationsModule, AddonMessageOutputModule, AddonModModule, + AddonQbehaviourModule, ], }) export class AddonsModule {} diff --git a/src/addons/block/recentlyaccesseditems/services/block-handler.ts b/src/addons/block/recentlyaccesseditems/services/block-handler.ts index cfc9ddf00..747f672ad 100644 --- a/src/addons/block/recentlyaccesseditems/services/block-handler.ts +++ b/src/addons/block/recentlyaccesseditems/services/block-handler.ts @@ -30,7 +30,6 @@ export class AddonBlockRecentlyAccessedItemsHandlerService extends CoreBlockBase /** * Returns the data needed to render the block. * - * @param injector Injector. * @param block The block to render. * @param contextLevel The context where the block will be used. * @param instanceId The instance ID associated with the context level. diff --git a/src/addons/qbehaviour/adaptive/adaptive.module.ts b/src/addons/qbehaviour/adaptive/adaptive.module.ts new file mode 100644 index 000000000..7858d6057 --- /dev/null +++ b/src/addons/qbehaviour/adaptive/adaptive.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourAdaptiveHandler } from './services/handlers/adaptive'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourAdaptiveHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourAdaptiveModule {} diff --git a/src/addons/qbehaviour/adaptive/services/handlers/adaptive.ts b/src/addons/qbehaviour/adaptive/services/handlers/adaptive.ts new file mode 100644 index 000000000..4c10c6023 --- /dev/null +++ b/src/addons/qbehaviour/adaptive/services/handlers/adaptive.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreQuestionBehaviourHandler } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionQuestionParsed } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support adaptive question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourAdaptiveHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourAdaptive'; + type = 'adaptive'; + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void { + // Just extract the button, it doesn't need any specific component. + CoreQuestionHelper.instance.extractQbehaviourButtons(question); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourAdaptiveHandler extends makeSingleton(AddonQbehaviourAdaptiveHandlerService) {} diff --git a/src/addons/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts b/src/addons/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts new file mode 100644 index 000000000..c8de10ff7 --- /dev/null +++ b/src/addons/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourAdaptiveNoPenaltyHandler } from './services/handlers/adaptivenopenalty'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourAdaptiveNoPenaltyHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourAdaptiveNoPenaltyModule {} diff --git a/src/addons/qbehaviour/adaptivenopenalty/services/handlers/adaptivenopenalty.ts b/src/addons/qbehaviour/adaptivenopenalty/services/handlers/adaptivenopenalty.ts new file mode 100644 index 000000000..85af22bb1 --- /dev/null +++ b/src/addons/qbehaviour/adaptivenopenalty/services/handlers/adaptivenopenalty.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreQuestionBehaviourHandler } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionQuestionParsed } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support adaptive no penalty question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourAdaptiveNoPenaltyHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourAdaptiveNoPenalty'; + type = 'adaptivenopenalty'; + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void { + // Just extract the button, it doesn't need any specific component. + CoreQuestionHelper.instance.extractQbehaviourButtons(question); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourAdaptiveNoPenaltyHandler extends makeSingleton(AddonQbehaviourAdaptiveNoPenaltyHandlerService) {} diff --git a/src/addons/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html b/src/addons/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html new file mode 100644 index 000000000..76dfebf56 --- /dev/null +++ b/src/addons/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html @@ -0,0 +1,15 @@ +
+ +

{{ 'core.question.certainty' | translate }}

+
+ + + + {{ option.text }} + + + + + + +
diff --git a/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts b/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts new file mode 100644 index 000000000..88b47fcf2 --- /dev/null +++ b/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts @@ -0,0 +1,38 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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, Output, EventEmitter } from '@angular/core'; + +import { CoreQuestionBehaviourButton, CoreQuestionQuestion } from '@features/question/services/question-helper'; + +/** + * Component to render the deferred CBM in a question. + */ +@Component({ + selector: 'addon-qbehaviour-deferredcbm', + templateUrl: 'addon-qbehaviour-deferredcbm.html', +}) +export class AddonQbehaviourDeferredCBMComponent { + + @Input() question?: CoreQuestionQuestion; // The question. + @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. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Output() buttonClicked = new EventEmitter(); // Will emit when a behaviour button is clicked. + @Output() onAbort = new EventEmitter(); // Should emit an event if the question should be aborted. + +} diff --git a/src/addons/qbehaviour/deferredcbm/deferredcbm.module.ts b/src/addons/qbehaviour/deferredcbm/deferredcbm.module.ts new file mode 100644 index 000000000..112c32e42 --- /dev/null +++ b/src/addons/qbehaviour/deferredcbm/deferredcbm.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourDeferredCBMComponent } from './component/deferredcbm'; +import { AddonQbehaviourDeferredCBMHandler } from './services/handlers/deferredcbm'; + +@NgModule({ + declarations: [ + AddonQbehaviourDeferredCBMComponent, + ], + imports: [ + CoreSharedModule, + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourDeferredCBMHandler.instance); + }, + }, + ], + exports: [ + AddonQbehaviourDeferredCBMComponent, + ], +}) +export class AddonQbehaviourDeferredCBMModule {} diff --git a/src/addons/qbehaviour/deferredcbm/services/handlers/deferredcbm.ts b/src/addons/qbehaviour/deferredcbm/services/handlers/deferredcbm.ts new file mode 100644 index 000000000..a2a2c0bb9 --- /dev/null +++ b/src/addons/qbehaviour/deferredcbm/services/handlers/deferredcbm.ts @@ -0,0 +1,152 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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, Type } from '@angular/core'; + +import { AddonQbehaviourDeferredFeedbackHandler } from '@addons/qbehaviour/deferredfeedback/services/handlers/deferredfeedback'; +import { CoreQuestionBehaviourHandler, CoreQuestionQuestionWithAnswers } from '@features/question/services/behaviour-delegate'; +import { makeSingleton } from '@singletons'; +import { CoreQuestionQuestionParsed, CoreQuestionsAnswers, CoreQuestionState } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { AddonQbehaviourDeferredCBMComponent } from '../../component/deferredcbm'; +import { CoreQuestionDelegate } from '@features/question/services/question-delegate'; + +/** + * Handler to support deferred CBM question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourDeferredCBMHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourDeferredCBM'; + type = 'deferredcbm'; + + /** + * Determine a question new state based on its answer(s). + * + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). + */ + determineNewState( + component: string, + attemptId: number, + question: CoreQuestionQuestionWithAnswers, + componentId: string | number, + siteId?: string, + ): CoreQuestionState | Promise { + // Depends on deferredfeedback. + return AddonQbehaviourDeferredFeedbackHandler.instance.determineNewStateDeferred( + component, + attemptId, + question, + componentId, + siteId, + this.isCompleteResponse.bind(this), + this.isSameResponse.bind(this), + ); + } + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void | Type[] { + if (CoreQuestionHelper.instance.extractQbehaviourCBM(question)) { + return [AddonQbehaviourDeferredCBMComponent]; + } + } + + /** + * Check if a response is complete. + * + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @param component The component the question is related to. + * @param componentId Component ID. + * @return 1 if complete, 0 if not complete, -1 if cannot determine. + */ + protected isCompleteResponse( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + component: string, + componentId: string | number, + ): number { + // First check if the question answer is complete. + const complete = CoreQuestionDelegate.instance.isCompleteResponse(question, answers, component, componentId); + if (complete > 0) { + // Answer is complete, check the user answered CBM too. + return answers['-certainty'] ? 1 : 0; + } + + return complete; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Check if two responses are the same. + * + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param newAnswers Object with the new question answers. + * @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param component The component the question is related to. + * @param componentId Component ID. + * @return Whether they're the same. + */ + protected isSameResponse( + question: CoreQuestionQuestionParsed, + prevAnswers: CoreQuestionsAnswers, + prevBasicAnswers: CoreQuestionsAnswers, + newAnswers: CoreQuestionsAnswers, + newBasicAnswers: CoreQuestionsAnswers, + component: string, + componentId: string | number, + ): boolean { + // First check if the question answer is the same. + const sameResponse = CoreQuestionDelegate.instance.isSameResponse( + question, + prevBasicAnswers, + newBasicAnswers, + component, + componentId, + ); + + if (sameResponse) { + // Same response, check the CBM is the same too. + return prevAnswers['-certainty'] == newAnswers['-certainty']; + } + + return sameResponse; + } + +} + +export class AddonQbehaviourDeferredCBMHandler extends makeSingleton(AddonQbehaviourDeferredCBMHandlerService) {} diff --git a/src/addons/qbehaviour/deferredfeedback/deferredfeedback.module.ts b/src/addons/qbehaviour/deferredfeedback/deferredfeedback.module.ts new file mode 100644 index 000000000..811f8dd00 --- /dev/null +++ b/src/addons/qbehaviour/deferredfeedback/deferredfeedback.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourDeferredFeedbackHandler } from './services/handlers/deferredfeedback'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourDeferredFeedbackHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourDeferredFeedbackModule {} diff --git a/src/addons/qbehaviour/deferredfeedback/services/handlers/deferredfeedback.ts b/src/addons/qbehaviour/deferredfeedback/services/handlers/deferredfeedback.ts new file mode 100644 index 000000000..6f536532a --- /dev/null +++ b/src/addons/qbehaviour/deferredfeedback/services/handlers/deferredfeedback.ts @@ -0,0 +1,216 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreQuestionBehaviourHandler, CoreQuestionQuestionWithAnswers } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionDBRecord } from '@features/question/services/database/question'; +import { + CoreQuestion, + CoreQuestionQuestionParsed, + CoreQuestionsAnswers, + CoreQuestionState, +} from '@features/question/services/question'; +import { CoreQuestionDelegate } from '@features/question/services/question-delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support deferred feedback question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourDeferredFeedbackHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourDeferredFeedback'; + type = 'deferredfeedback'; + + /** + * Determine a question new state based on its answer(s). + * + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). + */ + determineNewState( + component: string, + attemptId: number, + question: CoreQuestionQuestionWithAnswers, + componentId: string | number, + siteId?: string, + ): CoreQuestionState | Promise { + return this.determineNewStateDeferred(component, attemptId, question, componentId, siteId); + } + + /** + * Determine a question new state based on its answer(s) for deferred question behaviour. + * + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @param isCompleteFn Function to override the default isCompleteResponse check. + * @param isSameFn Function to override the default isSameResponse check. + * @return Promise resolved with state. + */ + async determineNewStateDeferred( + component: string, + attemptId: number, + question: CoreQuestionQuestionWithAnswers, + componentId: string | number, + siteId?: string, + isCompleteFn?: isCompleteResponseFunction, + isSameFn?: isSameResponseFunction, + ): Promise { + + // Check if we have local data for the question. + let dbQuestion: CoreQuestionDBRecord | CoreQuestionQuestionWithAnswers = question; + try { + dbQuestion = await CoreQuestion.instance.getQuestion(component, attemptId, question.slot, siteId); + } catch (error) { + // No entry found, use the original data. + } + + const state = CoreQuestion.instance.getState(dbQuestion.state); + + if (state.finished || !state.active) { + // Question is finished, it cannot change. + return state; + } + + const newBasicAnswers = CoreQuestion.instance.getBasicAnswers(question.answers || {}); + + if (dbQuestion.state) { + // Question already has a state stored. Check if answer has changed. + const prevAnswersList = await CoreQuestion.instance.getQuestionAnswers( + component, + attemptId, + question.slot, + false, + siteId, + ); + const prevAnswers = CoreQuestion.instance.convertAnswersArrayToObject(prevAnswersList, true); + const prevBasicAnswers = CoreQuestion.instance.getBasicAnswers(prevAnswers); + + // If answers haven't changed the state is the same. + let sameResponse = false; + + if (isSameFn) { + sameResponse = isSameFn( + question, + prevAnswers, + prevBasicAnswers, + question.answers || {}, + newBasicAnswers, + component, + componentId, + ); + } else { + sameResponse = CoreQuestionDelegate.instance.isSameResponse( + question, + prevBasicAnswers, + newBasicAnswers, + component, + componentId, + ); + } + + if (sameResponse) { + return state; + } + } + + // Answers have changed. Now check if the response is complete and calculate the new state. + let complete: number; + let newState: string; + + if (isCompleteFn) { + // Pass all the answers since some behaviours might need the extra data. + complete = isCompleteFn(question, question.answers || {}, component, componentId); + } else { + // Only pass the basic answers since questions should be independent of extra data. + complete = CoreQuestionDelegate.instance.isCompleteResponse(question, newBasicAnswers, component, componentId); + } + + if (complete < 0) { + newState = 'cannotdeterminestatus'; + } else if (complete > 0) { + newState = 'complete'; + } else { + const gradable = CoreQuestionDelegate.instance.isGradableResponse(question, newBasicAnswers, component, componentId); + if (gradable < 0) { + newState = 'cannotdeterminestatus'; + } else if (gradable > 0) { + newState = 'invalid'; + } else { + newState = 'todo'; + } + } + + return CoreQuestion.instance.getState(newState); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourDeferredFeedbackHandler extends makeSingleton(AddonQbehaviourDeferredFeedbackHandlerService) {} + + +/** + * Check if a response is complete. + * + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @param component The component the question is related to. + * @param componentId Component ID. + * @return 1 if complete, 0 if not complete, -1 if cannot determine. + */ +export type isCompleteResponseFunction = ( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + component: string, + componentId: string | number, +) => number; + +/** + * Check if two responses are the same. + * + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param newAnswers Object with the new question answers. + * @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param component The component the question is related to. + * @param componentId Component ID. + * @return Whether they're the same. + */ +export type isSameResponseFunction = ( + question: CoreQuestionQuestionParsed, + prevAnswers: CoreQuestionsAnswers, + prevBasicAnswers: CoreQuestionsAnswers, + newAnswers: CoreQuestionsAnswers, + newBasicAnswers: CoreQuestionsAnswers, + component: string, + componentId: string | number, +) => boolean; diff --git a/src/addons/qbehaviour/immediatecbm/immediatecbm.module.ts b/src/addons/qbehaviour/immediatecbm/immediatecbm.module.ts new file mode 100644 index 000000000..adcc19d47 --- /dev/null +++ b/src/addons/qbehaviour/immediatecbm/immediatecbm.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourImmediateCBMHandler } from './services/handlers/immediatecbm'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourImmediateCBMHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourImmediateCBMModule {} diff --git a/src/addons/qbehaviour/immediatecbm/services/handlers/immediatecbm.ts b/src/addons/qbehaviour/immediatecbm/services/handlers/immediatecbm.ts new file mode 100644 index 000000000..243e2c42c --- /dev/null +++ b/src/addons/qbehaviour/immediatecbm/services/handlers/immediatecbm.ts @@ -0,0 +1,61 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { AddonQbehaviourDeferredCBMComponent } from '@addons/qbehaviour/deferredcbm/component/deferredcbm'; +import { Injectable, Type } from '@angular/core'; + +import { CoreQuestionBehaviourHandler } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionQuestionParsed } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support immediate CBM question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourImmediateCBMHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourImmediateCBM'; + type = 'immediatecbm'; + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void | Type[] { + CoreQuestionHelper.instance.extractQbehaviourButtons(question); + + if (CoreQuestionHelper.instance.extractQbehaviourCBM(question)) { + // Depends on deferredcbm. + return [AddonQbehaviourDeferredCBMComponent]; + } + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourImmediateCBMHandler extends makeSingleton(AddonQbehaviourImmediateCBMHandlerService) {} diff --git a/src/addons/qbehaviour/immediatefeedback/immediatefeedback.module.ts b/src/addons/qbehaviour/immediatefeedback/immediatefeedback.module.ts new file mode 100644 index 000000000..617fd6b1c --- /dev/null +++ b/src/addons/qbehaviour/immediatefeedback/immediatefeedback.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourImmediateFeedbackHandler } from './services/handlers/immediatefeedback'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourImmediateFeedbackHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourImmediateFeedbackModule {} diff --git a/src/addons/qbehaviour/immediatefeedback/services/handlers/immediatefeedback.ts b/src/addons/qbehaviour/immediatefeedback/services/handlers/immediatefeedback.ts new file mode 100644 index 000000000..c3d542c48 --- /dev/null +++ b/src/addons/qbehaviour/immediatefeedback/services/handlers/immediatefeedback.ts @@ -0,0 +1,58 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreQuestionBehaviourHandler } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionQuestionParsed } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support immediate feedback question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourImmediateFeedbackHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourImmediateFeedback'; + type = 'immediatefeedback'; + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void { + // Just extract the button, it doesn't need any specific component. + CoreQuestionHelper.instance.extractQbehaviourButtons(question); + + return; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourImmediateFeedbackHandler extends makeSingleton(AddonQbehaviourImmediateFeedbackHandlerService) {} diff --git a/src/addons/qbehaviour/informationitem/component/addon-qbehaviour-informationitem.html b/src/addons/qbehaviour/informationitem/component/addon-qbehaviour-informationitem.html new file mode 100644 index 000000000..8f720da0f --- /dev/null +++ b/src/addons/qbehaviour/informationitem/component/addon-qbehaviour-informationitem.html @@ -0,0 +1,2 @@ + diff --git a/src/addons/qbehaviour/informationitem/component/informationitem.ts b/src/addons/qbehaviour/informationitem/component/informationitem.ts new file mode 100644 index 000000000..bbc0a243f --- /dev/null +++ b/src/addons/qbehaviour/informationitem/component/informationitem.ts @@ -0,0 +1,38 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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, Output, EventEmitter } from '@angular/core'; + +import { CoreQuestionBehaviourButton, CoreQuestionQuestion } from '@features/question/services/question-helper'; + +/** + * Component to render a "seen" hidden input for informationitem question behaviour. + */ +@Component({ + selector: 'addon-qbehaviour-informationitem', + templateUrl: 'addon-qbehaviour-informationitem.html', +}) +export class AddonQbehaviourInformationItemComponent { + + @Input() question?: CoreQuestionQuestion; // The question. + @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. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Output() buttonClicked = new EventEmitter(); // Will emit when a behaviour button is clicked. + @Output() onAbort = new EventEmitter(); // Should emit an event if the question should be aborted. + +} diff --git a/src/addons/qbehaviour/informationitem/informationitem.module.ts b/src/addons/qbehaviour/informationitem/informationitem.module.ts new file mode 100644 index 000000000..eab461bc9 --- /dev/null +++ b/src/addons/qbehaviour/informationitem/informationitem.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreSharedModule } from '@/core/shared.module'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourInformationItemComponent } from './component/informationitem'; +import { AddonQbehaviourInformationItemHandler } from './services/handlers/informationitem'; + +@NgModule({ + declarations: [ + AddonQbehaviourInformationItemComponent, + ], + imports: [ + CoreSharedModule, + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourInformationItemHandler.instance); + }, + }, + ], + exports: [ + AddonQbehaviourInformationItemComponent, + ], +}) +export class AddonQbehaviourInformationItemModule {} diff --git a/src/addons/qbehaviour/informationitem/services/handlers/informationitem.ts b/src/addons/qbehaviour/informationitem/services/handlers/informationitem.ts new file mode 100644 index 000000000..6ae1aabc8 --- /dev/null +++ b/src/addons/qbehaviour/informationitem/services/handlers/informationitem.ts @@ -0,0 +1,80 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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, Type } from '@angular/core'; + +import { CoreQuestionBehaviourHandler, CoreQuestionQuestionWithAnswers } from '@features/question/services/behaviour-delegate'; +import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionState } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; +import { AddonQbehaviourInformationItemComponent } from '../../component/informationitem'; + +/** + * Handler to support information item question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourInformationItemHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourInformationItem'; + type = 'informationitem'; + + /** + * Determine a question new state based on its answer(s). + * + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). + */ + determineNewState( + component: string, + attemptId: number, + question: CoreQuestionQuestionWithAnswers, + ): CoreQuestionState | Promise { + if (question.answers?.['-seen']) { + return CoreQuestion.instance.getState('complete'); + } + + return CoreQuestion.instance.getState(question.state || 'todo'); + } + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void | Type[] { + if (CoreQuestionHelper.instance.extractQbehaviourSeenInput(question)) { + return [AddonQbehaviourInformationItemComponent]; + } + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourInformationItemHandler extends makeSingleton(AddonQbehaviourInformationItemHandlerService) {} diff --git a/src/addons/qbehaviour/interactive/interactive.module.ts b/src/addons/qbehaviour/interactive/interactive.module.ts new file mode 100644 index 000000000..4f79ea766 --- /dev/null +++ b/src/addons/qbehaviour/interactive/interactive.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourInteractiveHandler } from './services/handlers/interactive'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourInteractiveHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourInteractiveModule {} diff --git a/src/addons/qbehaviour/interactive/services/handlers/interactive.ts b/src/addons/qbehaviour/interactive/services/handlers/interactive.ts new file mode 100644 index 000000000..aa934d7b5 --- /dev/null +++ b/src/addons/qbehaviour/interactive/services/handlers/interactive.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreQuestionBehaviourHandler } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionQuestionParsed } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support interactive question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourInteractiveHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourInteractive'; + type = 'interactive'; + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void { + // Just extract the button, it doesn't need any specific component. + CoreQuestionHelper.instance.extractQbehaviourButtons(question); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourInteractiveHandler extends makeSingleton(AddonQbehaviourInteractiveHandlerService) {} diff --git a/src/addons/qbehaviour/interactivecountback/interactivecountback.module.ts b/src/addons/qbehaviour/interactivecountback/interactivecountback.module.ts new file mode 100644 index 000000000..1b74410ff --- /dev/null +++ b/src/addons/qbehaviour/interactivecountback/interactivecountback.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourInteractiveCountbackHandler } from './services/handlers/interactivecountback'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourInteractiveCountbackHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourInteractiveCountbackModule {} diff --git a/src/addons/qbehaviour/interactivecountback/services/handlers/interactivecountback.ts b/src/addons/qbehaviour/interactivecountback/services/handlers/interactivecountback.ts new file mode 100644 index 000000000..9baebd181 --- /dev/null +++ b/src/addons/qbehaviour/interactivecountback/services/handlers/interactivecountback.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreQuestionBehaviourHandler } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionQuestionParsed } from '@features/question/services/question'; +import { CoreQuestionHelper } from '@features/question/services/question-helper'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support interactive countback question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourInteractiveCountbackHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourInteractiveCountback'; + type = 'interactivecountback'; + + /** + * 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 question The question. + * @return 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: CoreQuestionQuestionParsed): void { + // Just extract the button, it doesn't need any specific component. + CoreQuestionHelper.instance.extractQbehaviourButtons(question); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourInteractiveCountbackHandler extends makeSingleton(AddonQbehaviourInteractiveCountbackHandlerService) {} diff --git a/src/addons/qbehaviour/manualgraded/manualgraded.module.ts b/src/addons/qbehaviour/manualgraded/manualgraded.module.ts new file mode 100644 index 000000000..a1a2e5deb --- /dev/null +++ b/src/addons/qbehaviour/manualgraded/manualgraded.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; +import { AddonQbehaviourManualGradedHandler } from './services/handlers/manualgraded'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreQuestionBehaviourDelegate.instance.registerHandler(AddonQbehaviourManualGradedHandler.instance); + }, + }, + ], +}) +export class AddonQbehaviourManualGradedModule {} diff --git a/src/addons/qbehaviour/manualgraded/services/handlers/manualgraded.ts b/src/addons/qbehaviour/manualgraded/services/handlers/manualgraded.ts new file mode 100644 index 000000000..44ec70460 --- /dev/null +++ b/src/addons/qbehaviour/manualgraded/services/handlers/manualgraded.ts @@ -0,0 +1,69 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { AddonQbehaviourDeferredFeedbackHandler } from '@addons/qbehaviour/deferredfeedback/services/handlers/deferredfeedback'; +import { CoreQuestionBehaviourHandler, CoreQuestionQuestionWithAnswers } from '@features/question/services/behaviour-delegate'; +import { CoreQuestionState } from '@features/question/services/question'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support manual graded question behaviour. + */ +@Injectable({ providedIn: 'root' }) +export class AddonQbehaviourManualGradedHandlerService implements CoreQuestionBehaviourHandler { + + name = 'AddonQbehaviourManualGraded'; + type = 'manualgraded'; + + /** + * Determine a question new state based on its answer(s). + * + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). + */ + determineNewState( + component: string, + attemptId: number, + question: CoreQuestionQuestionWithAnswers, + componentId: string | number, + siteId?: string, + ): CoreQuestionState | Promise { + // Same implementation as the deferred feedback. Use that function instead of replicating it. + return AddonQbehaviourDeferredFeedbackHandler.instance.determineNewStateDeferred( + component, + attemptId, + question, + componentId, + siteId, + ); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + +} + +export class AddonQbehaviourManualGradedHandler extends makeSingleton(AddonQbehaviourManualGradedHandlerService) {} diff --git a/src/addons/qbehaviour/qbehaviour.module.ts b/src/addons/qbehaviour/qbehaviour.module.ts new file mode 100644 index 000000000..499ce1334 --- /dev/null +++ b/src/addons/qbehaviour/qbehaviour.module.ts @@ -0,0 +1,45 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { AddonQbehaviourAdaptiveModule } from './adaptive/adaptive.module'; +import { AddonQbehaviourAdaptiveNoPenaltyModule } from './adaptivenopenalty/adaptivenopenalty.module'; +import { AddonQbehaviourDeferredCBMModule } from './deferredcbm/deferredcbm.module'; +import { AddonQbehaviourDeferredFeedbackModule } from './deferredfeedback/deferredfeedback.module'; +import { AddonQbehaviourImmediateCBMModule } from './immediatecbm/immediatecbm.module'; +import { AddonQbehaviourImmediateFeedbackModule } from './immediatefeedback/immediatefeedback.module'; +import { AddonQbehaviourInformationItemModule } from './informationitem/informationitem.module'; +import { AddonQbehaviourInteractiveModule } from './interactive/interactive.module'; +import { AddonQbehaviourInteractiveCountbackModule } from './interactivecountback/interactivecountback.module'; +import { AddonQbehaviourManualGradedModule } from './manualgraded/manualgraded.module'; + +@NgModule({ + declarations: [], + imports: [ + AddonQbehaviourAdaptiveModule, + AddonQbehaviourAdaptiveNoPenaltyModule, + AddonQbehaviourDeferredCBMModule, + AddonQbehaviourDeferredFeedbackModule, + AddonQbehaviourImmediateCBMModule, + AddonQbehaviourImmediateFeedbackModule, + AddonQbehaviourInformationItemModule, + AddonQbehaviourInteractiveModule, + AddonQbehaviourInteractiveCountbackModule, + AddonQbehaviourManualGradedModule, + ], + providers: [ + ], + exports: [], +}) +export class AddonQbehaviourModule { } diff --git a/src/addons/userprofilefield/datetime/services/handlers/datetime.ts b/src/addons/userprofilefield/datetime/services/handlers/datetime.ts index 5a1ab635f..cc16bc6ea 100644 --- a/src/addons/userprofilefield/datetime/services/handlers/datetime.ts +++ b/src/addons/userprofilefield/datetime/services/handlers/datetime.ts @@ -69,7 +69,6 @@ export class AddonUserProfileFieldDatetimeHandlerService implements CoreUserProf * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param injector Injector. * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(): Type | Promise> { diff --git a/src/addons/userprofilefield/textarea/services/handlers/textarea.ts b/src/addons/userprofilefield/textarea/services/handlers/textarea.ts index e1a233652..6ad1ba8bb 100644 --- a/src/addons/userprofilefield/textarea/services/handlers/textarea.ts +++ b/src/addons/userprofilefield/textarea/services/handlers/textarea.ts @@ -76,7 +76,6 @@ export class AddonUserProfileFieldTextareaHandlerService implements CoreUserProf * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param injector Injector. * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(): Type | Promise> { diff --git a/src/core/features/block/services/block-delegate.ts b/src/core/features/block/services/block-delegate.ts index d46f34b1d..bd80bbc25 100644 --- a/src/core/features/block/services/block-delegate.ts +++ b/src/core/features/block/services/block-delegate.ts @@ -138,7 +138,6 @@ export class CoreBlockDelegateService extends CoreDelegate { /** * Get the display data for a certain block. * - * @param injector Injector. * @param block The block to render. * @param contextLevel The context where the block will be used. * @param instanceId The instance ID associated with the context level. @@ -200,4 +199,3 @@ export class CoreBlockDelegateService extends CoreDelegate { } export class CoreBlockDelegate extends makeSingleton(CoreBlockDelegateService) {} - diff --git a/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts b/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts index 4cc6d6189..a23f09060 100644 --- a/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts +++ b/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts @@ -127,7 +127,6 @@ export class CoreCourseFormatSingleActivityHandlerService implements CoreCourseF * If you want to customize the default format there are several methods to customize parts of it. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param injector Injector. * @param course The course to render. * @return The component (or promise resolved with component) to use, undefined if not found. */ diff --git a/src/core/features/course/services/handlers/course-tag-area.ts b/src/core/features/course/services/handlers/course-tag-area.ts index 216bf0479..4815096fb 100644 --- a/src/core/features/course/services/handlers/course-tag-area.ts +++ b/src/core/features/course/services/handlers/course-tag-area.ts @@ -67,7 +67,6 @@ export class CoreCourseTagAreaHandlerService implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param injector Injector. * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(): Type | Promise> { diff --git a/src/core/features/course/services/handlers/default-format.ts b/src/core/features/course/services/handlers/default-format.ts index f77d8f7df..115ca52be 100644 --- a/src/core/features/course/services/handlers/default-format.ts +++ b/src/core/features/course/services/handlers/default-format.ts @@ -109,7 +109,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @return Whether the refresher should be displayed. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - displayRefresher?(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): boolean { + displayRefresher(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): boolean { return true; } diff --git a/src/core/features/course/services/handlers/modules-tag-area.ts b/src/core/features/course/services/handlers/modules-tag-area.ts index 21a786cc9..fa488ebf7 100644 --- a/src/core/features/course/services/handlers/modules-tag-area.ts +++ b/src/core/features/course/services/handlers/modules-tag-area.ts @@ -50,7 +50,6 @@ export class CoreCourseModulesTagAreaHandlerService implements CoreTagAreaHandle /** * Get the component to use to display items. * - * @param injector Injector. * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(): Type | Promise> { diff --git a/src/core/features/user/services/handlers/tag-area.ts b/src/core/features/user/services/handlers/tag-area.ts index 384466574..5b51db223 100644 --- a/src/core/features/user/services/handlers/tag-area.ts +++ b/src/core/features/user/services/handlers/tag-area.ts @@ -82,7 +82,6 @@ export class CoreUserTagAreaHandlerService implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param injector Injector. * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(): Type | Promise> { diff --git a/src/core/features/user/services/user-profile-field-delegate.ts b/src/core/features/user/services/user-profile-field-delegate.ts index 833686c4f..7743bf540 100644 --- a/src/core/features/user/services/user-profile-field-delegate.ts +++ b/src/core/features/user/services/user-profile-field-delegate.ts @@ -96,7 +96,6 @@ export class CoreUserProfileFieldDelegateService extends CoreDelegate