MOBILE-2389 qtype: Implement description and multichoices types
parent
2f172f77dd
commit
be573d240b
|
@ -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 { CoreQuestionDelegate } from '@core/question/providers/delegate';
|
||||
import { AddonQtypeCalculatedMultiHandler } from './providers/handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeCalculatedMultiHandler
|
||||
]
|
||||
})
|
||||
export class AddonQtypeCalculatedMultiModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeCalculatedMultiHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
||||
import { AddonQtypeMultichoiceHandler } from '@addon/qtype/multichoice/providers/handler';
|
||||
import { AddonQtypeMultichoiceComponent } from '@addon/qtype/multichoice/component/multichoice';
|
||||
|
||||
/**
|
||||
* Handler to support calculated multi question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeCalculatedMulti';
|
||||
type = 'qtype_calculatedmulti';
|
||||
|
||||
constructor(private multichoiceHandler: AddonQtypeMultichoiceHandler) { }
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the question.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param {Injector} injector Injector.
|
||||
* @param {any} question The question to render.
|
||||
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(injector: Injector, question: any): any | Promise<any> {
|
||||
// Calculated multi behaves like a multichoice, use the same component.
|
||||
return AddonQtypeMultichoiceComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
isCompleteResponse(question: any, answers: any): number {
|
||||
// This question type depends on multichoice.
|
||||
return this.multichoiceHandler.isCompleteResponseSingle(answers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
||||
* or whether it must be considered aborted.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {any} answers Object with the question answers (without prefix).
|
||||
* @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||
*/
|
||||
isGradableResponse(question: any, answers: any): number {
|
||||
// This question type depends on multichoice.
|
||||
return this.multichoiceHandler.isGradableResponseSingle(answers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two responses are the same.
|
||||
*
|
||||
* @param {any} question Question.
|
||||
* @param {any} prevAnswers Object with the previous question answers.
|
||||
* @param {any} newAnswers Object with the new question answers.
|
||||
* @return {boolean} Whether they're the same.
|
||||
*/
|
||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
||||
// This question type depends on multichoice.
|
||||
return this.multichoiceHandler.isSameResponseSingle(prevAnswers, newAnswers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<section ion-list *ngIf="question.text || question.text === ''">
|
||||
<!-- "Seen" hidden input -->
|
||||
<input *ngIf="question.seenInput" type="hidden" [name]="question.seenInput.name" [value]="question.seenInput.value" >
|
||||
<ion-item text-wrap class="item item-text-wrap">
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
|
||||
</ion-item>
|
||||
</section>
|
|
@ -0,0 +1,50 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
* Component to render a description question.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-qtype-description',
|
||||
templateUrl: 'description.html'
|
||||
})
|
||||
export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeDescriptionComponent', questionHelper, domUtils);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const questionDiv = this.initComponent();
|
||||
if (questionDiv) {
|
||||
// Get the "seen" hidden input.
|
||||
const input = <HTMLInputElement> questionDiv.querySelector('input[type="hidden"][name*=seen]');
|
||||
if (input) {
|
||||
this.question.seenInput = {
|
||||
name: input.name,
|
||||
value: input.value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { CoreQuestionDelegate } from '@core/question/providers/delegate';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonQtypeDescriptionHandler } from './providers/handler';
|
||||
import { AddonQtypeDescriptionComponent } from './component/description';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonQtypeDescriptionComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeDescriptionHandler
|
||||
],
|
||||
exports: [
|
||||
AddonQtypeDescriptionComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonQtypeDescriptionComponent
|
||||
]
|
||||
})
|
||||
export class AddonQtypeDescriptionModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeDescriptionHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
||||
import { AddonQtypeDescriptionComponent } from '../component/description';
|
||||
|
||||
/**
|
||||
* Handler to support description question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeDescriptionHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeDescription';
|
||||
type = 'qtype_description';
|
||||
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the behaviour to use for the question.
|
||||
* If the question should use the default behaviour you shouldn't implement this function.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {string} behaviour The default behaviour.
|
||||
* @return {string} The behaviour to use.
|
||||
*/
|
||||
getBehaviour(question: any, behaviour: string): string {
|
||||
return 'informationitem';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the question.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param {Injector} injector Injector.
|
||||
* @param {any} question The question to render.
|
||||
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(injector: Injector, question: any): any | Promise<any> {
|
||||
return AddonQtypeDescriptionComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if an offline sequencecheck is valid compared with the online one.
|
||||
* This function only needs to be implemented if a specific compare is required.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {string} offlineSequenceCheck Sequence check stored in offline.
|
||||
* @return {boolean} Whether sequencecheck is valid.
|
||||
*/
|
||||
validateSequenceCheck(question: any, offlineSequenceCheck: string): boolean {
|
||||
// Descriptions don't have any answer so we'll always treat them as valid.
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<section ion-list *ngIf="question.text || question.text === ''">
|
||||
<!-- Question text first. -->
|
||||
<ion-item text-wrap>
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
|
||||
<p *ngIf="question.prompt"><core-format-text [component]="component" [componentId]="componentId" [text]="question.prompt"></core-format-text></p>
|
||||
</ion-item>
|
||||
|
||||
<!-- Checkbox for multiple choice. -->
|
||||
<ng-container *ngIf="question.multi">
|
||||
<ion-checkbox text-wrap [name]="option.name" [ngModel]="option.checked" [disabled]="option.disabled" *ngFor="let option of question.options" [ngClass]="{'core-question-answer-correct': option.isCorrect === 1, 'core-question-answer-incorrect': option.isCorrect === 0}">
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="option.text"></core-format-text></p>
|
||||
<p *ngIf="option.feedback" class="core-question-feedback-container"><core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"></core-format-text></p>
|
||||
</ion-checkbox>
|
||||
</ng-container>
|
||||
|
||||
<!-- Radio buttons for single choice. -->
|
||||
<div *ngIf="!question.multi" radio-group [ngModel]="question.singleChoiceModel" [name]="question.optionsName">
|
||||
<ion-item text-wrap>
|
||||
<ion-label><core-format-text [component]="component" [componentId]="componentId" [text]="option.text"></core-format-text></ion-label>
|
||||
<ion-radio *ngFor="let option of question.options" [value]="option.value" [disabled]="option.disabled" [ngClass]='{"core-question-answer-correct": option.isCorrect === 1, "core-question-answer-incorrect": option.isCorrect === 0}'>
|
||||
<p *ngIf="option.feedback" class="core-question-feedback-container"><core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"></core-format-text></p>
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</div>
|
||||
</section>
|
|
@ -0,0 +1,40 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
* Component to render a multichoice question.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-qtype-multichoice',
|
||||
templateUrl: 'multichoice.html'
|
||||
})
|
||||
export class AddonQtypeMultichoiceComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, questionHelper: CoreQuestionHelperProvider, domUtils: CoreDomUtilsProvider) {
|
||||
super(logger, 'AddonQtypeMultichoiceComponent', questionHelper, domUtils);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.initMultichoiceComponent();
|
||||
}
|
||||
}
|
|
@ -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 { CoreQuestionDelegate } from '@core/question/providers/delegate';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonQtypeMultichoiceHandler } from './providers/handler';
|
||||
import { AddonQtypeMultichoiceComponent } from './component/multichoice';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonQtypeMultichoiceComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeMultichoiceHandler
|
||||
],
|
||||
exports: [
|
||||
AddonQtypeMultichoiceComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonQtypeMultichoiceComponent
|
||||
]
|
||||
})
|
||||
export class AddonQtypeMultichoiceModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeMultichoiceHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -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, Injector } from '@angular/core';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
||||
import { AddonQtypeMultichoiceComponent } from '../component/multichoice';
|
||||
|
||||
/**
|
||||
* Handler to support multichoice question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeMultichoice';
|
||||
type = 'qtype_multichoice';
|
||||
|
||||
constructor(private utils: CoreUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the question.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param {Injector} injector Injector.
|
||||
* @param {any} question The question to render.
|
||||
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(injector: Injector, question: any): any | Promise<any> {
|
||||
return AddonQtypeMultichoiceComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
isCompleteResponse(question: any, answers: any): number {
|
||||
let isSingle = true,
|
||||
isMultiComplete = false;
|
||||
|
||||
// To know if it's single or multi answer we need to search for answers with "choice" in the name.
|
||||
for (const name in answers) {
|
||||
if (name.indexOf('choice') != -1) {
|
||||
isSingle = false;
|
||||
if (answers[name]) {
|
||||
isMultiComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSingle) {
|
||||
// Single.
|
||||
return this.isCompleteResponseSingle(answers);
|
||||
} else {
|
||||
// Multi.
|
||||
return isMultiComplete ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a response is complete. Only for single answer.
|
||||
*
|
||||
* @param {any} question The question.uestion answers (without prefix).
|
||||
* @return {number} 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||
*/
|
||||
isCompleteResponseSingle(answers: any): number {
|
||||
return (answers['answer'] && answers['answer'] !== '') ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
||||
* or whether it must be considered aborted.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {any} answers Object with the question answers (without prefix).
|
||||
* @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||
*/
|
||||
isGradableResponse(question: any, answers: any): number {
|
||||
return this.isCompleteResponse(question, answers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
||||
* or whether it must be considered aborted. Only for single answer.
|
||||
*
|
||||
* @param {any} answers Object with the question answers (without prefix).
|
||||
* @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||
*/
|
||||
isGradableResponseSingle(answers: any): number {
|
||||
return this.isCompleteResponseSingle(answers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two responses are the same.
|
||||
*
|
||||
* @param {any} question Question.
|
||||
* @param {any} prevAnswers Object with the previous question answers.
|
||||
* @param {any} newAnswers Object with the new question answers.
|
||||
* @return {boolean} Whether they're the same.
|
||||
*/
|
||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
||||
let isSingle = true,
|
||||
isMultiSame = true;
|
||||
|
||||
// To know if it's single or multi answer we need to search for answers with "choice" in the name.
|
||||
for (const name in newAnswers) {
|
||||
if (name.indexOf('choice') != -1) {
|
||||
isSingle = false;
|
||||
if (!this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, name)) {
|
||||
isMultiSame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSingle) {
|
||||
return this.isSameResponseSingle(prevAnswers, newAnswers);
|
||||
} else {
|
||||
return isMultiSame ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two responses are the same. Only for single answer.
|
||||
*
|
||||
* @param {any} prevAnswers Object with the previous question answers.
|
||||
* @param {any} newAnswers Object with the new question answers.
|
||||
* @return {boolean} Whether they're the same.
|
||||
*/
|
||||
isSameResponseSingle(prevAnswers: any, newAnswers: any): boolean {
|
||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
||||
}
|
||||
}
|
|
@ -13,14 +13,22 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AddonQtypeCalculatedMultiModule } from './calculatedmulti/calculatedmulti.module';
|
||||
import { AddonQtypeDescriptionModule } from './description/description.module';
|
||||
import { AddonQtypeMultichoiceModule } from './multichoice/multichoice.module';
|
||||
import { AddonQtypeNumericalModule } from './numerical/numerical.module';
|
||||
import { AddonQtypeShortAnswerModule } from './shortanswer/shortanswer.module';
|
||||
import { AddonQtypeTrueFalseModule } from './truefalse/truefalse.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
AddonQtypeCalculatedMultiModule,
|
||||
AddonQtypeDescriptionModule,
|
||||
AddonQtypeMultichoiceModule,
|
||||
AddonQtypeNumericalModule,
|
||||
AddonQtypeShortAnswerModule
|
||||
AddonQtypeShortAnswerModule,
|
||||
AddonQtypeTrueFalseModule
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
|
||||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
||||
import { AddonQtypeMultichoiceComponent } from '@addon/qtype/multichoice/component/multichoice';
|
||||
|
||||
/**
|
||||
* Handler to support true/false question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeTrueFalse';
|
||||
type = 'qtype_truefalse';
|
||||
|
||||
constructor(private utils: CoreUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the question.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param {Injector} injector Injector.
|
||||
* @param {any} question The question to render.
|
||||
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(injector: Injector, question: any): any | Promise<any> {
|
||||
// True/false behaves like a multichoice, use the same component.
|
||||
return AddonQtypeMultichoiceComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
isCompleteResponse(question: any, answers: any): number {
|
||||
return answers['answer'] ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
||||
* or whether it must be considered aborted.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {any} answers Object with the question answers (without prefix).
|
||||
* @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||
*/
|
||||
isGradableResponse(question: any, answers: any): number {
|
||||
return this.isCompleteResponse(question, answers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two responses are the same.
|
||||
*
|
||||
* @param {any} question Question.
|
||||
* @param {any} prevAnswers Object with the previous question answers.
|
||||
* @param {any} newAnswers Object with the new question answers.
|
||||
* @return {boolean} Whether they're the same.
|
||||
*/
|
||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
||||
}
|
||||
}
|
|
@ -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 { CoreQuestionDelegate } from '@core/question/providers/delegate';
|
||||
import { AddonQtypeTrueFalseHandler } from './providers/handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeTrueFalseHandler
|
||||
]
|
||||
})
|
||||
export class AddonQtypeTrueFalseModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeTrueFalseHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -95,4 +95,95 @@ export class CoreQuestionBaseComponent {
|
|||
|
||||
return questionDiv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component with a multiple choice (checkbox) or single choice (radio).
|
||||
*
|
||||
* @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid.
|
||||
*/
|
||||
initMultichoiceComponent(): void | HTMLElement {
|
||||
const questionDiv = this.initComponent();
|
||||
|
||||
if (questionDiv) {
|
||||
// Create the model for radio buttons.
|
||||
this.question.singleChoiceModel = {};
|
||||
|
||||
// Get the prompt.
|
||||
this.question.prompt = this.domUtils.getContentsOfElement(questionDiv, '.prompt');
|
||||
|
||||
// Search radio buttons first (single choice).
|
||||
let options = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="radio"]'));
|
||||
if (!options || !options.length) {
|
||||
// Radio buttons not found, it should be a multi answer. Search for checkbox.
|
||||
this.question.multi = true;
|
||||
options = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="checkbox"]'));
|
||||
|
||||
if (!options || !options.length) {
|
||||
// No checkbox found either. Abort.
|
||||
this.logger.warn('Aborting because of no radio and checkbox found.', this.question.name);
|
||||
|
||||
return this.questionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
}
|
||||
|
||||
this.question.options = [];
|
||||
|
||||
for (const i in options) {
|
||||
const element = options[i],
|
||||
option: any = {
|
||||
id: element.id,
|
||||
name: element.name,
|
||||
value: element.value,
|
||||
checked: element.checked,
|
||||
disabled: element.disabled
|
||||
},
|
||||
parent = element.parentElement;
|
||||
|
||||
this.question.optionsName = option.name;
|
||||
|
||||
// Get the label with the question text.
|
||||
const label = questionDiv.querySelector('label[for="' + option.id + '"]');
|
||||
if (label) {
|
||||
option.text = label.innerHTML;
|
||||
|
||||
// Check that we were able to successfully extract options required data.
|
||||
if (typeof option.name != 'undefined' && typeof option.value != 'undefined' &&
|
||||
typeof option.text != 'undefined') {
|
||||
|
||||
if (element.checked) {
|
||||
// If the option is checked and it's a single choice we use the model to select the one.
|
||||
if (!this.question.multi) {
|
||||
this.question.singleChoiceModel = option.value;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
// Check if answer is correct.
|
||||
if (parent && parent.className.indexOf('incorrect') >= 0) {
|
||||
option.isCorrect = 0;
|
||||
} else if (parent && parent.className.indexOf('correct') >= 0) {
|
||||
option.isCorrect = 1;
|
||||
}
|
||||
|
||||
// Search the feedback.
|
||||
const feedback = parent.querySelector('.specificfeedback');
|
||||
if (feedback) {
|
||||
option.feedback = feedback.innerHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.question.options.push(option);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Something went wrong when extracting the questions data. Abort.
|
||||
this.logger.warn('Aborting because of an error parsing options.', this.question.name, option.name);
|
||||
|
||||
return this.questionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
}
|
||||
|
||||
return questionDiv;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue