MOBILE-2389 qtype: Implement description and multichoices types

main
Dani Palou 2018-03-19 12:13:45 +01:00
parent 2f172f77dd
commit be573d240b
14 changed files with 782 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: [
],

View File

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

View File

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

View File

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