diff --git a/src/addon/qbehaviour/adaptive/adaptive.module.ts b/src/addon/qbehaviour/adaptive/adaptive.module.ts new file mode 100644 index 000000000..adbae973f --- /dev/null +++ b/src/addon/qbehaviour/adaptive/adaptive.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourAdaptiveHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourAdaptiveHandler + ] +}) +export class AddonQbehaviourAdaptiveModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourAdaptiveHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/adaptive/providers/handler.ts b/src/addon/qbehaviour/adaptive/providers/handler.ts new file mode 100644 index 000000000..37a5e908a --- /dev/null +++ b/src/addon/qbehaviour/adaptive/providers/handler.ts @@ -0,0 +1,56 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; + +/** + * Handler to support adaptive question behaviour. + */ +@Injectable() +export class AddonQbehaviourAdaptiveHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourAdaptive'; + type = 'adaptive'; + + constructor(private questionHelper: CoreQuestionHelperProvider) { + // Nothing to do. + } + + /** + * 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 {any} question The question. + * @return {any[]|Promise} 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 { + // Just extract the button, it doesn't need any specific component. + this.questionHelper.extractQbehaviourButtons(question); + + return; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts b/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts new file mode 100644 index 000000000..6fa4c5683 --- /dev/null +++ b/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourAdaptiveNoPenaltyHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourAdaptiveNoPenaltyHandler + ] +}) +export class AddonQbehaviourAdaptiveNoPenaltyModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourAdaptiveNoPenaltyHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts b/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts new file mode 100644 index 000000000..3f788eea5 --- /dev/null +++ b/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts @@ -0,0 +1,56 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; + +/** + * Handler to support adaptive no penalty question behaviour. + */ +@Injectable() +export class AddonQbehaviourAdaptiveNoPenaltyHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourAdaptiveNoPenalty'; + type = 'adaptivenopenalty'; + + constructor(private questionHelper: CoreQuestionHelperProvider) { + // Nothing to do. + } + + /** + * 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 {any} question The question. + * @return {any[]|Promise} 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 { + // Just extract the button, it doesn't need any specific component. + this.questionHelper.extractQbehaviourButtons(question); + + return; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html new file mode 100644 index 000000000..07c1ffc7d --- /dev/null +++ b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.html @@ -0,0 +1,6 @@ + +

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

+
+ +

+
diff --git a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts new file mode 100644 index 000000000..a6c822770 --- /dev/null +++ b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts @@ -0,0 +1,36 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, EventEmitter } from '@angular/core'; + +/** + * Component to render the deferred CBM in a question. + */ +@Component({ + selector: 'addon-qbehaviour-deferredcbm', + templateUrl: 'deferredcbm.html' +}) +export class AddonQbehaviourDeferredCBMComponent { + @Input() question: any; // 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() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. + @Input() onAbort: EventEmitter; // Should emit an event if the question should be aborted. + + constructor() { + // Nothing to do. + } +} diff --git a/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts b/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts new file mode 100644 index 000000000..3948f8abe --- /dev/null +++ b/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonQbehaviourDeferredCBMHandler } from './providers/handler'; +import { AddonQbehaviourDeferredCBMComponent } from './component/deferredcbm'; + +@NgModule({ + declarations: [ + AddonQbehaviourDeferredCBMComponent + ], + imports: [ + IonicModule, + TranslateModule.forChild(), + CoreDirectivesModule + ], + providers: [ + AddonQbehaviourDeferredCBMHandler + ], + exports: [ + AddonQbehaviourDeferredCBMComponent + ], + entryComponents: [ + AddonQbehaviourDeferredCBMComponent + ] +}) +export class AddonQbehaviourDeferredCBMModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourDeferredCBMHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/deferredcbm/providers/handler.ts b/src/addon/qbehaviour/deferredcbm/providers/handler.ts new file mode 100644 index 000000000..e15d47c4d --- /dev/null +++ b/src/addon/qbehaviour/deferredcbm/providers/handler.ts @@ -0,0 +1,116 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreQuestionState } from '@core/question/providers/question'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; +import { AddonQbehaviourDeferredFeedbackHandler } from '@addon/qbehaviour/deferredfeedback/providers/handler'; +import { AddonQbehaviourDeferredCBMComponent } from '../component/deferredcbm'; + +/** + * Handler to support deferred CBM question behaviour. + */ +@Injectable() +export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourDeferredCBM'; + type = 'deferredcbm'; + + constructor(private questionDelegate: CoreQuestionDelegate, private questionHelper: CoreQuestionHelperProvider, + private deferredFeedbackHandler: AddonQbehaviourDeferredFeedbackHandler) { + // Nothing to do. + } + + /** + * Determine a question new state based on its answer(s). + * + * @param {string} component Component the question belongs to. + * @param {number} attemptId Attempt ID the question belongs to. + * @param {any} question The question. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + */ + determineNewState(component: string, attemptId: number, question: any, siteId?: string) + : CoreQuestionState | Promise { + // Depends on deferredfeedback. + return this.deferredFeedbackHandler.determineNewStateDeferred(component, attemptId, question, 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 {any} question The question. + * @return {any[]|Promise} 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 { + if (this.questionHelper.extractQbehaviourCBM(question)) { + return [AddonQbehaviourDeferredCBMComponent]; + } + } + + /** + * Check if a response is complete. + * + * @param {any} question The question. + * @param {any} answers Object with the question answers (without prefix). + * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + */ + protected isCompleteResponse(question: any, answers: any): number { + // First check if the question answer is complete. + const complete = this.questionDelegate.isCompleteResponse(question, answers); + 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 {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Check if two responses are the same. + * + * @param {any} question Question. + * @param {any} prevAnswers Object with the previous question answers. + * @param {any} prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param {any} newAnswers Object with the new question answers. + * @param {any} newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @return {boolean} Whether they're the same. + */ + protected isSameResponse(question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, newBasicAnswers: any) + : boolean { + // First check if the question answer is the same. + const same = this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers); + if (same) { + // Same response, check the CBM is the same too. + return prevAnswers['-certainty'] == newAnswers['-certainty']; + } + + return same; + } +} diff --git a/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts b/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts new file mode 100644 index 000000000..99f39f0c5 --- /dev/null +++ b/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourDeferredFeedbackHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourDeferredFeedbackHandler + ] +}) +export class AddonQbehaviourDeferredFeedbackModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourDeferredFeedbackHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/deferredfeedback/providers/handler.ts b/src/addon/qbehaviour/deferredfeedback/providers/handler.ts new file mode 100644 index 000000000..8a3d82ef5 --- /dev/null +++ b/src/addon/qbehaviour/deferredfeedback/providers/handler.ts @@ -0,0 +1,154 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question'; + +/** + * Check if a response is complete. + * + * @param {any} question The question. + * @param {any} answers Object with the question answers (without prefix). + * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + */ +export type isCompleteResponseFunction = (question: any, answers: any) => number; + +/** + * Check if two responses are the same. + * + * @param {any} question Question. + * @param {any} prevAnswers Object with the previous question answers. + * @param {any} prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param {any} newAnswers Object with the new question answers. + * @param {any} newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @return {boolean} Whether they're the same. + */ +export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, + newBasicAnswers: any) => boolean; + +/** + * Handler to support deferred feedback question behaviour. + */ +@Injectable() +export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourDeferredFeedback'; + type = 'deferredfeedback'; + + constructor(private questionDelegate: CoreQuestionDelegate, private questionProvider: CoreQuestionProvider) { + // Nothing to do. + } + + /** + * Determine a question new state based on its answer(s). + * + * @param {string} component Component the question belongs to. + * @param {number} attemptId Attempt ID the question belongs to. + * @param {any} question The question. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + */ + determineNewState(component: string, attemptId: number, question: any, siteId?: string) + : CoreQuestionState | Promise { + return this.determineNewStateDeferred(component, attemptId, question, siteId); + } + + /** + * Determine a question new state based on its answer(s) for deferred question behaviour. + * + * @param {string} component Component the question belongs to. + * @param {number} attemptId Attempt ID the question belongs to. + * @param {any} question The question. + * @param {string} [siteId] Site ID. If not defined, current site. + * @param {isCompleteResponseFunction} [isCompleteFn] Function to override the default isCompleteResponse check. + * @param {isSameResponseFunction} [isSameFn] Function to override the default isSameResponse check. + * @return {Promise} Promise resolved with state. + */ + determineNewStateDeferred(component: string, attemptId: number, question: any, siteId?: string, + isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise { + + // Check if we have local data for the question. + return this.questionProvider.getQuestion(component, attemptId, question.slot, siteId).catch(() => { + // No entry found, use the original data. + return question; + }).then((dbQuestion) => { + const state = this.questionProvider.getState(dbQuestion.state); + + if (state.finished || !state.active) { + // Question is finished, it cannot change. + return state; + } + + // We need to check if the answers have changed. Retrieve current stored answers. + return this.questionProvider.getQuestionAnswers(component, attemptId, question.slot, false, siteId) + .then((prevAnswers) => { + + const newBasicAnswers = this.questionProvider.getBasicAnswers(question.answers); + + prevAnswers = this.questionProvider.convertAnswersArrayToObject(prevAnswers, true); + const prevBasicAnswers = this.questionProvider.getBasicAnswers(prevAnswers); + + // If answers haven't changed the state is the same. + if (isSameFn) { + if (isSameFn(question, prevAnswers, prevBasicAnswers, question.answers, newBasicAnswers)) { + return state; + } + } else { + if (this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers)) { + return state; + } + } + + // Answers have changed. Now check if the response is complete and calculate the new state. + let complete: number, + newState: string; + if (isCompleteFn) { + // Pass all the answers since some behaviours might need the extra data. + complete = isCompleteFn(question, question.answers); + } else { + // Only pass the basic answers since questions should be independent of extra data. + complete = this.questionDelegate.isCompleteResponse(question, newBasicAnswers); + } + + if (complete < 0) { + newState = 'unknown'; + } else if (complete > 0) { + newState = 'complete'; + } else { + const gradable = this.questionDelegate.isGradableResponse(question, newBasicAnswers); + if (gradable < 0) { + newState = 'unknown'; + } else if (gradable > 0) { + newState = 'invalid'; + } else { + newState = 'todo'; + } + } + + return this.questionProvider.getState(newState); + }); + }); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts b/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts new file mode 100644 index 000000000..a13e3e130 --- /dev/null +++ b/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourImmediateCBMHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourImmediateCBMHandler + ] +}) +export class AddonQbehaviourImmediateCBMModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourImmediateCBMHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/immediatecbm/providers/handler.ts b/src/addon/qbehaviour/immediatecbm/providers/handler.ts new file mode 100644 index 000000000..0f1493809 --- /dev/null +++ b/src/addon/qbehaviour/immediatecbm/providers/handler.ts @@ -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 { Injectable } 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'; + +/** + * Handler to support immediate CBM question behaviour. + */ +@Injectable() +export class AddonQbehaviourImmediateCBMHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourImmediateCBM'; + type = 'immediatecbm'; + + constructor(private questionHelper: CoreQuestionHelperProvider) { + // Nothing to do. + } + + /** + * 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 {any} question The question. + * @return {any[]|Promise} 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 { + // Just extract the button, it doesn't need any specific component. + this.questionHelper.extractQbehaviourButtons(question); + if (this.questionHelper.extractQbehaviourCBM(question)) { + // Depends on deferredcbm. + return [AddonQbehaviourDeferredCBMComponent]; + } + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts b/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts new file mode 100644 index 000000000..3d5e5ed66 --- /dev/null +++ b/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourImmediateFeedbackHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourImmediateFeedbackHandler + ] +}) +export class AddonQbehaviourImmediateFeedbackModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourImmediateFeedbackHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/immediatefeedback/providers/handler.ts b/src/addon/qbehaviour/immediatefeedback/providers/handler.ts new file mode 100644 index 000000000..304e71bdc --- /dev/null +++ b/src/addon/qbehaviour/immediatefeedback/providers/handler.ts @@ -0,0 +1,56 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; + +/** + * Handler to support immediate feedback question behaviour. + */ +@Injectable() +export class AddonQbehaviourImmediateFeedbackHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourImmediateFeedback'; + type = 'immediatefeedback'; + + constructor(private questionHelper: CoreQuestionHelperProvider) { + // Nothing to do. + } + + /** + * 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 {any} question The question. + * @return {any[]|Promise} 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 { + // Just extract the button, it doesn't need any specific component. + this.questionHelper.extractQbehaviourButtons(question); + + return; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/informationitem/component/informationitem.html b/src/addon/qbehaviour/informationitem/component/informationitem.html new file mode 100644 index 000000000..e7a0724c1 --- /dev/null +++ b/src/addon/qbehaviour/informationitem/component/informationitem.html @@ -0,0 +1 @@ + diff --git a/src/addon/qbehaviour/informationitem/component/informationitem.ts b/src/addon/qbehaviour/informationitem/component/informationitem.ts new file mode 100644 index 000000000..35e37f3f3 --- /dev/null +++ b/src/addon/qbehaviour/informationitem/component/informationitem.ts @@ -0,0 +1,36 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, EventEmitter } from '@angular/core'; + +/** + * Component to render a "seen" hidden input for informationitem question behaviour. + */ +@Component({ + selector: 'addon-qbehaviour-informationitem', + templateUrl: 'informationitem.html' +}) +export class AddonQbehaviourInformationItemComponent { + @Input() question: any; // 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() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. + @Input() onAbort: EventEmitter; // Should emit an event if the question should be aborted. + + constructor() { + // Nothing to do. + } +} diff --git a/src/addon/qbehaviour/informationitem/informationitem.module.ts b/src/addon/qbehaviour/informationitem/informationitem.module.ts new file mode 100644 index 000000000..0f45881a0 --- /dev/null +++ b/src/addon/qbehaviour/informationitem/informationitem.module.ts @@ -0,0 +1,42 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; +import { AddonQbehaviourInformationItemHandler } from './providers/handler'; +import { AddonQbehaviourInformationItemComponent } from './component/informationitem'; + +@NgModule({ + declarations: [ + AddonQbehaviourInformationItemComponent + ], + imports: [ + IonicModule + ], + providers: [ + AddonQbehaviourInformationItemHandler + ], + exports: [ + AddonQbehaviourInformationItemComponent + ], + entryComponents: [ + AddonQbehaviourInformationItemComponent + ] +}) +export class AddonQbehaviourInformationItemModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourInformationItemHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/informationitem/providers/handler.ts b/src/addon/qbehaviour/informationitem/providers/handler.ts new file mode 100644 index 000000000..ac2df43f2 --- /dev/null +++ b/src/addon/qbehaviour/informationitem/providers/handler.ts @@ -0,0 +1,73 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; +import { AddonQbehaviourInformationItemComponent } from '../component/informationitem'; + +/** + * Handler to support information item question behaviour. + */ +@Injectable() +export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourInformationItem'; + type = 'informationitem'; + + constructor(private questionHelper: CoreQuestionHelperProvider, private questionProvider: CoreQuestionProvider) { } + + /** + * Determine a question new state based on its answer(s). + * + * @param {string} component Component the question belongs to. + * @param {number} attemptId Attempt ID the question belongs to. + * @param {any} question The question. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + */ + determineNewState(component: string, attemptId: number, question: any, siteId?: string) + : CoreQuestionState | Promise { + if (question.answers['-seen']) { + return this.questionProvider.getState('complete'); + } + + return this.questionProvider.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 {any} question The question. + * @return {any[]|Promise} 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 { + if (this.questionHelper.extractQbehaviourSeenInput(question)) { + return [AddonQbehaviourInformationItemComponent]; + } + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/interactive/interactive.module.ts b/src/addon/qbehaviour/interactive/interactive.module.ts new file mode 100644 index 000000000..ab30d3e0a --- /dev/null +++ b/src/addon/qbehaviour/interactive/interactive.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourInteractiveHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourInteractiveHandler + ] +}) +export class AddonQbehaviourInteractiveModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourInteractiveHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/interactive/providers/handler.ts b/src/addon/qbehaviour/interactive/providers/handler.ts new file mode 100644 index 000000000..af6d6024c --- /dev/null +++ b/src/addon/qbehaviour/interactive/providers/handler.ts @@ -0,0 +1,56 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; + +/** + * Handler to support interactive question behaviour. + */ +@Injectable() +export class AddonQbehaviourInteractiveHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourInteractive'; + type = 'interactive'; + + constructor(private questionHelper: CoreQuestionHelperProvider) { + // Nothing to do. + } + + /** + * 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 {any} question The question. + * @return {any[]|Promise} 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 { + // Just extract the button, it doesn't need any specific component. + this.questionHelper.extractQbehaviourButtons(question); + + return; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts b/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts new file mode 100644 index 000000000..bbe2dab24 --- /dev/null +++ b/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourInteractiveCountbackHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourInteractiveCountbackHandler + ] +}) +export class AddonQbehaviourInteractiveCountbackModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourInteractiveCountbackHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/interactivecountback/providers/handler.ts b/src/addon/qbehaviour/interactivecountback/providers/handler.ts new file mode 100644 index 000000000..612abb104 --- /dev/null +++ b/src/addon/qbehaviour/interactivecountback/providers/handler.ts @@ -0,0 +1,56 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; + +/** + * Handler to support interactive countback question behaviour. + */ +@Injectable() +export class AddonQbehaviourInteractiveCountbackHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourInteractiveCountback'; + type = 'interactivecountback'; + + constructor(private questionHelper: CoreQuestionHelperProvider) { + // Nothing to do. + } + + /** + * 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 {any} question The question. + * @return {any[]|Promise} 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 { + // Just extract the button, it doesn't need any specific component. + this.questionHelper.extractQbehaviourButtons(question); + + return; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/manualgraded/manualgraded.module.ts b/src/addon/qbehaviour/manualgraded/manualgraded.module.ts new file mode 100644 index 000000000..dbeaf1d34 --- /dev/null +++ b/src/addon/qbehaviour/manualgraded/manualgraded.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonQbehaviourManualGradedHandler } from './providers/handler'; +import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; + +@NgModule({ + declarations: [ + ], + providers: [ + AddonQbehaviourManualGradedHandler + ] +}) +export class AddonQbehaviourManualGradedModule { + constructor(behaviourDelegate: CoreQuestionBehaviourDelegate, handler: AddonQbehaviourManualGradedHandler) { + behaviourDelegate.registerHandler(handler); + } +} diff --git a/src/addon/qbehaviour/manualgraded/providers/handler.ts b/src/addon/qbehaviour/manualgraded/providers/handler.ts new file mode 100644 index 000000000..58755ac3c --- /dev/null +++ b/src/addon/qbehaviour/manualgraded/providers/handler.ts @@ -0,0 +1,147 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate'; +import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question'; + +/** + * Check if a response is complete. + * + * @param {any} question The question. + * @param {any} answers Object with the question answers (without prefix). + * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + */ +export type isCompleteResponseFunction = (question: any, answers: any) => number; + +/** + * Check if two responses are the same. + * + * @param {any} question Question. + * @param {any} prevAnswers Object with the previous question answers. + * @param {any} prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param {any} newAnswers Object with the new question answers. + * @param {any} newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @return {boolean} Whether they're the same. + */ +export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, + newBasicAnswers: any) => boolean; + +/** + * Handler to support manual graded question behaviour. + */ +@Injectable() +export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviourHandler { + name = 'AddonQbehaviourManualGraded'; + type = 'manualgraded'; + + constructor(private questionDelegate: CoreQuestionDelegate, private questionProvider: CoreQuestionProvider) { + // Nothing to do. + } + + /** + * Determine a question new state based on its answer(s). + * + * @param {string} component Component the question belongs to. + * @param {number} attemptId Attempt ID the question belongs to. + * @param {any} question The question. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + */ + determineNewState(component: string, attemptId: number, question: any, siteId?: string) + : CoreQuestionState | Promise { + return this.determineNewStateManualGraded(component, attemptId, question, siteId); + } + + /** + * Determine a question new state based on its answer(s) for manual graded question behaviour. + * + * @param {string} component Component the question belongs to. + * @param {number} attemptId Attempt ID the question belongs to. + * @param {any} question The question. + * @param {string} [siteId] Site ID. If not defined, current site. + * @param {isCompleteResponseFunction} [isCompleteFn] Function to override the default isCompleteResponse check. + * @param {isSameResponseFunction} [isSameFn] Function to override the default isSameResponse check. + * @return {Promise} Promise resolved with state. + */ + determineNewStateManualGraded(component: string, attemptId: number, question: any, siteId?: string, + isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise { + + // Check if we have local data for the question. + return this.questionProvider.getQuestion(component, attemptId, question.slot, siteId).catch(() => { + // No entry found, use the original data. + return question; + }).then((dbQuestion) => { + const state = this.questionProvider.getState(dbQuestion.state); + + if (state.finished || !state.active) { + // Question is finished, it cannot change. + return state; + } + + // We need to check if the answers have changed. Retrieve current stored answers. + return this.questionProvider.getQuestionAnswers(component, attemptId, question.slot, false, siteId) + .then((prevAnswers) => { + + const newBasicAnswers = this.questionProvider.getBasicAnswers(question.answers); + + prevAnswers = this.questionProvider.convertAnswersArrayToObject(prevAnswers, true); + const prevBasicAnswers = this.questionProvider.getBasicAnswers(prevAnswers); + + // If answers haven't changed the state is the same. + if (isSameFn) { + if (isSameFn(question, prevAnswers, prevBasicAnswers, question.answers, newBasicAnswers)) { + return state; + } + } else { + if (this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers)) { + return state; + } + } + + // Answers have changed. Now check if the response is complete and calculate the new state. + let complete: number, + newState: string; + if (isCompleteFn) { + // Pass all the answers since some behaviours might need the extra data. + complete = isCompleteFn(question, question.answers); + } else { + // Only pass the basic answers since questions should be independent of extra data. + complete = this.questionDelegate.isCompleteResponse(question, newBasicAnswers); + } + + if (complete < 0) { + newState = 'unknown'; + } else if (complete > 0) { + newState = 'complete'; + } else { + newState = 'todo'; + } + + return this.questionProvider.getState(newState); + }); + }); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled(): boolean | Promise { + return true; + } +} diff --git a/src/addon/qbehaviour/qbehaviour.module.ts b/src/addon/qbehaviour/qbehaviour.module.ts new file mode 100644 index 000000000..dbe1bf47c --- /dev/null +++ b/src/addon/qbehaviour/qbehaviour.module.ts @@ -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 { 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/app/app.module.ts b/src/app/app.module.ts index d54c324a9..fbcf159f6 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -86,6 +86,7 @@ import { AddonMessagesModule } from '@addon/messages/messages.module'; import { AddonNotesModule } from '../addon/notes/notes.module'; import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module'; import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module'; +import { AddonQbehaviourModule } from '@addon/qbehaviour/qbehaviour.module'; // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { @@ -172,7 +173,8 @@ export const CORE_PROVIDERS: any[] = [ AddonMessagesModule, AddonNotesModule, AddonPushNotificationsModule, - AddonRemoteThemesModule + AddonRemoteThemesModule, + AddonQbehaviourModule ], bootstrap: [IonicApp], entryComponents: [ diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts index 5216fb5fd..5ffaf0c0e 100644 --- a/src/core/question/providers/helper.ts +++ b/src/core/question/providers/helper.ts @@ -52,6 +52,68 @@ export class CoreQuestionHelperProvider { }); } + /** + * Extract question behaviour submit buttons from the question's HTML and add them to "behaviourButtons" property. + * The buttons aren't deleted from the content because all the im-controls block will be removed afterwards. + * + * @param {any} question Question to treat. + * @param {string} [selector] Selector to search the buttons. By default, '.im-controls input[type="submit"]'. + */ + extractQbehaviourButtons(question: any, selector?: string): void { + selector = selector || '.im-controls input[type="submit"]'; + + this.div.innerHTML = question.html; + + // Search the buttons. + const buttons = Array.from(this.div.querySelectorAll(selector)); + buttons.forEach((button) => { + this.addBehaviourButton(question, button); + }); + + question.html = this.div.innerHTML; + } + + /** + * Check if the question has CBM and, if so, extract the certainty options and add them to a new + * "behaviourCertaintyOptions" property. + * The value of the selected option is stored in question.behaviourCertaintySelected. + * We don't remove them from HTML because the whole im-controls block will be removed afterwards. + * + * @param {any} question Question to treat. + * @return {boolean} Wether the certainty is found. + */ + extractQbehaviourCBM(question: any): boolean { + this.div.innerHTML = question.html; + + const labels = Array.from(this.div.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]')); + question.behaviourCertaintyOptions = []; + + labels.forEach((label) => { + // Search the radio button inside this certainty and add its data to the options array. + const input = label.querySelector('input[type="radio"]'); + if (input) { + question.behaviourCertaintyOptions.push({ + id: input.id, + name: input.name, + value: input.value, + text: this.textUtils.cleanTags(label.innerHTML), + disabled: input.disabled + }); + + if (input.checked) { + question.behaviourCertaintySelected = input.value; + } + } + }); + + // If we have a certainty value stored in local we'll use that one. + if (question.localAnswers && typeof question.localAnswers['-certainty'] != 'undefined') { + question.behaviourCertaintySelected = question.localAnswers['-certainty']; + } + + return labels.length > 0; + } + /** * Check if the question has a redo button and, if so, add it to "behaviourButtons" property * and remove it from the HTML. @@ -80,6 +142,33 @@ export class CoreQuestionHelperProvider { } } + /** + * Check if the question contains a "seen" input. + * If so, add the name and value to a "behaviourSeenInput" property and remove the input. + * + * @param {any} question Question to treat. + * @return {boolean} Whether the seen input is found. + */ + extractQbehaviourSeenInput(question: any): boolean { + this.div.innerHTML = question.html; + + // Search the "seen" input. + const seenInput = this.div.querySelector('input[type="hidden"][name*=seen]'); + if (seenInput) { + // Get the data and remove the input. + question.behaviourSeenInput = { + name: seenInput.name, + value: seenInput.value + }; + seenInput.parentElement.removeChild(seenInput); + question.html = this.div.innerHTML; + + return true; + } + + return false; + } + /** * Removes the comment from the question HTML code and adds it in a new "commentHtml" property. *